Skill: makhatib/mcp-microsoft365 (version 1.0.0)

# Microsoft 365 MCP Server Full Microsoft 365 integration via Model Context Protocol (MCP). ## Features ### 📧 Mail (Outlook) - List, read, send, and search emails - Filter by folder (inbox, sent, drafts) - HTML email support ### 📅 Calendar - List and create events - Teams meeting integration - Check availability/free-busy ### 📁 OneDrive - Browse files and folders - Search files - Read file content ### ✅ Tasks (Microsoft To-Do) - List task lists - Create and manage tasks - Set importance and due dates ### 💬 Teams - List chats - Read and send messages ### 👥 Users - List organization users - Get user profiles ## Requirements - Node.js 18+ - Azure Entra ID App with Microsoft Graph permissions ## Setup ### 1. Create Azure Entra ID App 1. Go to [Azure Portal](https://portal.azure.com) 2. Navigate to **Microsoft Entra ID** → **App registrations** → **New registration** 3. Configure: - Name: `MCP-Microsoft365` - Supported account types: Single tenant (recommended) - Redirect URI: `http://localhost:3000/callback` ### 2. Add API Permissions Add these **Application permissions** for Microsoft Graph: ``` Mail.Read, Mail.Send, Mail.ReadWrite Calendars.Read, Calendars.ReadWrite Files.Read.All, Files.ReadWrite.All Tasks.Read.All, Tasks.ReadWrite.All Chat.Read.All, Chat.ReadWrite.All User.Read.All ``` **Important:** Click "Grant admin consent" ### 3. Get Credentials Save these values: - Application (client) ID - Directory (tenant) ID - Client Secret (create under Certificates & secrets) ### 4. Install ```bash # Clone/download the skill cd mcp-microsoft365 # Install dependencies npm install # Build npm run build ``` ### 5. Configure mcporter ```bash mcporter config add m365 --stdio "node /path/to/mcp-microsoft365/dist/index.js" ``` Edit `config/mcporter.json` to add environment variables: ```json { "mcpServers": { "m365": { "command": "node /path/to/dist/index.js", "env": { "TENANT_ID": "your-tenant-id", "CLIENT_ID": "your-client-id", "CLIENT_SECRET": "your-client-secret", "DEFAULT_USER": "[REDACTED_SECRET]" } } } } ``` ## Usage ### Email ```bash # List recent emails mcporter call m365.m365_mail_list top:5 # Send email mcporter call m365.m365_mail_send to:"[REDACTED_SECRET]" subject:"Hello" body:"<p>Hi!</p>" # Search mcporter call m365.m365_mail_search query:"important" ``` ### Calendar ```bash # List events mcporter call m365.m365_calendar_list top:10 # Create event with Teams meeting mcporter call m365.m365_calendar_create subject:"Team Sync" start:"2026-01-27T10:00:00" end:"2026-01-27T11:00:00" isOnline:true ``` ### Files ```bash # List OneDrive root mcporter call m365.m365_files_list # Search files mcporter call m365.m365_files_search query:"report" ``` ### Tasks ```bash # List task lists mcporter call m365.m365_tasks_lists ``` ### Teams ```bash # List chats mcporter call m365.m365_teams_chats top:10 ``` ## 19 Available Tools | Tool | Description | |------|-------------| | `m365_mail_list` | List emails | | `m365_mail_read` | Read email by ID | | `m365_mail_send` | Send email | | `m365_mail_search` | Search emails | | `m365_calendar_list` | List events | | `m365_calendar_create` | Create event | | `m365_calendar_availability` | Check free/busy | | `m365_files_list` | List files | | `m365_files_search` | Search files | | `m365_files_read` | Read file content | | `m365_files_info` | Get file metadata | | `m365_tasks_lists` | List task lists | | `m365_tasks_list` | List tasks | | `m365_tasks_create` | Create task | | `m365_teams_chats` | List chats | | `m365_teams_messages` | Read messages | | `m365_teams_send` | Send message | | `m365_users_list` | List users | | `m365_user_info` | Get user profile | ## Author **Mahmoud Alkhatib** - Website: [malkhatib.com](https://malkhatib.com) - YouTube: [@malkhatib](https://youtube.com/@malkhatib) - Twitter: [@malkhateeb](https://twitter.com/malkhateeb) ## License MIT

