Skip to content

feat(mcp-server): pre-check tool scopes and retire plugin configuration - #2995

Draft
legreffier[bot] wants to merge 5 commits into
mcp-sdk-2-switchfrom
mcp-sdk-3-cleanup
Draft

legreffier[bot] wants to merge 5 commits into
mcp-sdk-2-switchfrom
mcp-sdk-3-cleanup

Conversation

@legreffier

@legreffier legreffier Bot commented Nov 8, 2026 •

Copy link
Copy Markdown
Contributor

Stacked on #2994, which sits on #2993 and #2990. Completes the @modelcontextprotocol/server migration for #2967.

Summary

  • Scope pre-check: with authorization on, a tool whose declared scopes aren't all on the verified token answers with Missing required scope: <scope> and the OpenAI-style insufficient_scope challenge, without calling the REST API. Declared scopes equal the routes' requiredScopes, so it refuses exactly what REST would refuse, without a round trip or error-text matching. Parsing REST errors stays as a fallback for anything the API demands beyond the declaration.
  • Cleanup: removes the Redis session and DCR-proxy configuration the plugin needed, and retires the plugin's catalog, release-age and Renovate entries.
  • Telemetry: restores per-operation OpenTelemetry spans. The official SDK has none: it only reserves the traceparent/tracestate/baggage _meta keys and never reads them.
  • Eval: removes the stale mcp-format-uuid-validation scenario.
  • Docs: describe the stateless server.

Why not the SDK's scopeChallenge

The official server can refuse a tool call before it runs, with HTTP 403 and WWW-Authenticate: insufficient_scope. It's deliberately not wired: that 403 would pre-empt the tool-result _meta["mcp/www_authenticate"] challenge, which is what OpenAI documents as the trigger for re-linking. Worth revisiting once a production test shows how ChatGPT handles the 403.

Changes

  • apps/mcp-server/src/mcp-registry.ts: the pre-check. tool-security.spec.ts covers the refusal without a handler call, the pass-through with the scope, and the REST fallback.
  • Config: MCP_REDIS_* and ORY_HYDRA_REGISTRATION_URL removed from the schema, docker-compose.e2e.yaml, deploy/self-host/compose.yaml, fly.toml comments and env.local.example. The loader only reads declared keys, so leftover Fly secrets are ignored. valkey stays in self-host because the REST API uses it.
  • pnpm-workspace.yaml, renovate.json: @getlarge/fastify-mcp catalog, release-age and Fastify-group entries removed.
  • libs/discovery: mcp_library is now @modelcontextprotocol/server.
  • AGENTS.md (and CLAUDE.md through the symlink), .github/copilot-instructions.md: the MCP stack line.
  • apps/mcp-server/src/mcp-telemetry.ts (new): the registry wraps every tool call, resource read and prompt get in an INTERNAL span named <method> <target> (for example tools/call diaries_list). Each span carries the MCP semconv attributes the plugin used (mcp.method.name, mcp.tool.name, mcp.resource.uri, mcp.prompt.name). A tool result with isError sets error.type: tool_error; a thrown exception is recorded on the span. The route also tags the @fastify/otel HTTP span with the JSON-RPC method, so initialize and the */list calls stay visible. Spans are no-ops without a tracer provider. mcp-telemetry.spec.ts checks them with an in-memory exporter.
  • evals/moltnet-practices/mcp-format-uuid-validation/ removed, along with its evals/batch.json entry. Its premise was that the plugin couldn't validate format: 'uuid', and TypeBox now enforces it. No CI job runs the batch.
  • docs/reference/mcp-server.md: stateless transport, _meta.securitySchemes, the pre-check. docs/understand/architecture.md: diagram label and the JWT library note.

Operator follow-up (not in this PR)

  • The MCP_REDIS_* Fly secrets on the MCP app are now unused and can be unset after deploy.

Not done

  • W3C trace context sent by a client in request _meta (traceparent) isn't extracted yet; spans nest under the HTTP request span.
  • mcp-telemetry.ts is a stopgap. feat: built-in OpenTelemetry tracing and metrics modelcontextprotocol/typescript-sdk#3115 (merged 2026-11-03, not yet in a release; 2.4.0 is the latest) builds OpenTelemetry into McpServer. It adds SERVER spans for every request, including initialize and the list calls, traceparent extraction from _meta, and mcp.server.operation.duration metrics. When the SDK bump lands, delete withMcpSpan and annotateRequestSpan, or pass openTelemetry: false, to avoid duplicate spans. The SDK names the tool gen_ai.tool.name (the semconv), not mcp.tool.name.

Test plan

  • pnpm exec nx run-many -t typecheck lint test check:externals --projects=@moltnet/mcp-server,@moltnet/mcp-server-e2e,@moltnet/discovery: 238 unit tests
  • pnpm install --frozen-lockfile
  • pnpm exec nx run @moltnet/mcp-server-e2e:e2e with the image rebuilt from this branch and the container running without MCP_REDIS_*: 79/79
  • Docs and examples are updated when the user-facing behavior changes
  • After deploy: ChatGPT/Codex reconnect, and Claude Code /mcp, list and search diaries

No UI impact.

legreffier Bot added 5 commits November 10, 2026 10:08
Refuse a tool call whose token lacks a declared scope with the scope error and insufficient_scope challenge, without a REST round trip or error-text matching. REST denials beyond the declaration still get the challenge.

Refs #2967

MoltNet-Diary: d57601d4-d9f4-4691-8944-cad58232be24
Task-Group: mcp-official-sdk
Drop MCP_REDIS_* and ORY_HYDRA_REGISTRATION_URL with their compose, Fly and example env entries, retire the @getlarge/fastify-mcp catalog, release-age and Renovate entries, and name the official SDK in discovery and agent instructions.

Refs #2967

MoltNet-Diary: d66c468f-f1ee-4eac-9348-7c9053ae817e
Task-Group: mcp-official-sdk
Refs #2967

MoltNet-Diary: 36592d0d-bca5-42ab-a39e-64944c3bc4a2
Task-Group: mcp-official-sdk
Task-Completes: true
Its premise (the MCP plugin could not validate format: 'uuid') no longer holds: the server now validates with TypeBox, which enforces it. No CI job runs the eval batch.

Refs #2967

MoltNet-Diary: c76312dc-9681-4c62-a4ed-0dece3ec9987
Task-Group: mcp-official-sdk
The official SDK emits no telemetry. Wrap tool calls, resource reads and prompt gets in spans with the MCP semantic-convention attributes, record tool errors and exceptions, and tag the HTTP request span with the JSON-RPC method.

Refs #2967

MoltNet-Diary: 0a1f1621-f8ef-497e-bbfe-1e7e822af3d1
Task-Group: mcp-official-sdk

This branch has not been deployed

No deployments
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.

0 participants