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/__init__.py
CHANGED
|
@@ -1,21 +1,50 @@
|
|
|
1
1
|
"""FastMCP - An ergonomic MCP interface."""
|
|
2
2
|
|
|
3
|
+
import warnings
|
|
3
4
|
from importlib.metadata import version
|
|
5
|
+
from fastmcp.settings import Settings
|
|
6
|
+
|
|
7
|
+
settings = Settings()
|
|
4
8
|
|
|
5
9
|
from fastmcp.server.server import FastMCP
|
|
6
10
|
from fastmcp.server.context import Context
|
|
7
11
|
import fastmcp.server
|
|
8
12
|
|
|
9
13
|
from fastmcp.client import Client
|
|
10
|
-
from
|
|
11
|
-
from . import client, settings
|
|
14
|
+
from . import client
|
|
12
15
|
|
|
13
16
|
__version__ = version("fastmcp")
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
# ensure deprecation warnings are displayed by default
|
|
20
|
+
if settings.deprecation_warnings:
|
|
21
|
+
warnings.simplefilter("default", DeprecationWarning)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def __getattr__(name: str):
|
|
25
|
+
"""
|
|
26
|
+
Used to deprecate the module-level Image class; can be removed once it is no longer imported to root.
|
|
27
|
+
"""
|
|
28
|
+
if name == "Image":
|
|
29
|
+
# Deprecated in 2.8.1
|
|
30
|
+
if settings.deprecation_warnings:
|
|
31
|
+
warnings.warn(
|
|
32
|
+
"The top-level `fastmcp.Image` import is deprecated "
|
|
33
|
+
"and will be removed in a future version. "
|
|
34
|
+
"Please use `fastmcp.utilities.types.Image` instead.",
|
|
35
|
+
DeprecationWarning,
|
|
36
|
+
stacklevel=2,
|
|
37
|
+
)
|
|
38
|
+
from fastmcp.utilities.types import Image
|
|
39
|
+
|
|
40
|
+
return Image
|
|
41
|
+
raise AttributeError(f"module '{__name__}' has no attribute '{name}'")
|
|
42
|
+
|
|
43
|
+
|
|
14
44
|
__all__ = [
|
|
15
45
|
"FastMCP",
|
|
16
46
|
"Context",
|
|
17
47
|
"client",
|
|
18
48
|
"Client",
|
|
19
49
|
"settings",
|
|
20
|
-
"Image",
|
|
21
50
|
]
|
fastmcp/cli/cli.py
CHANGED
|
@@ -18,6 +18,7 @@ from typer import Context, Exit
|
|
|
18
18
|
import fastmcp
|
|
19
19
|
from fastmcp.cli import claude
|
|
20
20
|
from fastmcp.cli import run as run_module
|
|
21
|
+
from fastmcp.server.server import FastMCP
|
|
21
22
|
from fastmcp.utilities.logging import get_logger
|
|
22
23
|
|
|
23
24
|
logger = get_logger("cli")
|
|
@@ -165,8 +166,8 @@ def dev(
|
|
|
165
166
|
|
|
166
167
|
try:
|
|
167
168
|
# Import server to get dependencies
|
|
168
|
-
server = run_module.import_server(file, server_object)
|
|
169
|
-
if
|
|
169
|
+
server: FastMCP = run_module.import_server(file, server_object)
|
|
170
|
+
if server.dependencies is not None:
|
|
170
171
|
with_packages = list(set(with_packages + server.dependencies))
|
|
171
172
|
|
|
172
173
|
env_vars = {}
|
fastmcp/client/auth/oauth.py
CHANGED
|
@@ -23,10 +23,10 @@ from mcp.shared.auth import (
|
|
|
23
23
|
)
|
|
24
24
|
from pydantic import AnyHttpUrl, ValidationError
|
|
25
25
|
|
|
26
|
+
from fastmcp import settings as fastmcp_global_settings
|
|
26
27
|
from fastmcp.client.oauth_callback import (
|
|
27
28
|
create_oauth_callback_server,
|
|
28
29
|
)
|
|
29
|
-
from fastmcp.settings import settings as fastmcp_global_settings
|
|
30
30
|
from fastmcp.utilities.http import find_available_port
|
|
31
31
|
from fastmcp.utilities.logging import get_logger
|
|
32
32
|
|
fastmcp/client/client.py
CHANGED
|
@@ -29,6 +29,7 @@ from fastmcp.exceptions import ToolError
|
|
|
29
29
|
from fastmcp.server import FastMCP
|
|
30
30
|
from fastmcp.utilities.exceptions import get_catch_handlers
|
|
31
31
|
from fastmcp.utilities.mcp_config import MCPConfig
|
|
32
|
+
from fastmcp.utilities.types import MCPContent
|
|
32
33
|
|
|
33
34
|
from .transports import (
|
|
34
35
|
ClientTransportT,
|
|
@@ -145,6 +146,7 @@ class Client(Generic[ClientTransportT]):
|
|
|
145
146
|
progress_handler: ProgressHandler | None = None,
|
|
146
147
|
timeout: datetime.timedelta | float | int | None = None,
|
|
147
148
|
init_timeout: datetime.timedelta | float | int | None = None,
|
|
149
|
+
client_info: mcp.types.Implementation | None = None,
|
|
148
150
|
auth: httpx.Auth | Literal["oauth"] | str | None = None,
|
|
149
151
|
):
|
|
150
152
|
self.transport = cast(ClientTransportT, infer_transport(transport))
|
|
@@ -165,7 +167,7 @@ class Client(Generic[ClientTransportT]):
|
|
|
165
167
|
|
|
166
168
|
# handle init handshake timeout
|
|
167
169
|
if init_timeout is None:
|
|
168
|
-
init_timeout = fastmcp.settings.
|
|
170
|
+
init_timeout = fastmcp.settings.client_init_timeout
|
|
169
171
|
if isinstance(init_timeout, datetime.timedelta):
|
|
170
172
|
init_timeout = init_timeout.total_seconds()
|
|
171
173
|
elif not init_timeout:
|
|
@@ -180,6 +182,7 @@ class Client(Generic[ClientTransportT]):
|
|
|
180
182
|
"logging_callback": create_log_callback(log_handler),
|
|
181
183
|
"message_handler": message_handler,
|
|
182
184
|
"read_timeout_seconds": timeout,
|
|
185
|
+
"client_info": client_info,
|
|
183
186
|
}
|
|
184
187
|
|
|
185
188
|
if roots is not None:
|
|
@@ -656,9 +659,7 @@ class Client(Generic[ClientTransportT]):
|
|
|
656
659
|
arguments: dict[str, Any] | None = None,
|
|
657
660
|
timeout: datetime.timedelta | float | int | None = None,
|
|
658
661
|
progress_handler: ProgressHandler | None = None,
|
|
659
|
-
) -> list[
|
|
660
|
-
mcp.types.TextContent | mcp.types.ImageContent | mcp.types.EmbeddedResource
|
|
661
|
-
]:
|
|
662
|
+
) -> list[MCPContent]:
|
|
662
663
|
"""Call a tool on the server.
|
|
663
664
|
|
|
664
665
|
Unlike call_tool_mcp, this method raises a ToolError if the tool call results in an error.
|
|
@@ -670,7 +671,7 @@ class Client(Generic[ClientTransportT]):
|
|
|
670
671
|
progress_handler (ProgressHandler | None, optional): The progress handler to use for the tool call. Defaults to None.
|
|
671
672
|
|
|
672
673
|
Returns:
|
|
673
|
-
list[mcp.types.TextContent | mcp.types.ImageContent | mcp.types.EmbeddedResource]:
|
|
674
|
+
list[mcp.types.TextContent | mcp.types.ImageContent | mcp.types.AudioContent | mcp.types.EmbeddedResource]:
|
|
674
675
|
The content returned by the tool.
|
|
675
676
|
|
|
676
677
|
Raises:
|
fastmcp/client/sampling.py
CHANGED
|
@@ -9,13 +9,7 @@ from mcp.shared.context import LifespanContextT, RequestContext
|
|
|
9
9
|
from mcp.types import CreateMessageRequestParams as SamplingParams
|
|
10
10
|
from mcp.types import SamplingMessage
|
|
11
11
|
|
|
12
|
-
__all__ = ["SamplingMessage", "SamplingParams", "
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
class MessageResult(CreateMessageResult):
|
|
16
|
-
role: mcp.types.Role = "assistant"
|
|
17
|
-
content: mcp.types.TextContent | mcp.types.ImageContent
|
|
18
|
-
model: str = "client-model"
|
|
12
|
+
__all__ = ["SamplingMessage", "SamplingParams", "SamplingHandler"]
|
|
19
13
|
|
|
20
14
|
|
|
21
15
|
SamplingHandler: TypeAlias = Callable[
|
|
@@ -39,8 +33,10 @@ def create_sampling_callback(sampling_handler: SamplingHandler) -> SamplingFnT:
|
|
|
39
33
|
result = await result
|
|
40
34
|
|
|
41
35
|
if isinstance(result, str):
|
|
42
|
-
result =
|
|
43
|
-
|
|
36
|
+
result = CreateMessageResult(
|
|
37
|
+
role="assistant",
|
|
38
|
+
model="fastmcp-client",
|
|
39
|
+
content=mcp.types.TextContent(type="text", text=result),
|
|
44
40
|
)
|
|
45
41
|
return result
|
|
46
42
|
except Exception as e:
|
fastmcp/client/transports.py
CHANGED
|
@@ -8,30 +8,19 @@ import sys
|
|
|
8
8
|
import warnings
|
|
9
9
|
from collections.abc import AsyncIterator, Callable
|
|
10
10
|
from pathlib import Path
|
|
11
|
-
from typing import
|
|
12
|
-
TYPE_CHECKING,
|
|
13
|
-
Any,
|
|
14
|
-
Literal,
|
|
15
|
-
TypedDict,
|
|
16
|
-
TypeVar,
|
|
17
|
-
cast,
|
|
18
|
-
overload,
|
|
19
|
-
)
|
|
11
|
+
from typing import Any, Literal, TypedDict, TypeVar, cast, overload
|
|
20
12
|
|
|
21
13
|
import anyio
|
|
22
14
|
import httpx
|
|
15
|
+
import mcp.types
|
|
23
16
|
from mcp import ClientSession, StdioServerParameters
|
|
24
|
-
from mcp.client.session import
|
|
25
|
-
ListRootsFnT,
|
|
26
|
-
LoggingFnT,
|
|
27
|
-
MessageHandlerFnT,
|
|
28
|
-
SamplingFnT,
|
|
29
|
-
)
|
|
17
|
+
from mcp.client.session import ListRootsFnT, LoggingFnT, MessageHandlerFnT, SamplingFnT
|
|
30
18
|
from mcp.server.fastmcp import FastMCP as FastMCP1Server
|
|
31
|
-
from mcp.shared.memory import
|
|
19
|
+
from mcp.shared.memory import create_client_server_memory_streams
|
|
32
20
|
from pydantic import AnyUrl
|
|
33
21
|
from typing_extensions import Unpack
|
|
34
22
|
|
|
23
|
+
import fastmcp
|
|
35
24
|
from fastmcp.client.auth.bearer import BearerAuth
|
|
36
25
|
from fastmcp.client.auth.oauth import OAuth
|
|
37
26
|
from fastmcp.server.dependencies import get_http_headers
|
|
@@ -39,9 +28,6 @@ from fastmcp.server.server import FastMCP
|
|
|
39
28
|
from fastmcp.utilities.logging import get_logger
|
|
40
29
|
from fastmcp.utilities.mcp_config import MCPConfig, infer_transport_type_from_url
|
|
41
30
|
|
|
42
|
-
if TYPE_CHECKING:
|
|
43
|
-
from fastmcp.utilities.mcp_config import MCPConfig
|
|
44
|
-
|
|
45
31
|
logger = get_logger(__name__)
|
|
46
32
|
|
|
47
33
|
# TypeVar for preserving specific ClientTransport subclass types
|
|
@@ -65,11 +51,12 @@ __all__ = [
|
|
|
65
51
|
class SessionKwargs(TypedDict, total=False):
|
|
66
52
|
"""Keyword arguments for the MCP ClientSession constructor."""
|
|
67
53
|
|
|
54
|
+
read_timeout_seconds: datetime.timedelta | None
|
|
68
55
|
sampling_callback: SamplingFnT | None
|
|
69
56
|
list_roots_callback: ListRootsFnT | None
|
|
70
57
|
logging_callback: LoggingFnT | None
|
|
71
58
|
message_handler: MessageHandlerFnT | None
|
|
72
|
-
|
|
59
|
+
client_info: mcp.types.Implementation | None
|
|
73
60
|
|
|
74
61
|
|
|
75
62
|
class ClientTransport(abc.ABC):
|
|
@@ -123,11 +110,12 @@ class WSTransport(ClientTransport):
|
|
|
123
110
|
|
|
124
111
|
def __init__(self, url: str | AnyUrl):
|
|
125
112
|
# we never really used this transport, so it can be removed at any time
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
113
|
+
if fastmcp.settings.deprecation_warnings:
|
|
114
|
+
warnings.warn(
|
|
115
|
+
"WSTransport is a deprecated MCP transport and will be removed in a future version. Use StreamableHttpTransport instead.",
|
|
116
|
+
DeprecationWarning,
|
|
117
|
+
stacklevel=2,
|
|
118
|
+
)
|
|
131
119
|
if isinstance(url, AnyUrl):
|
|
132
120
|
url = str(url)
|
|
133
121
|
if not isinstance(url, str) or not url.startswith("ws"):
|
|
@@ -662,24 +650,46 @@ class FastMCPTransport(ClientTransport):
|
|
|
662
650
|
tests or scenarios where client and server run in the same runtime.
|
|
663
651
|
"""
|
|
664
652
|
|
|
665
|
-
def __init__(self, mcp: FastMCP | FastMCP1Server):
|
|
653
|
+
def __init__(self, mcp: FastMCP | FastMCP1Server, raise_exceptions: bool = False):
|
|
666
654
|
"""Initialize a FastMCPTransport from a FastMCP server instance."""
|
|
667
655
|
|
|
668
656
|
# Accept both FastMCP 2.x and FastMCP 1.0 servers. Both expose a
|
|
669
657
|
# ``_mcp_server`` attribute pointing to the underlying MCP server
|
|
670
658
|
# implementation, so we can treat them identically.
|
|
671
659
|
self.server = mcp
|
|
660
|
+
self.raise_exceptions = raise_exceptions
|
|
672
661
|
|
|
673
662
|
@contextlib.asynccontextmanager
|
|
674
663
|
async def connect_session(
|
|
675
664
|
self, **session_kwargs: Unpack[SessionKwargs]
|
|
676
665
|
) -> AsyncIterator[ClientSession]:
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
666
|
+
async with create_client_server_memory_streams() as (
|
|
667
|
+
client_streams,
|
|
668
|
+
server_streams,
|
|
669
|
+
):
|
|
670
|
+
client_read, client_write = client_streams
|
|
671
|
+
server_read, server_write = server_streams
|
|
672
|
+
|
|
673
|
+
# Create a cancel scope for the server task
|
|
674
|
+
async with anyio.create_task_group() as tg:
|
|
675
|
+
tg.start_soon(
|
|
676
|
+
lambda: self.server._mcp_server.run(
|
|
677
|
+
server_read,
|
|
678
|
+
server_write,
|
|
679
|
+
self.server._mcp_server.create_initialization_options(),
|
|
680
|
+
raise_exceptions=self.raise_exceptions,
|
|
681
|
+
)
|
|
682
|
+
)
|
|
683
|
+
|
|
684
|
+
try:
|
|
685
|
+
async with ClientSession(
|
|
686
|
+
read_stream=client_read,
|
|
687
|
+
write_stream=client_write,
|
|
688
|
+
**session_kwargs,
|
|
689
|
+
) as client_session:
|
|
690
|
+
yield client_session
|
|
691
|
+
finally:
|
|
692
|
+
tg.cancel_scope.cancel()
|
|
683
693
|
|
|
684
694
|
def __repr__(self) -> str:
|
|
685
695
|
return f"<FastMCPTransport(server='{self.server.name}')>"
|
|
@@ -859,7 +869,6 @@ def infer_transport(
|
|
|
859
869
|
transport = infer_transport(config)
|
|
860
870
|
```
|
|
861
871
|
"""
|
|
862
|
-
from fastmcp.utilities.mcp_config import MCPConfig
|
|
863
872
|
|
|
864
873
|
# the transport is already a ClientTransport
|
|
865
874
|
if isinstance(transport, ClientTransport):
|
fastmcp/exceptions.py
CHANGED
fastmcp/prompts/prompt.py
CHANGED
|
@@ -5,21 +5,22 @@ from __future__ import annotations as _annotations
|
|
|
5
5
|
import inspect
|
|
6
6
|
from abc import ABC, abstractmethod
|
|
7
7
|
from collections.abc import Awaitable, Callable, Sequence
|
|
8
|
-
from typing import TYPE_CHECKING,
|
|
8
|
+
from typing import TYPE_CHECKING, Any
|
|
9
9
|
|
|
10
10
|
import pydantic_core
|
|
11
|
-
from mcp.types import EmbeddedResource, ImageContent, PromptMessage, Role, TextContent
|
|
12
11
|
from mcp.types import Prompt as MCPPrompt
|
|
13
12
|
from mcp.types import PromptArgument as MCPPromptArgument
|
|
14
|
-
from
|
|
13
|
+
from mcp.types import PromptMessage, Role, TextContent
|
|
14
|
+
from pydantic import Field, TypeAdapter, validate_call
|
|
15
15
|
|
|
16
16
|
from fastmcp.exceptions import PromptError
|
|
17
17
|
from fastmcp.server.dependencies import get_context
|
|
18
|
+
from fastmcp.utilities.components import FastMCPComponent
|
|
18
19
|
from fastmcp.utilities.json_schema import compress_schema
|
|
19
20
|
from fastmcp.utilities.logging import get_logger
|
|
20
21
|
from fastmcp.utilities.types import (
|
|
21
22
|
FastMCPBaseModel,
|
|
22
|
-
|
|
23
|
+
MCPContent,
|
|
23
24
|
find_kwarg_by_type,
|
|
24
25
|
get_cached_typeadapter,
|
|
25
26
|
)
|
|
@@ -27,13 +28,12 @@ from fastmcp.utilities.types import (
|
|
|
27
28
|
if TYPE_CHECKING:
|
|
28
29
|
pass
|
|
29
30
|
|
|
30
|
-
CONTENT_TYPES = TextContent | ImageContent | EmbeddedResource
|
|
31
31
|
|
|
32
32
|
logger = get_logger(__name__)
|
|
33
33
|
|
|
34
34
|
|
|
35
35
|
def Message(
|
|
36
|
-
content: str |
|
|
36
|
+
content: str | MCPContent, role: Role | None = None, **kwargs: Any
|
|
37
37
|
) -> PromptMessage:
|
|
38
38
|
"""A user-friendly constructor for PromptMessage."""
|
|
39
39
|
if isinstance(content, str):
|
|
@@ -66,26 +66,13 @@ class PromptArgument(FastMCPBaseModel):
|
|
|
66
66
|
)
|
|
67
67
|
|
|
68
68
|
|
|
69
|
-
class Prompt(
|
|
69
|
+
class Prompt(FastMCPComponent, ABC):
|
|
70
70
|
"""A prompt template that can be rendered with parameters."""
|
|
71
71
|
|
|
72
|
-
name: str = Field(description="Name of the prompt")
|
|
73
|
-
description: str | None = Field(
|
|
74
|
-
default=None, description="Description of what the prompt does"
|
|
75
|
-
)
|
|
76
|
-
tags: Annotated[set[str], BeforeValidator(_convert_set_defaults)] = Field(
|
|
77
|
-
default_factory=set, description="Tags for the prompt"
|
|
78
|
-
)
|
|
79
72
|
arguments: list[PromptArgument] | None = Field(
|
|
80
73
|
default=None, description="Arguments that can be passed to the prompt"
|
|
81
74
|
)
|
|
82
75
|
|
|
83
|
-
def __eq__(self, other: object) -> bool:
|
|
84
|
-
if type(self) is not type(other):
|
|
85
|
-
return False
|
|
86
|
-
assert isinstance(other, type(self))
|
|
87
|
-
return self.model_dump() == other.model_dump()
|
|
88
|
-
|
|
89
76
|
def to_mcp_prompt(self, **overrides: Any) -> MCPPrompt:
|
|
90
77
|
"""Convert the prompt to an MCP prompt."""
|
|
91
78
|
arguments = [
|
|
@@ -109,6 +96,7 @@ class Prompt(FastMCPBaseModel, ABC):
|
|
|
109
96
|
name: str | None = None,
|
|
110
97
|
description: str | None = None,
|
|
111
98
|
tags: set[str] | None = None,
|
|
99
|
+
enabled: bool | None = None,
|
|
112
100
|
) -> FunctionPrompt:
|
|
113
101
|
"""Create a Prompt from a function.
|
|
114
102
|
|
|
@@ -119,7 +107,7 @@ class Prompt(FastMCPBaseModel, ABC):
|
|
|
119
107
|
- A sequence of any of the above
|
|
120
108
|
"""
|
|
121
109
|
return FunctionPrompt.from_function(
|
|
122
|
-
fn=fn, name=name, description=description, tags=tags
|
|
110
|
+
fn=fn, name=name, description=description, tags=tags, enabled=enabled
|
|
123
111
|
)
|
|
124
112
|
|
|
125
113
|
@abstractmethod
|
|
@@ -143,6 +131,7 @@ class FunctionPrompt(Prompt):
|
|
|
143
131
|
name: str | None = None,
|
|
144
132
|
description: str | None = None,
|
|
145
133
|
tags: set[str] | None = None,
|
|
134
|
+
enabled: bool | None = None,
|
|
146
135
|
) -> FunctionPrompt:
|
|
147
136
|
"""Create a Prompt from a function.
|
|
148
137
|
|
|
@@ -208,6 +197,7 @@ class FunctionPrompt(Prompt):
|
|
|
208
197
|
description=description,
|
|
209
198
|
arguments=arguments,
|
|
210
199
|
tags=tags or set(),
|
|
200
|
+
enabled=enabled if enabled is not None else True,
|
|
211
201
|
fn=fn,
|
|
212
202
|
)
|
|
213
203
|
|
|
@@ -6,6 +6,7 @@ from typing import TYPE_CHECKING, Any
|
|
|
6
6
|
|
|
7
7
|
from mcp import GetPromptResult
|
|
8
8
|
|
|
9
|
+
from fastmcp import settings
|
|
9
10
|
from fastmcp.exceptions import NotFoundError, PromptError
|
|
10
11
|
from fastmcp.prompts.prompt import FunctionPrompt, Prompt, PromptResult
|
|
11
12
|
from fastmcp.settings import DuplicateBehavior
|
|
@@ -23,10 +24,10 @@ class PromptManager:
|
|
|
23
24
|
def __init__(
|
|
24
25
|
self,
|
|
25
26
|
duplicate_behavior: DuplicateBehavior | None = None,
|
|
26
|
-
mask_error_details: bool =
|
|
27
|
+
mask_error_details: bool | None = None,
|
|
27
28
|
):
|
|
28
29
|
self._prompts: dict[str, Prompt] = {}
|
|
29
|
-
self.mask_error_details = mask_error_details
|
|
30
|
+
self.mask_error_details = mask_error_details or settings.mask_error_details
|
|
30
31
|
|
|
31
32
|
# Default to "warn" if None is provided
|
|
32
33
|
if duplicate_behavior is None:
|
|
@@ -40,9 +41,11 @@ class PromptManager:
|
|
|
40
41
|
|
|
41
42
|
self.duplicate_behavior = duplicate_behavior
|
|
42
43
|
|
|
43
|
-
def get_prompt(self, key: str) -> Prompt
|
|
44
|
+
def get_prompt(self, key: str) -> Prompt:
|
|
44
45
|
"""Get prompt by key."""
|
|
45
|
-
|
|
46
|
+
if key in self._prompts:
|
|
47
|
+
return self._prompts[key]
|
|
48
|
+
raise NotFoundError(f"Unknown prompt: {key}")
|
|
46
49
|
|
|
47
50
|
def get_prompts(self) -> dict[str, Prompt]:
|
|
48
51
|
"""Get all registered prompts, indexed by registered key."""
|
|
@@ -57,11 +60,12 @@ class PromptManager:
|
|
|
57
60
|
) -> FunctionPrompt:
|
|
58
61
|
"""Create a prompt from a function."""
|
|
59
62
|
# deprecated in 2.7.0
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
63
|
+
if settings.deprecation_warnings:
|
|
64
|
+
warnings.warn(
|
|
65
|
+
"PromptManager.add_prompt_from_fn() is deprecated. Use Prompt.from_function() and call add_prompt() instead.",
|
|
66
|
+
DeprecationWarning,
|
|
67
|
+
stacklevel=2,
|
|
68
|
+
)
|
|
65
69
|
prompt = FunctionPrompt.from_function(
|
|
66
70
|
fn, name=name, description=description, tags=tags
|
|
67
71
|
)
|
fastmcp/resources/resource.py
CHANGED
|
@@ -11,18 +11,17 @@ import pydantic_core
|
|
|
11
11
|
from mcp.types import Resource as MCPResource
|
|
12
12
|
from pydantic import (
|
|
13
13
|
AnyUrl,
|
|
14
|
-
BeforeValidator,
|
|
15
14
|
ConfigDict,
|
|
16
15
|
Field,
|
|
17
16
|
UrlConstraints,
|
|
18
|
-
ValidationInfo,
|
|
19
17
|
field_validator,
|
|
18
|
+
model_validator,
|
|
20
19
|
)
|
|
20
|
+
from typing_extensions import Self
|
|
21
21
|
|
|
22
22
|
from fastmcp.server.dependencies import get_context
|
|
23
|
+
from fastmcp.utilities.components import FastMCPComponent
|
|
23
24
|
from fastmcp.utilities.types import (
|
|
24
|
-
FastMCPBaseModel,
|
|
25
|
-
_convert_set_defaults,
|
|
26
25
|
find_kwarg_by_type,
|
|
27
26
|
)
|
|
28
27
|
|
|
@@ -30,7 +29,7 @@ if TYPE_CHECKING:
|
|
|
30
29
|
pass
|
|
31
30
|
|
|
32
31
|
|
|
33
|
-
class Resource(
|
|
32
|
+
class Resource(FastMCPComponent, abc.ABC):
|
|
34
33
|
"""Base class for all resources."""
|
|
35
34
|
|
|
36
35
|
model_config = ConfigDict(validate_default=True)
|
|
@@ -38,13 +37,7 @@ class Resource(FastMCPBaseModel, abc.ABC):
|
|
|
38
37
|
uri: Annotated[AnyUrl, UrlConstraints(host_required=False)] = Field(
|
|
39
38
|
default=..., description="URI of the resource"
|
|
40
39
|
)
|
|
41
|
-
name: str
|
|
42
|
-
description: str | None = Field(
|
|
43
|
-
default=None, description="Description of the resource"
|
|
44
|
-
)
|
|
45
|
-
tags: Annotated[set[str], BeforeValidator(_convert_set_defaults)] = Field(
|
|
46
|
-
default_factory=set, description="Tags for the resource"
|
|
47
|
-
)
|
|
40
|
+
name: str = Field(default="", description="Name of the resource")
|
|
48
41
|
mime_type: str = Field(
|
|
49
42
|
default="text/plain",
|
|
50
43
|
description="MIME type of the resource content",
|
|
@@ -59,6 +52,7 @@ class Resource(FastMCPBaseModel, abc.ABC):
|
|
|
59
52
|
description: str | None = None,
|
|
60
53
|
mime_type: str | None = None,
|
|
61
54
|
tags: set[str] | None = None,
|
|
55
|
+
enabled: bool | None = None,
|
|
62
56
|
) -> FunctionResource:
|
|
63
57
|
return FunctionResource.from_function(
|
|
64
58
|
fn=fn,
|
|
@@ -67,6 +61,7 @@ class Resource(FastMCPBaseModel, abc.ABC):
|
|
|
67
61
|
description=description,
|
|
68
62
|
mime_type=mime_type,
|
|
69
63
|
tags=tags,
|
|
64
|
+
enabled=enabled,
|
|
70
65
|
)
|
|
71
66
|
|
|
72
67
|
@field_validator("mime_type", mode="before")
|
|
@@ -77,27 +72,22 @@ class Resource(FastMCPBaseModel, abc.ABC):
|
|
|
77
72
|
return mime_type
|
|
78
73
|
return "text/plain"
|
|
79
74
|
|
|
80
|
-
@
|
|
81
|
-
|
|
82
|
-
def set_default_name(cls, name: str | None, info: ValidationInfo) -> str:
|
|
75
|
+
@model_validator(mode="after")
|
|
76
|
+
def set_default_name(self) -> Self:
|
|
83
77
|
"""Set default name from URI if not provided."""
|
|
84
|
-
if name:
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
78
|
+
if self.name:
|
|
79
|
+
pass
|
|
80
|
+
elif self.uri:
|
|
81
|
+
self.name = str(self.uri)
|
|
82
|
+
else:
|
|
83
|
+
raise ValueError("Either name or uri must be provided")
|
|
84
|
+
return self
|
|
89
85
|
|
|
90
86
|
@abc.abstractmethod
|
|
91
87
|
async def read(self) -> str | bytes:
|
|
92
88
|
"""Read the resource content."""
|
|
93
89
|
pass
|
|
94
90
|
|
|
95
|
-
def __eq__(self, other: object) -> bool:
|
|
96
|
-
if type(self) is not type(other):
|
|
97
|
-
return False
|
|
98
|
-
assert isinstance(other, type(self))
|
|
99
|
-
return self.model_dump() == other.model_dump()
|
|
100
|
-
|
|
101
91
|
def to_mcp_resource(self, **overrides: Any) -> MCPResource:
|
|
102
92
|
"""Convert the resource to an MCPResource."""
|
|
103
93
|
kwargs = {
|
|
@@ -108,6 +98,9 @@ class Resource(FastMCPBaseModel, abc.ABC):
|
|
|
108
98
|
}
|
|
109
99
|
return MCPResource(**kwargs | overrides)
|
|
110
100
|
|
|
101
|
+
def __repr__(self) -> str:
|
|
102
|
+
return f"{self.__class__.__name__}(uri={self.uri!r}, name={self.name!r}, description={self.description!r}, tags={self.tags})"
|
|
103
|
+
|
|
111
104
|
|
|
112
105
|
class FunctionResource(Resource):
|
|
113
106
|
"""A resource that defers data loading by wrapping a function.
|
|
@@ -133,6 +126,7 @@ class FunctionResource(Resource):
|
|
|
133
126
|
description: str | None = None,
|
|
134
127
|
mime_type: str | None = None,
|
|
135
128
|
tags: set[str] | None = None,
|
|
129
|
+
enabled: bool | None = None,
|
|
136
130
|
) -> FunctionResource:
|
|
137
131
|
"""Create a FunctionResource from a function."""
|
|
138
132
|
if isinstance(uri, str):
|
|
@@ -144,6 +138,7 @@ class FunctionResource(Resource):
|
|
|
144
138
|
description=description or fn.__doc__,
|
|
145
139
|
mime_type=mime_type or "text/plain",
|
|
146
140
|
tags=tags or set(),
|
|
141
|
+
enabled=enabled if enabled is not None else True,
|
|
147
142
|
)
|
|
148
143
|
|
|
149
144
|
async def read(self) -> str | bytes:
|
|
@@ -7,6 +7,7 @@ from typing import Any
|
|
|
7
7
|
|
|
8
8
|
from pydantic import AnyUrl
|
|
9
9
|
|
|
10
|
+
from fastmcp import settings
|
|
10
11
|
from fastmcp.exceptions import NotFoundError, ResourceError
|
|
11
12
|
from fastmcp.resources.resource import Resource
|
|
12
13
|
from fastmcp.resources.template import (
|
|
@@ -25,7 +26,7 @@ class ResourceManager:
|
|
|
25
26
|
def __init__(
|
|
26
27
|
self,
|
|
27
28
|
duplicate_behavior: DuplicateBehavior | None = None,
|
|
28
|
-
mask_error_details: bool =
|
|
29
|
+
mask_error_details: bool | None = None,
|
|
29
30
|
):
|
|
30
31
|
"""Initialize the ResourceManager.
|
|
31
32
|
|
|
@@ -37,7 +38,7 @@ class ResourceManager:
|
|
|
37
38
|
"""
|
|
38
39
|
self._resources: dict[str, Resource] = {}
|
|
39
40
|
self._templates: dict[str, ResourceTemplate] = {}
|
|
40
|
-
self.mask_error_details = mask_error_details
|
|
41
|
+
self.mask_error_details = mask_error_details or settings.mask_error_details
|
|
41
42
|
|
|
42
43
|
# Default to "warn" if None is provided
|
|
43
44
|
if duplicate_behavior is None:
|
|
@@ -122,11 +123,12 @@ class ResourceManager:
|
|
|
122
123
|
returns the existing resource.
|
|
123
124
|
"""
|
|
124
125
|
# deprecated in 2.7.0
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
126
|
+
if settings.deprecation_warnings:
|
|
127
|
+
warnings.warn(
|
|
128
|
+
"add_resource_from_fn is deprecated. Use Resource.from_function() and call add_resource() instead.",
|
|
129
|
+
DeprecationWarning,
|
|
130
|
+
stacklevel=2,
|
|
131
|
+
)
|
|
130
132
|
resource = Resource.from_function(
|
|
131
133
|
fn=fn,
|
|
132
134
|
uri=uri,
|
|
@@ -179,11 +181,12 @@ class ResourceManager:
|
|
|
179
181
|
) -> ResourceTemplate:
|
|
180
182
|
"""Create a template from a function."""
|
|
181
183
|
# deprecated in 2.7.0
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
184
|
+
if settings.deprecation_warnings:
|
|
185
|
+
warnings.warn(
|
|
186
|
+
"add_template_from_fn is deprecated. Use ResourceTemplate.from_function() and call add_template() instead.",
|
|
187
|
+
DeprecationWarning,
|
|
188
|
+
stacklevel=2,
|
|
189
|
+
)
|
|
187
190
|
template = ResourceTemplate.from_function(
|
|
188
191
|
fn,
|
|
189
192
|
uri_template=uri_template,
|