fastmcp 0.4.1__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.
- fastmcp/__init__.py +15 -4
- fastmcp/cli/__init__.py +0 -1
- fastmcp/cli/claude.py +13 -11
- fastmcp/cli/cli.py +61 -41
- fastmcp/client/__init__.py +25 -0
- fastmcp/client/base.py +1 -0
- fastmcp/client/client.py +181 -0
- fastmcp/client/roots.py +75 -0
- fastmcp/client/sampling.py +50 -0
- fastmcp/client/transports.py +411 -0
- fastmcp/prompts/__init__.py +1 -1
- fastmcp/prompts/base.py +27 -26
- fastmcp/prompts/prompt_manager.py +50 -12
- fastmcp/resources/__init__.py +5 -5
- fastmcp/resources/base.py +2 -2
- fastmcp/resources/resource_manager.py +66 -9
- fastmcp/resources/templates.py +15 -10
- fastmcp/resources/types.py +16 -11
- fastmcp/server/__init__.py +5 -0
- fastmcp/server/context.py +222 -0
- fastmcp/server/openapi.py +625 -0
- fastmcp/server/proxy.py +219 -0
- fastmcp/{server.py → server/server.py} +251 -265
- fastmcp/settings.py +73 -0
- fastmcp/tools/base.py +28 -18
- fastmcp/tools/tool_manager.py +45 -10
- fastmcp/utilities/func_metadata.py +33 -19
- fastmcp/utilities/openapi.py +797 -0
- fastmcp/utilities/types.py +3 -4
- fastmcp-2.0.0.dist-info/METADATA +770 -0
- fastmcp-2.0.0.dist-info/RECORD +39 -0
- {fastmcp-0.4.1.dist-info → fastmcp-2.0.0.dist-info}/WHEEL +1 -1
- fastmcp-2.0.0.dist-info/licenses/LICENSE +201 -0
- fastmcp/prompts/manager.py +0 -50
- fastmcp-0.4.1.dist-info/METADATA +0 -587
- fastmcp-0.4.1.dist-info/RECORD +0 -28
- fastmcp-0.4.1.dist-info/licenses/LICENSE +0 -21
- {fastmcp-0.4.1.dist-info → fastmcp-2.0.0.dist-info}/entry_points.txt +0 -0
fastmcp/server/proxy.py
ADDED
|
@@ -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)
|