microsoft-agents-a365-tooling-extensions-agentframework 0.2.1.dev0__tar.gz → 0.2.1.dev4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {microsoft_agents_a365_tooling_extensions_agentframework-0.2.1.dev0 → microsoft_agents_a365_tooling_extensions_agentframework-0.2.1.dev4}/PKG-INFO +1 -3
- microsoft_agents_a365_tooling_extensions_agentframework-0.2.1.dev4/microsoft_agents_a365/tooling/extensions/agentframework/services/mcp_tool_registration_service.py +350 -0
- {microsoft_agents_a365_tooling_extensions_agentframework-0.2.1.dev0 → microsoft_agents_a365_tooling_extensions_agentframework-0.2.1.dev4}/microsoft_agents_a365_tooling_extensions_agentframework.egg-info/PKG-INFO +1 -3
- {microsoft_agents_a365_tooling_extensions_agentframework-0.2.1.dev0 → microsoft_agents_a365_tooling_extensions_agentframework-0.2.1.dev4}/microsoft_agents_a365_tooling_extensions_agentframework.egg-info/SOURCES.txt +0 -3
- microsoft_agents_a365_tooling_extensions_agentframework-0.2.1.dev0/microsoft_agents_a365/tooling/extensions/agentframework/services/mcp_tool_registration_service.py +0 -162
- {microsoft_agents_a365_tooling_extensions_agentframework-0.2.1.dev0 → microsoft_agents_a365_tooling_extensions_agentframework-0.2.1.dev4}/README.md +0 -0
- {microsoft_agents_a365_tooling_extensions_agentframework-0.2.1.dev0 → microsoft_agents_a365_tooling_extensions_agentframework-0.2.1.dev4}/microsoft_agents_a365/tooling/extensions/agentframework/__init__.py +0 -0
- {microsoft_agents_a365_tooling_extensions_agentframework-0.2.1.dev0 → microsoft_agents_a365_tooling_extensions_agentframework-0.2.1.dev4}/microsoft_agents_a365/tooling/extensions/agentframework/services/__init__.py +0 -0
- {microsoft_agents_a365_tooling_extensions_agentframework-0.2.1.dev0 → microsoft_agents_a365_tooling_extensions_agentframework-0.2.1.dev4}/microsoft_agents_a365_tooling_extensions_agentframework.egg-info/dependency_links.txt +0 -0
- {microsoft_agents_a365_tooling_extensions_agentframework-0.2.1.dev0 → microsoft_agents_a365_tooling_extensions_agentframework-0.2.1.dev4}/microsoft_agents_a365_tooling_extensions_agentframework.egg-info/requires.txt +0 -0
- {microsoft_agents_a365_tooling_extensions_agentframework-0.2.1.dev0 → microsoft_agents_a365_tooling_extensions_agentframework-0.2.1.dev4}/microsoft_agents_a365_tooling_extensions_agentframework.egg-info/top_level.txt +0 -0
- {microsoft_agents_a365_tooling_extensions_agentframework-0.2.1.dev0 → microsoft_agents_a365_tooling_extensions_agentframework-0.2.1.dev4}/pyproject.toml +0 -0
- {microsoft_agents_a365_tooling_extensions_agentframework-0.2.1.dev0 → microsoft_agents_a365_tooling_extensions_agentframework-0.2.1.dev4}/setup.cfg +0 -0
- {microsoft_agents_a365_tooling_extensions_agentframework-0.2.1.dev0 → microsoft_agents_a365_tooling_extensions_agentframework-0.2.1.dev4}/setup.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: microsoft-agents-a365-tooling-extensions-agentframework
|
|
3
|
-
Version: 0.2.1.
|
|
3
|
+
Version: 0.2.1.dev4
|
|
4
4
|
Summary: Agent Framework integration tools for Agent 365 AI agent tooling
|
|
5
5
|
Author-email: Microsoft <support@microsoft.com>
|
|
6
6
|
License: MIT
|
|
@@ -18,7 +18,6 @@ Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
|
18
18
|
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
19
19
|
Requires-Python: >=3.11
|
|
20
20
|
Description-Content-Type: text/markdown
|
|
21
|
-
License-File: LICENSE
|
|
22
21
|
Requires-Dist: microsoft-agents-a365-tooling>=0.0.0
|
|
23
22
|
Requires-Dist: microsoft-agents-hosting-core<0.6.0,>=0.4.0
|
|
24
23
|
Requires-Dist: agent-framework-azure-ai>=1.0.0b251114
|
|
@@ -33,7 +32,6 @@ Requires-Dist: mypy>=1.0.0; extra == "dev"
|
|
|
33
32
|
Provides-Extra: test
|
|
34
33
|
Requires-Dist: pytest>=7.0.0; extra == "test"
|
|
35
34
|
Requires-Dist: pytest-asyncio>=0.21.0; extra == "test"
|
|
36
|
-
Dynamic: license-file
|
|
37
35
|
|
|
38
36
|
# microsoft-agents-a365-tooling-extensions-agentframework
|
|
39
37
|
|
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
# Copyright (c) Microsoft Corporation.
|
|
2
|
+
# Licensed under the MIT License.
|
|
3
|
+
|
|
4
|
+
import logging
|
|
5
|
+
import uuid
|
|
6
|
+
from datetime import datetime, timezone
|
|
7
|
+
from typing import Any, List, Optional, Sequence, Union
|
|
8
|
+
|
|
9
|
+
from agent_framework import ChatAgent, ChatMessage, ChatMessageStoreProtocol, MCPStreamableHTTPTool
|
|
10
|
+
from agent_framework.azure import AzureOpenAIChatClient
|
|
11
|
+
from agent_framework.openai import OpenAIChatClient
|
|
12
|
+
|
|
13
|
+
from microsoft_agents.hosting.core import Authorization, TurnContext
|
|
14
|
+
|
|
15
|
+
from microsoft_agents_a365.runtime import OperationResult
|
|
16
|
+
from microsoft_agents_a365.runtime.utility import Utility
|
|
17
|
+
from microsoft_agents_a365.tooling.models import ChatHistoryMessage, ToolOptions
|
|
18
|
+
from microsoft_agents_a365.tooling.services.mcp_tool_server_configuration_service import (
|
|
19
|
+
McpToolServerConfigurationService,
|
|
20
|
+
)
|
|
21
|
+
from microsoft_agents_a365.tooling.utils.constants import Constants
|
|
22
|
+
from microsoft_agents_a365.tooling.utils.utility import (
|
|
23
|
+
get_mcp_platform_authentication_scope,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class McpToolRegistrationService:
|
|
28
|
+
"""
|
|
29
|
+
Provides MCP tool registration services for Agent Framework agents.
|
|
30
|
+
|
|
31
|
+
This service handles registration and management of MCP (Model Context Protocol)
|
|
32
|
+
tool servers with Agent Framework agents.
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
_orchestrator_name: str = "AgentFramework"
|
|
36
|
+
|
|
37
|
+
def __init__(self, logger: Optional[logging.Logger] = None):
|
|
38
|
+
"""
|
|
39
|
+
Initialize the MCP Tool Registration Service for Agent Framework.
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
logger: Logger instance for logging operations.
|
|
43
|
+
"""
|
|
44
|
+
self._logger = logger or logging.getLogger(self.__class__.__name__)
|
|
45
|
+
self._mcp_server_configuration_service = McpToolServerConfigurationService(
|
|
46
|
+
logger=self._logger
|
|
47
|
+
)
|
|
48
|
+
self._connected_servers = []
|
|
49
|
+
|
|
50
|
+
async def add_tool_servers_to_agent(
|
|
51
|
+
self,
|
|
52
|
+
chat_client: Union[OpenAIChatClient, AzureOpenAIChatClient],
|
|
53
|
+
agent_instructions: str,
|
|
54
|
+
initial_tools: List[Any],
|
|
55
|
+
auth: Authorization,
|
|
56
|
+
auth_handler_name: str,
|
|
57
|
+
turn_context: TurnContext,
|
|
58
|
+
auth_token: Optional[str] = None,
|
|
59
|
+
) -> Optional[ChatAgent]:
|
|
60
|
+
"""
|
|
61
|
+
Add MCP tool servers to a chat agent (mirrors .NET implementation).
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
chat_client: The chat client instance (Union[OpenAIChatClient, AzureOpenAIChatClient])
|
|
65
|
+
agent_instructions: Instructions for the agent behavior
|
|
66
|
+
initial_tools: List of initial tools to add to the agent
|
|
67
|
+
auth: Authorization context for token exchange
|
|
68
|
+
auth_handler_name: Name of the authorization handler.
|
|
69
|
+
turn_context: Turn context for the operation
|
|
70
|
+
auth_token: Optional bearer token for authentication
|
|
71
|
+
|
|
72
|
+
Returns:
|
|
73
|
+
ChatAgent instance with MCP tools registered, or None if creation failed
|
|
74
|
+
"""
|
|
75
|
+
try:
|
|
76
|
+
# Exchange token if not provided
|
|
77
|
+
if not auth_token:
|
|
78
|
+
scopes = get_mcp_platform_authentication_scope()
|
|
79
|
+
authToken = await auth.exchange_token(turn_context, scopes, auth_handler_name)
|
|
80
|
+
auth_token = authToken.token
|
|
81
|
+
|
|
82
|
+
agentic_app_id = Utility.resolve_agent_identity(turn_context, auth_token)
|
|
83
|
+
|
|
84
|
+
self._logger.info(f"Listing MCP tool servers for agent {agentic_app_id}")
|
|
85
|
+
|
|
86
|
+
options = ToolOptions(orchestrator_name=self._orchestrator_name)
|
|
87
|
+
|
|
88
|
+
# Get MCP server configurations
|
|
89
|
+
server_configs = await self._mcp_server_configuration_service.list_tool_servers(
|
|
90
|
+
agentic_app_id=agentic_app_id,
|
|
91
|
+
auth_token=auth_token,
|
|
92
|
+
options=options,
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
self._logger.info(f"Loaded {len(server_configs)} MCP server configurations")
|
|
96
|
+
|
|
97
|
+
# Create the agent with all tools (initial + MCP tools)
|
|
98
|
+
all_tools = list(initial_tools)
|
|
99
|
+
|
|
100
|
+
# Add servers as MCPStreamableHTTPTool instances
|
|
101
|
+
for config in server_configs:
|
|
102
|
+
# Use mcp_server_name if available (not None or empty), otherwise fall back to mcp_server_unique_name
|
|
103
|
+
server_name = config.mcp_server_name or config.mcp_server_unique_name
|
|
104
|
+
|
|
105
|
+
try:
|
|
106
|
+
# Prepare auth headers
|
|
107
|
+
headers = {}
|
|
108
|
+
if auth_token:
|
|
109
|
+
headers[Constants.Headers.AUTHORIZATION] = (
|
|
110
|
+
f"{Constants.Headers.BEARER_PREFIX} {auth_token}"
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
headers[Constants.Headers.USER_AGENT] = Utility.get_user_agent_header(
|
|
114
|
+
self._orchestrator_name
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
# Create and configure MCPStreamableHTTPTool
|
|
118
|
+
mcp_tools = MCPStreamableHTTPTool(
|
|
119
|
+
name=server_name,
|
|
120
|
+
url=config.url,
|
|
121
|
+
headers=headers,
|
|
122
|
+
description=f"MCP tools from {server_name}",
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
# Let Agent Framework handle the connection automatically
|
|
126
|
+
self._logger.info(f"Created MCP plugin for '{server_name}' at {config.url}")
|
|
127
|
+
|
|
128
|
+
all_tools.append(mcp_tools)
|
|
129
|
+
self._connected_servers.append(mcp_tools)
|
|
130
|
+
|
|
131
|
+
self._logger.info(f"Added MCP plugin '{server_name}' to agent tools")
|
|
132
|
+
|
|
133
|
+
except Exception as tool_ex:
|
|
134
|
+
self._logger.warning(
|
|
135
|
+
f"Failed to create MCP plugin for {server_name}: {tool_ex}"
|
|
136
|
+
)
|
|
137
|
+
continue
|
|
138
|
+
|
|
139
|
+
# Create the ChatAgent
|
|
140
|
+
agent = ChatAgent(
|
|
141
|
+
chat_client=chat_client,
|
|
142
|
+
tools=all_tools,
|
|
143
|
+
instructions=agent_instructions,
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
self._logger.info(f"Agent created with {len(all_tools)} total tools")
|
|
147
|
+
return agent
|
|
148
|
+
|
|
149
|
+
except Exception as ex:
|
|
150
|
+
self._logger.error(f"Failed to add tool servers to agent: {ex}")
|
|
151
|
+
raise
|
|
152
|
+
|
|
153
|
+
def _convert_chat_messages_to_history(
|
|
154
|
+
self,
|
|
155
|
+
chat_messages: Sequence[ChatMessage],
|
|
156
|
+
) -> List[ChatHistoryMessage]:
|
|
157
|
+
"""
|
|
158
|
+
Convert Agent Framework ChatMessage objects to ChatHistoryMessage format.
|
|
159
|
+
|
|
160
|
+
This internal helper method transforms Agent Framework's native ChatMessage
|
|
161
|
+
objects into the ChatHistoryMessage format expected by the MCP platform's
|
|
162
|
+
real-time threat protection endpoint.
|
|
163
|
+
|
|
164
|
+
Args:
|
|
165
|
+
chat_messages: Sequence of ChatMessage objects to convert.
|
|
166
|
+
|
|
167
|
+
Returns:
|
|
168
|
+
List of ChatHistoryMessage objects ready for the MCP platform.
|
|
169
|
+
|
|
170
|
+
Note:
|
|
171
|
+
- If message_id is None, a new UUID is generated
|
|
172
|
+
- Role is extracted via the .value property of the Role object
|
|
173
|
+
- Timestamp is set to current UTC time (ChatMessage has no timestamp)
|
|
174
|
+
- Messages with empty or whitespace-only content are filtered out and
|
|
175
|
+
logged at WARNING level. This is because ChatHistoryMessage requires
|
|
176
|
+
non-empty content for validation. The filtered messages will not be
|
|
177
|
+
sent to the MCP platform.
|
|
178
|
+
"""
|
|
179
|
+
history_messages: List[ChatHistoryMessage] = []
|
|
180
|
+
current_time = datetime.now(timezone.utc)
|
|
181
|
+
|
|
182
|
+
for msg in chat_messages:
|
|
183
|
+
message_id = msg.message_id if msg.message_id is not None else str(uuid.uuid4())
|
|
184
|
+
if msg.role is None:
|
|
185
|
+
self._logger.warning(
|
|
186
|
+
"Skipping message %s with missing role during conversion", message_id
|
|
187
|
+
)
|
|
188
|
+
continue
|
|
189
|
+
# Defensive handling: use .value if role is an enum, otherwise convert to string
|
|
190
|
+
role = msg.role.value if hasattr(msg.role, "value") else str(msg.role)
|
|
191
|
+
content = msg.text if msg.text is not None else ""
|
|
192
|
+
|
|
193
|
+
# Skip messages with empty content as ChatHistoryMessage validates non-empty content
|
|
194
|
+
if not content.strip():
|
|
195
|
+
self._logger.warning(
|
|
196
|
+
"Skipping message %s with empty content during conversion", message_id
|
|
197
|
+
)
|
|
198
|
+
continue
|
|
199
|
+
|
|
200
|
+
history_message = ChatHistoryMessage(
|
|
201
|
+
id=message_id,
|
|
202
|
+
role=role,
|
|
203
|
+
content=content,
|
|
204
|
+
timestamp=current_time,
|
|
205
|
+
)
|
|
206
|
+
history_messages.append(history_message)
|
|
207
|
+
|
|
208
|
+
self._logger.debug(
|
|
209
|
+
"Converted message %s with role '%s' to ChatHistoryMessage", message_id, role
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
return history_messages
|
|
213
|
+
|
|
214
|
+
async def send_chat_history_messages(
|
|
215
|
+
self,
|
|
216
|
+
chat_messages: Sequence[ChatMessage],
|
|
217
|
+
turn_context: TurnContext,
|
|
218
|
+
tool_options: Optional[ToolOptions] = None,
|
|
219
|
+
) -> OperationResult:
|
|
220
|
+
"""
|
|
221
|
+
Send chat history messages to the MCP platform for real-time threat protection.
|
|
222
|
+
|
|
223
|
+
This is the primary implementation method that handles message conversion
|
|
224
|
+
and delegation to the core tooling service.
|
|
225
|
+
|
|
226
|
+
Args:
|
|
227
|
+
chat_messages: Sequence of Agent Framework ChatMessage objects to send.
|
|
228
|
+
Can be empty - the request will still be sent to register
|
|
229
|
+
the user message from turn_context.activity.text.
|
|
230
|
+
turn_context: TurnContext from the Agents SDK containing conversation info.
|
|
231
|
+
tool_options: Optional configuration for the request. Defaults to
|
|
232
|
+
AgentFramework-specific options if not provided.
|
|
233
|
+
|
|
234
|
+
Returns:
|
|
235
|
+
OperationResult indicating success or failure of the operation.
|
|
236
|
+
|
|
237
|
+
Raises:
|
|
238
|
+
ValueError: If chat_messages or turn_context is None.
|
|
239
|
+
|
|
240
|
+
Note:
|
|
241
|
+
Even if chat_messages is empty or all messages are filtered during
|
|
242
|
+
conversion, the request will still be sent to the MCP platform. This
|
|
243
|
+
ensures the user message from turn_context.activity.text is registered
|
|
244
|
+
correctly for real-time threat protection.
|
|
245
|
+
|
|
246
|
+
Example:
|
|
247
|
+
>>> service = McpToolRegistrationService()
|
|
248
|
+
>>> messages = [ChatMessage(role=Role.USER, text="Hello")]
|
|
249
|
+
>>> result = await service.send_chat_history_messages(messages, turn_context)
|
|
250
|
+
>>> if result.succeeded:
|
|
251
|
+
... print("Chat history sent successfully")
|
|
252
|
+
"""
|
|
253
|
+
# Input validation
|
|
254
|
+
if chat_messages is None:
|
|
255
|
+
raise ValueError("chat_messages cannot be None")
|
|
256
|
+
|
|
257
|
+
if turn_context is None:
|
|
258
|
+
raise ValueError("turn_context cannot be None")
|
|
259
|
+
|
|
260
|
+
self._logger.info(f"Send chat history initiated with {len(chat_messages)} messages")
|
|
261
|
+
|
|
262
|
+
# Use default options if not provided
|
|
263
|
+
if tool_options is None:
|
|
264
|
+
tool_options = ToolOptions(orchestrator_name=self._orchestrator_name)
|
|
265
|
+
|
|
266
|
+
# Convert messages to ChatHistoryMessage format
|
|
267
|
+
history_messages = self._convert_chat_messages_to_history(chat_messages)
|
|
268
|
+
|
|
269
|
+
# Call core service even with empty history_messages to register
|
|
270
|
+
# the user message from turn_context.activity.text in the MCP platform.
|
|
271
|
+
if len(history_messages) == 0:
|
|
272
|
+
self._logger.info(
|
|
273
|
+
"Empty history messages (either no input or all filtered), "
|
|
274
|
+
"still sending to register user message"
|
|
275
|
+
)
|
|
276
|
+
|
|
277
|
+
# Delegate to core service
|
|
278
|
+
result = await self._mcp_server_configuration_service.send_chat_history(
|
|
279
|
+
turn_context=turn_context,
|
|
280
|
+
chat_history_messages=history_messages,
|
|
281
|
+
options=tool_options,
|
|
282
|
+
)
|
|
283
|
+
|
|
284
|
+
if result.succeeded:
|
|
285
|
+
self._logger.info(
|
|
286
|
+
f"Chat history sent successfully with {len(history_messages)} messages"
|
|
287
|
+
)
|
|
288
|
+
else:
|
|
289
|
+
self._logger.error(f"Failed to send chat history: {result}")
|
|
290
|
+
|
|
291
|
+
return result
|
|
292
|
+
|
|
293
|
+
async def send_chat_history_from_store(
|
|
294
|
+
self,
|
|
295
|
+
chat_message_store: ChatMessageStoreProtocol,
|
|
296
|
+
turn_context: TurnContext,
|
|
297
|
+
tool_options: Optional[ToolOptions] = None,
|
|
298
|
+
) -> OperationResult:
|
|
299
|
+
"""
|
|
300
|
+
Send chat history from a ChatMessageStore to the MCP platform.
|
|
301
|
+
|
|
302
|
+
This is a convenience method that extracts messages from the store
|
|
303
|
+
and delegates to send_chat_history_messages().
|
|
304
|
+
|
|
305
|
+
Args:
|
|
306
|
+
chat_message_store: ChatMessageStore containing the conversation history.
|
|
307
|
+
turn_context: TurnContext from the Agents SDK containing conversation info.
|
|
308
|
+
tool_options: Optional configuration for the request.
|
|
309
|
+
|
|
310
|
+
Returns:
|
|
311
|
+
OperationResult indicating success or failure of the operation.
|
|
312
|
+
|
|
313
|
+
Raises:
|
|
314
|
+
ValueError: If chat_message_store or turn_context is None.
|
|
315
|
+
|
|
316
|
+
Example:
|
|
317
|
+
>>> service = McpToolRegistrationService()
|
|
318
|
+
>>> result = await service.send_chat_history_from_store(
|
|
319
|
+
... thread.chat_message_store, turn_context
|
|
320
|
+
... )
|
|
321
|
+
"""
|
|
322
|
+
# Input validation
|
|
323
|
+
if chat_message_store is None:
|
|
324
|
+
raise ValueError("chat_message_store cannot be None")
|
|
325
|
+
|
|
326
|
+
if turn_context is None:
|
|
327
|
+
raise ValueError("turn_context cannot be None")
|
|
328
|
+
|
|
329
|
+
# Extract messages from the store
|
|
330
|
+
messages = await chat_message_store.list_messages()
|
|
331
|
+
|
|
332
|
+
# Delegate to the primary implementation
|
|
333
|
+
return await self.send_chat_history_messages(
|
|
334
|
+
chat_messages=messages,
|
|
335
|
+
turn_context=turn_context,
|
|
336
|
+
tool_options=tool_options,
|
|
337
|
+
)
|
|
338
|
+
|
|
339
|
+
async def cleanup(self):
|
|
340
|
+
"""Clean up any resources used by the service."""
|
|
341
|
+
try:
|
|
342
|
+
for plugin in self._connected_servers:
|
|
343
|
+
try:
|
|
344
|
+
if hasattr(plugin, "close"):
|
|
345
|
+
await plugin.close()
|
|
346
|
+
except Exception as cleanup_ex:
|
|
347
|
+
self._logger.debug(f"Error during cleanup: {cleanup_ex}")
|
|
348
|
+
self._connected_servers.clear()
|
|
349
|
+
except Exception as ex:
|
|
350
|
+
self._logger.debug(f"Error during service cleanup: {ex}")
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: microsoft-agents-a365-tooling-extensions-agentframework
|
|
3
|
-
Version: 0.2.1.
|
|
3
|
+
Version: 0.2.1.dev4
|
|
4
4
|
Summary: Agent Framework integration tools for Agent 365 AI agent tooling
|
|
5
5
|
Author-email: Microsoft <support@microsoft.com>
|
|
6
6
|
License: MIT
|
|
@@ -18,7 +18,6 @@ Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
|
18
18
|
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
19
19
|
Requires-Python: >=3.11
|
|
20
20
|
Description-Content-Type: text/markdown
|
|
21
|
-
License-File: LICENSE
|
|
22
21
|
Requires-Dist: microsoft-agents-a365-tooling>=0.0.0
|
|
23
22
|
Requires-Dist: microsoft-agents-hosting-core<0.6.0,>=0.4.0
|
|
24
23
|
Requires-Dist: agent-framework-azure-ai>=1.0.0b251114
|
|
@@ -33,7 +32,6 @@ Requires-Dist: mypy>=1.0.0; extra == "dev"
|
|
|
33
32
|
Provides-Extra: test
|
|
34
33
|
Requires-Dist: pytest>=7.0.0; extra == "test"
|
|
35
34
|
Requires-Dist: pytest-asyncio>=0.21.0; extra == "test"
|
|
36
|
-
Dynamic: license-file
|
|
37
35
|
|
|
38
36
|
# microsoft-agents-a365-tooling-extensions-agentframework
|
|
39
37
|
|
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
README.md
|
|
2
2
|
pyproject.toml
|
|
3
3
|
setup.py
|
|
4
|
-
../../LICENSE
|
|
5
|
-
docs/../../LICENSE
|
|
6
|
-
microsoft_agents_a365/../../LICENSE
|
|
7
4
|
microsoft_agents_a365/tooling/extensions/agentframework/__init__.py
|
|
8
5
|
microsoft_agents_a365/tooling/extensions/agentframework/services/__init__.py
|
|
9
6
|
microsoft_agents_a365/tooling/extensions/agentframework/services/mcp_tool_registration_service.py
|
|
@@ -1,162 +0,0 @@
|
|
|
1
|
-
# Copyright (c) Microsoft Corporation.
|
|
2
|
-
# Licensed under the MIT License.
|
|
3
|
-
|
|
4
|
-
from typing import Optional, List, Any, Union
|
|
5
|
-
import logging
|
|
6
|
-
|
|
7
|
-
from agent_framework import ChatAgent, MCPStreamableHTTPTool
|
|
8
|
-
from agent_framework.azure import AzureOpenAIChatClient
|
|
9
|
-
from agent_framework.openai import OpenAIChatClient
|
|
10
|
-
|
|
11
|
-
from microsoft_agents.hosting.core import Authorization, TurnContext
|
|
12
|
-
|
|
13
|
-
from microsoft_agents_a365.runtime.utility import Utility
|
|
14
|
-
from microsoft_agents_a365.tooling.services.mcp_tool_server_configuration_service import (
|
|
15
|
-
McpToolServerConfigurationService,
|
|
16
|
-
)
|
|
17
|
-
from microsoft_agents_a365.tooling.models import ToolOptions
|
|
18
|
-
from microsoft_agents_a365.tooling.utils.constants import Constants
|
|
19
|
-
|
|
20
|
-
from microsoft_agents_a365.tooling.utils.utility import (
|
|
21
|
-
get_mcp_platform_authentication_scope,
|
|
22
|
-
)
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
class McpToolRegistrationService:
|
|
26
|
-
"""
|
|
27
|
-
Provides MCP tool registration services for Agent Framework agents.
|
|
28
|
-
|
|
29
|
-
This service handles registration and management of MCP (Model Context Protocol)
|
|
30
|
-
tool servers with Agent Framework agents.
|
|
31
|
-
"""
|
|
32
|
-
|
|
33
|
-
_orchestrator_name: str = "AgentFramework"
|
|
34
|
-
|
|
35
|
-
def __init__(self, logger: Optional[logging.Logger] = None):
|
|
36
|
-
"""
|
|
37
|
-
Initialize the MCP Tool Registration Service for Agent Framework.
|
|
38
|
-
|
|
39
|
-
Args:
|
|
40
|
-
logger: Logger instance for logging operations.
|
|
41
|
-
"""
|
|
42
|
-
self._logger = logger or logging.getLogger(self.__class__.__name__)
|
|
43
|
-
self._mcp_server_configuration_service = McpToolServerConfigurationService(
|
|
44
|
-
logger=self._logger
|
|
45
|
-
)
|
|
46
|
-
self._connected_servers = []
|
|
47
|
-
|
|
48
|
-
async def add_tool_servers_to_agent(
|
|
49
|
-
self,
|
|
50
|
-
chat_client: Union[OpenAIChatClient, AzureOpenAIChatClient],
|
|
51
|
-
agent_instructions: str,
|
|
52
|
-
initial_tools: List[Any],
|
|
53
|
-
auth: Authorization,
|
|
54
|
-
auth_handler_name: str,
|
|
55
|
-
turn_context: TurnContext,
|
|
56
|
-
auth_token: Optional[str] = None,
|
|
57
|
-
) -> Optional[ChatAgent]:
|
|
58
|
-
"""
|
|
59
|
-
Add MCP tool servers to a chat agent (mirrors .NET implementation).
|
|
60
|
-
|
|
61
|
-
Args:
|
|
62
|
-
chat_client: The chat client instance (Union[OpenAIChatClient, AzureOpenAIChatClient])
|
|
63
|
-
agent_instructions: Instructions for the agent behavior
|
|
64
|
-
initial_tools: List of initial tools to add to the agent
|
|
65
|
-
auth: Authorization context for token exchange
|
|
66
|
-
auth_handler_name: Name of the authorization handler.
|
|
67
|
-
turn_context: Turn context for the operation
|
|
68
|
-
auth_token: Optional bearer token for authentication
|
|
69
|
-
|
|
70
|
-
Returns:
|
|
71
|
-
ChatAgent instance with MCP tools registered, or None if creation failed
|
|
72
|
-
"""
|
|
73
|
-
try:
|
|
74
|
-
# Exchange token if not provided
|
|
75
|
-
if not auth_token:
|
|
76
|
-
scopes = get_mcp_platform_authentication_scope()
|
|
77
|
-
authToken = await auth.exchange_token(turn_context, scopes, auth_handler_name)
|
|
78
|
-
auth_token = authToken.token
|
|
79
|
-
|
|
80
|
-
agentic_app_id = Utility.resolve_agent_identity(turn_context, auth_token)
|
|
81
|
-
|
|
82
|
-
self._logger.info(f"Listing MCP tool servers for agent {agentic_app_id}")
|
|
83
|
-
|
|
84
|
-
options = ToolOptions(orchestrator_name=self._orchestrator_name)
|
|
85
|
-
|
|
86
|
-
# Get MCP server configurations
|
|
87
|
-
server_configs = await self._mcp_server_configuration_service.list_tool_servers(
|
|
88
|
-
agentic_app_id=agentic_app_id,
|
|
89
|
-
auth_token=auth_token,
|
|
90
|
-
options=options,
|
|
91
|
-
)
|
|
92
|
-
|
|
93
|
-
self._logger.info(f"Loaded {len(server_configs)} MCP server configurations")
|
|
94
|
-
|
|
95
|
-
# Create the agent with all tools (initial + MCP tools)
|
|
96
|
-
all_tools = list(initial_tools)
|
|
97
|
-
|
|
98
|
-
# Add servers as MCPStreamableHTTPTool instances
|
|
99
|
-
for config in server_configs:
|
|
100
|
-
# Use mcp_server_name if available (not None or empty), otherwise fall back to mcp_server_unique_name
|
|
101
|
-
server_name = config.mcp_server_name or config.mcp_server_unique_name
|
|
102
|
-
|
|
103
|
-
try:
|
|
104
|
-
# Prepare auth headers
|
|
105
|
-
headers = {}
|
|
106
|
-
if auth_token:
|
|
107
|
-
headers[Constants.Headers.AUTHORIZATION] = (
|
|
108
|
-
f"{Constants.Headers.BEARER_PREFIX} {auth_token}"
|
|
109
|
-
)
|
|
110
|
-
|
|
111
|
-
headers[Constants.Headers.USER_AGENT] = Utility.get_user_agent_header(
|
|
112
|
-
self._orchestrator_name
|
|
113
|
-
)
|
|
114
|
-
|
|
115
|
-
# Create and configure MCPStreamableHTTPTool
|
|
116
|
-
mcp_tools = MCPStreamableHTTPTool(
|
|
117
|
-
name=server_name,
|
|
118
|
-
url=config.url,
|
|
119
|
-
headers=headers,
|
|
120
|
-
description=f"MCP tools from {server_name}",
|
|
121
|
-
)
|
|
122
|
-
|
|
123
|
-
# Let Agent Framework handle the connection automatically
|
|
124
|
-
self._logger.info(f"Created MCP plugin for '{server_name}' at {config.url}")
|
|
125
|
-
|
|
126
|
-
all_tools.append(mcp_tools)
|
|
127
|
-
self._connected_servers.append(mcp_tools)
|
|
128
|
-
|
|
129
|
-
self._logger.info(f"Added MCP plugin '{server_name}' to agent tools")
|
|
130
|
-
|
|
131
|
-
except Exception as tool_ex:
|
|
132
|
-
self._logger.warning(
|
|
133
|
-
f"Failed to create MCP plugin for {server_name}: {tool_ex}"
|
|
134
|
-
)
|
|
135
|
-
continue
|
|
136
|
-
|
|
137
|
-
# Create the ChatAgent
|
|
138
|
-
agent = ChatAgent(
|
|
139
|
-
chat_client=chat_client,
|
|
140
|
-
tools=all_tools,
|
|
141
|
-
instructions=agent_instructions,
|
|
142
|
-
)
|
|
143
|
-
|
|
144
|
-
self._logger.info(f"Agent created with {len(all_tools)} total tools")
|
|
145
|
-
return agent
|
|
146
|
-
|
|
147
|
-
except Exception as ex:
|
|
148
|
-
self._logger.error(f"Failed to add tool servers to agent: {ex}")
|
|
149
|
-
raise
|
|
150
|
-
|
|
151
|
-
async def cleanup(self):
|
|
152
|
-
"""Clean up any resources used by the service."""
|
|
153
|
-
try:
|
|
154
|
-
for plugin in self._connected_servers:
|
|
155
|
-
try:
|
|
156
|
-
if hasattr(plugin, "close"):
|
|
157
|
-
await plugin.close()
|
|
158
|
-
except Exception as cleanup_ex:
|
|
159
|
-
self._logger.debug(f"Error during cleanup: {cleanup_ex}")
|
|
160
|
-
self._connected_servers.clear()
|
|
161
|
-
except Exception as ex:
|
|
162
|
-
self._logger.debug(f"Error during service cleanup: {ex}")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|