Spaces:
Running
Running
File size: 9,653 Bytes
3d30e82 15f96d2 7826473 81e8514 8cc3f59 81e8514 8cc3f59 81e8514 7826473 3acddf3 2c75133 3becd4d 48f7143 7826473 fa364d7 7826473 3d30e82 7826473 3d30e82 7826473 3d30e82 7826473 3d30e82 1534a61 9dc2a8a c4ac623 7826473 7e50621 c4ac623 7826473 2c75133 c4ac623 7826473 3d30e82 7826473 3d30e82 7826473 1534a61 7826473 c4ac623 7826473 2c75133 7826473 3d30e82 7826473 3d30e82 7826473 3d30e82 1534a61 3d30e82 1ac845d 15f96d2 7826473 15f96d2 7826473 c4ac623 7826473 2c75133 7826473 15f96d2 7826473 c4ac623 7826473 3d30e82 7826473 3d30e82 7826473 1534a61 7826473 3becd4d 7826473 3d30e82 7826473 81e8514 d32f68a fd9bcfe d32f68a dccb245 d32f68a 81e8514 d32f68a dccb245 d32f68a 81e8514 d32f68a dccb245 d32f68a 81e8514 d32f68a dccb245 d32f68a 81e8514 3becd4d | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 | from __future__ import annotations
from typing import TYPE_CHECKING, Any, cast
from urllib.parse import quote
import mcp.types
from mcp.server.lowlevel.helper_types import ReadResourceContents
from mcp.shared.exceptions import McpError
from mcp.types import (
METHOD_NOT_FOUND,
BlobResourceContents,
EmbeddedResource,
GetPromptResult,
ImageContent,
TextContent,
TextResourceContents,
)
from pydantic.networks import AnyUrl
from fastmcp.client import Client
from fastmcp.exceptions import NotFoundError, ResourceError, ToolError
from fastmcp.prompts import Prompt, PromptMessage
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.logging import get_logger
if TYPE_CHECKING:
from fastmcp.server import Context
logger = get_logger(__name__)
def _proxy_passthrough():
pass
class ProxyTool(Tool):
def __init__(self, client: Client, **kwargs):
super().__init__(**kwargs)
self._client = client
@classmethod
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,
)
async def run(
self,
arguments: dict[str, Any],
context: Context | None = None,
) -> list[TextContent | ImageContent | EmbeddedResource]:
# 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_mcp(
name=self.name,
arguments=arguments,
)
if result.isError:
raise ToolError(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
@classmethod
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 ResourceError(f"Unsupported content type: {type(result[0])}")
class ProxyTemplate(ResourceTemplate):
def __init__(self, client: Client, **kwargs):
super().__init__(**kwargs)
self._client = client
@classmethod
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],
context: Context | None = None,
) -> ProxyResource:
# don't use the provided uri, because it may not be the same as the
# uri_template on the remote server.
# quote params to ensure they are valid for the uri_template
parameterized_uri = self.uri_template.format(
**{k: quote(v, safe="") for k, v in params.items()}
)
async with self._client:
result = await self._client.read_resource(parameterized_uri)
if isinstance(result[0], TextResourceContents):
value = result[0].text
elif isinstance(result[0], BlobResourceContents):
value = result[0].blob
else:
raise ResourceError(f"Unsupported content type: {type(result[0])}")
return ProxyResource(
client=self._client,
uri=parameterized_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
@classmethod
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[PromptMessage]:
async with self._client:
result = await self._client.get_prompt(self.name, arguments)
return result.messages
class FastMCPProxy(FastMCP):
def __init__(self, client: Client, **kwargs):
super().__init__(**kwargs)
self.client = client
async def get_tools(self) -> dict[str, Tool]:
tools = await super().get_tools()
async with self.client:
try:
client_tools = await self.client.list_tools()
except McpError as e:
if e.error.code == METHOD_NOT_FOUND:
client_tools = []
else:
raise e
for tool in client_tools:
tool_proxy = await ProxyTool.from_client(self.client, tool)
tools[tool_proxy.name] = tool_proxy
return tools
async def get_resources(self) -> dict[str, Resource]:
resources = await super().get_resources()
async with self.client:
try:
client_resources = await self.client.list_resources()
except McpError as e:
if e.error.code == METHOD_NOT_FOUND:
client_resources = []
else:
raise e
for resource in client_resources:
resource_proxy = await ProxyResource.from_client(self.client, resource)
resources[str(resource_proxy.uri)] = resource_proxy
return resources
async def get_resource_templates(self) -> dict[str, ResourceTemplate]:
templates = await super().get_resource_templates()
async with self.client:
try:
client_templates = await self.client.list_resource_templates()
except McpError as e:
if e.error.code == METHOD_NOT_FOUND:
client_templates = []
else:
raise e
for template in client_templates:
template_proxy = await ProxyTemplate.from_client(self.client, template)
templates[template_proxy.uri_template] = template_proxy
return templates
async def get_prompts(self) -> dict[str, Prompt]:
prompts = await super().get_prompts()
async with self.client:
try:
client_prompts = await self.client.list_prompts()
except McpError as e:
if e.error.code == METHOD_NOT_FOUND:
client_prompts = []
else:
raise e
for prompt in client_prompts:
prompt_proxy = await ProxyPrompt.from_client(self.client, prompt)
prompts[prompt_proxy.name] = prompt_proxy
return prompts
async def _mcp_call_tool(
self, key: str, arguments: dict[str, Any]
) -> list[TextContent | ImageContent | EmbeddedResource]:
try:
result = await super()._mcp_call_tool(key, arguments)
return result
except NotFoundError:
async with self.client:
result = await self.client.call_tool(key, arguments)
return result
async def _mcp_read_resource(self, uri: AnyUrl | str) -> list[ReadResourceContents]:
try:
result = await super()._mcp_read_resource(uri)
return result
except NotFoundError:
async with self.client:
resource = await self.client.read_resource(uri)
if isinstance(resource[0], TextResourceContents):
content = resource[0].text
elif isinstance(resource[0], BlobResourceContents):
content = resource[0].blob
else:
raise ValueError(f"Unsupported content type: {type(resource[0])}")
return [
ReadResourceContents(content=content, mime_type=resource[0].mimeType)
]
async def _mcp_get_prompt(
self, name: str, arguments: dict[str, Any] | None = None
) -> GetPromptResult:
try:
result = await super()._mcp_get_prompt(name, arguments)
return result
except NotFoundError:
async with self.client:
result = await self.client.get_prompt(name, arguments)
return result
|