fastmcp 2.7.1__py3-none-any.whl → 2.8.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/__init__.py +4 -1
- fastmcp/cli/cli.py +3 -2
- fastmcp/client/auth/oauth.py +1 -1
- fastmcp/client/client.py +3 -1
- fastmcp/client/transports.py +35 -28
- fastmcp/exceptions.py +4 -0
- fastmcp/prompts/prompt.py +8 -18
- fastmcp/prompts/prompt_manager.py +7 -4
- fastmcp/resources/resource.py +21 -26
- fastmcp/resources/resource_manager.py +3 -2
- fastmcp/resources/template.py +8 -16
- fastmcp/server/auth/providers/bearer_env.py +8 -11
- fastmcp/server/openapi.py +65 -38
- fastmcp/server/proxy.py +27 -14
- fastmcp/server/server.py +320 -131
- fastmcp/settings.py +100 -37
- fastmcp/tools/__init__.py +2 -1
- fastmcp/tools/tool.py +114 -75
- fastmcp/tools/tool_manager.py +3 -2
- fastmcp/tools/tool_transform.py +665 -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 +0 -9
- {fastmcp-2.7.1.dist-info → fastmcp-2.8.0.dist-info}/METADATA +3 -1
- {fastmcp-2.7.1.dist-info → fastmcp-2.8.0.dist-info}/RECORD +30 -28
- {fastmcp-2.7.1.dist-info → fastmcp-2.8.0.dist-info}/WHEEL +0 -0
- {fastmcp-2.7.1.dist-info → fastmcp-2.8.0.dist-info}/entry_points.txt +0 -0
- {fastmcp-2.7.1.dist-info → fastmcp-2.8.0.dist-info}/licenses/LICENSE +0 -0
fastmcp/__init__.py
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
"""FastMCP - An ergonomic MCP interface."""
|
|
2
2
|
|
|
3
3
|
from importlib.metadata import version
|
|
4
|
+
from fastmcp.settings import Settings
|
|
5
|
+
|
|
6
|
+
settings = Settings()
|
|
4
7
|
|
|
5
8
|
from fastmcp.server.server import FastMCP
|
|
6
9
|
from fastmcp.server.context import Context
|
|
@@ -8,7 +11,7 @@ import fastmcp.server
|
|
|
8
11
|
|
|
9
12
|
from fastmcp.client import Client
|
|
10
13
|
from fastmcp.utilities.types import Image
|
|
11
|
-
from . import client
|
|
14
|
+
from . import client
|
|
12
15
|
|
|
13
16
|
__version__ = version("fastmcp")
|
|
14
17
|
__all__ = [
|
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
|
@@ -145,6 +145,7 @@ class Client(Generic[ClientTransportT]):
|
|
|
145
145
|
progress_handler: ProgressHandler | None = None,
|
|
146
146
|
timeout: datetime.timedelta | float | int | None = None,
|
|
147
147
|
init_timeout: datetime.timedelta | float | int | None = None,
|
|
148
|
+
client_info: mcp.types.Implementation | None = None,
|
|
148
149
|
auth: httpx.Auth | Literal["oauth"] | str | None = None,
|
|
149
150
|
):
|
|
150
151
|
self.transport = cast(ClientTransportT, infer_transport(transport))
|
|
@@ -165,7 +166,7 @@ class Client(Generic[ClientTransportT]):
|
|
|
165
166
|
|
|
166
167
|
# handle init handshake timeout
|
|
167
168
|
if init_timeout is None:
|
|
168
|
-
init_timeout = fastmcp.settings.
|
|
169
|
+
init_timeout = fastmcp.settings.client_init_timeout
|
|
169
170
|
if isinstance(init_timeout, datetime.timedelta):
|
|
170
171
|
init_timeout = init_timeout.total_seconds()
|
|
171
172
|
elif not init_timeout:
|
|
@@ -180,6 +181,7 @@ class Client(Generic[ClientTransportT]):
|
|
|
180
181
|
"logging_callback": create_log_callback(log_handler),
|
|
181
182
|
"message_handler": message_handler,
|
|
182
183
|
"read_timeout_seconds": timeout,
|
|
184
|
+
"client_info": client_info,
|
|
183
185
|
}
|
|
184
186
|
|
|
185
187
|
if roots is not None:
|
fastmcp/client/transports.py
CHANGED
|
@@ -8,27 +8,15 @@ 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
|
|
|
@@ -39,9 +27,6 @@ from fastmcp.server.server import FastMCP
|
|
|
39
27
|
from fastmcp.utilities.logging import get_logger
|
|
40
28
|
from fastmcp.utilities.mcp_config import MCPConfig, infer_transport_type_from_url
|
|
41
29
|
|
|
42
|
-
if TYPE_CHECKING:
|
|
43
|
-
from fastmcp.utilities.mcp_config import MCPConfig
|
|
44
|
-
|
|
45
30
|
logger = get_logger(__name__)
|
|
46
31
|
|
|
47
32
|
# TypeVar for preserving specific ClientTransport subclass types
|
|
@@ -65,11 +50,12 @@ __all__ = [
|
|
|
65
50
|
class SessionKwargs(TypedDict, total=False):
|
|
66
51
|
"""Keyword arguments for the MCP ClientSession constructor."""
|
|
67
52
|
|
|
53
|
+
read_timeout_seconds: datetime.timedelta | None
|
|
68
54
|
sampling_callback: SamplingFnT | None
|
|
69
55
|
list_roots_callback: ListRootsFnT | None
|
|
70
56
|
logging_callback: LoggingFnT | None
|
|
71
57
|
message_handler: MessageHandlerFnT | None
|
|
72
|
-
|
|
58
|
+
client_info: mcp.types.Implementation | None
|
|
73
59
|
|
|
74
60
|
|
|
75
61
|
class ClientTransport(abc.ABC):
|
|
@@ -662,24 +648,46 @@ class FastMCPTransport(ClientTransport):
|
|
|
662
648
|
tests or scenarios where client and server run in the same runtime.
|
|
663
649
|
"""
|
|
664
650
|
|
|
665
|
-
def __init__(self, mcp: FastMCP | FastMCP1Server):
|
|
651
|
+
def __init__(self, mcp: FastMCP | FastMCP1Server, raise_exceptions: bool = False):
|
|
666
652
|
"""Initialize a FastMCPTransport from a FastMCP server instance."""
|
|
667
653
|
|
|
668
654
|
# Accept both FastMCP 2.x and FastMCP 1.0 servers. Both expose a
|
|
669
655
|
# ``_mcp_server`` attribute pointing to the underlying MCP server
|
|
670
656
|
# implementation, so we can treat them identically.
|
|
671
657
|
self.server = mcp
|
|
658
|
+
self.raise_exceptions = raise_exceptions
|
|
672
659
|
|
|
673
660
|
@contextlib.asynccontextmanager
|
|
674
661
|
async def connect_session(
|
|
675
662
|
self, **session_kwargs: Unpack[SessionKwargs]
|
|
676
663
|
) -> AsyncIterator[ClientSession]:
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
664
|
+
async with create_client_server_memory_streams() as (
|
|
665
|
+
client_streams,
|
|
666
|
+
server_streams,
|
|
667
|
+
):
|
|
668
|
+
client_read, client_write = client_streams
|
|
669
|
+
server_read, server_write = server_streams
|
|
670
|
+
|
|
671
|
+
# Create a cancel scope for the server task
|
|
672
|
+
async with anyio.create_task_group() as tg:
|
|
673
|
+
tg.start_soon(
|
|
674
|
+
lambda: self.server._mcp_server.run(
|
|
675
|
+
server_read,
|
|
676
|
+
server_write,
|
|
677
|
+
self.server._mcp_server.create_initialization_options(),
|
|
678
|
+
raise_exceptions=self.raise_exceptions,
|
|
679
|
+
)
|
|
680
|
+
)
|
|
681
|
+
|
|
682
|
+
try:
|
|
683
|
+
async with ClientSession(
|
|
684
|
+
read_stream=client_read,
|
|
685
|
+
write_stream=client_write,
|
|
686
|
+
**session_kwargs,
|
|
687
|
+
) as client_session:
|
|
688
|
+
yield client_session
|
|
689
|
+
finally:
|
|
690
|
+
tg.cancel_scope.cancel()
|
|
683
691
|
|
|
684
692
|
def __repr__(self) -> str:
|
|
685
693
|
return f"<FastMCPTransport(server='{self.server.name}')>"
|
|
@@ -859,7 +867,6 @@ def infer_transport(
|
|
|
859
867
|
transport = infer_transport(config)
|
|
860
868
|
```
|
|
861
869
|
"""
|
|
862
|
-
from fastmcp.utilities.mcp_config import MCPConfig
|
|
863
870
|
|
|
864
871
|
# the transport is already a ClientTransport
|
|
865
872
|
if isinstance(transport, ClientTransport):
|
fastmcp/exceptions.py
CHANGED
fastmcp/prompts/prompt.py
CHANGED
|
@@ -5,21 +5,21 @@ 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
11
|
from mcp.types import EmbeddedResource, ImageContent, PromptMessage, Role, TextContent
|
|
12
12
|
from mcp.types import Prompt as MCPPrompt
|
|
13
13
|
from mcp.types import PromptArgument as MCPPromptArgument
|
|
14
|
-
from pydantic import
|
|
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
|
-
_convert_set_defaults,
|
|
23
23
|
find_kwarg_by_type,
|
|
24
24
|
get_cached_typeadapter,
|
|
25
25
|
)
|
|
@@ -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."""
|
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:
|
fastmcp/resources/template.py
CHANGED
|
@@ -5,12 +5,11 @@ from __future__ import annotations
|
|
|
5
5
|
import inspect
|
|
6
6
|
import re
|
|
7
7
|
from collections.abc import Callable
|
|
8
|
-
from typing import
|
|
8
|
+
from typing import Any
|
|
9
9
|
from urllib.parse import unquote
|
|
10
10
|
|
|
11
11
|
from mcp.types import ResourceTemplate as MCPResourceTemplate
|
|
12
12
|
from pydantic import (
|
|
13
|
-
BeforeValidator,
|
|
14
13
|
Field,
|
|
15
14
|
field_validator,
|
|
16
15
|
validate_call,
|
|
@@ -18,10 +17,9 @@ from pydantic import (
|
|
|
18
17
|
|
|
19
18
|
from fastmcp.resources.types import Resource
|
|
20
19
|
from fastmcp.server.dependencies import get_context
|
|
20
|
+
from fastmcp.utilities.components import FastMCPComponent
|
|
21
21
|
from fastmcp.utilities.json_schema import compress_schema
|
|
22
22
|
from fastmcp.utilities.types import (
|
|
23
|
-
FastMCPBaseModel,
|
|
24
|
-
_convert_set_defaults,
|
|
25
23
|
find_kwarg_by_type,
|
|
26
24
|
get_cached_typeadapter,
|
|
27
25
|
)
|
|
@@ -51,17 +49,12 @@ def match_uri_template(uri: str, uri_template: str) -> dict[str, str] | None:
|
|
|
51
49
|
return None
|
|
52
50
|
|
|
53
51
|
|
|
54
|
-
class ResourceTemplate(
|
|
52
|
+
class ResourceTemplate(FastMCPComponent):
|
|
55
53
|
"""A template for dynamically creating resources."""
|
|
56
54
|
|
|
57
55
|
uri_template: str = Field(
|
|
58
56
|
description="URI template with parameters (e.g. weather://{city}/current)"
|
|
59
57
|
)
|
|
60
|
-
name: str = Field(description="Name of the resource")
|
|
61
|
-
description: str | None = Field(description="Description of what the resource does")
|
|
62
|
-
tags: Annotated[set[str], BeforeValidator(_convert_set_defaults)] = Field(
|
|
63
|
-
default_factory=set, description="Tags for the resource"
|
|
64
|
-
)
|
|
65
58
|
mime_type: str = Field(
|
|
66
59
|
default="text/plain", description="MIME type of the resource content"
|
|
67
60
|
)
|
|
@@ -77,6 +70,7 @@ class ResourceTemplate(FastMCPBaseModel):
|
|
|
77
70
|
description: str | None = None,
|
|
78
71
|
mime_type: str | None = None,
|
|
79
72
|
tags: set[str] | None = None,
|
|
73
|
+
enabled: bool | None = None,
|
|
80
74
|
) -> FunctionResourceTemplate:
|
|
81
75
|
return FunctionResourceTemplate.from_function(
|
|
82
76
|
fn=fn,
|
|
@@ -85,6 +79,7 @@ class ResourceTemplate(FastMCPBaseModel):
|
|
|
85
79
|
description=description,
|
|
86
80
|
mime_type=mime_type,
|
|
87
81
|
tags=tags,
|
|
82
|
+
enabled=enabled,
|
|
88
83
|
)
|
|
89
84
|
|
|
90
85
|
@field_validator("mime_type", mode="before")
|
|
@@ -120,14 +115,9 @@ class ResourceTemplate(FastMCPBaseModel):
|
|
|
120
115
|
description=self.description,
|
|
121
116
|
mime_type=self.mime_type,
|
|
122
117
|
tags=self.tags,
|
|
118
|
+
enabled=self.enabled,
|
|
123
119
|
)
|
|
124
120
|
|
|
125
|
-
def __eq__(self, other: object) -> bool:
|
|
126
|
-
if type(self) is not type(other):
|
|
127
|
-
return False
|
|
128
|
-
assert isinstance(other, type(self))
|
|
129
|
-
return self.model_dump() == other.model_dump()
|
|
130
|
-
|
|
131
121
|
def to_mcp_template(self, **overrides: Any) -> MCPResourceTemplate:
|
|
132
122
|
"""Convert the resource template to an MCPResourceTemplate."""
|
|
133
123
|
kwargs = {
|
|
@@ -168,6 +158,7 @@ class FunctionResourceTemplate(ResourceTemplate):
|
|
|
168
158
|
description: str | None = None,
|
|
169
159
|
mime_type: str | None = None,
|
|
170
160
|
tags: set[str] | None = None,
|
|
161
|
+
enabled: bool | None = None,
|
|
171
162
|
) -> FunctionResourceTemplate:
|
|
172
163
|
"""Create a template from a function."""
|
|
173
164
|
from fastmcp.server.context import Context
|
|
@@ -250,4 +241,5 @@ class FunctionResourceTemplate(ResourceTemplate):
|
|
|
250
241
|
fn=fn,
|
|
251
242
|
parameters=parameters,
|
|
252
243
|
tags=tags or set(),
|
|
244
|
+
enabled=enabled if enabled is not None else True,
|
|
253
245
|
)
|
|
@@ -1,13 +1,10 @@
|
|
|
1
|
+
from types import EllipsisType
|
|
2
|
+
|
|
1
3
|
from pydantic_settings import BaseSettings, SettingsConfigDict
|
|
2
4
|
|
|
3
5
|
from fastmcp.server.auth.providers.bearer import BearerAuthProvider
|
|
4
6
|
|
|
5
7
|
|
|
6
|
-
# Sentinel object to indicate that a setting is not set
|
|
7
|
-
class _NotSet:
|
|
8
|
-
pass
|
|
9
|
-
|
|
10
|
-
|
|
11
8
|
class EnvBearerAuthProviderSettings(BaseSettings):
|
|
12
9
|
"""Settings for the BearerAuthProvider."""
|
|
13
10
|
|
|
@@ -33,11 +30,11 @@ class EnvBearerAuthProvider(BearerAuthProvider):
|
|
|
33
30
|
|
|
34
31
|
def __init__(
|
|
35
32
|
self,
|
|
36
|
-
public_key: str | None |
|
|
37
|
-
jwks_uri: str | None |
|
|
38
|
-
issuer: str | None |
|
|
39
|
-
audience: str | None |
|
|
40
|
-
required_scopes: list[str] | None |
|
|
33
|
+
public_key: str | None | EllipsisType = ...,
|
|
34
|
+
jwks_uri: str | None | EllipsisType = ...,
|
|
35
|
+
issuer: str | None | EllipsisType = ...,
|
|
36
|
+
audience: str | None | EllipsisType = ...,
|
|
37
|
+
required_scopes: list[str] | None | EllipsisType = ...,
|
|
41
38
|
):
|
|
42
39
|
"""
|
|
43
40
|
Initialize the provider.
|
|
@@ -57,6 +54,6 @@ class EnvBearerAuthProvider(BearerAuthProvider):
|
|
|
57
54
|
"required_scopes": required_scopes,
|
|
58
55
|
}
|
|
59
56
|
settings = EnvBearerAuthProviderSettings(
|
|
60
|
-
**{k: v for k, v in kwargs.items() if v is not
|
|
57
|
+
**{k: v for k, v in kwargs.items() if v is not ...}
|
|
61
58
|
)
|
|
62
59
|
super().__init__(**settings.model_dump())
|