Skip to content

Commit 0eadad0

Browse files
authored
Merge branch 'main' into users/sellak/remove-backend-path-from-pyprojects
2 parents 2b6c9ae + dc936b1 commit 0eadad0

19 files changed

Lines changed: 2632 additions & 306 deletions

File tree

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

Lines changed: 45 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,19 @@
11
# Copyright (c) Microsoft Corporation.
22
# Licensed under the MIT License.
33

4+
from __future__ import annotations
5+
46
import logging
57
import uuid
68
from datetime import datetime, timezone
7-
from typing import Any, List, Optional, Sequence, Union
9+
from typing import TYPE_CHECKING, List, Optional, Sequence
810

9-
from agent_framework import RawAgent, Message, BaseHistoryProvider, MCPStreamableHTTPTool
10-
from agent_framework.azure import AzureOpenAIChatClient
11-
from agent_framework.openai import OpenAIChatClient
11+
from agent_framework import RawAgent, Message, HistoryProvider, MCPStreamableHTTPTool
1212
import httpx
1313

14+
if TYPE_CHECKING:
15+
from agent_framework.openai import OpenAIChatClient
16+
1417
from microsoft_agents.hosting.core import Authorization, TurnContext
1518

1619
from microsoft_agents_a365.runtime import OperationResult
@@ -22,6 +25,7 @@
2225
from microsoft_agents_a365.tooling.utils.constants import Constants
2326
from microsoft_agents_a365.tooling.utils.utility import (
2427
get_mcp_platform_authentication_scope,
28+
is_development_environment,
2529
)
2630

2731

@@ -55,9 +59,9 @@ def __init__(self, logger: Optional[logging.Logger] = None):
5559

