Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 40 additions & 36 deletions docs/python-sdk/fastmcp-server-context.mdx

Large diffs are not rendered by default.

77 changes: 39 additions & 38 deletions docs/python-sdk/fastmcp-server-dependencies.mdx

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions docs/python-sdk/fastmcp-server-providers-openapi-provider.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ OpenAPIProvider for creating MCP components from OpenAPI specifications.

## Classes

### `OpenAPIProvider` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/providers/openapi/provider.py#L52" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
### `OpenAPIProvider` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/providers/openapi/provider.py#L51" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>


Provider that creates MCP components from an OpenAPI specification.
Expand All @@ -21,7 +21,7 @@ spec. Each component makes HTTP calls to the described API endpoints.

**Methods:**

#### `lifespan` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/providers/openapi/provider.py#L182" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
#### `lifespan` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/providers/openapi/provider.py#L181" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>

```python
lifespan(self) -> AsyncIterator[None]
Expand All @@ -30,7 +30,7 @@ lifespan(self) -> AsyncIterator[None]
Manage the lifecycle of the auto-created httpx client.


#### `get_tasks` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/providers/openapi/provider.py#L447" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
#### `get_tasks` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/providers/openapi/provider.py#L431" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>

```python
get_tasks(self) -> Sequence[FastMCPComponent]
Expand Down
21 changes: 1 addition & 20 deletions docs/python-sdk/fastmcp-utilities-openapi-formatters.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -72,26 +72,7 @@ format_json_for_description(data: Any, indent: int = 2) -> str
Formats Python data as a JSON string block for Markdown.


### `format_simple_description` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/openapi/formatters.py#L192" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>

```python
format_simple_description(base_description: str, parameters: list[ParameterInfo] | None = None, request_body: RequestBodyInfo | None = None) -> str
```


Formats a simple description for MCP objects (tools, resources, prompts).
Excludes response details, examples, and verbose status codes.

**Args:**
- `base_description`: The initial description to be formatted.
- `parameters`: A list of parameter information.
- `request_body`: Information about the request body.

**Returns:**
- The formatted description string with minimal details.


### `format_description_with_responses` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/openapi/formatters.py#L225" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
### `format_description_with_responses` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/openapi/formatters.py#L192" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>

