Repository navigation
Description
Is your feature request related to a problem? Please describe.
Tool descriptions are not parsed as expected from the function docstring when using FastMCP. This affects tool calling performance.
Currently, FastMCP does some function inspection to create the docstring here:
python-sdk/src/mcp/server/fastmcp/tools/base.py
Lines 55 to 59 in 775f879
func_arg_metadata = func_metadata( fn, skip_names=[context_kwarg] if context_kwarg is not None else [], ) parameters = func_arg_metadata.arg_model.model_json_schema() python-sdk/src/mcp/server/fastmcp/utilities/func_metadata.py
Lines 105 to 174 in 775f879
def func_metadata(func: Callable, skip_names: Sequence[str] = ()) -> FuncMetadata: """Given a function, return metadata including a pydantic model representing its signature. The use case for this is ``` meta = func_to_pyd(func) validated_args = meta.arg_model.model_validate(some_raw_data_dict) return func(**validated_args.model_dump_one_level()) ``` **critically** it also provides pre-parse helper to attempt to parse things from JSON. Args: func: The function to convert to a pydantic model skip_names: A list of parameter names to skip. These will not be included in the model. Returns: A pydantic model representing the function's signature. """ sig = _get_typed_signature(func) params = sig.parameters dynamic_pydantic_model_params: dict[str, Any] = {} globalns = getattr(func, "__globals__", {}) for param in params.values(): if param.name.startswith("_"): raise InvalidSignature( f"Parameter {param.name} of {func.__name__} cannot start with '_'" ) if param.name in skip_names: continue annotation = param.annotation # `x: None` / `x: None = None` if annotation is None: annotation = Annotated[ None, Field( default=param.default if param.default is not inspect.Parameter.empty else PydanticUndefined ), ] # Untyped field if annotation is inspect.Parameter.empty: annotation = Annotated[ Any, Field(), # 🤷 WithJsonSchema({"title": param.name, "type": "string"}), ] field_info = FieldInfo.from_annotated_attribute( _get_typed_annotation(annotation, globalns), param.default if param.default is not inspect.Parameter.empty else PydanticUndefined, ) dynamic_pydantic_model_params[param.name] = (field_info.annotation, field_info) continue arguments_model = create_model( f"{func.__name__}Arguments", **dynamic_pydantic_model_params, __base__=ArgModelBase, ) resp = FuncMetadata(arg_model=arguments_model) return resp
From my understanding, it creates a FuncMetadata model in pydantic which then gets converted to jsonschema.
Current behaviour:
If we have a tool such as:
def add_numbers(a: float, b: float) -> float:
"""
Adds two numbers and returns the result.
Args:
a (float): The first number.
b (float): The second number.
Returns:
float: The sum of a and b.
"""
return a + b
it gets parsed into:
>>> func_arg_metadata = func_metadata(add_numbers)
>>> parameters = func_arg_metadata.arg_model.model_json_schema()
>>> parameters
{'properties': {'a': {'title': 'A', 'type': 'number'}, 'b': {'title': 'B', 'type': 'number'}}, 'required': ['a', 'b'], 'title': 'add_numbersArguments', 'type': 'object'}
>>> add_numbers.__doc__
'\nAdds two numbers and returns the result.\n\nArgs:\n a (float): The first number.\n b (float): The second number.\n\nReturns:\n float: The sum of a and b.\n'
Describe the solution you'd like
It'd be nicer to follow one of the python docstring styles and parse out the argument descriptions from the docstring.
{
"name": "add_numbers",
"description": "Adds two numbers and returns the sum.",
"parameters": {
"type": "object",
"properties": {
"a": {
"type": "number",
"description": "The first number to add."
},
"b": {
"type": "number",
"description": "The second number to add."
}
},
"required": ["a", "b"]
}
}
Describe alternatives you've considered
we used to do this in a previous python version of goose: https://github.com/block/goose/blob/eccb1b22614f39b751db4e5efd73d728d9ca40fc/packages/exchange/src/exchange/utils.py#L82-L107
here are some test examples: https://github.com/block/goose/blob/eccb1b22614f39b751db4e5efd73d728d9ca40fc/packages/exchange/tests/test_utils.py#L32-L136
Additional context
I am happy to add this in - wanted to post this first to check that you're okay with enforcing a docstring style ("google", "numpy", "sphinx") & adding griffe as a dependency.
Activity
maxisbey commentedon Dec 8, 2025
Note: This issue now consolidates:
inspect.getdoc(fn)instead offn.__doc__Important implementation detail from #1134: Using
inspect.getdoc(fn)instead offn.__doc__provides two benefits:This should be incorporated as part of the docstring parsing implementation.
AI Disclaimer
Ilan-kayesar commentedon Jan 1, 2026
I can try working on this
feat: extract parameter descriptions from function docstrings
heath-hunnicutt-ruach-tov commentedon Feb 2, 2026
I have submitted PR #1979 implementing this feature. The approach follows the maintainer guidance to infer docstring format automatically, adapted from pydantic-ai's implementation using griffe. Google, NumPy, and Sphinx formats are supported with auto-detection. Field(description=...) annotations take precedence over docstring descriptions for backward compatibility.
Apologies for not commenting here before starting — I should have signaled intent first per the contributing guide.
Kludex commentedon Feb 2, 2026
Pydantic will create a separate package from the logic we have in PydanticAI so we can use it here.
feat: extract parameter descriptions from docstrings into tool JSON s…
ameenalkhaldi commentedon Mar 15, 2026
I've opened a PR addressing this: #2295
It parses Google, NumPy, and Sphinx-style docstrings using griffe (as suggested by @Kludex) to automatically extract parameter descriptions into the generated tool JSON schemas. Explicit
Field(description=...)annotations always take priority.fix: remove GoogleOptions import for griffe compat with older versions
refactor: replace griffe with zero-dependency regex parser
m13v commentedon Mar 18, 2026
m13v commentedon Mar 18, 2026
feat: extract parameter descriptions from docstrings into JSON schema
feat: extract parameter descriptions from docstrings into JSON schema
feat: extract parameter descriptions from docstrings into JSON schema
feat: extract parameter descriptions from docstrings into JSON schema
feat: extract parameter descriptions from docstrings into JSON schema
feat: parse Google-style docstrings to populate tool parameter descri…
feat: parse Google-style docstrings to populate tool parameter descri…
Gaurangwad commentedon Jun 15, 2026
I'd like to take this. Plan: parse Google/NumPy/Sphinx docstrings via griffe (which is already in the dependency tree) and populate each parameter's description in the generated tool schema. An explicit Annotated[T, Field(description=...)] would still take precedence, and functions without docstrings are unaffected.
One open question from the original issue, are you okay adding griffe as a runtime dependency, or would you prefer a dependency-free parser? Happy to go either way. I have a branch with tests ready to open as a PR.
Extract tool parameter descriptions from docstrings
Extract tool parameter descriptions from docstrings
Extract tool parameter descriptions from docstrings
Gaurangwad commentedon Jun 15, 2026
I've opened #2876 to implement this. Approach: parse Google/NumPy/Sphinx docstrings via griffe (already in the dependency tree) and populate each parameter's description in the generated tool schema. An explicit Annotated[T, Field(description=...)] still takes precedence, and functions without docstrings are unaffected. Tests cover all three styles plus edge cases, and the change is at 100% coverage.
One open question from this issue: I added griffe as a runtime dependency. Happy to switch to a dependency-free parser if maintainers would prefer to avoid that...let me know.
feat: propagate docstring parameter descriptions to JSON Schema
feat(anti-pattern): add multi-parser-silent-fallback lesson
feat(lesson): add lesson-22 multi-parser-silent-fallback
1Ash0 commentedon Nov 6, 2026
I understand parsing is on hold until Pydantic's standalone package exists. Two data points for when it's decided:
1. Does parsing change which tool a model picks? Not in my test.
I wrote 18 look-alike tools, each with the same docs in Google, NumPy, reST and plain style, plus a "parsed" version (parameter docs moved into the schema). I sent 36 requests × 2 tool orders to gpt-oss-20b (temperature 0).
It never chose a wrong tool. Every miss was the model asking a clarifying question instead of calling a tool. Selection is at ceiling here, so this shows no benefit and doesn't rule one out for weaker models or bigger servers. Argument quality wasn't measured.
2. The
inspect.getdochalf (#1134) doesn't need a parser, and it's visible today.Tool.from_functionsendsfn.__doc__as-is. On Python 3.11 and 3.12, both supported, the description clients receive keeps the source indentation:So the same server sends different tool text depending on the interpreter.
inspect.getdocfixes that and the inherited-docstring case without a new dependency.Scripts, every request/response, and the analysis: https://github.com/1Ash0/mcp-226-docstring-eval. Prepared with AI assistance; I ran and checked everything.