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/tools/tool.py
CHANGED
|
@@ -2,19 +2,21 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import inspect
|
|
4
4
|
import json
|
|
5
|
+
from abc import ABC, abstractmethod
|
|
5
6
|
from collections.abc import Callable
|
|
6
7
|
from typing import TYPE_CHECKING, Annotated, Any
|
|
7
8
|
|
|
8
9
|
import pydantic_core
|
|
9
10
|
from mcp.types import EmbeddedResource, ImageContent, TextContent, ToolAnnotations
|
|
10
11
|
from mcp.types import Tool as MCPTool
|
|
11
|
-
from pydantic import
|
|
12
|
+
from pydantic import BeforeValidator, Field
|
|
12
13
|
|
|
13
14
|
import fastmcp
|
|
14
15
|
from fastmcp.server.dependencies import get_context
|
|
15
16
|
from fastmcp.utilities.json_schema import compress_schema
|
|
16
17
|
from fastmcp.utilities.logging import get_logger
|
|
17
18
|
from fastmcp.utilities.types import (
|
|
19
|
+
FastMCPBaseModel,
|
|
18
20
|
Image,
|
|
19
21
|
_convert_set_defaults,
|
|
20
22
|
find_kwarg_by_type,
|
|
@@ -31,10 +33,9 @@ def default_serializer(data: Any) -> str:
|
|
|
31
33
|
return pydantic_core.to_json(data, fallback=str, indent=2).decode()
|
|
32
34
|
|
|
33
35
|
|
|
34
|
-
class Tool(
|
|
36
|
+
class Tool(FastMCPBaseModel, ABC):
|
|
35
37
|
"""Internal tool registration info."""
|
|
36
38
|
|
|
37
|
-
fn: Callable[..., Any]
|
|
38
39
|
name: str = Field(description="Name of the tool")
|
|
39
40
|
description: str | None = Field(
|
|
40
41
|
default=None, description="Description of what the tool does"
|
|
@@ -54,6 +55,53 @@ class Tool(BaseModel):
|
|
|
54
55
|
None, description="Optional custom serializer for tool results"
|
|
55
56
|
)
|
|
56
57
|
|
|
58
|
+
def to_mcp_tool(self, **overrides: Any) -> MCPTool:
|
|
59
|
+
kwargs = {
|
|
60
|
+
"name": self.name,
|
|
61
|
+
"description": self.description,
|
|
62
|
+
"inputSchema": self.parameters,
|
|
63
|
+
"annotations": self.annotations,
|
|
64
|
+
}
|
|
65
|
+
return MCPTool(**kwargs | overrides)
|
|
66
|
+
|
|
67
|
+
@staticmethod
|
|
68
|
+
def from_function(
|
|
69
|
+
fn: Callable[..., Any],
|
|
70
|
+
name: str | None = None,
|
|
71
|
+
description: str | None = None,
|
|
72
|
+
tags: set[str] | None = None,
|
|
73
|
+
annotations: ToolAnnotations | None = None,
|
|
74
|
+
exclude_args: list[str] | None = None,
|
|
75
|
+
serializer: Callable[[Any], str] | None = None,
|
|
76
|
+
) -> FunctionTool:
|
|
77
|
+
"""Create a Tool from a function."""
|
|
78
|
+
return FunctionTool.from_function(
|
|
79
|
+
fn=fn,
|
|
80
|
+
name=name,
|
|
81
|
+
description=description,
|
|
82
|
+
tags=tags,
|
|
83
|
+
annotations=annotations,
|
|
84
|
+
exclude_args=exclude_args,
|
|
85
|
+
serializer=serializer,
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
def __eq__(self, other: object) -> bool:
|
|
89
|
+
if type(self) is not type(other):
|
|
90
|
+
return False
|
|
91
|
+
assert isinstance(other, type(self))
|
|
92
|
+
return self.model_dump() == other.model_dump()
|
|
93
|
+
|
|
94
|
+
@abstractmethod
|
|
95
|
+
async def run(
|
|
96
|
+
self, arguments: dict[str, Any]
|
|
97
|
+
) -> list[TextContent | ImageContent | EmbeddedResource]:
|
|
98
|
+
"""Run the tool with arguments."""
|
|
99
|
+
raise NotImplementedError("Subclasses must implement run()")
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
class FunctionTool(Tool):
|
|
103
|
+
fn: Callable[..., Any]
|
|
104
|
+
|
|
57
105
|
@classmethod
|
|
58
106
|
def from_function(
|
|
59
107
|
cls,
|
|
@@ -64,7 +112,7 @@ class Tool(BaseModel):
|
|
|
64
112
|
annotations: ToolAnnotations | None = None,
|
|
65
113
|
exclude_args: list[str] | None = None,
|
|
66
114
|
serializer: Callable[[Any], str] | None = None,
|
|
67
|
-
) ->
|
|
115
|
+
) -> FunctionTool:
|
|
68
116
|
"""Create a Tool from a function."""
|
|
69
117
|
from fastmcp.server.context import Context
|
|
70
118
|
|
|
@@ -98,6 +146,9 @@ class Tool(BaseModel):
|
|
|
98
146
|
# if the fn is a callable class, we need to get the __call__ method from here out
|
|
99
147
|
if not inspect.isroutine(fn):
|
|
100
148
|
fn = fn.__call__
|
|
149
|
+
# if the fn is a staticmethod, we need to work with the underlying function
|
|
150
|
+
if isinstance(fn, staticmethod):
|
|
151
|
+
fn = fn.__func__
|
|
101
152
|
|
|
102
153
|
type_adapter = get_cached_typeadapter(fn)
|
|
103
154
|
schema = type_adapter.json_schema()
|
|
@@ -170,20 +221,6 @@ class Tool(BaseModel):
|
|
|
170
221
|
|
|
171
222
|
return _convert_to_content(result, serializer=self.serializer)
|
|
172
223
|
|
|
173
|
-
def to_mcp_tool(self, **overrides: Any) -> MCPTool:
|
|
174
|
-
kwargs = {
|
|
175
|
-
"name": self.name,
|
|
176
|
-
"description": self.description,
|
|
177
|
-
"inputSchema": self.parameters,
|
|
178
|
-
"annotations": self.annotations,
|
|
179
|
-
}
|
|
180
|
-
return MCPTool(**kwargs | overrides)
|
|
181
|
-
|
|
182
|
-
def __eq__(self, other: object) -> bool:
|
|
183
|
-
if not isinstance(other, Tool):
|
|
184
|
-
return False
|
|
185
|
-
return self.model_dump() == other.model_dump()
|
|
186
|
-
|
|
187
224
|
|
|
188
225
|
def _convert_to_content(
|
|
189
226
|
result: Any,
|
|
@@ -215,9 +252,12 @@ def _convert_to_content(
|
|
|
215
252
|
mcp_types.append(_convert_to_content(item)[0])
|
|
216
253
|
else:
|
|
217
254
|
other_content.append(item)
|
|
255
|
+
|
|
218
256
|
if other_content:
|
|
219
257
|
other_content = _convert_to_content(
|
|
220
|
-
other_content
|
|
258
|
+
other_content[0] if len(other_content) == 1 else other_content,
|
|
259
|
+
serializer=serializer,
|
|
260
|
+
_process_as_single_item=True,
|
|
221
261
|
)
|
|
222
262
|
|
|
223
263
|
return other_content + mcp_types
|
fastmcp/tools/tool_manager.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations as _annotations
|
|
2
2
|
|
|
3
|
+
import warnings
|
|
3
4
|
from collections.abc import Callable
|
|
4
5
|
from typing import TYPE_CHECKING, Any
|
|
5
6
|
|
|
@@ -22,11 +23,9 @@ class ToolManager:
|
|
|
22
23
|
def __init__(
|
|
23
24
|
self,
|
|
24
25
|
duplicate_behavior: DuplicateBehavior | None = None,
|
|
25
|
-
serializer: Callable[[Any], str] | None = None,
|
|
26
26
|
mask_error_details: bool = False,
|
|
27
27
|
):
|
|
28
28
|
self._tools: dict[str, Tool] = {}
|
|
29
|
-
self._serializer = serializer
|
|
30
29
|
self.mask_error_details = mask_error_details
|
|
31
30
|
|
|
32
31
|
# Default to "warn" if None is provided
|
|
@@ -66,17 +65,24 @@ class ToolManager:
|
|
|
66
65
|
description: str | None = None,
|
|
67
66
|
tags: set[str] | None = None,
|
|
68
67
|
annotations: ToolAnnotations | None = None,
|
|
68
|
+
serializer: Callable[[Any], str] | None = None,
|
|
69
69
|
exclude_args: list[str] | None = None,
|
|
70
70
|
) -> Tool:
|
|
71
71
|
"""Add a tool to the server."""
|
|
72
|
+
# deprecated in 2.7.0
|
|
73
|
+
warnings.warn(
|
|
74
|
+
"ToolManager.add_tool_from_fn() is deprecated. Use Tool.from_function() and call add_tool() instead.",
|
|
75
|
+
DeprecationWarning,
|
|
76
|
+
stacklevel=2,
|
|
77
|
+
)
|
|
72
78
|
tool = Tool.from_function(
|
|
73
79
|
fn,
|
|
74
80
|
name=name,
|
|
75
81
|
description=description,
|
|
76
82
|
tags=tags,
|
|
77
83
|
annotations=annotations,
|
|
78
|
-
serializer=self._serializer,
|
|
79
84
|
exclude_args=exclude_args,
|
|
85
|
+
serializer=serializer,
|
|
80
86
|
)
|
|
81
87
|
return self.add_tool(tool)
|
|
82
88
|
|
fastmcp/utilities/mcp_config.py
CHANGED
|
@@ -3,7 +3,9 @@ from __future__ import annotations
|
|
|
3
3
|
from typing import TYPE_CHECKING, Any, Literal
|
|
4
4
|
from urllib.parse import urlparse
|
|
5
5
|
|
|
6
|
-
from pydantic import AnyUrl,
|
|
6
|
+
from pydantic import AnyUrl, Field
|
|
7
|
+
|
|
8
|
+
from fastmcp.utilities.types import FastMCPBaseModel
|
|
7
9
|
|
|
8
10
|
if TYPE_CHECKING:
|
|
9
11
|
from fastmcp.client.transports import (
|
|
@@ -32,7 +34,7 @@ def infer_transport_type_from_url(
|
|
|
32
34
|
return "streamable-http"
|
|
33
35
|
|
|
34
36
|
|
|
35
|
-
class StdioMCPServer(
|
|
37
|
+
class StdioMCPServer(FastMCPBaseModel):
|
|
36
38
|
command: str
|
|
37
39
|
args: list[str] = Field(default_factory=list)
|
|
38
40
|
env: dict[str, Any] = Field(default_factory=dict)
|
|
@@ -50,7 +52,7 @@ class StdioMCPServer(BaseModel):
|
|
|
50
52
|
)
|
|
51
53
|
|
|
52
54
|
|
|
53
|
-
class RemoteMCPServer(
|
|
55
|
+
class RemoteMCPServer(FastMCPBaseModel):
|
|
54
56
|
url: str
|
|
55
57
|
headers: dict[str, str] = Field(default_factory=dict)
|
|
56
58
|
transport: Literal["streamable-http", "sse", "http"] | None = None
|
|
@@ -69,7 +71,7 @@ class RemoteMCPServer(BaseModel):
|
|
|
69
71
|
return StreamableHttpTransport(self.url, headers=self.headers)
|
|
70
72
|
|
|
71
73
|
|
|
72
|
-
class MCPConfig(
|
|
74
|
+
class MCPConfig(FastMCPBaseModel):
|
|
73
75
|
mcpServers: dict[str, StdioMCPServer | RemoteMCPServer]
|
|
74
76
|
|
|
75
77
|
@classmethod
|
fastmcp/utilities/openapi.py
CHANGED
|
@@ -25,6 +25,7 @@ from openapi_pydantic.v3.v3_0 import Schema as Schema_30
|
|
|
25
25
|
from pydantic import BaseModel, Field, ValidationError
|
|
26
26
|
|
|
27
27
|
from fastmcp.utilities.json_schema import compress_schema
|
|
28
|
+
from fastmcp.utilities.types import FastMCPBaseModel
|
|
28
29
|
|
|
29
30
|
logger = logging.getLogger(__name__)
|
|
30
31
|
|
|
@@ -38,7 +39,7 @@ ParameterLocation = Literal["path", "query", "header", "cookie"]
|
|
|
38
39
|
JsonSchema = dict[str, Any]
|
|
39
40
|
|
|
40
41
|
|
|
41
|
-
class ParameterInfo(
|
|
42
|
+
class ParameterInfo(FastMCPBaseModel):
|
|
42
43
|
"""Represents a single parameter for an HTTP operation in our IR."""
|
|
43
44
|
|
|
44
45
|
name: str
|
|
@@ -48,7 +49,7 @@ class ParameterInfo(BaseModel):
|
|
|
48
49
|
description: str | None = None
|
|
49
50
|
|
|
50
51
|
|
|
51
|
-
class RequestBodyInfo(
|
|
52
|
+
class RequestBodyInfo(FastMCPBaseModel):
|
|
52
53
|
"""Represents the request body for an HTTP operation in our IR."""
|
|
53
54
|
|
|
54
55
|
required: bool = False
|
|
@@ -58,7 +59,7 @@ class RequestBodyInfo(BaseModel):
|
|
|
58
59
|
description: str | None = None
|
|
59
60
|
|
|
60
61
|
|
|
61
|
-
class ResponseInfo(
|
|
62
|
+
class ResponseInfo(FastMCPBaseModel):
|
|
62
63
|
"""Represents response information in our IR."""
|
|
63
64
|
|
|
64
65
|
description: str | None = None
|
|
@@ -66,7 +67,7 @@ class ResponseInfo(BaseModel):
|
|
|
66
67
|
content_schema: dict[str, JsonSchema] = Field(default_factory=dict)
|
|
67
68
|
|
|
68
69
|
|
|
69
|
-
class HTTPRoute(
|
|
70
|
+
class HTTPRoute(FastMCPBaseModel):
|
|
70
71
|
"""Intermediate Representation for a single OpenAPI operation."""
|
|
71
72
|
|
|
72
73
|
path: str
|
|
@@ -872,6 +873,50 @@ def format_description_with_responses(
|
|
|
872
873
|
return "\n".join(desc_parts)
|
|
873
874
|
|
|
874
875
|
|
|
876
|
+
def _replace_ref_with_defs(
|
|
877
|
+
info: dict[str, Any], description: str | None = None
|
|
878
|
+
) -> dict[str, Any]:
|
|
879
|
+
"""
|
|
880
|
+
Replace openapi $ref with jsonschema $defs
|
|
881
|
+
|
|
882
|
+
Examples:
|
|
883
|
+
- {"type": "object", "properties": {"$ref": "#/components/schemas/..."}}
|
|
884
|
+
- {"$ref": "#/components/schemas/..."}
|
|
885
|
+
- {"items": {"$ref": "#/components/schemas/..."}}
|
|
886
|
+
- {"anyOf": [{"$ref": "#/components/schemas/..."}]}
|
|
887
|
+
- {"allOf": [{"$ref": "#/components/schemas/..."}]}
|
|
888
|
+
- {"oneOf": [{"$ref": "#/components/schemas/..."}]}
|
|
889
|
+
|
|
890
|
+
Args:
|
|
891
|
+
info: dict[str, Any]
|
|
892
|
+
description: str | None
|
|
893
|
+
|
|
894
|
+
Returns:
|
|
895
|
+
dict[str, Any]
|
|
896
|
+
"""
|
|
897
|
+
schema = info.copy()
|
|
898
|
+
if ref_path := schema.get("$ref"):
|
|
899
|
+
if ref_path.startswith("#/components/schemas/"):
|
|
900
|
+
schema_name = ref_path.split("/")[-1]
|
|
901
|
+
schema["$ref"] = f"#/$defs/{schema_name}"
|
|
902
|
+
elif properties := schema.get("properties"):
|
|
903
|
+
if "$ref" in properties:
|
|
904
|
+
schema["properties"] = _replace_ref_with_defs(properties)
|
|
905
|
+
else:
|
|
906
|
+
schema["properties"] = {
|
|
907
|
+
prop_name: _replace_ref_with_defs(prop_schema)
|
|
908
|
+
for prop_name, prop_schema in properties.items()
|
|
909
|
+
}
|
|
910
|
+
elif item_schema := schema.get("items"):
|
|
911
|
+
schema["items"] = _replace_ref_with_defs(item_schema)
|
|
912
|
+
for section in ["anyOf", "allOf", "oneOf"]:
|
|
913
|
+
for i, item in enumerate(schema.get(section, [])):
|
|
914
|
+
schema[section][i] = _replace_ref_with_defs(item)
|
|
915
|
+
if info.get("description", description) and not schema.get("description"):
|
|
916
|
+
schema["description"] = description
|
|
917
|
+
return schema
|
|
918
|
+
|
|
919
|
+
|
|
875
920
|
def _combine_schemas(route: HTTPRoute) -> dict[str, Any]:
|
|
876
921
|
"""
|
|
877
922
|
Combines parameter and request body schemas into a single schema.
|
|
@@ -889,38 +934,18 @@ def _combine_schemas(route: HTTPRoute) -> dict[str, Any]:
|
|
|
889
934
|
for param in route.parameters:
|
|
890
935
|
if param.required:
|
|
891
936
|
required.append(param.name)
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
# Convert #/components/schemas references to #/$defs references
|
|
897
|
-
if isinstance(param_schema, dict) and "$ref" in param_schema:
|
|
898
|
-
ref_path = param_schema["$ref"]
|
|
899
|
-
if ref_path.startswith("#/components/schemas/"):
|
|
900
|
-
schema_name = ref_path.split("/")[-1]
|
|
901
|
-
param_schema["$ref"] = f"#/$defs/{schema_name}"
|
|
902
|
-
|
|
903
|
-
# Also handle anyOf, allOf, oneOf references
|
|
904
|
-
for section in ["anyOf", "allOf", "oneOf"]:
|
|
905
|
-
if section in param_schema and isinstance(param_schema[section], list):
|
|
906
|
-
for i, item in enumerate(param_schema[section]):
|
|
907
|
-
if isinstance(item, dict) and "$ref" in item:
|
|
908
|
-
ref_path = item["$ref"]
|
|
909
|
-
if ref_path.startswith("#/components/schemas/"):
|
|
910
|
-
schema_name = ref_path.split("/")[-1]
|
|
911
|
-
param_schema[section][i]["$ref"] = f"#/$defs/{schema_name}"
|
|
912
|
-
|
|
913
|
-
# Add parameter description to schema if available and not already present
|
|
914
|
-
if param.description and not param_schema.get("description"):
|
|
915
|
-
param_schema["description"] = param.description
|
|
916
|
-
|
|
917
|
-
properties[param.name] = param_schema
|
|
937
|
+
properties[param.name] = _replace_ref_with_defs(
|
|
938
|
+
param.schema_.copy(), param.description
|
|
939
|
+
)
|
|
918
940
|
|
|
919
941
|
# Add request body if it exists
|
|
920
942
|
if route.request_body and route.request_body.content_schema:
|
|
921
943
|
# For now, just use the first content type's schema
|
|
922
944
|
content_type = next(iter(route.request_body.content_schema))
|
|
923
|
-
body_schema =
|
|
945
|
+
body_schema = _replace_ref_with_defs(
|
|
946
|
+
route.request_body.content_schema[content_type].copy(),
|
|
947
|
+
route.request_body.description,
|
|
948
|
+
)
|
|
924
949
|
body_props = body_schema.get("properties", {})
|
|
925
950
|
|
|
926
951
|
# Add request body properties
|
|
@@ -935,7 +960,6 @@ def _combine_schemas(route: HTTPRoute) -> dict[str, Any]:
|
|
|
935
960
|
"properties": properties,
|
|
936
961
|
"required": required,
|
|
937
962
|
}
|
|
938
|
-
|
|
939
963
|
# Add schema definitions if available
|
|
940
964
|
if route.schema_definitions:
|
|
941
965
|
result["$defs"] = route.schema_definitions
|
fastmcp/utilities/types.py
CHANGED
|
@@ -9,11 +9,17 @@ from types import UnionType
|
|
|
9
9
|
from typing import Annotated, TypeVar, Union, get_args, get_origin
|
|
10
10
|
|
|
11
11
|
from mcp.types import ImageContent
|
|
12
|
-
from pydantic import TypeAdapter
|
|
12
|
+
from pydantic import BaseModel, ConfigDict, TypeAdapter
|
|
13
13
|
|
|
14
14
|
T = TypeVar("T")
|
|
15
15
|
|
|
16
16
|
|
|
17
|
+
class FastMCPBaseModel(BaseModel):
|
|
18
|
+
"""Base model for FastMCP models."""
|
|
19
|
+
|
|
20
|
+
model_config = ConfigDict(extra="forbid")
|
|
21
|
+
|
|
22
|
+
|
|
17
23
|
@lru_cache(maxsize=5000)
|
|
18
24
|
def get_cached_typeadapter(cls: T) -> TypeAdapter[T]:
|
|
19
25
|
"""
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: fastmcp
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.7.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
|
|
@@ -25,7 +25,8 @@ Requires-Dist: openapi-pydantic>=0.5.1
|
|
|
25
25
|
Requires-Dist: python-dotenv>=1.1.0
|
|
26
26
|
Requires-Dist: rich>=13.9.4
|
|
27
27
|
Requires-Dist: typer>=0.15.2
|
|
28
|
-
|
|
28
|
+
Provides-Extra: websockets
|
|
29
|
+
Requires-Dist: websockets>=15.0.1; extra == 'websockets'
|
|
29
30
|
Description-Content-Type: text/markdown
|
|
30
31
|
|
|
31
32
|
<div align="center">
|
|
@@ -61,7 +62,7 @@ from fastmcp import FastMCP
|
|
|
61
62
|
|
|
62
63
|
mcp = FastMCP("Demo 🚀")
|
|
63
64
|
|
|
64
|
-
@mcp.tool
|
|
65
|
+
@mcp.tool
|
|
65
66
|
def add(a: int, b: int) -> int:
|
|
66
67
|
"""Add two numbers"""
|
|
67
68
|
return a + b
|
|
@@ -104,6 +105,7 @@ There are two ways to access the LLM-friendly documentation:
|
|
|
104
105
|
- [Proxy Servers](#proxy-servers)
|
|
105
106
|
- [Composing MCP Servers](#composing-mcp-servers)
|
|
106
107
|
- [OpenAPI \& FastAPI Generation](#openapi--fastapi-generation)
|
|
108
|
+
- [Authentication \& Security](#authentication--security)
|
|
107
109
|
- [Running Your Server](#running-your-server)
|
|
108
110
|
- [Contributing](#contributing)
|
|
109
111
|
- [Prerequisites](#prerequisites)
|
|
@@ -116,20 +118,20 @@ There are two ways to access the LLM-friendly documentation:
|
|
|
116
118
|
|
|
117
119
|
## What is MCP?
|
|
118
120
|
|
|
119
|
-
The [Model Context Protocol (MCP)](https://modelcontextprotocol.io) lets you build servers that expose data and functionality to LLM applications in a secure, standardized way.
|
|
121
|
+
The [Model Context Protocol (MCP)](https://modelcontextprotocol.io) lets you build servers that expose data and functionality to LLM applications in a secure, standardized way. It is often described as "the USB-C port for AI", providing a uniform way to connect LLMs to resources they can use. It may be easier to think of it as an API, but specifically designed for LLM interactions. MCP servers can:
|
|
120
122
|
|
|
121
|
-
- Expose data through **Resources** (
|
|
122
|
-
- Provide functionality through **Tools** (
|
|
123
|
-
- Define interaction patterns through **Prompts** (reusable templates)
|
|
123
|
+
- Expose data through **Resources** (think of these sort of like GET endpoints; they are used to load information into the LLM's context)
|
|
124
|
+
- Provide functionality through **Tools** (sort of like POST endpoints; they are used to execute code or otherwise produce a side effect)
|
|
125
|
+
- Define interaction patterns through **Prompts** (reusable templates for LLM interactions)
|
|
124
126
|
- And more!
|
|
125
127
|
|
|
126
|
-
FastMCP provides a high-level, Pythonic interface for building and interacting with these servers.
|
|
128
|
+
FastMCP provides a high-level, Pythonic interface for building, managing, and interacting with these servers.
|
|
127
129
|
|
|
128
130
|
## Why FastMCP?
|
|
129
131
|
|
|
130
132
|
The MCP protocol is powerful but implementing it involves a lot of boilerplate - server setup, protocol handlers, content types, error management. FastMCP handles all the complex protocol details and server management, so you can focus on building great tools. It's designed to be high-level and Pythonic; in most cases, decorating a function is all you need.
|
|
131
133
|
|
|
132
|
-
|
|
134
|
+
FastMCP 2.0 has evolved into a comprehensive platform that goes far beyond basic protocol implementation. While 1.0 provided server-building capabilities (and is now part of the official MCP SDK), 2.0 offers a complete ecosystem including client libraries, authentication systems, deployment tools, integrations with major AI platforms, testing frameworks, and production-ready infrastructure patterns.
|
|
133
135
|
|
|
134
136
|
FastMCP aims to be:
|
|
135
137
|
|
|
@@ -139,7 +141,7 @@ FastMCP aims to be:
|
|
|
139
141
|
|
|
140
142
|
🐍 **Pythonic:** Feels natural to Python developers
|
|
141
143
|
|
|
142
|
-
🔍 **Complete:**
|
|
144
|
+
🔍 **Complete:** A comprehensive platform for all MCP use cases, from dev to prod
|
|
143
145
|
|
|
144
146
|
## Installation
|
|
145
147
|
|
|
@@ -157,7 +159,7 @@ These are the building blocks for creating MCP servers and clients with FastMCP.
|
|
|
157
159
|
|
|
158
160
|
### The `FastMCP` Server
|
|
159
161
|
|
|
160
|
-
The central object representing your MCP application. It holds your tools, resources, and prompts, manages connections, and can be configured with settings like
|
|
162
|
+
The central object representing your MCP application. It holds your tools, resources, and prompts, manages connections, and can be configured with settings like authentication.
|
|
161
163
|
|
|
162
164
|
```python
|
|
163
165
|
from fastmcp import FastMCP
|
|
@@ -173,7 +175,7 @@ Learn more in the [**FastMCP Server Documentation**](https://gofastmcp.com/serve
|
|
|
173
175
|
Tools allow LLMs to perform actions by executing your Python functions (sync or async). Ideal for computations, API calls, or side effects (like `POST`/`PUT`). FastMCP handles schema generation from type hints and docstrings. Tools can return various types, including text, JSON-serializable objects, and even images using the [`fastmcp.Image`](https://gofastmcp.com/servers/tools#return-values) helper.
|
|
174
176
|
|
|
175
177
|
```python
|
|
176
|
-
@mcp.tool
|
|
178
|
+
@mcp.tool
|
|
177
179
|
def multiply(a: float, b: float) -> float:
|
|
178
180
|
"""Multiplies two numbers."""
|
|
179
181
|
return a * b
|
|
@@ -202,10 +204,10 @@ Learn more in the [**Resources & Templates Documentation**](https://gofastmcp.co
|
|
|
202
204
|
|
|
203
205
|
### Prompts
|
|
204
206
|
|
|
205
|
-
Prompts define reusable message templates to guide LLM interactions. Decorate functions with `@mcp.prompt
|
|
207
|
+
Prompts define reusable message templates to guide LLM interactions. Decorate functions with `@mcp.prompt`. Return strings or `Message` objects.
|
|
206
208
|
|
|
207
209
|
```python
|
|
208
|
-
@mcp.prompt
|
|
210
|
+
@mcp.prompt
|
|
209
211
|
def summarize_request(text: str) -> str:
|
|
210
212
|
"""Generate a prompt asking for a summary."""
|
|
211
213
|
return f"Please summarize the following text:\n\n{text}"
|
|
@@ -230,7 +232,7 @@ from fastmcp import FastMCP, Context
|
|
|
230
232
|
|
|
231
233
|
mcp = FastMCP("My MCP Server")
|
|
232
234
|
|
|
233
|
-
@mcp.tool
|
|
235
|
+
@mcp.tool
|
|
234
236
|
async def process_data(uri: str, ctx: Context):
|
|
235
237
|
# Log a message to the client
|
|
236
238
|
await ctx.info(f"Processing {uri}...")
|
|
@@ -328,7 +330,17 @@ Learn more in the [**Composition Documentation**](https://gofastmcp.com/patterns
|
|
|
328
330
|
|
|
329
331
|
Automatically generate FastMCP servers from existing OpenAPI specifications (`FastMCP.from_openapi()`) or FastAPI applications (`FastMCP.from_fastapi()`), instantly bringing your web APIs to the MCP ecosystem.
|
|
330
332
|
|
|
331
|
-
Learn more: [**OpenAPI Integration**](https://gofastmcp.com/
|
|
333
|
+
Learn more: [**OpenAPI Integration**](https://gofastmcp.com/servers/openapi#openapi-integration) | [**FastAPI Integration**](https://gofastmcp.com/deployment/asgi#fastapi-integration).
|
|
334
|
+
|
|
335
|
+
### Authentication & Security
|
|
336
|
+
|
|
337
|
+
FastMCP provides built-in authentication support to secure both your MCP servers and clients in production environments. Protect your server endpoints from unauthorized access and authenticate your clients against secured MCP servers using industry-standard protocols.
|
|
338
|
+
|
|
339
|
+
- **Server Protection**: Secure your FastMCP server endpoints with configurable authentication providers
|
|
340
|
+
- **Client Authentication**: Connect to authenticated MCP servers with automatic credential management
|
|
341
|
+
- **Production Ready**: Support for common authentication patterns used in enterprise environments
|
|
342
|
+
|
|
343
|
+
Learn more in the **Authentication Documentation** for [servers](https://gofastmcp.com/servers/auth) and [clients](https://gofastmcp.com/clients/auth).
|
|
332
344
|
|
|
333
345
|
## Running Your Server
|
|
334
346
|
|
|
@@ -340,7 +352,7 @@ from fastmcp import FastMCP
|
|
|
340
352
|
|
|
341
353
|
mcp = FastMCP("Demo 🚀")
|
|
342
354
|
|
|
343
|
-
@mcp.tool
|
|
355
|
+
@mcp.tool
|
|
344
356
|
def hello(name: str) -> str:
|
|
345
357
|
return f"Hello, {name}!"
|
|
346
358
|
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
fastmcp/__init__.py,sha256=yTAqLZORsPqbr7AE0ayw6zIYBeMlxQlI-3HE2WqbvHk,435
|
|
2
2
|
fastmcp/exceptions.py,sha256=YvaKqOT3w0boXF9ylIoaSIzW9XiQ1qLFG1LZq6B60H8,680
|
|
3
|
+
fastmcp/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
3
4
|
fastmcp/settings.py,sha256=DbFdWx4EVHhJZF6e2GB0pBMCSjc425kXIVY8mqfOVX8,6175
|
|
4
5
|
fastmcp/cli/__init__.py,sha256=Ii284TNoG5lxTP40ETMGhHEq3lQZWxu9m9JuU57kUpQ,87
|
|
5
6
|
fastmcp/cli/claude.py,sha256=IAlcZ4qZKBBj09jZUMEx7EANZE_IR3vcu7zOBJmMOuU,4567
|
|
6
|
-
fastmcp/cli/cli.py,sha256=
|
|
7
|
-
fastmcp/cli/run.py,sha256=
|
|
7
|
+
fastmcp/cli/cli.py,sha256=zoHJ8DjUBLISVb4VHaVP-fCJP4Y8zG6-UhhNqSvczqY,12667
|
|
8
|
+
fastmcp/cli/run.py,sha256=sGH7M3Yi8HGju4sPypKGk3P2cdZq1n3l-_CpJmdGvDc,6277
|
|
8
9
|
fastmcp/client/__init__.py,sha256=kd2hhSuD8rZuF87c9zlPJP_icJ-Rx3exyNoK0EzfOtE,617
|
|
9
10
|
fastmcp/client/client.py,sha256=vQk0l6htoD6CyO0le8q23iYd5hX1l8NIbxchedbWqgE,24872
|
|
10
11
|
fastmcp/client/logging.py,sha256=hOPRailZUp89RUck6V4HPaWVZinVrNY8HD4hD0dd-fE,822
|
|
@@ -12,56 +13,55 @@ fastmcp/client/oauth_callback.py,sha256=ODAnVX-ettL82RuI5KpfkKf8iDtYMDue3Tnab5sj
|
|
|
12
13
|
fastmcp/client/progress.py,sha256=WjLLDbUKMsx8DK-fqO7AGsXb83ak-6BMrLvzzznGmcI,1043
|
|
13
14
|
fastmcp/client/roots.py,sha256=IxI_bHwHTmg6c2H-s1av1ZgrRnNDieHtYwdGFbzXT5c,2471
|
|
14
15
|
fastmcp/client/sampling.py,sha256=UlDHxnd6k_HoU8RA3ob0g8-e6haJBc9u27N_v291QoI,1698
|
|
15
|
-
fastmcp/client/transports.py,sha256=
|
|
16
|
+
fastmcp/client/transports.py,sha256=Pi4g2B9lKbgvey-7lTLkjsSVxE2DoT2yMq82D7xktK0,32091
|
|
16
17
|
fastmcp/client/auth/__init__.py,sha256=4DNsfp4iaQeBcpds0JDdMn6Mmfud44stWLsret0sVKY,91
|
|
17
18
|
fastmcp/client/auth/bearer.py,sha256=MFEFqcH6u_V86msYiOsEFKN5ks1V9BnBNiPsPLHUTqo,399
|
|
18
|
-
fastmcp/client/auth/oauth.py,sha256=
|
|
19
|
+
fastmcp/client/auth/oauth.py,sha256=LJHCB-34EC-sL9GC97XFQkyanK8Cc5skAp6GkH0tKzE,14709
|
|
19
20
|
fastmcp/contrib/README.md,sha256=rKknYSI1T192UvSszqwwDlQ2eYQpxywrNTLoj177SYU,878
|
|
20
21
|
fastmcp/contrib/bulk_tool_caller/README.md,sha256=5aUUY1TSFKtz1pvTLSDqkUCkGkuqMfMZNsLeaNqEgAc,1960
|
|
21
22
|
fastmcp/contrib/bulk_tool_caller/__init__.py,sha256=xvGSSaUXTQrc31erBoi1Gh7BikgOliETDiYVTP3rLxY,75
|
|
22
23
|
fastmcp/contrib/bulk_tool_caller/bulk_tool_caller.py,sha256=2NcrGS59qvHo1lfbRaT8NSWfCxN66knciLxFvnGwCLY,4165
|
|
23
|
-
fastmcp/contrib/bulk_tool_caller/example.py,sha256=
|
|
24
|
+
fastmcp/contrib/bulk_tool_caller/example.py,sha256=6og_8pCJN_CabworC5R82zPAwwwM-W7HNJLQQSnS3lU,319
|
|
24
25
|
fastmcp/contrib/mcp_mixin/README.md,sha256=9DDTJXWkA3yv1fp5V58gofmARPQ2xWDhblYGvUhKpDQ,1689
|
|
25
26
|
fastmcp/contrib/mcp_mixin/__init__.py,sha256=aw9IQ1ssNjCgws4ZNt8bkdpossAAGVAwwjBpMp9O5ZQ,153
|
|
26
27
|
fastmcp/contrib/mcp_mixin/example.py,sha256=GnunkXmtG5hLLTUsM8aW5ZURU52Z8vI4tNLl-fK7Dg0,1228
|
|
27
|
-
fastmcp/contrib/mcp_mixin/mcp_mixin.py,sha256=
|
|
28
|
+
fastmcp/contrib/mcp_mixin/mcp_mixin.py,sha256=3e0wHlKI9OF12t-SbpRTL-TWjBBLw7T8ATjCdoDtX6k,8173
|
|
28
29
|
fastmcp/prompts/__init__.py,sha256=An8uMBUh9Hrb7qqcn_5_Hent7IOeSh7EA2IUVsIrtHc,179
|
|
29
|
-
fastmcp/prompts/prompt.py,sha256=
|
|
30
|
-
fastmcp/prompts/prompt_manager.py,sha256=
|
|
31
|
-
fastmcp/resources/__init__.py,sha256=
|
|
32
|
-
fastmcp/resources/resource.py,sha256=
|
|
33
|
-
fastmcp/resources/resource_manager.py,sha256=
|
|
34
|
-
fastmcp/resources/template.py,sha256=
|
|
35
|
-
fastmcp/resources/types.py,sha256=
|
|
30
|
+
fastmcp/prompts/prompt.py,sha256=FMvDO3491LJkdPSorKKwr_2KtDJx_C54iYDbGtBzH5M,9474
|
|
31
|
+
fastmcp/prompts/prompt_manager.py,sha256=zAxlMzNHt8z5tD6lHl60aETvCBq6FNfE_1I4p5juKRE,4161
|
|
32
|
+
fastmcp/resources/__init__.py,sha256=y1iAuqx-GIrS1NqIYzKezIDiYyjNEzzHD35epHpMnXE,463
|
|
33
|
+
fastmcp/resources/resource.py,sha256=pvyeuUJvBGyudvxEKiBsof8B5X1EfYpCNwC4wO3-rZA,5139
|
|
34
|
+
fastmcp/resources/resource_manager.py,sha256=AZO05oYJ6DmFst7hWedT6Jx7WfhISodw4K-HdQpY5iI,11761
|
|
35
|
+
fastmcp/resources/template.py,sha256=EDoPOMFRwCNHugEtmlcuTuWx2O1qe5_nUOLrGNYvsXM,8749
|
|
36
|
+
fastmcp/resources/types.py,sha256=SiYNLnpXT-mHgNUrzqKUvXYUsY-V3gwJIrYdJfFwDDo,4868
|
|
36
37
|
fastmcp/server/__init__.py,sha256=bMD4aQD4yJqLz7-mudoNsyeV8UgQfRAg3PRwPvwTEds,119
|
|
37
|
-
fastmcp/server/context.py,sha256=
|
|
38
|
+
fastmcp/server/context.py,sha256=7r-gxMiCgDpd9AStTk0hwfme540H7S1dEcU0bjoerxU,10169
|
|
38
39
|
fastmcp/server/dependencies.py,sha256=DfN40fz4UkmdzvVg3QesuHKUVJ07iQU5ookkWawAoH4,2336
|
|
39
40
|
fastmcp/server/http.py,sha256=RbUnqqKsiThOGZwJH-BIzC5_V1EXQh9tBlN4S-JJhbY,11624
|
|
40
|
-
fastmcp/server/openapi.py,sha256=
|
|
41
|
-
fastmcp/server/proxy.py,sha256=
|
|
42
|
-
fastmcp/server/server.py,sha256=
|
|
41
|
+
fastmcp/server/openapi.py,sha256=heLQA3B57nCOLNsbOYLVFbZGI8XQaVeXL6ST5L55vuo,38596
|
|
42
|
+
fastmcp/server/proxy.py,sha256=EVup0L4gGMnxkG2SJHE09ugKvfhPyJ_3AnkhAS-Dc3E,9562
|
|
43
|
+
fastmcp/server/server.py,sha256=pupGTlXcbindBILxYQoFYqQomQcDMK5QS04vTngYIdk,65399
|
|
43
44
|
fastmcp/server/auth/__init__.py,sha256=doHCLwOIElvH1NrTdpeP9JKfnNf3MDYPSpQfdsQ-uI0,84
|
|
44
45
|
fastmcp/server/auth/auth.py,sha256=kz02HGwXYU0N0clURZDjFNWdKSpTYmgmCnGJN-jSG3Y,1640
|
|
45
46
|
fastmcp/server/auth/providers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
46
47
|
fastmcp/server/auth/providers/bearer.py,sha256=3pTKL3tEU7FlCD5yI81LTa2n0dBsM7GRpIIn30WCWsA,12679
|
|
47
48
|
fastmcp/server/auth/providers/bearer_env.py,sha256=MXsr4rjRm8DDmbdNd7IEXT6naCq48fkC1LlpoFAjt7c,1971
|
|
48
|
-
fastmcp/server/auth/providers/in_memory.py,sha256=
|
|
49
|
-
fastmcp/tools/__init__.py,sha256=
|
|
50
|
-
fastmcp/tools/tool.py,sha256=
|
|
51
|
-
fastmcp/tools/tool_manager.py,sha256=
|
|
49
|
+
fastmcp/server/auth/providers/in_memory.py,sha256=sCRJambxXFZLg_EbJ5ma-aUZvtxuuKbGy7lTxIbzVb0,13772
|
|
50
|
+
fastmcp/tools/__init__.py,sha256=G-XFAr0RhVFj_crAZFaWZDUqcBPCTCyzAndpKXtpIFc,126
|
|
51
|
+
fastmcp/tools/tool.py,sha256=J5VDyQE85IyZPXEq8x0n1V2qdNJ5zVlgB7FkzxJwW9E,9994
|
|
52
|
+
fastmcp/tools/tool_manager.py,sha256=C3mB0lgQU-vmcrUltqck-Yx7zrnIHXthHijS-dJliU8,4724
|
|
52
53
|
fastmcp/utilities/__init__.py,sha256=-imJ8S-rXmbXMWeDamldP-dHDqAPg_wwmPVz-LNX14E,31
|
|
53
54
|
fastmcp/utilities/cache.py,sha256=aV3oZ-ZhMgLSM9iAotlUlEy5jFvGXrVo0Y5Bj4PBtqY,707
|
|
54
|
-
fastmcp/utilities/decorators.py,sha256=AjhjsetQZF4YOPV5MTZmIxO21iFp_4fDIS3O2_KNCEg,2990
|
|
55
55
|
fastmcp/utilities/exceptions.py,sha256=Aax9K0larjzrrgJBS6o_PQwoIrvBvVwck2suZvgafXE,1359
|
|
56
56
|
fastmcp/utilities/http.py,sha256=1ns1ymBS-WSxbZjGP6JYjSO52Wa_ls4j4WbnXiupoa4,245
|
|
57
57
|
fastmcp/utilities/json_schema.py,sha256=m65XU9lPq7pCxJ9vvCeGRl0HOFr6ArezvYpMBR6-gAg,3777
|
|
58
58
|
fastmcp/utilities/logging.py,sha256=B1WNO-ZWFjd9wiFSh13YtW1hAKaNmbpscDZleIAhr-g,1317
|
|
59
|
-
fastmcp/utilities/mcp_config.py,sha256=
|
|
60
|
-
fastmcp/utilities/openapi.py,sha256=
|
|
59
|
+
fastmcp/utilities/mcp_config.py,sha256=Htux1dKyDaeK6-gT_fZwpJ_yj0MxUjGfPFAY3Dnnxsk,2233
|
|
60
|
+
fastmcp/utilities/openapi.py,sha256=ctceiGb4jYgzZGSseMb-yZccEEXf41P-dhB3ae9lGdk,38992
|
|
61
61
|
fastmcp/utilities/tests.py,sha256=4Vuua6nVgbE5uQspEK0fk4tBuJ0rO4GTBmnyD0kXJPA,3930
|
|
62
|
-
fastmcp/utilities/types.py,sha256=
|
|
63
|
-
fastmcp-2.
|
|
64
|
-
fastmcp-2.
|
|
65
|
-
fastmcp-2.
|
|
66
|
-
fastmcp-2.
|
|
67
|
-
fastmcp-2.
|
|
62
|
+
fastmcp/utilities/types.py,sha256=HX1y4JrDC3sZA2ENRqsyxMyLQepr5-JiS9StXhIX8TE,4548
|
|
63
|
+
fastmcp-2.7.0.dist-info/METADATA,sha256=kXjE9Y1-qt0-OP2PVR2b1Gjdp6QKb68olWBNxM5xQmc,17687
|
|
64
|
+
fastmcp-2.7.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
65
|
+
fastmcp-2.7.0.dist-info/entry_points.txt,sha256=ff8bMtKX1JvXyurMibAacMSKbJEPmac9ffAKU9mLnM8,44
|
|
66
|
+
fastmcp-2.7.0.dist-info/licenses/LICENSE,sha256=QwcOLU5TJoTeUhuIXzhdCEEDDvorGiC6-3YTOl4TecE,11356
|
|
67
|
+
fastmcp-2.7.0.dist-info/RECORD,,
|