npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@teamnetwork-nz/m365-mcp-server

v1.7.2

Published

MCP server for Microsoft 365 — Email, Calendar, To Do, SharePoint document libraries, and Teams — using client credentials (app-only auth). Supports multiple accounts, shared mailboxes, per-mailbox read-only access, and grant-based SharePoint access contr

Downloads

794

Readme

@teamnetwork-nz/m365-mcp-server

Streamable HTTP

Set M365_TRANSPORT=http to serve stateless MCP at /mcp. It binds to 127.0.0.1 by default; remote binding requires M365_HTTP_BEARER_TOKEN and M365_HTTP_ALLOWED_HOSTS. Optional controls include M365_HTTP_PORT, M365_HTTP_ALLOWED_ORIGINS, and M365_HTTP_MAX_BODY_BYTES; /health is available for health checks.

A Model Context Protocol (MCP) server for Microsoft 365 — Email, Calendar, To Do, and SharePoint document libraries — using app-only (client credentials) authentication — no user login flows required.

Supports multiple accounts, shared mailboxes, per-mailbox read-only access control, folder navigation, date/content filtering, and grant-based SharePoint access control.


Prerequisites

  • Node.js 18+
  • A Microsoft 365 tenant with an Azure AD app registration configured for app-only access (see Admin Setup below)

Installation

npm install -g @teamnetwork-nz/m365-mcp-server

Or run directly without installing:

npx @teamnetwork-nz/m365-mcp-server

Configuration

Create a JSON config file (e.g. m365-config.json) based on the example below. Keep this file secure — it contains your client secret.

{
  "connection": {
    "tenantId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
    "clientId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
    "clientSecret": "your-client-secret"
  },
  "accounts": [
    {
      "id": "main",
      "displayName": "Main Organisation",
      "mailboxes": [
        { "email": "[email protected]", "readonly": false },
        { "email": "[email protected]", "readonly": true, "displayName": "Shared (read-only)" }
      ]
    },
    {
      "id": "partner",
      "displayName": "Partner Tenant",
      "tenantId": "yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy",
      "clientId": "yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy",
      "clientSecret": "partner-secret",
      "mailboxes": [
        { "email": "[email protected]", "readonly": false }
      ]
    }
  ]
}

Config notes

  • The top-level connection block provides default credentials for all accounts.
  • Each account can override tenantId, clientId, and/or clientSecret individually — useful for multi-tenant setups.
  • readonly: true mailboxes block all write operations (send, reply, move, create/update events) at the server level.

Set the path to your config via the M365_CONFIG_PATH environment variable:

export M365_CONFIG_PATH=/path/to/m365-config.json

Datetime & Timezone Handling

All datetime inputs and outputs are automatically converted to and from your local timezone — you never need to think about UTC.

Input

Supply datetimes in your local time using ISO 8601 format. A timezone offset is optional:

2024-01-15T09:00:00          # naive — interpreted as your configured local timezone
2024-01-15T09:00:00+13:00   # explicit offset — converted to the configured timezone

Output

All dates returned by the server (email receivedDateTime, event start/end, etc.) are expressed in the configured timezone with their UTC offset, e.g. 2024-01-15T09:00:00+13:00.

Configuration

Set DATETIME_FORMAT in your environment:

| Value | Behaviour | |---|---| | local (default) | Use the server process's system local timezone | | Any IANA timezone name | Use that timezone, e.g. Pacific/Auckland, America/New_York | | UTC | No conversion — all datetimes are passed through in UTC as-is |

export DATETIME_FORMAT=Pacific/Auckland

Or in your Claude Desktop MCP config:

{
  "mcpServers": {
    "m365": {
      "command": "npx",
      "args": ["@teamnetwork-nz/m365-mcp-server"],
      "env": {
        "M365_CONFIG_PATH": "/path/to/m365-config.json",
        "DATETIME_FORMAT": "Pacific/Auckland"
      }
    }
  }
}

Claude Desktop / Claude Code Setup

Add the following to your Claude Desktop config (claude_desktop_config.json):

{
  "mcpServers": {
    "m365": {
      "command": "npx",
      "args": ["@teamnetwork-nz/m365-mcp-server"],
      "env": {
        "M365_CONFIG_PATH": "/path/to/m365-config.json"
      }
    }
  }
}

If installed globally, you can use the m365-mcp command instead:

{
  "mcpServers": {
    "m365": {
      "command": "m365-mcp",
      "env": {
        "M365_CONFIG_PATH": "/path/to/m365-config.json"
      }
    }
  }
}