5660
async def add_tool_servers_to_agent(
5761
self,
58-
chat_client: Union[OpenAIChatClient, AzureOpenAIChatClient],
62+
chat_client: OpenAIChatClient,
5963
agent_instructions: str,
60-
initial_tools: List[Any],
64+
initial_tools: List[object],
6165
auth: Authorization,
6266
auth_handler_name: str,
6367
turn_context: TurnContext,
@@ -67,7 +71,7 @@ async def add_tool_servers_to_agent(
6771
Add MCP tool servers to a RawAgent (mirrors .NET implementation).
6872
6973
Args:
70-
chat_client: The chat client instance (Union[OpenAIChatClient, AzureOpenAIChatClient])
74+
chat_client: The chat client instance (OpenAIChatClient supports both OpenAI and Azure OpenAI)
7175
agent_instructions: Instructions for the agent behavior
7276
initial_tools: List of initial tools to add to the agent
7377
auth: Authorization context for token exchange
@@ -82,23 +86,31 @@ async def add_tool_servers_to_agent(
8286
Exception: If agent creation fails.
8387
"""
8488
try:
85-
# Exchange token if not provided
86-
if not auth_token:
89+
is_dev = is_development_environment()
90+
if not auth_token and not is_dev:
91+
# Only exchange a token in production; dev mode uses BEARER_TOKEN* env vars instead.
8792
scopes = get_mcp_platform_authentication_scope()
8893
authToken = await auth.exchange_token(turn_context, scopes, auth_handler_name)
8994
auth_token = authToken.token
9095

91-
agentic_app_id = Utility.resolve_agent_identity(turn_context, auth_token)
96+
# In dev mode, agentic_app_id is not needed for manifest-based discovery.
97+
agentic_app_id = (
98+
"" if is_dev else Utility.resolve_agent_identity(turn_context, auth_token)
99+
)
92100

93101
self._logger.info(f"Listing MCP tool servers for agent {agentic_app_id}")
94102

95103
options = ToolOptions(orchestrator_name=self._orchestrator_name)
96104

97-
# Get MCP server configurations
105+
# Get MCP server configurations — pass auth context so each server receives
106+
# its own per-audience Authorization token (V1 = shared ATG, V2 = per-GUID).
98107
server_configs = await self._mcp_server_configuration_service.list_tool_servers(
99108
agentic_app_id=agentic_app_id,
100109
auth_token=auth_token,
101110
options=options,
111+
authorization=auth,
112+
auth_handler_name=auth_handler_name,
113+
turn_context=turn_context,
102114
)
103115

104116
self._logger.info(f"Loaded {len(server_configs)} MCP server configurations")
@@ -112,16 +124,26 @@ async def add_tool_servers_to_agent(
112124
server_name = config.mcp_server_name or config.mcp_server_unique_name
113125

114126
try:
115-
# Prepare auth headers
116-
headers = {}
117-
if auth_token:
118-
headers[Constants.Headers.AUTHORIZATION] = (
119-
f"{Constants.Headers.BEARER_PREFIX} {auth_token}"
127+
# Merge base (non-auth) headers with per-server headers from list_tool_servers.
128+
# server.headers already contains the correct per-audience Authorization token.
129+
base_headers = {
130+
Constants.Headers.USER_AGENT: Utility.get_user_agent_header(
131+
self._orchestrator_name
120132
)
121-
122-
headers[Constants.Headers.USER_AGENT] = Utility.get_user_agent_header(
123-
self._orchestrator_name
124-
)
133+
}
134+
server_headers = dict(config.headers) if config.headers else {}
135+
# Fall back to the shared discovery token when no per-server
136+
# Authorization header was attached (e.g. dev mode without
137+
# BEARER_TOKEN* env vars, or legacy V1 callers).
138+
if Constants.Headers.AUTHORIZATION not in server_headers and auth_token:
139+
server_headers[Constants.Headers.AUTHORIZATION] = (
140+
auth_token
141+
if auth_token.lower().startswith(
142+
f"{Constants.Headers.BEARER_PREFIX.lower()} "
143+
)
144+
else f"{Constants.Headers.BEARER_PREFIX} {auth_token}"
145+
)
146+
headers = {**base_headers, **server_headers} # server auth takes precedence
125147

126148
# Create httpx client with auth headers configured
127149
http_client = httpx.AsyncClient(
@@ -307,18 +329,18 @@ async def send_chat_history_messages(
307329

308330
async def send_chat_history_from_store(
309331
self,
310-
chat_message_store: BaseHistoryProvider,
332+
chat_message_store: HistoryProvider,
311333
turn_context: TurnContext,
312334
tool_options: Optional[ToolOptions] = None,
313335
) -> OperationResult:
314336
"""
315-
Send chat history from a BaseHistoryProvider to the MCP platform.
337+
Send chat history from a HistoryProvider to the MCP platform.
316338
317339
This is a convenience method that extracts messages from the store
318340
and delegates to send_chat_history_messages().
319341
320342
Args:
321-
chat_message_store: BaseHistoryProvider containing the conversation history.
343+
chat_message_store: HistoryProvider containing the conversation history.
322344
turn_context: TurnContext from the Agents SDK containing conversation info.
323345
tool_options: Optional configuration for the request.
324346

libraries/microsoft-agents-a365-tooling-extensions-agentframework/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ license = {text = "MIT"}
2525
dependencies = [
2626
"microsoft-agents-a365-tooling",
2727
"microsoft-agents-hosting-core",
28-
"agent-framework-azure-ai",
28+
"agent-framework",
2929
"azure-identity",
3030
"typing-extensions",
3131
"httpx",

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

Lines changed: 37 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,10 @@
2727
McpToolServerConfigurationService,
2828
)
2929
from microsoft_agents_a365.tooling.utils.constants import Constants
30-
from microsoft_agents_a365.tooling.utils.utility import get_mcp_platform_authentication_scope
30+
from microsoft_agents_a365.tooling.utils.utility import (
31+
get_mcp_platform_authentication_scope,
32+
is_development_environment,
33+
)
3134

3235

3336
class McpToolRegistrationService:
@@ -99,16 +102,18 @@ async def add_tool_servers_to_agent(
99102
if project_client is None:
100103
raise ValueError("project_client cannot be None")
101104

102-
if not auth_token:
105+
is_dev = is_development_environment()
106+
if not auth_token and not is_dev:
103107
scopes = get_mcp_platform_authentication_scope()
104108
authToken = await auth.exchange_token(context, scopes, auth_handler_name)
105109
auth_token = authToken.token
106110

107111
try:
108-
agentic_app_id = Utility.resolve_agent_identity(context, auth_token)
109-
# Get the tool definitions and resources using the async implementation
112+
agentic_app_id = "" if is_dev else Utility.resolve_agent_identity(context, auth_token)
113+
# Get the tool definitions and resources — pass auth context so each server receives
114+
# its own per-audience Authorization token (V1 = shared ATG, V2 = per-GUID).
110115
tool_definitions, tool_resources = await self._get_mcp_tool_definitions_and_resources(
111-
agentic_app_id, auth_token or ""
116+
agentic_app_id, auth_token or "", auth, auth_handler_name, context
112117
)
113118

114119
# Update the agent with the tools
@@ -127,7 +132,12 @@ async def add_tool_servers_to_agent(
127132
raise
128133

129134
async def _get_mcp_tool_definitions_and_resources(
130-
self, agentic_app_id: str, auth_token: str
135+
self,
136+
agentic_app_id: str,
137+
auth_token: str,
138+
authorization: Authorization,
139+
auth_handler_name: str,
140+
turn_context: TurnContext,
131141
) -> Tuple[List[McpTool], Optional[ToolResources]]:
132142
"""
133143
Internal method to get MCP tool definitions and resources.
@@ -136,7 +146,10 @@ async def _get_mcp_tool_definitions_and_resources(
136146
137147
Args:
138148
agentic_app_id: Agentic App ID for the agent.
139-
auth_token: Authentication token to access the MCP servers.
149+
auth_token: Authentication token used for gateway discovery.
150+
authorization: Authorization context for per-audience token exchange.
151+
auth_handler_name: Auth handler name for per-audience token exchange.
152+
turn_context: TurnContext for per-audience token exchange.
140153
141154
Returns:
142155
Tuple containing tool definitions and resources.
@@ -145,11 +158,17 @@ async def _get_mcp_tool_definitions_and_resources(
145158
self._logger.error("MCP server configuration service is not available")
146159
return ([], None)
147160

148-
# Get MCP server configurations
161+
# Get MCP server configurations — pass auth context so each server receives
162+
# its own per-audience Authorization token (V1 = shared ATG, V2 = per-GUID).
149163
options = ToolOptions(orchestrator_name=self._orchestrator_name)
150164
try:
151165
servers = await self._mcp_server_configuration_service.list_tool_servers(
152-
agentic_app_id, auth_token, options
166+
agentic_app_id,
167+
auth_token,
168+
options,
169+
authorization=authorization,
170+
auth_handler_name=auth_handler_name,
171+
turn_context=turn_context,
153172
)
154173
except Exception as ex:
155174
self._logger.error(
@@ -191,14 +210,19 @@ async def _get_mcp_tool_definitions_and_resources(
191210
# Configure the tool
192211
mcp_tool.set_approval_mode("never")
193212

194-
# Set up authorization header
195-
if auth_token:
196-
header_value = (
213+
# Set per-server headers: use per-audience token from list_tool_servers
214+
# (V1 servers get the shared ATG token, V2 servers get their own audience token).
215+
# Fall back to the shared discovery auth_token only if no per-server header is set.
216+
server_headers = dict(server.headers) if server.headers else {}
217+
auth_header = server_headers.get(Constants.Headers.AUTHORIZATION)
218+
if not auth_header and auth_token:
219+
auth_header = (
197220
auth_token
198221
if auth_token.lower().startswith(f"{Constants.Headers.BEARER_PREFIX.lower()} ")
199222
else f"{Constants.Headers.BEARER_PREFIX} {auth_token}"
200223
)
201-
mcp_tool.update_headers(Constants.Headers.AUTHORIZATION, header_value)
224+
if auth_header:
225+
mcp_tool.update_headers(Constants.Headers.AUTHORIZATION, auth_header)
202226

203227
mcp_tool.update_headers(
204228
Constants.Headers.USER_AGENT, Utility.get_user_agent_header(self._orchestrator_name)

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

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
from microsoft_agents_a365.tooling.utils.constants import Constants
2727
from microsoft_agents_a365.tooling.utils.utility import (
2828
get_mcp_platform_authentication_scope,
29+
is_development_environment,
2930
)
3031

3132

@@ -76,19 +77,25 @@ async def add_tool_servers_to_agent(
7677
Returns:
7778
None
7879
"""
79-
if not auth_token:
80+
is_dev = is_development_environment()
81+
if not auth_token and not is_dev:
82+
# Only exchange a token in production; dev mode uses BEARER_TOKEN* env vars instead.
8083
scopes = get_mcp_platform_authentication_scope()
8184
auth_token_obj = await auth.exchange_token(context, scopes, auth_handler_name)
8285
auth_token = auth_token_obj.token
8386

84-
agentic_app_id = Utility.resolve_agent_identity(context, auth_token)
87+
# In dev mode, agentic_app_id is not needed for manifest-based discovery.
88+
agentic_app_id = "" if is_dev else Utility.resolve_agent_identity(context, auth_token)
8589
self._logger.info(f"Listing MCP tool servers for agent {agentic_app_id}")
8690

8791
options = ToolOptions(orchestrator_name=self._orchestrator_name)
8892
mcp_server_configs = await self._mcp_server_configuration_service.list_tool_servers(
8993
agentic_app_id=agentic_app_id,
9094
auth_token=auth_token,
9195
options=options,
96+
authorization=auth,
97+
auth_handler_name=auth_handler_name,
98+
turn_context=context,
9299
)
93100

94101
self._logger.info(f"Loaded {len(mcp_server_configs)} MCP server configurations")
@@ -104,10 +111,6 @@ async def add_tool_servers_to_agent(
104111

105112
# Convert MCP server configs to McpToolset objects (only new ones)
106113
mcp_servers_info = []
107-
mcp_server_headers = {
108-
Constants.Headers.AUTHORIZATION: f"{Constants.Headers.BEARER_PREFIX} {auth_token}",
109-
Constants.Headers.USER_AGENT: Utility.get_user_agent_header(self._orchestrator_name),
110-
}
111114

112115
for server_config in mcp_server_configs:
113116
# Skip if server URL already exists
@@ -119,10 +122,29 @@ async def add_tool_servers_to_agent(
119122
continue
120123

121124
try:
125+
base_headers = {
126+
Constants.Headers.USER_AGENT: Utility.get_user_agent_header(
127+
self._orchestrator_name
128+
)
129+
}
130+
server_headers = dict(server_config.headers) if server_config.headers else {}
131+
# Fall back to the shared discovery token when no per-server
132+
# Authorization header was attached (e.g. dev mode without
133+
# BEARER_TOKEN* env vars, or legacy V1 callers).
134+
if Constants.Headers.AUTHORIZATION not in server_headers and auth_token:
135+
server_headers[Constants.Headers.AUTHORIZATION] = (
136+
auth_token
137+
if auth_token.lower().startswith(
138+
f"{Constants.Headers.BEARER_PREFIX.lower()} "
139+
)
140+
else f"{Constants.Headers.BEARER_PREFIX} {auth_token}"
141+
)
142+
headers = {**base_headers, **server_headers} # per-audience token takes precedence
143+
122144
server_info = McpToolset(
123145
connection_params=StreamableHTTPConnectionParams(
124146
url=server_config.url,
125-
headers=mcp_server_headers,
147+
headers=headers,
126148
)
127149
)
128150

0 commit comments

Comments
 (0)