Skip to content

feat(agentsts): lazy STS token exchange with per-audience scoping#1431

Open
syn-zhu wants to merge 3 commits intokagent-dev:mainfrom
syn-zhu:feat/lazy-sts-exchange
Open

feat(agentsts): lazy STS token exchange with per-audience scoping#1431
syn-zhu wants to merge 3 commits intokagent-dev:mainfrom
syn-zhu:feat/lazy-sts-exchange

Conversation

@syn-zhu
Copy link

@syn-zhu syn-zhu commented Mar 5, 2026

Summary

Closes #1430

This PR refactors STS token exchange in ADKTokenPropagationPlugin and adds per-audience token scoping so each MCP server can receive a differently-scoped access token.

Lazy token exchange (moved from before_run_callback to before_tool_callback)

  • before_run_callback now only extracts and stores the subject token from request headers -- no HTTP round-trip to the STS
  • before_tool_callback exchanges on first MCPTool call per (session, audience) pair; skips non-MCP tools (memory tools, AgentTool, etc.)
  • Cached tokens are reused for subsequent MCP calls in the same session

Per-audience token exchange

  • New STSAudience field on RemoteMCPServerSpec (server-level default) and McpServerTool (per-agent override)
  • Go translator implements two-level override: McpServerTool.STSAudience > RemoteMCPServer.Spec.STSAudience > empty
  • Python ADKTokenPropagationPlugin tracks id(session_manager) -> audience mappings via register_toolset()
  • create_header_provider in types.py creates per-toolset closures that forward the audience to the STS header provider
  • KAgentMcpToolset carries sts_audience so to_agent() can wire it through

Cleanup

  • Removed add_to_agent method from ADKTokenPropagationPlugin -- it would overwrite the audience-aware closures created by create_header_provider, causing all audience-scoped cache lookups to miss
  • Removed unused imports (BaseAgent, LlmAgent, AuthCredential, Event, Runner, BaseSessionService, Session)
  • Made mock STS server audience-aware: generateMockAccessToken now includes aud claim in generated tokens when audience is provided

Changed files

CRD / API types (Go)

  • go/api/v1alpha2/agent_types.go -- STSAudience *string on McpServerTool
  • go/api/v1alpha2/remotemcpserver_types.go -- STSAudience *string on RemoteMCPServerSpec
  • go/api/adk/types.go -- STSAudience on HttpMcpServerConfig and SseMcpServerConfig
  • go/api/v1alpha2/zz_generated.deepcopy.go -- generated
  • Helm CRD templates updated

Translator (Go)

  • go/core/internal/controller/translator/agent/adk_api_translator.go -- audience resolution + two-level override

Golden tests (Go)

  • testdata/inputs/agent_with_sts_audience.yaml + output
  • testdata/inputs/agent_with_sts_audience_override.yaml + output
  • All existing golden outputs updated for new sts_audience field

Plugin (Python)

  • agentsts-adk/src/agentsts/adk/_base.py -- ADKTokenPropagationPlugin rewrite
  • agentsts-adk/tests/test_adk_integration.py -- comprehensive tests for all paths

ADK types (Python)

  • kagent-adk/src/kagent/adk/types.py -- create_header_provider with audience closure, to_agent wiring
  • kagent-adk/src/kagent/adk/_mcp_toolset.py -- KAgentMcpToolset.sts_audience
  • kagent-adk/tests/unittests/test_header_propagation.py -- audience forwarding tests

E2E test infrastructure (Go)

  • go/core/test/e2e/mocks/mock_sts_server.go -- audience-aware token generation

Test plan

  • All agentsts-adk unit tests pass (audience scoping, cache isolation, cleanup)
  • All agentsts-core unit tests pass (no changes to core)
  • All kagent-adk header propagation tests pass (audience forwarding)
  • Go golden tests pass for audience and audience-override scenarios
  • Go mock STS server compiles with audience parameter
  • E2E test with actual STS (Keycloak) and MCP server

Co-Authored-By: Claude Opus 4.6 noreply@anthropic.com

Copilot AI review requested due to automatic review settings March 5, 2026 05:07
Copy link
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

This PR updates the agentsts-adk integration to perform STS token exchange lazily at tool-call time (via before_tool_callback) instead of eagerly at invocation start (before_run_callback), caching the resulting access token per session for subsequent MCP tool calls.

Changes:

  • Refactors ADKTokenPropagationPlugin so before_run_callback only extracts/stores the subject token, while before_tool_callback performs (and caches) the STS exchange only for MCP tools.
  • Adds a subject-token cache (_subject_tokens) and ensures both caches are cleared in after_run_callback.
  • Updates and adds unit tests to validate the new lifecycle, including skipping non-MCP tools and no-op behavior when STS is not configured.

Reviewed changes

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

File Description
python/packages/agentsts-adk/src/agentsts/adk/_base.py Moves exchange to before_tool_callback, adds subject-token caching, updates cleanup behavior.
python/packages/agentsts-adk/tests/test_adk_integration.py Updates existing tests to match the new flow and adds coverage for non-MCP/no-STS scenarios.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Move STS token exchange from before_run_callback (eager, once per
invocation) to before_tool_callback (lazy, per MCP tool call). This
avoids the sync/async problem in header_provider and only performs the
exchange when an MCP tool is actually invoked.

- before_run_callback now only extracts and stores the subject token
- before_tool_callback exchanges on first McpTool call per session
- Non-MCP tools (memory, AgentTool, etc.) are skipped
- Cached tokens are reused for subsequent MCP calls in same session
- Sets the stage for per-audience token exchange

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Simon Zhu <simon.zhu@mongodb.com>
@syn-zhu syn-zhu force-pushed the feat/lazy-sts-exchange branch from e8c0cff to 12afde3 Compare March 5, 2026 05:13
@syn-zhu syn-zhu requested a review from ilackarms as a code owner March 5, 2026 08:54
@syn-zhu syn-zhu changed the title feat(agentsts): move STS token exchange to before_tool_callback feat(agentsts): lazy STS token exchange with per-audience scoping Mar 5, 2026
syn-zhu and others added 2 commits March 5, 2026 03:59
Signed-off-by: Simon Zhu <simon.zhu@mongodb.com>
- Remove unused imports from _base.py (BaseAgent, LlmAgent, AuthCredential, etc.)
- Remove add_to_agent method that would overwrite audience-aware closures
  created by create_header_provider in types.py
- Remove LlmAgent import and add_to_agent test code from test_adk_integration.py
- Make mock STS server include 'aud' claim in generated tokens when audience
  is provided, improving E2E test coverage for per-audience token exchange

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Simon Zhu <simon.zhu@mongodb.com>
@syn-zhu syn-zhu force-pushed the feat/lazy-sts-exchange branch from 4106e87 to cc07ac7 Compare March 5, 2026 08:59
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.

Lazy STS token exchange with per-audience scoping

2 participants