Authdog

AI guide

MCP Security

Protect an MCP server before its transport or tools process a request. @authdog/mcp-sdk gives you four opt-in controls, authenticate, trust store, authorize, telemetry, that compose into that boundary. This guide wires them up using the included Cloudflare Worker sample.

Prerequisites

  • An Authdog OIDC issuer for the environment that issues access tokens.
  • An MCP resource URL, normally the public server URL plus /mcp.
  • JWT access tokens and either an issuer or a direct JWKS URI.
  • Required MCP scopes chosen for the service and for sensitive tools.
  • Bun and Wrangler for the included Cloudflare Worker sample.

Key bindings: AUTHDOG_OIDC_ISSUER, JWKS_URI, AUTHDOG_OIDC_AUDIENCE, MCP_REQUIRED_SCOPES, MCP_PROTECTED_RESOURCE, MCP_JWT_ONLY. Opaque-token introspection is opt-in via MCP_ALLOW_OPAQUE_INTROSPECTION=true.

Implementation

1. The four controls

Authenticate (authenticateRequest) verifies the token against JWKS and serves RFC 9728 protected-resource metadata. Trust store (checkTrustStore) denies unverified non-human identities and caps their scopes (MCP_TRUST_STORE_MODE=off|monitor|enforce, default off). Authorize (withAuthdogToolGuard) asks Authdog's AuthZEN PDP whether the identity may invoke this specific tool (MCP_AUTHZ_MODE=off|monitor|enforce, default monitor). Telemetry emits MCP_TOOL_INVOKED/DENIED/FAILED events feeding audit, SIEM, and the gated MCP threat detectors, raw arguments are never sent, only a digest.

2. Run the sample

cd examples/mcp-auth-sample
bun install
bun run dev

Copy .dev.vars.example to .dev.vars and set your environment's issuer, JWKS, audience, and required scope (e.g. mcp:invoke).

3. Authenticate before the transport runs

The sample calls authenticateRequest(request, env) before forwarding /mcp. It discovers OIDC metadata, verifies the JWT, and checks issuer, audience, and scopes. Missing or invalid credentials produce 401; missing scopes produce 403.

4. Re-authorize inside privileged tools

Authentication protects the service; it doesn't authorize every side effect. The sample's admin_echo handler applies a narrower check:

requireClaimScopes(auth.claims, ["mcp:admin"])

5. Publish protected-resource metadata

buildProtectedResourceMetadata(request, env) returns the resource URL, authorization servers, and supported scopes. Publish it at /.well-known/oauth-protected-resource.

Security considerations

  • Prefer bearer tokens in the Authorization header, the query-token fallback (MCP_ALLOW_QUERY_TOKEN) is not recommended.
  • Use MCP_JWT_ONLY=true when the deployment accepts only JWT access tokens.
  • Don't enable opaque introspection merely because stale credentials exist, it requires explicit configuration and stays server-side.
  • Set AUTHDOG_OIDC_AUDIENCE to the actual MCP resource URL, this stops a token minted for another resource from being reused.
  • Distinguish service-wide scopes from tool-level scopes, authentication alone doesn't authorize a tool's side effects.

Validation checklist

  • Metadata endpoint returns the intended resource and authorization server.
  • /mcp rejects a request without Authorization: Bearer.
  • Valid JWT succeeds only with matching issuer and audience.
  • Missing baseline scope returns 403.
  • Sensitive tool rejects a token lacking its tool-specific scope.
  • MCP_JWT_ONLY=true prevents introspection.
  • Logs and error handling never expose bearer tokens or introspection secrets.

Troubleshooting

"Missing token verification settings": neither JWT verification nor introspection is fully configured, set an issuer or JWKS URI.

"Token audience mismatch": match AUTHDOG_OIDC_AUDIENCE, MCP_PROTECTED_RESOURCE, and the public /mcp URL.

403 reporting missing scopes: signature and claims verification succeeded, but a required scope wasn't granted.

Next steps

  • Inspect examples/mcp-auth-sample/src/index.ts for request ordering and tool checks.
  • Inspect packages/mcp-sdk/src/auth.ts for exact exported configuration.
  • Read Machine identity for non-human OAuth clients.
  • Apply Authorization to each state-changing MCP tool.