fastmcp 2.10.6__py3-none-any.whl → 2.11.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 +128 -33
- fastmcp/cli/install/claude_code.py +42 -1
- fastmcp/cli/install/claude_desktop.py +42 -1
- fastmcp/cli/install/cursor.py +42 -1
- fastmcp/cli/install/mcp_json.py +41 -0
- fastmcp/cli/run.py +127 -1
- fastmcp/client/__init__.py +2 -0
- fastmcp/client/auth/oauth.py +68 -99
- fastmcp/client/oauth_callback.py +18 -0
- fastmcp/client/transports.py +69 -15
- fastmcp/contrib/component_manager/example.py +2 -2
- fastmcp/experimental/server/openapi/README.md +266 -0
- fastmcp/experimental/server/openapi/__init__.py +38 -0
- fastmcp/experimental/server/openapi/components.py +348 -0
- fastmcp/experimental/server/openapi/routing.py +132 -0
- fastmcp/experimental/server/openapi/server.py +466 -0
- fastmcp/experimental/utilities/openapi/README.md +239 -0
- fastmcp/experimental/utilities/openapi/__init__.py +68 -0
- fastmcp/experimental/utilities/openapi/director.py +208 -0
- fastmcp/experimental/utilities/openapi/formatters.py +355 -0
- fastmcp/experimental/utilities/openapi/json_schema_converter.py +340 -0
- fastmcp/experimental/utilities/openapi/models.py +85 -0
- fastmcp/experimental/utilities/openapi/parser.py +618 -0
- fastmcp/experimental/utilities/openapi/schemas.py +538 -0
- fastmcp/mcp_config.py +125 -88
- fastmcp/prompts/prompt.py +11 -1
- fastmcp/resources/resource.py +21 -1
- fastmcp/resources/template.py +20 -1
- fastmcp/server/auth/__init__.py +17 -2
- fastmcp/server/auth/auth.py +144 -7
- fastmcp/server/auth/providers/bearer.py +25 -473
- fastmcp/server/auth/providers/in_memory.py +4 -2
- fastmcp/server/auth/providers/jwt.py +538 -0
- fastmcp/server/auth/providers/workos.py +170 -0
- fastmcp/server/auth/registry.py +52 -0
- fastmcp/server/context.py +107 -26
- fastmcp/server/dependencies.py +9 -2
- fastmcp/server/http.py +62 -30
- fastmcp/server/middleware/middleware.py +3 -23
- fastmcp/server/openapi.py +1 -1
- fastmcp/server/proxy.py +50 -11
- fastmcp/server/server.py +168 -59
- fastmcp/settings.py +73 -6
- fastmcp/tools/tool.py +36 -3
- fastmcp/tools/tool_manager.py +38 -2
- fastmcp/tools/tool_transform.py +112 -3
- fastmcp/utilities/components.py +35 -2
- fastmcp/utilities/json_schema.py +136 -98
- fastmcp/utilities/json_schema_type.py +1 -3
- fastmcp/utilities/mcp_config.py +28 -0
- fastmcp/utilities/openapi.py +240 -50
- fastmcp/utilities/tests.py +54 -6
- fastmcp/utilities/types.py +89 -11
- {fastmcp-2.10.6.dist-info → fastmcp-2.11.0.dist-info}/METADATA +4 -3
- fastmcp-2.11.0.dist-info/RECORD +108 -0
- fastmcp/server/auth/providers/bearer_env.py +0 -63
- fastmcp/utilities/cache.py +0 -26
- fastmcp-2.10.6.dist-info/RECORD +0 -93
- {fastmcp-2.10.6.dist-info → fastmcp-2.11.0.dist-info}/WHEEL +0 -0
- {fastmcp-2.10.6.dist-info → fastmcp-2.11.0.dist-info}/entry_points.txt +0 -0
- {fastmcp-2.10.6.dist-info → fastmcp-2.11.0.dist-info}/licenses/LICENSE +0 -0
fastmcp/tools/tool.py
CHANGED
|
@@ -3,7 +3,15 @@ from __future__ import annotations
|
|
|
3
3
|
import inspect
|
|
4
4
|
from collections.abc import Callable
|
|
5
5
|
from dataclasses import dataclass
|
|
6
|
-
from typing import
|
|
6
|
+
from typing import (
|
|
7
|
+
TYPE_CHECKING,
|
|
8
|
+
Annotated,
|
|
9
|
+
Any,
|
|
10
|
+
Generic,
|
|
11
|
+
Literal,
|
|
12
|
+
TypeVar,
|
|
13
|
+
get_type_hints,
|
|
14
|
+
)
|
|
7
15
|
|
|
8
16
|
import mcp.types
|
|
9
17
|
import pydantic_core
|
|
@@ -122,7 +130,12 @@ class Tool(FastMCPComponent):
|
|
|
122
130
|
except RuntimeError:
|
|
123
131
|
pass # No context available
|
|
124
132
|
|
|
125
|
-
def to_mcp_tool(
|
|
133
|
+
def to_mcp_tool(
|
|
134
|
+
self,
|
|
135
|
+
*,
|
|
136
|
+
include_fastmcp_meta: bool | None = None,
|
|
137
|
+
**overrides: Any,
|
|
138
|
+
) -> MCPTool:
|
|
126
139
|
if self.title:
|
|
127
140
|
title = self.title
|
|
128
141
|
elif self.annotations and self.annotations.title:
|
|
@@ -137,6 +150,7 @@ class Tool(FastMCPComponent):
|
|
|
137
150
|
"outputSchema": self.output_schema,
|
|
138
151
|
"annotations": self.annotations,
|
|
139
152
|
"title": title,
|
|
153
|
+
"_meta": self.get_meta(include_fastmcp_meta=include_fastmcp_meta),
|
|
140
154
|
}
|
|
141
155
|
return MCPTool(**kwargs | overrides)
|
|
142
156
|
|
|
@@ -151,6 +165,7 @@ class Tool(FastMCPComponent):
|
|
|
151
165
|
exclude_args: list[str] | None = None,
|
|
152
166
|
output_schema: dict[str, Any] | None | NotSetT | Literal[False] = NotSet,
|
|
153
167
|
serializer: Callable[[Any], str] | None = None,
|
|
168
|
+
meta: dict[str, Any] | None = None,
|
|
154
169
|
enabled: bool | None = None,
|
|
155
170
|
) -> FunctionTool:
|
|
156
171
|
"""Create a Tool from a function."""
|
|
@@ -164,6 +179,7 @@ class Tool(FastMCPComponent):
|
|
|
164
179
|
exclude_args=exclude_args,
|
|
165
180
|
output_schema=output_schema,
|
|
166
181
|
serializer=serializer,
|
|
182
|
+
meta=meta,
|
|
167
183
|
enabled=enabled,
|
|
168
184
|
)
|
|
169
185
|
|
|
@@ -192,6 +208,7 @@ class Tool(FastMCPComponent):
|
|
|
192
208
|
annotations: ToolAnnotations | None = None,
|
|
193
209
|
output_schema: dict[str, Any] | None | Literal[False] = None,
|
|
194
210
|
serializer: Callable[[Any], str] | None = None,
|
|
211
|
+
meta: dict[str, Any] | None | NotSetT = NotSet,
|
|
195
212
|
enabled: bool | None = None,
|
|
196
213
|
) -> TransformedTool:
|
|
197
214
|
from fastmcp.tools.tool_transform import TransformedTool
|
|
@@ -207,6 +224,7 @@ class Tool(FastMCPComponent):
|
|
|
207
224
|
annotations=annotations,
|
|
208
225
|
output_schema=output_schema,
|
|
209
226
|
serializer=serializer,
|
|
227
|
+
meta=meta,
|
|
210
228
|
enabled=enabled,
|
|
211
229
|
)
|
|
212
230
|
|
|
@@ -226,6 +244,7 @@ class FunctionTool(Tool):
|
|
|
226
244
|
exclude_args: list[str] | None = None,
|
|
227
245
|
output_schema: dict[str, Any] | None | NotSetT | Literal[False] = NotSet,
|
|
228
246
|
serializer: Callable[[Any], str] | None = None,
|
|
247
|
+
meta: dict[str, Any] | None = None,
|
|
229
248
|
enabled: bool | None = None,
|
|
230
249
|
) -> FunctionTool:
|
|
231
250
|
"""Create a Tool from a function."""
|
|
@@ -258,6 +277,7 @@ class FunctionTool(Tool):
|
|
|
258
277
|
annotations=annotations,
|
|
259
278
|
tags=tags or set(),
|
|
260
279
|
serializer=serializer,
|
|
280
|
+
meta=meta,
|
|
261
281
|
enabled=enabled if enabled is not None else True,
|
|
262
282
|
)
|
|
263
283
|
|
|
@@ -371,7 +391,20 @@ class ParsedFunction:
|
|
|
371
391
|
input_schema = compress_schema(input_schema, prune_params=prune_params)
|
|
372
392
|
|
|
373
393
|
output_schema = None
|
|
374
|
-
|
|
394
|
+
# Get the return annotation from the signature
|
|
395
|
+
sig = inspect.signature(fn)
|
|
396
|
+
output_type = sig.return_annotation
|
|
397
|
+
|
|
398
|
+
# If the annotation is a string (from __future__ annotations), resolve it
|
|
399
|
+
if isinstance(output_type, str):
|
|
400
|
+
try:
|
|
401
|
+
# Use get_type_hints to resolve the return type
|
|
402
|
+
# include_extras=True preserves Annotated metadata
|
|
403
|
+
type_hints = get_type_hints(fn, include_extras=True)
|
|
404
|
+
output_type = type_hints.get("return", output_type)
|
|
405
|
+
except Exception:
|
|
406
|
+
# If resolution fails, keep the string annotation
|
|
407
|
+
pass
|
|
375
408
|
|
|
376
409
|
if output_type not in (inspect._empty, None, Any, ...):
|
|
377
410
|
# there are a variety of types that we don't want to attempt to
|
fastmcp/tools/tool_manager.py
CHANGED
|
@@ -10,6 +10,10 @@ from fastmcp import settings
|
|
|
10
10
|
from fastmcp.exceptions import NotFoundError, ToolError
|
|
11
11
|
from fastmcp.settings import DuplicateBehavior
|
|
12
12
|
from fastmcp.tools.tool import Tool, ToolResult
|
|
13
|
+
from fastmcp.tools.tool_transform import (
|
|
14
|
+
ToolTransformConfig,
|
|
15
|
+
apply_transformations_to_tools,
|
|
16
|
+
)
|
|
13
17
|
from fastmcp.utilities.logging import get_logger
|
|
14
18
|
|
|
15
19
|
if TYPE_CHECKING:
|
|
@@ -25,10 +29,12 @@ class ToolManager:
|
|
|
25
29
|
self,
|
|
26
30
|
duplicate_behavior: DuplicateBehavior | None = None,
|
|
27
31
|
mask_error_details: bool | None = None,
|
|
32
|
+
transformations: dict[str, ToolTransformConfig] | None = None,
|
|
28
33
|
):
|
|
29
34
|
self._tools: dict[str, Tool] = {}
|
|
30
35
|
self._mounted_servers: list[MountedServer] = []
|
|
31
36
|
self.mask_error_details = mask_error_details or settings.mask_error_details
|
|
37
|
+
self.transformations = transformations or {}
|
|
32
38
|
|
|
33
39
|
# Default to "warn" if None is provided
|
|
34
40
|
if duplicate_behavior is None:
|
|
@@ -82,7 +88,13 @@ class ToolManager:
|
|
|
82
88
|
|
|
83
89
|
# Finally, add local tools, which always take precedence
|
|
84
90
|
all_tools.update(self._tools)
|
|
85
|
-
|
|
91
|
+
|
|
92
|
+
transformed_tools = apply_transformations_to_tools(
|
|
93
|
+
tools=all_tools,
|
|
94
|
+
transformations=self.transformations,
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
return transformed_tools
|
|
86
98
|
|
|
87
99
|
async def has_tool(self, key: str) -> bool:
|
|
88
100
|
"""Check if a tool exists."""
|
|
@@ -109,6 +121,15 @@ class ToolManager:
|
|
|
109
121
|
tools_dict = await self._load_tools(via_server=True)
|
|
110
122
|
return list(tools_dict.values())
|
|
111
123
|
|
|
124
|
+
@property
|
|
125
|
+
def _tools_transformed(self) -> list[str]:
|
|
126
|
+
"""Get the local tools."""
|
|
127
|
+
|
|
128
|
+
return [
|
|
129
|
+
transformation.name or tool_name
|
|
130
|
+
for tool_name, transformation in self.transformations.items()
|
|
131
|
+
]
|
|
132
|
+
|
|
112
133
|
def add_tool_from_fn(
|
|
113
134
|
self,
|
|
114
135
|
fn: Callable[..., Any],
|
|
@@ -155,6 +176,21 @@ class ToolManager:
|
|
|
155
176
|
self._tools[tool.key] = tool
|
|
156
177
|
return tool
|
|
157
178
|
|
|
179
|
+
def add_tool_transformation(
|
|
180
|
+
self, tool_name: str, transformation: ToolTransformConfig
|
|
181
|
+
) -> None:
|
|
182
|
+
"""Add a tool transformation."""
|
|
183
|
+
self.transformations[tool_name] = transformation
|
|
184
|
+
|
|
185
|
+
def get_tool_transformation(self, tool_name: str) -> ToolTransformConfig | None:
|
|
186
|
+
"""Get a tool transformation."""
|
|
187
|
+
return self.transformations.get(tool_name)
|
|
188
|
+
|
|
189
|
+
def remove_tool_transformation(self, tool_name: str) -> None:
|
|
190
|
+
"""Remove a tool transformation."""
|
|
191
|
+
if tool_name in self.transformations:
|
|
192
|
+
del self.transformations[tool_name]
|
|
193
|
+
|
|
158
194
|
def remove_tool(self, key: str) -> None:
|
|
159
195
|
"""Remove a tool from the server.
|
|
160
196
|
|
|
@@ -175,7 +211,7 @@ class ToolManager:
|
|
|
175
211
|
filtered protocol path.
|
|
176
212
|
"""
|
|
177
213
|
# 1. Check local tools first. The server will have already applied its filter.
|
|
178
|
-
if key in self._tools:
|
|
214
|
+
if key in self._tools or key in self._tools_transformed:
|
|
179
215
|
tool = await self.get_tool(key)
|
|
180
216
|
if not tool:
|
|
181
217
|
raise NotFoundError(f"Tool {key!r} not found")
|
fastmcp/tools/tool_transform.py
CHANGED
|
@@ -4,14 +4,23 @@ import inspect
|
|
|
4
4
|
from collections.abc import Callable
|
|
5
5
|
from contextvars import ContextVar
|
|
6
6
|
from dataclasses import dataclass
|
|
7
|
-
from typing import Any, Literal
|
|
7
|
+
from typing import Annotated, Any, Literal
|
|
8
8
|
|
|
9
9
|
from mcp.types import ToolAnnotations
|
|
10
10
|
from pydantic import ConfigDict
|
|
11
|
+
from pydantic.fields import Field
|
|
12
|
+
from pydantic.functional_validators import BeforeValidator
|
|
11
13
|
|
|
12
14
|
from fastmcp.tools.tool import ParsedFunction, Tool, ToolResult, _convert_to_content
|
|
15
|
+
from fastmcp.utilities.components import _convert_set_default_none
|
|
16
|
+
from fastmcp.utilities.json_schema import compress_schema
|
|
13
17
|
from fastmcp.utilities.logging import get_logger
|
|
14
|
-
from fastmcp.utilities.types import
|
|
18
|
+
from fastmcp.utilities.types import (
|
|
19
|
+
FastMCPBaseModel,
|
|
20
|
+
NotSet,
|
|
21
|
+
NotSetT,
|
|
22
|
+
get_cached_typeadapter,
|
|
23
|
+
)
|
|
15
24
|
|
|
16
25
|
logger = get_logger(__name__)
|
|
17
26
|
|
|
@@ -193,6 +202,30 @@ class ArgTransform:
|
|
|
193
202
|
)
|
|
194
203
|
|
|
195
204
|
|
|
205
|
+
class ArgTransformConfig(FastMCPBaseModel):
|
|
206
|
+
"""A model for requesting a single argument transform."""
|
|
207
|
+
|
|
208
|
+
name: str | None = Field(default=None, description="The new name for the argument.")
|
|
209
|
+
description: str | None = Field(
|
|
210
|
+
default=None, description="The new description for the argument."
|
|
211
|
+
)
|
|
212
|
+
default: str | int | float | bool | None = Field(
|
|
213
|
+
default=None, description="The new default value for the argument."
|
|
214
|
+
)
|
|
215
|
+
hide: bool = Field(
|
|
216
|
+
default=False, description="Whether to hide the argument from the tool."
|
|
217
|
+
)
|
|
218
|
+
required: Literal[True] | None = Field(
|
|
219
|
+
default=None, description="Whether the argument is required."
|
|
220
|
+
)
|
|
221
|
+
examples: Any | None = Field(default=None, description="Examples of the argument.")
|
|
222
|
+
|
|
223
|
+
def to_arg_transform(self) -> ArgTransform:
|
|
224
|
+
"""Convert the argument transform to a FastMCP argument transform."""
|
|
225
|
+
|
|
226
|
+
return ArgTransform(**self.model_dump(exclude_unset=True)) # pyright: ignore[reportAny]
|
|
227
|
+
|
|
228
|
+
|
|
196
229
|
class TransformedTool(Tool):
|
|
197
230
|
"""A tool that is transformed from another tool.
|
|
198
231
|
|
|
@@ -333,6 +366,7 @@ class TransformedTool(Tool):
|
|
|
333
366
|
annotations: ToolAnnotations | None = None,
|
|
334
367
|
output_schema: dict[str, Any] | None | Literal[False] = None,
|
|
335
368
|
serializer: Callable[[Any], str] | None = None,
|
|
369
|
+
meta: dict[str, Any] | None | NotSetT = NotSet,
|
|
336
370
|
enabled: bool | None = None,
|
|
337
371
|
) -> TransformedTool:
|
|
338
372
|
"""Create a transformed tool from a parent tool.
|
|
@@ -357,6 +391,10 @@ class TransformedTool(Tool):
|
|
|
357
391
|
- dict: Use custom output schema
|
|
358
392
|
- False: Disable output schema and structured outputs
|
|
359
393
|
serializer: New serializer. Defaults to parent's serializer.
|
|
394
|
+
meta: Control meta information:
|
|
395
|
+
- NotSet (default): Inherit from parent tool
|
|
396
|
+
- dict: Use custom meta information
|
|
397
|
+
- None: Remove meta information
|
|
360
398
|
|
|
361
399
|
Returns:
|
|
362
400
|
TransformedTool with the specified transformations.
|
|
@@ -413,7 +451,7 @@ class TransformedTool(Tool):
|
|
|
413
451
|
if unknown_args:
|
|
414
452
|
raise ValueError(
|
|
415
453
|
f"Unknown arguments in transform_args: {', '.join(sorted(unknown_args))}. "
|
|
416
|
-
f"Parent tool has: {', '.join(sorted(parent_params))}"
|
|
454
|
+
f"Parent tool `{tool.name}` has: {', '.join(sorted(parent_params))}"
|
|
417
455
|
)
|
|
418
456
|
|
|
419
457
|
# Always create the forwarding transform
|
|
@@ -513,6 +551,7 @@ class TransformedTool(Tool):
|
|
|
513
551
|
description if not isinstance(description, NotSetT) else tool.description
|
|
514
552
|
)
|
|
515
553
|
final_title = title if not isinstance(title, NotSetT) else tool.title
|
|
554
|
+
final_meta = meta if not isinstance(meta, NotSetT) else tool.meta
|
|
516
555
|
|
|
517
556
|
transformed_tool = cls(
|
|
518
557
|
fn=final_fn,
|
|
@@ -526,6 +565,7 @@ class TransformedTool(Tool):
|
|
|
526
565
|
tags=tags or tool.tags,
|
|
527
566
|
annotations=annotations or tool.annotations,
|
|
528
567
|
serializer=serializer or tool.serializer,
|
|
568
|
+
meta=final_meta,
|
|
529
569
|
transform_args=transform_args,
|
|
530
570
|
enabled=enabled if enabled is not None else True,
|
|
531
571
|
)
|
|
@@ -613,6 +653,7 @@ class TransformedTool(Tool):
|
|
|
613
653
|
|
|
614
654
|
if parent_defs:
|
|
615
655
|
schema["$defs"] = parent_defs
|
|
656
|
+
schema = compress_schema(schema, prune_defs=True)
|
|
616
657
|
|
|
617
658
|
# Create forwarding function that closes over everything it needs
|
|
618
659
|
async def _forward(**kwargs):
|
|
@@ -798,3 +839,71 @@ class TransformedTool(Tool):
|
|
|
798
839
|
return any(
|
|
799
840
|
p.kind == inspect.Parameter.VAR_KEYWORD for p in sig.parameters.values()
|
|
800
841
|
)
|
|
842
|
+
|
|
843
|
+
|
|
844
|
+
class ToolTransformConfig(FastMCPBaseModel):
|
|
845
|
+
"""Provides a way to transform a tool."""
|
|
846
|
+
|
|
847
|
+
name: str | None = Field(default=None, description="The new name for the tool.")
|
|
848
|
+
|
|
849
|
+
title: str | None = Field(
|
|
850
|
+
default=None,
|
|
851
|
+
description="The new title of the tool.",
|
|
852
|
+
)
|
|
853
|
+
description: str | None = Field(
|
|
854
|
+
default=None,
|
|
855
|
+
description="The new description of the tool.",
|
|
856
|
+
)
|
|
857
|
+
tags: Annotated[set[str], BeforeValidator(_convert_set_default_none)] = Field(
|
|
858
|
+
default_factory=set,
|
|
859
|
+
description="The new tags for the tool.",
|
|
860
|
+
)
|
|
861
|
+
meta: dict[str, Any] | None = Field(
|
|
862
|
+
default=None,
|
|
863
|
+
description="The new meta information for the tool.",
|
|
864
|
+
)
|
|
865
|
+
|
|
866
|
+
enabled: bool = Field(
|
|
867
|
+
default=True,
|
|
868
|
+
description="Whether the tool is enabled.",
|
|
869
|
+
)
|
|
870
|
+
|
|
871
|
+
arguments: dict[str, ArgTransformConfig] = Field(
|
|
872
|
+
default_factory=dict,
|
|
873
|
+
description="A dictionary of argument transforms to apply to the tool.",
|
|
874
|
+
)
|
|
875
|
+
|
|
876
|
+
def apply(self, tool: Tool) -> TransformedTool:
|
|
877
|
+
"""Create a TransformedTool from a provided tool and this transformation configuration."""
|
|
878
|
+
|
|
879
|
+
tool_changes: dict[str, Any] = self.model_dump(
|
|
880
|
+
exclude_unset=True, exclude={"arguments"}
|
|
881
|
+
)
|
|
882
|
+
|
|
883
|
+
return TransformedTool.from_tool(
|
|
884
|
+
tool=tool,
|
|
885
|
+
**tool_changes,
|
|
886
|
+
transform_args={k: v.to_arg_transform() for k, v in self.arguments.items()},
|
|
887
|
+
)
|
|
888
|
+
|
|
889
|
+
|
|
890
|
+
def apply_transformations_to_tools(
|
|
891
|
+
tools: dict[str, Tool],
|
|
892
|
+
transformations: dict[str, ToolTransformConfig],
|
|
893
|
+
) -> dict[str, Tool]:
|
|
894
|
+
"""Apply a list of transformations to a list of tools. Tools that do not have any transforamtions
|
|
895
|
+
are left unchanged.
|
|
896
|
+
"""
|
|
897
|
+
|
|
898
|
+
transformed_tools: dict[str, Tool] = {}
|
|
899
|
+
|
|
900
|
+
for tool_name, tool in tools.items():
|
|
901
|
+
if transformation := transformations.get(tool_name):
|
|
902
|
+
transformed_tools[transformation.name or tool_name] = transformation.apply(
|
|
903
|
+
tool
|
|
904
|
+
)
|
|
905
|
+
continue
|
|
906
|
+
|
|
907
|
+
transformed_tools[tool_name] = tool
|
|
908
|
+
|
|
909
|
+
return transformed_tools
|
fastmcp/utilities/components.py
CHANGED
|
@@ -1,14 +1,21 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
from collections.abc import Sequence
|
|
2
|
-
from typing import Annotated, Any, TypeVar
|
|
4
|
+
from typing import Annotated, Any, TypedDict, TypeVar
|
|
3
5
|
|
|
4
6
|
from pydantic import BeforeValidator, Field, PrivateAttr
|
|
5
7
|
from typing_extensions import Self
|
|
6
8
|
|
|
9
|
+
import fastmcp
|
|
7
10
|
from fastmcp.utilities.types import FastMCPBaseModel
|
|
8
11
|
|
|
9
12
|
T = TypeVar("T")
|
|
10
13
|
|
|
11
14
|
|
|
15
|
+
class FastMCPMeta(TypedDict, total=False):
|
|
16
|
+
tags: list[str]
|
|
17
|
+
|
|
18
|
+
|
|
12
19
|
def _convert_set_default_none(maybe_set: set[T] | Sequence[T] | None) -> set[T]:
|
|
13
20
|
"""Convert a sequence to a set, defaulting to an empty set if None."""
|
|
14
21
|
if maybe_set is None:
|
|
@@ -36,7 +43,9 @@ class FastMCPComponent(FastMCPBaseModel):
|
|
|
36
43
|
default_factory=set,
|
|
37
44
|
description="Tags for the component.",
|
|
38
45
|
)
|
|
39
|
-
|
|
46
|
+
meta: dict[str, Any] | None = Field(
|
|
47
|
+
default=None, description="Meta information about the component"
|
|
48
|
+
)
|
|
40
49
|
enabled: bool = Field(
|
|
41
50
|
default=True,
|
|
42
51
|
description="Whether the component is enabled.",
|
|
@@ -58,6 +67,30 @@ class FastMCPComponent(FastMCPBaseModel):
|
|
|
58
67
|
"""
|
|
59
68
|
return self._key or self.name
|
|
60
69
|
|
|
70
|
+
def get_meta(
|
|
71
|
+
self, include_fastmcp_meta: bool | None = None
|
|
72
|
+
) -> dict[str, Any] | None:
|
|
73
|
+
"""
|
|
74
|
+
Get the meta information about the component.
|
|
75
|
+
|
|
76
|
+
If include_fastmcp_meta is True, a `_fastmcp` key will be added to the
|
|
77
|
+
meta, containing a `tags` field with the tags of the component.
|
|
78
|
+
"""
|
|
79
|
+
|
|
80
|
+
if include_fastmcp_meta is None:
|
|
81
|
+
include_fastmcp_meta = fastmcp.settings.include_fastmcp_meta
|
|
82
|
+
|
|
83
|
+
meta = self.meta or {}
|
|
84
|
+
|
|
85
|
+
if include_fastmcp_meta:
|
|
86
|
+
fastmcp_meta = FastMCPMeta(tags=sorted(self.tags))
|
|
87
|
+
# overwrite any existing _fastmcp meta with keys from the new one
|
|
88
|
+
if upstream_meta := meta.get("_fastmcp"):
|
|
89
|
+
fastmcp_meta = upstream_meta | fastmcp_meta
|
|
90
|
+
meta["_fastmcp"] = fastmcp_meta
|
|
91
|
+
|
|
92
|
+
return meta or None
|
|
93
|
+
|
|
61
94
|
def with_key(self, key: str) -> Self:
|
|
62
95
|
return self.model_copy(update={"_key": key})
|
|
63
96
|
|