fastmcp 2.7.0__py3-none-any.whl → 2.8.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 +4 -1
- fastmcp/cli/cli.py +4 -3
- fastmcp/client/auth/oauth.py +1 -1
- fastmcp/client/client.py +3 -1
- fastmcp/client/transports.py +43 -37
- fastmcp/exceptions.py +4 -0
- fastmcp/prompts/prompt.py +10 -20
- fastmcp/prompts/prompt_manager.py +7 -4
- fastmcp/resources/resource.py +21 -26
- fastmcp/resources/resource_manager.py +3 -2
- fastmcp/resources/template.py +8 -16
- fastmcp/server/auth/providers/bearer_env.py +8 -11
- fastmcp/server/dependencies.py +1 -0
- fastmcp/server/http.py +2 -1
- fastmcp/server/openapi.py +65 -38
- fastmcp/server/proxy.py +27 -14
- fastmcp/server/server.py +323 -133
- fastmcp/settings.py +100 -37
- fastmcp/tools/__init__.py +2 -1
- fastmcp/tools/tool.py +116 -77
- fastmcp/tools/tool_manager.py +3 -2
- fastmcp/tools/tool_transform.py +665 -0
- fastmcp/utilities/components.py +55 -0
- fastmcp/utilities/exceptions.py +1 -1
- fastmcp/utilities/mcp_config.py +12 -4
- fastmcp/utilities/tests.py +3 -3
- fastmcp/utilities/types.py +0 -9
- {fastmcp-2.7.0.dist-info → fastmcp-2.8.0.dist-info}/METADATA +3 -1
- {fastmcp-2.7.0.dist-info → fastmcp-2.8.0.dist-info}/RECORD +32 -30
- {fastmcp-2.7.0.dist-info → fastmcp-2.8.0.dist-info}/WHEEL +0 -0
- {fastmcp-2.7.0.dist-info → fastmcp-2.8.0.dist-info}/entry_points.txt +0 -0
- {fastmcp-2.7.0.dist-info → fastmcp-2.8.0.dist-info}/licenses/LICENSE +0 -0
fastmcp/server/server.py
CHANGED
|
@@ -41,9 +41,9 @@ from starlette.requests import Request
|
|
|
41
41
|
from starlette.responses import Response
|
|
42
42
|
from starlette.routing import BaseRoute, Route
|
|
43
43
|
|
|
44
|
+
import fastmcp
|
|
44
45
|
import fastmcp.server
|
|
45
|
-
|
|
46
|
-
from fastmcp.exceptions import NotFoundError
|
|
46
|
+
from fastmcp.exceptions import DisabledError, NotFoundError
|
|
47
47
|
from fastmcp.prompts import Prompt, PromptManager
|
|
48
48
|
from fastmcp.prompts.prompt import FunctionPrompt
|
|
49
49
|
from fastmcp.resources import Resource, ResourceManager
|
|
@@ -55,9 +55,11 @@ from fastmcp.server.http import (
|
|
|
55
55
|
create_sse_app,
|
|
56
56
|
create_streamable_http_app,
|
|
57
57
|
)
|
|
58
|
+
from fastmcp.settings import Settings
|
|
58
59
|
from fastmcp.tools import ToolManager
|
|
59
60
|
from fastmcp.tools.tool import FunctionTool, Tool
|
|
60
61
|
from fastmcp.utilities.cache import TimedCache
|
|
62
|
+
from fastmcp.utilities.components import FastMCPComponent
|
|
61
63
|
from fastmcp.utilities.logging import get_logger
|
|
62
64
|
from fastmcp.utilities.mcp_config import MCPConfig
|
|
63
65
|
|
|
@@ -119,8 +121,6 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
119
121
|
]
|
|
120
122
|
| None
|
|
121
123
|
) = None,
|
|
122
|
-
tags: set[str] | None = None,
|
|
123
|
-
dependencies: list[str] | None = None,
|
|
124
124
|
tool_serializer: Callable[[Any], str] | None = None,
|
|
125
125
|
cache_expiration_seconds: float | None = None,
|
|
126
126
|
on_duplicate_tools: DuplicateBehavior | None = None,
|
|
@@ -129,24 +129,28 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
129
129
|
resource_prefix_format: Literal["protocol", "path"] | None = None,
|
|
130
130
|
mask_error_details: bool | None = None,
|
|
131
131
|
tools: list[Tool | Callable[..., Any]] | None = None,
|
|
132
|
-
|
|
132
|
+
dependencies: list[str] | None = None,
|
|
133
|
+
include_tags: set[str] | None = None,
|
|
134
|
+
exclude_tags: set[str] | None = None,
|
|
135
|
+
# ---
|
|
136
|
+
# ---
|
|
137
|
+
# --- The following arguments are DEPRECATED ---
|
|
138
|
+
# ---
|
|
139
|
+
# ---
|
|
140
|
+
log_level: str | None = None,
|
|
141
|
+
debug: bool | None = None,
|
|
142
|
+
host: str | None = None,
|
|
143
|
+
port: int | None = None,
|
|
144
|
+
sse_path: str | None = None,
|
|
145
|
+
message_path: str | None = None,
|
|
146
|
+
streamable_http_path: str | None = None,
|
|
147
|
+
json_response: bool | None = None,
|
|
148
|
+
stateless_http: bool | None = None,
|
|
133
149
|
):
|
|
134
|
-
self.
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
if mask_error_details is not None:
|
|
138
|
-
self.settings.mask_error_details = mask_error_details
|
|
139
|
-
|
|
140
|
-
self.resource_prefix_format: Literal["protocol", "path"]
|
|
141
|
-
if resource_prefix_format is None:
|
|
142
|
-
self.resource_prefix_format = (
|
|
143
|
-
fastmcp.settings.settings.resource_prefix_format
|
|
144
|
-
)
|
|
145
|
-
else:
|
|
146
|
-
self.resource_prefix_format = resource_prefix_format
|
|
150
|
+
self.resource_prefix_format: Literal["protocol", "path"] = (
|
|
151
|
+
resource_prefix_format or fastmcp.settings.resource_prefix_format
|
|
152
|
+
)
|
|
147
153
|
|
|
148
|
-
self.tags: set[str] = tags or set()
|
|
149
|
-
self.dependencies = dependencies
|
|
150
154
|
self._cache = TimedCache(
|
|
151
155
|
expiration=datetime.timedelta(seconds=cache_expiration_seconds or 0)
|
|
152
156
|
)
|
|
@@ -154,15 +158,15 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
154
158
|
self._additional_http_routes: list[BaseRoute] = []
|
|
155
159
|
self._tool_manager = ToolManager(
|
|
156
160
|
duplicate_behavior=on_duplicate_tools,
|
|
157
|
-
mask_error_details=
|
|
161
|
+
mask_error_details=mask_error_details,
|
|
158
162
|
)
|
|
159
163
|
self._resource_manager = ResourceManager(
|
|
160
164
|
duplicate_behavior=on_duplicate_resources,
|
|
161
|
-
mask_error_details=
|
|
165
|
+
mask_error_details=mask_error_details,
|
|
162
166
|
)
|
|
163
167
|
self._prompt_manager = PromptManager(
|
|
164
168
|
duplicate_behavior=on_duplicate_prompts,
|
|
165
|
-
mask_error_details=
|
|
169
|
+
mask_error_details=mask_error_details,
|
|
166
170
|
)
|
|
167
171
|
self._tool_serializer = tool_serializer
|
|
168
172
|
|
|
@@ -177,7 +181,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
177
181
|
lifespan=_lifespan_wrapper(self, lifespan),
|
|
178
182
|
)
|
|
179
183
|
|
|
180
|
-
if auth is None and
|
|
184
|
+
if auth is None and fastmcp.settings.default_auth_provider == "bearer_env":
|
|
181
185
|
auth = EnvBearerAuthProvider()
|
|
182
186
|
self.auth = auth
|
|
183
187
|
|
|
@@ -187,12 +191,67 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
187
191
|
tool = Tool.from_function(tool, serializer=self._tool_serializer)
|
|
188
192
|
self.add_tool(tool)
|
|
189
193
|
|
|
194
|
+
self.include_tags = include_tags
|
|
195
|
+
self.exclude_tags = exclude_tags
|
|
196
|
+
|
|
190
197
|
# Set up MCP protocol handlers
|
|
191
198
|
self._setup_handlers()
|
|
199
|
+
self.dependencies = dependencies or fastmcp.settings.server_dependencies
|
|
200
|
+
|
|
201
|
+
# handle deprecated settings
|
|
202
|
+
self._handle_deprecated_settings(
|
|
203
|
+
log_level=log_level,
|
|
204
|
+
debug=debug,
|
|
205
|
+
host=host,
|
|
206
|
+
port=port,
|
|
207
|
+
sse_path=sse_path,
|
|
208
|
+
message_path=message_path,
|
|
209
|
+
streamable_http_path=streamable_http_path,
|
|
210
|
+
json_response=json_response,
|
|
211
|
+
stateless_http=stateless_http,
|
|
212
|
+
)
|
|
192
213
|
|
|
193
214
|
def __repr__(self) -> str:
|
|
194
215
|
return f"{type(self).__name__}({self.name!r})"
|
|
195
216
|
|
|
217
|
+
def _handle_deprecated_settings(
|
|
218
|
+
self,
|
|
219
|
+
log_level: str | None,
|
|
220
|
+
debug: bool | None,
|
|
221
|
+
host: str | None,
|
|
222
|
+
port: int | None,
|
|
223
|
+
sse_path: str | None,
|
|
224
|
+
message_path: str | None,
|
|
225
|
+
streamable_http_path: str | None,
|
|
226
|
+
json_response: bool | None,
|
|
227
|
+
stateless_http: bool | None,
|
|
228
|
+
) -> None:
|
|
229
|
+
"""Handle deprecated settings. Deprecated in 2.8.0."""
|
|
230
|
+
deprecated_settings: dict[str, Any] = {}
|
|
231
|
+
|
|
232
|
+
for name, arg in [
|
|
233
|
+
("log_level", log_level),
|
|
234
|
+
("debug", debug),
|
|
235
|
+
("host", host),
|
|
236
|
+
("port", port),
|
|
237
|
+
("sse_path", sse_path),
|
|
238
|
+
("message_path", message_path),
|
|
239
|
+
("streamable_http_path", streamable_http_path),
|
|
240
|
+
("json_response", json_response),
|
|
241
|
+
("stateless_http", stateless_http),
|
|
242
|
+
]:
|
|
243
|
+
if arg is not None:
|
|
244
|
+
# Deprecated in 2.8.0
|
|
245
|
+
warnings.warn(
|
|
246
|
+
f"Providing `{name}` when creating a server is deprecated. Provide it when calling `run` or as a global setting instead.",
|
|
247
|
+
DeprecationWarning,
|
|
248
|
+
stacklevel=2,
|
|
249
|
+
)
|
|
250
|
+
deprecated_settings[name] = arg
|
|
251
|
+
|
|
252
|
+
combined_settings = fastmcp.settings.model_dump() | deprecated_settings
|
|
253
|
+
self._deprecated_settings = Settings(**combined_settings)
|
|
254
|
+
|
|
196
255
|
@property
|
|
197
256
|
def name(self) -> str:
|
|
198
257
|
return self._mcp_server.name
|
|
@@ -239,12 +298,12 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
239
298
|
def _setup_handlers(self) -> None:
|
|
240
299
|
"""Set up core MCP protocol handlers."""
|
|
241
300
|
self._mcp_server.list_tools()(self._mcp_list_tools)
|
|
242
|
-
self._mcp_server.call_tool()(self._mcp_call_tool)
|
|
243
301
|
self._mcp_server.list_resources()(self._mcp_list_resources)
|
|
244
|
-
self._mcp_server.
|
|
302
|
+
self._mcp_server.list_resource_templates()(self._mcp_list_resource_templates)
|
|
245
303
|
self._mcp_server.list_prompts()(self._mcp_list_prompts)
|
|
304
|
+
self._mcp_server.call_tool()(self._mcp_call_tool)
|
|
305
|
+
self._mcp_server.read_resource()(self._mcp_read_resource)
|
|
246
306
|
self._mcp_server.get_prompt()(self._mcp_get_prompt)
|
|
247
|
-
self._mcp_server.list_resource_templates()(self._mcp_list_resource_templates)
|
|
248
307
|
|
|
249
308
|
async def get_tools(self) -> dict[str, Tool]:
|
|
250
309
|
"""Get all registered tools, indexed by registered key."""
|
|
@@ -263,6 +322,12 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
263
322
|
self._cache.set("tools", tools)
|
|
264
323
|
return tools
|
|
265
324
|
|
|
325
|
+
async def get_tool(self, key: str) -> Tool:
|
|
326
|
+
tools = await self.get_tools()
|
|
327
|
+
if key not in tools:
|
|
328
|
+
raise NotFoundError(f"Unknown tool: {key}")
|
|
329
|
+
return tools[key]
|
|
330
|
+
|
|
266
331
|
async def get_resources(self) -> dict[str, Resource]:
|
|
267
332
|
"""Get all registered resources, indexed by registered key."""
|
|
268
333
|
if (resources := self._cache.get("resources")) is self._cache.NOT_FOUND:
|
|
@@ -280,6 +345,12 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
280
345
|
self._cache.set("resources", resources)
|
|
281
346
|
return resources
|
|
282
347
|
|
|
348
|
+
async def get_resource(self, key: str) -> Resource:
|
|
349
|
+
resources = await self.get_resources()
|
|
350
|
+
if key not in resources:
|
|
351
|
+
raise NotFoundError(f"Unknown resource: {key}")
|
|
352
|
+
return resources[key]
|
|
353
|
+
|
|
283
354
|
async def get_resource_templates(self) -> dict[str, ResourceTemplate]:
|
|
284
355
|
"""Get all registered resource templates, indexed by registered key."""
|
|
285
356
|
if (
|
|
@@ -300,6 +371,12 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
300
371
|
self._cache.set("resource_templates", templates)
|
|
301
372
|
return templates
|
|
302
373
|
|
|
374
|
+
async def get_resource_template(self, key: str) -> ResourceTemplate:
|
|
375
|
+
templates = await self.get_resource_templates()
|
|
376
|
+
if key not in templates:
|
|
377
|
+
raise NotFoundError(f"Unknown resource template: {key}")
|
|
378
|
+
return templates[key]
|
|
379
|
+
|
|
303
380
|
async def get_prompts(self) -> dict[str, Prompt]:
|
|
304
381
|
"""
|
|
305
382
|
List all available prompts.
|
|
@@ -319,6 +396,12 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
319
396
|
self._cache.set("prompts", prompts)
|
|
320
397
|
return prompts
|
|
321
398
|
|
|
399
|
+
async def get_prompt(self, key: str) -> Prompt:
|
|
400
|
+
prompts = await self.get_prompts()
|
|
401
|
+
if key not in prompts:
|
|
402
|
+
raise NotFoundError(f"Unknown prompt: {key}")
|
|
403
|
+
return prompts[key]
|
|
404
|
+
|
|
322
405
|
def custom_route(
|
|
323
406
|
self,
|
|
324
407
|
path: str,
|
|
@@ -370,7 +453,13 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
370
453
|
|
|
371
454
|
"""
|
|
372
455
|
tools = await self.get_tools()
|
|
373
|
-
|
|
456
|
+
|
|
457
|
+
mcp_tools: list[MCPTool] = []
|
|
458
|
+
for key, tool in tools.items():
|
|
459
|
+
if self._should_enable_component(tool):
|
|
460
|
+
mcp_tools.append(tool.to_mcp_tool(name=key))
|
|
461
|
+
|
|
462
|
+
return mcp_tools
|
|
374
463
|
|
|
375
464
|
async def _mcp_list_resources(self) -> list[MCPResource]:
|
|
376
465
|
"""
|
|
@@ -379,9 +468,11 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
379
468
|
|
|
380
469
|
"""
|
|
381
470
|
resources = await self.get_resources()
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
471
|
+
mcp_resources: list[MCPResource] = []
|
|
472
|
+
for key, resource in resources.items():
|
|
473
|
+
if self._should_enable_component(resource):
|
|
474
|
+
mcp_resources.append(resource.to_mcp_resource(uri=key))
|
|
475
|
+
return mcp_resources
|
|
385
476
|
|
|
386
477
|
async def _mcp_list_resource_templates(self) -> list[MCPResourceTemplate]:
|
|
387
478
|
"""
|
|
@@ -390,10 +481,11 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
390
481
|
|
|
391
482
|
"""
|
|
392
483
|
templates = await self.get_resource_templates()
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
484
|
+
mcp_templates: list[MCPResourceTemplate] = []
|
|
485
|
+
for key, template in templates.items():
|
|
486
|
+
if self._should_enable_component(template):
|
|
487
|
+
mcp_templates.append(template.to_mcp_template(uriTemplate=key))
|
|
488
|
+
return mcp_templates
|
|
397
489
|
|
|
398
490
|
async def _mcp_list_prompts(self) -> list[MCPPrompt]:
|
|
399
491
|
"""
|
|
@@ -402,12 +494,19 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
402
494
|
|
|
403
495
|
"""
|
|
404
496
|
prompts = await self.get_prompts()
|
|
405
|
-
|
|
497
|
+
mcp_prompts: list[MCPPrompt] = []
|
|
498
|
+
for key, prompt in prompts.items():
|
|
499
|
+
if self._should_enable_component(prompt):
|
|
500
|
+
mcp_prompts.append(prompt.to_mcp_prompt(name=key))
|
|
501
|
+
return mcp_prompts
|
|
406
502
|
|
|
407
503
|
async def _mcp_call_tool(
|
|
408
504
|
self, key: str, arguments: dict[str, Any]
|
|
409
505
|
) -> list[TextContent | ImageContent | EmbeddedResource]:
|
|
410
|
-
"""
|
|
506
|
+
"""
|
|
507
|
+
Handle MCP 'callTool' requests.
|
|
508
|
+
|
|
509
|
+
Delegates to _call_tool, which should be overridden by FastMCP subclasses.
|
|
411
510
|
|
|
412
511
|
Args:
|
|
413
512
|
key: The name of the tool to call
|
|
@@ -420,43 +519,109 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
420
519
|
|
|
421
520
|
# Create and use context for the entire call
|
|
422
521
|
with fastmcp.server.context.Context(fastmcp=self):
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
522
|
+
try:
|
|
523
|
+
return await self._call_tool(key, arguments)
|
|
524
|
+
except DisabledError:
|
|
525
|
+
# convert to NotFoundError to avoid leaking tool presence
|
|
526
|
+
raise NotFoundError(f"Unknown tool: {key}")
|
|
527
|
+
except NotFoundError:
|
|
528
|
+
# standardize NotFound message
|
|
529
|
+
raise NotFoundError(f"Unknown tool: {key}")
|
|
530
|
+
|
|
531
|
+
async def _call_tool(
|
|
532
|
+
self, key: str, arguments: dict[str, Any]
|
|
533
|
+
) -> list[TextContent | ImageContent | EmbeddedResource]:
|
|
534
|
+
"""
|
|
535
|
+
Call a tool with raw MCP arguments. FastMCP subclasses should override
|
|
536
|
+
this method, not _mcp_call_tool.
|
|
426
537
|
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
tool_key = server.strip_tool_prefix(key)
|
|
431
|
-
return await server.server._mcp_call_tool(tool_key, arguments)
|
|
538
|
+
Args:
|
|
539
|
+
key: The name of the tool to call arguments: Arguments to pass to
|
|
540
|
+
the tool
|
|
432
541
|
|
|
433
|
-
|
|
542
|
+
Returns:
|
|
543
|
+
List of MCP Content objects containing the tool results
|
|
544
|
+
"""
|
|
545
|
+
|
|
546
|
+
# Get tool, checking first from our tools, then from the mounted servers
|
|
547
|
+
if self._tool_manager.has_tool(key):
|
|
548
|
+
tool = self._tool_manager.get_tool(key)
|
|
549
|
+
if not self._should_enable_component(tool):
|
|
550
|
+
raise DisabledError(f"Tool {key!r} is disabled")
|
|
551
|
+
return await self._tool_manager.call_tool(key, arguments)
|
|
552
|
+
|
|
553
|
+
# Check mounted servers to see if they have the tool
|
|
554
|
+
for server in self._mounted_servers.values():
|
|
555
|
+
if server.match_tool(key):
|
|
556
|
+
tool_key = server.strip_tool_prefix(key)
|
|
557
|
+
return await server.server._call_tool(tool_key, arguments)
|
|
558
|
+
|
|
559
|
+
raise NotFoundError(f"Unknown tool: {key!r}")
|
|
434
560
|
|
|
435
561
|
async def _mcp_read_resource(self, uri: AnyUrl | str) -> list[ReadResourceContents]:
|
|
562
|
+
"""
|
|
563
|
+
Handle MCP 'readResource' requests.
|
|
564
|
+
|
|
565
|
+
Delegates to _read_resource, which should be overridden by FastMCP subclasses.
|
|
566
|
+
"""
|
|
567
|
+
logger.debug("Read resource: %s", uri)
|
|
568
|
+
|
|
569
|
+
with fastmcp.server.context.Context(fastmcp=self):
|
|
570
|
+
try:
|
|
571
|
+
return await self._read_resource(uri)
|
|
572
|
+
except DisabledError:
|
|
573
|
+
# convert to NotFoundError to avoid leaking resource presence
|
|
574
|
+
raise NotFoundError(f"Unknown resource: {str(uri)!r}")
|
|
575
|
+
except NotFoundError:
|
|
576
|
+
# standardize NotFound message
|
|
577
|
+
raise NotFoundError(f"Unknown resource: {str(uri)!r}")
|
|
578
|
+
|
|
579
|
+
async def _read_resource(self, uri: AnyUrl | str) -> list[ReadResourceContents]:
|
|
436
580
|
"""
|
|
437
581
|
Read a resource by URI, in the format expected by the low-level MCP
|
|
438
582
|
server.
|
|
439
583
|
"""
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
584
|
+
if self._resource_manager.has_resource(uri):
|
|
585
|
+
resource = await self._resource_manager.get_resource(uri)
|
|
586
|
+
if not self._should_enable_component(resource):
|
|
587
|
+
raise DisabledError(f"Resource {str(uri)!r} is disabled")
|
|
588
|
+
content = await self._resource_manager.read_resource(uri)
|
|
589
|
+
return [
|
|
590
|
+
ReadResourceContents(
|
|
591
|
+
content=content,
|
|
592
|
+
mime_type=resource.mime_type,
|
|
593
|
+
)
|
|
594
|
+
]
|
|
595
|
+
else:
|
|
596
|
+
for server in self._mounted_servers.values():
|
|
597
|
+
if server.match_resource(str(uri)):
|
|
598
|
+
new_uri = server.strip_resource_prefix(str(uri))
|
|
599
|
+
return await server.server._mcp_read_resource(new_uri)
|
|
450
600
|
else:
|
|
451
|
-
|
|
452
|
-
if server.match_resource(str(uri)):
|
|
453
|
-
new_uri = server.strip_resource_prefix(str(uri))
|
|
454
|
-
return await server.server._mcp_read_resource(new_uri)
|
|
455
|
-
else:
|
|
456
|
-
raise NotFoundError(f"Unknown resource: {uri}")
|
|
601
|
+
raise NotFoundError(f"Unknown resource: {uri}")
|
|
457
602
|
|
|
458
603
|
async def _mcp_get_prompt(
|
|
459
604
|
self, name: str, arguments: dict[str, Any] | None = None
|
|
605
|
+
) -> GetPromptResult:
|
|
606
|
+
"""
|
|
607
|
+
Handle MCP 'getPrompt' requests.
|
|
608
|
+
|
|
609
|
+
Delegates to _get_prompt, which should be overridden by FastMCP subclasses.
|
|
610
|
+
"""
|
|
611
|
+
logger.debug("Get prompt: %s with %s", name, arguments)
|
|
612
|
+
|
|
613
|
+
with fastmcp.server.context.Context(fastmcp=self):
|
|
614
|
+
try:
|
|
615
|
+
return await self._get_prompt(name, arguments)
|
|
616
|
+
except DisabledError:
|
|
617
|
+
# convert to NotFoundError to avoid leaking prompt presence
|
|
618
|
+
raise NotFoundError(f"Unknown prompt: {name}")
|
|
619
|
+
except NotFoundError:
|
|
620
|
+
# standardize NotFound message
|
|
621
|
+
raise NotFoundError(f"Unknown prompt: {name}")
|
|
622
|
+
|
|
623
|
+
async def _get_prompt(
|
|
624
|
+
self, name: str, arguments: dict[str, Any] | None = None
|
|
460
625
|
) -> GetPromptResult:
|
|
461
626
|
"""Handle MCP 'getPrompt' requests.
|
|
462
627
|
|
|
@@ -469,19 +634,20 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
469
634
|
"""
|
|
470
635
|
logger.debug("Get prompt: %s with %s", name, arguments)
|
|
471
636
|
|
|
472
|
-
#
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
if self.
|
|
476
|
-
|
|
637
|
+
# Get prompt, checking first from our prompts, then from the mounted servers
|
|
638
|
+
if self._prompt_manager.has_prompt(name):
|
|
639
|
+
prompt = self._prompt_manager.get_prompt(name)
|
|
640
|
+
if not self._should_enable_component(prompt):
|
|
641
|
+
raise DisabledError(f"Prompt {name!r} is disabled")
|
|
642
|
+
return await self._prompt_manager.render_prompt(name, arguments)
|
|
477
643
|
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
644
|
+
# Check mounted servers to see if they have the prompt
|
|
645
|
+
for server in self._mounted_servers.values():
|
|
646
|
+
if server.match_prompt(name):
|
|
647
|
+
prompt_name = server.strip_prompt_prefix(name)
|
|
648
|
+
return await server.server._mcp_get_prompt(prompt_name, arguments)
|
|
483
649
|
|
|
484
|
-
|
|
650
|
+
raise NotFoundError(f"Unknown prompt: {name}")
|
|
485
651
|
|
|
486
652
|
def add_tool(self, tool: Tool) -> None:
|
|
487
653
|
"""Add a tool to the server.
|
|
@@ -490,11 +656,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
490
656
|
with the Context type annotation. See the @tool decorator for examples.
|
|
491
657
|
|
|
492
658
|
Args:
|
|
493
|
-
|
|
494
|
-
name: Optional name for the tool (defaults to function name)
|
|
495
|
-
description: Optional description of what the tool does
|
|
496
|
-
tags: Optional set of tags for categorizing the tool
|
|
497
|
-
annotations: Optional annotations about the tool's behavior
|
|
659
|
+
tool: The Tool instance to register
|
|
498
660
|
"""
|
|
499
661
|
self._tool_manager.add_tool(tool)
|
|
500
662
|
self._cache.clear()
|
|
@@ -521,6 +683,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
521
683
|
tags: set[str] | None = None,
|
|
522
684
|
annotations: ToolAnnotations | dict[str, Any] | None = None,
|
|
523
685
|
exclude_args: list[str] | None = None,
|
|
686
|
+
enabled: bool | None = None,
|
|
524
687
|
) -> FunctionTool: ...
|
|
525
688
|
|
|
526
689
|
@overload
|
|
@@ -533,6 +696,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
533
696
|
tags: set[str] | None = None,
|
|
534
697
|
annotations: ToolAnnotations | dict[str, Any] | None = None,
|
|
535
698
|
exclude_args: list[str] | None = None,
|
|
699
|
+
enabled: bool | None = None,
|
|
536
700
|
) -> Callable[[AnyFunction], FunctionTool]: ...
|
|
537
701
|
|
|
538
702
|
def tool(
|
|
@@ -544,6 +708,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
544
708
|
tags: set[str] | None = None,
|
|
545
709
|
annotations: ToolAnnotations | dict[str, Any] | None = None,
|
|
546
710
|
exclude_args: list[str] | None = None,
|
|
711
|
+
enabled: bool | None = None,
|
|
547
712
|
) -> Callable[[AnyFunction], FunctionTool] | FunctionTool:
|
|
548
713
|
"""Decorator to register a tool.
|
|
549
714
|
|
|
@@ -560,11 +725,12 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
560
725
|
|
|
561
726
|
Args:
|
|
562
727
|
name_or_fn: Either a function (when used as @tool), a string name, or None
|
|
728
|
+
name: Optional name for the tool (keyword-only, alternative to name_or_fn)
|
|
563
729
|
description: Optional description of what the tool does
|
|
564
730
|
tags: Optional set of tags for categorizing the tool
|
|
565
|
-
annotations: Optional annotations about the tool's behavior
|
|
731
|
+
annotations: Optional annotations about the tool's behavior (e.g. {"is_async": True})
|
|
566
732
|
exclude_args: Optional list of argument names to exclude from the tool schema
|
|
567
|
-
|
|
733
|
+
enabled: Optional boolean to enable or disable the tool
|
|
568
734
|
|
|
569
735
|
Example:
|
|
570
736
|
@server.tool
|
|
@@ -617,6 +783,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
617
783
|
annotations=annotations,
|
|
618
784
|
exclude_args=exclude_args,
|
|
619
785
|
serializer=self._tool_serializer,
|
|
786
|
+
enabled=enabled,
|
|
620
787
|
)
|
|
621
788
|
self.add_tool(tool)
|
|
622
789
|
return tool
|
|
@@ -645,6 +812,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
645
812
|
tags=tags,
|
|
646
813
|
annotations=annotations,
|
|
647
814
|
exclude_args=exclude_args,
|
|
815
|
+
enabled=enabled,
|
|
648
816
|
)
|
|
649
817
|
|
|
650
818
|
def add_resource(self, resource: Resource, key: str | None = None) -> None:
|
|
@@ -711,6 +879,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
711
879
|
description: str | None = None,
|
|
712
880
|
mime_type: str | None = None,
|
|
713
881
|
tags: set[str] | None = None,
|
|
882
|
+
enabled: bool | None = None,
|
|
714
883
|
) -> Callable[[AnyFunction], Resource | ResourceTemplate]:
|
|
715
884
|
"""Decorator to register a function as a resource.
|
|
716
885
|
|
|
@@ -733,6 +902,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
733
902
|
description: Optional description of the resource
|
|
734
903
|
mime_type: Optional MIME type for the resource
|
|
735
904
|
tags: Optional set of tags for categorizing the resource
|
|
905
|
+
enabled: Optional boolean to enable or disable the resource
|
|
736
906
|
|
|
737
907
|
Example:
|
|
738
908
|
@server.resource("resource://my-resource")
|
|
@@ -797,6 +967,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
797
967
|
description=description,
|
|
798
968
|
mime_type=mime_type,
|
|
799
969
|
tags=tags,
|
|
970
|
+
enabled=enabled,
|
|
800
971
|
)
|
|
801
972
|
self.add_template(template)
|
|
802
973
|
return template
|
|
@@ -808,6 +979,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
808
979
|
description=description,
|
|
809
980
|
mime_type=mime_type,
|
|
810
981
|
tags=tags,
|
|
982
|
+
enabled=enabled,
|
|
811
983
|
)
|
|
812
984
|
self.add_resource(resource)
|
|
813
985
|
return resource
|
|
@@ -836,6 +1008,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
836
1008
|
name: str | None = None,
|
|
837
1009
|
description: str | None = None,
|
|
838
1010
|
tags: set[str] | None = None,
|
|
1011
|
+
enabled: bool | None = None,
|
|
839
1012
|
) -> FunctionPrompt: ...
|
|
840
1013
|
|
|
841
1014
|
@overload
|
|
@@ -846,6 +1019,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
846
1019
|
name: str | None = None,
|
|
847
1020
|
description: str | None = None,
|
|
848
1021
|
tags: set[str] | None = None,
|
|
1022
|
+
enabled: bool | None = None,
|
|
849
1023
|
) -> Callable[[AnyFunction], FunctionPrompt]: ...
|
|
850
1024
|
|
|
851
1025
|
def prompt(
|
|
@@ -855,6 +1029,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
855
1029
|
name: str | None = None,
|
|
856
1030
|
description: str | None = None,
|
|
857
1031
|
tags: set[str] | None = None,
|
|
1032
|
+
enabled: bool | None = None,
|
|
858
1033
|
) -> Callable[[AnyFunction], FunctionPrompt] | FunctionPrompt:
|
|
859
1034
|
"""Decorator to register a prompt.
|
|
860
1035
|
|
|
@@ -864,16 +1039,17 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
864
1039
|
|
|
865
1040
|
This decorator supports multiple calling patterns:
|
|
866
1041
|
- @server.prompt (without parentheses)
|
|
867
|
-
- @server.prompt (with empty parentheses)
|
|
1042
|
+
- @server.prompt() (with empty parentheses)
|
|
868
1043
|
- @server.prompt("custom_name") (with name as first argument)
|
|
869
1044
|
- @server.prompt(name="custom_name") (with name as keyword argument)
|
|
870
1045
|
- server.prompt(function, name="custom_name") (direct function call)
|
|
871
1046
|
|
|
872
1047
|
Args:
|
|
873
1048
|
name_or_fn: Either a function (when used as @prompt), a string name, or None
|
|
1049
|
+
name: Optional name for the prompt (keyword-only, alternative to name_or_fn)
|
|
874
1050
|
description: Optional description of what the prompt does
|
|
875
1051
|
tags: Optional set of tags for categorizing the prompt
|
|
876
|
-
|
|
1052
|
+
enabled: Optional boolean to enable or disable the prompt
|
|
877
1053
|
|
|
878
1054
|
Example:
|
|
879
1055
|
@server.prompt
|
|
@@ -886,7 +1062,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
886
1062
|
}
|
|
887
1063
|
]
|
|
888
1064
|
|
|
889
|
-
@server.prompt
|
|
1065
|
+
@server.prompt()
|
|
890
1066
|
def analyze_with_context(table_name: str, ctx: Context) -> list[Message]:
|
|
891
1067
|
ctx.info(f"Analyzing table {table_name}")
|
|
892
1068
|
schema = read_table_schema(table_name)
|
|
@@ -946,6 +1122,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
946
1122
|
name=prompt_name,
|
|
947
1123
|
description=description,
|
|
948
1124
|
tags=tags,
|
|
1125
|
+
enabled=enabled,
|
|
949
1126
|
)
|
|
950
1127
|
self.add_prompt(prompt)
|
|
951
1128
|
|
|
@@ -973,6 +1150,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
973
1150
|
name=prompt_name,
|
|
974
1151
|
description=description,
|
|
975
1152
|
tags=tags,
|
|
1153
|
+
enabled=enabled,
|
|
976
1154
|
)
|
|
977
1155
|
|
|
978
1156
|
async def run_stdio_async(self) -> None:
|
|
@@ -1007,9 +1185,11 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
1007
1185
|
path: Path for the endpoint (defaults to settings.streamable_http_path or settings.sse_path)
|
|
1008
1186
|
uvicorn_config: Additional configuration for the Uvicorn server
|
|
1009
1187
|
"""
|
|
1010
|
-
host = host or self.
|
|
1011
|
-
port = port or self.
|
|
1012
|
-
default_log_level_to_use = (
|
|
1188
|
+
host = host or self._deprecated_settings.host
|
|
1189
|
+
port = port or self._deprecated_settings.port
|
|
1190
|
+
default_log_level_to_use = (
|
|
1191
|
+
log_level or self._deprecated_settings.log_level
|
|
1192
|
+
).lower()
|
|
1013
1193
|
|
|
1014
1194
|
app = self.http_app(path=path, transport=transport, middleware=middleware)
|
|
1015
1195
|
|
|
@@ -1083,10 +1263,10 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
1083
1263
|
)
|
|
1084
1264
|
return create_sse_app(
|
|
1085
1265
|
server=self,
|
|
1086
|
-
message_path=message_path or self.
|
|
1087
|
-
sse_path=path or self.
|
|
1266
|
+
message_path=message_path or self._deprecated_settings.message_path,
|
|
1267
|
+
sse_path=path or self._deprecated_settings.sse_path,
|
|
1088
1268
|
auth=self.auth,
|
|
1089
|
-
debug=self.
|
|
1269
|
+
debug=self._deprecated_settings.debug,
|
|
1090
1270
|
middleware=middleware,
|
|
1091
1271
|
)
|
|
1092
1272
|
|
|
@@ -1114,6 +1294,8 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
1114
1294
|
self,
|
|
1115
1295
|
path: str | None = None,
|
|
1116
1296
|
middleware: list[Middleware] | None = None,
|
|
1297
|
+
json_response: bool | None = None,
|
|
1298
|
+
stateless_http: bool | None = None,
|
|
1117
1299
|
transport: Literal["streamable-http", "sse"] = "streamable-http",
|
|
1118
1300
|
) -> StarletteWithLifespan:
|
|
1119
1301
|
"""Create a Starlette app using the specified HTTP transport.
|
|
@@ -1130,21 +1312,22 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
1130
1312
|
if transport == "streamable-http":
|
|
1131
1313
|
return create_streamable_http_app(
|
|
1132
1314
|
server=self,
|
|
1133
|
-
streamable_http_path=path
|
|
1315
|
+
streamable_http_path=path
|
|
1316
|
+
or self._deprecated_settings.streamable_http_path,
|
|
1134
1317
|
event_store=None,
|
|
1135
1318
|
auth=self.auth,
|
|
1136
|
-
json_response=self.
|
|
1137
|
-
stateless_http=self.
|
|
1138
|
-
debug=self.
|
|
1319
|
+
json_response=self._deprecated_settings.json_response,
|
|
1320
|
+
stateless_http=self._deprecated_settings.stateless_http,
|
|
1321
|
+
debug=self._deprecated_settings.debug,
|
|
1139
1322
|
middleware=middleware,
|
|
1140
1323
|
)
|
|
1141
1324
|
elif transport == "sse":
|
|
1142
1325
|
return create_sse_app(
|
|
1143
1326
|
server=self,
|
|
1144
|
-
message_path=self.
|
|
1145
|
-
sse_path=path or self.
|
|
1327
|
+
message_path=self._deprecated_settings.message_path,
|
|
1328
|
+
sse_path=path or self._deprecated_settings.sse_path,
|
|
1146
1329
|
auth=self.auth,
|
|
1147
|
-
debug=self.
|
|
1330
|
+
debug=self._deprecated_settings.debug,
|
|
1148
1331
|
middleware=middleware,
|
|
1149
1332
|
)
|
|
1150
1333
|
|
|
@@ -1375,28 +1558,13 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
1375
1558
|
route_map_fn: OpenAPIRouteMapFn | None = None,
|
|
1376
1559
|
mcp_component_fn: OpenAPIComponentFn | None = None,
|
|
1377
1560
|
mcp_names: dict[str, str] | None = None,
|
|
1378
|
-
|
|
1561
|
+
tags: set[str] | None = None,
|
|
1379
1562
|
**settings: Any,
|
|
1380
1563
|
) -> FastMCPOpenAPI:
|
|
1381
1564
|
"""
|
|
1382
1565
|
Create a FastMCP server from an OpenAPI specification.
|
|
1383
1566
|
"""
|
|
1384
|
-
from .openapi import FastMCPOpenAPI
|
|
1385
|
-
|
|
1386
|
-
# Deprecated since 2.5.0
|
|
1387
|
-
if all_routes_as_tools:
|
|
1388
|
-
warnings.warn(
|
|
1389
|
-
"The 'all_routes_as_tools' parameter is deprecated and will be removed in a future version. "
|
|
1390
|
-
'Use \'route_maps=[RouteMap(methods="*", pattern=r".*", mcp_type=MCPType.TOOL)]\' instead.',
|
|
1391
|
-
DeprecationWarning,
|
|
1392
|
-
stacklevel=2,
|
|
1393
|
-
)
|
|
1394
|
-
|
|
1395
|
-
if all_routes_as_tools and route_maps:
|
|
1396
|
-
raise ValueError("Cannot specify both all_routes_as_tools and route_maps")
|
|
1397
|
-
|
|
1398
|
-
elif all_routes_as_tools:
|
|
1399
|
-
route_maps = [RouteMap(methods="*", pattern=r".*", mcp_type=MCPType.TOOL)]
|
|
1567
|
+
from .openapi import FastMCPOpenAPI
|
|
1400
1568
|
|
|
1401
1569
|
return FastMCPOpenAPI(
|
|
1402
1570
|
openapi_spec=openapi_spec,
|
|
@@ -1405,6 +1573,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
1405
1573
|
route_map_fn=route_map_fn,
|
|
1406
1574
|
mcp_component_fn=mcp_component_fn,
|
|
1407
1575
|
mcp_names=mcp_names,
|
|
1576
|
+
tags=tags,
|
|
1408
1577
|
**settings,
|
|
1409
1578
|
)
|
|
1410
1579
|
|
|
@@ -1417,30 +1586,15 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
1417
1586
|
route_map_fn: OpenAPIRouteMapFn | None = None,
|
|
1418
1587
|
mcp_component_fn: OpenAPIComponentFn | None = None,
|
|
1419
1588
|
mcp_names: dict[str, str] | None = None,
|
|
1420
|
-
all_routes_as_tools: bool = False,
|
|
1421
1589
|
httpx_client_kwargs: dict[str, Any] | None = None,
|
|
1590
|
+
tags: set[str] | None = None,
|
|
1422
1591
|
**settings: Any,
|
|
1423
1592
|
) -> FastMCPOpenAPI:
|
|
1424
1593
|
"""
|
|
1425
1594
|
Create a FastMCP server from a FastAPI application.
|
|
1426
1595
|
"""
|
|
1427
1596
|
|
|
1428
|
-
from .openapi import FastMCPOpenAPI
|
|
1429
|
-
|
|
1430
|
-
# Deprecated since 2.5.0
|
|
1431
|
-
if all_routes_as_tools:
|
|
1432
|
-
warnings.warn(
|
|
1433
|
-
"The 'all_routes_as_tools' parameter is deprecated and will be removed in a future version. "
|
|
1434
|
-
'Use \'route_maps=[RouteMap(methods="*", pattern=r".*", mcp_type=MCPType.TOOL)]\' instead.',
|
|
1435
|
-
DeprecationWarning,
|
|
1436
|
-
stacklevel=2,
|
|
1437
|
-
)
|
|
1438
|
-
|
|
1439
|
-
if all_routes_as_tools and route_maps:
|
|
1440
|
-
raise ValueError("Cannot specify both all_routes_as_tools and route_maps")
|
|
1441
|
-
|
|
1442
|
-
elif all_routes_as_tools:
|
|
1443
|
-
route_maps = [RouteMap(methods="*", pattern=r".*", mcp_type=MCPType.TOOL)]
|
|
1597
|
+
from .openapi import FastMCPOpenAPI
|
|
1444
1598
|
|
|
1445
1599
|
if httpx_client_kwargs is None:
|
|
1446
1600
|
httpx_client_kwargs = {}
|
|
@@ -1461,6 +1615,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
1461
1615
|
route_map_fn=route_map_fn,
|
|
1462
1616
|
mcp_component_fn=mcp_component_fn,
|
|
1463
1617
|
mcp_names=mcp_names,
|
|
1618
|
+
tags=tags,
|
|
1464
1619
|
**settings,
|
|
1465
1620
|
)
|
|
1466
1621
|
|
|
@@ -1510,6 +1665,41 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
1510
1665
|
|
|
1511
1666
|
return cls.as_proxy(client, **settings)
|
|
1512
1667
|
|
|
1668
|
+
def _should_enable_component(
|
|
1669
|
+
self,
|
|
1670
|
+
component: FastMCPComponent,
|
|
1671
|
+
) -> bool:
|
|
1672
|
+
"""
|
|
1673
|
+
Given a component, determine if it should be enabled. Returns True if it should be enabled; False if it should not.
|
|
1674
|
+
|
|
1675
|
+
Rules:
|
|
1676
|
+
• If the component's enabled property is False, always return False.
|
|
1677
|
+
• If both include_tags and exclude_tags are None, return True.
|
|
1678
|
+
• If exclude_tags is provided, check each exclude tag:
|
|
1679
|
+
- If the exclude tag is a string, it must be present in the input tags to exclude.
|
|
1680
|
+
• If include_tags is provided, check each include tag:
|
|
1681
|
+
- If the include tag is a string, it must be present in the input tags to include.
|
|
1682
|
+
• If include_tags is provided and none of the include tags match, return False.
|
|
1683
|
+
• If include_tags is not provided, return True.
|
|
1684
|
+
"""
|
|
1685
|
+
if not component.enabled:
|
|
1686
|
+
return False
|
|
1687
|
+
|
|
1688
|
+
if self.include_tags is None and self.exclude_tags is None:
|
|
1689
|
+
return True
|
|
1690
|
+
|
|
1691
|
+
if self.exclude_tags is not None:
|
|
1692
|
+
if any(etag in component.tags for etag in self.exclude_tags):
|
|
1693
|
+
return False
|
|
1694
|
+
|
|
1695
|
+
if self.include_tags is not None:
|
|
1696
|
+
if any(itag in component.tags for itag in self.include_tags):
|
|
1697
|
+
return True
|
|
1698
|
+
else:
|
|
1699
|
+
return False
|
|
1700
|
+
|
|
1701
|
+
return True
|
|
1702
|
+
|
|
1513
1703
|
|
|
1514
1704
|
class MountedServer:
|
|
1515
1705
|
def __init__(
|
|
@@ -1596,7 +1786,7 @@ def add_resource_prefix(
|
|
|
1596
1786
|
# Get the server settings to check for legacy format preference
|
|
1597
1787
|
|
|
1598
1788
|
if prefix_format is None:
|
|
1599
|
-
prefix_format = fastmcp.settings.
|
|
1789
|
+
prefix_format = fastmcp.settings.resource_prefix_format
|
|
1600
1790
|
|
|
1601
1791
|
if prefix_format == "protocol":
|
|
1602
1792
|
# Legacy style: prefix+protocol://path
|
|
@@ -1645,7 +1835,7 @@ def remove_resource_prefix(
|
|
|
1645
1835
|
return uri
|
|
1646
1836
|
|
|
1647
1837
|
if prefix_format is None:
|
|
1648
|
-
prefix_format = fastmcp.settings.
|
|
1838
|
+
prefix_format = fastmcp.settings.resource_prefix_format
|
|
1649
1839
|
|
|
1650
1840
|
if prefix_format == "protocol":
|
|
1651
1841
|
# Legacy style: prefix+protocol://path
|
|
@@ -1705,7 +1895,7 @@ def has_resource_prefix(
|
|
|
1705
1895
|
# Get the server settings to check for legacy format preference
|
|
1706
1896
|
|
|
1707
1897
|
if prefix_format is None:
|
|
1708
|
-
prefix_format = fastmcp.settings.
|
|
1898
|
+
prefix_format = fastmcp.settings.resource_prefix_format
|
|
1709
1899
|
|
|
1710
1900
|
if prefix_format == "protocol":
|
|
1711
1901
|
# Legacy style: prefix+protocol://path
|