Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions .changeset/scope-challenge-primitives.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
---
'@modelcontextprotocol/server': minor
'@modelcontextprotocol/node': minor
---

Extend OAuth scope challenge support to non-tool primitives.

Building on the tool-side implementation, `resources/read`, `prompts/get`, and
`completion/complete` now participate in the same pre-execution scope check.

- `registerResource()` accepts `scopes` in its config, supporting static
arrays, `ToolScopeConfig`, or a dynamic resolver `(uri, variables) => scopes`
for templated resources where the required scope depends on path parameters.
- `registerPrompt()` accepts `scopes` in its config.
- `setResourceScopes()`, `setPromptScopes()`, and `setCompletionScopes()` allow
decoupled or centralised scope declaration.
- Completion scopes are an explicit, separate domain with no inheritance from
the referenced prompt or resource; pass `'*'` as `argumentName` to apply the
same scopes to every argument of a reference.

Internally, the transport's `ScopeResolver` is now operation-aware. It
receives the full JSON-RPC request and returns a `ScopeResolution` object
carrying both the scope config and an `operationName` label
(`tool:foo`, `resource:foo://bar`, `prompt:foo`, `completion:...`).
`McpServer.connect()` auto-wires a router that dispatches on JSON-RPC method.
26 changes: 26 additions & 0 deletions .changeset/scope-challenge-server.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
---
'@modelcontextprotocol/server': minor
'@modelcontextprotocol/node': minor
---

Add server-side OAuth scope challenge support (step-up auth) per MCP spec §10.1
and [SEP-2350](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2350).

Servers can now declare required OAuth scopes per tool and the Streamable HTTP transport
automatically returns HTTP 403 with `WWW-Authenticate` headers when a client's token
lacks sufficient scopes, triggering the client's existing re-authorization flow.

Per RFC 6750 §3.1 / SEP-2350, the `WWW-Authenticate` `scope` parameter advertises
only the scopes required for the current operation — clients are expected to
accumulate scopes across step-up challenges (see #1657). An opt-in
`scopeChallenge.includeGrantedScopes: true` restores the additive union behavior
for servers that need to defend against non-accumulating clients.

New APIs:
- `ToolScopeConfig` type for declaring `required` (AND) and `accepted` (OR / hierarchy) scopes per tool
- `ScopeChallengeConfig` transport option with `resourceMetadataUrl` and optional `includeGrantedScopes`
- `McpServer.registerTool()` accepts a `scopes` option (`string[]` or `ToolScopeConfig`)
- `McpServer.setToolScopes()` for decoupled/centralized scope declaration
- `McpServer.getToolScopes()` to query resolved scope config
- `setScopeResolver()` on both `WebStandardStreamableHTTPServerTransport` and `NodeStreamableHTTPServerTransport`
- Auto-wiring of scope resolver in `McpServer.connect()`
518 changes: 518 additions & 0 deletions docs/proposals/scope-challenge-server-sdk.md

Large diffs are not rendered by default.

13 changes: 12 additions & 1 deletion packages/middleware/node/src/streamableHttp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import type { IncomingMessage, ServerResponse } from 'node:http';

import { getRequestListener } from '@hono/node-server';
import type { AuthInfo, JSONRPCMessage, MessageExtraInfo, RequestId, Transport } from '@modelcontextprotocol/core';
import type { WebStandardStreamableHTTPServerTransportOptions } from '@modelcontextprotocol/server';
import type { ScopeResolver, WebStandardStreamableHTTPServerTransportOptions } from '@modelcontextprotocol/server';
import { WebStandardStreamableHTTPServerTransport } from '@modelcontextprotocol/server';

/**
Expand Down Expand Up @@ -152,6 +152,17 @@ export class NodeStreamableHTTPServerTransport implements Transport {
return this._webStandardTransport.send(message, options);
}

/**
* Sets the scope resolver for pre-execution scope challenge checks.
* Delegates to the underlying {@linkcode WebStandardStreamableHTTPServerTransport}.
*
* This is auto-wired by `McpServer.connect()` when `scopeChallenge`
* is configured on the transport.
*/
setScopeResolver(resolver: ScopeResolver): void {
this._webStandardTransport.setScopeResolver(resolver);
}

/**
* Handles an incoming HTTP request, whether `GET` or `POST`.
*
Expand Down
11 changes: 9 additions & 2 deletions packages/server/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,10 @@ export type {
RegisteredResourceTemplate,
RegisteredTool,
ResourceMetadata,
ToolCallback
ResourceScopeConfig,
ResourceScopeFn,
ToolCallback,
ToolScopeConfig
} from './server/mcp.js';
export { McpServer, ResourceTemplate } from './server/mcp.js';
export type { HostHeaderValidationResult } from './server/middleware/hostHeaderValidation.js';
Expand All @@ -35,10 +38,14 @@ export type {
EventId,
EventStore,
HandleRequestOptions,
ScopeAware,
ScopeChallengeConfig,
ScopeResolution,
ScopeResolver,
StreamId,
WebStandardStreamableHTTPServerTransportOptions
} from './server/streamableHttp.js';
export { WebStandardStreamableHTTPServerTransport } from './server/streamableHttp.js';
export { isScopeAware, WebStandardStreamableHTTPServerTransport } from './server/streamableHttp.js';

// experimental exports
export type { CreateTaskRequestHandler, TaskRequestHandler, ToolTaskHandler } from './experimental/tasks/interfaces.js';
Expand Down
Loading