Default Mailbox Behaviour

All email and calendar tools accept optional accountId and mailbox parameters. When either is omitted:

  • accountId defaults to the first account in the config file.
  • mailbox defaults to the first mailbox listed under the resolved account.

Config file ordering is the source of truth — put your primary account and mailbox first. Use list_mailboxes to see which account and mailbox are marked isDefault: true.

Operations always target exactly one mailbox per request, either the default or a specific one provided by name.


Available Tools

Email

| Tool | Description | |---|---| | list_mailboxes | List configured accounts and mailboxes with read/write status | | list_folders | List mail folders (top-level or child folders) | | list_messages | Search one folder or the whole mailbox with composable date presets, subject/body, sender name/address, recipients, attachment filename, read state, importance, attachment and sensitivity filters. Supports compact/full body modes and nextPageToken pagination | | list_conversation_messages | Fetch a complete Outlook thread by the conversationId returned from list_messages | | get_message | Get full message details. Set includeBody, includeAttachments, or includeInternetMessageHeaders as needed | | send_message | Send an email (non-readonly mailboxes only) | | reply_message | Reply or reply-all to a message (non-readonly mailboxes only) | | move_message | Move a message to another folder (non-readonly mailboxes only) | | list_message_attachments | List all attachments on a message (file, item, and reference types) | | download_attachment | Download an attachment. Pass savePath to write decoded bytes directly beneath a configured attachmentDownloads.allowedRoots directory and receive its path, byte count, and SHA-256. Otherwise returns base64 inline; images use MCP image content |

Calendar

| Tool | Description | |---|---| | list_calendars | List calendars for a mailbox | | list_events | Search by subject, category, attendee, organizer, show-as, location, or response status. With a date window, recurring events expand into occurrences; without one, recurrence masters are returned | | get_schedule | Retrieve free/busy grids for one or more attendees before proposing a meeting time | | find_meeting_times | Ask Graph to rank available meeting slots for required attendees within a time window | | get_event | Get full event details | | create_event | Create a new event (non-readonly mailboxes only) | | update_event | Update an existing event (non-readonly mailboxes only) | | delete_event | Permanently delete an event or recurring series master (non-readonly mailboxes only) |

Agent search guidance

  • Start with relativeDate (for example last7days or thisMonth) and searchAllFolders: true when the folder is unknown. Use the per-call timeZone override when the request's locale differs from server configuration.
  • sender accepts a display-name or address substring. Combine it freely with subject, bodySearch, recipient, flag, and attachment filters. When Microsoft Graph cannot safely combine criteria, searchMetadata identifies the local filtering and candidate count.
  • Sender matching is local because Graph cannot reliably combine sender substring matching with the other filters. An empty or partial page does not prove that older matches do not exist: follow nextPageToken, especially for searches spanning several months.
  • Use bodyFormat: "none" for triage, "preview" for screening, and "text" or "html" only when full content is required. Use select to further minimize payloads.
  • Continue a search by passing the returned nextPageToken as pageToken. Treat the token as opaque and do not edit it.
  • Request conversationId (included automatically by the search tool) and call list_conversation_messages to inspect a thread. Use attachmentName to locate messages before calling the attachment tools.
  • Call get_schedule to inspect availability or find_meeting_times to get ranked candidate slots before creating a meeting. list_events already uses Graph calendarView, so recurring occurrences in the requested window are returned individually.
  • Microsoft Graph's ordinary mail API cannot access Exchange Online Archive/In-Place Archive mailboxes. searchAllFolders covers folders in the primary mailbox only; Outlook's ordinary Archive folder is included, but the separate Online Archive is not.
  • For get_message, use bodyFormat: "preserveRaw" when fidelity matters. text intentionally asks Graph to convert HTML to plain text for easier agent consumption.

To Do

| Tool | Description | |---|---| | list_todo_lists | List all To Do lists for a mailbox. The default list has wellknownListName: "defaultList" | | list_todo_tasks | List tasks in a To Do list with optional status and importance filters | | get_todo_task | Get full details of a specific task | | create_todo_task | Create a new task (non-readonly mailboxes only) | | update_todo_task | Update a task — title, body, status, importance, due date, reminder (non-readonly mailboxes only) | | delete_todo_task | Permanently delete a task (non-readonly mailboxes only) | | list_linked_resources | List all linked resources (external back-references) attached to a task | | get_linked_resource | Get a specific linked resource | | create_linked_resource | Attach a linked resource to a task — set externalId for reverse lookup, webUrl for a one-click link back to the source item | | update_linked_resource | Update a linked resource (non-readonly mailboxes only) | | delete_linked_resource | Remove a linked resource from a task (non-readonly mailboxes only) |

