fastmcp 2.1.2__py3-none-any.whl → 2.2.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.
fastmcp/server/proxy.py CHANGED
@@ -1,10 +1,20 @@
1
1
  from typing import Any, cast
2
+ from urllib.parse import quote
2
3
 
3
4
  import mcp.types
4
- from mcp.types import BlobResourceContents, TextResourceContents
5
+ from mcp.server.lowlevel.helper_types import ReadResourceContents
6
+ from mcp.types import (
7
+ BlobResourceContents,
8
+ EmbeddedResource,
9
+ GetPromptResult,
10
+ ImageContent,
11
+ TextContent,
12
+ TextResourceContents,
13
+ )
14
+ from pydantic.networks import AnyUrl
5
15
 
6
- import fastmcp
7
16
  from fastmcp.client import Client
17
+ from fastmcp.exceptions import NotFoundError
8
18
  from fastmcp.prompts import Message, Prompt
9
19
  from fastmcp.resources import Resource, ResourceTemplate
10
20
  from fastmcp.server.context import Context
@@ -104,8 +114,14 @@ class ProxyTemplate(ResourceTemplate):
104
114
  )
105
115
 
106
116
  async def create_resource(self, uri: str, params: dict[str, Any]) -> ProxyResource:
117
+ # dont use the provided uri, because it may not be the same as the
118
+ # uri_template on the remote server.
119
+ # quote params to ensure they are valid for the uri_template
120
+ parameterized_uri = self.uri_template.format(
121
+ **{k: quote(v, safe="") for k, v in params.items()}
122
+ )
107
123
  async with self._client:
108
- result = await self._client.read_resource(uri)
124
+ result = await self._client.read_resource(parameterized_uri)
109
125
 
110
126
  if isinstance(result[0], TextResourceContents):
111
127
  value = result[0].text
@@ -116,7 +132,7 @@ class ProxyTemplate(ResourceTemplate):
116
132
 