path: src/index.ts
content: #!/usr/bin/env node
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
  CallToolRequestSchema,
  ListToolsRequestSchema,
  Tool,
} from "@modelcontextprotocol/sdk/types.js";
import { config } from "dotenv";

// Load environment variables
config();

const TENANT_ID = process.env.TENANT_ID!;
const CLIENT_ID = process.env.CLIENT_ID!;
const CLIENT_SECRET = process.env.CLIENT_SECRET!;
const DEFAULT_USER = process.env.DEFAULT_USER || "";

const GRAPH_BASE = "https://graph.microsoft.com/v1.0";

// Token cache
let tokenCache: { [REDACTED_SECRET]; expires: number } | null = null;

async function getAccessToken(): Promise<string> {
  if (tokenCache && tokenCache.expires > Date.now()) {
    return tokenCache.token;
  }

  const response = await fetch(
    `https://login.microsoftonline.com/${TENANT_ID}/oauth2/v2.0/token`,
    {
      method: "POST",
      headers: { "Content-Type": "application/x-www-form-urlencoded" },
      body: new URLSearchParams({
        client_id: CLIENT_ID,
        client_secret: CLIENT_SECRET,
        scope: "https://graph.microsoft.com/.default",
        grant_type: "client_credentials",
      }),
    }
  );

  const data = await response.json();
  if (data.error) {
    throw new Error(`Auth error: ${data.error_description}`);
  }

  tokenCache = {
    [REDACTED_SECRET],
    expires: Date.now() + (data.expires_in - 60) * 1000,
  };

  return data.access_token;
}

async function graphRequest(
  endpoint: string,
  method: string = "GET",
  body?: any
): Promise<any> {
  const token = await getAccessToken();
  const options: RequestInit = {
    method,
    headers: {
      Authorization: `Bearer ${token}`,
      "Content-Type": "application/json",
    },
  };

  if (body) {
    options.body = JSON.stringify(body);
  }

  const response = await fetch(`${GRAPH_BASE}${endpoint}`, options);
  
  if (response.status === 204) {
    return { success: true };
  }
  
  const data = await response.json();
  
  if (data.error) {
    throw new Error(`Graph API error: ${data.error.message}`);
  }
  
  return data;
}

