fastmcp 2.5.1__py3-none-any.whl → 2.6.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 +1 -3
- fastmcp/client/__init__.py +3 -0
- fastmcp/client/auth/__init__.py +4 -0
- fastmcp/client/auth/bearer.py +17 -0
- fastmcp/client/auth/oauth.py +394 -0
- fastmcp/client/client.py +154 -38
- fastmcp/client/oauth_callback.py +310 -0
- fastmcp/client/transports.py +249 -23
- fastmcp/resources/template.py +1 -1
- fastmcp/server/auth/__init__.py +4 -0
- fastmcp/server/auth/auth.py +45 -0
- fastmcp/server/auth/providers/bearer.py +377 -0
- fastmcp/server/auth/providers/bearer_env.py +62 -0
- fastmcp/server/auth/providers/in_memory.py +330 -0
- fastmcp/server/dependencies.py +27 -6
- fastmcp/server/http.py +38 -66
- fastmcp/server/openapi.py +2 -0
- fastmcp/server/server.py +64 -32
- fastmcp/settings.py +34 -8
- fastmcp/tools/tool.py +26 -5
- fastmcp/tools/tool_manager.py +2 -0
- fastmcp/utilities/http.py +8 -0
- fastmcp/utilities/tests.py +22 -10
- {fastmcp-2.5.1.dist-info → fastmcp-2.6.0.dist-info}/METADATA +9 -8
- {fastmcp-2.5.1.dist-info → fastmcp-2.6.0.dist-info}/RECORD +29 -22
- fastmcp/client/base.py +0 -0
- fastmcp/low_level/README.md +0 -1
- fastmcp/py.typed +0 -0
- /fastmcp/{low_level → server/auth/providers}/__init__.py +0 -0
- {fastmcp-2.5.1.dist-info → fastmcp-2.6.0.dist-info}/WHEEL +0 -0
- {fastmcp-2.5.1.dist-info → fastmcp-2.6.0.dist-info}/entry_points.txt +0 -0
- {fastmcp-2.5.1.dist-info → fastmcp-2.6.0.dist-info}/licenses/LICENSE +0 -0
fastmcp/server/server.py
CHANGED
|
@@ -18,7 +18,6 @@ from typing import TYPE_CHECKING, Any, Generic, Literal
|
|
|
18
18
|
import anyio
|
|
19
19
|
import httpx
|
|
20
20
|
import uvicorn
|
|
21
|
-
from mcp.server.auth.provider import OAuthAuthorizationServerProvider
|
|
22
21
|
from mcp.server.lowlevel.helper_types import ReadResourceContents
|
|
23
22
|
from mcp.server.lowlevel.server import LifespanResultT, NotificationOptions
|
|
24
23
|
from mcp.server.lowlevel.server import Server as MCPServer
|
|
@@ -48,6 +47,8 @@ from fastmcp.prompts import Prompt, PromptManager
|
|
|
48
47
|
from fastmcp.prompts.prompt import PromptResult
|
|
49
48
|
from fastmcp.resources import Resource, ResourceManager
|
|
50
49
|
from fastmcp.resources.template import ResourceTemplate
|
|
50
|
+
from fastmcp.server.auth.auth import OAuthProvider
|
|
51
|
+
from fastmcp.server.auth.providers.bearer_env import EnvBearerAuthProvider
|
|
51
52
|
from fastmcp.server.http import (
|
|
52
53
|
StarletteWithLifespan,
|
|
53
54
|
create_sse_app,
|
|
@@ -62,7 +63,7 @@ from fastmcp.utilities.mcp_config import MCPConfig
|
|
|
62
63
|
|
|
63
64
|
if TYPE_CHECKING:
|
|
64
65
|
from fastmcp.client import Client
|
|
65
|
-
from fastmcp.client.transports import ClientTransport
|
|
66
|
+
from fastmcp.client.transports import ClientTransport, ClientTransportT
|
|
66
67
|
from fastmcp.server.openapi import ComponentFn as OpenAPIComponentFn
|
|
67
68
|
from fastmcp.server.openapi import FastMCPOpenAPI, RouteMap
|
|
68
69
|
from fastmcp.server.openapi import RouteMapFn as OpenAPIRouteMapFn
|
|
@@ -110,8 +111,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
110
111
|
self,
|
|
111
112
|
name: str | None = None,
|
|
112
113
|
instructions: str | None = None,
|
|
113
|
-
|
|
114
|
-
| None = None,
|
|
114
|
+
auth: OAuthProvider | None = None,
|
|
115
115
|
lifespan: (
|
|
116
116
|
Callable[
|
|
117
117
|
[FastMCP[LifespanResultT]],
|
|
@@ -128,6 +128,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
128
128
|
on_duplicate_prompts: DuplicateBehavior | None = None,
|
|
129
129
|
resource_prefix_format: Literal["protocol", "path"] | None = None,
|
|
130
130
|
mask_error_details: bool | None = None,
|
|
131
|
+
tools: list[Tool | Callable[..., Any]] | None = None,
|
|
131
132
|
**settings: Any,
|
|
132
133
|
):
|
|
133
134
|
if settings:
|
|
@@ -186,13 +187,16 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
186
187
|
lifespan=_lifespan_wrapper(self, lifespan),
|
|
187
188
|
)
|
|
188
189
|
|
|
189
|
-
if
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
190
|
+
if auth is None and self.settings.default_auth_provider == "bearer_env":
|
|
191
|
+
auth = EnvBearerAuthProvider()
|
|
192
|
+
self.auth = auth
|
|
193
|
+
|
|
194
|
+
if tools:
|
|
195
|
+
for tool in tools:
|
|
196
|
+
if isinstance(tool, Tool):
|
|
197
|
+
self._tool_manager.add_tool(tool)
|
|
198
|
+
else:
|
|
199
|
+
self.add_tool(tool)
|
|
196
200
|
|
|
197
201
|
# Set up MCP protocol handlers
|
|
198
202
|
self._setup_handlers()
|
|
@@ -257,9 +261,15 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
257
261
|
"""Get all registered tools, indexed by registered key."""
|
|
258
262
|
if (tools := self._cache.get("tools")) is self._cache.NOT_FOUND:
|
|
259
263
|
tools: dict[str, Tool] = {}
|
|
260
|
-
for server in self._mounted_servers.
|
|
261
|
-
|
|
262
|
-
|
|
264
|
+
for prefix, server in self._mounted_servers.items():
|
|
265
|
+
try:
|
|
266
|
+
server_tools = await server.get_tools()
|
|
267
|
+
tools.update(server_tools)
|
|
268
|
+
except Exception as e:
|
|
269
|
+
logger.warning(
|
|
270
|
+
f"Failed to get tools from mounted server '{prefix}': {e}"
|
|
271
|
+
)
|
|
272
|
+
continue
|
|
263
273
|
tools.update(self._tool_manager.get_tools())
|
|
264
274
|
self._cache.set("tools", tools)
|
|
265
275
|
return tools
|
|
@@ -268,9 +278,15 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
268
278
|
"""Get all registered resources, indexed by registered key."""
|
|
269
279
|
if (resources := self._cache.get("resources")) is self._cache.NOT_FOUND:
|
|
270
280
|
resources: dict[str, Resource] = {}
|
|
271
|
-
for server in self._mounted_servers.
|
|
272
|
-
|
|
273
|
-
|
|
281
|
+
for prefix, server in self._mounted_servers.items():
|
|
282
|
+
try:
|
|
283
|
+
server_resources = await server.get_resources()
|
|
284
|
+
resources.update(server_resources)
|
|
285
|
+
except Exception as e:
|
|
286
|
+
logger.warning(
|
|
287
|
+
f"Failed to get resources from mounted server '{prefix}': {e}"
|
|
288
|
+
)
|
|
289
|
+
continue
|
|
274
290
|
resources.update(self._resource_manager.get_resources())
|
|
275
291
|
self._cache.set("resources", resources)
|
|
276
292
|
return resources
|
|
@@ -281,9 +297,16 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
281
297
|
templates := self._cache.get("resource_templates")
|
|
282
298
|
) is self._cache.NOT_FOUND:
|
|
283
299
|
templates: dict[str, ResourceTemplate] = {}
|
|
284
|
-
for server in self._mounted_servers.
|
|
285
|
-
|
|
286
|
-
|
|
300
|
+
for prefix, server in self._mounted_servers.items():
|
|
301
|
+
try:
|
|
302
|
+
server_templates = await server.get_resource_templates()
|
|
303
|
+
templates.update(server_templates)
|
|
304
|
+
except Exception as e:
|
|
305
|
+
logger.warning(
|
|
306
|
+
"Failed to get resource templates from mounted server "
|
|
307
|
+
f"'{prefix}': {e}"
|
|
308
|
+
)
|
|
309
|
+
continue
|
|
287
310
|
templates.update(self._resource_manager.get_templates())
|
|
288
311
|
self._cache.set("resource_templates", templates)
|
|
289
312
|
return templates
|
|
@@ -294,9 +317,15 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
294
317
|
"""
|
|
295
318
|
if (prompts := self._cache.get("prompts")) is self._cache.NOT_FOUND:
|
|
296
319
|
prompts: dict[str, Prompt] = {}
|
|
297
|
-
for server in self._mounted_servers.
|
|
298
|
-
|
|
299
|
-
|
|
320
|
+
for prefix, server in self._mounted_servers.items():
|
|
321
|
+
try:
|
|
322
|
+
server_prompts = await server.get_prompts()
|
|
323
|
+
prompts.update(server_prompts)
|
|
324
|
+
except Exception as e:
|
|
325
|
+
logger.warning(
|
|
326
|
+
f"Failed to get prompts from mounted server '{prefix}': {e}"
|
|
327
|
+
)
|
|
328
|
+
continue
|
|
300
329
|
prompts.update(self._prompt_manager.get_prompts())
|
|
301
330
|
self._cache.set("prompts", prompts)
|
|
302
331
|
return prompts
|
|
@@ -472,6 +501,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
472
501
|
description: str | None = None,
|
|
473
502
|
tags: set[str] | None = None,
|
|
474
503
|
annotations: ToolAnnotations | dict[str, Any] | None = None,
|
|
504
|
+
exclude_args: list[str] | None = None,
|
|
475
505
|
) -> None:
|
|
476
506
|
"""Add a tool to the server.
|
|
477
507
|
|
|
@@ -494,6 +524,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
494
524
|
description=description,
|
|
495
525
|
tags=tags,
|
|
496
526
|
annotations=annotations,
|
|
527
|
+
exclude_args=exclude_args,
|
|
497
528
|
)
|
|
498
529
|
self._cache.clear()
|
|
499
530
|
|
|
@@ -515,6 +546,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
515
546
|
description: str | None = None,
|
|
516
547
|
tags: set[str] | None = None,
|
|
517
548
|
annotations: ToolAnnotations | dict[str, Any] | None = None,
|
|
549
|
+
exclude_args: list[str] | None = None,
|
|
518
550
|
) -> Callable[[AnyFunction], AnyFunction]:
|
|
519
551
|
"""Decorator to register a tool.
|
|
520
552
|
|
|
@@ -558,6 +590,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
558
590
|
description=description,
|
|
559
591
|
tags=tags,
|
|
560
592
|
annotations=annotations,
|
|
593
|
+
exclude_args=exclude_args,
|
|
561
594
|
)
|
|
562
595
|
return fn
|
|
563
596
|
|
|
@@ -802,7 +835,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
802
835
|
"""
|
|
803
836
|
host = host or self.settings.host
|
|
804
837
|
port = port or self.settings.port
|
|
805
|
-
default_log_level_to_use = log_level or self.settings.log_level.lower()
|
|
838
|
+
default_log_level_to_use = (log_level or self.settings.log_level).lower()
|
|
806
839
|
|
|
807
840
|
app = self.http_app(path=path, transport=transport, middleware=middleware)
|
|
808
841
|
|
|
@@ -878,8 +911,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
878
911
|
server=self,
|
|
879
912
|
message_path=message_path or self.settings.message_path,
|
|
880
913
|
sse_path=path or self.settings.sse_path,
|
|
881
|
-
|
|
882
|
-
auth_settings=self.settings.auth,
|
|
914
|
+
auth=self.auth,
|
|
883
915
|
debug=self.settings.debug,
|
|
884
916
|
middleware=middleware,
|
|
885
917
|
)
|
|
@@ -926,8 +958,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
926
958
|
server=self,
|
|
927
959
|
streamable_http_path=path or self.settings.streamable_http_path,
|
|
928
960
|
event_store=None,
|
|
929
|
-
|
|
930
|
-
auth_settings=self.settings.auth,
|
|
961
|
+
auth=self.auth,
|
|
931
962
|
json_response=self.settings.json_response,
|
|
932
963
|
stateless_http=self.settings.stateless_http,
|
|
933
964
|
debug=self.settings.debug,
|
|
@@ -938,8 +969,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
938
969
|
server=self,
|
|
939
970
|
message_path=self.settings.message_path,
|
|
940
971
|
sse_path=path or self.settings.sse_path,
|
|
941
|
-
|
|
942
|
-
auth_settings=self.settings.auth,
|
|
972
|
+
auth=self.auth,
|
|
943
973
|
debug=self.settings.debug,
|
|
944
974
|
middleware=middleware,
|
|
945
975
|
)
|
|
@@ -1263,7 +1293,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
1263
1293
|
@classmethod
|
|
1264
1294
|
def as_proxy(
|
|
1265
1295
|
cls,
|
|
1266
|
-
backend: Client
|
|
1296
|
+
backend: Client[ClientTransportT]
|
|
1267
1297
|
| ClientTransport
|
|
1268
1298
|
| FastMCP[Any]
|
|
1269
1299
|
| AnyUrl
|
|
@@ -1291,7 +1321,9 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
1291
1321
|
return FastMCPProxy(client=client, **settings)
|
|
1292
1322
|
|
|
1293
1323
|
@classmethod
|
|
1294
|
-
def from_client(
|
|
1324
|
+
def from_client(
|
|
1325
|
+
cls, client: Client[ClientTransportT], **settings: Any
|
|
1326
|
+
) -> FastMCPProxy:
|
|
1295
1327
|
"""
|
|
1296
1328
|
Create a FastMCP proxy server from a FastMCP client.
|
|
1297
1329
|
"""
|
fastmcp/settings.py
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
from __future__ import annotations as _annotations
|
|
2
2
|
|
|
3
3
|
import inspect
|
|
4
|
-
from
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Annotated, Literal
|
|
5
6
|
|
|
6
|
-
from mcp.server.auth.settings import AuthSettings
|
|
7
7
|
from pydantic import Field, model_validator
|
|
8
|
-
from pydantic_settings import
|
|
8
|
+
from pydantic_settings import (
|
|
9
|
+
BaseSettings,
|
|
10
|
+
SettingsConfigDict,
|
|
11
|
+
)
|
|
9
12
|
from typing_extensions import Self
|
|
10
13
|
|
|
11
|
-
if TYPE_CHECKING:
|
|
12
|
-
pass
|
|
13
|
-
|
|
14
14
|
LOG_LEVEL = Literal["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]
|
|
15
15
|
|
|
16
16
|
DuplicateBehavior = Literal["warn", "error", "replace", "ignore"]
|
|
@@ -27,6 +27,8 @@ class Settings(BaseSettings):
|
|
|
27
27
|
nested_model_default_partial_update=True,
|
|
28
28
|
)
|
|
29
29
|
|
|
30
|
+
home: Path = Path.home() / ".fastmcp"
|
|
31
|
+
|
|
30
32
|
test_mode: bool = False
|
|
31
33
|
log_level: LOG_LEVEL = "INFO"
|
|
32
34
|
enable_rich_tracebacks: Annotated[
|
|
@@ -87,6 +89,13 @@ class Settings(BaseSettings):
|
|
|
87
89
|
),
|
|
88
90
|
] = False
|
|
89
91
|
|
|
92
|
+
client_init_timeout: Annotated[
|
|
93
|
+
float | None,
|
|
94
|
+
Field(
|
|
95
|
+
description="The timeout for the client's initialization handshake, in seconds. Set to None or 0 to disable.",
|
|
96
|
+
),
|
|
97
|
+
] = None
|
|
98
|
+
|
|
90
99
|
@model_validator(mode="after")
|
|
91
100
|
def setup_logging(self) -> Self:
|
|
92
101
|
"""Finalize the settings."""
|
|
@@ -164,13 +173,30 @@ class ServerSettings(BaseSettings):
|
|
|
164
173
|
# cache settings (for checking mounted servers)
|
|
165
174
|
cache_expiration_seconds: float = 0
|
|
166
175
|
|
|
167
|
-
auth: AuthSettings | None = None
|
|
168
|
-
|
|
169
176
|
# StreamableHTTP settings
|
|
170
177
|
json_response: bool = False
|
|
171
178
|
stateless_http: bool = (
|
|
172
179
|
False # If True, uses true stateless mode (new transport per request)
|
|
173
180
|
)
|
|
174
181
|
|
|
182
|
+
# Auth settings
|
|
183
|
+
default_auth_provider: Annotated[
|
|
184
|
+
Literal["bearer_env"] | None,
|
|
185
|
+
Field(
|
|
186
|
+
description=inspect.cleandoc(
|
|
187
|
+
"""
|
|
188
|
+
Configure the authentication provider. This setting is intended only to
|
|
189
|
+
be used for remote confirugation of providers that fully support
|
|
190
|
+
environment variable configuration.
|
|
191
|
+
|
|
192
|
+
If None, no automatic configuration will take place.
|
|
193
|
+
|
|
194
|
+
This setting is *always* overriden by any auth provider passed to the
|
|
195
|
+
FastMCP constructor.
|
|
196
|
+
"""
|
|
197
|
+
),
|
|
198
|
+
),
|
|
199
|
+
] = None
|
|
200
|
+
|
|
175
201
|
|
|
176
202
|
settings = Settings()
|
fastmcp/tools/tool.py
CHANGED
|
@@ -36,7 +36,9 @@ class Tool(BaseModel):
|
|
|
36
36
|
|
|
37
37
|
fn: Callable[..., Any]
|
|
38
38
|
name: str = Field(description="Name of the tool")
|
|
39
|
-
description: str = Field(
|
|
39
|
+
description: str | None = Field(
|
|
40
|
+
default=None, description="Description of what the tool does"
|
|
41
|
+
)
|
|
40
42
|
parameters: dict[str, Any] = Field(description="JSON schema for tool parameters")
|
|
41
43
|
tags: Annotated[set[str], BeforeValidator(_convert_set_defaults)] = Field(
|
|
42
44
|
default_factory=set, description="Tags for the tool"
|
|
@@ -44,6 +46,10 @@ class Tool(BaseModel):
|
|
|
44
46
|
annotations: ToolAnnotations | None = Field(
|
|
45
47
|
None, description="Additional annotations about the tool"
|
|
46
48
|
)
|
|
49
|
+
exclude_args: list[str] | None = Field(
|
|
50
|
+
None,
|
|
51
|
+
description="Arguments to exclude from the tool schema, such as State, Memory, or Credential",
|
|
52
|
+
)
|
|
47
53
|
serializer: Callable[[Any], str] | None = Field(
|
|
48
54
|
None, description="Optional custom serializer for tool results"
|
|
49
55
|
)
|
|
@@ -56,6 +62,7 @@ class Tool(BaseModel):
|
|
|
56
62
|
description: str | None = None,
|
|
57
63
|
tags: set[str] | None = None,
|
|
58
64
|
annotations: ToolAnnotations | None = None,
|
|
65
|
+
exclude_args: list[str] | None = None,
|
|
59
66
|
serializer: Callable[[Any], str] | None = None,
|
|
60
67
|
) -> Tool:
|
|
61
68
|
"""Create a Tool from a function."""
|
|
@@ -69,12 +76,24 @@ class Tool(BaseModel):
|
|
|
69
76
|
if param.kind == inspect.Parameter.VAR_KEYWORD:
|
|
70
77
|
raise ValueError("Functions with **kwargs are not supported as tools")
|
|
71
78
|
|
|
79
|
+
if exclude_args:
|
|
80
|
+
for arg_name in exclude_args:
|
|
81
|
+
if arg_name not in sig.parameters:
|
|
82
|
+
raise ValueError(
|
|
83
|
+
f"Parameter '{arg_name}' in exclude_args does not exist in function."
|
|
84
|
+
)
|
|
85
|
+
param = sig.parameters[arg_name]
|
|
86
|
+
if param.default == inspect.Parameter.empty:
|
|
87
|
+
raise ValueError(
|
|
88
|
+
f"Parameter '{arg_name}' in exclude_args must have a default value."
|
|
89
|
+
)
|
|
90
|
+
|
|
72
91
|
func_name = name or getattr(fn, "__name__", None) or fn.__class__.__name__
|
|
73
92
|
|
|
74
93
|
if func_name == "<lambda>":
|
|
75
94
|
raise ValueError("You must provide a name for lambda functions")
|
|
76
95
|
|
|
77
|
-
func_doc = description or fn.__doc__
|
|
96
|
+
func_doc = description or fn.__doc__
|
|
78
97
|
|
|
79
98
|
# if the fn is a callable class, we need to get the __call__ method from here out
|
|
80
99
|
if not inspect.isroutine(fn):
|
|
@@ -83,11 +102,12 @@ class Tool(BaseModel):
|
|
|
83
102
|
type_adapter = get_cached_typeadapter(fn)
|
|
84
103
|
schema = type_adapter.json_schema()
|
|
85
104
|
|
|
105
|
+
prune_params: list[str] = []
|
|
86
106
|
context_kwarg = find_kwarg_by_type(fn, kwarg_type=Context)
|
|
87
107
|
if context_kwarg:
|
|
88
|
-
prune_params
|
|
89
|
-
|
|
90
|
-
prune_params
|
|
108
|
+
prune_params.append(context_kwarg)
|
|
109
|
+
if exclude_args:
|
|
110
|
+
prune_params.extend(exclude_args)
|
|
91
111
|
|
|
92
112
|
schema = compress_schema(schema, prune_params=prune_params)
|
|
93
113
|
|
|
@@ -98,6 +118,7 @@ class Tool(BaseModel):
|
|
|
98
118
|
parameters=schema,
|
|
99
119
|
tags=tags or set(),
|
|
100
120
|
annotations=annotations,
|
|
121
|
+
exclude_args=exclude_args,
|
|
101
122
|
serializer=serializer,
|
|
102
123
|
)
|
|
103
124
|
|
fastmcp/tools/tool_manager.py
CHANGED
|
@@ -66,6 +66,7 @@ class ToolManager:
|
|
|
66
66
|
description: str | None = None,
|
|
67
67
|
tags: set[str] | None = None,
|
|
68
68
|
annotations: ToolAnnotations | None = None,
|
|
69
|
+
exclude_args: list[str] | None = None,
|
|
69
70
|
) -> Tool:
|
|
70
71
|
"""Add a tool to the server."""
|
|
71
72
|
tool = Tool.from_function(
|
|
@@ -75,6 +76,7 @@ class ToolManager:
|
|
|
75
76
|
tags=tags,
|
|
76
77
|
annotations=annotations,
|
|
77
78
|
serializer=self._serializer,
|
|
79
|
+
exclude_args=exclude_args,
|
|
78
80
|
)
|
|
79
81
|
return self.add_tool(tool)
|
|
80
82
|
|
fastmcp/utilities/tests.py
CHANGED
|
@@ -11,6 +11,7 @@ from typing import TYPE_CHECKING, Any, Literal
|
|
|
11
11
|
import uvicorn
|
|
12
12
|
|
|
13
13
|
from fastmcp.settings import settings
|
|
14
|
+
from fastmcp.utilities.http import find_available_port
|
|
14
15
|
|
|
15
16
|
if TYPE_CHECKING:
|
|
16
17
|
from fastmcp.server.server import FastMCP
|
|
@@ -71,30 +72,38 @@ def _run_server(mcp_server: FastMCP, transport: Literal["sse"], port: int) -> No
|
|
|
71
72
|
|
|
72
73
|
@contextmanager
|
|
73
74
|
def run_server_in_process(
|
|
74
|
-
server_fn: Callable[..., None],
|
|
75
|
+
server_fn: Callable[..., None],
|
|
76
|
+
*args,
|
|
77
|
+
provide_host_and_port: bool = True,
|
|
78
|
+
**kwargs,
|
|
75
79
|
) -> Generator[str, None, None]:
|
|
76
80
|
"""
|
|
77
|
-
Context manager that runs a
|
|
78
|
-
server URL. When the context manager is exited, the server process is killed.
|
|
81
|
+
Context manager that runs a FastMCP server in a separate process and
|
|
82
|
+
returns the server URL. When the context manager is exited, the server process is killed.
|
|
79
83
|
|
|
80
84
|
Args:
|
|
81
|
-
|
|
85
|
+
server_fn: The function that runs a FastMCP server. FastMCP servers are
|
|
86
|
+
not pickleable, so we need a function that creates and runs one.
|
|
87
|
+
*args: Arguments to pass to the server function.
|
|
88
|
+
provide_host_and_port: Whether to provide the host and port to the server function as kwargs.
|
|
89
|
+
**kwargs: Keyword arguments to pass to the server function.
|
|
82
90
|
|
|
83
91
|
Returns:
|
|
84
92
|
The server URL.
|
|
85
93
|
"""
|
|
86
94
|
host = "127.0.0.1"
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
95
|
+
port = find_available_port()
|
|
96
|
+
|
|
97
|
+
if provide_host_and_port:
|
|
98
|
+
kwargs |= {"host": host, "port": port}
|
|
90
99
|
|
|
91
100
|
proc = multiprocessing.Process(
|
|
92
|
-
target=server_fn, args=
|
|
101
|
+
target=server_fn, args=args, kwargs=kwargs, daemon=True
|
|
93
102
|
)
|
|
94
103
|
proc.start()
|
|
95
104
|
|
|
96
105
|
# Wait for server to be running
|
|
97
|
-
max_attempts =
|
|
106
|
+
max_attempts = 10
|
|
98
107
|
attempt = 0
|
|
99
108
|
while attempt < max_attempts and proc.is_alive():
|
|
100
109
|
try:
|
|
@@ -102,7 +111,10 @@ def run_server_in_process(
|
|
|
102
111
|
s.connect((host, port))
|
|
103
112
|
break
|
|
104
113
|
except ConnectionRefusedError:
|
|
105
|
-
|
|
114
|
+
if attempt < 3:
|
|
115
|
+
time.sleep(0.01)
|
|
116
|
+
else:
|
|
117
|
+
time.sleep(0.1)
|
|
106
118
|
attempt += 1
|
|
107
119
|
else:
|
|
108
120
|
raise RuntimeError(f"Server failed to start after {max_attempts} attempts")
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: fastmcp
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.6.0
|
|
4
4
|
Summary: The fast, Pythonic way to build MCP servers.
|
|
5
5
|
Project-URL: Homepage, https://gofastmcp.com
|
|
6
6
|
Project-URL: Repository, https://github.com/jlowin/fastmcp
|
|
@@ -17,9 +17,10 @@ Classifier: Programming Language :: Python :: 3.12
|
|
|
17
17
|
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
18
18
|
Classifier: Typing :: Typed
|
|
19
19
|
Requires-Python: >=3.10
|
|
20
|
+
Requires-Dist: authlib>=1.5.2
|
|
20
21
|
Requires-Dist: exceptiongroup>=1.2.2
|
|
21
22
|
Requires-Dist: httpx>=0.28.1
|
|
22
|
-
Requires-Dist: mcp<2.0.0,>=1.9.
|
|
23
|
+
Requires-Dist: mcp<2.0.0,>=1.9.2
|
|
23
24
|
Requires-Dist: openapi-pydantic>=0.5.1
|
|
24
25
|
Requires-Dist: python-dotenv>=1.1.0
|
|
25
26
|
Requires-Dist: rich>=13.9.4
|
|
@@ -41,14 +42,14 @@ Description-Content-Type: text/markdown
|
|
|
41
42
|
<a href="https://trendshift.io/repositories/13266" target="_blank"><img src="https://trendshift.io/api/badge/repositories/13266" alt="jlowin%2Ffastmcp | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
|
|
42
43
|
</div>
|
|
43
44
|
|
|
44
|
-
> [!
|
|
45
|
-
> ####
|
|
45
|
+
> [!Note]
|
|
46
|
+
> #### Beyond the Protocol
|
|
47
|
+
>
|
|
48
|
+
> FastMCP is the standard framework for working with the Model Context Protocol. FastMCP 1.0 was incorporated into the [official low-level Python SDK](https://github.com/modelcontextprotocol/python-sdk), and FastMCP 2.0 *(this project)* provides a complete toolkit for working with the MCP ecosystem.
|
|
46
49
|
>
|
|
47
|
-
> FastMCP
|
|
50
|
+
> FastMCP has a comprehensive set of features that go far beyond the core MCP specification, all in service of providing **the simplest path to production**. These include client support, server composition, auth, automatic generation from OpenAPI specs, remote server proxying, built-in testing tools, integrations, and more.
|
|
48
51
|
>
|
|
49
|
-
>
|
|
50
|
-
>
|
|
51
|
-
> FastMCP 2.0 is the complete toolkit for modern AI applications. Ready to upgrade or get started? Follow the [installation instructions](https://gofastmcp.com/getting-started/installation), which include specific steps for upgrading from the official MCP SDK.
|
|
52
|
+
> Ready to upgrade or get started? Follow the [installation instructions](/getting-started/installation), which include specific steps for upgrading from the official MCP SDK.
|
|
52
53
|
|
|
53
54
|
---
|
|
54
55
|
|
|
@@ -1,19 +1,21 @@
|
|
|
1
1
|
fastmcp/__init__.py,sha256=yTAqLZORsPqbr7AE0ayw6zIYBeMlxQlI-3HE2WqbvHk,435
|
|
2
2
|
fastmcp/exceptions.py,sha256=YvaKqOT3w0boXF9ylIoaSIzW9XiQ1qLFG1LZq6B60H8,680
|
|
3
|
-
fastmcp/py
|
|
4
|
-
fastmcp/settings.py,sha256=ES59HUoZGLCtBiCKjf4ioVXjPSZtKLJrXhBN4OH_1N4,5356
|
|
3
|
+
fastmcp/settings.py,sha256=DbFdWx4EVHhJZF6e2GB0pBMCSjc425kXIVY8mqfOVX8,6175
|
|
5
4
|
fastmcp/cli/__init__.py,sha256=Ii284TNoG5lxTP40ETMGhHEq3lQZWxu9m9JuU57kUpQ,87
|
|
6
5
|
fastmcp/cli/claude.py,sha256=IAlcZ4qZKBBj09jZUMEx7EANZE_IR3vcu7zOBJmMOuU,4567
|
|
7
|
-
fastmcp/cli/cli.py,sha256=
|
|
6
|
+
fastmcp/cli/cli.py,sha256=CQxpRTXgnQQynGJLEV5g1FnLMaiWoiUgefnMZ7VxS4o,12367
|
|
8
7
|
fastmcp/cli/run.py,sha256=o7Ge6JZKXYwlY2vYdMNoVX8agBchAaeU_73iPndojIM,5351
|
|
9
|
-
fastmcp/client/__init__.py,sha256=
|
|
10
|
-
fastmcp/client/
|
|
11
|
-
fastmcp/client/client.py,sha256=QJsb8PHheUTn4UPG9kxzgn8M10g0cUkFttnGj-OgPdk,20847
|
|
8
|
+
fastmcp/client/__init__.py,sha256=kd2hhSuD8rZuF87c9zlPJP_icJ-Rx3exyNoK0EzfOtE,617
|
|
9
|
+
fastmcp/client/client.py,sha256=vQk0l6htoD6CyO0le8q23iYd5hX1l8NIbxchedbWqgE,24872
|
|
12
10
|
fastmcp/client/logging.py,sha256=hOPRailZUp89RUck6V4HPaWVZinVrNY8HD4hD0dd-fE,822
|
|
11
|
+
fastmcp/client/oauth_callback.py,sha256=ODAnVX-ettL82RuI5KpfkKf8iDtYMDue3Tnab5sjQtM,10071
|
|
13
12
|
fastmcp/client/progress.py,sha256=WjLLDbUKMsx8DK-fqO7AGsXb83ak-6BMrLvzzznGmcI,1043
|
|
14
13
|
fastmcp/client/roots.py,sha256=IxI_bHwHTmg6c2H-s1av1ZgrRnNDieHtYwdGFbzXT5c,2471
|
|
15
14
|
fastmcp/client/sampling.py,sha256=UlDHxnd6k_HoU8RA3ob0g8-e6haJBc9u27N_v291QoI,1698
|
|
16
|
-
fastmcp/client/transports.py,sha256=
|
|
15
|
+
fastmcp/client/transports.py,sha256=XBbw5nDzDQffeWmfDgWgXZUOX0hJqk3fdusnqqUge9c,31822
|
|
16
|
+
fastmcp/client/auth/__init__.py,sha256=4DNsfp4iaQeBcpds0JDdMn6Mmfud44stWLsret0sVKY,91
|
|
17
|
+
fastmcp/client/auth/bearer.py,sha256=MFEFqcH6u_V86msYiOsEFKN5ks1V9BnBNiPsPLHUTqo,399
|
|
18
|
+
fastmcp/client/auth/oauth.py,sha256=qznXKVnoTHpmhUzRgIcQ-44l-_FMBZ4Vmn4wG5njXOM,14865
|
|
17
19
|
fastmcp/contrib/README.md,sha256=rKknYSI1T192UvSszqwwDlQ2eYQpxywrNTLoj177SYU,878
|
|
18
20
|
fastmcp/contrib/bulk_tool_caller/README.md,sha256=5aUUY1TSFKtz1pvTLSDqkUCkGkuqMfMZNsLeaNqEgAc,1960
|
|
19
21
|
fastmcp/contrib/bulk_tool_caller/__init__.py,sha256=xvGSSaUXTQrc31erBoi1Gh7BikgOliETDiYVTP3rLxY,75
|
|
@@ -23,38 +25,43 @@ fastmcp/contrib/mcp_mixin/README.md,sha256=9DDTJXWkA3yv1fp5V58gofmARPQ2xWDhblYGv
|
|
|
23
25
|
fastmcp/contrib/mcp_mixin/__init__.py,sha256=aw9IQ1ssNjCgws4ZNt8bkdpossAAGVAwwjBpMp9O5ZQ,153
|
|
24
26
|
fastmcp/contrib/mcp_mixin/example.py,sha256=GnunkXmtG5hLLTUsM8aW5ZURU52Z8vI4tNLl-fK7Dg0,1228
|
|
25
27
|
fastmcp/contrib/mcp_mixin/mcp_mixin.py,sha256=cfIRbnSxsVzglTD-auyTE0izVQeHP7Oz18qzYoBZJgg,7899
|
|
26
|
-
fastmcp/low_level/README.md,sha256=IRvElvOOc_RLLsqbUm7e6VOEwrKHPJeox0pV7JVKHWw,106
|
|
27
|
-
fastmcp/low_level/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
28
28
|
fastmcp/prompts/__init__.py,sha256=An8uMBUh9Hrb7qqcn_5_Hent7IOeSh7EA2IUVsIrtHc,179
|
|
29
29
|
fastmcp/prompts/prompt.py,sha256=_bMuLMSnkH_vJpPcf_b8HOUnMOsQJXZdtjZBoebzNjI,8249
|
|
30
30
|
fastmcp/prompts/prompt_manager.py,sha256=qptEhZHMwc8XxQd5lTQg8iIb5MiTZVsNaux_XLvQ0mw,3871
|
|
31
31
|
fastmcp/resources/__init__.py,sha256=t0x1j8lc74rjUKtXe9H5Gs4fpQt82K4NgBK6Y7A0xTg,467
|
|
32
32
|
fastmcp/resources/resource.py,sha256=Rx1My_fi1f-oqnQ9R_v7ejopAk4BJDfbB75-s4d31dM,2492
|
|
33
33
|
fastmcp/resources/resource_manager.py,sha256=nsgCR3lo9t4Q0QR6txPfAas2upqIb8P8ZlqWAfV9Qc0,11344
|
|
34
|
-
fastmcp/resources/template.py,sha256=
|
|
34
|
+
fastmcp/resources/template.py,sha256=u0_-yNMmZfnl5DqtSRndGbGBrm7JgbzBU8IUd0hrEWE,7523
|
|
35
35
|
fastmcp/resources/types.py,sha256=5fUFvzRlekNjtfihtq8S-fT0alKoNfclzrugqeM5JRE,6366
|
|
36
36
|
fastmcp/server/__init__.py,sha256=bMD4aQD4yJqLz7-mudoNsyeV8UgQfRAg3PRwPvwTEds,119
|
|
37
37
|
fastmcp/server/context.py,sha256=yN1e0LsnCl7cEpr9WlbvFhSf8oE56kKb-20m8h2SsBY,10171
|
|
38
|
-
fastmcp/server/dependencies.py,sha256=
|
|
39
|
-
fastmcp/server/http.py,sha256=
|
|
40
|
-
fastmcp/server/openapi.py,sha256=
|
|
38
|
+
fastmcp/server/dependencies.py,sha256=DfN40fz4UkmdzvVg3QesuHKUVJ07iQU5ookkWawAoH4,2336
|
|
39
|
+
fastmcp/server/http.py,sha256=RbUnqqKsiThOGZwJH-BIzC5_V1EXQh9tBlN4S-JJhbY,11624
|
|
40
|
+
fastmcp/server/openapi.py,sha256=yYO-_9QQhi6IUGW-TsSHkhZFLll4wt5ysbtl0VHaHZM,39239
|
|
41
41
|
fastmcp/server/proxy.py,sha256=mt3eM6TQWfnZD5XehmTXisskZ4CBbsWyjRPjprlTjBY,9653
|
|
42
|
-
fastmcp/server/server.py,sha256=
|
|
42
|
+
fastmcp/server/server.py,sha256=l1kD2MBTEiZTB6pYXBSB7T3cYMRe1Fk6tQvOhLJZQyM,58210
|
|
43
|
+
fastmcp/server/auth/__init__.py,sha256=doHCLwOIElvH1NrTdpeP9JKfnNf3MDYPSpQfdsQ-uI0,84
|
|
44
|
+
fastmcp/server/auth/auth.py,sha256=kz02HGwXYU0N0clURZDjFNWdKSpTYmgmCnGJN-jSG3Y,1640
|
|
45
|
+
fastmcp/server/auth/providers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
46
|
+
fastmcp/server/auth/providers/bearer.py,sha256=3pTKL3tEU7FlCD5yI81LTa2n0dBsM7GRpIIn30WCWsA,12679
|
|
47
|
+
fastmcp/server/auth/providers/bearer_env.py,sha256=MXsr4rjRm8DDmbdNd7IEXT6naCq48fkC1LlpoFAjt7c,1971
|
|
48
|
+
fastmcp/server/auth/providers/in_memory.py,sha256=PmHc6qnnPB2DDY9ybokvp0JL536llwCTmcd1B7-RTpo,13909
|
|
43
49
|
fastmcp/tools/__init__.py,sha256=ocw-SFTtN6vQ8fgnlF8iNAOflRmh79xS1xdO0Bc3QPE,96
|
|
44
|
-
fastmcp/tools/tool.py,sha256=
|
|
45
|
-
fastmcp/tools/tool_manager.py,sha256=
|
|
50
|
+
fastmcp/tools/tool.py,sha256=Lj0PZYNKbqZRBouqn17ayz2gDgU2-bEfHOC75r4GOB8,8687
|
|
51
|
+
fastmcp/tools/tool_manager.py,sha256=HVT1-8Sdir5yY45oOUedMWnD8HJUkVkLUy0U-eAayoQ,4516
|
|
46
52
|
fastmcp/utilities/__init__.py,sha256=-imJ8S-rXmbXMWeDamldP-dHDqAPg_wwmPVz-LNX14E,31
|
|
47
53
|
fastmcp/utilities/cache.py,sha256=aV3oZ-ZhMgLSM9iAotlUlEy5jFvGXrVo0Y5Bj4PBtqY,707
|
|
48
54
|
fastmcp/utilities/decorators.py,sha256=AjhjsetQZF4YOPV5MTZmIxO21iFp_4fDIS3O2_KNCEg,2990
|
|
49
55
|
fastmcp/utilities/exceptions.py,sha256=Aax9K0larjzrrgJBS6o_PQwoIrvBvVwck2suZvgafXE,1359
|
|
56
|
+
fastmcp/utilities/http.py,sha256=1ns1ymBS-WSxbZjGP6JYjSO52Wa_ls4j4WbnXiupoa4,245
|
|
50
57
|
fastmcp/utilities/json_schema.py,sha256=m65XU9lPq7pCxJ9vvCeGRl0HOFr6ArezvYpMBR6-gAg,3777
|
|
51
58
|
fastmcp/utilities/logging.py,sha256=B1WNO-ZWFjd9wiFSh13YtW1hAKaNmbpscDZleIAhr-g,1317
|
|
52
59
|
fastmcp/utilities/mcp_config.py,sha256=_wY3peaFDEgyOBkJ_Tb8sETk3mtdwtw1053q7ry0za0,2169
|
|
53
60
|
fastmcp/utilities/openapi.py,sha256=QQos4vP59HQ8vPDTKftWOIVv_zmW30mNxYSXVU7JUbY,38441
|
|
54
|
-
fastmcp/utilities/tests.py,sha256=
|
|
61
|
+
fastmcp/utilities/tests.py,sha256=4Vuua6nVgbE5uQspEK0fk4tBuJ0rO4GTBmnyD0kXJPA,3930
|
|
55
62
|
fastmcp/utilities/types.py,sha256=6CcqAQ1QqCO2HGSFlPS6FO5JRWnacjCcO2-EhyEnZV0,4400
|
|
56
|
-
fastmcp-2.
|
|
57
|
-
fastmcp-2.
|
|
58
|
-
fastmcp-2.
|
|
59
|
-
fastmcp-2.
|
|
60
|
-
fastmcp-2.
|
|
63
|
+
fastmcp-2.6.0.dist-info/METADATA,sha256=vKeS0OYYNKKjJLcXi6jLtnb9TIsBo9niw38QTfwruxg,16605
|
|
64
|
+
fastmcp-2.6.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
65
|
+
fastmcp-2.6.0.dist-info/entry_points.txt,sha256=ff8bMtKX1JvXyurMibAacMSKbJEPmac9ffAKU9mLnM8,44
|
|
66
|
+
fastmcp-2.6.0.dist-info/licenses/LICENSE,sha256=QwcOLU5TJoTeUhuIXzhdCEEDDvorGiC6-3YTOl4TecE,11356
|
|
67
|
+
fastmcp-2.6.0.dist-info/RECORD,,
|
fastmcp/client/base.py
DELETED
|
File without changes
|
fastmcp/low_level/README.md
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
Patched low-level objects. When possible, we prefer the official SDK, but we patch bugs here if necessary.
|
fastmcp/py.typed
DELETED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|