Skip to content

Commit cb0293d

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 babb477 commit cb0293d

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
@@ -245,6 +245,7 @@ def add_tool(
245245
fn: AnyFunction,
246246
name: str | None = None,
247247
description: str | None = None,
248+
skip_names: Sequence[str] = (),
248249
) -> None:
249250
"""Add a tool to the server.
250251
@@ -255,11 +256,21 @@ def add_tool(
255256
fn: The function to register as a tool
256257
name: Optional name for the tool (defaults to function name)
257258
description: Optional description of what the tool does
259+
skip_names: A list of parameter names to skip. These will not be included in
260+
the model.
258261
"""
259-
self._tool_manager.add_tool(fn, name=name, description=description)
262+
self._tool_manager.add_tool(
263+
fn,
264+
name=name,
265+
description=description,
266+
skip_names=skip_names,
267+
)
260268

261269
def tool(
262-
self, name: str | None = None, description: str | None = None
270+
self,
271+
name: str | None = None,
272+
description: str | None = None,
273+
skip_names: Sequence[str] = (),
263274
) -> Callable[[AnyFunction], AnyFunction]:
264275
"""Decorator to register a tool.
265276
@@ -270,6 +281,8 @@ def tool(
270281
Args:
271282
name: Optional name for the tool (defaults to function name)
272283
description: Optional description of what the tool does
284+
skip_names: A list of parameter names to skip. These will not be included in
285+
the model.
273286
274287
Example:
275288
@server.tool()
@@ -294,7 +307,7 @@ async def async_tool(x: int, context: Context) -> str:
294307
)
295308

296309
def decorator(fn: AnyFunction) -> AnyFunction:
297-
self.add_tool(fn, name=name, description=description)
310+
self.add_tool(fn, name=name, description=description, skip_names=skip_names)
298311
return fn
299312

300313
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, get_origin
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
@@ -59,10 +60,10 @@ def from_function(
5960
context_kwarg = param_name
6061
break
6162

62-
func_arg_metadata = func_metadata(
63-
fn,
64-
skip_names=[context_kwarg] if context_kwarg is not None else [],
65-
)
63+
if context_kwarg is not None:
64+
skip_names = (context_kwarg, *skip_names)
65+
66+
func_arg_metadata = func_metadata(fn, skip_names=skip_names)
6667
parameters = func_arg_metadata.arg_model.model_json_schema()
6768

6869
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)