Check that each MCP tool's OAuth scope matches its permission
You will map each MCP tool to the scope it requires and confirm the token carries that scope. This keeps tool calls from failing with insufficient-permission errors and keeps access no broader than intended.
Scope mismatch
- Tool calls return -32005 Insufficient permissions
- Token lacks the per-tool scope
- Login succeeds but tool access fails
Scopes matched
- Each tool maps to the scope the server checks
- Token carries that scope via scope or a custom claim
- Unused scopes removed
Why it matters
A scope mismatch fails at call time. On a self-hosted obsidian-mcp-server, every tool call returned -32005 / Insufficient permissions because the JWT lacked the per-tool scope the server checked (obsidian-mcp-server issue #47 (opens external site)). Manifests can drift from what the server actually does too: an auto-generated Octocode manifest included a filesystem permission the server does not need (octocode issue #127 (opens external site)).
Steps
- List every tool your MCP server exposes and note the operation each one performs: read, write, delete or execute.
- For each tool, write down the exact scope name the server checks. The obsidian-mcp-server case used
tool:obsidian_list_notes:read, but that is one example, so confirm the names your own server expects rather than assuming a pattern. - Decode a token from a real tool call and compare its claims with your list. Check the
scopeclaim and any custom claim the server reads. - If your identity provider cannot put per-tool scopes into
scope, add a property mapping that emits a separate claim. The recommended route in obsidian-mcp-server v3.1.6 is a custommcp_tool_scopesclaim, which the server reads alongsidescpandscope. It can be a string or an array:
json
{"mcp_tool_scopes": "tool:obsidian_list_notes:read tool:obsidian_get_note:read"}
- Remove any scope that no tool needs, and keep write scopes separate from read scopes so read-only clients never receive them.
- Retest each tool with a token from the client you actually use, since some clients, such as Claude.ai, do not let you choose requested scopes.
- If you must, use
MCP_AUTH_DISABLE_SCOPE_CHECKS=truetemporarily. It is a bypass, not the fix. It skips per-tool scope checks but still validates signature, audience, issuer and expiry, and it logs a startup warning. Remove it once the claim mapping works.
Common mistakes
- Assuming a scope is present because login succeeded. Login proves identity; check the claims in the token that the tool call uses.
- Using the scope-check bypass as a permanent fix instead of adding the missing claim.
- Copying a generated permission manifest without checking each entry against what the server does. The Octocode maintainer found one entry unnecessary.
- Granting environment-variable read access broadly. It can expose API keys to the sandboxed process, so limit it.
Checklist
0 of 8 done