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