Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -97,14 +97,10 @@ async def add_tool_servers_to_agent(

# Add servers as MCPStreamableHTTPTool instances
for config in server_configs:
try:
server_url = getattr(config, "server_url", None) or getattr(
config, "mcp_server_unique_name", None
)
if not server_url:
self._logger.warning(f"MCP server config missing server_url: {config}")
continue
# Use mcp_server_name if available (not None or empty), otherwise fall back to mcp_server_unique_name
server_name = config.mcp_server_name or config.mcp_server_unique_name

try:
# Prepare auth headers
headers = {}
if auth_token:
Expand All @@ -116,26 +112,23 @@ async def add_tool_servers_to_agent(
self._orchestrator_name
)

server_name = getattr(config, "mcp_server_name", "Unknown")

# Create and configure MCPStreamableHTTPTool
mcp_tools = MCPStreamableHTTPTool(
name=server_name,
url=server_url,
url=config.url,
headers=headers,
description=f"MCP tools from {server_name}",
)

# Let Agent Framework handle the connection automatically
self._logger.info(f"Created MCP plugin for '{server_name}' at {server_url}")
self._logger.info(f"Created MCP plugin for '{server_name}' at {config.url}")

all_tools.append(mcp_tools)
self._connected_servers.append(mcp_tools)

self._logger.info(f"Added MCP plugin '{server_name}' to agent tools")

except Exception as tool_ex:
server_name = getattr(config, "mcp_server_name", "Unknown")
self._logger.warning(
f"Failed to create MCP plugin for {server_name}: {tool_ex}"
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -179,8 +179,11 @@ async def _get_mcp_tool_definitions_and_resources(
else server.mcp_server_name
)

# Use the URL from server (always populated by the configuration service)
server_url = server.url

# Create MCP tool using Azure Foundry SDK
mcp_tool = McpTool(server_label=server_label, server_url=server.mcp_server_unique_name)
mcp_tool = McpTool(server_label=server_label, server_url=server_url)

# Configure the tool
mcp_tool.set_approval_mode("never")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,9 +102,13 @@ async def add_tool_servers_to_agent(
# Convert MCP server configs to MCPServerInfo objects
mcp_servers_info = []
for server_config in mcp_server_configs:
# Use mcp_server_name if available (not None or empty), otherwise fall back to mcp_server_unique_name
server_name = server_config.mcp_server_name or server_config.mcp_server_unique_name
# Use the URL from config (always populated by the configuration service)
server_url = server_config.url
server_info = MCPServerInfo(
name=server_config.mcp_server_name,
url=server_config.mcp_server_unique_name,
name=server_name,
url=server_url,
)
mcp_servers_info.append(server_info)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,17 +126,23 @@ async def add_tool_servers_to_agent(
self._orchestrator_name
)

# Use the URL from server (always populated by the configuration service)
server_url = server.url

# Use mcp_server_name if available (not None or empty), otherwise fall back to mcp_server_unique_name
server_name = server.mcp_server_name or server.mcp_server_unique_name

plugin = MCPStreamableHttpPlugin(
name=server.mcp_server_name,
url=server.mcp_server_unique_name,
name=server_name,
url=server_url,
headers=headers,
)

# Connect the plugin
await plugin.connect()

# Add plugin to kernel
kernel.add_plugin(plugin, server.mcp_server_name)
kernel.add_plugin(plugin, server_name)

# Store reference to keep plugin alive throughout application lifecycle
# By storing plugin references in _connected_plugins, we prevent Python's garbage collector from cleaning up the plugin objects
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"""

from dataclasses import dataclass
from typing import Optional


@dataclass
Expand All @@ -20,6 +21,10 @@ class MCPServerConfig:
#: Gets or sets the unique name of the MCP server.
mcp_server_unique_name: str

#: Gets or sets the custom URL for the MCP server. If provided, this URL will be used
#: instead of constructing the URL from the base URL and unique name.
url: Optional[str] = None

def __post_init__(self):
"""Validate the configuration after initialization."""
if not self.mcp_server_name:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -412,16 +412,26 @@ def _parse_manifest_server_config(
MCPServerConfig object or None if parsing fails.
"""
try:
name = self._extract_server_name(server_element)
server_name = self._extract_server_unique_name(server_element)
mcp_server_name = self._extract_server_name(server_element)
mcp_server_unique_name = self._extract_server_unique_name(server_element)

if not self._validate_server_strings(name, server_name):
if not self._validate_server_strings(mcp_server_name, mcp_server_unique_name):
return None

# Construct full URL using environment utilities
full_url = build_mcp_server_url(server_name)
# Check if a URL is provided
endpoint = self._extract_server_url(server_element)

return MCPServerConfig(mcp_server_name=name, mcp_server_unique_name=full_url)
# Use mcp_server_name if available, otherwise fall back to mcp_server_unique_name for URL construction
server_name = mcp_server_name or mcp_server_unique_name

# Determine the final URL: use custom URL if provided, otherwise construct it
final_url = endpoint if endpoint else build_mcp_server_url(server_name)

return MCPServerConfig(
mcp_server_name=mcp_server_name,
mcp_server_unique_name=mcp_server_unique_name,
url=final_url,
)

except Exception:
return None
Expand All @@ -439,13 +449,26 @@ def _parse_gateway_server_config(
MCPServerConfig object or None if parsing fails.
"""
try:
name = self._extract_server_name(server_element)
endpoint = self._extract_server_unique_name(server_element)
mcp_server_name = self._extract_server_name(server_element)
mcp_server_unique_name = self._extract_server_unique_name(server_element)

if not self._validate_server_strings(name, endpoint):
if not self._validate_server_strings(mcp_server_name, mcp_server_unique_name):
return None

return MCPServerConfig(mcp_server_name=name, mcp_server_unique_name=endpoint)
# Check if a URL is provided by the gateway
endpoint = self._extract_server_url(server_element)

# Use mcp_server_name if available, otherwise fall back to mcp_server_unique_name for URL construction
server_name = mcp_server_name or mcp_server_unique_name

# Determine the final URL: use custom URL if provided, otherwise construct it
final_url = endpoint if endpoint else build_mcp_server_url(server_name)

return MCPServerConfig(
mcp_server_name=mcp_server_name,
mcp_server_unique_name=mcp_server_unique_name,
url=final_url,
)

except Exception:
return None
Expand Down Expand Up @@ -500,6 +523,21 @@ def _extract_server_unique_name(self, server_element: Dict[str, Any]) -> Optiona
return server_element["mcpServerUniqueName"]
return None

def _extract_server_url(self, server_element: Dict[str, Any]) -> Optional[str]:
"""
Extracts custom server URL from configuration element.

Args:
server_element: Configuration dictionary.

Returns:
Server URL string or None.
"""
# Check for 'url' field in both manifest and gateway responses
if "url" in server_element and isinstance(server_element["url"], str):
return server_element["url"]
return None

def _validate_server_strings(self, name: Optional[str], unique_name: Optional[str]) -> bool:
"""
Validates that server name and unique name are valid strings.
Expand Down
2 changes: 2 additions & 0 deletions tests/tooling/__init__.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.

"""Tests for tooling components."""
Loading
Loading