Demonstrates per-tool OAuth scope enforcement on a createMcpHandler
deployment: the HTTP gate does bearer-verify + 401 only, and each tool
handler checks ctx.http?.authInfo?.scopes for the scope it needs. The scope
decision lives next to the code it guards — the handler is the only place that
authoritatively knows which tool is executing — instead of in middleware that
would have to re-derive the operation from the request body.
server.ts runs a minimal demo Authorization Server alongside the MCP Resource
Server. client.ts connects with a files:read token, calls list-files
(works), then calls write-file → the handler returns { isError: true } with
insufficient_scope: requires files:write.
The transport's automatic 403 insufficient_scope step-up flow (SEP-2350 —
scope union, refresh-bypass, maxStepUpRetries) applies when the RS responds
403 at the HTTP layer; that path is exercised by
test/e2e/scenarios/client-auth.test.ts.
pnpm --filter @mcp-examples/scoped-tools server -- --http --port 3000
pnpm --filter @mcp-examples/scoped-tools client -- --http http://127.0.0.1:3000/mcpDEMO ONLY — the bundled AS auto-approves and grants whatever scope is asked for. Do not deploy.