fastmcp 2.7.1__py3-none-any.whl → 2.8.1__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 +32 -3
- fastmcp/cli/cli.py +3 -2
- fastmcp/client/auth/oauth.py +1 -1
- fastmcp/client/client.py +6 -5
- fastmcp/client/sampling.py +5 -9
- fastmcp/client/transports.py +42 -33
- fastmcp/exceptions.py +4 -0
- fastmcp/prompts/prompt.py +11 -21
- fastmcp/prompts/prompt_manager.py +13 -9
- fastmcp/resources/resource.py +21 -26
- fastmcp/resources/resource_manager.py +15 -12
- fastmcp/resources/template.py +8 -16
- fastmcp/server/auth/providers/bearer_env.py +8 -11
- fastmcp/server/auth/providers/in_memory.py +2 -2
- fastmcp/server/context.py +12 -10
- fastmcp/server/openapi.py +87 -57
- fastmcp/server/proxy.py +29 -20
- fastmcp/server/server.py +422 -206
- fastmcp/settings.py +113 -37
- fastmcp/tools/__init__.py +2 -1
- fastmcp/tools/tool.py +125 -85
- fastmcp/tools/tool_manager.py +12 -11
- fastmcp/tools/tool_transform.py +669 -0
- fastmcp/utilities/components.py +55 -0
- fastmcp/utilities/exceptions.py +1 -1
- fastmcp/utilities/mcp_config.py +1 -1
- fastmcp/utilities/tests.py +3 -3
- fastmcp/utilities/types.py +82 -14
- {fastmcp-2.7.1.dist-info → fastmcp-2.8.1.dist-info}/METADATA +48 -26
- {fastmcp-2.7.1.dist-info → fastmcp-2.8.1.dist-info}/RECORD +33 -31
- {fastmcp-2.7.1.dist-info → fastmcp-2.8.1.dist-info}/WHEEL +0 -0
- {fastmcp-2.7.1.dist-info → fastmcp-2.8.1.dist-info}/entry_points.txt +0 -0
- {fastmcp-2.7.1.dist-info → fastmcp-2.8.1.dist-info}/licenses/LICENSE +0 -0
fastmcp/server/server.py
CHANGED
|
@@ -25,10 +25,7 @@ from mcp.server.lowlevel.server import Server as MCPServer
|
|
|
25
25
|
from mcp.server.stdio import stdio_server
|
|
26
26
|
from mcp.types import (
|
|
27
27
|
AnyFunction,
|
|
28
|
-
EmbeddedResource,
|
|
29
28
|
GetPromptResult,
|
|
30
|
-
ImageContent,
|
|
31
|
-
TextContent,
|
|
32
29
|
ToolAnnotations,
|
|
33
30
|
)
|
|
34
31
|
from mcp.types import Prompt as MCPPrompt
|
|
@@ -43,8 +40,7 @@ from starlette.routing import BaseRoute, Route
|
|
|
43
40
|
|
|
44
41
|
import fastmcp
|
|
45
42
|
import fastmcp.server
|
|
46
|
-
|
|
47
|
-
from fastmcp.exceptions import NotFoundError
|
|
43
|
+
from fastmcp.exceptions import DisabledError, NotFoundError
|
|
48
44
|
from fastmcp.prompts import Prompt, PromptManager
|
|
49
45
|
from fastmcp.prompts.prompt import FunctionPrompt
|
|
50
46
|
from fastmcp.resources import Resource, ResourceManager
|
|
@@ -56,11 +52,14 @@ from fastmcp.server.http import (
|
|
|
56
52
|
create_sse_app,
|
|
57
53
|
create_streamable_http_app,
|
|
58
54
|
)
|
|
55
|
+
from fastmcp.settings import Settings
|
|
59
56
|
from fastmcp.tools import ToolManager
|
|
60
57
|
from fastmcp.tools.tool import FunctionTool, Tool
|
|
61
58
|
from fastmcp.utilities.cache import TimedCache
|
|
59
|
+
from fastmcp.utilities.components import FastMCPComponent
|
|
62
60
|
from fastmcp.utilities.logging import get_logger
|
|
63
61
|
from fastmcp.utilities.mcp_config import MCPConfig
|
|
62
|
+
from fastmcp.utilities.types import MCPContent
|
|
64
63
|
|
|
65
64
|
if TYPE_CHECKING:
|
|
66
65
|
from fastmcp.client import Client
|
|
@@ -120,8 +119,6 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
120
119
|
]
|
|
121
120
|
| None
|
|
122
121
|
) = None,
|
|
123
|
-
tags: set[str] | None = None,
|
|
124
|
-
dependencies: list[str] | None = None,
|
|
125
122
|
tool_serializer: Callable[[Any], str] | None = None,
|
|
126
123
|
cache_expiration_seconds: float | None = None,
|
|
127
124
|
on_duplicate_tools: DuplicateBehavior | None = None,
|
|
@@ -130,44 +127,44 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
130
127
|
resource_prefix_format: Literal["protocol", "path"] | None = None,
|
|
131
128
|
mask_error_details: bool | None = None,
|
|
132
129
|
tools: list[Tool | Callable[..., Any]] | None = None,
|
|
133
|
-
|
|
130
|
+
dependencies: list[str] | None = None,
|
|
131
|
+
include_tags: set[str] | None = None,
|
|
132
|
+
exclude_tags: set[str] | None = None,
|
|
133
|
+
# ---
|
|
134
|
+
# ---
|
|
135
|
+
# --- The following arguments are DEPRECATED ---
|
|
136
|
+
# ---
|
|
137
|
+
# ---
|
|
138
|
+
log_level: str | None = None,
|
|
139
|
+
debug: bool | None = None,
|
|
140
|
+
host: str | None = None,
|
|
141
|
+
port: int | None = None,
|
|
142
|
+
sse_path: str | None = None,
|
|
143
|
+
message_path: str | None = None,
|
|
144
|
+
streamable_http_path: str | None = None,
|
|
145
|
+
json_response: bool | None = None,
|
|
146
|
+
stateless_http: bool | None = None,
|
|
134
147
|
):
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
# If mask_error_details is provided, override the settings value
|
|
140
|
-
if mask_error_details is not None:
|
|
141
|
-
self.settings.mask_error_details = mask_error_details
|
|
142
|
-
|
|
143
|
-
self.resource_prefix_format: Literal["protocol", "path"]
|
|
144
|
-
if resource_prefix_format is None:
|
|
145
|
-
self.resource_prefix_format = (
|
|
146
|
-
fastmcp.settings.settings.resource_prefix_format
|
|
147
|
-
)
|
|
148
|
-
else:
|
|
149
|
-
self.resource_prefix_format = resource_prefix_format
|
|
148
|
+
self.resource_prefix_format: Literal["protocol", "path"] = (
|
|
149
|
+
resource_prefix_format or fastmcp.settings.resource_prefix_format
|
|
150
|
+
)
|
|
150
151
|
|
|
151
|
-
self.tags: set[str] = tags or set()
|
|
152
|
-
self.dependencies = dependencies
|
|
153
152
|
self._cache = TimedCache(
|
|
154
|
-
expiration=datetime.timedelta(
|
|
155
|
-
seconds=self.settings.cache_expiration_seconds
|
|
156
|
-
)
|
|
153
|
+
expiration=datetime.timedelta(seconds=cache_expiration_seconds or 0)
|
|
157
154
|
)
|
|
158
155
|
self._mounted_servers: dict[str, MountedServer] = {}
|
|
159
156
|
self._additional_http_routes: list[BaseRoute] = []
|
|
160
157
|
self._tool_manager = ToolManager(
|
|
161
158
|
duplicate_behavior=on_duplicate_tools,
|
|
162
|
-
mask_error_details=
|
|
159
|
+
mask_error_details=mask_error_details,
|
|
163
160
|
)
|
|
164
161
|
self._resource_manager = ResourceManager(
|
|
165
162
|
duplicate_behavior=on_duplicate_resources,
|
|
166
|
-
mask_error_details=
|
|
163
|
+
mask_error_details=mask_error_details,
|
|
167
164
|
)
|
|
168
165
|
self._prompt_manager = PromptManager(
|
|
169
166
|
duplicate_behavior=on_duplicate_prompts,
|
|
170
|
-
mask_error_details=
|
|
167
|
+
mask_error_details=mask_error_details,
|
|
171
168
|
)
|
|
172
169
|
self._tool_serializer = tool_serializer
|
|
173
170
|
|
|
@@ -182,7 +179,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
182
179
|
lifespan=_lifespan_wrapper(self, lifespan),
|
|
183
180
|
)
|
|
184
181
|
|
|
185
|
-
if auth is None and
|
|
182
|
+
if auth is None and fastmcp.settings.default_auth_provider == "bearer_env":
|
|
186
183
|
auth = EnvBearerAuthProvider()
|
|
187
184
|
self.auth = auth
|
|
188
185
|
|
|
@@ -192,12 +189,79 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
192
189
|
tool = Tool.from_function(tool, serializer=self._tool_serializer)
|
|
193
190
|
self.add_tool(tool)
|
|
194
191
|
|
|
192
|
+
self.include_tags = include_tags
|
|
193
|
+
self.exclude_tags = exclude_tags
|
|
194
|
+
|
|
195
195
|
# Set up MCP protocol handlers
|
|
196
196
|
self._setup_handlers()
|
|
197
|
+
self.dependencies = dependencies or fastmcp.settings.server_dependencies
|
|
198
|
+
|
|
199
|
+
# handle deprecated settings
|
|
200
|
+
self._handle_deprecated_settings(
|
|
201
|
+
log_level=log_level,
|
|
202
|
+
debug=debug,
|
|
203
|
+
host=host,
|
|
204
|
+
port=port,
|
|
205
|
+
sse_path=sse_path,
|
|
206
|
+
message_path=message_path,
|
|
207
|
+
streamable_http_path=streamable_http_path,
|
|
208
|
+
json_response=json_response,
|
|
209
|
+
stateless_http=stateless_http,
|
|
210
|
+
)
|
|
197
211
|
|
|
198
212
|
def __repr__(self) -> str:
|
|
199
213
|
return f"{type(self).__name__}({self.name!r})"
|
|
200
214
|
|
|
215
|
+
def _handle_deprecated_settings(
|
|
216
|
+
self,
|
|
217
|
+
log_level: str | None,
|
|
218
|
+
debug: bool | None,
|
|
219
|
+
host: str | None,
|
|
220
|
+
port: int | None,
|
|
221
|
+
sse_path: str | None,
|
|
222
|
+
message_path: str | None,
|
|
223
|
+
streamable_http_path: str | None,
|
|
224
|
+
json_response: bool | None,
|
|
225
|
+
stateless_http: bool | None,
|
|
226
|
+
) -> None:
|
|
227
|
+
"""Handle deprecated settings. Deprecated in 2.8.0."""
|
|
228
|
+
deprecated_settings: dict[str, Any] = {}
|
|
229
|
+
|
|
230
|
+
for name, arg in [
|
|
231
|
+
("log_level", log_level),
|
|
232
|
+
("debug", debug),
|
|
233
|
+
("host", host),
|
|
234
|
+
("port", port),
|
|
235
|
+
("sse_path", sse_path),
|
|
236
|
+
("message_path", message_path),
|
|
237
|
+
("streamable_http_path", streamable_http_path),
|
|
238
|
+
("json_response", json_response),
|
|
239
|
+
("stateless_http", stateless_http),
|
|
240
|
+
]:
|
|
241
|
+
if arg is not None:
|
|
242
|
+
# Deprecated in 2.8.0
|
|
243
|
+
if fastmcp.settings.deprecation_warnings:
|
|
244
|
+
warnings.warn(
|
|
245
|
+
f"Providing `{name}` when creating a server is deprecated. Provide it when calling `run` or as a global setting instead.",
|
|
246
|
+
DeprecationWarning,
|
|
247
|
+
stacklevel=2,
|
|
248
|
+
)
|
|
249
|
+
deprecated_settings[name] = arg
|
|
250
|
+
|
|
251
|
+
combined_settings = fastmcp.settings.model_dump() | deprecated_settings
|
|
252
|
+
self._deprecated_settings = Settings(**combined_settings)
|
|
253
|
+
|
|
254
|
+
@property
|
|
255
|
+
def settings(self) -> Settings:
|
|
256
|
+
# Deprecated in 2.8.0
|
|
257
|
+
if fastmcp.settings.deprecation_warnings:
|
|
258
|
+
warnings.warn(
|
|
259
|
+
"Accessing `.settings` on a FastMCP instance is deprecated. Use the global `fastmcp.settings` instead.",
|
|
260
|
+
DeprecationWarning,
|
|
261
|
+
stacklevel=2,
|
|
262
|
+
)
|
|
263
|
+
return self._deprecated_settings
|
|
264
|
+
|
|
201
265
|
@property
|
|
202
266
|
def name(self) -> str:
|
|
203
267
|
return self._mcp_server.name
|
|
@@ -244,12 +308,12 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
244
308
|
def _setup_handlers(self) -> None:
|
|
245
309
|
"""Set up core MCP protocol handlers."""
|
|
246
310
|
self._mcp_server.list_tools()(self._mcp_list_tools)
|
|
247
|
-
self._mcp_server.call_tool()(self._mcp_call_tool)
|
|
248
311
|
self._mcp_server.list_resources()(self._mcp_list_resources)
|
|
249
|
-
self._mcp_server.
|
|
312
|
+
self._mcp_server.list_resource_templates()(self._mcp_list_resource_templates)
|
|
250
313
|
self._mcp_server.list_prompts()(self._mcp_list_prompts)
|
|
314
|
+
self._mcp_server.call_tool()(self._mcp_call_tool)
|
|
315
|
+
self._mcp_server.read_resource()(self._mcp_read_resource)
|
|
251
316
|
self._mcp_server.get_prompt()(self._mcp_get_prompt)
|
|
252
|
-
self._mcp_server.list_resource_templates()(self._mcp_list_resource_templates)
|
|
253
317
|
|
|
254
318
|
async def get_tools(self) -> dict[str, Tool]:
|
|
255
319
|
"""Get all registered tools, indexed by registered key."""
|
|
@@ -268,6 +332,12 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
268
332
|
self._cache.set("tools", tools)
|
|
269
333
|
return tools
|
|
270
334
|
|
|
335
|
+
async def get_tool(self, key: str) -> Tool:
|
|
336
|
+
tools = await self.get_tools()
|
|
337
|
+
if key not in tools:
|
|
338
|
+
raise NotFoundError(f"Unknown tool: {key}")
|
|
339
|
+
return tools[key]
|
|
340
|
+
|
|
271
341
|
async def get_resources(self) -> dict[str, Resource]:
|
|
272
342
|
"""Get all registered resources, indexed by registered key."""
|
|
273
343
|
if (resources := self._cache.get("resources")) is self._cache.NOT_FOUND:
|
|
@@ -285,6 +355,12 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
285
355
|
self._cache.set("resources", resources)
|
|
286
356
|
return resources
|
|
287
357
|
|
|
358
|
+
async def get_resource(self, key: str) -> Resource:
|
|
359
|
+
resources = await self.get_resources()
|
|
360
|
+
if key not in resources:
|
|
361
|
+
raise NotFoundError(f"Unknown resource: {key}")
|
|
362
|
+
return resources[key]
|
|
363
|
+
|
|
288
364
|
async def get_resource_templates(self) -> dict[str, ResourceTemplate]:
|
|
289
365
|
"""Get all registered resource templates, indexed by registered key."""
|
|
290
366
|
if (
|
|
@@ -305,6 +381,12 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
305
381
|
self._cache.set("resource_templates", templates)
|
|
306
382
|
return templates
|
|
307
383
|
|
|
384
|
+
async def get_resource_template(self, key: str) -> ResourceTemplate:
|
|
385
|
+
templates = await self.get_resource_templates()
|
|
386
|
+
if key not in templates:
|
|
387
|
+
raise NotFoundError(f"Unknown resource template: {key}")
|
|
388
|
+
return templates[key]
|
|
389
|
+
|
|
308
390
|
async def get_prompts(self) -> dict[str, Prompt]:
|
|
309
391
|
"""
|
|
310
392
|
List all available prompts.
|
|
@@ -324,6 +406,12 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
324
406
|
self._cache.set("prompts", prompts)
|
|
325
407
|
return prompts
|
|
326
408
|
|
|
409
|
+
async def get_prompt(self, key: str) -> Prompt:
|
|
410
|
+
prompts = await self.get_prompts()
|
|
411
|
+
if key not in prompts:
|
|
412
|
+
raise NotFoundError(f"Unknown prompt: {key}")
|
|
413
|
+
return prompts[key]
|
|
414
|
+
|
|
327
415
|
def custom_route(
|
|
328
416
|
self,
|
|
329
417
|
path: str,
|
|
@@ -375,7 +463,13 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
375
463
|
|
|
376
464
|
"""
|
|
377
465
|
tools = await self.get_tools()
|
|
378
|
-
|
|
466
|
+
|
|
467
|
+
mcp_tools: list[MCPTool] = []
|
|
468
|
+
for key, tool in tools.items():
|
|
469
|
+
if self._should_enable_component(tool):
|
|
470
|
+
mcp_tools.append(tool.to_mcp_tool(name=key))
|
|
471
|
+
|
|
472
|
+
return mcp_tools
|
|
379
473
|
|
|
380
474
|
async def _mcp_list_resources(self) -> list[MCPResource]:
|
|
381
475
|
"""
|
|
@@ -384,9 +478,11 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
384
478
|
|
|
385
479
|
"""
|
|
386
480
|
resources = await self.get_resources()
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
481
|
+
mcp_resources: list[MCPResource] = []
|
|
482
|
+
for key, resource in resources.items():
|
|
483
|
+
if self._should_enable_component(resource):
|
|
484
|
+
mcp_resources.append(resource.to_mcp_resource(uri=key))
|
|
485
|
+
return mcp_resources
|
|
390
486
|
|
|
391
487
|
async def _mcp_list_resource_templates(self) -> list[MCPResourceTemplate]:
|
|
392
488
|
"""
|
|
@@ -395,10 +491,11 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
395
491
|
|
|
396
492
|
"""
|
|
397
493
|
templates = await self.get_resource_templates()
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
494
|
+
mcp_templates: list[MCPResourceTemplate] = []
|
|
495
|
+
for key, template in templates.items():
|
|
496
|
+
if self._should_enable_component(template):
|
|
497
|
+
mcp_templates.append(template.to_mcp_template(uriTemplate=key))
|
|
498
|
+
return mcp_templates
|
|
402
499
|
|
|
403
500
|
async def _mcp_list_prompts(self) -> list[MCPPrompt]:
|
|
404
501
|
"""
|
|
@@ -407,12 +504,19 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
407
504
|
|
|
408
505
|
"""
|
|
409
506
|
prompts = await self.get_prompts()
|
|
410
|
-
|
|
507
|
+
mcp_prompts: list[MCPPrompt] = []
|
|
508
|
+
for key, prompt in prompts.items():
|
|
509
|
+
if self._should_enable_component(prompt):
|
|
510
|
+
mcp_prompts.append(prompt.to_mcp_prompt(name=key))
|
|
511
|
+
return mcp_prompts
|
|
411
512
|
|
|
412
513
|
async def _mcp_call_tool(
|
|
413
514
|
self, key: str, arguments: dict[str, Any]
|
|
414
|
-
) -> list[
|
|
415
|
-
"""
|
|
515
|
+
) -> list[MCPContent]:
|
|
516
|
+
"""
|
|
517
|
+
Handle MCP 'callTool' requests.
|
|
518
|
+
|
|
519
|
+
Delegates to _call_tool, which should be overridden by FastMCP subclasses.
|
|
416
520
|
|
|
417
521
|
Args:
|
|
418
522
|
key: The name of the tool to call
|
|
@@ -425,43 +529,107 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
425
529
|
|
|
426
530
|
# Create and use context for the entire call
|
|
427
531
|
with fastmcp.server.context.Context(fastmcp=self):
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
532
|
+
try:
|
|
533
|
+
return await self._call_tool(key, arguments)
|
|
534
|
+
except DisabledError:
|
|
535
|
+
# convert to NotFoundError to avoid leaking tool presence
|
|
536
|
+
raise NotFoundError(f"Unknown tool: {key}")
|
|
537
|
+
except NotFoundError:
|
|
538
|
+
# standardize NotFound message
|
|
539
|
+
raise NotFoundError(f"Unknown tool: {key}")
|
|
540
|
+
|
|
541
|
+
async def _call_tool(self, key: str, arguments: dict[str, Any]) -> list[MCPContent]:
|
|
542
|
+
"""
|
|
543
|
+
Call a tool with raw MCP arguments. FastMCP subclasses should override
|
|
544
|
+
this method, not _mcp_call_tool.
|
|
431
545
|
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
tool_key = server.strip_tool_prefix(key)
|
|
436
|
-
return await server.server._mcp_call_tool(tool_key, arguments)
|
|
546
|
+
Args:
|
|
547
|
+
key: The name of the tool to call arguments: Arguments to pass to
|
|
548
|
+
the tool
|
|
437
549
|
|
|
438
|
-
|
|
550
|
+
Returns:
|
|
551
|
+
List of MCP Content objects containing the tool results
|
|
552
|
+
"""
|
|
553
|
+
|
|
554
|
+
# Get tool, checking first from our tools, then from the mounted servers
|
|
555
|
+
if self._tool_manager.has_tool(key):
|
|
556
|
+
tool = self._tool_manager.get_tool(key)
|
|
557
|
+
if not self._should_enable_component(tool):
|
|
558
|
+
raise DisabledError(f"Tool {key!r} is disabled")
|
|
559
|
+
return await self._tool_manager.call_tool(key, arguments)
|
|
560
|
+
|
|
561
|
+
# Check mounted servers to see if they have the tool
|
|
562
|
+
for server in self._mounted_servers.values():
|
|
563
|
+
if server.match_tool(key):
|
|
564
|
+
tool_key = server.strip_tool_prefix(key)
|
|
565
|
+
return await server.server._call_tool(tool_key, arguments)
|
|
566
|
+
|
|
567
|
+
raise NotFoundError(f"Unknown tool: {key!r}")
|
|
439
568
|
|
|
440
569
|
async def _mcp_read_resource(self, uri: AnyUrl | str) -> list[ReadResourceContents]:
|
|
570
|
+
"""
|
|
571
|
+
Handle MCP 'readResource' requests.
|
|
572
|
+
|
|
573
|
+
Delegates to _read_resource, which should be overridden by FastMCP subclasses.
|
|
574
|
+
"""
|
|
575
|
+
logger.debug("Read resource: %s", uri)
|
|
576
|
+
|
|
577
|
+
with fastmcp.server.context.Context(fastmcp=self):
|
|
578
|
+
try:
|
|
579
|
+
return await self._read_resource(uri)
|
|
580
|
+
except DisabledError:
|
|
581
|
+
# convert to NotFoundError to avoid leaking resource presence
|
|
582
|
+
raise NotFoundError(f"Unknown resource: {str(uri)!r}")
|
|
583
|
+
except NotFoundError:
|
|
584
|
+
# standardize NotFound message
|
|
585
|
+
raise NotFoundError(f"Unknown resource: {str(uri)!r}")
|
|
586
|
+
|
|
587
|
+
async def _read_resource(self, uri: AnyUrl | str) -> list[ReadResourceContents]:
|
|
441
588
|
"""
|
|
442
589
|
Read a resource by URI, in the format expected by the low-level MCP
|
|
443
590
|
server.
|
|
444
591
|
"""
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
592
|
+
if self._resource_manager.has_resource(uri):
|
|
593
|
+
resource = await self._resource_manager.get_resource(uri)
|
|
594
|
+
if not self._should_enable_component(resource):
|
|
595
|
+
raise DisabledError(f"Resource {str(uri)!r} is disabled")
|
|
596
|
+
content = await self._resource_manager.read_resource(uri)
|
|
597
|
+
return [
|
|
598
|
+
ReadResourceContents(
|
|
599
|
+
content=content,
|
|
600
|
+
mime_type=resource.mime_type,
|
|
601
|
+
)
|
|
602
|
+
]
|
|
603
|
+
else:
|
|
604
|
+
for server in self._mounted_servers.values():
|
|
605
|
+
if server.match_resource(str(uri)):
|
|
606
|
+
new_uri = server.strip_resource_prefix(str(uri))
|
|
607
|
+
return await server.server._mcp_read_resource(new_uri)
|
|
455
608
|
else:
|
|
456
|
-
|
|
457
|
-
if server.match_resource(str(uri)):
|
|
458
|
-
new_uri = server.strip_resource_prefix(str(uri))
|
|
459
|
-
return await server.server._mcp_read_resource(new_uri)
|
|
460
|
-
else:
|
|
461
|
-
raise NotFoundError(f"Unknown resource: {uri}")
|
|
609
|
+
raise NotFoundError(f"Unknown resource: {uri}")
|
|
462
610
|
|
|
463
611
|
async def _mcp_get_prompt(
|
|
464
612
|
self, name: str, arguments: dict[str, Any] | None = None
|
|
613
|
+
) -> GetPromptResult:
|
|
614
|
+
"""
|
|
615
|
+
Handle MCP 'getPrompt' requests.
|
|
616
|
+
|
|
617
|
+
Delegates to _get_prompt, which should be overridden by FastMCP subclasses.
|
|
618
|
+
"""
|
|
619
|
+
logger.debug("Get prompt: %s with %s", name, arguments)
|
|
620
|
+
|
|
621
|
+
with fastmcp.server.context.Context(fastmcp=self):
|
|
622
|
+
try:
|
|
623
|
+
return await self._get_prompt(name, arguments)
|
|
624
|
+
except DisabledError:
|
|
625
|
+
# convert to NotFoundError to avoid leaking prompt presence
|
|
626
|
+
raise NotFoundError(f"Unknown prompt: {name}")
|
|
627
|
+
except NotFoundError:
|
|
628
|
+
# standardize NotFound message
|
|
629
|
+
raise NotFoundError(f"Unknown prompt: {name}")
|
|
630
|
+
|
|
631
|
+
async def _get_prompt(
|
|
632
|
+
self, name: str, arguments: dict[str, Any] | None = None
|
|
465
633
|
) -> GetPromptResult:
|
|
466
634
|
"""Handle MCP 'getPrompt' requests.
|
|
467
635
|
|
|
@@ -474,19 +642,20 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
474
642
|
"""
|
|
475
643
|
logger.debug("Get prompt: %s with %s", name, arguments)
|
|
476
644
|
|
|
477
|
-
#
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
if self.
|
|
481
|
-
|
|
645
|
+
# Get prompt, checking first from our prompts, then from the mounted servers
|
|
646
|
+
if self._prompt_manager.has_prompt(name):
|
|
647
|
+
prompt = self._prompt_manager.get_prompt(name)
|
|
648
|
+
if not self._should_enable_component(prompt):
|
|
649
|
+
raise DisabledError(f"Prompt {name!r} is disabled")
|
|
650
|
+
return await self._prompt_manager.render_prompt(name, arguments)
|
|
482
651
|
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
652
|
+
# Check mounted servers to see if they have the prompt
|
|
653
|
+
for server in self._mounted_servers.values():
|
|
654
|
+
if server.match_prompt(name):
|
|
655
|
+
prompt_name = server.strip_prompt_prefix(name)
|
|
656
|
+
return await server.server._mcp_get_prompt(prompt_name, arguments)
|
|
488
657
|
|
|
489
|
-
|
|
658
|
+
raise NotFoundError(f"Unknown prompt: {name}")
|
|
490
659
|
|
|
491
660
|
def add_tool(self, tool: Tool) -> None:
|
|
492
661
|
"""Add a tool to the server.
|
|
@@ -522,6 +691,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
522
691
|
tags: set[str] | None = None,
|
|
523
692
|
annotations: ToolAnnotations | dict[str, Any] | None = None,
|
|
524
693
|
exclude_args: list[str] | None = None,
|
|
694
|
+
enabled: bool | None = None,
|
|
525
695
|
) -> FunctionTool: ...
|
|
526
696
|
|
|
527
697
|
@overload
|
|
@@ -534,6 +704,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
534
704
|
tags: set[str] | None = None,
|
|
535
705
|
annotations: ToolAnnotations | dict[str, Any] | None = None,
|
|
536
706
|
exclude_args: list[str] | None = None,
|
|
707
|
+
enabled: bool | None = None,
|
|
537
708
|
) -> Callable[[AnyFunction], FunctionTool]: ...
|
|
538
709
|
|
|
539
710
|
def tool(
|
|
@@ -545,6 +716,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
545
716
|
tags: set[str] | None = None,
|
|
546
717
|
annotations: ToolAnnotations | dict[str, Any] | None = None,
|
|
547
718
|
exclude_args: list[str] | None = None,
|
|
719
|
+
enabled: bool | None = None,
|
|
548
720
|
) -> Callable[[AnyFunction], FunctionTool] | FunctionTool:
|
|
549
721
|
"""Decorator to register a tool.
|
|
550
722
|
|
|
@@ -561,11 +733,12 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
561
733
|
|
|
562
734
|
Args:
|
|
563
735
|
name_or_fn: Either a function (when used as @tool), a string name, or None
|
|
736
|
+
name: Optional name for the tool (keyword-only, alternative to name_or_fn)
|
|
564
737
|
description: Optional description of what the tool does
|
|
565
738
|
tags: Optional set of tags for categorizing the tool
|
|
566
|
-
annotations: Optional annotations about the tool's behavior
|
|
739
|
+
annotations: Optional annotations about the tool's behavior (e.g. {"is_async": True})
|
|
567
740
|
exclude_args: Optional list of argument names to exclude from the tool schema
|
|
568
|
-
|
|
741
|
+
enabled: Optional boolean to enable or disable the tool
|
|
569
742
|
|
|
570
743
|
Example:
|
|
571
744
|
@server.tool
|
|
@@ -618,6 +791,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
618
791
|
annotations=annotations,
|
|
619
792
|
exclude_args=exclude_args,
|
|
620
793
|
serializer=self._tool_serializer,
|
|
794
|
+
enabled=enabled,
|
|
621
795
|
)
|
|
622
796
|
self.add_tool(tool)
|
|
623
797
|
return tool
|
|
@@ -646,6 +820,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
646
820
|
tags=tags,
|
|
647
821
|
annotations=annotations,
|
|
648
822
|
exclude_args=exclude_args,
|
|
823
|
+
enabled=enabled,
|
|
649
824
|
)
|
|
650
825
|
|
|
651
826
|
def add_resource(self, resource: Resource, key: str | None = None) -> None:
|
|
@@ -689,11 +864,12 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
689
864
|
tags: Optional set of tags for categorizing the resource
|
|
690
865
|
"""
|
|
691
866
|
# deprecated since 2.7.0
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
867
|
+
if fastmcp.settings.deprecation_warnings:
|
|
868
|
+
warnings.warn(
|
|
869
|
+
"The add_resource_fn method is deprecated. Use the resource decorator instead.",
|
|
870
|
+
DeprecationWarning,
|
|
871
|
+
stacklevel=2,
|
|
872
|
+
)
|
|
697
873
|
self._resource_manager.add_resource_or_template_from_fn(
|
|
698
874
|
fn=fn,
|
|
699
875
|
uri=uri,
|
|
@@ -712,6 +888,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
712
888
|
description: str | None = None,
|
|
713
889
|
mime_type: str | None = None,
|
|
714
890
|
tags: set[str] | None = None,
|
|
891
|
+
enabled: bool | None = None,
|
|
715
892
|
) -> Callable[[AnyFunction], Resource | ResourceTemplate]:
|
|
716
893
|
"""Decorator to register a function as a resource.
|
|
717
894
|
|
|
@@ -734,6 +911,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
734
911
|
description: Optional description of the resource
|
|
735
912
|
mime_type: Optional MIME type for the resource
|
|
736
913
|
tags: Optional set of tags for categorizing the resource
|
|
914
|
+
enabled: Optional boolean to enable or disable the resource
|
|
737
915
|
|
|
738
916
|
Example:
|
|
739
917
|
@server.resource("resource://my-resource")
|
|
@@ -798,6 +976,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
798
976
|
description=description,
|
|
799
977
|
mime_type=mime_type,
|
|
800
978
|
tags=tags,
|
|
979
|
+
enabled=enabled,
|
|
801
980
|
)
|
|
802
981
|
self.add_template(template)
|
|
803
982
|
return template
|
|
@@ -809,6 +988,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
809
988
|
description=description,
|
|
810
989
|
mime_type=mime_type,
|
|
811
990
|
tags=tags,
|
|
991
|
+
enabled=enabled,
|
|
812
992
|
)
|
|
813
993
|
self.add_resource(resource)
|
|
814
994
|
return resource
|
|
@@ -837,6 +1017,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
837
1017
|
name: str | None = None,
|
|
838
1018
|
description: str | None = None,
|
|
839
1019
|
tags: set[str] | None = None,
|
|
1020
|
+
enabled: bool | None = None,
|
|
840
1021
|
) -> FunctionPrompt: ...
|
|
841
1022
|
|
|
842
1023
|
@overload
|
|
@@ -847,6 +1028,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
847
1028
|
name: str | None = None,
|
|
848
1029
|
description: str | None = None,
|
|
849
1030
|
tags: set[str] | None = None,
|
|
1031
|
+
enabled: bool | None = None,
|
|
850
1032
|
) -> Callable[[AnyFunction], FunctionPrompt]: ...
|
|
851
1033
|
|
|
852
1034
|
def prompt(
|
|
@@ -856,6 +1038,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
856
1038
|
name: str | None = None,
|
|
857
1039
|
description: str | None = None,
|
|
858
1040
|
tags: set[str] | None = None,
|
|
1041
|
+
enabled: bool | None = None,
|
|
859
1042
|
) -> Callable[[AnyFunction], FunctionPrompt] | FunctionPrompt:
|
|
860
1043
|
"""Decorator to register a prompt.
|
|
861
1044
|
|
|
@@ -872,9 +1055,10 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
872
1055
|
|
|
873
1056
|
Args:
|
|
874
1057
|
name_or_fn: Either a function (when used as @prompt), a string name, or None
|
|
1058
|
+
name: Optional name for the prompt (keyword-only, alternative to name_or_fn)
|
|
875
1059
|
description: Optional description of what the prompt does
|
|
876
1060
|
tags: Optional set of tags for categorizing the prompt
|
|
877
|
-
|
|
1061
|
+
enabled: Optional boolean to enable or disable the prompt
|
|
878
1062
|
|
|
879
1063
|
Example:
|
|
880
1064
|
@server.prompt
|
|
@@ -947,6 +1131,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
947
1131
|
name=prompt_name,
|
|
948
1132
|
description=description,
|
|
949
1133
|
tags=tags,
|
|
1134
|
+
enabled=enabled,
|
|
950
1135
|
)
|
|
951
1136
|
self.add_prompt(prompt)
|
|
952
1137
|
|
|
@@ -974,6 +1159,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
974
1159
|
name=prompt_name,
|
|
975
1160
|
description=description,
|
|
976
1161
|
tags=tags,
|
|
1162
|
+
enabled=enabled,
|
|
977
1163
|
)
|
|
978
1164
|
|
|
979
1165
|
async def run_stdio_async(self) -> None:
|
|
@@ -1008,9 +1194,11 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
1008
1194
|
path: Path for the endpoint (defaults to settings.streamable_http_path or settings.sse_path)
|
|
1009
1195
|
uvicorn_config: Additional configuration for the Uvicorn server
|
|
1010
1196
|
"""
|
|
1011
|
-
host = host or self.
|
|
1012
|
-
port = port or self.
|
|
1013
|
-
default_log_level_to_use = (
|
|
1197
|
+
host = host or self._deprecated_settings.host
|
|
1198
|
+
port = port or self._deprecated_settings.port
|
|
1199
|
+
default_log_level_to_use = (
|
|
1200
|
+
log_level or self._deprecated_settings.log_level
|
|
1201
|
+
).lower()
|
|
1014
1202
|
|
|
1015
1203
|
app = self.http_app(path=path, transport=transport, middleware=middleware)
|
|
1016
1204
|
|
|
@@ -1039,19 +1227,19 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
1039
1227
|
port: int | None = None,
|
|
1040
1228
|
log_level: str | None = None,
|
|
1041
1229
|
path: str | None = None,
|
|
1042
|
-
message_path: str | None = None,
|
|
1043
1230
|
uvicorn_config: dict[str, Any] | None = None,
|
|
1044
1231
|
) -> None:
|
|
1045
1232
|
"""Run the server using SSE transport."""
|
|
1046
1233
|
|
|
1047
1234
|
# Deprecated since 2.3.2
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1235
|
+
if fastmcp.settings.deprecation_warnings:
|
|
1236
|
+
warnings.warn(
|
|
1237
|
+
"The run_sse_async method is deprecated (as of 2.3.2). Use run_http_async for a "
|
|
1238
|
+
"modern (non-SSE) alternative, or create an SSE app with "
|
|
1239
|
+
"`fastmcp.server.http.create_sse_app` and run it directly.",
|
|
1240
|
+
DeprecationWarning,
|
|
1241
|
+
stacklevel=2,
|
|
1242
|
+
)
|
|
1055
1243
|
await self.run_http_async(
|
|
1056
1244
|
transport="sse",
|
|
1057
1245
|
host=host,
|
|
@@ -1076,18 +1264,19 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
1076
1264
|
middleware: A list of middleware to apply to the app
|
|
1077
1265
|
"""
|
|
1078
1266
|
# Deprecated since 2.3.2
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1267
|
+
if fastmcp.settings.deprecation_warnings:
|
|
1268
|
+
warnings.warn(
|
|
1269
|
+
"The sse_app method is deprecated (as of 2.3.2). Use http_app as a modern (non-SSE) "
|
|
1270
|
+
"alternative, or call `fastmcp.server.http.create_sse_app` directly.",
|
|
1271
|
+
DeprecationWarning,
|
|
1272
|
+
stacklevel=2,
|
|
1273
|
+
)
|
|
1085
1274
|
return create_sse_app(
|
|
1086
1275
|
server=self,
|
|
1087
|
-
message_path=message_path or self.
|
|
1088
|
-
sse_path=path or self.
|
|
1276
|
+
message_path=message_path or self._deprecated_settings.message_path,
|
|
1277
|
+
sse_path=path or self._deprecated_settings.sse_path,
|
|
1089
1278
|
auth=self.auth,
|
|
1090
|
-
debug=self.
|
|
1279
|
+
debug=self._deprecated_settings.debug,
|
|
1091
1280
|
middleware=middleware,
|
|
1092
1281
|
)
|
|
1093
1282
|
|
|
@@ -1104,17 +1293,20 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
1104
1293
|
middleware: A list of middleware to apply to the app
|
|
1105
1294
|
"""
|
|
1106
1295
|
# Deprecated since 2.3.2
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1296
|
+
if fastmcp.settings.deprecation_warnings:
|
|
1297
|
+
warnings.warn(
|
|
1298
|
+
"The streamable_http_app method is deprecated (as of 2.3.2). Use http_app() instead.",
|
|
1299
|
+
DeprecationWarning,
|
|
1300
|
+
stacklevel=2,
|
|
1301
|
+
)
|
|
1112
1302
|
return self.http_app(path=path, middleware=middleware)
|
|
1113
1303
|
|
|
1114
1304
|
def http_app(
|
|
1115
1305
|
self,
|
|
1116
1306
|
path: str | None = None,
|
|
1117
1307
|
middleware: list[Middleware] | None = None,
|
|
1308
|
+
json_response: bool | None = None,
|
|
1309
|
+
stateless_http: bool | None = None,
|
|
1118
1310
|
transport: Literal["streamable-http", "sse"] = "streamable-http",
|
|
1119
1311
|
) -> StarletteWithLifespan:
|
|
1120
1312
|
"""Create a Starlette app using the specified HTTP transport.
|
|
@@ -1131,21 +1323,30 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
1131
1323
|
if transport == "streamable-http":
|
|
1132
1324
|
return create_streamable_http_app(
|
|
1133
1325
|
server=self,
|
|
1134
|
-
streamable_http_path=path
|
|
1326
|
+
streamable_http_path=path
|
|
1327
|
+
or self._deprecated_settings.streamable_http_path,
|
|
1135
1328
|
event_store=None,
|
|
1136
1329
|
auth=self.auth,
|
|
1137
|
-
json_response=
|
|
1138
|
-
|
|
1139
|
-
|
|
1330
|
+
json_response=(
|
|
1331
|
+
json_response
|
|
1332
|
+
if json_response is not None
|
|
1333
|
+
else self._deprecated_settings.json_response
|
|
1334
|
+
),
|
|
1335
|
+
stateless_http=(
|
|
1336
|
+
stateless_http
|
|
1337
|
+
if stateless_http is not None
|
|
1338
|
+
else self._deprecated_settings.stateless_http
|
|
1339
|
+
),
|
|
1340
|
+
debug=self._deprecated_settings.debug,
|
|
1140
1341
|
middleware=middleware,
|
|
1141
1342
|
)
|
|
1142
1343
|
elif transport == "sse":
|
|
1143
1344
|
return create_sse_app(
|
|
1144
1345
|
server=self,
|
|
1145
|
-
message_path=self.
|
|
1146
|
-
sse_path=path or self.
|
|
1346
|
+
message_path=self._deprecated_settings.message_path,
|
|
1347
|
+
sse_path=path or self._deprecated_settings.sse_path,
|
|
1147
1348
|
auth=self.auth,
|
|
1148
|
-
debug=self.
|
|
1349
|
+
debug=self._deprecated_settings.debug,
|
|
1149
1350
|
middleware=middleware,
|
|
1150
1351
|
)
|
|
1151
1352
|
|
|
@@ -1158,12 +1359,13 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
1158
1359
|
uvicorn_config: dict[str, Any] | None = None,
|
|
1159
1360
|
) -> None:
|
|
1160
1361
|
# Deprecated since 2.3.2
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1362
|
+
if fastmcp.settings.deprecation_warnings:
|
|
1363
|
+
warnings.warn(
|
|
1364
|
+
"The run_streamable_http_async method is deprecated (as of 2.3.2). "
|
|
1365
|
+
"Use run_http_async instead.",
|
|
1366
|
+
DeprecationWarning,
|
|
1367
|
+
stacklevel=2,
|
|
1368
|
+
)
|
|
1167
1369
|
await self.run_http_async(
|
|
1168
1370
|
transport="streamable-http",
|
|
1169
1371
|
host=host,
|
|
@@ -1231,30 +1433,33 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
1231
1433
|
|
|
1232
1434
|
if tool_separator is not None:
|
|
1233
1435
|
# Deprecated since 2.4.0
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1436
|
+
if fastmcp.settings.deprecation_warnings:
|
|
1437
|
+
warnings.warn(
|
|
1438
|
+
"The tool_separator parameter is deprecated and will be removed in a future version. "
|
|
1439
|
+
"Tools are now prefixed using 'prefix_toolname' format.",
|
|
1440
|
+
DeprecationWarning,
|
|
1441
|
+
stacklevel=2,
|
|
1442
|
+
)
|
|
1240
1443
|
|
|
1241
1444
|
if resource_separator is not None:
|
|
1242
1445
|
# Deprecated since 2.4.0
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1446
|
+
if fastmcp.settings.deprecation_warnings:
|
|
1447
|
+
warnings.warn(
|
|
1448
|
+
"The resource_separator parameter is deprecated and ignored. "
|
|
1449
|
+
"Resource prefixes are now added using the protocol://prefix/path format.",
|
|
1450
|
+
DeprecationWarning,
|
|
1451
|
+
stacklevel=2,
|
|
1452
|
+
)
|
|
1249
1453
|
|
|
1250
1454
|
if prompt_separator is not None:
|
|
1251
1455
|
# Deprecated since 2.4.0
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1456
|
+
if fastmcp.settings.deprecation_warnings:
|
|
1457
|
+
warnings.warn(
|
|
1458
|
+
"The prompt_separator parameter is deprecated and will be removed in a future version. "
|
|
1459
|
+
"Prompts are now prefixed using 'prefix_promptname' format.",
|
|
1460
|
+
DeprecationWarning,
|
|
1461
|
+
stacklevel=2,
|
|
1462
|
+
)
|
|
1258
1463
|
|
|
1259
1464
|
# if as_proxy is not specified and the server has a custom lifespan,
|
|
1260
1465
|
# we should treat it as a proxy
|
|
@@ -1316,30 +1521,33 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
1316
1521
|
"""
|
|
1317
1522
|
if tool_separator is not None:
|
|
1318
1523
|
# Deprecated since 2.4.0
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1524
|
+
if fastmcp.settings.deprecation_warnings:
|
|
1525
|
+
warnings.warn(
|
|
1526
|
+
"The tool_separator parameter is deprecated and will be removed in a future version. "
|
|
1527
|
+
"Tools are now prefixed using 'prefix_toolname' format.",
|
|
1528
|
+
DeprecationWarning,
|
|
1529
|
+
stacklevel=2,
|
|
1530
|
+
)
|
|
1325
1531
|
|
|
1326
1532
|
if resource_separator is not None:
|
|
1327
1533
|
# Deprecated since 2.4.0
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1534
|
+
if fastmcp.settings.deprecation_warnings:
|
|
1535
|
+
warnings.warn(
|
|
1536
|
+
"The resource_separator parameter is deprecated and ignored. "
|
|
1537
|
+
"Resource prefixes are now added using the protocol://prefix/path format.",
|
|
1538
|
+
DeprecationWarning,
|
|
1539
|
+
stacklevel=2,
|
|
1540
|
+
)
|
|
1334
1541
|
|
|
1335
1542
|
if prompt_separator is not None:
|
|
1336
1543
|
# Deprecated since 2.4.0
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1544
|
+
if fastmcp.settings.deprecation_warnings:
|
|
1545
|
+
warnings.warn(
|
|
1546
|
+
"The prompt_separator parameter is deprecated and will be removed in a future version. "
|
|
1547
|
+
"Prompts are now prefixed using 'prefix_promptname' format.",
|
|
1548
|
+
DeprecationWarning,
|
|
1549
|
+
stacklevel=2,
|
|
1550
|
+
)
|
|
1343
1551
|
|
|
1344
1552
|
# Import tools from the mounted server
|
|
1345
1553
|
tool_prefix = f"{prefix}_"
|
|
@@ -1376,28 +1584,13 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
1376
1584
|
route_map_fn: OpenAPIRouteMapFn | None = None,
|
|
1377
1585
|
mcp_component_fn: OpenAPIComponentFn | None = None,
|
|
1378
1586
|
mcp_names: dict[str, str] | None = None,
|
|
1379
|
-
|
|
1587
|
+
tags: set[str] | None = None,
|
|
1380
1588
|
**settings: Any,
|
|
1381
1589
|
) -> FastMCPOpenAPI:
|
|
1382
1590
|
"""
|
|
1383
1591
|
Create a FastMCP server from an OpenAPI specification.
|
|
1384
1592
|
"""
|
|
1385
|
-
from .openapi import FastMCPOpenAPI
|
|
1386
|
-
|
|
1387
|
-
# Deprecated since 2.5.0
|
|
1388
|
-
if all_routes_as_tools:
|
|
1389
|
-
warnings.warn(
|
|
1390
|
-
"The 'all_routes_as_tools' parameter is deprecated and will be removed in a future version. "
|
|
1391
|
-
'Use \'route_maps=[RouteMap(methods="*", pattern=r".*", mcp_type=MCPType.TOOL)]\' instead.',
|
|
1392
|
-
DeprecationWarning,
|
|
1393
|
-
stacklevel=2,
|
|
1394
|
-
)
|
|
1395
|
-
|
|
1396
|
-
if all_routes_as_tools and route_maps:
|
|
1397
|
-
raise ValueError("Cannot specify both all_routes_as_tools and route_maps")
|
|
1398
|
-
|
|
1399
|
-
elif all_routes_as_tools:
|
|
1400
|
-
route_maps = [RouteMap(methods="*", pattern=r".*", mcp_type=MCPType.TOOL)]
|
|
1593
|
+
from .openapi import FastMCPOpenAPI
|
|
1401
1594
|
|
|
1402
1595
|
return FastMCPOpenAPI(
|
|
1403
1596
|
openapi_spec=openapi_spec,
|
|
@@ -1406,6 +1599,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
1406
1599
|
route_map_fn=route_map_fn,
|
|
1407
1600
|
mcp_component_fn=mcp_component_fn,
|
|
1408
1601
|
mcp_names=mcp_names,
|
|
1602
|
+
tags=tags,
|
|
1409
1603
|
**settings,
|
|
1410
1604
|
)
|
|
1411
1605
|
|
|
@@ -1418,30 +1612,15 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
1418
1612
|
route_map_fn: OpenAPIRouteMapFn | None = None,
|
|
1419
1613
|
mcp_component_fn: OpenAPIComponentFn | None = None,
|
|
1420
1614
|
mcp_names: dict[str, str] | None = None,
|
|
1421
|
-
all_routes_as_tools: bool = False,
|
|
1422
1615
|
httpx_client_kwargs: dict[str, Any] | None = None,
|
|
1616
|
+
tags: set[str] | None = None,
|
|
1423
1617
|
**settings: Any,
|
|
1424
1618
|
) -> FastMCPOpenAPI:
|
|
1425
1619
|
"""
|
|
1426
1620
|
Create a FastMCP server from a FastAPI application.
|
|
1427
1621
|
"""
|
|
1428
1622
|
|
|
1429
|
-
from .openapi import FastMCPOpenAPI
|
|
1430
|
-
|
|
1431
|
-
# Deprecated since 2.5.0
|
|
1432
|
-
if all_routes_as_tools:
|
|
1433
|
-
warnings.warn(
|
|
1434
|
-
"The 'all_routes_as_tools' parameter is deprecated and will be removed in a future version. "
|
|
1435
|
-
'Use \'route_maps=[RouteMap(methods="*", pattern=r".*", mcp_type=MCPType.TOOL)]\' instead.',
|
|
1436
|
-
DeprecationWarning,
|
|
1437
|
-
stacklevel=2,
|
|
1438
|
-
)
|
|
1439
|
-
|
|
1440
|
-
if all_routes_as_tools and route_maps:
|
|
1441
|
-
raise ValueError("Cannot specify both all_routes_as_tools and route_maps")
|
|
1442
|
-
|
|
1443
|
-
elif all_routes_as_tools:
|
|
1444
|
-
route_maps = [RouteMap(methods="*", pattern=r".*", mcp_type=MCPType.TOOL)]
|
|
1623
|
+
from .openapi import FastMCPOpenAPI
|
|
1445
1624
|
|
|
1446
1625
|
if httpx_client_kwargs is None:
|
|
1447
1626
|
httpx_client_kwargs = {}
|
|
@@ -1462,6 +1641,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
1462
1641
|
route_map_fn=route_map_fn,
|
|
1463
1642
|
mcp_component_fn=mcp_component_fn,
|
|
1464
1643
|
mcp_names=mcp_names,
|
|
1644
|
+
tags=tags,
|
|
1465
1645
|
**settings,
|
|
1466
1646
|
)
|
|
1467
1647
|
|
|
@@ -1503,14 +1683,50 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
1503
1683
|
Create a FastMCP proxy server from a FastMCP client.
|
|
1504
1684
|
"""
|
|
1505
1685
|
# Deprecated since 2.3.5
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1686
|
+
if fastmcp.settings.deprecation_warnings:
|
|
1687
|
+
warnings.warn(
|
|
1688
|
+
"FastMCP.from_client() is deprecated; use FastMCP.as_proxy() instead.",
|
|
1689
|
+
DeprecationWarning,
|
|
1690
|
+
stacklevel=2,
|
|
1691
|
+
)
|
|
1511
1692
|
|
|
1512
1693
|
return cls.as_proxy(client, **settings)
|
|
1513
1694
|
|
|
1695
|
+
def _should_enable_component(
|
|
1696
|
+
self,
|
|
1697
|
+
component: FastMCPComponent,
|
|
1698
|
+
) -> bool:
|
|
1699
|
+
"""
|
|
1700
|
+
Given a component, determine if it should be enabled. Returns True if it should be enabled; False if it should not.
|
|
1701
|
+
|
|
1702
|
+
Rules:
|
|
1703
|
+
• If the component's enabled property is False, always return False.
|
|
1704
|
+
• If both include_tags and exclude_tags are None, return True.
|
|
1705
|
+
• If exclude_tags is provided, check each exclude tag:
|
|
1706
|
+
- If the exclude tag is a string, it must be present in the input tags to exclude.
|
|
1707
|
+
• If include_tags is provided, check each include tag:
|
|
1708
|
+
- If the include tag is a string, it must be present in the input tags to include.
|
|
1709
|
+
• If include_tags is provided and none of the include tags match, return False.
|
|
1710
|
+
• If include_tags is not provided, return True.
|
|
1711
|
+
"""
|
|
1712
|
+
if not component.enabled:
|
|
1713
|
+
return False
|
|
1714
|
+
|
|
1715
|
+
if self.include_tags is None and self.exclude_tags is None:
|
|
1716
|
+
return True
|
|
1717
|
+
|
|
1718
|
+
if self.exclude_tags is not None:
|
|
1719
|
+
if any(etag in component.tags for etag in self.exclude_tags):
|
|
1720
|
+
return False
|
|
1721
|
+
|
|
1722
|
+
if self.include_tags is not None:
|
|
1723
|
+
if any(itag in component.tags for itag in self.include_tags):
|
|
1724
|
+
return True
|
|
1725
|
+
else:
|
|
1726
|
+
return False
|
|
1727
|
+
|
|
1728
|
+
return True
|
|
1729
|
+
|
|
1514
1730
|
|
|
1515
1731
|
class MountedServer:
|
|
1516
1732
|
def __init__(
|
|
@@ -1597,7 +1813,7 @@ def add_resource_prefix(
|
|
|
1597
1813
|
# Get the server settings to check for legacy format preference
|
|
1598
1814
|
|
|
1599
1815
|
if prefix_format is None:
|
|
1600
|
-
prefix_format = fastmcp.settings.
|
|
1816
|
+
prefix_format = fastmcp.settings.resource_prefix_format
|
|
1601
1817
|
|
|
1602
1818
|
if prefix_format == "protocol":
|
|
1603
1819
|
# Legacy style: prefix+protocol://path
|
|
@@ -1646,7 +1862,7 @@ def remove_resource_prefix(
|
|
|
1646
1862
|
return uri
|
|
1647
1863
|
|
|
1648
1864
|
if prefix_format is None:
|
|
1649
|
-
prefix_format = fastmcp.settings.
|
|
1865
|
+
prefix_format = fastmcp.settings.resource_prefix_format
|
|
1650
1866
|
|
|
1651
1867
|
if prefix_format == "protocol":
|
|
1652
1868
|
# Legacy style: prefix+protocol://path
|
|
@@ -1706,7 +1922,7 @@ def has_resource_prefix(
|
|
|
1706
1922
|
# Get the server settings to check for legacy format preference
|
|
1707
1923
|
|
|
1708
1924
|
if prefix_format is None:
|
|
1709
|
-
prefix_format = fastmcp.settings.
|
|
1925
|
+
prefix_format = fastmcp.settings.resource_prefix_format
|
|
1710
1926
|
|
|
1711
1927
|
if prefix_format == "protocol":
|
|
1712
1928
|
# Legacy style: prefix+protocol://path
|