fastmcp 1.0__py3-none-any.whl → 2.1.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.
Files changed (39) hide show
  1. fastmcp/__init__.py +15 -4
  2. fastmcp/cli/__init__.py +0 -1
  3. fastmcp/cli/claude.py +13 -11
  4. fastmcp/cli/cli.py +59 -39
  5. fastmcp/client/__init__.py +25 -0
  6. fastmcp/client/base.py +1 -0
  7. fastmcp/client/client.py +226 -0
  8. fastmcp/client/roots.py +75 -0
  9. fastmcp/client/sampling.py +50 -0
  10. fastmcp/client/transports.py +411 -0
  11. fastmcp/prompts/__init__.py +2 -2
  12. fastmcp/prompts/{base.py → prompt.py} +47 -26
  13. fastmcp/prompts/prompt_manager.py +69 -15
  14. fastmcp/resources/__init__.py +6 -6
  15. fastmcp/resources/{base.py → resource.py} +21 -2
  16. fastmcp/resources/resource_manager.py +116 -17
  17. fastmcp/resources/{templates.py → template.py} +36 -11
  18. fastmcp/resources/types.py +18 -13
  19. fastmcp/server/__init__.py +5 -0
  20. fastmcp/server/context.py +222 -0
  21. fastmcp/server/openapi.py +637 -0
  22. fastmcp/server/proxy.py +223 -0
  23. fastmcp/{server.py → server/server.py} +323 -267
  24. fastmcp/settings.py +81 -0
  25. fastmcp/tools/__init__.py +1 -1
  26. fastmcp/tools/{base.py → tool.py} +47 -18
  27. fastmcp/tools/tool_manager.py +57 -16
  28. fastmcp/utilities/func_metadata.py +33 -19
  29. fastmcp/utilities/openapi.py +797 -0
  30. fastmcp/utilities/types.py +15 -4
  31. fastmcp-2.1.0.dist-info/METADATA +770 -0
  32. fastmcp-2.1.0.dist-info/RECORD +39 -0
  33. fastmcp-2.1.0.dist-info/licenses/LICENSE +201 -0
  34. fastmcp/prompts/manager.py +0 -50
  35. fastmcp-1.0.dist-info/METADATA +0 -604
  36. fastmcp-1.0.dist-info/RECORD +0 -28
  37. fastmcp-1.0.dist-info/licenses/LICENSE +0 -21
  38. {fastmcp-1.0.dist-info → fastmcp-2.1.0.dist-info}/WHEEL +0 -0
  39. {fastmcp-1.0.dist-info → fastmcp-2.1.0.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,223 @@
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.tool 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
+ # the client context manager will swallow any exceptions inside a TaskGroup
44
+ # so we return the raw result and raise an exception ourselves
45
+ async with self._client:
46
+ result = await self._client.call_tool(
47
+ self.name, arguments, _return_raw_result=True
48
+ )
49
+ if result.isError:
50
+ raise ValueError(cast(mcp.types.TextContent, result.content[0]).text)
51
+ return result.content
52
+
53
+
54
+ class ProxyResource(Resource):
55
+ def __init__(
56
+ self, client: "Client", *, _value: str | bytes | None = None, **kwargs
57
+ ):
58
+ super().__init__(**kwargs)
59
+ self._client = client
60
+ self._value = _value
61
+
62
+ @classmethod
63
+ async def from_client(
64
+ cls, client: "Client", resource: mcp.types.Resource
65
+ ) -> "ProxyResource":
66
+ return cls(
67
+ client=client,
68
+ uri=resource.uri,
69
+ name=resource.name,
70
+ description=resource.description,
71
+ mime_type=resource.mimeType,
72
+ )
73
+
74
+ async def read(self) -> str | bytes:
75
+ if self._value is not None:
76
+ return self._value
77
+
78
+ async with self._client:
79
+ result = await self._client.read_resource(self.uri)
80
+ if isinstance(result[0], TextResourceContents):
81
+ return result[0].text
82
+ elif isinstance(result[0], BlobResourceContents):
83
+ return result[0].blob
84
+ else:
85
+ raise ValueError(f"Unsupported content type: {type(result[0])}")
86
+
87
+
88
+ class ProxyTemplate(ResourceTemplate):
89
+ def __init__(self, client: "Client", **kwargs):
90
+ super().__init__(**kwargs)
91
+ self._client = client
92
+
93
+ @classmethod
94
+ async def from_client(
95
+ cls, client: "Client", template: mcp.types.ResourceTemplate
96
+ ) -> "ProxyTemplate":
97
+ return cls(
98
+ client=client,
99
+ uri_template=template.uriTemplate,
100
+ name=template.name,
101
+ description=template.description,
102
+ fn=_proxy_passthrough,
103
+ parameters={},
104
+ )
105
+
106
+ async def create_resource(self, uri: str, params: dict[str, Any]) -> ProxyResource:
107
+ async with self._client:
108
+ result = await self._client.read_resource(uri)
109
+
110
+ if isinstance(result[0], TextResourceContents):
111
+ value = result[0].text
112
+ elif isinstance(result[0], BlobResourceContents):
113
+ value = result[0].blob
114
+ else:
115
+ raise ValueError(f"Unsupported content type: {type(result[0])}")
116
+
117
+ return ProxyResource(
118
+ client=self._client,
119
+ uri=uri,
120
+ name=self.name,
121
+ description=self.description,
122
+ mime_type=result[0].mimeType,
123
+ contents=result,
124
+ _value=value,
125
+ )
126
+
127
+
128
+ class ProxyPrompt(Prompt):
129
+ def __init__(self, client: "Client", **kwargs):
130
+ super().__init__(**kwargs)
131
+ self._client = client
132
+
133
+ @classmethod
134
+ async def from_client(
135
+ cls, client: "Client", prompt: mcp.types.Prompt
136
+ ) -> "ProxyPrompt":
137
+ return cls(
138
+ client=client,
139
+ name=prompt.name,
140
+ description=prompt.description,
141
+ arguments=[a.model_dump() for a in prompt.arguments or []],
142
+ fn=_proxy_passthrough,
143
+ )
144
+
145
+ async def render(self, arguments: dict[str, Any]) -> list[PromptMessage]:
146
+ async with self._client:
147
+ result = await self._client.get_prompt(self.name, arguments)
148
+ return result.messages
149
+
150
+
151
+ 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
+ )
157
+ 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)