Skip to content

Commit b65ab81

Browse files
committed
feat(func_metadata): expose skip_names argument
And move the `if param.name in skip_names:` check to the top It allows users to ignore specific parameters, such as optional ones and especially those starting with an underscore.
1 parent d788424 commit b65ab81

File tree

5 files changed

+43
-15
lines changed

5 files changed

+43
-15
lines changed

src/mcp/server/fastmcp/server.py

+16-3
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,7 @@ def add_tool(
246246
fn: AnyFunction,
247247
name: str | None = None,
248248
description: str | None = None,
249+
skip_names: Sequence[str] = (),
249250
) -> None:
250251
"""Add a tool to the server.
251252
@@ -256,11 +257,21 @@ def add_tool(
256257
fn: The function to register as a tool
257258
name: Optional name for the tool (defaults to function name)
258259
description: Optional description of what the tool does
260+
skip_names: A list of parameter names to skip. These will not be included in
261+
the model.
259262
"""
260-
self._tool_manager.add_tool(fn, name=name, description=description)
263+
self._tool_manager.add_tool(
264+
fn,
265+
name=name,
266+
description=description,
267+
skip_names=skip_names,
268+
)
261269

262270
def tool(
263-
self, name: str | None = None, description: str | None = None
271+
self,
272+
name: str | None = None,
273+
description: str | None = None,
274+
skip_names: Sequence[str] = (),
264275
) -> Callable[[AnyFunction], AnyFunction]:
265276
"""Decorator to register a tool.
266277
@@ -271,6 +282,8 @@ def tool(
271282
Args:
272283
name: Optional name for the tool (defaults to function name)
273284
description: Optional description of what the tool does
285+
skip_names: A list of parameter names to skip. These will not be included in
286+
the model.
274287
275288
Example:
276289
@server.tool()
@@ -295,7 +308,7 @@ async def async_tool(x: int, context: Context) -> str:
295308
)
296309

297310
def decorator(fn: AnyFunction) -> AnyFunction:
298-
self.add_tool(fn, name=name, description=description)
311+
self.add_tool(fn, name=name, description=description, skip_names=skip_names)
299312
return fn
300313

301314
return decorator

src/mcp/server/fastmcp/tools/base.py

+6-5
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from __future__ import annotations as _annotations
22

33
import inspect
4-
from collections.abc import Callable
4+
from collections.abc import Callable, Sequence
55
from typing import TYPE_CHECKING, Any
66

77
from pydantic import BaseModel, Field
@@ -38,6 +38,7 @@ def from_function(
3838
name: str | None = None,
3939
description: str | None = None,
4040
context_kwarg: str | None = None,
41+
skip_names: Sequence[str] = (),
4142
) -> Tool:
4243
"""Create a Tool from a function."""
4344
from mcp.server.fastmcp import Context
@@ -57,10 +58,10 @@ def from_function(
5758
context_kwarg = param_name
5859
break
5960

60-
func_arg_metadata = func_metadata(
61-
fn,
62-
skip_names=[context_kwarg] if context_kwarg is not None else [],
63-
)
61+
if context_kwarg is not None:
62+
skip_names = (context_kwarg, *skip_names)
63+
64+
func_arg_metadata = func_metadata(fn, skip_names=skip_names)
6465
parameters = func_arg_metadata.arg_model.model_json_schema()
6566

6667
return cls(

src/mcp/server/fastmcp/tools/tool_manager.py

+8-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from __future__ import annotations as _annotations
22

3-
from collections.abc import Callable
3+
from collections.abc import Callable, Sequence
44
from typing import TYPE_CHECKING, Any
55

66
from mcp.server.fastmcp.exceptions import ToolError
@@ -35,9 +35,15 @@ def add_tool(
3535
fn: Callable[..., Any],
3636
name: str | None = None,
3737
description: str | None = None,
38+
skip_names: Sequence[str] = (),
3839
) -> Tool:
3940
"""Add a tool to the server."""
40-
tool = Tool.from_function(fn, name=name, description=description)
41+
tool = Tool.from_function(
42+
fn,
43+
name=name,
44+
description=description,
45+
skip_names=skip_names,
46+
)
4147
existing = self._tools.get(tool.name)
4248
if existing:
4349
if self.warn_on_duplicate_tools:

src/mcp/server/fastmcp/utilities/func_metadata.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -130,12 +130,12 @@ def func_metadata(
130130
dynamic_pydantic_model_params: dict[str, Any] = {}
131131
globalns = getattr(func, "__globals__", {})
132132
for param in params.values():
133+
if param.name in skip_names:
134+
continue
133135
if param.name.startswith("_"):
134136
raise InvalidSignature(
135137
f"Parameter {param.name} of {func.__name__} cannot start with '_'"
136138
)
137-
if param.name in skip_names:
138-
continue
139139
annotation = param.annotation
140140

141141
# `x: None` / `x: None = None`

tests/server/fastmcp/test_func_metadata.py

+11-3
Original file line numberDiff line numberDiff line change
@@ -180,18 +180,26 @@ def test_skip_names():
180180
"""Test that skipped parameters are not included in the model"""
181181

182182
def func_with_many_params(
183-
keep_this: int, skip_this: str, also_keep: float, also_skip: bool
183+
keep_this: int,
184+
skip_this: str,
185+
also_keep: float,
186+
also_skip: bool,
187+
_skip_this_too: int = 0,
184188
):
185-
return keep_this, skip_this, also_keep, also_skip
189+
return keep_this, skip_this, also_keep, also_skip, _skip_this_too
186190

187191
# Skip some parameters
188-
meta = func_metadata(func_with_many_params, skip_names=["skip_this", "also_skip"])
192+
meta = func_metadata(
193+
func_with_many_params,
194+
skip_names=["skip_this", "also_skip", "_skip_this_too"],
195+
)
189196

190197
# Check model fields
191198
assert "keep_this" in meta.arg_model.model_fields
192199
assert "also_keep" in meta.arg_model.model_fields
193200
assert "skip_this" not in meta.arg_model.model_fields
194201
assert "also_skip" not in meta.arg_model.model_fields
202+
assert "_skip_this_too" not in meta.arg_model.model_fields
195203

196204
# Validate that we can call with only non-skipped parameters
197205
model: BaseModel = meta.arg_model.model_validate({"keep_this": 1, "also_keep": 2.5}) # type: ignore

0 commit comments

Comments
 (0)