Authentication and the Acting User
Version: mcp_server 2.0.0-beta5; with OAuth, mcp_server_oauth 1.0.0-alpha1 and simple_oauth 6.1.1.
When to Use
Read this before granting any permission. Every tool, resource and prompt runs as one Drupal account, and this page shows how that account is chosen and who may reach
/mcp.
Decision
| Transport | Acting Drupal user | Who chooses it | Source |
|---|---|---|---|
| STDIO | The account argument (name, ID, or 0 for anonymous) |
Whoever writes the client config | McpServerCommands.php |
| HTTP, core only | The user Drupal's cookie provider resolves, else anonymous |
The browser session | mcp_server.routing.yml |
HTTP + mcp_server_oauth |
The token's user, with permissions narrowed by the token's scopes (admin accounts excepted; see OAuth Client Connection) | The OAuth grant | OAuth Client Connection owns the rules |
Cookie auth is for same-site browser use. STDIO is local. Use OAuth for any remote client.
Who gets access mcp server. Grant it to a dedicated role held by the accounts agents use. Never grant it to anonymous. On an OAuth site, the anonymous 401 is what starts a client's OAuth flow.
Denial responses
McpExceptionSubscriber runs at priority 80 on route mcp_server.handle. It turns a route-permission denial into a JSON-RPC error body. From mcp_server 2.0.0-beta5 src/EventSubscriber/McpExceptionSubscriber.php:
| Who | HTTP | Header |
|---|---|---|
| Anonymous | 401 | WWW-Authenticate: Bearer realm="mcp_server" |
| Authenticated | 403 | WWW-Authenticate: Bearer error="insufficient_scope" |
Other denials surface differently:
| Denial | What the client gets | Source |
|---|---|---|
Resource checkAccess() |
JSON-RPC internal error -32603 "Error while reading resource" (both SDK versions) | SDK ReadResourceHandler.php |
McpAuthorizationDeniedException from a RequestEvent listener, SDK v0.7.1 |
HTTP 401 or 403 as above | SDK v0.7.1 Protocol.php:183 dispatches outside its try blocks |
| Same, SDK v0.8.1 | JSON-RPC internal error -32603 "Internal server error." | SDK v0.8.1 Protocol.php:202-219 |
Where access is checked
| Layer | Check | When |
|---|---|---|
| Route (HTTP only) | access mcp server |
Every request |
Native #[Tool] plugin |
checkAccess($account), default allowed |
When the server is built; denied tools are absent from tools/list |
| Bridged Tool API tool | Tool API access() |
At call time; the tool is still listed |
| Resource | checkAccess($uri, $account) |
Every read |
| Prompt | None | Never |
Common Mistakes
- Granting
access mcp serverto anonymous because the client has no cookie. Use OAuth instead. - Assuming the 401
Bearerchallenge means core accepts tokens. Core declares onlycookie; the header exists for OAuth clients. - Relying on
access mcp server prompts. It is declared but no code checks it; see Prompts. - Using an account with an admin role as the acting user. Every tool then runs with every permission.
See Also
- STDIO Transport | HTTP Transport
- OAuth Client Connection → for token users and scope narrowing
- Security Considerations
- Reference:
modules/contrib/mcp_server/src/EventSubscriber/McpExceptionSubscriber.php,src/Exception/McpAuthorizationDeniedException.php