SharePoint

| Tool | Description | |---|---| | discover_sharepoint_sites | Search for SharePoint sites and their document library drives — returns the siteId/driveId values needed to add a library to config. Always available, no sharepoint config required | | list_sharepoint_libraries | List configured SharePoint libraries with their grant definitions | | list_drive_items | List files and folders at a path — always permitted, no grant required | | get_drive_item | Get metadata for a file or folder — always permitted, no grant required | | check_sharepoint_access | Check whether an operation is permitted by the grant config (no network call, never errors) — use before content operations | | download_file | Download a file as base64. Images returned as MCP image content; others as a JSON payload with fileName, mimeType, encoding, and data. Requires read grant | | upload_file | Upload a file (max 4 MB) via sourcePath (preferred — reads from disk) or inline content (contentEncoding: "utf8" or "base64", optionally checked against expectedBytes/sha256). Creates or overwrites. Requires create or update grant | | create_folder | Create a new folder. Requires create grant on the parent path | | delete_drive_item | Permanently delete a file or folder. Requires delete grant | | move_drive_item | Move a file or folder, optionally renaming. Requires update at source and create at destination | | search_drive | Full-text search within a library (only for libraries with searchAllowed: true). Only results passing all three grant axes (path + file + operation) are returned — everything else is silently dropped |

SharePoint grant model

Access to file content and write operations is controlled by grants defined per library. Each grant combines three axes — all three must match for access to be allowed:

| Axis | Description | |---|---| | allowedPaths | Which folder paths are in scope. [] = nothing; ["*"] = any path; ["/Finance"] = exact folder only; ["/Finance/*"] = folder and all descendants | | allowedFiles | Which filenames are allowed. [] = nothing; ["*"] = any file; ["*.pdf", "*.xlsx"] = glob patterns. If omitted, inherits globalAllowedFiles | | allowedOperations | Which operations are permitted: read, create, update, delete, or *. If omitted, inherits globalAllowedOperations |

globalAllowedFiles and globalAllowedOperations are library-level defaults inherited by grants that omit those fields. An explicit grant value (even []) always overrides the library default. The defaults are a convenience — they are not a ceiling; grants may expand beyond them.

list_drive_items and get_drive_item bypass grant evaluation entirely — always permitted.

"sharepoint": [
  {
    "id": "company-docs",
    "displayName": "Company Documents",
    "siteId": "contoso.sharepoint.com,aaa-guid,bbb-guid",
    "driveId": "b!abc123...",
    "searchAllowed": true,
    "globalAllowedOperations": ["read"],
    "globalAllowedFiles": ["*.pdf"],
    "grants": [
      {
        "name": "Finance spreadsheets and PDFs",
        "description": "Read and create Excel and PDF files in Finance and subfolders",
        "allowedPaths": ["/Finance", "/Finance/*"],
        "allowedFiles": ["*.xlsx", "*.pdf"],
        "allowedOperations": ["read", "create"]
      },
      {
        "name": "Reports PDFs (inherited defaults)",
        "description": "Read-only PDF access in /Reports — inherits globalAllowedFiles and globalAllowedOperations",
        "allowedPaths": ["/Reports", "/Reports/*"]
      }
    ]
  }
]

Teams

| Tool | Description | |---|---| | list_teams | List teams in the tenant (filtered to the configured allowedTeams scope) | | list_channels | List channels of a team | | list_team_members | List team members with roles | | list_call_records | List call records — participants, timing, modalities (metadata only, no content). Requires callRecordsAllowed: true | | get_call_record | Get a single call record, optionally with per-session network/quality detail | | list_channel_messages | List recent channel messages. Requires channelMessagesAllowed: true and Microsoft protected-API approval | | get_channel_message | Get a channel message, optionally with its reply thread. Same requirements | | list_chats | List a configured user's chats. Requires chatMessagesAllowed: true and Microsoft protected-API approval | | list_chat_messages | List messages in a chat. Same requirements; app-only chat reads may be metered by Microsoft |

Teams configuration

"teams": {
  "allowedTeams": ["*"],
  "callRecordsAllowed": true,
  "channelMessagesAllowed": false,
  "chatMessagesAllowed": false
}
  • Omit the teams block entirely to disable all Teams tools.
  • allowedTeams gates every team-scoped call: [] = none, ["*"] = all teams, or an explicit list of team IDs. list_teams silently omits teams outside the scope.
  • Message tools are only registered when their flag is true.
  • Chat access is restricted to mailboxes present in the config — other users in the tenant cannot be queried.

