fastmcp 2.6.1__py3-none-any.whl → 2.7.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/cli/cli.py +10 -2
- fastmcp/cli/run.py +32 -1
- fastmcp/client/transports.py +21 -13
- fastmcp/contrib/bulk_tool_caller/example.py +1 -1
- fastmcp/contrib/mcp_mixin/mcp_mixin.py +10 -3
- fastmcp/prompts/prompt.py +68 -30
- fastmcp/prompts/prompt_manager.py +13 -6
- fastmcp/resources/__init__.py +1 -2
- fastmcp/resources/resource.py +92 -6
- fastmcp/resources/resource_manager.py +17 -6
- fastmcp/resources/template.py +90 -56
- fastmcp/resources/types.py +0 -44
- fastmcp/server/context.py +1 -1
- fastmcp/server/dependencies.py +1 -0
- fastmcp/server/http.py +2 -1
- fastmcp/server/openapi.py +17 -32
- fastmcp/server/proxy.py +5 -8
- fastmcp/server/server.py +280 -95
- fastmcp/settings.py +1 -1
- fastmcp/tools/__init__.py +2 -2
- fastmcp/tools/tool.py +62 -22
- fastmcp/tools/tool_manager.py +9 -3
- fastmcp/utilities/mcp_config.py +17 -7
- fastmcp/utilities/openapi.py +56 -32
- fastmcp/utilities/types.py +7 -1
- {fastmcp-2.6.1.dist-info → fastmcp-2.7.1.dist-info}/METADATA +10 -9
- {fastmcp-2.6.1.dist-info → fastmcp-2.7.1.dist-info}/RECORD +30 -31
- fastmcp/utilities/decorators.py +0 -101
- {fastmcp-2.6.1.dist-info → fastmcp-2.7.1.dist-info}/WHEEL +0 -0
- {fastmcp-2.6.1.dist-info → fastmcp-2.7.1.dist-info}/entry_points.txt +0 -0
- {fastmcp-2.6.1.dist-info → fastmcp-2.7.1.dist-info}/licenses/LICENSE +0 -0
fastmcp/server/server.py
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
import datetime
|
|
6
|
+
import inspect
|
|
6
7
|
import re
|
|
7
8
|
import warnings
|
|
8
9
|
from collections.abc import AsyncIterator, Awaitable, Callable
|
|
@@ -13,7 +14,7 @@ from contextlib import (
|
|
|
13
14
|
)
|
|
14
15
|
from functools import partial
|
|
15
16
|
from pathlib import Path
|
|
16
|
-
from typing import TYPE_CHECKING, Any, Generic, Literal
|
|
17
|
+
from typing import TYPE_CHECKING, Any, Generic, Literal, overload
|
|
17
18
|
|
|
18
19
|
import anyio
|
|
19
20
|
import httpx
|
|
@@ -40,11 +41,12 @@ from starlette.requests import Request
|
|
|
40
41
|
from starlette.responses import Response
|
|
41
42
|
from starlette.routing import BaseRoute, Route
|
|
42
43
|
|
|
44
|
+
import fastmcp
|
|
43
45
|
import fastmcp.server
|
|
44
46
|
import fastmcp.settings
|
|
45
47
|
from fastmcp.exceptions import NotFoundError
|
|
46
48
|
from fastmcp.prompts import Prompt, PromptManager
|
|
47
|
-
from fastmcp.prompts.prompt import
|
|
49
|
+
from fastmcp.prompts.prompt import FunctionPrompt
|
|
48
50
|
from fastmcp.resources import Resource, ResourceManager
|
|
49
51
|
from fastmcp.resources.template import ResourceTemplate
|
|
50
52
|
from fastmcp.server.auth.auth import OAuthProvider
|
|
@@ -55,9 +57,8 @@ from fastmcp.server.http import (
|
|
|
55
57
|
create_streamable_http_app,
|
|
56
58
|
)
|
|
57
59
|
from fastmcp.tools import ToolManager
|
|
58
|
-
from fastmcp.tools.tool import Tool
|
|
60
|
+
from fastmcp.tools.tool import FunctionTool, Tool
|
|
59
61
|
from fastmcp.utilities.cache import TimedCache
|
|
60
|
-
from fastmcp.utilities.decorators import DecoratedFunction
|
|
61
62
|
from fastmcp.utilities.logging import get_logger
|
|
62
63
|
from fastmcp.utilities.mcp_config import MCPConfig
|
|
63
64
|
|
|
@@ -131,6 +132,8 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
131
132
|
tools: list[Tool | Callable[..., Any]] | None = None,
|
|
132
133
|
**settings: Any,
|
|
133
134
|
):
|
|
135
|
+
if cache_expiration_seconds is not None:
|
|
136
|
+
settings["cache_expiration_seconds"] = cache_expiration_seconds
|
|
134
137
|
self.settings = fastmcp.settings.ServerSettings(**settings)
|
|
135
138
|
|
|
136
139
|
# If mask_error_details is provided, override the settings value
|
|
@@ -148,13 +151,14 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
148
151
|
self.tags: set[str] = tags or set()
|
|
149
152
|
self.dependencies = dependencies
|
|
150
153
|
self._cache = TimedCache(
|
|
151
|
-
expiration=datetime.timedelta(
|
|
154
|
+
expiration=datetime.timedelta(
|
|
155
|
+
seconds=self.settings.cache_expiration_seconds
|
|
156
|
+
)
|
|
152
157
|
)
|
|
153
158
|
self._mounted_servers: dict[str, MountedServer] = {}
|
|
154
159
|
self._additional_http_routes: list[BaseRoute] = []
|
|
155
160
|
self._tool_manager = ToolManager(
|
|
156
161
|
duplicate_behavior=on_duplicate_tools,
|
|
157
|
-
serializer=tool_serializer,
|
|
158
162
|
mask_error_details=self.settings.mask_error_details,
|
|
159
163
|
)
|
|
160
164
|
self._resource_manager = ResourceManager(
|
|
@@ -165,6 +169,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
165
169
|
duplicate_behavior=on_duplicate_prompts,
|
|
166
170
|
mask_error_details=self.settings.mask_error_details,
|
|
167
171
|
)
|
|
172
|
+
self._tool_serializer = tool_serializer
|
|
168
173
|
|
|
169
174
|
if lifespan is None:
|
|
170
175
|
self._has_lifespan = False
|
|
@@ -183,10 +188,9 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
183
188
|
|
|
184
189
|
if tools:
|
|
185
190
|
for tool in tools:
|
|
186
|
-
if isinstance(tool, Tool):
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
self.add_tool(tool)
|
|
191
|
+
if not isinstance(tool, Tool):
|
|
192
|
+
tool = Tool.from_function(tool, serializer=self._tool_serializer)
|
|
193
|
+
self.add_tool(tool)
|
|
190
194
|
|
|
191
195
|
# Set up MCP protocol handlers
|
|
192
196
|
self._setup_handlers()
|
|
@@ -349,18 +353,18 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
349
353
|
"""
|
|
350
354
|
|
|
351
355
|
def decorator(
|
|
352
|
-
|
|
356
|
+
fn: Callable[[Request], Awaitable[Response]],
|
|
353
357
|
) -> Callable[[Request], Awaitable[Response]]:
|
|
354
358
|
self._additional_http_routes.append(
|
|
355
359
|
Route(
|
|
356
360
|
path,
|
|
357
|
-
endpoint=
|
|
361
|
+
endpoint=fn,
|
|
358
362
|
methods=methods,
|
|
359
363
|
name=name,
|
|
360
364
|
include_in_schema=include_in_schema,
|
|
361
365
|
)
|
|
362
366
|
)
|
|
363
|
-
return
|
|
367
|
+
return fn
|
|
364
368
|
|
|
365
369
|
return decorator
|
|
366
370
|
|
|
@@ -484,38 +488,16 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
484
488
|
|
|
485
489
|
raise NotFoundError(f"Unknown prompt: {name}")
|
|
486
490
|
|
|
487
|
-
def add_tool(
|
|
488
|
-
self,
|
|
489
|
-
fn: AnyFunction,
|
|
490
|
-
name: str | None = None,
|
|
491
|
-
description: str | None = None,
|
|
492
|
-
tags: set[str] | None = None,
|
|
493
|
-
annotations: ToolAnnotations | dict[str, Any] | None = None,
|
|
494
|
-
exclude_args: list[str] | None = None,
|
|
495
|
-
) -> None:
|
|
491
|
+
def add_tool(self, tool: Tool) -> None:
|
|
496
492
|
"""Add a tool to the server.
|
|
497
493
|
|
|
498
494
|
The tool function can optionally request a Context object by adding a parameter
|
|
499
495
|
with the Context type annotation. See the @tool decorator for examples.
|
|
500
496
|
|
|
501
497
|
Args:
|
|
502
|
-
|
|
503
|
-
name: Optional name for the tool (defaults to function name)
|
|
504
|
-
description: Optional description of what the tool does
|
|
505
|
-
tags: Optional set of tags for categorizing the tool
|
|
506
|
-
annotations: Optional annotations about the tool's behavior
|
|
498
|
+
tool: The Tool instance to register
|
|
507
499
|
"""
|
|
508
|
-
|
|
509
|
-
annotations = ToolAnnotations(**annotations)
|
|
510
|
-
|
|
511
|
-
self._tool_manager.add_tool_from_fn(
|
|
512
|
-
fn,
|
|
513
|
-
name=name,
|
|
514
|
-
description=description,
|
|
515
|
-
tags=tags,
|
|
516
|
-
annotations=annotations,
|
|
517
|
-
exclude_args=exclude_args,
|
|
518
|
-
)
|
|
500
|
+
self._tool_manager.add_tool(tool)
|
|
519
501
|
self._cache.clear()
|
|
520
502
|
|
|
521
503
|
def remove_tool(self, name: str) -> None:
|
|
@@ -530,61 +512,141 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
530
512
|
self._tool_manager.remove_tool(name)
|
|
531
513
|
self._cache.clear()
|
|
532
514
|
|
|
515
|
+
@overload
|
|
516
|
+
def tool(
|
|
517
|
+
self,
|
|
518
|
+
name_or_fn: AnyFunction,
|
|
519
|
+
*,
|
|
520
|
+
name: str | None = None,
|
|
521
|
+
description: str | None = None,
|
|
522
|
+
tags: set[str] | None = None,
|
|
523
|
+
annotations: ToolAnnotations | dict[str, Any] | None = None,
|
|
524
|
+
exclude_args: list[str] | None = None,
|
|
525
|
+
) -> FunctionTool: ...
|
|
526
|
+
|
|
527
|
+
@overload
|
|
528
|
+
def tool(
|
|
529
|
+
self,
|
|
530
|
+
name_or_fn: str | None = None,
|
|
531
|
+
*,
|
|
532
|
+
name: str | None = None,
|
|
533
|
+
description: str | None = None,
|
|
534
|
+
tags: set[str] | None = None,
|
|
535
|
+
annotations: ToolAnnotations | dict[str, Any] | None = None,
|
|
536
|
+
exclude_args: list[str] | None = None,
|
|
537
|
+
) -> Callable[[AnyFunction], FunctionTool]: ...
|
|
538
|
+
|
|
533
539
|
def tool(
|
|
534
540
|
self,
|
|
541
|
+
name_or_fn: str | AnyFunction | None = None,
|
|
542
|
+
*,
|
|
535
543
|
name: str | None = None,
|
|
536
544
|
description: str | None = None,
|
|
537
545
|
tags: set[str] | None = None,
|
|
538
546
|
annotations: ToolAnnotations | dict[str, Any] | None = None,
|
|
539
547
|
exclude_args: list[str] | None = None,
|
|
540
|
-
) -> Callable[[AnyFunction],
|
|
548
|
+
) -> Callable[[AnyFunction], FunctionTool] | FunctionTool:
|
|
541
549
|
"""Decorator to register a tool.
|
|
542
550
|
|
|
543
551
|
Tools can optionally request a Context object by adding a parameter with the
|
|
544
552
|
Context type annotation. The context provides access to MCP capabilities like
|
|
545
553
|
logging, progress reporting, and resource access.
|
|
546
554
|
|
|
555
|
+
This decorator supports multiple calling patterns:
|
|
556
|
+
- @server.tool (without parentheses)
|
|
557
|
+
- @server.tool (with empty parentheses)
|
|
558
|
+
- @server.tool("custom_name") (with name as first argument)
|
|
559
|
+
- @server.tool(name="custom_name") (with name as keyword argument)
|
|
560
|
+
- server.tool(function, name="custom_name") (direct function call)
|
|
561
|
+
|
|
547
562
|
Args:
|
|
548
|
-
|
|
563
|
+
name_or_fn: Either a function (when used as @tool), a string name, or None
|
|
549
564
|
description: Optional description of what the tool does
|
|
550
565
|
tags: Optional set of tags for categorizing the tool
|
|
551
566
|
annotations: Optional annotations about the tool's behavior
|
|
567
|
+
exclude_args: Optional list of argument names to exclude from the tool schema
|
|
568
|
+
name: Optional name for the tool (keyword-only, alternative to name_or_fn)
|
|
552
569
|
|
|
553
570
|
Example:
|
|
554
|
-
@server.tool
|
|
571
|
+
@server.tool
|
|
572
|
+
def my_tool(x: int) -> str:
|
|
573
|
+
return str(x)
|
|
574
|
+
|
|
575
|
+
@server.tool
|
|
555
576
|
def my_tool(x: int) -> str:
|
|
556
577
|
return str(x)
|
|
557
578
|
|
|
558
|
-
@server.tool()
|
|
559
|
-
def
|
|
560
|
-
ctx.info(f"Processing {x}")
|
|
579
|
+
@server.tool("custom_name")
|
|
580
|
+
def my_tool(x: int) -> str:
|
|
561
581
|
return str(x)
|
|
562
582
|
|
|
563
|
-
@server.tool()
|
|
564
|
-
|
|
565
|
-
await context.report_progress(50, 100)
|
|
583
|
+
@server.tool(name="custom_name")
|
|
584
|
+
def my_tool(x: int) -> str:
|
|
566
585
|
return str(x)
|
|
586
|
+
|
|
587
|
+
# Direct function call
|
|
588
|
+
server.tool(my_function, name="custom_name")
|
|
567
589
|
"""
|
|
590
|
+
if isinstance(annotations, dict):
|
|
591
|
+
annotations = ToolAnnotations(**annotations)
|
|
568
592
|
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
593
|
+
if isinstance(name_or_fn, classmethod):
|
|
594
|
+
raise ValueError(
|
|
595
|
+
inspect.cleandoc(
|
|
596
|
+
"""
|
|
597
|
+
To decorate a classmethod, first define the method and then call
|
|
598
|
+
tool() directly on the method instead of using it as a
|
|
599
|
+
decorator. See https://gofastmcp.com/patterns/decorating-methods
|
|
600
|
+
for examples and more information.
|
|
601
|
+
"""
|
|
602
|
+
)
|
|
574
603
|
)
|
|
575
604
|
|
|
576
|
-
|
|
577
|
-
|
|
605
|
+
# Determine the actual name and function based on the calling pattern
|
|
606
|
+
if inspect.isroutine(name_or_fn):
|
|
607
|
+
# Case 1: @tool (without parens) - function passed directly
|
|
608
|
+
# Case 2: direct call like tool(fn, name="something")
|
|
609
|
+
fn = name_or_fn
|
|
610
|
+
tool_name = name # Use keyword name if provided, otherwise None
|
|
611
|
+
|
|
612
|
+
# Register the tool immediately and return the tool object
|
|
613
|
+
tool = Tool.from_function(
|
|
578
614
|
fn,
|
|
579
|
-
name=
|
|
615
|
+
name=tool_name,
|
|
580
616
|
description=description,
|
|
581
617
|
tags=tags,
|
|
582
618
|
annotations=annotations,
|
|
583
619
|
exclude_args=exclude_args,
|
|
620
|
+
serializer=self._tool_serializer,
|
|
621
|
+
)
|
|
622
|
+
self.add_tool(tool)
|
|
623
|
+
return tool
|
|
624
|
+
|
|
625
|
+
elif isinstance(name_or_fn, str):
|
|
626
|
+
# Case 3: @tool("custom_name") - name passed as first argument
|
|
627
|
+
if name is not None:
|
|
628
|
+
raise TypeError(
|
|
629
|
+
"Cannot specify both a name as first argument and as keyword argument. "
|
|
630
|
+
f"Use either @tool('{name_or_fn}') or @tool(name='{name}'), not both."
|
|
631
|
+
)
|
|
632
|
+
tool_name = name_or_fn
|
|
633
|
+
elif name_or_fn is None:
|
|
634
|
+
# Case 4: @tool or @tool(name="something") - use keyword name
|
|
635
|
+
tool_name = name
|
|
636
|
+
else:
|
|
637
|
+
raise TypeError(
|
|
638
|
+
f"First argument to @tool must be a function, string, or None, got {type(name_or_fn)}"
|
|
584
639
|
)
|
|
585
|
-
return fn
|
|
586
640
|
|
|
587
|
-
|
|
641
|
+
# Return partial for cases where we need to wait for the function
|
|
642
|
+
return partial(
|
|
643
|
+
self.tool,
|
|
644
|
+
name=tool_name,
|
|
645
|
+
description=description,
|
|
646
|
+
tags=tags,
|
|
647
|
+
annotations=annotations,
|
|
648
|
+
exclude_args=exclude_args,
|
|
649
|
+
)
|
|
588
650
|
|
|
589
651
|
def add_resource(self, resource: Resource, key: str | None = None) -> None:
|
|
590
652
|
"""Add a resource to the server.
|
|
@@ -596,6 +658,14 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
596
658
|
self._resource_manager.add_resource(resource, key=key)
|
|
597
659
|
self._cache.clear()
|
|
598
660
|
|
|
661
|
+
def add_template(self, template: ResourceTemplate, key: str | None = None) -> None:
|
|
662
|
+
"""Add a resource template to the server.
|
|
663
|
+
|
|
664
|
+
Args:
|
|
665
|
+
template: A ResourceTemplate instance to add
|
|
666
|
+
"""
|
|
667
|
+
self._resource_manager.add_template(template, key=key)
|
|
668
|
+
|
|
599
669
|
def add_resource_fn(
|
|
600
670
|
self,
|
|
601
671
|
fn: AnyFunction,
|
|
@@ -618,6 +688,12 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
618
688
|
mime_type: Optional MIME type for the resource
|
|
619
689
|
tags: Optional set of tags for categorizing the resource
|
|
620
690
|
"""
|
|
691
|
+
# deprecated since 2.7.0
|
|
692
|
+
warnings.warn(
|
|
693
|
+
"The add_resource_fn method is deprecated. Use the resource decorator instead.",
|
|
694
|
+
DeprecationWarning,
|
|
695
|
+
stacklevel=2,
|
|
696
|
+
)
|
|
621
697
|
self._resource_manager.add_resource_or_template_from_fn(
|
|
622
698
|
fn=fn,
|
|
623
699
|
uri=uri,
|
|
@@ -636,7 +712,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
636
712
|
description: str | None = None,
|
|
637
713
|
mime_type: str | None = None,
|
|
638
714
|
tags: set[str] | None = None,
|
|
639
|
-
) -> Callable[[AnyFunction],
|
|
715
|
+
) -> Callable[[AnyFunction], Resource | ResourceTemplate]:
|
|
640
716
|
"""Decorator to register a function as a resource.
|
|
641
717
|
|
|
642
718
|
The function will be called when the resource is read to generate its content.
|
|
@@ -684,64 +760,124 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
684
760
|
return f"Weather for {city}: {data}"
|
|
685
761
|
"""
|
|
686
762
|
# Check if user passed function directly instead of calling decorator
|
|
687
|
-
if
|
|
763
|
+
if inspect.isroutine(uri):
|
|
688
764
|
raise TypeError(
|
|
689
765
|
"The @resource decorator was used incorrectly. "
|
|
690
766
|
"Did you forget to call it? Use @resource('uri') instead of @resource"
|
|
691
767
|
)
|
|
692
768
|
|
|
693
|
-
def decorator(fn: AnyFunction) ->
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
769
|
+
def decorator(fn: AnyFunction) -> Resource | ResourceTemplate:
|
|
770
|
+
from fastmcp.server.context import Context
|
|
771
|
+
|
|
772
|
+
if isinstance(fn, classmethod): # type: ignore[reportUnnecessaryIsInstance]
|
|
773
|
+
raise ValueError(
|
|
774
|
+
inspect.cleandoc(
|
|
775
|
+
"""
|
|
776
|
+
To decorate a classmethod, first define the method and then call
|
|
777
|
+
resource() directly on the method instead of using it as a
|
|
778
|
+
decorator. See https://gofastmcp.com/patterns/decorating-methods
|
|
779
|
+
for examples and more information.
|
|
780
|
+
"""
|
|
781
|
+
)
|
|
782
|
+
)
|
|
783
|
+
|
|
784
|
+
# Check if this should be a template
|
|
785
|
+
has_uri_params = "{" in uri and "}" in uri
|
|
786
|
+
# check if the function has any parameters (other than injected context)
|
|
787
|
+
has_func_params = any(
|
|
788
|
+
p
|
|
789
|
+
for p in inspect.signature(fn).parameters.values()
|
|
790
|
+
if p.annotation is not Context
|
|
701
791
|
)
|
|
702
|
-
|
|
792
|
+
|
|
793
|
+
if has_uri_params or has_func_params:
|
|
794
|
+
template = ResourceTemplate.from_function(
|
|
795
|
+
fn=fn,
|
|
796
|
+
uri_template=uri,
|
|
797
|
+
name=name,
|
|
798
|
+
description=description,
|
|
799
|
+
mime_type=mime_type,
|
|
800
|
+
tags=tags,
|
|
801
|
+
)
|
|
802
|
+
self.add_template(template)
|
|
803
|
+
return template
|
|
804
|
+
elif not has_uri_params and not has_func_params:
|
|
805
|
+
resource = Resource.from_function(
|
|
806
|
+
fn=fn,
|
|
807
|
+
uri=uri,
|
|
808
|
+
name=name,
|
|
809
|
+
description=description,
|
|
810
|
+
mime_type=mime_type,
|
|
811
|
+
tags=tags,
|
|
812
|
+
)
|
|
813
|
+
self.add_resource(resource)
|
|
814
|
+
return resource
|
|
815
|
+
else:
|
|
816
|
+
raise ValueError(
|
|
817
|
+
"Invalid resource or template definition due to a "
|
|
818
|
+
"mismatch between URI parameters and function parameters."
|
|
819
|
+
)
|
|
703
820
|
|
|
704
821
|
return decorator
|
|
705
822
|
|
|
706
|
-
def add_prompt(
|
|
707
|
-
self,
|
|
708
|
-
fn: Callable[..., PromptResult | Awaitable[PromptResult]],
|
|
709
|
-
name: str | None = None,
|
|
710
|
-
description: str | None = None,
|
|
711
|
-
tags: set[str] | None = None,
|
|
712
|
-
) -> None:
|
|
823
|
+
def add_prompt(self, prompt: Prompt) -> None:
|
|
713
824
|
"""Add a prompt to the server.
|
|
714
825
|
|
|
715
826
|
Args:
|
|
716
827
|
prompt: A Prompt instance to add
|
|
717
828
|
"""
|
|
718
|
-
self._prompt_manager.
|
|
719
|
-
fn=fn,
|
|
720
|
-
name=name,
|
|
721
|
-
description=description,
|
|
722
|
-
tags=tags,
|
|
723
|
-
)
|
|
829
|
+
self._prompt_manager.add_prompt(prompt)
|
|
724
830
|
self._cache.clear()
|
|
725
831
|
|
|
832
|
+
@overload
|
|
726
833
|
def prompt(
|
|
727
834
|
self,
|
|
835
|
+
name_or_fn: AnyFunction,
|
|
836
|
+
*,
|
|
728
837
|
name: str | None = None,
|
|
729
838
|
description: str | None = None,
|
|
730
839
|
tags: set[str] | None = None,
|
|
731
|
-
) ->
|
|
840
|
+
) -> FunctionPrompt: ...
|
|
841
|
+
|
|
842
|
+
@overload
|
|
843
|
+
def prompt(
|
|
844
|
+
self,
|
|
845
|
+
name_or_fn: str | None = None,
|
|
846
|
+
*,
|
|
847
|
+
name: str | None = None,
|
|
848
|
+
description: str | None = None,
|
|
849
|
+
tags: set[str] | None = None,
|
|
850
|
+
) -> Callable[[AnyFunction], FunctionPrompt]: ...
|
|
851
|
+
|
|
852
|
+
def prompt(
|
|
853
|
+
self,
|
|
854
|
+
name_or_fn: str | AnyFunction | None = None,
|
|
855
|
+
*,
|
|
856
|
+
name: str | None = None,
|
|
857
|
+
description: str | None = None,
|
|
858
|
+
tags: set[str] | None = None,
|
|
859
|
+
) -> Callable[[AnyFunction], FunctionPrompt] | FunctionPrompt:
|
|
732
860
|
"""Decorator to register a prompt.
|
|
733
861
|
|
|
734
862
|
Prompts can optionally request a Context object by adding a parameter with the
|
|
735
863
|
Context type annotation. The context provides access to MCP capabilities like
|
|
736
864
|
logging, progress reporting, and session information.
|
|
737
865
|
|
|
866
|
+
This decorator supports multiple calling patterns:
|
|
867
|
+
- @server.prompt (without parentheses)
|
|
868
|
+
- @server.prompt() (with empty parentheses)
|
|
869
|
+
- @server.prompt("custom_name") (with name as first argument)
|
|
870
|
+
- @server.prompt(name="custom_name") (with name as keyword argument)
|
|
871
|
+
- server.prompt(function, name="custom_name") (direct function call)
|
|
872
|
+
|
|
738
873
|
Args:
|
|
739
|
-
|
|
874
|
+
name_or_fn: Either a function (when used as @prompt), a string name, or None
|
|
740
875
|
description: Optional description of what the prompt does
|
|
741
876
|
tags: Optional set of tags for categorizing the prompt
|
|
877
|
+
name: Optional name for the prompt (keyword-only, alternative to name_or_fn)
|
|
742
878
|
|
|
743
879
|
Example:
|
|
744
|
-
@server.prompt
|
|
880
|
+
@server.prompt
|
|
745
881
|
def analyze_table(table_name: str) -> list[Message]:
|
|
746
882
|
schema = read_table_schema(table_name)
|
|
747
883
|
return [
|
|
@@ -762,8 +898,8 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
762
898
|
}
|
|
763
899
|
]
|
|
764
900
|
|
|
765
|
-
@server.prompt()
|
|
766
|
-
|
|
901
|
+
@server.prompt("custom_name")
|
|
902
|
+
def analyze_file(path: str) -> list[Message]:
|
|
767
903
|
content = await read_file(path)
|
|
768
904
|
return [
|
|
769
905
|
{
|
|
@@ -777,19 +913,68 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
777
913
|
}
|
|
778
914
|
}
|
|
779
915
|
]
|
|
916
|
+
|
|
917
|
+
@server.prompt(name="custom_name")
|
|
918
|
+
def another_prompt(data: str) -> list[Message]:
|
|
919
|
+
return [{"role": "user", "content": data}]
|
|
920
|
+
|
|
921
|
+
# Direct function call
|
|
922
|
+
server.prompt(my_function, name="custom_name")
|
|
780
923
|
"""
|
|
781
|
-
|
|
782
|
-
if
|
|
783
|
-
raise
|
|
784
|
-
|
|
785
|
-
|
|
924
|
+
|
|
925
|
+
if isinstance(name_or_fn, classmethod):
|
|
926
|
+
raise ValueError(
|
|
927
|
+
inspect.cleandoc(
|
|
928
|
+
"""
|
|
929
|
+
To decorate a classmethod, first define the method and then call
|
|
930
|
+
prompt() directly on the method instead of using it as a
|
|
931
|
+
decorator. See https://gofastmcp.com/patterns/decorating-methods
|
|
932
|
+
for examples and more information.
|
|
933
|
+
"""
|
|
934
|
+
)
|
|
786
935
|
)
|
|
787
936
|
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
937
|
+
# Determine the actual name and function based on the calling pattern
|
|
938
|
+
if inspect.isroutine(name_or_fn):
|
|
939
|
+
# Case 1: @prompt (without parens) - function passed directly as decorator
|
|
940
|
+
# Case 2: direct call like prompt(fn, name="something")
|
|
941
|
+
fn = name_or_fn
|
|
942
|
+
prompt_name = name # Use keyword name if provided, otherwise None
|
|
791
943
|
|
|
792
|
-
|
|
944
|
+
# Register the prompt immediately
|
|
945
|
+
prompt = Prompt.from_function(
|
|
946
|
+
fn=fn,
|
|
947
|
+
name=prompt_name,
|
|
948
|
+
description=description,
|
|
949
|
+
tags=tags,
|
|
950
|
+
)
|
|
951
|
+
self.add_prompt(prompt)
|
|
952
|
+
|
|
953
|
+
return prompt
|
|
954
|
+
|
|
955
|
+
elif isinstance(name_or_fn, str):
|
|
956
|
+
# Case 3: @prompt("custom_name") - name passed as first argument
|
|
957
|
+
if name is not None:
|
|
958
|
+
raise TypeError(
|
|
959
|
+
"Cannot specify both a name as first argument and as keyword argument. "
|
|
960
|
+
f"Use either @prompt('{name_or_fn}') or @prompt(name='{name}'), not both."
|
|
961
|
+
)
|
|
962
|
+
prompt_name = name_or_fn
|
|
963
|
+
elif name_or_fn is None:
|
|
964
|
+
# Case 4: @prompt() or @prompt(name="something") - use keyword name
|
|
965
|
+
prompt_name = name
|
|
966
|
+
else:
|
|
967
|
+
raise TypeError(
|
|
968
|
+
f"First argument to @prompt must be a function, string, or None, got {type(name_or_fn)}"
|
|
969
|
+
)
|
|
970
|
+
|
|
971
|
+
# Return partial for cases where we need to wait for the function
|
|
972
|
+
return partial(
|
|
973
|
+
self.prompt,
|
|
974
|
+
name=prompt_name,
|
|
975
|
+
description=description,
|
|
976
|
+
tags=tags,
|
|
977
|
+
)
|
|
793
978
|
|
|
794
979
|
async def run_stdio_async(self) -> None:
|
|
795
980
|
"""Run the server using stdio transport."""
|
fastmcp/settings.py
CHANGED
|
@@ -170,7 +170,7 @@ class ServerSettings(BaseSettings):
|
|
|
170
170
|
),
|
|
171
171
|
] = []
|
|
172
172
|
|
|
173
|
-
# cache settings (for
|
|
173
|
+
# cache settings (for getting attributes from servers, used to avoid repeated calls)
|
|
174
174
|
cache_expiration_seconds: float = 0
|
|
175
175
|
|
|
176
176
|
# StreamableHTTP settings
|
fastmcp/tools/__init__.py
CHANGED