fastmcp 2.8.1__py3-none-any.whl → 2.9.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/cli/cli.py +99 -1
- fastmcp/cli/run.py +1 -3
- fastmcp/client/auth/oauth.py +1 -2
- fastmcp/client/client.py +21 -5
- fastmcp/client/transports.py +17 -2
- fastmcp/contrib/mcp_mixin/README.md +79 -2
- fastmcp/contrib/mcp_mixin/mcp_mixin.py +14 -0
- fastmcp/prompts/prompt.py +91 -11
- fastmcp/prompts/prompt_manager.py +119 -43
- fastmcp/resources/resource.py +11 -1
- fastmcp/resources/resource_manager.py +249 -76
- fastmcp/resources/template.py +27 -1
- fastmcp/server/auth/providers/bearer.py +32 -10
- fastmcp/server/context.py +41 -2
- fastmcp/server/http.py +8 -0
- fastmcp/server/middleware/__init__.py +6 -0
- fastmcp/server/middleware/error_handling.py +206 -0
- fastmcp/server/middleware/logging.py +165 -0
- fastmcp/server/middleware/middleware.py +236 -0
- fastmcp/server/middleware/rate_limiting.py +231 -0
- fastmcp/server/middleware/timing.py +156 -0
- fastmcp/server/proxy.py +250 -140
- fastmcp/server/server.py +320 -242
- fastmcp/settings.py +2 -2
- fastmcp/tools/tool.py +6 -2
- fastmcp/tools/tool_manager.py +114 -45
- fastmcp/utilities/components.py +22 -2
- fastmcp/utilities/inspect.py +326 -0
- fastmcp/utilities/json_schema.py +67 -23
- fastmcp/utilities/mcp_config.py +13 -7
- fastmcp/utilities/openapi.py +5 -3
- fastmcp/utilities/tests.py +1 -1
- fastmcp/utilities/types.py +90 -1
- {fastmcp-2.8.1.dist-info → fastmcp-2.9.0.dist-info}/METADATA +2 -2
- {fastmcp-2.8.1.dist-info → fastmcp-2.9.0.dist-info}/RECORD +38 -31
- {fastmcp-2.8.1.dist-info → fastmcp-2.9.0.dist-info}/WHEEL +0 -0
- {fastmcp-2.8.1.dist-info → fastmcp-2.9.0.dist-info}/entry_points.txt +0 -0
- {fastmcp-2.8.1.dist-info → fastmcp-2.9.0.dist-info}/licenses/LICENSE +0 -0
fastmcp/server/proxy.py
CHANGED
|
@@ -4,7 +4,6 @@ from typing import TYPE_CHECKING, Any, cast
|
|
|
4
4
|
from urllib.parse import quote
|
|
5
5
|
|
|
6
6
|
import mcp.types
|
|
7
|
-
from mcp.server.lowlevel.helper_types import ReadResourceContents
|
|
8
7
|
from mcp.shared.exceptions import McpError
|
|
9
8
|
from mcp.types import (
|
|
10
9
|
METHOD_NOT_FOUND,
|
|
@@ -17,10 +16,14 @@ from pydantic.networks import AnyUrl
|
|
|
17
16
|
from fastmcp.client import Client
|
|
18
17
|
from fastmcp.exceptions import NotFoundError, ResourceError, ToolError
|
|
19
18
|
from fastmcp.prompts import Prompt, PromptMessage
|
|
19
|
+
from fastmcp.prompts.prompt import PromptArgument
|
|
20
|
+
from fastmcp.prompts.prompt_manager import PromptManager
|
|
20
21
|
from fastmcp.resources import Resource, ResourceTemplate
|
|
22
|
+
from fastmcp.resources.resource_manager import ResourceManager
|
|
21
23
|
from fastmcp.server.context import Context
|
|
22
24
|
from fastmcp.server.server import FastMCP
|
|
23
25
|
from fastmcp.tools.tool import Tool
|
|
26
|
+
from fastmcp.tools.tool_manager import ToolManager
|
|
24
27
|
from fastmcp.utilities.logging import get_logger
|
|
25
28
|
from fastmcp.utilities.types import MCPContent
|
|
26
29
|
|
|
@@ -30,18 +33,197 @@ if TYPE_CHECKING:
|
|
|
30
33
|
logger = get_logger(__name__)
|
|
31
34
|
|
|
32
35
|
|
|
36
|
+
class ProxyToolManager(ToolManager):
|
|
37
|
+
"""A ToolManager that sources its tools from a remote client in addition to local and mounted tools."""
|
|
38
|
+
|
|
39
|
+
def __init__(self, client: Client, **kwargs):
|
|
40
|
+
super().__init__(**kwargs)
|
|
41
|
+
self.client = client
|
|
42
|
+
|
|
43
|
+
async def get_tools(self) -> dict[str, Tool]:
|
|
44
|
+
"""Gets the unfiltered tool inventory including local, mounted, and proxy tools."""
|
|
45
|
+
# First get local and mounted tools from parent
|
|
46
|
+
all_tools = await super().get_tools()
|
|
47
|
+
|
|
48
|
+
# Then add proxy tools, but don't overwrite existing ones
|
|
49
|
+
try:
|
|
50
|
+
async with self.client:
|
|
51
|
+
client_tools = await self.client.list_tools()
|
|
52
|
+
for tool in client_tools:
|
|
53
|
+
if tool.name not in all_tools:
|
|
54
|
+
all_tools[tool.name] = ProxyTool.from_mcp_tool(
|
|
55
|
+
self.client, tool
|
|
56
|
+
)
|
|
57
|
+
except McpError as e:
|
|
58
|
+
if e.error.code == METHOD_NOT_FOUND:
|
|
59
|
+
pass # No tools available from proxy
|
|
60
|
+
else:
|
|
61
|
+
raise e
|
|
62
|
+
|
|
63
|
+
return all_tools
|
|
64
|
+
|
|
65
|
+
async def list_tools(self) -> list[Tool]:
|
|
66
|
+
"""Gets the filtered list of tools including local, mounted, and proxy tools."""
|
|
67
|
+
tools_dict = await self.get_tools()
|
|
68
|
+
return list(tools_dict.values())
|
|
69
|
+
|
|
70
|
+
async def call_tool(self, key: str, arguments: dict[str, Any]) -> list[MCPContent]:
|
|
71
|
+
"""Calls a tool, trying local/mounted first, then proxy if not found."""
|
|
72
|
+
try:
|
|
73
|
+
# First try local and mounted tools
|
|
74
|
+
return await super().call_tool(key, arguments)
|
|
75
|
+
except NotFoundError:
|
|
76
|
+
# If not found locally, try proxy
|
|
77
|
+
async with self.client:
|
|
78
|
+
return await self.client.call_tool(key, arguments)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
class ProxyResourceManager(ResourceManager):
|
|
82
|
+
"""A ResourceManager that sources its resources from a remote client in addition to local and mounted resources."""
|
|
83
|
+
|
|
84
|
+
def __init__(self, client: Client, **kwargs):
|
|
85
|
+
super().__init__(**kwargs)
|
|
86
|
+
self.client = client
|
|
87
|
+
|
|
88
|
+
async def get_resources(self) -> dict[str, Resource]:
|
|
89
|
+
"""Gets the unfiltered resource inventory including local, mounted, and proxy resources."""
|
|
90
|
+
# First get local and mounted resources from parent
|
|
91
|
+
all_resources = await super().get_resources()
|
|
92
|
+
|
|
93
|
+
# Then add proxy resources, but don't overwrite existing ones
|
|
94
|
+
try:
|
|
95
|
+
async with self.client:
|
|
96
|
+
client_resources = await self.client.list_resources()
|
|
97
|
+
for resource in client_resources:
|
|
98
|
+
if str(resource.uri) not in all_resources:
|
|
99
|
+
all_resources[str(resource.uri)] = (
|
|
100
|
+
ProxyResource.from_mcp_resource(self.client, resource)
|
|
101
|
+
)
|
|
102
|
+
except McpError as e:
|
|
103
|
+
if e.error.code == METHOD_NOT_FOUND:
|
|
104
|
+
pass # No resources available from proxy
|
|
105
|
+
else:
|
|
106
|
+
raise e
|
|
107
|
+
|
|
108
|
+
return all_resources
|
|
109
|
+
|
|
110
|
+
async def get_resource_templates(self) -> dict[str, ResourceTemplate]:
|
|
111
|
+
"""Gets the unfiltered template inventory including local, mounted, and proxy templates."""
|
|
112
|
+
# First get local and mounted templates from parent
|
|
113
|
+
all_templates = await super().get_resource_templates()
|
|
114
|
+
|
|
115
|
+
# Then add proxy templates, but don't overwrite existing ones
|
|
116
|
+
try:
|
|
117
|
+
async with self.client:
|
|
118
|
+
client_templates = await self.client.list_resource_templates()
|
|
119
|
+
for template in client_templates:
|
|
120
|
+
if template.uriTemplate not in all_templates:
|
|
121
|
+
all_templates[template.uriTemplate] = (
|
|
122
|
+
ProxyTemplate.from_mcp_template(self.client, template)
|
|
123
|
+
)
|
|
124
|
+
except McpError as e:
|
|
125
|
+
if e.error.code == METHOD_NOT_FOUND:
|
|
126
|
+
pass # No templates available from proxy
|
|
127
|
+
else:
|
|
128
|
+
raise e
|
|
129
|
+
|
|
130
|
+
return all_templates
|
|
131
|
+
|
|
132
|
+
async def list_resources(self) -> list[Resource]:
|
|
133
|
+
"""Gets the filtered list of resources including local, mounted, and proxy resources."""
|
|
134
|
+
resources_dict = await self.get_resources()
|
|
135
|
+
return list(resources_dict.values())
|
|
136
|
+
|
|
137
|
+
async def list_resource_templates(self) -> list[ResourceTemplate]:
|
|
138
|
+
"""Gets the filtered list of templates including local, mounted, and proxy templates."""
|
|
139
|
+
templates_dict = await self.get_resource_templates()
|
|
140
|
+
return list(templates_dict.values())
|
|
141
|
+
|
|
142
|
+
async def read_resource(self, uri: AnyUrl | str) -> str | bytes:
|
|
143
|
+
"""Reads a resource, trying local/mounted first, then proxy if not found."""
|
|
144
|
+
try:
|
|
145
|
+
# First try local and mounted resources
|
|
146
|
+
return await super().read_resource(uri)
|
|
147
|
+
except NotFoundError:
|
|
148
|
+
# If not found locally, try proxy
|
|
149
|
+
async with self.client:
|
|
150
|
+
result = await self.client.read_resource(uri)
|
|
151
|
+
if isinstance(result[0], TextResourceContents):
|
|
152
|
+
return result[0].text
|
|
153
|
+
elif isinstance(result[0], BlobResourceContents):
|
|
154
|
+
return result[0].blob
|
|
155
|
+
else:
|
|
156
|
+
raise ResourceError(f"Unsupported content type: {type(result[0])}")
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
class ProxyPromptManager(PromptManager):
|
|
160
|
+
"""A PromptManager that sources its prompts from a remote client in addition to local and mounted prompts."""
|
|
161
|
+
|
|
162
|
+
def __init__(self, client: Client, **kwargs):
|
|
163
|
+
super().__init__(**kwargs)
|
|
164
|
+
self.client = client
|
|
165
|
+
|
|
166
|
+
async def get_prompts(self) -> dict[str, Prompt]:
|
|
167
|
+
"""Gets the unfiltered prompt inventory including local, mounted, and proxy prompts."""
|
|
168
|
+
# First get local and mounted prompts from parent
|
|
169
|
+
all_prompts = await super().get_prompts()
|
|
170
|
+
|
|
171
|
+
# Then add proxy prompts, but don't overwrite existing ones
|
|
172
|
+
try:
|
|
173
|
+
async with self.client:
|
|
174
|
+
client_prompts = await self.client.list_prompts()
|
|
175
|
+
for prompt in client_prompts:
|
|
176
|
+
if prompt.name not in all_prompts:
|
|
177
|
+
all_prompts[prompt.name] = ProxyPrompt.from_mcp_prompt(
|
|
178
|
+
self.client, prompt
|
|
179
|
+
)
|
|
180
|
+
except McpError as e:
|
|
181
|
+
if e.error.code == METHOD_NOT_FOUND:
|
|
182
|
+
pass # No prompts available from proxy
|
|
183
|
+
else:
|
|
184
|
+
raise e
|
|
185
|
+
|
|
186
|
+
return all_prompts
|
|
187
|
+
|
|
188
|
+
async def list_prompts(self) -> list[Prompt]:
|
|
189
|
+
"""Gets the filtered list of prompts including local, mounted, and proxy prompts."""
|
|
190
|
+
prompts_dict = await self.get_prompts()
|
|
191
|
+
return list(prompts_dict.values())
|
|
192
|
+
|
|
193
|
+
async def render_prompt(
|
|
194
|
+
self,
|
|
195
|
+
name: str,
|
|
196
|
+
arguments: dict[str, Any] | None = None,
|
|
197
|
+
) -> GetPromptResult:
|
|
198
|
+
"""Renders a prompt, trying local/mounted first, then proxy if not found."""
|
|
199
|
+
try:
|
|
200
|
+
# First try local and mounted prompts
|
|
201
|
+
return await super().render_prompt(name, arguments)
|
|
202
|
+
except NotFoundError:
|
|
203
|
+
# If not found locally, try proxy
|
|
204
|
+
async with self.client:
|
|
205
|
+
result = await self.client.get_prompt(name, arguments)
|
|
206
|
+
return result
|
|
207
|
+
|
|
208
|
+
|
|
33
209
|
class ProxyTool(Tool):
|
|
210
|
+
"""
|
|
211
|
+
A Tool that represents and executes a tool on a remote server.
|
|
212
|
+
"""
|
|
213
|
+
|
|
34
214
|
def __init__(self, client: Client, **kwargs):
|
|
35
215
|
super().__init__(**kwargs)
|
|
36
216
|
self._client = client
|
|
37
217
|
|
|
38
218
|
@classmethod
|
|
39
|
-
|
|
219
|
+
def from_mcp_tool(cls, client: Client, mcp_tool: mcp.types.Tool) -> ProxyTool:
|
|
220
|
+
"""Factory method to create a ProxyTool from a raw MCP tool schema."""
|
|
40
221
|
return cls(
|
|
41
222
|
client=client,
|
|
42
|
-
name=
|
|
43
|
-
description=
|
|
44
|
-
parameters=
|
|
223
|
+
name=mcp_tool.name,
|
|
224
|
+
description=mcp_tool.description,
|
|
225
|
+
parameters=mcp_tool.inputSchema,
|
|
226
|
+
annotations=mcp_tool.annotations,
|
|
45
227
|
)
|
|
46
228
|
|
|
47
229
|
async def run(
|
|
@@ -49,8 +231,8 @@ class ProxyTool(Tool):
|
|
|
49
231
|
arguments: dict[str, Any],
|
|
50
232
|
context: Context | None = None,
|
|
51
233
|
) -> list[MCPContent]:
|
|
52
|
-
|
|
53
|
-
#
|
|
234
|
+
"""Executes the tool by making a call through the client."""
|
|
235
|
+
# This is where the remote execution logic lives.
|
|
54
236
|
async with self._client:
|
|
55
237
|
result = await self._client.call_tool_mcp(
|
|
56
238
|
name=self.name,
|
|
@@ -62,6 +244,10 @@ class ProxyTool(Tool):
|
|
|
62
244
|
|
|
63
245
|
|
|
64
246
|
class ProxyResource(Resource):
|
|
247
|
+
"""
|
|
248
|
+
A Resource that represents and reads a resource from a remote server.
|
|
249
|
+
"""
|
|
250
|
+
|
|
65
251
|
_client: Client
|
|
66
252
|
_value: str | bytes | None = None
|
|
67
253
|
|
|
@@ -71,18 +257,20 @@ class ProxyResource(Resource):
|
|
|
71
257
|
self._value = _value
|
|
72
258
|
|
|
73
259
|
@classmethod
|
|
74
|
-
|
|
75
|
-
cls, client: Client,
|
|
260
|
+
def from_mcp_resource(
|
|
261
|
+
cls, client: Client, mcp_resource: mcp.types.Resource
|
|
76
262
|
) -> ProxyResource:
|
|
263
|
+
"""Factory method to create a ProxyResource from a raw MCP resource schema."""
|
|
77
264
|
return cls(
|
|
78
265
|
client=client,
|
|
79
|
-
uri=
|
|
80
|
-
name=
|
|
81
|
-
description=
|
|
82
|
-
mime_type=
|
|
266
|
+
uri=mcp_resource.uri,
|
|
267
|
+
name=mcp_resource.name,
|
|
268
|
+
description=mcp_resource.description,
|
|
269
|
+
mime_type=mcp_resource.mimeType or "text/plain",
|
|
83
270
|
)
|
|
84
271
|
|
|
85
272
|
async def read(self) -> str | bytes:
|
|
273
|
+
"""Read the resource content from the remote server."""
|
|
86
274
|
if self._value is not None:
|
|
87
275
|
return self._value
|
|
88
276
|
|
|
@@ -97,20 +285,26 @@ class ProxyResource(Resource):
|
|
|
97
285
|
|
|
98
286
|
|
|
99
287
|
class ProxyTemplate(ResourceTemplate):
|
|
288
|
+
"""
|
|
289
|
+
A ResourceTemplate that represents and creates resources from a remote server template.
|
|
290
|
+
"""
|
|
291
|
+
|
|
100
292
|
def __init__(self, client: Client, **kwargs):
|
|
101
293
|
super().__init__(**kwargs)
|
|
102
294
|
self._client = client
|
|
103
295
|
|
|
104
296
|
@classmethod
|
|
105
|
-
|
|
106
|
-
cls, client: Client,
|
|
297
|
+
def from_mcp_template(
|
|
298
|
+
cls, client: Client, mcp_template: mcp.types.ResourceTemplate
|
|
107
299
|
) -> ProxyTemplate:
|
|
300
|
+
"""Factory method to create a ProxyTemplate from a raw MCP template schema."""
|
|
108
301
|
return cls(
|
|
109
302
|
client=client,
|
|
110
|
-
uri_template=
|
|
111
|
-
name=
|
|
112
|
-
description=
|
|
113
|
-
|
|
303
|
+
uri_template=mcp_template.uriTemplate,
|
|
304
|
+
name=mcp_template.name,
|
|
305
|
+
description=mcp_template.description,
|
|
306
|
+
mime_type=mcp_template.mimeType or "text/plain",
|
|
307
|
+
parameters={}, # Remote templates don't have local parameters
|
|
114
308
|
)
|
|
115
309
|
|
|
116
310
|
async def create_resource(
|
|
@@ -119,6 +313,7 @@ class ProxyTemplate(ResourceTemplate):
|
|
|
119
313
|
params: dict[str, Any],
|
|
120
314
|
context: Context | None = None,
|
|
121
315
|
) -> ProxyResource:
|
|
316
|
+
"""Create a resource from the template by calling the remote server."""
|
|
122
317
|
# don't use the provided uri, because it may not be the same as the
|
|
123
318
|
# uri_template on the remote server.
|
|
124
319
|
# quote params to ensure they are valid for the uri_template
|
|
@@ -146,6 +341,10 @@ class ProxyTemplate(ResourceTemplate):
|
|
|
146
341
|
|
|
147
342
|
|
|
148
343
|
class ProxyPrompt(Prompt):
|
|
344
|
+
"""
|
|
345
|
+
A Prompt that represents and renders a prompt from a remote server.
|
|
346
|
+
"""
|
|
347
|
+
|
|
149
348
|
_client: Client
|
|
150
349
|
|
|
151
350
|
def __init__(self, client: Client, **kwargs):
|
|
@@ -153,139 +352,50 @@ class ProxyPrompt(Prompt):
|
|
|
153
352
|
self._client = client
|
|
154
353
|
|
|
155
354
|
@classmethod
|
|
156
|
-
|
|
355
|
+
def from_mcp_prompt(
|
|
356
|
+
cls, client: Client, mcp_prompt: mcp.types.Prompt
|
|
357
|
+
) -> ProxyPrompt:
|
|
358
|
+
"""Factory method to create a ProxyPrompt from a raw MCP prompt schema."""
|
|
359
|
+
arguments = [
|
|
360
|
+
PromptArgument(
|
|
361
|
+
name=arg.name,
|
|
362
|
+
description=arg.description,
|
|
363
|
+
required=arg.required or False,
|
|
364
|
+
)
|
|
365
|
+
for arg in mcp_prompt.arguments or []
|
|
366
|
+
]
|
|
157
367
|
return cls(
|
|
158
368
|
client=client,
|
|
159
|
-
name=
|
|
160
|
-
description=
|
|
161
|
-
arguments=
|
|
369
|
+
name=mcp_prompt.name,
|
|
370
|
+
description=mcp_prompt.description,
|
|
371
|
+
arguments=arguments,
|
|
162
372
|
)
|
|
163
373
|
|
|
164
374
|
async def render(self, arguments: dict[str, Any]) -> list[PromptMessage]:
|
|
375
|
+
"""Render the prompt by making a call through the client."""
|
|
165
376
|
async with self._client:
|
|
166
377
|
result = await self._client.get_prompt(self.name, arguments)
|
|
167
378
|
return result.messages
|
|
168
379
|
|
|
169
380
|
|
|
170
381
|
class FastMCPProxy(FastMCP):
|
|
382
|
+
"""
|
|
383
|
+
A FastMCP server that acts as a proxy to a remote MCP-compliant server.
|
|
384
|
+
It uses specialized managers that fulfill requests via an HTTP client.
|
|
385
|
+
"""
|
|
386
|
+
|
|
171
387
|
def __init__(self, client: Client, **kwargs):
|
|
388
|
+
"""
|
|
389
|
+
Initializes the proxy server.
|
|
390
|
+
|
|
391
|
+
Args:
|
|
392
|
+
client: The FastMCP client connected to the backend server.
|
|
393
|
+
**kwargs: Additional settings for the FastMCP server.
|
|
394
|
+
"""
|
|
172
395
|
super().__init__(**kwargs)
|
|
173
396
|
self.client = client
|
|
174
397
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
try:
|
|
180
|
-
client_tools = await self.client.list_tools()
|
|
181
|
-
except McpError as e:
|
|
182
|
-
if e.error.code == METHOD_NOT_FOUND:
|
|
183
|
-
client_tools = []
|
|
184
|
-
else:
|
|
185
|
-
raise e
|
|
186
|
-
for tool in client_tools:
|
|
187
|
-
# don't overwrite tools defined in the server
|
|
188
|
-
if tool.name not in tools:
|
|
189
|
-
tool_proxy = await ProxyTool.from_client(self.client, tool)
|
|
190
|
-
tools[tool_proxy.name] = tool_proxy
|
|
191
|
-
|
|
192
|
-
return tools
|
|
193
|
-
|
|
194
|
-
async def get_resources(self) -> dict[str, Resource]:
|
|
195
|
-
resources = await super().get_resources()
|
|
196
|
-
|
|
197
|
-
async with self.client:
|
|
198
|
-
try:
|
|
199
|
-
client_resources = await self.client.list_resources()
|
|
200
|
-
except McpError as e:
|
|
201
|
-
if e.error.code == METHOD_NOT_FOUND:
|
|
202
|
-
client_resources = []
|
|
203
|
-
else:
|
|
204
|
-
raise e
|
|
205
|
-
for resource in client_resources:
|
|
206
|
-
# don't overwrite resources defined in the server
|
|
207
|
-
if str(resource.uri) not in resources:
|
|
208
|
-
resource_proxy = await ProxyResource.from_client(
|
|
209
|
-
self.client, resource
|
|
210
|
-
)
|
|
211
|
-
resources[str(resource_proxy.uri)] = resource_proxy
|
|
212
|
-
|
|
213
|
-
return resources
|
|
214
|
-
|
|
215
|
-
async def get_resource_templates(self) -> dict[str, ResourceTemplate]:
|
|
216
|
-
templates = await super().get_resource_templates()
|
|
217
|
-
|
|
218
|
-
async with self.client:
|
|
219
|
-
try:
|
|
220
|
-
client_templates = await self.client.list_resource_templates()
|
|
221
|
-
except McpError as e:
|
|
222
|
-
if e.error.code == METHOD_NOT_FOUND:
|
|
223
|
-
client_templates = []
|
|
224
|
-
else:
|
|
225
|
-
raise e
|
|
226
|
-
for template in client_templates:
|
|
227
|
-
# don't overwrite templates defined in the server
|
|
228
|
-
if template.uriTemplate not in templates:
|
|
229
|
-
template_proxy = await ProxyTemplate.from_client(
|
|
230
|
-
self.client, template
|
|
231
|
-
)
|
|
232
|
-
templates[template_proxy.uri_template] = template_proxy
|
|
233
|
-
|
|
234
|
-
return templates
|
|
235
|
-
|
|
236
|
-
async def get_prompts(self) -> dict[str, Prompt]:
|
|
237
|
-
prompts = await super().get_prompts()
|
|
238
|
-
|
|
239
|
-
async with self.client:
|
|
240
|
-
try:
|
|
241
|
-
client_prompts = await self.client.list_prompts()
|
|
242
|
-
except McpError as e:
|
|
243
|
-
if e.error.code == METHOD_NOT_FOUND:
|
|
244
|
-
client_prompts = []
|
|
245
|
-
else:
|
|
246
|
-
raise e
|
|
247
|
-
for prompt in client_prompts:
|
|
248
|
-
# don't overwrite prompts defined in the server
|
|
249
|
-
if prompt.name not in prompts:
|
|
250
|
-
prompt_proxy = await ProxyPrompt.from_client(self.client, prompt)
|
|
251
|
-
prompts[prompt_proxy.name] = prompt_proxy
|
|
252
|
-
|
|
253
|
-
return prompts
|
|
254
|
-
|
|
255
|
-
async def _call_tool(self, key: str, arguments: dict[str, Any]) -> list[MCPContent]:
|
|
256
|
-
try:
|
|
257
|
-
result = await super()._call_tool(key, arguments)
|
|
258
|
-
return result
|
|
259
|
-
except NotFoundError:
|
|
260
|
-
async with self.client:
|
|
261
|
-
result = await self.client.call_tool(key, arguments)
|
|
262
|
-
return result
|
|
263
|
-
|
|
264
|
-
async def _read_resource(self, uri: AnyUrl | str) -> list[ReadResourceContents]:
|
|
265
|
-
try:
|
|
266
|
-
result = await super()._read_resource(uri)
|
|
267
|
-
return result
|
|
268
|
-
except NotFoundError:
|
|
269
|
-
async with self.client:
|
|
270
|
-
resource = await self.client.read_resource(uri)
|
|
271
|
-
if isinstance(resource[0], TextResourceContents):
|
|
272
|
-
content = resource[0].text
|
|
273
|
-
elif isinstance(resource[0], BlobResourceContents):
|
|
274
|
-
content = resource[0].blob
|
|
275
|
-
else:
|
|
276
|
-
raise ValueError(f"Unsupported content type: {type(resource[0])}")
|
|
277
|
-
|
|
278
|
-
return [
|
|
279
|
-
ReadResourceContents(content=content, mime_type=resource[0].mimeType)
|
|
280
|
-
]
|
|
281
|
-
|
|
282
|
-
async def _get_prompt(
|
|
283
|
-
self, name: str, arguments: dict[str, Any] | None = None
|
|
284
|
-
) -> GetPromptResult:
|
|
285
|
-
try:
|
|
286
|
-
result = await super()._get_prompt(name, arguments)
|
|
287
|
-
return result
|
|
288
|
-
except NotFoundError:
|
|
289
|
-
async with self.client:
|
|
290
|
-
result = await self.client.get_prompt(name, arguments)
|
|
291
|
-
return result
|
|
398
|
+
# Replace the default managers with our specialized proxy managers.
|
|
399
|
+
self._tool_manager = ProxyToolManager(client=self.client)
|
|
400
|
+
self._resource_manager = ProxyResourceManager(client=self.client)
|
|
401
|
+
self._prompt_manager = ProxyPromptManager(client=self.client)
|