```python
format_description_with_responses(base_description: str, responses: dict[str, Any], parameters: list[ParameterInfo] | None = None, request_body: RequestBodyInfo | None = None) -> str
Expand Down
2 changes: 0 additions & 2 deletions src/fastmcp/experimental/utilities/openapi/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
RequestBodyInfo,
ResponseInfo,
extract_output_schema_from_responses,
format_simple_description,
parse_openapi_to_http_routes,
_combine_schemas,
)
Expand All @@ -32,6 +31,5 @@
"ResponseInfo",
"_combine_schemas",
"extract_output_schema_from_responses",
"format_simple_description",
"parse_openapi_to_http_routes",
]
22 changes: 3 additions & 19 deletions src/fastmcp/server/providers/openapi/provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@
from fastmcp.utilities.openapi import (
HTTPRoute,
extract_output_schema_from_responses,
format_simple_description,
parse_openapi_to_http_routes,
)
from fastmcp.utilities.openapi.director import RequestDirector
Expand Down Expand Up @@ -255,18 +254,13 @@ def _create_openapi_tool(
or route.summary
or f"Executes {route.method} {route.path}"
)
enhanced_description = format_simple_description(
base_description=base_description,
parameters=route.parameters,
request_body=route.request_body,
)

tool = OpenAPITool(
client=self._client,
route=route,
director=self._director,
name=tool_name,
description=enhanced_description,
description=base_description,
parameters=combined_schema,
output_schema=output_schema,
tags=set(route.tags or []) | tags,
Expand All @@ -293,19 +287,14 @@ def _create_openapi_resource(
base_description = (
route.description or route.summary or f"Represents {route.path}"
)
enhanced_description = format_simple_description(
base_description=base_description,
parameters=route.parameters,
request_body=route.request_body,
)

resource = OpenAPIResource(
client=self._client,
route=route,
director=self._director,
uri=resource_uri,
name=resource_name,
description=enhanced_description,
description=base_description,
mime_type=_extract_mime_type_from_route(route),
tags=set(route.tags or []) | tags,
)
Expand Down Expand Up @@ -338,11 +327,6 @@ def _create_openapi_template(
base_description = (
route.description or route.summary or f"Template for {route.path}"
)
enhanced_description = format_simple_description(
base_description=base_description,
parameters=route.parameters,
request_body=route.request_body,
)

template_params_schema = {
"type": "object",
Expand Down Expand Up @@ -372,7 +356,7 @@ def _create_openapi_template(
director=self._director,
uri_template=uri_template_str,
name=template_name,
description=enhanced_description,
description=base_description,
parameters=template_params_schema,
tags=set(route.tags or []) | tags,
mime_type=_extract_mime_type_from_route(route),
Expand Down
2 changes: 0 additions & 2 deletions src/fastmcp/utilities/openapi/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
format_deep_object_parameter,
format_description_with_responses,
format_json_for_description,
Comment on lines 20 to 22
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Keep format_simple_description importable for compatibility

Removing format_simple_description from the public OpenAPI utilities API introduces a backwards-incompatible runtime break: existing integrations that do from fastmcp.utilities.openapi import format_simple_description (or import it from ...openapi.formatters) will now fail with ImportError immediately after upgrade. Even if provider code no longer uses it, this symbol was previously exported and documented, so it should remain as a deprecated shim to avoid breaking downstream code unexpectedly.

Useful? React with 👍 / 👎.

format_simple_description,
generate_example_from_schema,
)

Expand Down Expand Up @@ -57,7 +56,6 @@
"format_deep_object_parameter",
"format_description_with_responses",
"format_json_for_description",
"format_simple_description",
"generate_example_from_schema",
"parse_openapi_to_http_routes",
]
34 changes: 0 additions & 34 deletions src/fastmcp/utilities/openapi/formatters.py
Original file line number Diff line number Diff line change
Expand Up @@ -189,39 +189,6 @@ def format_json_for_description(data: Any, indent: int = 2) -> str:
return f"```\nCould not serialize to JSON: {data}\n```"


def format_simple_description(
base_description: str,
parameters: list[ParameterInfo] | None = None,
request_body: RequestBodyInfo | None = None,
) -> str:
"""
Formats a simple description for MCP objects (tools, resources, prompts).
Excludes response details, examples, and verbose status codes.

Args:
base_description (str): The initial description to be formatted.
parameters (list[ParameterInfo] | None, optional): A list of parameter information.
request_body (RequestBodyInfo | None, optional): Information about the request body.

Returns:
str: The formatted description string with minimal details.
"""
desc_parts = [base_description]

# Only add critical parameter information if they have descriptions
if parameters:
path_params = [p for p in parameters if p.location == "path" and p.description]
if path_params:
desc_parts.append("\n\n**Path Parameters:**")
for param in path_params:
desc_parts.append(f"\n- **{param.name}**: {param.description}")

# Skip query parameters, request body details, and all response information
# These are already captured in the inputSchema

return "\n".join(desc_parts)


def format_description_with_responses(
base_description: str,
responses: dict[
Expand Down Expand Up @@ -384,6 +351,5 @@ def format_description_with_responses(
"format_deep_object_parameter",
"format_description_with_responses",
"format_json_for_description",
"format_simple_description",
"generate_example_from_schema",
]
78 changes: 77 additions & 1 deletion tests/server/test_tool_transformation.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
import httpx

from fastmcp import FastMCP
from fastmcp.client import Client
from fastmcp.server.transforms import ToolTransform
from fastmcp.tools.tool_transform import ToolTransformConfig
from fastmcp.tools.tool_transform import (
ArgTransformConfig,
ToolTransformConfig,
)


async def test_tool_transformation_via_layer():
Expand Down Expand Up @@ -207,3 +213,73 @@ def my_tool() -> str:

# Tool should now be visible
assert "my_tool" in tool_names


async def test_openapi_path_params_not_duplicated_in_description():
"""Path parameter details should live in inputSchema, not the description.

Regression test for https://github.com/jlowin/fastmcp/issues/3130 — hiding
a path param via ToolTransform left stale references in the description
because the description was generated before transforms ran. The fix is to
keep parameter docs in inputSchema only, where transforms can control them.
"""
spec = {
"openapi": "3.1.0",
"info": {"title": "Test", "version": "0.1.0"},
"paths": {
"/api/{version}/users/{user_id}": {
"get": {
"operationId": "my_endpoint",
"summary": "My endpoint",
"parameters": [
{
"name": "version",
"in": "path",
"required": True,
"description": "API version",
"schema": {"type": "string"},
},
{
"name": "user_id",
"in": "path",
"required": True,
"description": "The user ID",
"schema": {"type": "string"},
},
],
"responses": {"200": {"description": "OK"}},
},
},
},
}

async with httpx.AsyncClient(base_url="http://localhost") as http_client:
mcp = FastMCP.from_openapi(openapi_spec=spec, client=http_client)

# Hide one of the two path params
mcp.add_transform(
ToolTransform(
{
"my_endpoint": ToolTransformConfig(
arguments={
"version": ArgTransformConfig(hide=True, default="v1"),
}
)
}
)
)

async with Client(mcp) as client:
tools = await client.list_tools()
tool = tools[0]

# Description should be the summary only — no parameter details
assert tool.description == "My endpoint"

# Hidden param gone from schema, visible param still present
assert "version" not in tool.inputSchema.get("properties", {})
assert "user_id" in tool.inputSchema["properties"]
assert (
tool.inputSchema["properties"]["user_id"]["description"]
== "The user ID"
)