117
133
  return ProxyResource(
118
134
  client=self._client,
119
- uri=uri,
135
+ uri=parameterized_uri,
120
136
  name=self.name,
121
137
  description=self.description,
122
138
  mime_type=result[0].mimeType,
@@ -145,79 +161,89 @@ class ProxyPrompt(Prompt):
145
161
  async def render(self, arguments: dict[str, Any]) -> list[Message]:
146
162
  async with self._client:
147
163
  result = await self._client.get_prompt(self.name, arguments)
148
- return [Message(role=m.role, content=m.content) for m in result.messages]
164
+ return [Message(role=m.role, content=m.content) for m in result]
149
165
 
150
166
 
151
167
  class FastMCPProxy(FastMCP):
152
- def __init__(self, _async_constructor: bool, **kwargs):
153
- if not _async_constructor:
154
- raise ValueError(
155
- "FastMCPProxy() was initialied unexpectedly. Please use a constructor like `FastMCPProxy.from_client()` instead."
156
- )
168
+ def __init__(self, client: "Client", **kwargs):
157
169
  super().__init__(**kwargs)
158
-
159
- @classmethod
160
- async def from_client(
161
- cls,
162
- client: "Client",
163
- name: str | None = None,
164
- **settings: fastmcp.settings.ServerSettings,
165
- ) -> "FastMCPProxy":
166
- """Create a FastMCP proxy server from a client.
167
-
168
- This method creates a new FastMCP server instance that proxies requests to the provided client.
169
- It discovers the client's tools, resources, prompts, and templates, and creates corresponding
170
- components in the server that forward requests to the client.
171
-
172
- Args:
173
- client: The client to proxy requests to
174
- name: Optional name for the new FastMCP server (defaults to client name if available)
175
- **settings: Additional settings for the FastMCP server
176
-
177
- Returns:
178
- A FastMCP server that proxies requests to the client
179
- """
180
- server = cls(name=name, **settings, _async_constructor=True)
181
-
182
- async with client:
183
- # Register proxies for client tools
184
- tools = await client.list_tools()
185
- for tool in tools:
186
- tool_proxy = await ProxyTool.from_client(client, tool)
187
- server._tool_manager._tools[tool_proxy.name] = tool_proxy
188
- logger.debug(f"Created proxy for tool: {tool_proxy.name}")
189
-
190
- # Register proxies for client resources
191
- resources = await client.list_resources()
192
- for resource in resources:
193
- resource_proxy = await ProxyResource.from_client(client, resource)
194
- server._resource_manager._resources[str(resource_proxy.uri)] = (
195
- resource_proxy
196
- )
197
- logger.debug(f"Created proxy for resource: {resource_proxy.uri}")
198
-
199
- # Register proxies for client resource templates
200
- templates = await client.list_resource_templates()
201
- for template in templates:
202
- template_proxy = await ProxyTemplate.from_client(client, template)
203
- server._resource_manager._templates[template_proxy.uri_template] = (
204
- template_proxy
205
- )
206
- logger.debug(
207
- f"Created proxy for template: {template_proxy.uri_template}"
208
- )
209
-
210
- # Register proxies for client prompts
211
- prompts = await client.list_prompts()
212
- for prompt in prompts:
213
- prompt_proxy = await ProxyPrompt.from_client(client, prompt)
214
- server._prompt_manager._prompts[prompt_proxy.name] = prompt_proxy
215
- logger.debug(f"Created proxy for prompt: {prompt_proxy.name}")
216
-
217
- logger.info(f"Created server '{server.name}' proxying to client: {client}")
218
- return server
219
-
220
- @classmethod
221
- async def from_server(cls, server: FastMCP, **settings: Any) -> "FastMCPProxy":
222
- client = Client(transport=fastmcp.client.transports.FastMCPTransport(server))
223
- return await cls.from_client(client, **settings)
170
+ self.client = client
171
+
172
+ async def get_tools(self) -> dict[str, Tool]:
173
+ tools = await super().get_tools()
174
+
175
+ async with self.client:
176
+ for tool in await self.client.list_tools():
177
+ tool_proxy = await ProxyTool.from_client(self.client, tool)
178
+ tools[tool_proxy.name] = tool_proxy
179
+
180
+ return tools
181
+
182
+ async def get_resources(self) -> dict[str, Resource]:
183
+ resources = await super().get_resources()
184
+
185
+ async with self.client:
186
+ for resource in await self.client.list_resources():
187
+ resource_proxy = await ProxyResource.from_client(self.client, resource)
188
+ resources[str(resource_proxy.uri)] = resource_proxy
189
+
190
+ return resources
191
+
192
+ async def get_resource_templates(self) -> dict[str, ResourceTemplate]:
193
+ templates = await super().get_resource_templates()
194
+
195
+ async with self.client:
196
+ for template in await self.client.list_resource_templates():
197
+ template_proxy = await ProxyTemplate.from_client(self.client, template)
198
+ templates[template_proxy.uri_template] = template_proxy
199
+
200
+ return templates
201
+
202
+ async def get_prompts(self) -> dict[str, Prompt]:
203
+ prompts = await super().get_prompts()
204
+
205
+ async with self.client:
206
+ for prompt in await self.client.list_prompts():
207
+ prompt_proxy = await ProxyPrompt.from_client(self.client, prompt)
208
+ prompts[prompt_proxy.name] = prompt_proxy
209
+ return prompts
210
+
211
+ async def _mcp_call_tool(
212
+ self, key: str, arguments: dict[str, Any]
213
+ ) -> list[TextContent | ImageContent | EmbeddedResource]:
214
+ try:
215
+ result = await super()._mcp_call_tool(key, arguments)
216
+ return result
217
+ except NotFoundError:
218
+ async with self.client:
219
+ result = await self.client.call_tool(key, arguments)
220
+ return result
221
+
222
+ async def _mcp_read_resource(self, uri: AnyUrl | str) -> list[ReadResourceContents]:
223
+ try:
224
+ result = await super()._mcp_read_resource(uri)
225
+ return result
226
+ except NotFoundError:
227
+ async with self.client:
228
+ resource = await self.client.read_resource(uri)
229
+ if isinstance(resource[0], TextResourceContents):
230
+ content = resource[0].text
231
+ elif isinstance(resource[0], BlobResourceContents):
232
+ content = resource[0].blob
233
+ else:
234
+ raise ValueError(f"Unsupported content type: {type(resource[0])}")
235
+
236
+ return [
237
+ ReadResourceContents(content=content, mime_type=resource[0].mimeType)
238
+ ]
239
+
240
+ async def _mcp_get_prompt(
241
+ self, name: str, arguments: dict[str, Any] | None = None
242
+ ) -> GetPromptResult:
243
+ try:
244
+ result = await super()._mcp_get_prompt(name, arguments)
245
+ return result
246
+ except NotFoundError:
247
+ async with self.client:
248
+ result = await self.client.get_prompt(name, arguments)
249
+ return GetPromptResult(messages=result)