MCP Auth: `google` authService drops email/sub from tokeninfo claims for opaque access tokens, so authenticated parameters cannot resolve them

Author: asato-watanabeCreated Sep 16, 2026Updated Sep 16, 2026
Labelstype: bugpriority: p2

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.

yaml
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:

bash
$ 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):

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?

  1. Configure the authServices / tools above and run Toolbox with --toolbox-url=<public url>.
  2. Connect with an MCP client that performs the OAuth code flow against accounts.google.com and sends the access token as a bearer token.
  3. tools/list succeeds.
  4. tools/call fails to resolve the email claim for caller_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.