Repository navigation
Server SDK support for scope challenges on tool calls #1151
Description
Summary
Server SDK authors currently have no ergonomic way to trigger OAuth scope challenges during tool execution. While the MCP spec (SEP-835) supports returning 403 responses with WWW-Authenticate headers to request additional scopes, server SDKs don't provide facilities to easily declare required scopes per tool or dynamically trigger scope challenges during tool execution.
Use Case
Consider a server like GitHub's MCP server that wraps an API with granular permissions:
- A user connects with a valid OAuth token but minimal/no scopes
- They call a tool that may or may not need additional scopes depending on the specific resource (e.g., accessing a public vs private repo)
- The server makes a request to its backend service, which responds indicating more scopes are needed
- The server wants to pass this back as a scope challenge rather than a failed tool call
Currently, server authors must implement this entirely outside SDK land:
- Parse raw JSON-RPC
- Match tool names
- Check required scopes against active scopes
- Manually construct and return the 403 response with WWW-Authenticate header
Proposed Features
1. Declare required scopes when registering a tool (minimum viable)
server.registerTool(
'get_private_repo',
{
description: 'Get private repository details',
inputSchema: { ... },
requiredScopes: ['repo:read'] // New field
},
async (args) => { ... }
);The SDK would automatically check scopes before tool execution and return a 403 scope challenge if the token lacks required scopes.
2. Dynamically trigger scope challenge during tool execution (ideal)
server.registerTool(
'get_repo_issue',
{
description: 'Get repository issue',
inputSchema: { ... }
},
async (args, context) => {
const response = await fetchFromBackend(args);
if (response.needsAdditionalScopes) {
// Trigger scope challenge mid-execution
throw new ScopeChallengeError(['repo:read']);
// or: context.requestScopes(['repo:read']);
}
return { content: [...] };
}
);This would allow servers to defer scope decisions to downstream services and pass through their authorization requirements.
Additional Considerations
- Scope hierarchy: Some OAuth systems have hierarchical scopes (e.g.,
repoincludesrepo:read). SDKs may need to support scope resolution/comparison utilities. - Access to current scopes: Tool handlers need a way to inspect what scopes the current token has (perhaps via context parameter).
Related
- SEP-835 (Scope Challenge Handling): https://modelcontextprotocol.io/specification/draft/basic/authorization#scope-challenge-handling
- Conformance test for client-side handling: https://github.com/modelcontextprotocol/conformance/blob/main/src/scenarios/client/auth/scope-handling.ts#L237
- Python SDK client implementation: https://github.com/modelcontextprotocol/python-sdk/blob/main/src/mcp/client/auth/oauth2.py#L591-L608
Context
Discussion from Discord with @SamMorrowDrums (GitHub MCP server author) about server-side SDK support for scope challenges.
https://discord.com/channels/1358869848138059966/1441013745496227870
Activity
SamMorrowDrums commentedon Nov 21, 2025
Changes to streamable http might make this impossible, and perhaps it might need to be rolled into a sort of MCP preflight check eventually?
modelcontextprotocol/modelcontextprotocol#1862 (comment)
It's a similar problem, might need to determine certain things before it is possible to make a tool call to keep scopes minimal and accurate tool annotations.
feat(server): add OAuth scope challenge support (step-up auth)
feat(server): add OAuth scope challenge support (step-up auth)
feat(server): add OAuth scope challenge support (step-up auth)
felixweinberger commentedon Oct 5, 2026
Shipped in 2.1.0 with #1624:
scopeChallengeon a tool registration answers the call with a 403 scope challenge before the handler runs, and its callback sees the request and the token's scopes. A challenge raised from inside a running handler is tracked in #2957. Closing.