fastmcp 2.6.0__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/auth/oauth.py +0 -3
- 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/py.typed +0 -0
- 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/auth/providers/in_memory.py +1 -6
- fastmcp/server/context.py +1 -1
- fastmcp/server/openapi.py +17 -32
- fastmcp/server/proxy.py +5 -8
- fastmcp/server/server.py +274 -100
- 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.0.dist-info → fastmcp-2.7.0.dist-info}/METADATA +29 -17
- {fastmcp-2.6.0.dist-info → fastmcp-2.7.0.dist-info}/RECORD +30 -30
- fastmcp/utilities/decorators.py +0 -101
- {fastmcp-2.6.0.dist-info → fastmcp-2.7.0.dist-info}/WHEEL +0 -0
- {fastmcp-2.6.0.dist-info → fastmcp-2.7.0.dist-info}/entry_points.txt +0 -0
- {fastmcp-2.6.0.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
|
|
|
@@ -131,16 +131,6 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
131
131
|
tools: list[Tool | Callable[..., Any]] | None = None,
|
|
132
132
|
**settings: Any,
|
|
133
133
|
):
|
|
134
|
-
if settings:
|
|
135
|
-
# TODO: remove settings. Deprecated since 2.3.4
|
|
136
|
-
warnings.warn(
|
|
137
|
-
"Passing runtime and transport-specific settings as kwargs "
|
|
138
|
-
"to the FastMCP constructor is deprecated (as of 2.3.4), "
|
|
139
|
-
"including most transport settings. If possible, provide settings when calling "
|
|
140
|
-
"run() instead.",
|
|
141
|
-
DeprecationWarning,
|
|
142
|
-
stacklevel=2,
|
|
143
|
-
)
|
|
144
134
|
self.settings = fastmcp.settings.ServerSettings(**settings)
|
|
145
135
|
|
|
146
136
|
# If mask_error_details is provided, override the settings value
|
|
@@ -164,7 +154,6 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
164
154
|
self._additional_http_routes: list[BaseRoute] = []
|
|
165
155
|
self._tool_manager = ToolManager(
|
|
166
156
|
duplicate_behavior=on_duplicate_tools,
|
|
167
|
-
serializer=tool_serializer,
|
|
168
157
|
mask_error_details=self.settings.mask_error_details,
|
|
169
158
|
)
|
|
170
159
|
self._resource_manager = ResourceManager(
|
|
@@ -175,6 +164,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
175
164
|
duplicate_behavior=on_duplicate_prompts,
|
|
176
165
|
mask_error_details=self.settings.mask_error_details,
|
|
177
166
|
)
|
|
167
|
+
self._tool_serializer = tool_serializer
|
|
178
168
|
|
|
179
169
|
if lifespan is None:
|
|
180
170
|
self._has_lifespan = False
|
|
@@ -193,10 +183,9 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
193
183
|
|
|
194
184
|
if tools:
|
|
195
185
|
for tool in tools:
|
|
196
|
-
if isinstance(tool, Tool):
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
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)
|
|
200
189
|
|
|
201
190
|
# Set up MCP protocol handlers
|
|
202
191
|
self._setup_handlers()
|
|
@@ -359,18 +348,18 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
359
348
|
"""
|
|
360
349
|
|
|
361
350
|
def decorator(
|
|
362
|
-
|
|
351
|
+
fn: Callable[[Request], Awaitable[Response]],
|
|
363
352
|
) -> Callable[[Request], Awaitable[Response]]:
|
|
364
353
|
self._additional_http_routes.append(
|
|
365
354
|
Route(
|
|
366
355
|
path,
|
|
367
|
-
endpoint=
|
|
356
|
+
endpoint=fn,
|
|
368
357
|
methods=methods,
|
|
369
358
|
name=name,
|
|
370
359
|
include_in_schema=include_in_schema,
|
|
371
360
|
)
|
|
372
361
|
)
|
|
373
|
-
return
|
|
362
|
+
return fn
|
|
374
363
|
|
|
375
364
|
return decorator
|
|
376
365
|
|
|
@@ -494,15 +483,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
494
483
|
|
|
495
484
|
raise NotFoundError(f"Unknown prompt: {name}")
|
|
496
485
|
|
|
497
|
-
def add_tool(
|
|
498
|
-
self,
|
|
499
|
-
fn: AnyFunction,
|
|
500
|
-
name: str | None = None,
|
|
501
|
-
description: str | None = None,
|
|
502
|
-
tags: set[str] | None = None,
|
|
503
|
-
annotations: ToolAnnotations | dict[str, Any] | None = None,
|
|
504
|
-
exclude_args: list[str] | None = None,
|
|
505
|
-
) -> None:
|
|
486
|
+
def add_tool(self, tool: Tool) -> None:
|
|
506
487
|
"""Add a tool to the server.
|
|
507
488
|
|
|
508
489
|
The tool function can optionally request a Context object by adding a parameter
|
|
@@ -515,17 +496,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
515
496
|
tags: Optional set of tags for categorizing the tool
|
|
516
497
|
annotations: Optional annotations about the tool's behavior
|
|
517
498
|
"""
|
|
518
|
-
|
|
519
|
-
annotations = ToolAnnotations(**annotations)
|
|
520
|
-
|
|
521
|
-
self._tool_manager.add_tool_from_fn(
|
|
522
|
-
fn,
|
|
523
|
-
name=name,
|
|
524
|
-
description=description,
|
|
525
|
-
tags=tags,
|
|
526
|
-
annotations=annotations,
|
|
527
|
-
exclude_args=exclude_args,
|
|
528
|
-
)
|
|
499
|
+
self._tool_manager.add_tool(tool)
|
|
529
500
|
self._cache.clear()
|
|
530
501
|
|
|
531
502
|
def remove_tool(self, name: str) -> None:
|
|
@@ -540,61 +511,141 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
540
511
|
self._tool_manager.remove_tool(name)
|
|
541
512
|
self._cache.clear()
|
|
542
513
|
|
|
514
|
+
@overload
|
|
543
515
|
def tool(
|
|
544
516
|
self,
|
|
517
|
+
name_or_fn: AnyFunction,
|
|
518
|
+
*,
|
|
545
519
|
name: str | None = None,
|
|
546
520
|
description: str | None = None,
|
|
547
521
|
tags: set[str] | None = None,
|
|
548
522
|
annotations: ToolAnnotations | dict[str, Any] | None = None,
|
|
549
523
|
exclude_args: list[str] | None = None,
|
|
550
|
-
) ->
|
|
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:
|
|
551
548
|
"""Decorator to register a tool.
|
|
552
549
|
|
|
553
550
|
Tools can optionally request a Context object by adding a parameter with the
|
|
554
551
|
Context type annotation. The context provides access to MCP capabilities like
|
|
555
552
|
logging, progress reporting, and resource access.
|
|
556
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
|
+
|
|
557
561
|
Args:
|
|
558
|
-
|
|
562
|
+
name_or_fn: Either a function (when used as @tool), a string name, or None
|
|
559
563
|
description: Optional description of what the tool does
|
|
560
564
|
tags: Optional set of tags for categorizing the tool
|
|
561
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)
|
|
562
568
|
|
|
563
569
|
Example:
|
|
564
|
-
@server.tool
|
|
570
|
+
@server.tool
|
|
565
571
|
def my_tool(x: int) -> str:
|
|
566
572
|
return str(x)
|
|
567
573
|
|
|
568
|
-
@server.tool
|
|
569
|
-
def
|
|
570
|
-
|
|
574
|
+
@server.tool
|
|
575
|
+
def my_tool(x: int) -> str:
|
|
576
|
+
return str(x)
|
|
577
|
+
|
|
578
|
+
@server.tool("custom_name")
|
|
579
|
+
def my_tool(x: int) -> str:
|
|
571
580
|
return str(x)
|
|
572
581
|
|
|
573
|
-
@server.tool()
|
|
574
|
-
|
|
575
|
-
await context.report_progress(50, 100)
|
|
582
|
+
@server.tool(name="custom_name")
|
|
583
|
+
def my_tool(x: int) -> str:
|
|
576
584
|
return str(x)
|
|
585
|
+
|
|
586
|
+
# Direct function call
|
|
587
|
+
server.tool(my_function, name="custom_name")
|
|
577
588
|
"""
|
|
589
|
+
if isinstance(annotations, dict):
|
|
590
|
+
annotations = ToolAnnotations(**annotations)
|
|
578
591
|
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
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
|
+
)
|
|
584
602
|
)
|
|
585
603
|
|
|
586
|
-
|
|
587
|
-
|
|
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(
|
|
588
613
|
fn,
|
|
589
|
-
name=
|
|
614
|
+
name=tool_name,
|
|
590
615
|
description=description,
|
|
591
616
|
tags=tags,
|
|
592
617
|
annotations=annotations,
|
|
593
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)}"
|
|
594
638
|
)
|
|
595
|
-
return fn
|
|
596
639
|
|
|
597
|
-
|
|
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
|
+
)
|
|
598
649
|
|
|
599
650
|
def add_resource(self, resource: Resource, key: str | None = None) -> None:
|
|
600
651
|
"""Add a resource to the server.
|
|
@@ -606,6 +657,14 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
606
657
|
self._resource_manager.add_resource(resource, key=key)
|
|
607
658
|
self._cache.clear()
|
|
608
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
|
+
|
|
609
668
|
def add_resource_fn(
|
|
610
669
|
self,
|
|
611
670
|
fn: AnyFunction,
|
|
@@ -628,6 +687,12 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
628
687
|
mime_type: Optional MIME type for the resource
|
|
629
688
|
tags: Optional set of tags for categorizing the resource
|
|
630
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
|
+
)
|
|
631
696
|
self._resource_manager.add_resource_or_template_from_fn(
|
|
632
697
|
fn=fn,
|
|
633
698
|
uri=uri,
|
|
@@ -646,7 +711,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
646
711
|
description: str | None = None,
|
|
647
712
|
mime_type: str | None = None,
|
|
648
713
|
tags: set[str] | None = None,
|
|
649
|
-
) -> Callable[[AnyFunction],
|
|
714
|
+
) -> Callable[[AnyFunction], Resource | ResourceTemplate]:
|
|
650
715
|
"""Decorator to register a function as a resource.
|
|
651
716
|
|
|
652
717
|
The function will be called when the resource is read to generate its content.
|
|
@@ -694,64 +759,124 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
694
759
|
return f"Weather for {city}: {data}"
|
|
695
760
|
"""
|
|
696
761
|
# Check if user passed function directly instead of calling decorator
|
|
697
|
-
if
|
|
762
|
+
if inspect.isroutine(uri):
|
|
698
763
|
raise TypeError(
|
|
699
764
|
"The @resource decorator was used incorrectly. "
|
|
700
765
|
"Did you forget to call it? Use @resource('uri') instead of @resource"
|
|
701
766
|
)
|
|
702
767
|
|
|
703
|
-
def decorator(fn: AnyFunction) ->
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
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
|
|
711
790
|
)
|
|
712
|
-
|
|
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
|
+
)
|
|
713
819
|
|
|
714
820
|
return decorator
|
|
715
821
|
|
|
716
|
-
def add_prompt(
|
|
717
|
-
self,
|
|
718
|
-
fn: Callable[..., PromptResult | Awaitable[PromptResult]],
|
|
719
|
-
name: str | None = None,
|
|
720
|
-
description: str | None = None,
|
|
721
|
-
tags: set[str] | None = None,
|
|
722
|
-
) -> None:
|
|
822
|
+
def add_prompt(self, prompt: Prompt) -> None:
|
|
723
823
|
"""Add a prompt to the server.
|
|
724
824
|
|
|
725
825
|
Args:
|
|
726
826
|
prompt: A Prompt instance to add
|
|
727
827
|
"""
|
|
728
|
-
self._prompt_manager.
|
|
729
|
-
fn=fn,
|
|
730
|
-
name=name,
|
|
731
|
-
description=description,
|
|
732
|
-
tags=tags,
|
|
733
|
-
)
|
|
828
|
+
self._prompt_manager.add_prompt(prompt)
|
|
734
829
|
self._cache.clear()
|
|
735
830
|
|
|
831
|
+
@overload
|
|
736
832
|
def prompt(
|
|
737
833
|
self,
|
|
834
|
+
name_or_fn: AnyFunction,
|
|
835
|
+
*,
|
|
738
836
|
name: str | None = None,
|
|
739
837
|
description: str | None = None,
|
|
740
838
|
tags: set[str] | None = None,
|
|
741
|
-
) ->
|
|
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:
|
|
742
859
|
"""Decorator to register a prompt.
|
|
743
860
|
|
|
744
861
|
Prompts can optionally request a Context object by adding a parameter with the
|
|
745
862
|
Context type annotation. The context provides access to MCP capabilities like
|
|
746
863
|
logging, progress reporting, and session information.
|
|
747
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
|
+
|
|
748
872
|
Args:
|
|
749
|
-
|
|
873
|
+
name_or_fn: Either a function (when used as @prompt), a string name, or None
|
|
750
874
|
description: Optional description of what the prompt does
|
|
751
875
|
tags: Optional set of tags for categorizing the prompt
|
|
876
|
+
name: Optional name for the prompt (keyword-only, alternative to name_or_fn)
|
|
752
877
|
|
|
753
878
|
Example:
|
|
754
|
-
@server.prompt
|
|
879
|
+
@server.prompt
|
|
755
880
|
def analyze_table(table_name: str) -> list[Message]:
|
|
756
881
|
schema = read_table_schema(table_name)
|
|
757
882
|
return [
|
|
@@ -761,7 +886,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
761
886
|
}
|
|
762
887
|
]
|
|
763
888
|
|
|
764
|
-
@server.prompt
|
|
889
|
+
@server.prompt
|
|
765
890
|
def analyze_with_context(table_name: str, ctx: Context) -> list[Message]:
|
|
766
891
|
ctx.info(f"Analyzing table {table_name}")
|
|
767
892
|
schema = read_table_schema(table_name)
|
|
@@ -772,8 +897,8 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
772
897
|
}
|
|
773
898
|
]
|
|
774
899
|
|
|
775
|
-
@server.prompt()
|
|
776
|
-
|
|
900
|
+
@server.prompt("custom_name")
|
|
901
|
+
def analyze_file(path: str) -> list[Message]:
|
|
777
902
|
content = await read_file(path)
|
|
778
903
|
return [
|
|
779
904
|
{
|
|
@@ -787,19 +912,68 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
787
912
|
}
|
|
788
913
|
}
|
|
789
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")
|
|
790
922
|
"""
|
|
791
|
-
|
|
792
|
-
if
|
|
793
|
-
raise
|
|
794
|
-
|
|
795
|
-
|
|
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
|
+
)
|
|
796
934
|
)
|
|
797
935
|
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
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
|
|
801
942
|
|
|
802
|
-
|
|
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
|
+
)
|
|
803
977
|
|
|
804
978
|
async def run_stdio_async(self) -> None:
|
|
805
979
|
"""Run the server using stdio transport."""
|
fastmcp/tools/__init__.py
CHANGED