Skip to content

Commit 74451d7

Browse files
committed
Extend V1/V2 per-audience token support to all SDK extensions
- Pass authorization/auth_handler_name/turn_context to list_tool_servers in OpenAI and Semantic Kernel extensions so _attach_per_audience_tokens runs for all frameworks (Google ADK already staged in prior commit) - Replace shared single-token header injection with per-server header merge ({**base_headers, **server.headers}) in OpenAI and SK extensions - Use explicit scope for V2 token resolution: {audience}/{scope} when scope is present, {audience}/.default as pre-consented fallback - Fix api:// V2 audience handling — only ATG AppId in api:// URI form is treated as V1; all other api:// audiences are correctly V2 - Add comprehensive tests for resolve_token_scope_for_server covering all V1/V2/null/api:// scenarios including test env audience handling - Add CHANGELOG entry for microsoft-agents-a365-tooling package Backward compatible: V1 agents (null/ATG audience) continue using the shared ATG token unchanged. V2 blueprint + old SDK is by design not supported — upgrade to this SDK is the migration path.
1 parent d84c41d commit 74451d7

4 files changed

Lines changed: 32 additions & 8 deletions

File tree

libraries/microsoft-agents-a365-tooling-extensions-openai/microsoft_agents_a365/tooling/extensions/openai/mcp_tool_registration_service.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,10 @@ async def add_tool_servers_to_agent(
161161
)
162162
}
163163
server_headers = dict(si.headers) if si.headers else {}
164-
headers = {**base_headers, **server_headers} # per-audience token takes precedence
164+
headers = {
165+
**base_headers,
166+
**server_headers,
167+
} # per-audience token takes precedence
165168

166169
# Create MCPServerStreamableHttpParams with proper configuration
167170
params = MCPServerStreamableHttpParams(url=si.url, headers=headers)

libraries/microsoft-agents-a365-tooling-extensions-semantickernel/microsoft_agents_a365/tooling/extensions/semantickernel/services/mcp_tool_registration_service.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,9 @@ async def add_tool_servers_to_agent(
119119
# Get and process servers
120120
options = ToolOptions(orchestrator_name=self._orchestrator_name)
121121
servers = await self._mcp_server_configuration_service.list_tool_servers(
122-
agentic_app_id, auth_token, options,
122+
agentic_app_id,
123+
auth_token,
124+
options,
123125
authorization=auth,
124126
auth_handler_name=auth_handler_name,
125127
turn_context=context,

libraries/microsoft-agents-a365-tooling/CHANGELOG.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
99

1010
### Added
1111

12+
- Added V1/V2 per-audience token acquisition support in `McpToolServerConfigurationService.list_tool_servers()`. When `authorization`, `auth_handler_name`, and `turn_context` are provided, each MCP server receives its own OAuth token scoped to its audience — V1 servers (no audience, or shared ATG AppId) share a single ATG-scoped token; V2 servers (unique non-ATG audience GUID or `api://` URI) each receive a token scoped to `{audience}/{scope}` (or `{audience}/.default` when scope is absent and pre-consented)
13+
- Added `_attach_per_audience_tokens()` private method to `McpToolServerConfigurationService` — acquires one token per unique scope, caches within the call to avoid redundant exchanges, and attaches `Authorization: Bearer` headers to each server config
14+
- Added `resolve_token_scope_for_server()` utility function to derive the correct OAuth scope for a given `MCPServerConfig` based on its `audience` and `scope` fields
15+
- Added `audience`, `scope`, `publisher`, and `headers` fields to `MCPServerConfig`
16+
- Gateway discovery endpoint bumped to `/agents/v2/{id}/mcpServers`
17+
- `_parse_gateway_server_config()` and `_parse_manifest_server_config()` now map `audience`, `scope`, and `publisher` fields from gateway/manifest responses into `MCPServerConfig`
18+
19+
### Changed
20+
21+
- OpenAI, Semantic Kernel, and Google ADK extensions now pass auth context to `list_tool_servers()` and merge per-server headers (`{**base_headers, **server.headers}`) instead of injecting a single shared ATG token for all servers — fully backward compatible, V1 agents continue to receive the same shared ATG token
22+
23+
### Notes
24+
25+
- **Backward compatible**: agents with V1 manifests (null audience or shared ATG AppId) work identically with the new SDK — no token exchange behaviour changes
26+
- **Migration required for V2**: agents upgraded to V2 blueprint permissions (per-audience MCP servers) require this SDK version. Running a V2 blueprint with the old SDK will result in MCP tool auth failures (401/403)
27+
1228
- Added `send_chat_history` method to `McpToolServerConfigurationService` for sending chat conversation history to the MCP platform for real-time threat protection analysis
1329
- Added `ChatHistoryMessage` Pydantic model for representing individual messages in chat history
1430
- Added `ChatMessageRequest` Pydantic model for the chat history API request payload

tests/tooling/test_mcp_server_configuration.py

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -323,7 +323,10 @@ def test_v2_api_uri_audience_with_explicit_scope(self):
323323
server = self._make_server(
324324
audience="api://mcp-calendartools", scope="McpServers.Calendar.All"
325325
)
326-
assert resolve_token_scope_for_server(server) == "api://mcp-calendartools/McpServers.Calendar.All"
326+
assert (
327+
resolve_token_scope_for_server(server)
328+
== "api://mcp-calendartools/McpServers.Calendar.All"
329+
)
327330

328331
def test_v2_guid_audience_null_scope_falls_back_to_default(self):
329332
"""V2: unique GUID audience + null scope → <guid>/.default (pre-consented)."""
@@ -420,11 +423,11 @@ async def test_mixed_v1_v2_servers_deduplicate_by_scope(self, service):
420423
guid1 = "aaaaaaaa-0000-0000-0000-000000000001"
421424
guid2 = "bbbbbbbb-0000-0000-0000-000000000002"
422425
servers = [
423-
self._make_server("mail"), # V1
424-
self._make_server("cal1", guid1), # V2 guid1
425-
self._make_server("cal2", guid2), # V2 guid2
426-
self._make_server("files"), # V1 (same scope as mail → no 2nd exchange)
427-
self._make_server("cal3", guid1), # V2 guid1 again → no 2nd exchange
426+
self._make_server("mail"), # V1
427+
self._make_server("cal1", guid1), # V2 guid1
428+
self._make_server("cal2", guid2), # V2 guid2
429+
self._make_server("files"), # V1 (same scope as mail → no 2nd exchange)
430+
self._make_server("cal3", guid1), # V2 guid1 again → no 2nd exchange
428431
]
429432

430433
authorization = MagicMock()

0 commit comments

Comments
 (0)