MCP Auth: `google` authService drops email/sub from tokeninfo claims for opaque access tokens, so authenticated parameters cannot resolve them
Prerequisites
- I've searched the current open issues
- I've updated to the latest version of Toolbox
Toolbox version
1.9.0 (deployed). Confirmed unchanged in 1.10.0, 1.11.0 and on main at 7b41e73 (2026-09-15).
Environment
Toolbox on Cloud Run, image pinned by digest, config mounted from Secret Manager.
authServices:
caller:
kind: google
mcpEnabled: true
audience: <web application client id>
scopesRequired:
- https://www.googleapis.com/auth/userinfo.email
tools:
query:
kind: bigquery-sql
source: bq
authRequired: [caller]
templateParameters:
- name: caller_email
type: string
authServices:
- name: caller
field: email
statement: |
-- caller: {{.caller_email}}
SELECT ...The authorization server is Google (https://accounts.google.com), the OAuth client is an
Internal Web application client in a Google Workspace organization.
Client
A standards-based MCP client that completes the OAuth code flow against accounts.google.com
and sends the resulting opaque access token (ya29...) in Authorization: Bearer
(Claude.ai custom connectors, for example). Such clients send the access token, not an ID token,
so the idtoken.Validate branch of ValidateMCPAuth — which does return email — is never reached.
Expected Behavior
The tokeninfo response fields email, email_verified and sub are available as claims, so an
authenticated parameter with field: email resolves in MCP auth mode exactly as it does with an ID token.
Google does return them when the token carries the userinfo.email scope:
$ curl -s "https://oauth2.googleapis.com/tokeninfo?access_token=$TOKEN" | jq 'keys'
["access_type","aud","azp","email","email_verified","exp","expires_in","scope","sub"]Current Behavior
tools/list succeeds; tools/call cannot resolve the email claim, because the opaque-token branch
of ValidateMCPAuth decodes only three fields from the tokeninfo response and then publishes two of
them as claims (internal/auth/google/google.go):
var tokenInfo struct {
Aud string `json:"aud"`
Azp string `json:"azp"`
Scope string `json:"scope"`
}
...
claims := map[string]any{
"aud": aud,
"scope": tokenInfo.Scope,
}email, email_verified and sub are discarded at the decode step, before the claims map is
built, even though claimsFromAuth in internal/server/api.go is populated from the MCP-validated token.
The generic authService's introspection branch has the same shape (active / scope / aud /
exp / iss only) and additionally requires iss, which Google's tokeninfo does not return for
access tokens, so it is not a workaround either.
Steps to reproduce?
- Configure the
authServices/toolsabove and run Toolbox with--toolbox-url=<public url>. - Connect with an MCP client that performs the OAuth code flow against
accounts.google.comand sends the access token as a bearer token. tools/listsucceeds.tools/callfails to resolve theemailclaim forcaller_email.
Additional Details
Decoding the full tokeninfo response and copying it into the claims map (or at least
email, email_verified and sub) would make field: email behave the same in MCP auth mode as it
does with an ID token. Happy to send a PR if that shape is acceptable.
Why it matters: field: email in a templateParameters entry is the documented way to get the caller's
identity into the SQL text, which is how the call becomes attributable in the database's audit log.
In MCP auth mode that attribution is currently unavailable for any client that sends an access token.
Source: googleapis/mcp-toolbox