Skip to content

feat: added m365 mcp server#44

Open
250519 wants to merge 3 commits into
mainfrom
feat/add-m365-mcp
Open

feat: added m365 mcp server#44
250519 wants to merge 3 commits into
mainfrom
feat/add-m365-mcp

Conversation

@250519

@250519 250519 commented Jun 3, 2026

Copy link
Copy Markdown

Note

High Risk
New externally reachable service that proxies bearer tokens to Microsoft Graph and exposes write-capable tools (mail send, file upload, Teams messages, SharePoint changes); misconfiguration or overly broad token scopes could cause real tenant impact.

Overview
Introduces a new m365-mcp package: a stateless Node.js MCP server that exposes Microsoft 365 (Mail, Calendar, Teams, OneDrive, SharePoint) as Graph-backed tools on Streamable HTTP at POST /mcp, with Authorization: Bearer required and the token forwarded to Graph without storage or exchange.

The runtime builds a fresh McpServer and transport per request, serves GET /health, enforces 401 on missing bearer tokens, and includes graceful shutdown for container rollouts. A shared graph.js client adds timeouts, retry/backoff on throttling and transient errors, and caps in-memory file transfer size; tool modules use Zod schemas and map Graph failures to MCP tool errors.

Deployment and packaging: multi-stage Dockerfile (npm ci, non-root node user, /health HEALTHCHECK), .dockerignore / .tfyignore, deploy.py for TrueFoundry remote build, plus npm test (util unit tests and a server smoke test).

Reviewed by Cursor Bugbot for commit eea1ff1. Bugbot is set up for automated code reviews on this repo. Configure here.

Comment thread m365-mcp/src/tools/sharepoint.js Outdated
Comment thread m365-mcp/src/tools/calendar.js Outdated
Comment thread m365-mcp/src/tools/teams.js
Comment thread m365-mcp/src/tools/calendar.js
test("odataString doubles single quotes and percent-encodes", () => {
assert.equal(odataString("inbox"), "inbox");
assert.equal(odataString("a'b"), "a''b");
assert.equal(odataString("a b"), "a%20b");

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

odataString test mismatch

Low Severity

The unit test expects odataString("a'b") to be a''b, but the helper also runs encodeURIComponent, producing a%27%27b. npm test fails on this assertion.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 962efac. Configure here.

Comment thread m365-mcp/src/tools/teams.js
Comment thread m365-mcp/src/tools/teams.js Outdated
Comment thread m365-mcp/src/tools/onedrive.js

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 4 potential issues.

There are 5 total unresolved issues (including 1 from previous review).

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit eea1ff1. Configure here.


const body = { chatType: type, members: allMembers.map(memberBind) };
if (topic && type === "group") body.topic = topic;
return graphPost(token, "/chats", body);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

create_chat oneOnOne member count

Medium Severity

The create_chat tool can build a oneOnOne request with only one member (e.g. empty members, or only the caller’s UPN) or with three or more after auto-adding the signed-in user when chat_type is oneOnOne and multiple addresses are supplied. Microsoft Graph requires exactly two members for oneOnOne chats, so these calls fail or behave incorrectly.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit eea1ff1. Configure here.

(m) => m.toLowerCase() === upn.toLowerCase(),
)
? members
: [...members, me.id];

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

create_chat duplicate caller member

Medium Severity

When deciding whether to add the signed-in user to members, create_chat only checks for a matching userPrincipalName, not the user’s Graph id. If the client already passes the caller’s object id, the handler still appends me.id, producing a duplicate member entry and a failed Graph request.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit eea1ff1. Configure here.

Comment thread m365-mcp/src/graph.js
/** Per-attempt request timeout (ms). */
const REQUEST_TIMEOUT_MS = Number(process.env.GRAPH_TIMEOUT_MS) || 30_000;
/** Extra attempts after the first for transient failures. */
const MAX_RETRIES = Number(process.env.GRAPH_MAX_RETRIES) || 3;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GRAPH_MAX_RETRIES zero ignored

Low Severity

GRAPH_MAX_RETRIES and GRAPH_TIMEOUT_MS are parsed with Number(...) || default, so an explicit 0 is treated as missing and replaced by 3 retries and 30000 ms. Operators cannot disable retries or set a zero timeout via environment variables as the README implies.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit eea1ff1. Configure here.

Comment thread m365-mcp/src/graph.js

if (isRetryable(res.status) && attempt < MAX_RETRIES) {
await sleep(retryDelayMs(res, attempt));
continue;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Retry leaves response unread

Medium Severity

On retryable HTTP statuses (429, 503, 504), sendWithRetry loops without reading or canceling the response body. Under Graph throttling, unconsumed bodies can hold connections open and reduce throughput or exhaust sockets in the pod.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit eea1ff1. Configure here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant