Spaces:
Running
Running
| from typing import Any, cast | |
| import mcp.types | |
| from mcp.types import BlobResourceContents, TextResourceContents | |
| import fastmcp | |
| from fastmcp.client import Client | |
| from fastmcp.prompts import Message, Prompt | |
| from fastmcp.resources import Resource, ResourceTemplate | |
| from fastmcp.server.context import Context | |
| from fastmcp.server.server import FastMCP | |
| from fastmcp.tools.tool import Tool | |
| from fastmcp.utilities.func_metadata import func_metadata | |
| from fastmcp.utilities.logging import get_logger | |
| logger = get_logger(__name__) | |
| def _proxy_passthrough(): | |
| pass | |
| class ProxyTool(Tool): | |
| def __init__(self, client: "Client", **kwargs): | |
| super().__init__(**kwargs) | |
| self._client = client | |
| async def from_client(cls, client: "Client", tool: mcp.types.Tool) -> "ProxyTool": | |
| return cls( | |
| client=client, | |
| name=tool.name, | |
| description=tool.description, | |
| parameters=tool.inputSchema, | |
| fn=_proxy_passthrough, | |
| fn_metadata=func_metadata(_proxy_passthrough), | |
| is_async=True, | |
| ) | |
| async def run( | |
| self, arguments: dict[str, Any], context: Context | None = None | |
| ) -> Any: | |
| # the client context manager will swallow any exceptions inside a TaskGroup | |
| # so we return the raw result and raise an exception ourselves | |
| async with self._client: | |
| result = await self._client.call_tool( | |
| self.name, arguments, _return_raw_result=True | |
| ) | |
| if result.isError: | |
| raise ValueError(cast(mcp.types.TextContent, result.content[0]).text) | |
| return result.content | |
| class ProxyResource(Resource): | |
| def __init__( | |
| self, client: "Client", *, _value: str | bytes | None = None, **kwargs | |
| ): | |
| super().__init__(**kwargs) | |
| self._client = client | |
| self._value = _value | |
| async def from_client( | |
| cls, client: "Client", resource: mcp.types.Resource | |
| ) -> "ProxyResource": | |
| return cls( | |
| client=client, | |
| uri=resource.uri, | |
| name=resource.name, | |
| description=resource.description, | |
| mime_type=resource.mimeType, | |
| ) | |
| async def read(self) -> str | bytes: | |
| if self._value is not None: | |
| return self._value | |
| async with self._client: | |
| result = await self._client.read_resource(self.uri) | |
| if isinstance(result[0], TextResourceContents): | |
| return result[0].text | |
| elif isinstance(result[0], BlobResourceContents): | |
| return result[0].blob | |
| else: | |
| raise ValueError(f"Unsupported content type: {type(result[0])}") | |
| class ProxyTemplate(ResourceTemplate): | |
| def __init__(self, client: "Client", **kwargs): | |
| super().__init__(**kwargs) | |
| self._client = client | |
| async def from_client( | |
| cls, client: "Client", template: mcp.types.ResourceTemplate | |
| ) -> "ProxyTemplate": | |
| return cls( | |
| client=client, | |
| uri_template=template.uriTemplate, | |
| name=template.name, | |
| description=template.description, | |
| fn=_proxy_passthrough, | |
| parameters={}, | |
| ) | |
| async def create_resource(self, uri: str, params: dict[str, Any]) -> ProxyResource: | |
| async with self._client: | |
| result = await self._client.read_resource(uri) | |
| if isinstance(result[0], TextResourceContents): | |
| value = result[0].text | |
| elif isinstance(result[0], BlobResourceContents): | |
| value = result[0].blob | |
| else: | |
| raise ValueError(f"Unsupported content type: {type(result[0])}") | |
| return ProxyResource( | |
| client=self._client, | |
| uri=uri, | |
| name=self.name, | |
| description=self.description, | |
| mime_type=result[0].mimeType, | |
| contents=result, | |
| _value=value, | |
| ) | |
| class ProxyPrompt(Prompt): | |
| def __init__(self, client: "Client", **kwargs): | |
| super().__init__(**kwargs) | |
| self._client = client | |
| async def from_client( | |
| cls, client: "Client", prompt: mcp.types.Prompt | |
| ) -> "ProxyPrompt": | |
| return cls( | |
| client=client, | |
| name=prompt.name, | |
| description=prompt.description, | |
| arguments=[a.model_dump() for a in prompt.arguments or []], | |
| fn=_proxy_passthrough, | |
| ) | |
| async def render(self, arguments: dict[str, Any]) -> list[Message]: | |
| async with self._client: | |
| result = await self._client.get_prompt(self.name, arguments) | |
| return [Message(role=m.role, content=m.content) for m in result.messages] | |
| class FastMCPProxy(FastMCP): | |
| def __init__(self, _async_constructor: bool, **kwargs): | |
| if not _async_constructor: | |
| raise ValueError( | |
| "FastMCPProxy() was initialied unexpectedly. Please use a constructor like `FastMCPProxy.from_client()` instead." | |
| ) | |
| super().__init__(**kwargs) | |
| async def from_client( | |
| cls, | |
| client: "Client", | |
| name: str | None = None, | |
| **settings: fastmcp.settings.ServerSettings, | |
| ) -> "FastMCPProxy": | |
| """Create a FastMCP proxy server from a client. | |
| This method creates a new FastMCP server instance that proxies requests to the provided client. | |
| It discovers the client's tools, resources, prompts, and templates, and creates corresponding | |
| components in the server that forward requests to the client. | |
| Args: | |
| client: The client to proxy requests to | |
| name: Optional name for the new FastMCP server (defaults to client name if available) | |
| **settings: Additional settings for the FastMCP server | |
| Returns: | |
| A FastMCP server that proxies requests to the client | |
| """ | |
| server = cls(name=name, **settings, _async_constructor=True) | |
| async with client: | |
| # Register proxies for client tools | |
| tools = await client.list_tools() | |
| for tool in tools: | |
| tool_proxy = await ProxyTool.from_client(client, tool) | |
| server._tool_manager._tools[tool_proxy.name] = tool_proxy | |
| logger.debug(f"Created proxy for tool: {tool_proxy.name}") | |
| # Register proxies for client resources | |
| resources = await client.list_resources() | |
| for resource in resources: | |
| resource_proxy = await ProxyResource.from_client(client, resource) | |
| server._resource_manager._resources[str(resource_proxy.uri)] = ( | |
| resource_proxy | |
| ) | |
| logger.debug(f"Created proxy for resource: {resource_proxy.uri}") | |
| # Register proxies for client resource templates | |
| templates = await client.list_resource_templates() | |
| for template in templates: | |
| template_proxy = await ProxyTemplate.from_client(client, template) | |
| server._resource_manager._templates[template_proxy.uri_template] = ( | |
| template_proxy | |
| ) | |
| logger.debug( | |
| f"Created proxy for template: {template_proxy.uri_template}" | |
| ) | |
| # Register proxies for client prompts | |
| prompts = await client.list_prompts() | |
| for prompt in prompts: | |
| prompt_proxy = await ProxyPrompt.from_client(client, prompt) | |
| server._prompt_manager._prompts[prompt_proxy.name] = prompt_proxy | |
| logger.debug(f"Created proxy for prompt: {prompt_proxy.name}") | |
| logger.info(f"Created server '{server.name}' proxying to client: {client}") | |
| return server | |
| async def from_server(cls, server: FastMCP, **settings: Any) -> "FastMCPProxy": | |
| client = Client(transport=fastmcp.client.transports.FastMCPTransport(server)) | |
| return await cls.from_client(client, **settings) | |