// Define all tools
const tools: Tool[] = [
  // === MAIL TOOLS ===
  {
    name: "m365_mail_list",
    description: "List emails from a user's mailbox",
    inputSchema: {
      type: "object",
      properties: {
        user: { type: "string", description: "User email (default: configured user)" },
        folder: { type: "string", description: "Folder name (inbox, sentitems, drafts)", default: "inbox" },
        top: { type: "number", description: "Number of emails to return", default: 10 },
        filter: { type: "string", description: "OData filter (e.g., 'isRead eq false')" },
      },
    },
  },
  {
    name: "m365_mail_read",
    description: "Read a specific email by ID",
    inputSchema: {
      type: "object",
      properties: {
        user: { type: "string", description: "User email" },
        messageId: { type: "string", description: "Message ID" },
      },
      required: ["messageId"],
    },
  },
  {
    name: "m365_mail_send",
    description: "Send an email",
    inputSchema: {
      type: "object",
      properties: {
        user: { type: "string", description: "Sender email (must have SendAs permission)" },
        to: { type: "string", description: "Recipient email(s), comma-separated" },
        subject: { type: "string", description: "Email subject" },
        body: { type: "string", description: "Email body (HTML supported)" },
        cc: { type: "string", description: "CC recipients, comma-separated" },
      },
      required: ["to", "subject", "body"],
    },
  },
  {
    name: "m365_mail_search",
    description: "Search emails",
    inputSchema: {
      type: "object",
      properties: {
        user: { type: "string", description: "User email" },
        query: { type: "string", description: "Search query" },
        top: { type: "number", description: "Number of results", default: 10 },
      },
      required: ["query"],
    },
  },

  // === CALENDAR TOOLS ===
  {
    name: "m365_calendar_list",
    description: "List calendar events",
    inputSchema: {
      type: "object",
      properties: {
        user: { type: "string", description: "User email" },
        startDateTime: { type: "string", description: "Start date (ISO format)" },
        endDateTime: { type: "string", description: "End date (ISO format)" },
        top: { type: "number", description: "Number of events", default: 10 },
      },
    },
  },
  {
    name: "m365_calendar_create",
    description: "Create a calendar event",
    inputSchema: {
      type: "object",
      properties: {
        user: { type: "string", description: "User email" },
        subject: { type: "string", description: "Event subject" },
        start: { type: "string", description: "Start datetime (ISO format)" },
        end: { type: "string", description: "End datetime (ISO format)" },
        body: { type: "string", description: "Event description" },
        location: { type: "string", description: "Event location" },
        attendees: { type: "string", description: "Attendee emails, comma-separated" },
        isOnline: { type: "boolean", description: "Create Teams meeting", default: false },
      },
      required: ["subject", "start", "end"],
    },
  },
  {
    name: "m365_calendar_availability",
    description: "Check user availability / free-busy status",
    inputSchema: {
      type: "object",
      properties: {
        users: { type: "string", description: "User emails to check, comma-separated" },
        startDateTime: { type: "string", description: "Start datetime (ISO format)" },
        endDateTime: { type: "string", description: "End datetime (ISO format)" },
      },
      required: ["users", "startDateTime", "endDateTime"],
    },
  },

  // === ONEDRIVE TOOLS ===
  {
    name: "m365_files_list",
    description: "List files and folders in OneDrive",
    inputSchema: {
      type: "object",
      properties: {
        user: { type: "string", description: "User email" },
        path: { type: "string", description: "Folder path (empty for root)", default: "" },
        top: { type: "number", description: "Number of items", default: 20 },
      },
    },
  },
  {
    name: "m365_files_search",
    description: "Search for files in OneDrive",
    inputSchema: {
      type: "object",
      properties: {
        user: { type: "string", description: "User email" },
        query: { type: "string", description: "Search query" },
        top: { type: "number", description: "Number of results", default: 10 },
      },
      required: ["query"],
    },
  },
  {
    name: "m365_files_read",
    description: "Read file content (text files only)",
    inputSchema: {
      type: "object",
      properties: {
        user: { type: "string", description: "User email" },
        itemId: { type: "string", description: "File item ID" },
      },
      required: ["itemId"],
    },
  },
  {
    name: "m365_files_info",
    description: "Get file/folder metadata",
    inputSchema: {
      type: "object",
      properties: {
        user: { type: "string", description: "User email" },
        itemId: { type: "string", description: "Item ID" },
      },
      required: ["itemId"],
    },
  },

  // === TASKS TOOLS ===
  {
    name: "m365_tasks_lists",
    description: "List all task lists (To-Do)",
    inputSchema: {
      type: "object",
      properties: {
        user: { type: "string", description: "User email" },
      },
    },
  },
  {
    name: "m365_tasks_list",
    description: "List tasks in a task list",
    inputSchema: {
      type: "object",
      properties: {
        user: { type: "string", description: "User email" },
        listId: { type: "string", description: "Task list ID (use m365_tasks_lists to get)" },
        top: { type: "number", description: "Number of tasks", default: 20 },
      },
      required: ["listId"],
    },
  },
  {
    name: "m365_tasks_create",
    description: "Create a new task",
    inputSchema: {
      type: "object",
      properties: {
        user: { type: "string", description: "User email" },
        listId: { type: "string", description: "Task list ID" },
        title: { type: "string", description: "Task title" },
        body: { type: "string", description: "Task description" },
        dueDateTime: { type: "string", description: "Due date (ISO format)" },
        importance: { type: "string", description: "low, normal, or high", default: "normal" },
      },
      required: ["listId", "title"],
    },
  },

  // === TEAMS TOOLS ===
  {
    name: "m365_teams_chats",
    description: "List user's Teams chats",
    inputSchema: {
      type: "object",
      properties: {
        user: { type: "string", description: "User email" },
        top: { type: "number", description: "Number of chats", default: 20 },
      },
    },
  },
  {
    name: "m365_teams_messages",
    description: "Get messages from a Teams chat",
    inputSchema: {
      type: "object",
      properties: {
        user: { type: "string", description: "User email" },
        chatId: { type: "string", description: "Chat ID" },
        top: { type: "number", description: "Number of messages", default: 20 },
      },
      required: ["chatId"],
    },
  },
  {
    name: "m365_teams_send",
    description: "Send a message to a Teams chat",
    inputSchema: {
      type: "object",
      properties: {
        user: { type: "string", description: "User email" },
        chatId: { type: "string", description: "Chat ID" },
        message: { type: "string", description: "Message content" },
      },
      required: ["chatId", "message"],
    },
  },

  // === USER TOOLS ===
  {
    name: "m365_users_list",
    description: "List organization users",
    inputSchema: {
      type: "object",
      properties: {
        top: { type: "number", description: "Number of users", default: 20 },
        filter: { type: "string", description: "OData filter" },
      },
    },
  },
  {
    name: "m365_user_info",
    description: "Get user profile information",
    inputSchema: {
      type: "object",
      properties: {
        user: { type: "string", description: "User email or ID" },
      },
      required: ["user"],
    },
  },
];

