Support V1/V2 per-audience token acquisition for MCP servers#226
Merged
Support V1/V2 per-audience token acquisition for MCP servers#226
Conversation
Contributor
There was a problem hiding this comment.
Pull request overview
Note
Copilot was unable to run its full agentic suite in this review.
Adds support for per-audience OAuth token acquisition for MCP servers (V1 shared audience vs V2 per-server audiences) and aligns multiple observability libraries with OTEL gen-ai message envelopes, safer JSON serialization, and span-size enforcement during export.
Changes:
- Add
audience/scopefields and implement per-audience token acquisition with deduplication + header attachment for discovered MCP servers. - Update MCP tooling extensions (Claude/OpenAI/LangChain) to consume per-server
Authorizationfromserver.headersinstead of overwriting with a single token. - Expand observability to use OTEL gen-ai message envelopes, introduce
safeSerializeToJson, add exporter-side span truncation, and add agent/caller agent version attributes.
Reviewed changes
Copilot reviewed 52 out of 52 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/tooling/configuration/ToolingConfiguration.test.ts | Adds unit tests for per-server token scope resolution rules. |
| tests/tooling/McpToolServerConfigurationService.test.ts | Updates/extends tests for per-audience token acquisition and gateway v2 pathing. |
| tests/observability/tracing/message-utils.test.ts | Adds tests for message envelope normalization/serialization helpers. |
| tests/observability/integration/openai-agent-instrument.test.ts | Updates integration assertions for structured tool result serialization. |
| tests/observability/extension/openai/OpenAIMessageContract.test.ts | Adds contract tests validating OpenAI message-to-OTEL envelope mapping. |
| tests/observability/extension/openai/OpenAIAgentsTraceProcessor.test.ts | Updates tests to validate structured message envelopes and tool arg/result serialization. |
| tests/observability/extension/langchain/LangChainObservabilityAttributes.test.ts | Updates expected OTEL message envelope attributes + tool arg/result shapes. |
| tests/observability/extension/langchain/LangChainMessageContract.test.ts | Adds contract tests mapping real LangChain messages into OTEL envelopes. |
| tests/observability/extension/hosting/scope-utils.test.ts | Updates expected gen_ai.input.messages envelope serialization. |
| tests/observability/extension/hosting/output-logging-middleware.test.ts | Updates expected gen_ai.output.messages envelope serialization. |
| tests/observability/extension/hosting/TurnContextUtils.test.ts | Removes execution-type baggage utility test coverage. |
| tests/observability/extension/helpers/message-schema-validator.ts | Adds shared helper assertions for OTEL message envelope validation. |
| tests/observability/core/scopes.test.ts | Refactors tracer provider setup, adds message serialization + agent version coverage. |
| tests/observability/core/scope-messages.test.ts | Adds focused scope tests for message envelope recording and complex parts. |
| tests/observability/core/output-scope.test.ts | Updates OutputScope semantics/expectations for envelope output and overwrite behavior. |
| tests/observability/core/agent365-exporter.test.ts | Adds extensive tests for exporter-level span truncation behavior. |
| tests/observability/core/SpanProcessor.test.ts | Updates attribute allow-lists to include agent/caller agent version attributes. |
| tests/observability/core/BaggageBuilder.test.ts | Adds baggage coverage for agentVersion. |
| packages/agents-a365-tooling/src/contracts.ts | Adds audience/scope/publisher fields to server config/manifest contracts. |
| packages/agents-a365-tooling/src/configuration/ToolingConfiguration.ts | Exports resolveTokenScopeForServer and adds dev token lookup helper. |
| packages/agents-a365-tooling/src/Utility.ts | Switches tooling gateway URL construction to /agents/v2/... path. |
| packages/agents-a365-tooling/src/McpToolServerConfigurationService.ts | Implements per-audience token acquisition + attaches per-server Authorization headers. |
| packages/agents-a365-tooling-extensions-openai/src/McpToolRegistrationService.ts | Merges base headers with per-server headers (incl. Authorization) from discovery. |
| packages/agents-a365-tooling-extensions-langchain/src/McpToolRegistrationService.ts | Merges base headers with per-server headers (incl. Authorization) from discovery. |
| packages/agents-a365-tooling-extensions-claude/src/McpToolRegistrationService.ts | Preserves per-server Authorization by merging base + per-server headers. |
| packages/agents-a365-observability/src/tracing/util.ts | Adds safeSerializeToJson helper for consistently JSON-parseable attributes. |
| packages/agents-a365-observability/src/tracing/scopes/OutputScope.ts | Switches output recording to OTEL envelopes + overwrite semantics + raw dict passthrough. |
| packages/agents-a365-observability/src/tracing/scopes/OpenTelemetryScope.ts | Adds shared message envelope recording methods + agent version tag support. |
| packages/agents-a365-observability/src/tracing/scopes/InvokeAgentScope.ts | Records request content as input messages; caller agent version tagging. |
| packages/agents-a365-observability/src/tracing/scopes/InferenceScope.ts | Switches recordInput/OutputMessages to OTEL envelope recording via base scope helpers. |
| packages/agents-a365-observability/src/tracing/scopes/ExecuteToolScope.ts | Uses safeSerializeToJson for tool args/result; allows spanKind override. |
| packages/agents-a365-observability/src/tracing/processors/util.ts | Extends attribute allow-lists to include agent & caller agent versions. |
| packages/agents-a365-observability/src/tracing/middleware/BaggageBuilder.ts | Adds fluent agentVersion() setter. |
| packages/agents-a365-observability/src/tracing/message-utils.ts | Adds normalization + serialization helpers for OTEL gen-ai message envelopes. |
| packages/agents-a365-observability/src/tracing/exporter/utils.ts | Adds span-size enforcement (truncateSpan) with message-aware shrink strategy. |
| packages/agents-a365-observability/src/tracing/exporter/Agent365Exporter.ts | Applies truncateSpan to mapped OTLP spans before export. |
| packages/agents-a365-observability/src/tracing/contracts.ts | Adds OTEL gen-ai message types/envelopes; broadens request/response message parameter types. |
| packages/agents-a365-observability/src/tracing/constants.ts | Adds agent/caller agent version keys; deprecates execution-type key constant usage. |
| packages/agents-a365-observability/src/index.ts | Re-exports new message types/utilities + safe JSON helper and span-size constant. |
| packages/agents-a365-observability/docs/design.md | Updates design doc for new envelope format + exporter span truncation behavior. |
| packages/agents-a365-observability-hosting/src/utils/TurnContextUtils.ts | Removes execution type derivation helper. |
| packages/agents-a365-observability-hosting/src/utils/ScopeUtils.ts | Removes tenant details helper and related type usage. |
| packages/agents-a365-observability-hosting/src/utils/BaggageBuilderUtils.ts | Stops setting execution-type baggage. |
| packages/agents-a365-observability-hosting/src/middleware/BaggageMiddleware.ts | Stops injecting execution-type baggage. |
| packages/agents-a365-observability-hosting/docs/design.md | Removes execution-type documentation and examples. |
| packages/agents-a365-observability-extensions-openai/src/Utils.ts | Moves to message envelopes + safe JSON serialization; adds OpenAI-to-OTEL mapping utilities. |
| packages/agents-a365-observability-extensions-openai/src/OpenAIAgentsTraceProcessor.ts | Always emits structured message envelopes; removes content-recording gating option. |
| packages/agents-a365-observability-extensions-openai/src/OpenAIAgentsTraceInstrumentor.ts | Removes isContentRecordingEnabled config plumbing. |
| packages/agents-a365-observability-extensions-langchain/src/tracer.ts | Always records content attributes; removes content-recording option plumbing. |
| packages/agents-a365-observability-extensions-langchain/src/Utils.ts | Emits structured message envelopes + safe JSON for tool args/results; expands v0/v1 parsing support. |
| packages/agents-a365-observability-extensions-langchain/src/LangChainTraceInstrumentor.ts | Removes content-recording option plumbing when patching LangChain handlers. |
| CHANGELOG.md | Documents tooling per-audience token acquisition and observability breaking changes/additions. |
Comments suppressed due to low confidence (6)
packages/agents-a365-tooling/src/configuration/ToolingConfiguration.ts:1
- The docstring claims a fallback to
BEARER_TOKEN, but the implementation only checksBEARER_TOKEN_<SERVERNAME_UPPER>. This is likely to mislead callers and also contradicts the comments increateDevTokenAcquirer()that describe a shared fallback token. Either implement the fallback (per-server first, thenprocess.env.BEARER_TOKEN) or update the docstrings/comments to match the actual behavior.
packages/agents-a365-tooling/src/McpToolServerConfigurationService.ts:1 MCPServerConfignow includespublisher?: string, but this gateway mapping drops it. This loses data even when the gateway returnspublisher, and it conflicts with the PR description ("preserve audience / scope" plus manifest parsing passthrough). Includepublisher: s.publisher(and similarly ensure any other newly-added fields are passed through consistently).
packages/agents-a365-tooling/src/McpToolServerConfigurationService.ts:1- The manifest-to-config mapping also drops the newly-added
publisherfield (MCPServerManifestEntry.publisher). Ifpublisheris intended to be preserved (contracts + PR description suggest it is), addpublisher: s.publisherhere too to avoid silently discarding manifest metadata.
tests/observability/extension/helpers/message-schema-validator.ts:1 validateMessageEnvelopecurrently requiresmessagesto be a non-empty array, but several flows in this PR intentionally produce/allow an empty envelope (e.g.,normalizeInputMessages([])returns{ version, messages: [] }). Consider validating thatmessagesis an array (and leave emptiness checks toexpectValidInputMessages/expectValidOutputMessages, if desired), otherwise this helper is likely to cause false failures as coverage expands.
packages/agents-a365-tooling/src/McpToolServerConfigurationService.ts:1- Token acquisition for distinct scopes is awaited sequentially inside the loop. Since per-scope calls are independent, this can add avoidable latency when the server list contains multiple unique audiences. Consider first computing unique scopes, acquiring tokens in parallel (e.g.,
Promise.all), then attaching headers in a second pass while preserving the same cache semantics.
packages/agents-a365-tooling/src/configuration/ToolingConfiguration.ts:1 - The PR description says the V1 fallback applies when the audience has an
api://prefix, but the implementation only treats the ATGapi://URI form as V1 and treats otherapi://...audiences as V2. If the code is correct, the PR description should be updated to avoid misleading consumers; if the description is correct, the fallback condition should be adjusted.
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.
…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.
…r, and legacy path per-audience tokens
b11dc9b to
e7f4ccf
Compare
… breaking change - resolveTokenScopeForServer now accepts sharedScope parameter (defaults to prod ATG constant for backward compat) so V1 fallback scope is derived from mcpPlatformAuthenticationScope rather than a hardcoded constant. This fixes a false migration error when the auth scope is overridden via configuration. - attachPerAudienceTokens reads mcpPlatformAuthenticationScope from config and passes it into resolveTokenScopeForServer, ensuring the scope resolution and the legacy-path guard always compare against the same configured value. - publisher field is now preserved through both gateway and manifest normalization (previously declared in MCPServerConfig but silently dropped). - Updated GetToolingGatewayForDigitalWorker JSDoc example URL to /agents/v2/. - Added Breaking Changes section to CHANGELOG [Unreleased] documenting the listToolServers(agenticAppId, authToken) throw for V2 servers with migration guidance. Added publisher to the Added section. - 5 new tests for resolveTokenScopeForServer with custom sharedScope, including a regression guard for the false migration-error scenario.
In dev, createDevTokenAcquirer previously ignored the resolved scope and silently fell back to BEARER_TOKEN for all servers. V2 servers (distinct audience) would receive a token scoped to the shared ATG audience and get a 401 from the MCP server with no indication of why. Now emits a console.warn identifying the server, the required scope, and the exact env var to set (BEARER_TOKEN_<SERVERNAME_UPPER>) so the misconfiguration is visible before hitting the 401.
d5a2c42 to
4ea9f41
Compare
ajmfehr
approved these changes
Apr 16, 2026
gwharris7
approved these changes
Apr 16, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds V1/V2 per-audience token acquisition for MCP servers, so agents using blueprints with new per-server permissions (
audiencefield) receive correctly scoped OBO tokens for each MCP server instead of a single shared ATG token.What changed
MCPServerConfiggainsaudience,scope, andpublisherfields. V2 servers carry their own audience GUID; V1 servers have none (or the shared ATG app ID).McpToolServerConfigurationService.listToolServersacquires one OBO token per unique audience and attaches it asAuthorization: Beareron each server before returning.TokenAcquirer): env-var-based in dev, OBO-based in prod, keeping dev and prod paths structurally identical.server.headerswith priority over the shared discovery token.UtilityURLs updated to/agents/v2/.Bugs fixed in this branch
BEARER_TOKEN_<NAME>→BEARER_TOKEN)x-ms-agentidheader missing when using TurnContext overloadPost-review fixes (latest commit)
resolveTokenScopeForServernow accepts the configuredmcpPlatformAuthenticationScopeassharedScopeparameter — previously used a hardcoded constant, causing false migration errors and wrong OBO scopes when the auth scope was overridden viaToolingConfigurationpublisherfield now preserved through gateway and manifest normalization (was declared in the interface but silently dropped)GetToolingGatewayForDigitalWorkerJSDoc example URL updated to/agents/v2/[Unreleased]section updated with Breaking Changes entry and migration guideTest plan
pnpm test— 1233 tests pass (5 new tests for custom-scoperesolveTokenScopeForServer)pnpm build— clean TypeScript build (CJS + ESM) across all 9 packagesaudience): receives shared ATG token via OBOaudience): receives per-audience OBO tokenmcpPlatformAuthenticationScopeoverride: V1 fallback uses configured scope, no false migration errorBEARER_TOKEN_<SERVERNAME>attaches per-server;BEARER_TOKENas shared fallback