feat: propagate docstring parameter descriptions to JSON Schema (#226)

Closed
ZS
2mo ago
+242-1

Summary

Parse Python docstrings (Google, NumPy, and Sphinx styles) using griffe to extract parameter descriptions, then include them in the generated JSON Schema via Field(description=...).

This closes #226.

Motivation

When an MCP tool function has a docstring with an Args/Parameters section, the descriptions are currently ignored during JSON Schema generation. This means LLMs only see parameter names and types, missing the rich context that docstrings provide.

Implementation

Changes

  1. pyproject.toml: Added griffe>=1.0.0 to runtime dependencies
  2. func_metadata.py:
    • Added _parse_docstring_params(func) — parses docstrings via griffe and returns {param_name: description}
    • Added _has_field_description(annotation) — checks if an Annotated[...] field already has a Field(description=...) to avoid overriding explicit descriptions
    • Modified func_metadata() loop to add Field(description=...) to each parameter's field kwargs when a docstring description is available
  3. test_func_metadata.py: Added 8 new tests covering all three docstring styles, edge cases, and precedence rules

Design Decisions

  • Multi-style support: Tries Google, NumPy, and Sphinx parsers; returns the result from whichever yields the most parameter descriptions
  • Explicit > inferred: Annotated[str, Field(description="...")] takes precedence over docstring descriptions
  • Graceful degradation: No docstring, no Args section, or unrecognized format → no descriptions added (no errors)
  • Warning suppression: griffe emits noisy "No type or annotation for parameter" warnings since we're parsing descriptions not types — these are silenced via the griffe logger

Before / After

def search(query: str, limit: int = 10) -> list[dict]:
    """Search the database.
    
    Args:
        query: The search query string.
        limit: Maximum number of results to return.
    """

Before — schema has no descriptions:

{"query": {"title": "Query", "type": "string"}, "limit": {"default": 10, "title": "Limit", "type": "integer"}}

After — schema includes descriptions:

{"query": {"title": "Query", "type": "string", "description": "The search query string."}, "limit": {"default": 10, "title": "Limit", "type": "integer", "description": "Maximum number of results to return."}}

Testing

All 63 existing tests pass unchanged. 8 new tests added:

  • test_google_style_docstring_descriptions
  • test_numpy_style_docstring_descriptions
  • test_sphinx_style_docstring_descriptions
  • test_no_docstring
  • test_docstring_no_args_section
  • test_docstring_partial_args
  • test_docstring_with_skip_names
  • test_field_description_preserved_over_docstring

🤖 Generated with Claude Code