// Tool handler implementations
async function handleTool(name: string, args: any): Promise<string> {
  const user = args.user || DEFAULT_USER;

  switch (name) {
    // === MAIL ===
    case "m365_mail_list": {
      const folder = args.folder || "inbox";
      const top = args.top || 10;
      let endpoint = `/users/${user}/mailFolders/${folder}/messages?$top=${top}&$select=id,subject,from,receivedDateTime,isRead,bodyPreview`;
      if (args.filter) endpoint += `&$filter=${encodeURIComponent(args.filter)}`;
      const data = await graphRequest(endpoint);
      return JSON.stringify(data.value.map((m: any) => ({
        id: m.id,
        subject: m.subject,
        from: m.from?.emailAddress?.address,
        received: m.receivedDateTime,
        isRead: m.isRead,
        preview: m.bodyPreview?.substring(0, 200),
      })), null, 2);
    }

    case "m365_mail_read": {
      const data = await graphRequest(`/users/${user}/messages/${args.messageId}`);
      return JSON.stringify({
        id: data.id,
        subject: data.subject,
        from: data.from?.emailAddress,
        to: data.toRecipients?.map((r: any) => r.emailAddress),
        received: data.receivedDateTime,
        body: data.body?.content,
      }, null, 2);
    }

    case "m365_mail_send": {
      const toRecipients = args.to.split(",").map((email: string) => ({
        emailAddress: { address: email.trim() },
      }));
      const message: any = {
        subject: args.subject,
        body: { contentType: "HTML", content: args.body },
        toRecipients,
      };
      if (args.cc) {
        message.ccRecipients = args.cc.split(",").map((email: string) => ({
          emailAddress: { address: email.trim() },
        }));
      }
      await graphRequest(`/users/${user}/sendMail`, "POST", { message });
      return JSON.stringify({ success: true, message: "Email sent successfully" });
    }

    case "m365_mail_search": {
      const data = await graphRequest(
        `/users/${user}/messages?$search="${encodeURIComponent(args.query)}"&$top=${args.top || 10}&$select=id,subject,from,receivedDateTime,bodyPreview`
      );
      return JSON.stringify(data.value, null, 2);
    }

    // === CALENDAR ===
    case "m365_calendar_list": {
      let endpoint = `/users/${user}/events?$top=${args.top || 10}&$select=id,subject,start,end,location,isOnlineMeeting,onlineMeetingUrl&$orderby=start/dateTime`;
      if (args.startDateTime && args.endDateTime) {
        endpoint = `/users/${user}/calendarView?startDateTime=${args.startDateTime}&endDateTime=${args.endDateTime}&$top=${args.top || 10}&$select=id,subject,start,end,location,isOnlineMeeting`;
      }
      const data = await graphRequest(endpoint);
      return JSON.stringify(data.value.map((e: any) => ({
        id: e.id,
        subject: e.subject,
        start: e.start,
        end: e.end,
        location: e.location?.displayName,
        isOnline: e.isOnlineMeeting,
        meetingUrl: e.onlineMeetingUrl,
      })), null, 2);
    }

    case "m365_calendar_create": {
      const event: any = {
        subject: args.subject,
        start: { dateTime: args.start, timeZone: "UTC" },
        end: { dateTime: args.end, timeZone: "UTC" },
      };
      if (args.body) event.body = { contentType: "HTML", content: args.body };
      if (args.location) event.location = { displayName: args.location };
      if (args.attendees) {
        event.attendees = args.attendees.split(",").map((email: string) => ({
          emailAddress: { address: email.trim() },
          type: "required",
        }));
      }
      if (args.isOnline) event.isOnlineMeeting = true;
      
      const data = await graphRequest(`/users/${user}/events`, "POST", event);
      return JSON.stringify({
        id: data.id,
        subject: data.subject,
        webLink: data.webLink,
        onlineMeetingUrl: data.onlineMeetingUrl,
      }, null, 2);
    }

    case "m365_calendar_availability": {
      const schedules = args.users.split(",").map((u: string) => u.trim());
      const data = await graphRequest(`/users/${user}/calendar/getSchedule`, "POST", {
        schedules,
        startTime: { dateTime: args.startDateTime, timeZone: "UTC" },
        endTime: { dateTime: args.endDateTime, timeZone: "UTC" },
      });
      return JSON.stringify(data.value, null, 2);
    }

    // === ONEDRIVE ===
    case "m365_files_list": {
      const path = args.path ? `:/${args.path}:` : "";
      const endpoint = `/users/${user}/drive/root${path}/children?$top=${args.top || 20}&$select=id,name,size,folder,file,webUrl,lastModifiedDateTime`;
      const data = await graphRequest(endpoint);
      return JSON.stringify(data.value.map((f: any) => ({
        id: f.id,
        name: f.name,
        size: f.size,
        isFolder: !!f.folder,
        mimeType: f.file?.mimeType,
        webUrl: f.webUrl,
        modified: f.lastModifiedDateTime,
      })), null, 2);
    }

    case "m365_files_search": {
      const data = await graphRequest(`/users/${user}/drive/root/search(q='${encodeURIComponent(args.query)}')?$top=${args.top || 10}`);
      return JSON.stringify(data.value.map((f: any) => ({
        id: f.id,
        name: f.name,
        path: f.parentReference?.path,
        webUrl: f.webUrl,
      })), null, 2);
    }

    case "m365_files_read": {
      const token = await getAccessToken();
      const metaResponse = await graphRequest(`/users/${user}/drive/items/${args.itemId}`);
      const downloadUrl = metaResponse["@microsoft.graph.downloadUrl"];
      
      const contentResponse = await fetch(downloadUrl);
      const content = await contentResponse.text();
      return content.substring(0, 50000); // Limit content size
    }

    case "m365_files_info": {
      const data = await graphRequest(`/users/${user}/drive/items/${args.itemId}`);
      return JSON.stringify(data, null, 2);
    }

    // === TASKS ===
    case "m365_tasks_lists": {
      const data = await graphRequest(`/users/${user}/todo/lists`);
      return JSON.stringify(data.value.map((l: any) => ({
        id: l.id,
        displayName: l.displayName,
        isOwner: l.isOwner,
      })), null, 2);
    }

    case "m365_tasks_list": {
      const listId = encodeURIComponent(args.listId);
      const data = await graphRequest(`/users/${user}/todo/lists/${listId}/tasks?$top=${args.top || 20}`);
      return JSON.stringify(data.value.map((t: any) => ({
        id: t.id,
        title: t.title,
        status: t.status,
        importance: t.importance,
        dueDateTime: t.dueDateTime,
        body: t.body?.content,
      })), null, 2);
    }

    case "m365_tasks_create": {
      const listId = encodeURIComponent(args.listId);
      const task: any = {
        title: args.title,
        importance: args.importance || "normal",
      };
      if (args.body) task.body = { contentType: "text", content: args.body };
      if (args.dueDateTime) task.dueDateTime = { dateTime: args.dueDateTime, timeZone: "UTC" };
      
      const data = await graphRequest(`/users/${user}/todo/lists/${listId}/tasks`, "POST", task);
      return JSON.stringify({ id: data.id, title: data.title, status: data.status }, null, 2);
    }

    // === TEAMS ===
    case "m365_teams_chats": {
      const data = await graphRequest(`/users/${user}/chats?$top=${args.top || 20}&$expand=members`);
      return JSON.stringify(data.value.map((c: any) => ({
        id: c.id,
        topic: c.topic,
        chatType: c.chatType,
        members: c.members?.map((m: any) => m.displayName),
      })), null, 2);
    }

    case "m365_teams_messages": {
      const data = await graphRequest(`/users/${user}/chats/${args.chatId}/messages?$top=${args.top || 20}`);
      return JSON.stringify(data.value.map((m: any) => ({
        id: m.id,
        from: m.from?.user?.displayName,
        content: m.body?.content,
        createdDateTime: m.createdDateTime,
      })), null, 2);
    }

    case "m365_teams_send": {
      const data = await graphRequest(`/users/${user}/chats/${args.chatId}/messages`, "POST", {
        body: { content: args.message },
      });
      return JSON.stringify({ id: data.id, success: true }, null, 2);
    }

    // === USERS ===
    case "m365_users_list": {
      let endpoint = `/users?$top=${args.top || 20}&$select=id,displayName,mail,userPrincipalName,jobTitle`;
      if (args.filter) endpoint += `&$filter=${encodeURIComponent(args.filter)}`;
      const data = await graphRequest(endpoint);
      return JSON.stringify(data.value, null, 2);
    }

    case "m365_user_info": {
      const data = await graphRequest(`/users/${args.user}?$select=id,displayName,mail,userPrincipalName,jobTitle,department,officeLocation,mobilePhone`);
      return JSON.stringify(data, null, 2);
    }

    default:
      throw new Error(`Unknown tool: ${name}`);
  }
}

