Skip to content

Commit 1ba4db5

Browse files
committed
fix problem with post requests
1 parent 3d36432 commit 1ba4db5

File tree

2 files changed

+40
-54
lines changed

2 files changed

+40
-54
lines changed

fastapi_mcp/http_tools.py

+18-24
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,9 @@
1515
from pydantic import Field
1616

1717
from .openapi_utils import (
18-
OPENAPI_PYTHON_TYPES_MAP,
1918
clean_schema_for_display,
20-
extract_model_examples_from_components,
2119
generate_example_from_schema,
22-
parse_parameters_for_args_schema,
20+
parse_param_schema_for_python_type_and_default,
2321
resolve_schema_references,
2422
PYTHON_TYPE_IMPORTS,
2523
)
@@ -54,7 +52,7 @@ def create_mcp_tools_from_openapi(
5452
)
5553

5654
# Resolve all references in the schema at once
57-
openapi_schema = resolve_schema_references(openapi_schema, openapi_schema)
55+
resolved_openapi_schema = resolve_schema_references(openapi_schema, openapi_schema)
5856

5957
if not base_url:
6058
# Try to determine the base URL from FastAPI config
@@ -74,7 +72,7 @@ def create_mcp_tools_from_openapi(
7472
base_url = base_url[:-1]
7573

7674
# Process each path in the OpenAPI schema
77-
for path, path_item in openapi_schema.get("paths", {}).items():
75+
for path, path_item in resolved_openapi_schema.get("paths", {}).items():
7876
for method, operation in path_item.items():
7977
# Skip non-HTTP methods
8078
if method not in ["get", "post", "put", "delete", "patch"]:
@@ -97,24 +95,28 @@ def create_mcp_tools_from_openapi(
9795
parameters=operation.get("parameters", []),
9896
request_body=operation.get("requestBody", {}),
9997
responses=operation.get("responses", {}),
100-
openapi_schema=openapi_schema,
98+
openapi_schema=resolved_openapi_schema,
10199
describe_all_responses=describe_all_responses,
102100
describe_full_response_schema=describe_full_response_schema,
103101
)
104102

105-
def _create_http_tool_function(function_template: Callable, args_schema: Dict[str, Any], additional_variables: Dict[str, Any]) -> Callable:
103+
def _create_http_tool_function(function_template: Callable, properties: Dict[str, Any], additional_variables: Dict[str, Any]) -> Callable:
106104
# Build parameter string with type hints
107105
param_list = []
108-
for name, type_info in args_schema.items():
109-
type_hint = OPENAPI_PYTHON_TYPES_MAP.get(type_info, 'Any')
110-
param_list.append(f"{name}: {type_hint}")
111-
parameters_str = ", ".join(param_list)
112-
106+
param_list_with_defaults = []
107+
for name, schema in properties.items():
108+
type_hint, has_default_value = parse_param_schema_for_python_type_and_default(schema)
109+
if has_default_value:
110+
param_list_with_defaults.append(f"{name}: {type_hint}")
111+
else:
112+
param_list.append(f"{name}: {type_hint}")
113+
parameters_str = ", ".join(param_list + param_list_with_defaults)
114+
113115
dynamic_function_body = f"""async def dynamic_http_tool_function({parameters_str}):
114-
kwargs = {{{', '.join([f"'{k}': {k}" for k in args_schema.keys()])}}}
116+
kwargs = {{{', '.join([f"'{k}': {k}" for k in properties.keys()])}}}
115117
return await http_tool_function_template(**kwargs)
116118
"""
117-
119+
118120
# Create function namespace with required imports
119121
namespace = {
120122
"http_tool_function_template": function_template,
@@ -207,13 +209,6 @@ def create_http_tool(
207209
model_name = None
208210
model_examples = None
209211
items_model_name = None
210-
if "$ref" in schema: # This check is now just for information
211-
ref_path = schema["$ref"]
212-
if ref_path.startswith("#/components/schemas/"):
213-
model_name = ref_path.split("/")[-1]
214-
response_info += f"\nModel: {model_name}"
215-
# Try to get examples from the model
216-
model_examples = extract_model_examples_from_components(model_name, openapi_schema)
217212

218213
# Check if this is an array of items
219214
if schema.get("type") == "array" and "items" in schema and "$ref" in schema["items"]:
@@ -390,7 +385,7 @@ def create_http_tool(
390385

391386
if required_props:
392387
input_schema["required"] = required_props
393-
388+
394389
# Dynamically create a function to call the API endpoint
395390
async def http_tool_function_template(**kwargs):
396391
# Prepare URL with path parameters
@@ -437,9 +432,8 @@ async def http_tool_function_template(**kwargs):
437432
return response.text
438433

439434
# Create the http_tool_function (with name and docstring)
440-
args_schema = parse_parameters_for_args_schema(parameters)
441435
additional_variables = {"path_params": path_params, "query_params": query_params, "header_params": header_params}
442-
http_tool_function = _create_http_tool_function(http_tool_function_template, args_schema, additional_variables)
436+
http_tool_function = _create_http_tool_function(http_tool_function_template, properties, additional_variables)
443437
http_tool_function.__name__ = operation_id
444438
http_tool_function.__doc__ = tool_description
445439

fastapi_mcp/openapi_utils.py

+22-30
Original file line numberDiff line numberDiff line change
@@ -108,44 +108,36 @@
108108
}
109109

110110

111-
def parse_parameters_for_args_schema(parameters: List[Dict[str, Any]]) -> Dict[str, Any]:
111+
def parse_param_schema_for_python_type_and_default(param_schema: Dict[str, Any]) -> tuple[str, bool]:
112112
"""
113-
Parse OpenAPI parameters into an arguments schema.
113+
Parse OpenAPI parameters into a python type and default value string.
114114
115115
Args:
116116
parameters: List of OpenAPI parameter objects
117117
118118
Returns:
119-
Dictionary mapping parameter names to their types
119+
A tuple containing:
120+
- A string representing the Python type annotation (e.g. "str", "Optional[int]", etc.)
121+
- A boolean indicating whether a default value is present
120122
"""
121-
args_schema = {}
122-
for param in parameters:
123-
param_name = param.get("name")
124-
param_schema = param.get("schema", {})
125-
126-
# Handle anyOf case
127-
if "anyOf" in param_schema:
128-
types = set()
129-
for schema in param_schema["anyOf"]:
130-
type_val = schema.get("type")
131-
if type_val:
132-
types.add(type_val)
133-
# If null is one of the types, remove it and make the field optional
134-
if "null" in types:
135-
types.remove("null")
136-
# Check if there are any remaining types after removing null
137-
if types:
138-
args_schema[param_name] = f"Optional[{next(iter(types))}]"
139-
else:
140-
args_schema[param_name] = "Optional[str]"
141-
continue
142-
# If we get here, there was no null type, so use first available type
143-
args_schema[param_name] = next(iter(types)) if types else "str"
144-
else:
145-
# Handle direct type specification
146-
args_schema[param_name] = param_schema.get("type", "string")
123+
# Handle anyOf case
124+
if "anyOf" in param_schema:
125+
# Get a set of possible types for this parameter
126+
types = {schema.get("type") for schema in param_schema["anyOf"] if schema.get("type")}
127+
# If null is one of the types, make None the default value
128+
if "null" in types:
129+
types.remove("null")
130+
if types:
131+
return f"Optional[{OPENAPI_PYTHON_TYPES_MAP.get(next(iter(types)), 'Any')}] = None", True
132+
return f"Optional[str] = None", True
133+
return f"Union[{', '.join([OPENAPI_PYTHON_TYPES_MAP.get(t, 'Any') for t in types])}]", False
147134

148-
return args_schema
135+
# Handle direct type specification
136+
python_type = OPENAPI_PYTHON_TYPES_MAP.get(param_schema.get("type"), 'Any')
137+
default_value = param_schema.get("default")
138+
if default_value is not None:
139+
return f"{python_type} = {default_value}", True
140+
return python_type, False
149141

150142
def resolve_schema_references(schema_part: Dict[str, Any], reference_schema: Dict[str, Any]) -> Dict[str, Any]:
151143
"""

0 commit comments

Comments
 (0)