Skip to content

Commit 306c026

Browse files
pontemontiJohan Broberg
andauthored
Add auth_handler_name parameter to Authorization.exchange_token call (#55)
* Add auth_handler_name parameter to Authorization.exchange_token call * Add support for resolving agent identity. --------- Co-authored-by: Johan Broberg <johanb@microsoft.com>
1 parent e965247 commit 306c026

File tree

8 files changed

+272
-13
lines changed

8 files changed

+272
-13
lines changed

libraries/microsoft-agents-a365-runtime/microsoft_agents_a365/runtime/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,13 @@
22

33
from .environment_utils import get_observability_authentication_scope
44
from .power_platform_api_discovery import ClusterCategory, PowerPlatformApiDiscovery
5+
from .utility import Utility
56

67
__all__ = [
78
"get_observability_authentication_scope",
89
"PowerPlatformApiDiscovery",
910
"ClusterCategory",
11+
"Utility",
1012
]
1113

1214
__path__ = __import__("pkgutil").extend_path(__path__, __name__)
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
# Copyright (c) Microsoft. All rights reserved.
2+
3+
"""
4+
Utility functions for Microsoft Agent 365 runtime operations.
5+
6+
This module provides utility functions for token handling, agent identity resolution,
7+
and other common runtime operations.
8+
"""
9+
10+
from __future__ import annotations
11+
12+
import uuid
13+
from typing import Any, Optional
14+
15+
import jwt
16+
17+
18+
class Utility:
19+
"""
20+
Utility class providing common runtime operations for Agent 365.
21+
22+
This class contains static methods for token processing, agent identity resolution,
23+
and other utility functions used across the Agent 365 runtime.
24+
"""
25+
26+
@staticmethod
27+
def get_app_id_from_token(token: Optional[str]) -> str:
28+
"""
29+
Decodes the current token and retrieves the App ID (appid or azp claim).
30+
31+
Args:
32+
token: JWT token to decode. Can be None or empty.
33+
34+
Returns:
35+
str: The App ID from the token's claims, or empty GUID if token is invalid.
36+
Returns "00000000-0000-0000-0000-000000000000" if no valid App ID is found.
37+
"""
38+
if not token or not token.strip():
39+
return str(uuid.UUID(int=0))
40+
41+
try:
42+
# Decode the JWT token without verification (we only need the claims)
43+
# Note: verify=False is used because we only need to extract claims,
44+
# not verify the token's authenticity
45+
decoded_payload = jwt.decode(token, options={"verify_signature": False})
46+
47+
# Look for appid or azp claims (appid takes precedence)
48+
app_id = decoded_payload.get("appid") or decoded_payload.get("azp")
49+
return app_id if app_id else ""
50+
51+
except (jwt.DecodeError, jwt.InvalidTokenError):
52+
# Token is malformed or invalid
53+
return ""
54+
55+
@staticmethod
56+
def resolve_agent_identity(context: Any, auth_token: Optional[str]) -> str:
57+
"""
58+
Resolves the agent identity from the turn context or auth token.
59+
60+
Args:
61+
context: Turn context of the conversation turn. Expected to have an Activity
62+
with methods like is_agentic_request() and get_agentic_instance_id().
63+
auth_token: Authentication token if available.
64+
65+
Returns:
66+
str: The agent identity (App ID). Returns the agentic instance ID if the
67+
request is agentic, otherwise returns the App ID from the auth token.
68+
"""
69+
try:
70+
# App ID is required to pass to MCP server URL
71+
# Try to get agentic instance ID if this is an agentic request
72+
if context and context.activity and context.activity.is_agentic_request():
73+
agentic_id = context.activity.get_agentic_instance_id()
74+
return agentic_id if agentic_id else ""
75+
76+
except (AttributeError, TypeError, Exception):
77+
# Context/activity doesn't have the expected methods or properties
78+
# or any other error occurred while accessing context/activity
79+
pass
80+
81+
# Fallback to extracting App ID from the auth token
82+
return Utility.get_app_id_from_token(auth_token)

libraries/microsoft-agents-a365-runtime/pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ classifiers = [
2525
license = {text = "MIT"}
2626
keywords = ["observability", "telemetry", "tracing", "opentelemetry", "monitoring", "ai", "agents"]
2727
dependencies = [
28+
"PyJWT >= 2.8.0",
2829
]
2930

3031
[project.urls]

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

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
from microsoft_agents.hosting.core import Authorization, TurnContext
1111

12+
from microsoft_agents_a365.runtime.utility import Utility
1213
from microsoft_agents_a365.tooling.services.mcp_tool_server_configuration_service import (
1314
McpToolServerConfigurationService,
1415
)
@@ -45,8 +46,8 @@ async def add_tool_servers_to_agent(
4546
chat_client: Union[OpenAIChatClient, AzureOpenAIChatClient],
4647
agent_instructions: str,
4748
initial_tools: List[Any],
48-
agentic_app_id: str,
4949
auth: Authorization,
50+
auth_handler_name: str,
5051
turn_context: TurnContext,
5152
auth_token: Optional[str] = None,
5253
) -> Optional[ChatAgent]:
@@ -57,8 +58,8 @@ async def add_tool_servers_to_agent(
5758
chat_client: The chat client instance (Union[OpenAIChatClient, AzureOpenAIChatClient])
5859
agent_instructions: Instructions for the agent behavior
5960
initial_tools: List of initial tools to add to the agent
60-
agentic_app_id: Agentic app identifier for the agent
6161
auth: Authorization context for token exchange
62+
auth_handler_name: Name of the authorization handler.
6263
turn_context: Turn context for the operation
6364
auth_token: Optional bearer token for authentication
6465
@@ -69,9 +70,11 @@ async def add_tool_servers_to_agent(
6970
# Exchange token if not provided
7071
if not auth_token:
7172
scopes = get_mcp_platform_authentication_scope()
72-
authToken = await auth.exchange_token(turn_context, scopes, "AGENTIC")
73+
authToken = await auth.exchange_token(turn_context, scopes, auth_handler_name)
7374
auth_token = authToken.token
7475

76+
agentic_app_id = Utility.resolve_agent_identity(turn_context, auth_token)
77+
7578
self._logger.info(f"Listing MCP tool servers for agent {agentic_app_id}")
7679

7780
# Get MCP server configurations

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

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
from azure.identity import DefaultAzureCredential
1818
from azure.ai.agents.models import McpTool, ToolResources
1919
from microsoft_agents.hosting.core import Authorization, TurnContext
20+
from microsoft_agents_a365.runtime.utility import Utility
2021
from microsoft_agents_a365.tooling.services.mcp_tool_server_configuration_service import (
2122
McpToolServerConfigurationService,
2223
)
@@ -69,8 +70,8 @@ def __init__(
6970
async def add_tool_servers_to_agent(
7071
self,
7172
project_client: "AIProjectClient",
72-
agentic_app_id: str,
7373
auth: Authorization,
74+
auth_handler_name: str,
7475
context: TurnContext,
7576
auth_token: Optional[str] = None,
7677
) -> None:
@@ -79,7 +80,9 @@ async def add_tool_servers_to_agent(
7980
8081
Args:
8182
project_client: The Azure Foundry AIProjectClient instance.
82-
agentic_app_id: Agentic App ID for the agent.
83+
auth: Authorization handler for token exchange.
84+
auth_handler_name: Name of the authorization handler.
85+
context: Turn context for the current operation.
8386
auth_token: Authentication token to access the MCP servers.
8487
8588
Raises:
@@ -91,10 +94,11 @@ async def add_tool_servers_to_agent(
9194

9295
if not auth_token:
9396
scopes = get_mcp_platform_authentication_scope()
94-
authToken = await auth.exchange_token(context, scopes, "AGENTIC")
97+
authToken = await auth.exchange_token(context, scopes, auth_handler_name)
9598
auth_token = authToken.token
9699

97100
try:
101+
agentic_app_id = Utility.resolve_agent_identity(context, auth_token)
98102
# Get the tool definitions and resources using the async implementation
99103
tool_definitions, tool_resources = await self._get_mcp_tool_definitions_and_resources(
100104
agentic_app_id, auth_token or ""

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

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
MCPServerStreamableHttp,
1313
MCPServerStreamableHttpParams,
1414
)
15+
from microsoft_agents_a365.runtime.utility import Utility
1516
from microsoft_agents_a365.tooling.services.mcp_tool_server_configuration_service import (
1617
McpToolServerConfigurationService,
1718
)
@@ -50,8 +51,8 @@ def __init__(self, logger: Optional[logging.Logger] = None):
5051
async def add_tool_servers_to_agent(
5152
self,
5253
agent: Agent,
53-
agentic_app_id: str,
5454
auth: Authorization,
55+
auth_handler_name: str,
5556
context: TurnContext,
5657
auth_token: Optional[str] = None,
5758
):
@@ -64,22 +65,25 @@ async def add_tool_servers_to_agent(
6465
6566
Args:
6667
agent: The existing agent to add servers to
67-
agentic_app_id: Agentic App ID for the agent
68-
auth_token: Authentication token to access the MCP servers
68+
auth: Authorization handler for token exchange.
69+
auth_handler_name: Name of the authorization handler.
70+
context: Turn context for the current operation.
71+
auth_token: Authentication token to access the MCP servers.
6972
7073
Returns:
7174
New Agent instance with all MCP servers, or original agent if no new servers
7275
"""
7376

7477
if not auth_token:
7578
scopes = get_mcp_platform_authentication_scope()
76-
authToken = await auth.exchange_token(context, scopes, "AGENTIC")
79+
authToken = await auth.exchange_token(context, scopes, auth_handler_name)
7780
auth_token = authToken.token
7881

7982
# Get MCP server configurations from the configuration service
8083
# mcp_server_configs = []
8184
# TODO: radevika: Update once the common project is merged.
8285

86+
agentic_app_id = Utility.resolve_agent_identity(context, auth_token)
8387
self._logger.info(f"Listing MCP tool servers for agent {agentic_app_id}")
8488
mcp_server_configs = await self.config_service.list_tool_servers(
8589
agentic_app_id=agentic_app_id,

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

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
from semantic_kernel import kernel as sk
1717
from semantic_kernel.connectors.mcp import MCPStreamableHttpPlugin
1818
from microsoft_agents.hosting.core import Authorization, TurnContext
19+
from microsoft_agents_a365.runtime.utility import Utility
1920
from microsoft_agents_a365.tooling.services.mcp_tool_server_configuration_service import (
2021
McpToolServerConfigurationService,
2122
)
@@ -77,8 +78,8 @@ def __init__(
7778
async def add_tool_servers_to_agent(
7879
self,
7980
kernel: sk.Kernel,
80-
agentic_app_id: str,
8181
auth: Authorization,
82+
auth_handler_name: str,
8283
context: TurnContext,
8384
auth_token: Optional[str] = None,
8485
) -> None:
@@ -87,7 +88,9 @@ async def add_tool_servers_to_agent(
8788
8889
Args:
8990
kernel: The Semantic Kernel instance to which the tools will be added.
90-
agentic_app_id: Agentic App ID for the agent.
91+
auth: Authorization handler for token exchange.
92+
auth_handler_name: Name of the authorization handler.
93+
context: Turn context for the current operation.
9194
auth_token: Authentication token to access the MCP servers.
9295
9396
Raises:
@@ -97,9 +100,10 @@ async def add_tool_servers_to_agent(
97100

98101
if not auth_token:
99102
scopes = get_mcp_platform_authentication_scope()
100-
authToken = await auth.exchange_token(context, scopes, "AGENTIC")
103+
authToken = await auth.exchange_token(context, scopes, auth_handler_name)
101104
auth_token = authToken.token
102105

106+
agentic_app_id = Utility.resolve_agent_identity(context, auth_token)
103107
self._validate_inputs(kernel, agentic_app_id, auth_token)
104108

105109
# Get and process servers

0 commit comments

Comments
 (0)