// Create and run the MCP server
const server = new Server(
  { name: "mcp-microsoft365", version: "1.0.0" },
  { capabilities: { tools: {} } }
);

server.setRequestHandler(ListToolsRequestSchema, async () => ({
  tools,
}));

server.setRequestHandler(CallToolRequestSchema, async (request) => {
  try {
    const result = await handleTool(request.params.name, request.params.arguments || {});
    return {
      content: [{ type: "text", text: result }],
    };
  } catch (error: any) {
    return {
      content: [{ type: "text", text: `Error: ${error.message}` }],
      isError: true,
    };
  }
});

async function main() {
  const transport = new StdioServerTransport();
  await server.connect(transport);
  console.error("MCP Microsoft 365 Server running on stdio");
}

main().catch(console.error);


path: tsconfig.json
content: {
  "compilerOptions": {
    "target": "ES2022",
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "outDir": "./dist",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "resolveJsonModule": true,
    "declaration": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "dist"]
}


path: README.md
content: # MCP Microsoft 365 Server

An MCP (Model Context Protocol) server for Microsoft 365 integration.

## Features

### 📧 Mail (Outlook)
- `m365_mail_list` - List emails from inbox/folders
- `m365_mail_read` - Read specific email
- `m365_mail_send` - Send emails
- `m365_mail_search` - Search emails

