You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Require authentication and room authorization for room-bound MCP tools - #137
/ai/v1/mcp-rooms: requires a user session or the internal token; adds get-node, add-node, update-node, delete-node.
Room tools now take roomId + pageId and check room membership and page ownership on every call. Unknown pages and non-members get the same generic error. Sessions are bound to the identity that created them. The in-app Mastra agents use the internal token over /mcp-rooms, and x-internal-token / _token are redacted from logs.
SSRF hardening (#136) and share-link lifecycle are out of scope; separate PRs.
Verification
npm run lint and npm run build pass; openspec validate --strict passes.
Local harnesses: anonymous and wrong-token calls to /mcp-rooms return 401; /mcp lists only stateless tools; the authorization helper was checked in 7 cases.
Live verification (local server, real better-auth sessions, develop Azure storage, real LLM)
Anonymous initialize returns 401; a member lists all 9 tools on /mcp-rooms.
Member add-node, get-node (from a second session), update-node and delete-node on their own page work.
Non-member, page from another room and unknown page all return the same generic error.
Session id reused by a different logged-in user returns 403; anonymous returns 401; wrong internal token returns 401.
AI room-edit through the chat endpoint: plan, approval, then the workflow added the requested node to the page via /mcp-rooms with the internal token. The other room's page was untouched.
Not verified
Visual rendering of the added nodes on the real canvas (frontend is not in this repo). Persisted document content was checked directly.
Live verification (local server, real better-auth session, develop Azure storage, real LLM):
4.3 MCP auth: anonymous init → 401; member lists all 9 tools; add/get/update/delete-node on own page OK; non-member, cross-room page and unknown page → same generic error; another user's or anonymous use of a session id → 403/401; wrong internal token → 401.
5.3 AI edit: chat route → plan → approval → workflow added the requested rectangle to the page via /mcp-rooms with the internal token; the other room's page was untouched.
Not verified: visual rendering on the real canvas (frontend not in this repo).
This helper omits isError: true, so a missing-node result from get-node is reported as a successful MCP result. For add-node, update-node, and delete-node, its { error } payload also fails the declared success schema requiring nodeId, obscuring the intended error with output validation. Mark these results as errors, as roomAccessErrorResult() already does.
Restore missing room document guards in all handlers
code/src/mcp/tools/get-node.ts:58
Authorization checks membership and page metadata, not document availability. If getRoomDocument returns undefined, the removed guard now lets that value reach getYjsElement instead of returning a controlled error. The same guard was removed in add-node.ts:80, update-node.ts:72, and delete-node.ts:49. Restore if (!roomDocument) return roomAccessErrorResult() after loading in all four handlers, before accessing the document; templates/templates.ts:24–30 still handles this case separately.
Replies to the 'previously missed' notes, both valid, fixed in 1a624c9: (1) toolErrorResult now sets isError: true, so error results no longer fail the declared output schema (e.g. delete-node's nodeId) and mask the message; (2) I had removed the if (!roomDocument) guard when switching to the page-derived document id, which was a regression, so it is restored in all four handlers (returning the generic access error).
The new endpoint inherits a CORS policy that prevents allowlisted cross-origin browser clients from completing an MCP session. middlewares/cors.ts:29–35 omits Mcp-Session-Id and MCP-Protocol-Version from allowed request headers and does not expose the returned Mcp-Session-Id. A cookie-authenticated browser cannot read the initialization session ID, and requests carrying the protocol headers fail preflight. Allow these request headers and expose the session ID for both MCP routes, including their OPTIONS handler, while preserving the origin allowlist.
The reason will be displayed to describe this comment to others. Learn more.
Valid, fixed in b37f4a2: resumeWorkflowTool now compares the run's saved roomId (initialState) with the request context's room, which the chat route authorized for the caller, and answers execution-not-found on mismatch or when there is no context. Checked against a real suspended Mastra run: a different room and a missing context are rejected. Note: saved state alone is no longer treated as proof of authorization.
Reply to the 'previously missed' CORS note (Mcp-Session-Id / MCP-Protocol-Version): not applying it in this PR. The CORS policy is inherited unchanged from the existing /mcp endpoint, there is no browser-based MCP client in this project (the frontend never calls MCP; only the backend's agents and server-side tools do), and widening allowed/exposed headers is a separate change that should come with an actual browser client. Happy to open a follow-up if browser MCP clients become a requirement.
alexmontesg
changed the title
fix(mcp): require authentication and room authorization for room-bound MCP tools
Require authentication and room authorization for room-bound MCP tools
Oct 5, 2026
Split the MCP server into a public endpoint (/ai/v1/mcp, stateless tools)
and an authenticated one (/ai/v1/mcp-rooms) that adds the room tools.
Room tools now take roomId + pageId and check room membership and page
ownership on every call. The in-app AI agents use the internal token.
Refs #135
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Overwrite roomId/pageId on internal room tool calls from the request
context, fully redact percent-encoded _token query params from logs and
allow sharing INTERNAL_SERVICE_TOKEN with Mastra Studio.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Closes #135.
Splits the MCP server into two endpoints:
/ai/v1/mcp: public, five stateless tools only./ai/v1/mcp-rooms: requires a user session or the internal token; addsget-node,add-node,update-node,delete-node.Room tools now take
roomId+pageIdand check room membership and page ownership on every call. Unknown pages and non-members get the same generic error. Sessions are bound to the identity that created them. The in-app Mastra agents use the internal token over/mcp-rooms, andx-internal-token/_tokenare redacted from logs.SSRF hardening (#136) and share-link lifecycle are out of scope; separate PRs.
Verification
npm run lintandnpm run buildpass;openspec validate --strictpasses./mcp-roomsreturn 401;/mcplists only stateless tools; the authorization helper was checked in 7 cases.Live verification (local server, real better-auth sessions, develop Azure storage, real LLM)
initializereturns 401; a member lists all 9 tools on/mcp-rooms.add-node,get-node(from a second session),update-nodeanddelete-nodeon their own page work./mcp-roomswith the internal token. The other room's page was untouched.Not verified