⚠️ Protected APIs — message content requires Microsoft approval

Reading channel or chat message content with application permissions uses Microsoft Graph protected APIs. Admin consent in the Azure portal is not sufficient — you must also apply to Microsoft:

  1. Submit the Microsoft Graph protected APIs request form with your app ID, publisher details, and data-use justification.
  2. Wait for approval (typically 1–3 weeks).
  3. Until approved, list_channel_messages, get_channel_message, list_chats, and list_chat_messages return 403 Forbidden from Graph even though the permissions appear consented.

Structure tools (list_teams, list_channels, list_team_members) and call records work with ordinary application permissions — no Microsoft approval needed.

Finding your siteId and driveId

Use the discover_sharepoint_sites tool — it needs only Sites.Read.All and works with no sharepoint config present, so it can be called before you've added a single library:

discover_sharepoint_sites { "search": "Finance" }

Returns each matching site's siteId (format hostname,site-guid,web-guid) along with its document library drives (driveId, name). Use "search": "*" to list every site the app can see — large tenants may see results truncated, in which case narrow with a keyword instead.

If you'd rather do it manually, the same data is available via the Azure Portal SharePoint admin centre or a direct Graph call:

GET https://graph.microsoft.com/v1.0/sites/{hostname}:/sites/{sitePath}
GET https://graph.microsoft.com/v1.0/sites/{siteId}/drives

Admin Setup

Before the server can access mailboxes, a tenant admin must complete the following steps.

1. Register an Azure AD Application

In the Azure Portal → App registrations → New registration:

  • Set a name (e.g. M365 MCP Server)
  • Note the Application (client) ID and Directory (tenant) ID
  • Under Certificates & secrets, create a client secret and note the value

Grant the following Application permissions (not Delegated) and click Grant admin consent:

| Permission | Required for | |---|---| | Mail.Read | Reading email | | Mail.ReadWrite | Moving messages | | Mail.Send | Sending and replying | | Calendars.Read | Reading calendar events | | Calendars.ReadWrite | Creating and updating events | | Tasks.ReadWrite.All | Reading and writing To Do lists and tasks | | Sites.Read.All | Reading SharePoint sites, drives, and file metadata | | Sites.ReadWrite.All | Uploading files, creating folders, deleting, and moving items in SharePoint | | Team.ReadBasic.All | Listing teams | | Channel.ReadBasic.All | Listing channels | | TeamMember.Read.All | Listing team members | | CallRecords.Read.All | Reading call records (metadata only) | | ChannelMessage.Read.All | Reading channel messages — protected API, requires Microsoft approval (see Teams section) | | Chat.Read.All | Reading chats and chat messages — protected API, requires Microsoft approval (see Teams section) |

2. Restrict Mailbox Access (Recommended)

By default, the above permissions grant access to all mailboxes in the tenant. Use an ApplicationAccessPolicy in Exchange Online to restrict access to only the mailboxes in your config.

Run the following in Exchange Online PowerShell:

# Create a mail-enabled security group and add the allowed mailboxes
New-DistributionGroup -Name "MCP-Allowed-Mailboxes" -Type Security
Add-DistributionGroupMember -Identity "MCP-Allowed-Mailboxes" -Member "[email protected]"
Add-DistributionGroupMember -Identity "MCP-Allowed-Mailboxes" -Member "[email protected]"

# Restrict the app to only those mailboxes
New-ApplicationAccessPolicy `
  -AppId "<your-client-id>" `
  -PolicyScopeGroupId "MCP-Allowed-Mailboxes" `
  -AccessRight RestrictAccess `
  -Description "Restrict MCP server to configured mailboxes"

# Verify (should return Granted for allowed mailboxes)
Test-ApplicationAccessPolicy -AppId "<your-client-id>" -Identity "[email protected]"

Note: Policy changes can take up to 1 hour to propagate. Shared mailboxes must be added to the group directly.


Security

  • Credentials are never logged. The server scrubs secrets, tokens, and email body content from all log output.
  • All Graph API calls use the /users/{email}/... path (application permissions), never /me/.
  • Write operations are blocked at the server level for any mailbox marked readonly: true in the config — this check happens before any network call is made.
  • The config file should be kept outside your project directory and never committed to source control. Use restrictive file permissions (chmod 600 on Linux/macOS).

License

MIT