Skip to content

Support V1/V2 per-audience token acquisition for MCP servers#226

Draft
biswapm wants to merge 9 commits intomainfrom
pmohapatra-MCP-V1-V2-nodeJs
Draft

Support V1/V2 per-audience token acquisition for MCP servers#226
biswapm wants to merge 9 commits intomainfrom
pmohapatra-MCP-V1-V2-nodeJs

Conversation

@biswapm
Copy link
Copy Markdown

@biswapm biswapm commented Mar 30, 2026

Summary

Problem

The SDK previously hardcoded a single shared token audience (ea9ffc3e-8a23-4a7d-836d-234d7c7565c1/.default) for all MCP servers.
With V2 MCP servers introducing per-server audience GUIDs, each server now requires its own OAuth token. Using the shared ATG token for V2 servers leads to authentication failures.


Changes

agents-a365-tooling

  • contracts.ts

    • Added optional audience?: string and scope?: string to:
      • MCPServerConfig
      • MCPServerManifestEntry
    • No breaking changes.
  • ToolingConfiguration.ts

    • Exported resolveTokenScopeForServer(server)
      • Returns ${audience}/.default for V2 GUID audiences
      • Falls back to shared ATG scope for V1 cases:
        • No audience
        • ATG AppId
        • api:// prefix
  • McpToolServerConfigurationService.ts

    • Added private attachPerAudienceTokens():
      • Groups servers by scope
      • Performs one GetAgenticUserToken call per unique audience (with caching)
      • Attaches the correct Authorization: Bearer header per server
    • Updated listToolServers (TurnContext path):
      • Calls attachPerAudienceTokens() after discovery
    • Legacy path (agenticAppId, authToken) remains unchanged
    • Gateway response mapping and manifest parsing now preserve audience / scope

agents-a365-tooling-extensions-claude

  • McpToolRegistrationService.ts
    • Uses server.headers (from listToolServers) for Authorization
    • No longer overwrites all servers with a single ATG token
    • Continues composing non-auth headers (e.g., channel ID, user-agent) via GetToolRequestHeaders

Compatibility

Change Breaking? Notes
audience? / scope? fields No Optional additions
resolveTokenScopeForServer export No Additive
Per-audience token acquisition (TurnContext path) No V1 still resolves to ATG scope
Claude extension header handling No V1 unchanged; V2 gets correct token
listToolServers(agenticAppId, authToken) No Fully unchanged

Tests

  • Updated

    • 5 existing tests adjusted for per-audience token acquisition
  • New (11 total)

    resolveTokenScopeForServer

    • V1 fallback:
      • No audience
      • ATG AppId
      • api:// prefix
    • V2 GUID handling
    • V2 GUID with explicit scope

    Per-audience token acquisition

    • V1 ATG header behavior
    • V2 per-server scope handling
    • Shared-audience deduplication
    • Mixed V1 + V2 scenarios
    • Failure propagation
    • Manifest field passthrough

V2 MCP servers carry their own audience GUID and require a distinct
OAuth token per server rather than the shared ATG token. This change
adds per-audience token caching in listToolServers (TurnContext path)
so each server receives the correct Authorization header.

Key changes:
- contracts.ts: add optional audience?/scope? to MCPServerConfig and
  MCPServerManifestEntry
- ToolingConfiguration.ts: export resolveTokenScopeForServer — returns
  per-server scope for V2 GUIDs, falls back to shared ATG scope for V1
- McpToolServerConfigurationService: new private attachPerAudienceTokens
  acquires one token per unique scope and patches each server's headers;
  gateway response and manifest parsing now preserve audience/scope fields
- McpToolRegistrationService (Claude ext): use server.headers for the
  Authorization token set by listToolServers instead of overwriting with
  the single ATG token

V1 agents are unaffected — no audience field means ATG scope as before.
@biswapm biswapm requested review from ajmfehr and gwharris7 March 30, 2026 07:52
biswapm and others added 8 commits March 31, 2026 15:02
…js extensions

- resolveTokenScopeForServer now uses server.scope for V2 servers when present,
  falling back to audience/.default (pre-consented scopes cover both cases)
- OpenAI and LangChain extensions now apply per-audience Authorization headers
  from server.headers instead of a single shared discovery token
- Restore MCP_PLATFORM_PROD_BASE_URL to production endpoint
- Update tests to reflect V2 explicit scope behavior
…pattern

Replaces the earlier OBO-only approach with a TokenAcquirer abstraction so
dev and prod share the same attachPerAudienceTokens code path. Dev mode uses
env-var-based acquirer (BEARER_TOKEN_<NAME> / BEARER_TOKEN fallback); prod
uses OBO via AgenticAuthenticationService. Eliminates the dev hang where
GetAgenticUserToken was called unconditionally before manifest loading.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Resolves ESLint no-restricted-properties violation: direct process.env access
in McpToolServerConfigurationService is replaced by getBearerTokenForServer()
on ToolingConfiguration. Removes the shared BEARER_TOKEN fallback — each server
now requires its own BEARER_TOKEN_<NAME> env var. Updated tests accordingly.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace broad startsWith('api://') check with an exact match against
ATG_APP_ID_URI ('api://ea9ffc3e-...'), mirroring the Python utility.py logic.
This allows V2 servers that use api:// audience URIs to get their own
per-server token instead of being silently routed to the shared ATG scope.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant