Spaces:
Running
Running
Jeremiah Lowin commited on
Commit ·
db65850
1
Parent(s): e423e66
Update proxy behavior and conflict resolution
Browse files- src/fastmcp/server/proxy.py +26 -15
- src/fastmcp/server/server.py +27 -21
src/fastmcp/server/proxy.py
CHANGED
|
@@ -172,8 +172,11 @@ class FastMCPProxy(FastMCP):
|
|
| 172 |
super().__init__(**kwargs)
|
| 173 |
self.client = client
|
| 174 |
|
| 175 |
-
async def
|
| 176 |
-
tools =
|
|
|
|
|
|
|
|
|
|
| 177 |
|
| 178 |
async with self.client:
|
| 179 |
try:
|
|
@@ -187,12 +190,17 @@ class FastMCPProxy(FastMCP):
|
|
| 187 |
# don't overwrite tools defined in the server
|
| 188 |
if tool.name not in tools:
|
| 189 |
tool_proxy = await ProxyTool.from_client(self.client, tool)
|
| 190 |
-
tools[tool_proxy.
|
| 191 |
|
| 192 |
-
return tools
|
| 193 |
|
| 194 |
-
async def
|
| 195 |
-
resources =
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 196 |
|
| 197 |
async with self.client:
|
| 198 |
try:
|
|
@@ -204,16 +212,19 @@ class FastMCPProxy(FastMCP):
|
|
| 204 |
raise e
|
| 205 |
for resource in client_resources:
|
| 206 |
# don't overwrite resources defined in the server
|
| 207 |
-
if
|
| 208 |
resource_proxy = await ProxyResource.from_client(
|
| 209 |
self.client, resource
|
| 210 |
)
|
| 211 |
-
resources[
|
| 212 |
|
| 213 |
-
return resources
|
| 214 |
|
| 215 |
-
async def
|
| 216 |
-
templates =
|
|
|
|
|
|
|
|
|
|
| 217 |
|
| 218 |
async with self.client:
|
| 219 |
try:
|
|
@@ -231,10 +242,10 @@ class FastMCPProxy(FastMCP):
|
|
| 231 |
)
|
| 232 |
templates[template_proxy.uri_template] = template_proxy
|
| 233 |
|
| 234 |
-
return templates
|
| 235 |
|
| 236 |
-
async def
|
| 237 |
-
prompts = await super().
|
| 238 |
|
| 239 |
async with self.client:
|
| 240 |
try:
|
|
@@ -250,7 +261,7 @@ class FastMCPProxy(FastMCP):
|
|
| 250 |
prompt_proxy = await ProxyPrompt.from_client(self.client, prompt)
|
| 251 |
prompts[prompt_proxy.name] = prompt_proxy
|
| 252 |
|
| 253 |
-
return prompts
|
| 254 |
|
| 255 |
async def _call_tool(self, key: str, arguments: dict[str, Any]) -> list[MCPContent]:
|
| 256 |
try:
|
|
|
|
| 172 |
super().__init__(**kwargs)
|
| 173 |
self.client = client
|
| 174 |
|
| 175 |
+
async def _list_tools(self, apply_middleware: bool = True) -> list[Tool]:
|
| 176 |
+
tools = {
|
| 177 |
+
tool.name: tool
|
| 178 |
+
for tool in await super()._list_tools(apply_middleware=apply_middleware)
|
| 179 |
+
}
|
| 180 |
|
| 181 |
async with self.client:
|
| 182 |
try:
|
|
|
|
| 190 |
# don't overwrite tools defined in the server
|
| 191 |
if tool.name not in tools:
|
| 192 |
tool_proxy = await ProxyTool.from_client(self.client, tool)
|
| 193 |
+
tools[tool_proxy.key] = tool_proxy
|
| 194 |
|
| 195 |
+
return list(tools.values())
|
| 196 |
|
| 197 |
+
async def _list_resources(self, apply_middleware: bool = True) -> list[Resource]:
|
| 198 |
+
resources = {
|
| 199 |
+
resource.uri: resource
|
| 200 |
+
for resource in await super()._list_resources(
|
| 201 |
+
apply_middleware=apply_middleware
|
| 202 |
+
)
|
| 203 |
+
}
|
| 204 |
|
| 205 |
async with self.client:
|
| 206 |
try:
|
|
|
|
| 212 |
raise e
|
| 213 |
for resource in client_resources:
|
| 214 |
# don't overwrite resources defined in the server
|
| 215 |
+
if resource.uri not in resources:
|
| 216 |
resource_proxy = await ProxyResource.from_client(
|
| 217 |
self.client, resource
|
| 218 |
)
|
| 219 |
+
resources[resource_proxy.uri] = resource_proxy
|
| 220 |
|
| 221 |
+
return list(resources.values())
|
| 222 |
|
| 223 |
+
async def _list_resource_templates(self) -> list[ResourceTemplate]:
|
| 224 |
+
templates = {
|
| 225 |
+
template.uri_template: template
|
| 226 |
+
for template in await super()._list_resource_templates()
|
| 227 |
+
}
|
| 228 |
|
| 229 |
async with self.client:
|
| 230 |
try:
|
|
|
|
| 242 |
)
|
| 243 |
templates[template_proxy.uri_template] = template_proxy
|
| 244 |
|
| 245 |
+
return list(templates.values())
|
| 246 |
|
| 247 |
+
async def _list_prompts(self) -> list[Prompt]:
|
| 248 |
+
prompts = {prompt.name: prompt for prompt in await super()._list_prompts()}
|
| 249 |
|
| 250 |
async with self.client:
|
| 251 |
try:
|
|
|
|
| 261 |
prompt_proxy = await ProxyPrompt.from_client(self.client, prompt)
|
| 262 |
prompts[prompt_proxy.name] = prompt_proxy
|
| 263 |
|
| 264 |
+
return list(prompts.values())
|
| 265 |
|
| 266 |
async def _call_tool(self, key: str, arguments: dict[str, Any]) -> list[MCPContent]:
|
| 267 |
try:
|
src/fastmcp/server/server.py
CHANGED
|
@@ -435,7 +435,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|
| 435 |
|
| 436 |
with fastmcp.server.context.Context(fastmcp=self):
|
| 437 |
tools = await self._middleware_list_tools()
|
| 438 |
-
return [tool.to_mcp_tool(name=tool.
|
| 439 |
|
| 440 |
async def _middleware_list_tools(self) -> list[Tool]:
|
| 441 |
"""
|
|
@@ -475,7 +475,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|
| 475 |
"""
|
| 476 |
|
| 477 |
if (tools := self._cache.get("tools")) is self._cache.NOT_FOUND:
|
| 478 |
-
tools:
|
| 479 |
|
| 480 |
# iterate such that new mounts overwrite older ones
|
| 481 |
for mounted_server in self._mounted_servers:
|
|
@@ -490,17 +490,17 @@ class FastMCP(Generic[LifespanResultT]):
|
|
| 490 |
if mounted_server.prefix:
|
| 491 |
for tool in server_tools:
|
| 492 |
tool = tool.with_key(f"{mounted_server.prefix}_{tool.key}")
|
| 493 |
-
tools.
|
| 494 |
else:
|
| 495 |
-
tools.
|
| 496 |
except Exception as e:
|
| 497 |
logger.warning(
|
| 498 |
f"Failed to get tools from mounted server '{mounted_server.prefix}': {e}"
|
| 499 |
)
|
| 500 |
continue
|
| 501 |
-
tools.
|
| 502 |
self._cache.set("tools", tools)
|
| 503 |
-
return tools
|
| 504 |
|
| 505 |
async def _mcp_list_resources(self) -> list[MCPResource]:
|
| 506 |
logger.debug("Handler called: list_resources")
|
|
@@ -549,7 +549,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|
| 549 |
"""
|
| 550 |
|
| 551 |
if (resources := self._cache.get("resources")) is self._cache.NOT_FOUND:
|
| 552 |
-
resources:
|
| 553 |
|
| 554 |
# iterate such that new mounts overwrite older ones
|
| 555 |
for mounted_server in self._mounted_servers:
|
|
@@ -570,17 +570,19 @@ class FastMCP(Generic[LifespanResultT]):
|
|
| 570 |
self.resource_prefix_format,
|
| 571 |
)
|
| 572 |
)
|
| 573 |
-
resources.
|
| 574 |
else:
|
| 575 |
-
resources.
|
|
|
|
|
|
|
| 576 |
except Exception as e:
|
| 577 |
logger.warning(
|
| 578 |
f"Failed to get resources from mounted server '{mounted_server.prefix}': {e}"
|
| 579 |
)
|
| 580 |
continue
|
| 581 |
-
resources.
|
| 582 |
self._cache.set("resources", resources)
|
| 583 |
-
return resources
|
| 584 |
|
| 585 |
async def _mcp_list_resource_templates(self) -> list[MCPResourceTemplate]:
|
| 586 |
logger.debug("Handler called: list_resource_templates")
|
|
@@ -634,7 +636,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|
| 634 |
if (
|
| 635 |
templates := self._cache.get("resource_templates")
|
| 636 |
) is self._cache.NOT_FOUND:
|
| 637 |
-
templates:
|
| 638 |
|
| 639 |
# iterate such that new mounts overwrite older ones
|
| 640 |
for mounted_server in self._mounted_servers:
|
|
@@ -655,18 +657,20 @@ class FastMCP(Generic[LifespanResultT]):
|
|
| 655 |
self.resource_prefix_format,
|
| 656 |
)
|
| 657 |
)
|
| 658 |
-
templates.
|
| 659 |
else:
|
| 660 |
-
templates.
|
|
|
|
|
|
|
| 661 |
except Exception as e:
|
| 662 |
logger.warning(
|
| 663 |
"Failed to get resource templates from mounted server "
|
| 664 |
f"'{mounted_server.prefix}': {e}"
|
| 665 |
)
|
| 666 |
continue
|
| 667 |
-
templates.
|
| 668 |
self._cache.set("resource_templates", templates)
|
| 669 |
-
return templates
|
| 670 |
|
| 671 |
async def _mcp_list_prompts(self) -> list[MCPPrompt]:
|
| 672 |
logger.debug("Handler called: list_prompts")
|
|
@@ -713,7 +717,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|
| 713 |
"""
|
| 714 |
|
| 715 |
if (prompts := self._cache.get("prompts")) is self._cache.NOT_FOUND:
|
| 716 |
-
prompts:
|
| 717 |
|
| 718 |
# iterate such that new mounts overwrite older ones
|
| 719 |
for mounted_server in self._mounted_servers:
|
|
@@ -730,17 +734,19 @@ class FastMCP(Generic[LifespanResultT]):
|
|
| 730 |
prompt = prompt.with_key(
|
| 731 |
f"{mounted_server.prefix}_{prompt.key}"
|
| 732 |
)
|
| 733 |
-
prompts.
|
| 734 |
else:
|
| 735 |
-
prompts.
|
|
|
|
|
|
|
| 736 |
except Exception as e:
|
| 737 |
logger.warning(
|
| 738 |
f"Failed to get prompts from mounted server '{mounted_server.prefix}': {e}"
|
| 739 |
)
|
| 740 |
continue
|
| 741 |
-
prompts.
|
| 742 |
self._cache.set("prompts", prompts)
|
| 743 |
-
return prompts
|
| 744 |
|
| 745 |
async def _mcp_call_tool(
|
| 746 |
self, key: str, arguments: dict[str, Any]
|
|
|
|
| 435 |
|
| 436 |
with fastmcp.server.context.Context(fastmcp=self):
|
| 437 |
tools = await self._middleware_list_tools()
|
| 438 |
+
return [tool.to_mcp_tool(name=tool.key) for tool in tools]
|
| 439 |
|
| 440 |
async def _middleware_list_tools(self) -> list[Tool]:
|
| 441 |
"""
|
|
|
|
| 475 |
"""
|
| 476 |
|
| 477 |
if (tools := self._cache.get("tools")) is self._cache.NOT_FOUND:
|
| 478 |
+
tools: dict[str, Tool] = {}
|
| 479 |
|
| 480 |
# iterate such that new mounts overwrite older ones
|
| 481 |
for mounted_server in self._mounted_servers:
|
|
|
|
| 490 |
if mounted_server.prefix:
|
| 491 |
for tool in server_tools:
|
| 492 |
tool = tool.with_key(f"{mounted_server.prefix}_{tool.key}")
|
| 493 |
+
tools[tool.key] = tool
|
| 494 |
else:
|
| 495 |
+
tools.update({tool.key: tool for tool in server_tools})
|
| 496 |
except Exception as e:
|
| 497 |
logger.warning(
|
| 498 |
f"Failed to get tools from mounted server '{mounted_server.prefix}': {e}"
|
| 499 |
)
|
| 500 |
continue
|
| 501 |
+
tools.update(self._tool_manager.get_tools())
|
| 502 |
self._cache.set("tools", tools)
|
| 503 |
+
return list(tools.values())
|
| 504 |
|
| 505 |
async def _mcp_list_resources(self) -> list[MCPResource]:
|
| 506 |
logger.debug("Handler called: list_resources")
|
|
|
|
| 549 |
"""
|
| 550 |
|
| 551 |
if (resources := self._cache.get("resources")) is self._cache.NOT_FOUND:
|
| 552 |
+
resources: dict[str, Resource] = {}
|
| 553 |
|
| 554 |
# iterate such that new mounts overwrite older ones
|
| 555 |
for mounted_server in self._mounted_servers:
|
|
|
|
| 570 |
self.resource_prefix_format,
|
| 571 |
)
|
| 572 |
)
|
| 573 |
+
resources[resource.key] = resource
|
| 574 |
else:
|
| 575 |
+
resources.update(
|
| 576 |
+
{resource.key: resource for resource in server_resources}
|
| 577 |
+
)
|
| 578 |
except Exception as e:
|
| 579 |
logger.warning(
|
| 580 |
f"Failed to get resources from mounted server '{mounted_server.prefix}': {e}"
|
| 581 |
)
|
| 582 |
continue
|
| 583 |
+
resources.update(self._resource_manager.get_resources())
|
| 584 |
self._cache.set("resources", resources)
|
| 585 |
+
return list(resources.values())
|
| 586 |
|
| 587 |
async def _mcp_list_resource_templates(self) -> list[MCPResourceTemplate]:
|
| 588 |
logger.debug("Handler called: list_resource_templates")
|
|
|
|
| 636 |
if (
|
| 637 |
templates := self._cache.get("resource_templates")
|
| 638 |
) is self._cache.NOT_FOUND:
|
| 639 |
+
templates: dict[str, ResourceTemplate] = {}
|
| 640 |
|
| 641 |
# iterate such that new mounts overwrite older ones
|
| 642 |
for mounted_server in self._mounted_servers:
|
|
|
|
| 657 |
self.resource_prefix_format,
|
| 658 |
)
|
| 659 |
)
|
| 660 |
+
templates[template.key] = template
|
| 661 |
else:
|
| 662 |
+
templates.update(
|
| 663 |
+
{template.key: template for template in server_templates}
|
| 664 |
+
)
|
| 665 |
except Exception as e:
|
| 666 |
logger.warning(
|
| 667 |
"Failed to get resource templates from mounted server "
|
| 668 |
f"'{mounted_server.prefix}': {e}"
|
| 669 |
)
|
| 670 |
continue
|
| 671 |
+
templates.update(self._resource_manager.get_templates())
|
| 672 |
self._cache.set("resource_templates", templates)
|
| 673 |
+
return list(templates.values())
|
| 674 |
|
| 675 |
async def _mcp_list_prompts(self) -> list[MCPPrompt]:
|
| 676 |
logger.debug("Handler called: list_prompts")
|
|
|
|
| 717 |
"""
|
| 718 |
|
| 719 |
if (prompts := self._cache.get("prompts")) is self._cache.NOT_FOUND:
|
| 720 |
+
prompts: dict[str, Prompt] = {}
|
| 721 |
|
| 722 |
# iterate such that new mounts overwrite older ones
|
| 723 |
for mounted_server in self._mounted_servers:
|
|
|
|
| 734 |
prompt = prompt.with_key(
|
| 735 |
f"{mounted_server.prefix}_{prompt.key}"
|
| 736 |
)
|
| 737 |
+
prompts[prompt.key] = prompt
|
| 738 |
else:
|
| 739 |
+
prompts.update(
|
| 740 |
+
{prompt.key: prompt for prompt in server_prompts}
|
| 741 |
+
)
|
| 742 |
except Exception as e:
|
| 743 |
logger.warning(
|
| 744 |
f"Failed to get prompts from mounted server '{mounted_server.prefix}': {e}"
|
| 745 |
)
|
| 746 |
continue
|
| 747 |
+
prompts.update(self._prompt_manager.get_prompts())
|
| 748 |
self._cache.set("prompts", prompts)
|
| 749 |
+
return list(prompts.values())
|
| 750 |
|
| 751 |
async def _mcp_call_tool(
|
| 752 |
self, key: str, arguments: dict[str, Any]
|