### 📅 Calendar
- `m365_calendar_list` - List events
- `m365_calendar_create` - Create events (with Teams meeting support)
- `m365_calendar_availability` - Check free/busy status

### 📁 OneDrive
- `m365_files_list` - List files/folders
- `m365_files_search` - Search files
- `m365_files_read` - Read file content
- `m365_files_info` - Get file metadata

### ✅ Tasks (To-Do)
- `m365_tasks_lists` - List task lists
- `m365_tasks_list` - List tasks in a list
- `m365_tasks_create` - Create new task

### 💬 Teams
- `m365_teams_chats` - List chats
- `m365_teams_messages` - Read chat messages
- `m365_teams_send` - Send message to chat

### 👥 Users
- `m365_users_list` - List organization users
- `m365_user_info` - Get user profile

## Setup

### 1. Azure Entra ID App

Create an app in [Azure Portal](https://portal.azure.com):
- Microsoft Entra ID → App registrations → New registration
- Add Application permissions for Microsoft Graph:
  - `Mail.Read`, `Mail.Send`, `Mail.ReadWrite`
  - `Calendars.Read`, `Calendars.ReadWrite`
  - `Files.Read.All`, `Files.ReadWrite.All`
  - `Tasks.Read.All`, `Tasks.ReadWrite.All`
  - `Chat.Read.All`, `Chat.ReadWrite.All`
  - `User.Read.All`
- Grant admin consent

### 2. Configure

Create `.env` file:
```env
TENANT_ID=your-tenant-id
CLIENT_ID=your-client-id
CLIENT_SECRET=your-client-secret
DEFAULT_USER=[REDACTED_SECRET]
```

### 3. Install & Build

```bash
npm install
npm run build
```

### 4. Run

```bash
npm start
# or
node dist/index.js
```

### 5. Use with mcporter

```bash
mcporter config add m365 --stdio "node /path/to/dist/index.js"
mcporter call m365.m365_mail_list top:5
```

## Usage Examples

```bash
# List emails
mcporter call m365.m365_mail_list top:5

# Send email
mcporter call m365.m365_mail_send to:[REDACTED_SECRET] subject:"Hello" body:"<p>Hi there!</p>"

# List calendar events
mcporter call m365.m365_calendar_list top:10

# Create event with Teams meeting
mcporter call m365.m365_calendar_create subject:"Meeting" start:"2024-01-30T10:00:00" end:"2024-01-30T11:00:00" isOnline:true

# List OneDrive files
mcporter call m365.m365_files_list path:"Documents"

# Create task
mcporter call m365.m365_tasks_create listId:"xxx" title:"New task" importance:"high"
```

## Author

Mahmoud Alkhatib - [malkhatib.com](https://malkhatib.com)

## License

MIT


path: package.json
content: {
  "name": "mcp-microsoft365",
  "version": "1.0.0",
  "description": "MCP Server for Microsoft 365 (Outlook, Calendar, OneDrive, Tasks, Teams)",
  "main": "dist/index.js",
  "type": "module",
  "scripts": {
    "build": "tsc",
    "start": "node dist/index.js",
    "dev": "tsx src/index.ts"
  },
  "keywords": ["mcp", "microsoft365", "outlook", "teams", "onedrive"],
  "author": "Mahmoud Alkhatib",
  "license": "MIT",
  "dependencies": {
    "@modelcontextprotocol/sdk": "^1.0.0",
    "dotenv": "^16.3.1"
  },
  "devDependencies": {
    "@types/node": "^20.10.0",
    "tsx": "^4.7.0",
    "typescript": "^5.3.0"
  }
}


Security scan verdicts — ClawScan: suspicious | static analysis: suspicious | VirusTotal: suspicious | SkillSpector: suspicious (CRITICAL)