fastmcp/README_OPENAPI.md
Jeremiah Lowin b0f45a85c4
Fix OpenAPI deepObject style parameter encoding (#1122)
* Fix OpenAPI deepObject style parameter encoding

Add support for deepObject style with explode=true to properly serialize
object parameters using bracket notation (param[key]=value) instead of
JSON strings. Fixes #1114.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

* Create README_OPENAPI.md

---------

Co-authored-by: Claude <noreply@anthropic.com>
2025-07-11 12:50:19 -04:00

246 lines
No EOL
8.5 KiB
Markdown

# FastMCP OpenAPI Integration
This document explains how FastMCP's OpenAPI integration works, what features are supported, and how to extend it. The OpenAPI functionality is split across two main files:
- `server/openapi.py` - High-level FastMCP server implementation and MCP component creation
- `utilities/openapi.py` - Low-level OpenAPI parsing and intermediate representation
## Architecture Overview
```
OpenAPI Spec → Parse → HTTPRoute IR → Create MCP Components → FastMCP Server
```
### 1. Parsing Phase (`utilities/openapi.py`)
OpenAPI specifications are parsed into an intermediate representation (IR) that normalizes differences between OpenAPI 3.0 and 3.1:
- **Input**: Raw OpenAPI spec (dict)
- **Output**: List of `HTTPRoute` objects with normalized parameter information
- **Key Classes**:
- `HTTPRoute` - Represents a single operation
- `ParameterInfo` - Represents a parameter with location, style, explode, etc.
- `RequestBodyInfo` - Represents request body information
- `ResponseInfo` - Represents response information
### 2. Component Creation Phase (`server/openapi.py`)
HTTPRoute objects are converted into FastMCP components based on route mapping rules:
- **Tools** (`OpenAPITool`) - HTTP operations that can be called
- **Resources** (`OpenAPIResource`) - HTTP endpoints that return data
- **Resource Templates** (`OpenAPIResourceTemplate`) - Parameterized resources
## Parameter Handling
FastMCP supports various OpenAPI parameter serialization styles and formats:
### Supported Parameter Locations
- `query` - Query string parameters
- `path` - Path parameters
- `header` - HTTP headers
- `cookie` - Cookie parameters (parsed but not used in requests)
### Supported Parameter Styles
#### Query Parameters
- **`form`** (default) - Standard query parameter format
- `explode=true` (default): `?tags=red&tags=blue`
- `explode=false`: `?tags=red,blue`
- **`deepObject`** - Object parameters with bracket notation
- `explode=true`: `?filter[name]=John&filter[age]=30`
- `explode=false`: Falls back to JSON string (non-standard, logs warning)
#### Path Parameters
- **`simple`** (default) - Comma-separated for arrays: `/users/1,2,3`
#### Header Parameters
- **`simple`** (default) - Standard header format
### Parameter Type Support
#### Arrays
- String arrays with `explode=true/false`
- Number arrays with `explode=true/false`
- Boolean arrays with `explode=true/false`
- Complex object arrays (basic support, may not handle all cases)
#### Objects
- Objects with `deepObject` style and `explode=true`
- Objects with other styles fall back to JSON serialization
#### Primitives
- Strings, numbers, booleans
- Enums
- Default values
## Request Body Handling
### Supported Content Types
- `application/json` - JSON request bodies
### Schema Support
- Object schemas with properties
- Array schemas
- Primitive schemas
- Schema references (`$ref` to local schemas only)
- Required properties
- Default values
## Response Handling
### Content Type Detection
- `application/json` - Parsed as JSON
- `text/*` - Returned as text
- `application/xml` - Returned as text
- Other types - Returned as binary
### Output Schema Generation
- Success response schemas (200, 201, 202, 204)
- Object response wrapping for MCP compliance
- Schema compression (removes unused `$defs`)
## Route Mapping
Routes are mapped to MCP component types using `RouteMap` configurations:
```python
RouteMap(
methods=["GET", "POST"], # HTTP methods to match
pattern=r"/api/users/.*", # Regex pattern for path
mcp_type=MCPType.RESOURCE_TEMPLATE, # Target component type
tags={"user"}, # OpenAPI tags to match (AND condition)
mcp_tags={"fastmcp-user"} # Tags to add to created components
)
```
### Default Behavior
- All routes become **Tools** by default
- Use route maps to override specific patterns
### Component Types
- `MCPType.TOOL` - Callable operations
- `MCPType.RESOURCE` - Static data endpoints
- `MCPType.RESOURCE_TEMPLATE` - Parameterized data endpoints
- `MCPType.EXCLUDE` - Skip route entirely
## Known Limitations & Edge Cases
### Parameter Edge Cases
1. **Parameter Name Collisions** - When path/query parameters have same names as request body properties, non-body parameters get `__location` suffixes
2. **Complex Array Serialization** - Limited support for arrays containing objects
3. **Cookie Parameters** - Parsed but not used in requests
4. **Non-standard Combinations** - e.g., `deepObject` with `explode=false`
### Request Body Edge Cases
1. **Content Type Priority** - Only first available content type is used
2. **Nested Objects** - Deep nesting may not serialize correctly
3. **Binary Content** - No support for file uploads or binary data
### Response Edge Cases
1. **Multiple Content Types** - Only JSON-compatible types are used for output schemas
2. **Error Responses** - Not used for MCP output schema generation
3. **Response Headers** - Not captured or exposed
### Schema Edge Cases
1. **External References** - `$ref` to external files not supported
2. **Circular References** - May cause issues in schema processing
3. **Polymorphism** - `oneOf`/`anyOf`/`allOf` limited support
## Debugging Tips
### Common Issues
1. **"Unknown tool/resource"** - Check route mapping configuration
2. **Parameter not found** - Check for name collisions or incorrect style/explode
3. **Invalid request format** - Check parameter serialization and content types
4. **Schema validation errors** - Check for external refs or complex schemas
### Debugging Tools
```python
# Parse routes to inspect intermediate representation
routes = parse_openapi_to_http_routes(openapi_spec)
for route in routes:
print(f"{route.method} {route.path}")
for param in route.parameters:
print(f" {param.name} ({param.location}): style={param.style}, explode={param.explode}")
# Check component creation
server = FastMCP.from_openapi(openapi_spec, client)
tools = await server.get_tools()
print(f"Created {len(tools)} tools: {list(tools.keys())}")
```
### Logging
- Set `FASTMCP_LOG_LEVEL=DEBUG` to see detailed parameter processing
- Look for warnings about non-standard parameter combinations
- Check for schema parsing errors in logs
## Extension Points
### Adding New Parameter Styles
1. Add style handling in `utilities/openapi.py` - `ParameterInfo` class
2. Implement serialization logic in `server/openapi.py` - `OpenAPITool.run()`
3. Add tests for parsing and serialization
### Adding New Content Types
1. Extend request body handling in `OpenAPITool.run()`
2. Add response parsing logic for new types
3. Update content type priority in utilities
### Custom Route Mapping
Use `route_map_fn` for complex routing logic:
```python
def custom_mapper(route: HTTPRoute, current_type: MCPType) -> MCPType:
if route.path.startswith("/admin"):
return MCPType.EXCLUDE
return current_type
server = FastMCP.from_openapi(spec, client, route_map_fn=custom_mapper)
```
## Testing Patterns
### Unit Tests
- Test parameter parsing with various styles/explode combinations
- Test route mapping with different patterns and tags
- Test schema generation and compression
### Integration Tests
- Mock HTTP client to verify actual request parameters
- Test end-to-end component creation and execution
- Test error handling and edge cases
### Example Test Pattern
```python
async def test_parameter_style():
# 1. Create OpenAPI spec with specific parameter configuration
spec = {"openapi": "3.1.0", ...}
# 2. Parse and create components
routes = parse_openapi_to_http_routes(spec)
tool = OpenAPITool(mock_client, routes[0], ...)
# 3. Execute and verify request parameters
await tool.run({"param": "value"})
actual_params = mock_client.request.call_args.kwargs["params"]
assert actual_params == expected_params
```
## Testing
OpenAPI functionality is tested across multiple files in `tests/server/openapi/`:
- `test_basic_functionality.py` - Core component creation and execution
- `test_explode_integration.py` - Parameter explode behavior
- `test_deepobject_style.py` - DeepObject style parameter encoding
- `test_parameter_collisions.py` - Parameter name collision handling
- `test_openapi_path_parameters.py` - Path parameter serialization
- `test_configuration.py` - Route mapping and MCP names
- `test_description_propagation.py` - Schema and description handling
When adding new OpenAPI features, create focused test files rather than adding to existing monolithic files.
---
*This document should be updated when new OpenAPI features are added or when edge cases are discovered and addressed.*