fastmcp 1.0__py3-none-any.whl → 2.0.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.
@@ -0,0 +1,219 @@
1
+ from typing import Any, cast
2
+
3
+ import mcp.types
4
+ from mcp.types import BlobResourceContents, PromptMessage, TextResourceContents
5
+
6
+ import fastmcp
7
+ from fastmcp.client import Client
8
+ from fastmcp.prompts import Prompt
9
+ from fastmcp.resources import Resource, ResourceTemplate
10
+ from fastmcp.server.context import Context
11
+ from fastmcp.server.server import FastMCP
12
+ from fastmcp.tools.base import Tool
13
+ from fastmcp.utilities.func_metadata import func_metadata
14
+ from fastmcp.utilities.logging import get_logger
15
+
16
+ logger = get_logger(__name__)
17
+
18
+
19
+ def _proxy_passthrough():
20
+ pass
21
+
22
+
23
+ class ProxyTool(Tool):
24
+ def __init__(self, client: "Client", **kwargs):
25
+ super().__init__(**kwargs)
26
+ self._client = client
27
+
28
+ @classmethod
29
+ async def from_client(cls, client: "Client", tool: mcp.types.Tool) -> "ProxyTool":
30
+ return cls(
31
+ client=client,
32
+ name=tool.name,
33
+ description=tool.description,
34
+ parameters=tool.inputSchema,
35
+ fn=_proxy_passthrough,
36
+ fn_metadata=func_metadata(_proxy_passthrough),
37
+ is_async=True,
38
+ )
39
+
40
+ async def run(
41
+ self, arguments: dict[str, Any], context: Context | None = None
42
+ ) -> Any:
43
+ async with self._client:
44
+ result = await self._client.call_tool(self.name, arguments)
45
+ if result.isError:
46
+ raise ValueError(cast(mcp.types.TextContent, result.content[0]).text)
47
+ return result.content[0]
48
+
49
+
50
+ class ProxyResource(Resource):
51
+ def __init__(
52
+ self, client: "Client", *, _value: str | bytes | None = None, **kwargs
53
+ ):
54
+ super().__init__(**kwargs)
55
+ self._client = client
56
+ self._value = _value
57
+
58
+ @classmethod
59
+ async def from_client(
60
+ cls, client: "Client", resource: mcp.types.Resource
61
+ ) -> "ProxyResource":
62
+ return cls(
63
+ client=client,
64
+ uri=resource.uri,
65
+ name=resource.name,
66
+ description=resource.description,
67
+ mime_type=resource.mimeType,
68
+ )
69
+
70
+ async def read(self) -> str | bytes:
71
+ if self._value is not None:
72
+ return self._value
73
+
74
+ async with self._client:
75
+ result = await self._client.read_resource(self.uri)
76
+ if isinstance(result.contents[0], TextResourceContents):
77
+ return result.contents[0].text
78
+ elif isinstance(result.contents[0], BlobResourceContents):
79
+ return result.contents[0].blob
80
+ else:
81
+ raise ValueError(f"Unsupported content type: {type(result.contents[0])}")
82
+
83
+
84
+ class ProxyTemplate(ResourceTemplate):
85
+ def __init__(self, client: "Client", **kwargs):
86
+ super().__init__(**kwargs)
87
+ self._client = client
88
+
89
+ @classmethod
90
+ async def from_client(
91
+ cls, client: "Client", template: mcp.types.ResourceTemplate
92
+ ) -> "ProxyTemplate":
93
+ return cls(
94
+ client=client,
95
+ uri_template=template.uriTemplate,
96
+ name=template.name,
97
+ description=template.description,
98
+ fn=_proxy_passthrough,
99
+ parameters={},
100
+ )
101
+
102
+ async def create_resource(self, uri: str, params: dict[str, Any]) -> ProxyResource:
103
+ async with self._client:
104
+ result = await self._client.read_resource(uri)
105
+
106
+ if isinstance(result.contents[0], TextResourceContents):
107
+ value = result.contents[0].text
108
+ elif isinstance(result.contents[0], BlobResourceContents):
109
+ value = result.contents[0].blob
110
+ else:
111
+ raise ValueError(f"Unsupported content type: {type(result.contents[0])}")
112
+
113
+ return ProxyResource(
114
+ client=self._client,
115
+ uri=uri,
116
+ name=self.name,
117
+ description=self.description,
118
+ mime_type=result.contents[0].mimeType,
119
+ contents=result.contents,
120
+ _value=value,
121
+ )
122
+
123
+
124
+ class ProxyPrompt(Prompt):
125
+ def __init__(self, client: "Client", **kwargs):
126
+ super().__init__(**kwargs)
127
+ self._client = client
128
+
129
+ @classmethod
130
+ async def from_client(
131
+ cls, client: "Client", prompt: mcp.types.Prompt
132
+ ) -> "ProxyPrompt":
133
+ return cls(
134
+ client=client,
135
+ name=prompt.name,
136
+ description=prompt.description,
137
+ arguments=[a.model_dump() for a in prompt.arguments or []],
138
+ fn=_proxy_passthrough,
139
+ )
140
+
141
+ async def render(self, arguments: dict[str, Any]) -> list[PromptMessage]:
142
+ async with self._client:
143
+ result = await self._client.get_prompt(self.name, arguments)
144
+ return result.messages
145
+
146
+
147
+ class FastMCPProxy(FastMCP):
148
+ def __init__(self, _async_constructor: bool, **kwargs):
149
+ if not _async_constructor:
150
+ raise ValueError(
151
+ "FastMCPProxy() was initialied unexpectedly. Please use a constructor like `FastMCPProxy.from_client()` instead."
152
+ )
153
+ super().__init__(**kwargs)
154
+
155
+ @classmethod
156
+ async def from_client(
157
+ cls,
158
+ client: "Client",
159
+ name: str | None = None,
160
+ **settings: fastmcp.settings.ServerSettings,
161
+ ) -> "FastMCPProxy":
162
+ """Create a FastMCP proxy server from a client.
163
+
164
+ This method creates a new FastMCP server instance that proxies requests to the provided client.
165
+ It discovers the client's tools, resources, prompts, and templates, and creates corresponding
166
+ components in the server that forward requests to the client.
167
+
168
+ Args:
169
+ client: The client to proxy requests to
170
+ name: Optional name for the new FastMCP server (defaults to client name if available)
171
+ **settings: Additional settings for the FastMCP server
172
+
173
+ Returns:
174
+ A FastMCP server that proxies requests to the client
175
+ """
176
+ server = cls(name=name, **settings, _async_constructor=True)
177
+
178
+ async with client:
179
+ # Register proxies for client tools
180
+ tools_result = await client.list_tools()
181
+ for tool in tools_result.tools:
182
+ tool_proxy = await ProxyTool.from_client(client, tool)
183
+ server._tool_manager._tools[tool_proxy.name] = tool_proxy
184
+ logger.debug(f"Created proxy for tool: {tool_proxy.name}")
185
+
186
+ # Register proxies for client resources
187
+ resources_result = await client.list_resources()
188
+ for resource in resources_result.resources:
189
+ resource_proxy = await ProxyResource.from_client(client, resource)
190
+ server._resource_manager._resources[str(resource_proxy.uri)] = (
191
+ resource_proxy
192
+ )
193
+ logger.debug(f"Created proxy for resource: {resource_proxy.uri}")
194
+
195
+ # Register proxies for client resource templates
196
+ templates_result = await client.list_resource_templates()
197
+ for template in templates_result.resourceTemplates:
198
+ template_proxy = await ProxyTemplate.from_client(client, template)
199
+ server._resource_manager._templates[template_proxy.uri_template] = (
200
+ template_proxy
201
+ )
202
+ logger.debug(
203
+ f"Created proxy for template: {template_proxy.uri_template}"
204
+ )
205
+
206
+ # Register proxies for client prompts
207
+ prompts_result = await client.list_prompts()
208
+ for prompt in prompts_result.prompts:
209
+ prompt_proxy = await ProxyPrompt.from_client(client, prompt)
210
+ server._prompt_manager._prompts[prompt_proxy.name] = prompt_proxy
211
+ logger.debug(f"Created proxy for prompt: {prompt_proxy.name}")
212
+
213
+ logger.info(f"Created server '{server.name}' proxying to client: {client}")
214
+ return server
215
+
216
+ @classmethod
217
+ async def from_server(cls, server: FastMCP, **settings: Any) -> "FastMCPProxy":
218
+ client = Client(transport=fastmcp.client.transports.FastMCPTransport(server))
219
+ return await cls.from_client(client, **settings)