Compare commits

...

1 commit

Author SHA1 Message Date
claude[bot]
d4cd0b03dd Fix hidden path parameters appearing in tool descriptions
Remove path parameter descriptions from format_simple_description since
parameters are already visible in the tool's inputSchema with their
descriptions. This prevents hidden parameters (via transforms) from
still appearing in descriptions and avoids redundant information.

Fixes #3130

Co-authored-by: Jeremiah Lowin <jlowin@users.noreply.github.com>
2026-02-10 16:11:38 +00:00
2 changed files with 253 additions and 14 deletions

View file

@ -206,20 +206,10 @@ def format_simple_description(
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)
# All parameters (path, query, etc.) are already visible in the tool's inputSchema,
# so we don't need to duplicate them in the description. This also prevents issues
# where hidden parameters (via transforms) would still appear in the description.
return base_description
def format_description_with_responses(

View file

@ -0,0 +1,249 @@
"""Tests for hidden path parameters in OpenAPI tool descriptions."""
import httpx
import pytest
from fastmcp import FastMCP
from fastmcp.client import Client
from fastmcp.server.transforms import ToolTransform
from fastmcp.tools.tool_transform import ArgTransformConfig, ToolTransformConfig
@pytest.mark.asyncio
async def test_hidden_path_parameter_not_in_description():
"""Test that hidden path parameters are not included in tool descriptions.
This test replicates the bug described in issue #3130 where path parameters
that are hidden via ToolTransform still appear in the tool description.
"""
openapi_spec = {
"openapi": "3.1.0",
"info": {"title": "Test API", "version": "0.1.0"},
"paths": {
"/hello/{not_for_agent}": {
"get": {
"operationId": "hello",
"summary": "Say hello",
"parameters": [
{
"name": "not_for_agent",
"in": "path",
"required": True,
"description": "Secret setting",
"schema": {
"type": "string",
"title": "Not For Agent"
}
}
],
"responses": {
"200": {
"description": "Success response",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"message": {"type": "string"}
}
}
}
}
}
}
}
}
}
}
async with httpx.AsyncClient(base_url="http://localhost:8000") as http_client:
mcp = FastMCP.from_openapi(
openapi_spec=openapi_spec,
client=http_client,
)
# Hide not_for_agent parameter and set default
mcp.add_transform(
ToolTransform({
"hello": ToolTransformConfig(
arguments={
"not_for_agent": ArgTransformConfig(
hide=True,
default="secret",
)
}
)
})
)
async with Client(mcp) as mcp_client:
tools = await mcp_client.list_tools()
assert len(tools) == 1
hello_tool = tools[0]
assert hello_tool.name == "hello"
# The parameter should not be in the schema
assert "not_for_agent" not in hello_tool.inputSchema.get("properties", {})
# The BUG: parameter description should not appear in the tool description
assert "Secret setting" not in hello_tool.description
assert "not_for_agent" not in hello_tool.description
# The description should only contain the summary
assert "Say hello" in hello_tool.description
@pytest.mark.asyncio
async def test_visible_path_parameter_in_schema():
"""Test that visible path parameters are included in the inputSchema.
Path parameters are not included in the description since they're already
visible in the inputSchema with their descriptions.
"""
openapi_spec = {
"openapi": "3.1.0",
"info": {"title": "Test API", "version": "0.1.0"},
"paths": {
"/users/{user_id}": {
"get": {
"operationId": "get_user",
"summary": "Get user by ID",
"parameters": [
{
"name": "user_id",
"in": "path",
"required": True,
"description": "The user's unique identifier",
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "Success response",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"id": {"type": "string"},
"name": {"type": "string"}
}
}
}
}
}
}
}
}
}
}
async with httpx.AsyncClient(base_url="http://localhost:8000") as http_client:
mcp = FastMCP.from_openapi(
openapi_spec=openapi_spec,
client=http_client,
)
async with Client(mcp) as mcp_client:
tools = await mcp_client.list_tools()
assert len(tools) == 1
user_tool = tools[0]
assert user_tool.name == "get_user"
# The parameter should be in the schema with its description
assert "user_id" in user_tool.inputSchema.get("properties", {})
assert user_tool.inputSchema["properties"]["user_id"]["description"] == "The user's unique identifier"
# The description should only contain the summary, not parameter details
assert user_tool.description == "Get user by ID"
@pytest.mark.asyncio
async def test_mixed_path_parameters_in_schema():
"""Test that when some path params are hidden, only visible ones appear in schema."""
openapi_spec = {
"openapi": "3.1.0",
"info": {"title": "Test API", "version": "0.1.0"},
"paths": {
"/api/{api_version}/users/{user_id}": {
"get": {
"operationId": "get_versioned_user",
"summary": "Get user from specific API version",
"parameters": [
{
"name": "api_version",
"in": "path",
"required": True,
"description": "API version to use",
"schema": {
"type": "string"
}
},
{
"name": "user_id",
"in": "path",
"required": True,
"description": "The user's unique identifier",
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "Success response",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"id": {"type": "string"},
"name": {"type": "string"}
}
}
}
}
}
}
}
}
}
}
async with httpx.AsyncClient(base_url="http://localhost:8000") as http_client:
mcp = FastMCP.from_openapi(
openapi_spec=openapi_spec,
client=http_client,
)
# Hide api_version but not user_id
mcp.add_transform(
ToolTransform({
"get_versioned_user": ToolTransformConfig(
arguments={
"api_version": ArgTransformConfig(
hide=True,
default="v1",
)
}
)
})
)
async with Client(mcp) as mcp_client:
tools = await mcp_client.list_tools()
assert len(tools) == 1
tool = tools[0]
assert tool.name == "get_versioned_user"
# Only user_id should be in the schema
assert "user_id" in tool.inputSchema.get("properties", {})
assert "api_version" not in tool.inputSchema.get("properties", {})
# The description should only contain the summary, not parameter details
assert tool.description == "Get user from specific API version"