microsoft-agents-a365-tooling 0.1.0__py3-none-any.whl
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/__init__.py +29 -0
- microsoft_agents_a365/tooling/models/__init__.py +11 -0
- microsoft_agents_a365/tooling/models/mcp_server_config.py +27 -0
- microsoft_agents_a365/tooling/services/__init__.py +14 -0
- microsoft_agents_a365/tooling/services/mcp_tool_server_configuration_service.py +481 -0
- microsoft_agents_a365/tooling/utils/__init__.py +24 -0
- microsoft_agents_a365/tooling/utils/constants.py +22 -0
- microsoft_agents_a365/tooling/utils/utility.py +123 -0
- microsoft_agents_a365_tooling-0.1.0.dist-info/METADATA +62 -0
- microsoft_agents_a365_tooling-0.1.0.dist-info/RECORD +12 -0
- microsoft_agents_a365_tooling-0.1.0.dist-info/WHEEL +5 -0
- microsoft_agents_a365_tooling-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Copyright (c) Microsoft Corporation.
|
|
2
|
+
# Licensed under the MIT License.
|
|
3
|
+
|
|
4
|
+
"""
|
|
5
|
+
Microsoft Agent 365 Tooling SDK
|
|
6
|
+
|
|
7
|
+
Core tooling functionality shared across different AI frameworks.
|
|
8
|
+
Provides base utilities and common helper functions.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from .models import MCPServerConfig
|
|
12
|
+
from .services import McpToolServerConfigurationService
|
|
13
|
+
from .utils import Constants
|
|
14
|
+
from .utils.utility import (
|
|
15
|
+
get_tooling_gateway_for_digital_worker,
|
|
16
|
+
get_mcp_base_url,
|
|
17
|
+
build_mcp_server_url,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
__version__ = "1.0.0"
|
|
21
|
+
|
|
22
|
+
__all__ = [
|
|
23
|
+
"MCPServerConfig",
|
|
24
|
+
"McpToolServerConfigurationService",
|
|
25
|
+
"Constants",
|
|
26
|
+
"get_tooling_gateway_for_digital_worker",
|
|
27
|
+
"get_mcp_base_url",
|
|
28
|
+
"build_mcp_server_url",
|
|
29
|
+
]
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Copyright (c) Microsoft. All rights reserved.
|
|
2
|
+
|
|
3
|
+
"""
|
|
4
|
+
MCP Server Configuration model.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@dataclass
|
|
11
|
+
class MCPServerConfig:
|
|
12
|
+
"""
|
|
13
|
+
Represents the configuration for an MCP server, including its name and endpoint.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
#: Gets or sets the name of the MCP server.
|
|
17
|
+
mcp_server_name: str
|
|
18
|
+
|
|
19
|
+
#: Gets or sets the unique name of the MCP server.
|
|
20
|
+
mcp_server_unique_name: str
|
|
21
|
+
|
|
22
|
+
def __post_init__(self):
|
|
23
|
+
"""Validate the configuration after initialization."""
|
|
24
|
+
if not self.mcp_server_name:
|
|
25
|
+
raise ValueError("mcp_server_name cannot be empty")
|
|
26
|
+
if not self.mcp_server_unique_name:
|
|
27
|
+
raise ValueError("mcp_server_unique_name cannot be empty")
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# Copyright (c) Microsoft. All rights reserved.
|
|
2
|
+
|
|
3
|
+
"""
|
|
4
|
+
MCP tooling services package.
|
|
5
|
+
|
|
6
|
+
This package contains service implementations for MCP (Model Context Protocol)
|
|
7
|
+
tooling functionality.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from .mcp_tool_server_configuration_service import McpToolServerConfigurationService
|
|
11
|
+
|
|
12
|
+
__all__ = [
|
|
13
|
+
"McpToolServerConfigurationService",
|
|
14
|
+
]
|
|
@@ -0,0 +1,481 @@
|
|
|
1
|
+
# Copyright (c) Microsoft. All rights reserved.
|
|
2
|
+
|
|
3
|
+
"""
|
|
4
|
+
MCP Tool Server Configuration Service.
|
|
5
|
+
|
|
6
|
+
This module provides the implementation of the MCP (Model Context Protocol)
|
|
7
|
+
tool server configuration service that communicates with the tooling gateway to
|
|
8
|
+
discover and configure MCP tool servers.
|
|
9
|
+
|
|
10
|
+
The service supports both development and production scenarios:
|
|
11
|
+
- Development: Reads configuration from ToolingManifest.json
|
|
12
|
+
- Production: Retrieves configuration from tooling gateway endpoint
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
# ==============================================================================
|
|
16
|
+
# IMPORTS
|
|
17
|
+
# ==============================================================================
|
|
18
|
+
|
|
19
|
+
# Standard library imports
|
|
20
|
+
import json
|
|
21
|
+
import logging
|
|
22
|
+
import os
|
|
23
|
+
import sys
|
|
24
|
+
from pathlib import Path
|
|
25
|
+
from typing import Any, Dict, List, Optional
|
|
26
|
+
|
|
27
|
+
# Third-party imports
|
|
28
|
+
import aiohttp
|
|
29
|
+
|
|
30
|
+
# Local imports
|
|
31
|
+
from ..models import MCPServerConfig
|
|
32
|
+
from ..utils import Constants
|
|
33
|
+
from ..utils.utility import get_tooling_gateway_for_digital_worker, build_mcp_server_url
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
# ==============================================================================
|
|
37
|
+
# MAIN SERVICE CLASS
|
|
38
|
+
# ==============================================================================
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class McpToolServerConfigurationService:
|
|
42
|
+
"""
|
|
43
|
+
Provides services for MCP tool server configuration management.
|
|
44
|
+
|
|
45
|
+
This service handles discovery and configuration of MCP (Model Context Protocol)
|
|
46
|
+
tool servers from multiple sources:
|
|
47
|
+
- Development: Local ToolingManifest.json files
|
|
48
|
+
- Production: Remote tooling gateway endpoints
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
# --------------------------------------------------------------------------
|
|
52
|
+
# INITIALIZATION
|
|
53
|
+
# --------------------------------------------------------------------------
|
|
54
|
+
|
|
55
|
+
def __init__(self, logger: Optional[logging.Logger] = None):
|
|
56
|
+
"""
|
|
57
|
+
Initialize the MCP Tool Server Configuration Service.
|
|
58
|
+
|
|
59
|
+
Args:
|
|
60
|
+
logger: Logger instance for logging operations. If None, creates a new logger.
|
|
61
|
+
"""
|
|
62
|
+
self._logger = logger or logging.getLogger(self.__class__.__name__)
|
|
63
|
+
|
|
64
|
+
# --------------------------------------------------------------------------
|
|
65
|
+
# PUBLIC API
|
|
66
|
+
# --------------------------------------------------------------------------
|
|
67
|
+
|
|
68
|
+
async def list_tool_servers(
|
|
69
|
+
self, agentic_app_id: str, auth_token: str
|
|
70
|
+
) -> List[MCPServerConfig]:
|
|
71
|
+
"""
|
|
72
|
+
Gets the list of MCP Servers that are configured for the agent.
|
|
73
|
+
|
|
74
|
+
Args:
|
|
75
|
+
agentic_app_id: Agentic App ID for the agent.
|
|
76
|
+
auth_token: Authentication token to access the MCP servers.
|
|
77
|
+
|
|
78
|
+
Returns:
|
|
79
|
+
List[MCPServerConfig]: Returns the list of MCP Servers that are configured.
|
|
80
|
+
|
|
81
|
+
Raises:
|
|
82
|
+
ValueError: If required parameters are invalid or empty.
|
|
83
|
+
Exception: If there's an error communicating with the tooling gateway.
|
|
84
|
+
"""
|
|
85
|
+
# Validate input parameters
|
|
86
|
+
self._validate_input_parameters(agentic_app_id, auth_token)
|
|
87
|
+
|
|
88
|
+
self._logger.info(f"Listing MCP tool servers for agent {agentic_app_id}")
|
|
89
|
+
|
|
90
|
+
# Determine configuration source based on environment
|
|
91
|
+
if self._is_development_scenario():
|
|
92
|
+
return self._load_servers_from_manifest()
|
|
93
|
+
else:
|
|
94
|
+
return await self._load_servers_from_gateway(agentic_app_id, auth_token)
|
|
95
|
+
|
|
96
|
+
# --------------------------------------------------------------------------
|
|
97
|
+
# ENVIRONMENT DETECTION
|
|
98
|
+
# --------------------------------------------------------------------------
|
|
99
|
+
|
|
100
|
+
def _is_development_scenario(self) -> bool:
|
|
101
|
+
"""
|
|
102
|
+
Determines if this is a development scenario.
|
|
103
|
+
|
|
104
|
+
Returns:
|
|
105
|
+
bool: True if running in development mode, False otherwise.
|
|
106
|
+
"""
|
|
107
|
+
environment = os.getenv("ENVIRONMENT", "Development")
|
|
108
|
+
|
|
109
|
+
is_dev = environment.lower() == "development"
|
|
110
|
+
self._logger.debug(f"Environment: {environment}, Development scenario: {is_dev}")
|
|
111
|
+
return is_dev
|
|
112
|
+
|
|
113
|
+
# --------------------------------------------------------------------------
|
|
114
|
+
# DEVELOPMENT: MANIFEST-BASED CONFIGURATION
|
|
115
|
+
# --------------------------------------------------------------------------
|
|
116
|
+
|
|
117
|
+
def _load_servers_from_manifest(self) -> List[MCPServerConfig]:
|
|
118
|
+
"""
|
|
119
|
+
Reads MCP server configurations from ToolingManifest.json in the application's content root.
|
|
120
|
+
|
|
121
|
+
The manifest file should be located at: [ProjectRoot]/ToolingManifest.json
|
|
122
|
+
|
|
123
|
+
Example ToolingManifest.json structure:
|
|
124
|
+
{
|
|
125
|
+
"mcpServers": [
|
|
126
|
+
{
|
|
127
|
+
"mcpServerName": "mailMCPServer",
|
|
128
|
+
"mcpServerUniqueName": "mcp_MailTools"
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
"mcpServerName": "sharePointMCPServer",
|
|
132
|
+
"mcpServerUniqueName": "mcp_SharePointTools"
|
|
133
|
+
}
|
|
134
|
+
]
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
Returns:
|
|
138
|
+
List[MCPServerConfig]: List of MCP server configurations from manifest.
|
|
139
|
+
|
|
140
|
+
Raises:
|
|
141
|
+
Exception: If manifest file cannot be read or parsed.
|
|
142
|
+
"""
|
|
143
|
+
mcp_servers: List[MCPServerConfig] = []
|
|
144
|
+
|
|
145
|
+
try:
|
|
146
|
+
manifest_path = self._find_manifest_file()
|
|
147
|
+
|
|
148
|
+
if manifest_path and manifest_path.exists():
|
|
149
|
+
self._logger.info(f"Loading MCP servers from: {manifest_path}")
|
|
150
|
+
mcp_servers = self._parse_manifest_file(manifest_path)
|
|
151
|
+
else:
|
|
152
|
+
self._log_manifest_search_failure()
|
|
153
|
+
|
|
154
|
+
except Exception as e:
|
|
155
|
+
raise Exception(
|
|
156
|
+
f"Failed to read MCP servers from ToolingManifest.json: {str(e)}"
|
|
157
|
+
) from e
|
|
158
|
+
|
|
159
|
+
return mcp_servers
|
|
160
|
+
|
|
161
|
+
def _find_manifest_file(self) -> Optional[Path]:
|
|
162
|
+
"""
|
|
163
|
+
Searches for ToolingManifest.json in various common locations.
|
|
164
|
+
|
|
165
|
+
Returns:
|
|
166
|
+
Path to manifest file if found, None otherwise.
|
|
167
|
+
"""
|
|
168
|
+
search_locations = self._get_manifest_search_locations()
|
|
169
|
+
|
|
170
|
+
for potential_path in search_locations:
|
|
171
|
+
self._logger.debug(f"Checking for manifest at: {potential_path}")
|
|
172
|
+
if potential_path.exists():
|
|
173
|
+
self._logger.info(f"Found manifest at: {potential_path}")
|
|
174
|
+
return potential_path
|
|
175
|
+
else:
|
|
176
|
+
self._logger.debug(f"Manifest not found at: {potential_path}")
|
|
177
|
+
|
|
178
|
+
return None
|
|
179
|
+
|
|
180
|
+
def _get_manifest_search_locations(self) -> List[Path]:
|
|
181
|
+
"""
|
|
182
|
+
Gets list of potential locations for ToolingManifest.json.
|
|
183
|
+
|
|
184
|
+
Returns:
|
|
185
|
+
List of Path objects to search for the manifest file.
|
|
186
|
+
"""
|
|
187
|
+
current_dir = Path.cwd()
|
|
188
|
+
search_locations = []
|
|
189
|
+
|
|
190
|
+
# Current working directory
|
|
191
|
+
search_locations.append(current_dir / "ToolingManifest.json")
|
|
192
|
+
|
|
193
|
+
# Parent directory
|
|
194
|
+
search_locations.append(current_dir.parent / "ToolingManifest.json")
|
|
195
|
+
|
|
196
|
+
# Script location and project root
|
|
197
|
+
if __file__:
|
|
198
|
+
if hasattr(sys, "_MEIPASS"):
|
|
199
|
+
# Running as PyInstaller bundle
|
|
200
|
+
base_dir = Path(sys._MEIPASS)
|
|
201
|
+
else:
|
|
202
|
+
# Running as normal Python script
|
|
203
|
+
current_file_path = Path(__file__)
|
|
204
|
+
# Navigate to project root
|
|
205
|
+
base_dir = current_file_path.parent.parent.parent.parent
|
|
206
|
+
|
|
207
|
+
search_locations.extend(
|
|
208
|
+
[
|
|
209
|
+
base_dir / "ToolingManifest.json",
|
|
210
|
+
]
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
return search_locations
|
|
214
|
+
|
|
215
|
+
def _parse_manifest_file(self, manifest_path: Path) -> List[MCPServerConfig]:
|
|
216
|
+
"""
|
|
217
|
+
Parses the manifest file and extracts MCP server configurations.
|
|
218
|
+
|
|
219
|
+
Args:
|
|
220
|
+
manifest_path: Path to the manifest file.
|
|
221
|
+
|
|
222
|
+
Returns:
|
|
223
|
+
List of parsed MCP server configurations.
|
|
224
|
+
"""
|
|
225
|
+
mcp_servers: List[MCPServerConfig] = []
|
|
226
|
+
|
|
227
|
+
with open(manifest_path, "r", encoding="utf-8") as file:
|
|
228
|
+
json_content = file.read()
|
|
229
|
+
|
|
230
|
+
print(f"📄 Manifest content: {json_content}")
|
|
231
|
+
manifest_data = json.loads(json_content)
|
|
232
|
+
|
|
233
|
+
if "mcpServers" in manifest_data:
|
|
234
|
+
print("✅ Found 'mcpServers' section in ToolingManifest.json")
|
|
235
|
+
self._logger.info("Found 'mcpServers' section in ToolingManifest.json")
|
|
236
|
+
mcp_servers_data = manifest_data["mcpServers"]
|
|
237
|
+
|
|
238
|
+
if isinstance(mcp_servers_data, list):
|
|
239
|
+
print(f"📊 Processing {len(mcp_servers_data)} server entries")
|
|
240
|
+
for server_element in mcp_servers_data:
|
|
241
|
+
print(f"🔧 Processing server element: {server_element}")
|
|
242
|
+
server_config = self._parse_manifest_server_config(server_element)
|
|
243
|
+
if server_config is not None:
|
|
244
|
+
print(
|
|
245
|
+
f"✅ Created server config: {server_config.mcp_server_name} -> {server_config.mcp_server_unique_name}"
|
|
246
|
+
)
|
|
247
|
+
mcp_servers.append(server_config)
|
|
248
|
+
else:
|
|
249
|
+
print(f"❌ Failed to parse server config from: {server_element}")
|
|
250
|
+
else:
|
|
251
|
+
print("❌ No 'mcpServers' section found in ToolingManifest.json")
|
|
252
|
+
|
|
253
|
+
print(f"📊 Final result: Loaded {len(mcp_servers)} MCP server configurations")
|
|
254
|
+
self._logger.info(f"Loaded {len(mcp_servers)} MCP server configurations")
|
|
255
|
+
|
|
256
|
+
return mcp_servers
|
|
257
|
+
|
|
258
|
+
def _log_manifest_search_failure(self) -> None:
|
|
259
|
+
"""Logs information about failed manifest file search."""
|
|
260
|
+
search_locations = self._get_manifest_search_locations()
|
|
261
|
+
|
|
262
|
+
print("❌ ToolingManifest.json not found. Checked locations:")
|
|
263
|
+
for path in search_locations:
|
|
264
|
+
print(f" - {path}")
|
|
265
|
+
|
|
266
|
+
self._logger.info(
|
|
267
|
+
f"ToolingManifest.json not found. Checked {len(search_locations)} locations"
|
|
268
|
+
)
|
|
269
|
+
self._logger.info(
|
|
270
|
+
"Please ensure ToolingManifest.json exists in your project's output directory."
|
|
271
|
+
)
|
|
272
|
+
|
|
273
|
+
# --------------------------------------------------------------------------
|
|
274
|
+
# PRODUCTION: GATEWAY-BASED CONFIGURATION
|
|
275
|
+
# --------------------------------------------------------------------------
|
|
276
|
+
|
|
277
|
+
async def _load_servers_from_gateway(
|
|
278
|
+
self, agentic_app_id: str, auth_token: str
|
|
279
|
+
) -> List[MCPServerConfig]:
|
|
280
|
+
"""
|
|
281
|
+
Reads MCP server configurations from tooling gateway endpoint for production scenario.
|
|
282
|
+
|
|
283
|
+
Args:
|
|
284
|
+
agentic_app_id: Agentic App ID for the agent.
|
|
285
|
+
auth_token: Authentication token to access the tooling gateway.
|
|
286
|
+
|
|
287
|
+
Returns:
|
|
288
|
+
List[MCPServerConfig]: List of MCP server configurations from tooling gateway.
|
|
289
|
+
|
|
290
|
+
Raises:
|
|
291
|
+
Exception: If there's an error communicating with the tooling gateway.
|
|
292
|
+
"""
|
|
293
|
+
mcp_servers: List[MCPServerConfig] = []
|
|
294
|
+
|
|
295
|
+
try:
|
|
296
|
+
config_endpoint = get_tooling_gateway_for_digital_worker(agentic_app_id)
|
|
297
|
+
headers = self._prepare_gateway_headers(auth_token)
|
|
298
|
+
|
|
299
|
+
self._logger.info(f"Calling tooling gateway endpoint: {config_endpoint}")
|
|
300
|
+
|
|
301
|
+
async with aiohttp.ClientSession() as session:
|
|
302
|
+
async with session.get(config_endpoint, headers=headers) as response:
|
|
303
|
+
if response.status == 200:
|
|
304
|
+
mcp_servers = await self._parse_gateway_response(response)
|
|
305
|
+
self._logger.info(
|
|
306
|
+
f"Retrieved {len(mcp_servers)} MCP tool servers from tooling gateway"
|
|
307
|
+
)
|
|
308
|
+
else:
|
|
309
|
+
raise Exception(f"HTTP {response.status}: {await response.text()}")
|
|
310
|
+
|
|
311
|
+
except aiohttp.ClientError as http_ex:
|
|
312
|
+
error_msg = f"Failed to connect to MCP configuration endpoint: {str(http_ex)}"
|
|
313
|
+
self._logger.error(error_msg)
|
|
314
|
+
raise Exception(error_msg) from http_ex
|
|
315
|
+
except json.JSONDecodeError as json_ex:
|
|
316
|
+
error_msg = f"Failed to parse MCP server configuration response: {str(json_ex)}"
|
|
317
|
+
self._logger.error(error_msg)
|
|
318
|
+
raise Exception(error_msg) from json_ex
|
|
319
|
+
except Exception as e:
|
|
320
|
+
error_msg = f"Failed to read MCP servers from endpoint: {str(e)}"
|
|
321
|
+
self._logger.error(error_msg)
|
|
322
|
+
raise Exception(error_msg) from e
|
|
323
|
+
|
|
324
|
+
return mcp_servers
|
|
325
|
+
|
|
326
|
+
def _prepare_gateway_headers(self, auth_token: str) -> Dict[str, str]:
|
|
327
|
+
"""
|
|
328
|
+
Prepares headers for tooling gateway requests.
|
|
329
|
+
|
|
330
|
+
Args:
|
|
331
|
+
auth_token: Authentication token.
|
|
332
|
+
|
|
333
|
+
Returns:
|
|
334
|
+
Dictionary of HTTP headers.
|
|
335
|
+
"""
|
|
336
|
+
return {
|
|
337
|
+
"Authorization": f"{Constants.Headers.BEARER_PREFIX} {auth_token}",
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
async def _parse_gateway_response(
|
|
341
|
+
self, response: aiohttp.ClientResponse
|
|
342
|
+
) -> List[MCPServerConfig]:
|
|
343
|
+
"""
|
|
344
|
+
Parses the response from the tooling gateway.
|
|
345
|
+
|
|
346
|
+
Args:
|
|
347
|
+
response: HTTP response from the gateway.
|
|
348
|
+
|
|
349
|
+
Returns:
|
|
350
|
+
List of parsed MCP server configurations.
|
|
351
|
+
"""
|
|
352
|
+
mcp_servers: List[MCPServerConfig] = []
|
|
353
|
+
|
|
354
|
+
response_text = await response.text()
|
|
355
|
+
config_data = json.loads(response_text)
|
|
356
|
+
|
|
357
|
+
if "mcpServers" in config_data and isinstance(config_data["mcpServers"], list):
|
|
358
|
+
for server_element in config_data["mcpServers"]:
|
|
359
|
+
server_config = self._parse_gateway_server_config(server_element)
|
|
360
|
+
if server_config is not None:
|
|
361
|
+
mcp_servers.append(server_config)
|
|
362
|
+
|
|
363
|
+
return mcp_servers
|
|
364
|
+
|
|
365
|
+
# --------------------------------------------------------------------------
|
|
366
|
+
# CONFIGURATION PARSING HELPERS
|
|
367
|
+
# --------------------------------------------------------------------------
|
|
368
|
+
|
|
369
|
+
def _parse_manifest_server_config(
|
|
370
|
+
self, server_element: Dict[str, Any]
|
|
371
|
+
) -> Optional[MCPServerConfig]:
|
|
372
|
+
"""
|
|
373
|
+
Parses a server configuration from manifest data, constructing full URL.
|
|
374
|
+
|
|
375
|
+
Args:
|
|
376
|
+
server_element: Dictionary containing server configuration from manifest.
|
|
377
|
+
|
|
378
|
+
Returns:
|
|
379
|
+
MCPServerConfig object or None if parsing fails.
|
|
380
|
+
"""
|
|
381
|
+
try:
|
|
382
|
+
name = self._extract_server_name(server_element)
|
|
383
|
+
server_name = self._extract_server_unique_name(server_element)
|
|
384
|
+
|
|
385
|
+
if not self._validate_server_strings(name, server_name):
|
|
386
|
+
return None
|
|
387
|
+
|
|
388
|
+
# Construct full URL using environment utilities
|
|
389
|
+
full_url = build_mcp_server_url(server_name)
|
|
390
|
+
|
|
391
|
+
return MCPServerConfig(mcp_server_name=name, mcp_server_unique_name=full_url)
|
|
392
|
+
|
|
393
|
+
except Exception:
|
|
394
|
+
return None
|
|
395
|
+
|
|
396
|
+
def _parse_gateway_server_config(
|
|
397
|
+
self, server_element: Dict[str, Any]
|
|
398
|
+
) -> Optional[MCPServerConfig]:
|
|
399
|
+
"""
|
|
400
|
+
Parses a server configuration from gateway response data.
|
|
401
|
+
|
|
402
|
+
Args:
|
|
403
|
+
server_element: Dictionary containing server configuration from gateway.
|
|
404
|
+
|
|
405
|
+
Returns:
|
|
406
|
+
MCPServerConfig object or None if parsing fails.
|
|
407
|
+
"""
|
|
408
|
+
try:
|
|
409
|
+
name = self._extract_server_name(server_element)
|
|
410
|
+
endpoint = self._extract_server_unique_name(server_element)
|
|
411
|
+
|
|
412
|
+
if not self._validate_server_strings(name, endpoint):
|
|
413
|
+
return None
|
|
414
|
+
|
|
415
|
+
return MCPServerConfig(mcp_server_name=name, mcp_server_unique_name=endpoint)
|
|
416
|
+
|
|
417
|
+
except Exception:
|
|
418
|
+
return None
|
|
419
|
+
|
|
420
|
+
# --------------------------------------------------------------------------
|
|
421
|
+
# VALIDATION AND UTILITY HELPERS
|
|
422
|
+
# --------------------------------------------------------------------------
|
|
423
|
+
|
|
424
|
+
def _validate_input_parameters(self, agentic_app_id: str, auth_token: str) -> None:
|
|
425
|
+
"""
|
|
426
|
+
Validates input parameters for the main API method.
|
|
427
|
+
|
|
428
|
+
Args:
|
|
429
|
+
agentic_app_id: Agentic App ID to validate.
|
|
430
|
+
auth_token: Authentication token to validate.
|
|
431
|
+
|
|
432
|
+
Raises:
|
|
433
|
+
ValueError: If any parameter is invalid or empty.
|
|
434
|
+
"""
|
|
435
|
+
if not agentic_app_id:
|
|
436
|
+
raise ValueError("agentic_app_id cannot be empty or None")
|
|
437
|
+
if not auth_token:
|
|
438
|
+
raise ValueError("auth_token cannot be empty or None")
|
|
439
|
+
|
|
440
|
+
def _extract_server_name(self, server_element: Dict[str, Any]) -> Optional[str]:
|
|
441
|
+
"""
|
|
442
|
+
Extracts server name from configuration element.
|
|
443
|
+
|
|
444
|
+
Args:
|
|
445
|
+
server_element: Configuration dictionary.
|
|
446
|
+
|
|
447
|
+
Returns:
|
|
448
|
+
Server name string or None.
|
|
449
|
+
"""
|
|
450
|
+
if "mcpServerName" in server_element and isinstance(server_element["mcpServerName"], str):
|
|
451
|
+
return server_element["mcpServerName"]
|
|
452
|
+
return None
|
|
453
|
+
|
|
454
|
+
def _extract_server_unique_name(self, server_element: Dict[str, Any]) -> Optional[str]:
|
|
455
|
+
"""
|
|
456
|
+
Extracts server unique name from configuration element.
|
|
457
|
+
|
|
458
|
+
Args:
|
|
459
|
+
server_element: Configuration dictionary.
|
|
460
|
+
|
|
461
|
+
Returns:
|
|
462
|
+
Server unique name string or None.
|
|
463
|
+
"""
|
|
464
|
+
if "mcpServerUniqueName" in server_element and isinstance(
|
|
465
|
+
server_element["mcpServerUniqueName"], str
|
|
466
|
+
):
|
|
467
|
+
return server_element["mcpServerUniqueName"]
|
|
468
|
+
return None
|
|
469
|
+
|
|
470
|
+
def _validate_server_strings(self, name: Optional[str], unique_name: Optional[str]) -> bool:
|
|
471
|
+
"""
|
|
472
|
+
Validates that server name and unique name are valid strings.
|
|
473
|
+
|
|
474
|
+
Args:
|
|
475
|
+
name: Server name to validate.
|
|
476
|
+
unique_name: Server unique name to validate.
|
|
477
|
+
|
|
478
|
+
Returns:
|
|
479
|
+
True if both strings are valid, False otherwise.
|
|
480
|
+
"""
|
|
481
|
+
return name is not None and name.strip() and unique_name is not None and unique_name.strip()
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Copyright (c) Microsoft Corporation.
|
|
2
|
+
# Licensed under the MIT License.
|
|
3
|
+
|
|
4
|
+
"""
|
|
5
|
+
Utility modules for the Microsoft Agent 365 Tooling SDK.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from .constants import Constants
|
|
9
|
+
from .utility import (
|
|
10
|
+
get_tooling_gateway_for_digital_worker,
|
|
11
|
+
get_mcp_base_url,
|
|
12
|
+
build_mcp_server_url,
|
|
13
|
+
get_tools_mode,
|
|
14
|
+
get_mcp_platform_authentication_scope,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
__all__ = [
|
|
18
|
+
"Constants",
|
|
19
|
+
"get_tooling_gateway_for_digital_worker",
|
|
20
|
+
"get_mcp_base_url",
|
|
21
|
+
"build_mcp_server_url",
|
|
22
|
+
"get_tools_mode",
|
|
23
|
+
"get_mcp_platform_authentication_scope",
|
|
24
|
+
]
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Copyright (c) Microsoft. All rights reserved.
|
|
2
|
+
|
|
3
|
+
"""
|
|
4
|
+
Provides constant values used throughout the Tooling components.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Constants:
|
|
9
|
+
"""
|
|
10
|
+
Provides constant values used throughout the Tooling components.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
class Headers:
|
|
14
|
+
"""
|
|
15
|
+
Provides constant header values used for authentication.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
#: The header name used for HTTP authorization tokens.
|
|
19
|
+
AUTHORIZATION = "Authorization"
|
|
20
|
+
|
|
21
|
+
#: The prefix used for Bearer authentication tokens in HTTP headers.
|
|
22
|
+
BEARER_PREFIX = "Bearer"
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
# Copyright (c) Microsoft. All rights reserved.
|
|
2
|
+
|
|
3
|
+
"""
|
|
4
|
+
Provides utility functions for the Tooling components.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import os
|
|
8
|
+
from enum import Enum
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class ToolsMode(Enum):
|
|
12
|
+
"""Enumeration for different tools modes."""
|
|
13
|
+
|
|
14
|
+
MOCK_MCP_SERVER = "MockMCPServer"
|
|
15
|
+
MCP_PLATFORM = "MCPPlatform"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
# Constants for base URLs
|
|
19
|
+
MCP_PLATFORM_PROD_BASE_URL = "https://agent365.svc.cloud.microsoft"
|
|
20
|
+
|
|
21
|
+
PPAPI_TOKEN_SCOPE = "https://api.powerplatform.com"
|
|
22
|
+
PROD_MCP_PLATFORM_AUTHENTICATION_SCOPE = "ea9ffc3e-8a23-4a7d-836d-234d7c7565c1/.default"
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def get_tooling_gateway_for_digital_worker(agentic_app_id: str) -> str:
|
|
26
|
+
"""
|
|
27
|
+
Gets the tooling gateway URL for the specified digital worker.
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
agentic_app_id: The agentic app identifier of the digital worker.
|
|
31
|
+
|
|
32
|
+
Returns:
|
|
33
|
+
str: The tooling gateway URL for the digital worker.
|
|
34
|
+
"""
|
|
35
|
+
# The endpoint needs to be updated based on the environment (prod, dev, etc.)
|
|
36
|
+
return f"{_get_mcp_platform_base_url()}/agents/{agentic_app_id}/mcpServers"
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def get_mcp_base_url() -> str:
|
|
40
|
+
"""
|
|
41
|
+
Gets the base URL for MCP servers.
|
|
42
|
+
|
|
43
|
+
Returns:
|
|
44
|
+
str: The base URL for MCP servers.
|
|
45
|
+
"""
|
|
46
|
+
environment = _get_current_environment().lower()
|
|
47
|
+
|
|
48
|
+
if environment == "development":
|
|
49
|
+
tools_mode = get_tools_mode()
|
|
50
|
+
if tools_mode == ToolsMode.MOCK_MCP_SERVER:
|
|
51
|
+
return os.getenv("MOCK_MCP_SERVER_URL", "http://localhost:5309/mcp-mock/agents/servers")
|
|
52
|
+
|
|
53
|
+
return f"{_get_mcp_platform_base_url()}/agents/servers"
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def build_mcp_server_url(server_name: str) -> str:
|
|
57
|
+
"""
|
|
58
|
+
Constructs the full MCP server URL using the base URL and server name.
|
|
59
|
+
|
|
60
|
+
Args:
|
|
61
|
+
server_name: The MCP server name.
|
|
62
|
+
|
|
63
|
+
Returns:
|
|
64
|
+
str: The full MCP server URL.
|
|
65
|
+
"""
|
|
66
|
+
base_url = get_mcp_base_url()
|
|
67
|
+
|
|
68
|
+
return f"{base_url}/{server_name}"
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _get_current_environment() -> str:
|
|
72
|
+
"""
|
|
73
|
+
Gets the current environment name.
|
|
74
|
+
|
|
75
|
+
Returns:
|
|
76
|
+
str: The current environment name.
|
|
77
|
+
"""
|
|
78
|
+
return os.getenv("ASPNETCORE_ENVIRONMENT") or os.getenv("DOTNET_ENVIRONMENT") or "Development"
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def _get_mcp_platform_base_url() -> str:
|
|
82
|
+
"""
|
|
83
|
+
Gets the base URL for MCP platform, defaults to production URL if not set.
|
|
84
|
+
|
|
85
|
+
Returns:
|
|
86
|
+
str: The base URL for MCP platform.
|
|
87
|
+
"""
|
|
88
|
+
if os.getenv("MCP_PLATFORM_ENDPOINT") is not None:
|
|
89
|
+
return os.getenv("MCP_PLATFORM_ENDPOINT")
|
|
90
|
+
|
|
91
|
+
return MCP_PLATFORM_PROD_BASE_URL
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def get_tools_mode() -> ToolsMode:
|
|
95
|
+
"""
|
|
96
|
+
Gets the tools mode for the application.
|
|
97
|
+
|
|
98
|
+
Returns:
|
|
99
|
+
ToolsMode: The tools mode enum value.
|
|
100
|
+
"""
|
|
101
|
+
tools_mode = os.getenv("TOOLS_MODE", "MCPPlatform").lower()
|
|
102
|
+
|
|
103
|
+
if tools_mode == "mockmcpserver":
|
|
104
|
+
return ToolsMode.MOCK_MCP_SERVER
|
|
105
|
+
else:
|
|
106
|
+
return ToolsMode.MCP_PLATFORM
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def get_mcp_platform_authentication_scope():
|
|
110
|
+
"""
|
|
111
|
+
Gets the MCP platform authentication scope based on the current environment.
|
|
112
|
+
|
|
113
|
+
Returns:
|
|
114
|
+
list: A list containing the appropriate MCP platform authentication scope.
|
|
115
|
+
"""
|
|
116
|
+
environment = _get_current_environment().lower()
|
|
117
|
+
|
|
118
|
+
envScope = os.getenv("MCP_PLATFORM_AUTHENTICATION_SCOPE", "")
|
|
119
|
+
|
|
120
|
+
if envScope:
|
|
121
|
+
return [envScope]
|
|
122
|
+
|
|
123
|
+
return [PROD_MCP_PLATFORM_AUTHENTICATION_SCOPE]
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: microsoft-agents-a365-tooling
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Agent 365 Tooling SDK, providing functionality to work with Agent 365 Tools.
|
|
5
|
+
Author-email: Microsoft <support@microsoft.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/microsoft/Agent365-python
|
|
8
|
+
Project-URL: Repository, https://github.com/microsoft/Agent365-python
|
|
9
|
+
Project-URL: Issues, https://github.com/microsoft/Agent365-python/issues
|
|
10
|
+
Project-URL: Documentation, https://github.com/microsoft/Agent365-python/tree/main/libraries/microsoft-agents-a365-tooling
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Operating System :: OS Independent
|
|
17
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
18
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
19
|
+
Requires-Python: >=3.11
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
Requires-Dist: pydantic>=2.0.0
|
|
22
|
+
Requires-Dist: typing-extensions>=4.0.0
|
|
23
|
+
Provides-Extra: dev
|
|
24
|
+
Requires-Dist: pytest>=7.0.0; extra == "dev"
|
|
25
|
+
Requires-Dist: pytest-asyncio>=0.21.0; extra == "dev"
|
|
26
|
+
Requires-Dist: black>=23.0.0; extra == "dev"
|
|
27
|
+
Requires-Dist: ruff>=0.1.0; extra == "dev"
|
|
28
|
+
Requires-Dist: mypy>=1.0.0; extra == "dev"
|
|
29
|
+
|
|
30
|
+
# microsoft-agents-a365-tooling
|
|
31
|
+
|
|
32
|
+
[](https://pypi.org/project/microsoft-agents-a365-tooling)
|
|
33
|
+
[](https://pypi.org/project/microsoft-agents-a365-tooling)
|
|
34
|
+
|
|
35
|
+
Core tooling functionality for MCP (Model Context Protocol) tool server management in applications built with the Microsoft Agent 365 SDK. This package provides the foundation for discovering, registering, and managing tool servers across different AI frameworks.
|
|
36
|
+
|
|
37
|
+
## Installation
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
pip install microsoft-agents-a365-tooling
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Usage
|
|
44
|
+
|
|
45
|
+
For usage examples and detailed documentation, see the [Tooling documentation](https://learn.microsoft.com/microsoft-agent-365/developer/tooling?tabs=python) on Microsoft Learn.
|
|
46
|
+
|
|
47
|
+
## Support
|
|
48
|
+
|
|
49
|
+
For issues, questions, or feedback:
|
|
50
|
+
|
|
51
|
+
- File issues in the [GitHub Issues](https://github.com/microsoft/Agent365-python/issues) section
|
|
52
|
+
- See the [main documentation](../../../README.md) for more information
|
|
53
|
+
|
|
54
|
+
## Trademarks
|
|
55
|
+
|
|
56
|
+
*Microsoft, Windows, Microsoft Azure and/or other Microsoft products and services referenced in the documentation may be either trademarks or registered trademarks of Microsoft in the United States and/or other countries. The licenses for this project do not grant you rights to use any Microsoft names, logos, or trademarks. Microsoft's general trademark guidelines can be found at http://go.microsoft.com/fwlink/?LinkID=254653.*
|
|
57
|
+
|
|
58
|
+
## License
|
|
59
|
+
|
|
60
|
+
Copyright (c) Microsoft Corporation. All rights reserved.
|
|
61
|
+
|
|
62
|
+
Licensed under the MIT License - see the [LICENSE](../../../LICENSE.md) file for details.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
microsoft_agents_a365/tooling/__init__.py,sha256=bpk3bKvjOPS59JxqMC0wxuEvL0onB33lb_0Fn77Enns,696
|
|
2
|
+
microsoft_agents_a365/tooling/models/__init__.py,sha256=h--GXLSDDOVh1A07GfNF799d-hnowt_2BEJr4pOaptI,239
|
|
3
|
+
microsoft_agents_a365/tooling/models/mcp_server_config.py,sha256=kdmP712DltY9qMb6VEJ9qE0GGwi7gEHlxfjYT8XTA8M,732
|
|
4
|
+
microsoft_agents_a365/tooling/services/__init__.py,sha256=7qpWbK20z-8HyLaXm5y1H_alRtpmfEnl7TnWwSko-0M,332
|
|
5
|
+
microsoft_agents_a365/tooling/services/mcp_tool_server_configuration_service.py,sha256=SfNR58uoqsN94It9dGhxa58h_i_EzDrj0ilS22Cu7DM,17638
|
|
6
|
+
microsoft_agents_a365/tooling/utils/__init__.py,sha256=OlytL5dUPJf3Bx8aUzSLXBvhDMSy85q4ZLXTC0Mx134,550
|
|
7
|
+
microsoft_agents_a365/tooling/utils/constants.py,sha256=HoJF9sCGJYP7iIMdn07wbe3Z8S-RG-e-CuikeYNfwz8,550
|
|
8
|
+
microsoft_agents_a365/tooling/utils/utility.py,sha256=91tYpogNOSWOxaT9tpHIRKw9nFJ5KzUBHc2QZxScr8Q,3219
|
|
9
|
+
microsoft_agents_a365_tooling-0.1.0.dist-info/METADATA,sha256=BYLLNFoP7f6XmtKhfAHC34j1ToGz8mOB7OveWWe5-AQ,3071
|
|
10
|
+
microsoft_agents_a365_tooling-0.1.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
11
|
+
microsoft_agents_a365_tooling-0.1.0.dist-info/top_level.txt,sha256=G3c2_4sy5_EM_BWO67SbK2tKj4G8XFn-QXRbh8g9Lgk,22
|
|
12
|
+
microsoft_agents_a365_tooling-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
microsoft_agents_a365
|