Skip to content

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

Merged
gwharris7 merged 14 commits intomainfrom
pmohapatra-MCP-V1-V2-nodeJs
Apr 16, 2026
Merged

Support V1/V2 per-audience token acquisition for MCP servers#226
gwharris7 merged 14 commits intomainfrom
pmohapatra-MCP-V1-V2-nodeJs

Conversation

@biswapm
Copy link
Copy Markdown
Contributor

@biswapm biswapm commented Mar 30, 2026

Summary

Adds V1/V2 per-audience token acquisition for MCP servers, so agents using blueprints with new per-server permissions (audience field) receive correctly scoped OBO tokens for each MCP server instead of a single shared ATG token.

What changed

  • MCPServerConfig gains audience, scope, and publisher fields. V2 servers carry their own audience GUID; V1 servers have none (or the shared ATG app ID).
  • McpToolServerConfigurationService.listToolServers acquires one OBO token per unique audience and attaches it as Authorization: Bearer on each server before returning.
  • Token acquisition is injectable (TokenAcquirer): env-var-based in dev, OBO-based in prod, keeping dev and prod paths structurally identical.
  • All three extensions (Claude, LangChain, OpenAI) merge per-audience headers from server.headers with priority over the shared discovery token.
  • Gateway and Utility URLs updated to /agents/v2/.

Bugs fixed in this branch

  • Bearer token fallback in dev (BEARER_TOKEN_<NAME>  BEARER_TOKEN)
  • x-ms-agentid header missing when using TurnContext overload
  • Legacy path V2 servers now get explicit migration error instead of silent wrong token

Post-review fixes (latest commit)

  • resolveTokenScopeForServer now accepts the configured mcpPlatformAuthenticationScope as sharedScope parameter — previously used a hardcoded constant, causing false migration errors and wrong OBO scopes when the auth scope was overridden via ToolingConfiguration
  • publisher field now preserved through gateway and manifest normalization (was declared in the interface but silently dropped)
  • GetToolingGatewayForDigitalWorker JSDoc example URL updated to /agents/v2/
  • CHANGELOG [Unreleased] section updated with Breaking Changes entry and migration guide

Test plan

  •  pnpm test — 1233 tests pass (5 new tests for custom-scope resolveTokenScopeForServer)
  •  pnpm build — clean TypeScript build (CJS + ESM) across all 9 packages
  •  V1 server (no audience): receives shared ATG token via OBO
  •  V2 server (unique audience): receives per-audience OBO token
  •  Two V2 servers sharing same audience: exactly one OBO call (token cache)
  •  Legacy overload + V2 server: throws with migration message pointing to TurnContext overload
  •  Custom mcpPlatformAuthenticationScope override: V1 fallback uses configured scope, no false migration error
  •  Dev mode: BEARER_TOKEN_<SERVERNAME> attaches per-server; BEARER_TOKEN as shared fallback

@biswapm biswapm requested review from ajmfehr and gwharris7 March 30, 2026 07:52
@biswapm biswapm marked this pull request as ready for review April 15, 2026 14:33
@biswapm biswapm requested review from a team as code owners April 15, 2026 14:33
Copilot AI review requested due to automatic review settings April 15, 2026 14:33
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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/scope fields 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 Authorization from server.headers instead 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 checks BEARER_TOKEN_<SERVERNAME_UPPER>. This is likely to mislead callers and also contradicts the comments in createDevTokenAcquirer() that describe a shared fallback token. Either implement the fallback (per-server first, then process.env.BEARER_TOKEN) or update the docstrings/comments to match the actual behavior.
    packages/agents-a365-tooling/src/McpToolServerConfigurationService.ts:1
  • MCPServerConfig now includes publisher?: string, but this gateway mapping drops it. This loses data even when the gateway returns publisher, and it conflicts with the PR description ("preserve audience / scope" plus manifest parsing passthrough). Include publisher: 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 publisher field (MCPServerManifestEntry.publisher). If publisher is intended to be preserved (contracts + PR description suggest it is), add publisher: s.publisher here too to avoid silently discarding manifest metadata.
    tests/observability/extension/helpers/message-schema-validator.ts:1
  • validateMessageEnvelope currently requires messages to 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 that messages is an array (and leave emptiness checks to expectValidInputMessages/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 ATG api:// URI form as V1 and treats other api://... 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.

Comment thread packages/agents-a365-observability/docs/design.md
biswapm and others added 10 commits April 16, 2026 12:09
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.
@biswapm biswapm force-pushed the pmohapatra-MCP-V1-V2-nodeJs branch from b11dc9b to e7f4ccf Compare April 16, 2026 06:49
biswapm and others added 2 commits April 16, 2026 13:37
… 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.
Copilot AI review requested due to automatic review settings April 16, 2026 08:51
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 10 out of 10 changed files in this pull request and generated 3 comments.

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.
Copilot AI review requested due to automatic review settings April 16, 2026 14:41
@biswapm biswapm force-pushed the pmohapatra-MCP-V1-V2-nodeJs branch from d5a2c42 to 4ea9f41 Compare April 16, 2026 14:41
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 10 out of 10 changed files in this pull request and generated 2 comments.

@gwharris7 gwharris7 merged commit 9f76baa into main Apr 16, 2026
7 checks passed
@gwharris7 gwharris7 deleted the pmohapatra-MCP-V1-V2-nodeJs branch April 16, 2026 17:28
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.

4 participants