fastmcp 2.10.6__py3-none-any.whl → 2.11.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- fastmcp/cli/cli.py +128 -33
- fastmcp/cli/install/claude_code.py +42 -1
- fastmcp/cli/install/claude_desktop.py +42 -1
- fastmcp/cli/install/cursor.py +42 -1
- fastmcp/cli/install/mcp_json.py +41 -0
- fastmcp/cli/run.py +127 -1
- fastmcp/client/__init__.py +2 -0
- fastmcp/client/auth/oauth.py +68 -99
- fastmcp/client/oauth_callback.py +18 -0
- fastmcp/client/transports.py +69 -15
- fastmcp/contrib/component_manager/example.py +2 -2
- fastmcp/experimental/server/openapi/README.md +266 -0
- fastmcp/experimental/server/openapi/__init__.py +38 -0
- fastmcp/experimental/server/openapi/components.py +348 -0
- fastmcp/experimental/server/openapi/routing.py +132 -0
- fastmcp/experimental/server/openapi/server.py +466 -0
- fastmcp/experimental/utilities/openapi/README.md +239 -0
- fastmcp/experimental/utilities/openapi/__init__.py +68 -0
- fastmcp/experimental/utilities/openapi/director.py +208 -0
- fastmcp/experimental/utilities/openapi/formatters.py +355 -0
- fastmcp/experimental/utilities/openapi/json_schema_converter.py +340 -0
- fastmcp/experimental/utilities/openapi/models.py +85 -0
- fastmcp/experimental/utilities/openapi/parser.py +618 -0
- fastmcp/experimental/utilities/openapi/schemas.py +538 -0
- fastmcp/mcp_config.py +125 -88
- fastmcp/prompts/prompt.py +11 -1
- fastmcp/resources/resource.py +21 -1
- fastmcp/resources/template.py +20 -1
- fastmcp/server/auth/__init__.py +18 -2
- fastmcp/server/auth/auth.py +225 -7
- fastmcp/server/auth/providers/bearer.py +25 -473
- fastmcp/server/auth/providers/in_memory.py +4 -2
- fastmcp/server/auth/providers/jwt.py +538 -0
- fastmcp/server/auth/providers/workos.py +151 -0
- fastmcp/server/auth/registry.py +52 -0
- fastmcp/server/context.py +107 -26
- fastmcp/server/dependencies.py +9 -2
- fastmcp/server/http.py +48 -57
- fastmcp/server/middleware/middleware.py +3 -23
- fastmcp/server/openapi.py +1 -1
- fastmcp/server/proxy.py +50 -11
- fastmcp/server/server.py +168 -59
- fastmcp/settings.py +73 -6
- fastmcp/tools/tool.py +36 -3
- fastmcp/tools/tool_manager.py +38 -2
- fastmcp/tools/tool_transform.py +112 -3
- fastmcp/utilities/components.py +41 -3
- fastmcp/utilities/json_schema.py +136 -98
- fastmcp/utilities/json_schema_type.py +1 -3
- fastmcp/utilities/mcp_config.py +28 -0
- fastmcp/utilities/openapi.py +243 -57
- fastmcp/utilities/tests.py +54 -6
- fastmcp/utilities/types.py +94 -11
- {fastmcp-2.10.6.dist-info → fastmcp-2.11.1.dist-info}/METADATA +4 -3
- fastmcp-2.11.1.dist-info/RECORD +108 -0
- fastmcp/server/auth/providers/bearer_env.py +0 -63
- fastmcp/utilities/cache.py +0 -26
- fastmcp-2.10.6.dist-info/RECORD +0 -93
- {fastmcp-2.10.6.dist-info → fastmcp-2.11.1.dist-info}/WHEEL +0 -0
- {fastmcp-2.10.6.dist-info → fastmcp-2.11.1.dist-info}/entry_points.txt +0 -0
- {fastmcp-2.10.6.dist-info → fastmcp-2.11.1.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
# OpenAPI Utilities (New Implementation)
|
|
2
|
+
|
|
3
|
+
This directory contains the next-generation OpenAPI integration utilities for FastMCP, designed to replace the legacy `openapi.py` implementation.
|
|
4
|
+
|
|
5
|
+
## Architecture Overview
|
|
6
|
+
|
|
7
|
+
The new implementation follows a **stateless request building strategy** using `openapi-core` for high-performance, per-request HTTP request construction, eliminating startup latency while maintaining robust OpenAPI compliance.
|
|
8
|
+
|
|
9
|
+
### Core Components
|
|
10
|
+
|
|
11
|
+
1. **`director.py`** - `RequestDirector` for stateless HTTP request building
|
|
12
|
+
2. **`parser.py`** - OpenAPI spec parsing and route extraction with pre-calculated schemas
|
|
13
|
+
3. **`schemas.py`** - Schema processing with parameter mapping for collision handling
|
|
14
|
+
4. **`models.py`** - Enhanced data models with pre-calculated fields for performance
|
|
15
|
+
5. **`formatters.py`** - Response formatting and processing utilities
|
|
16
|
+
|
|
17
|
+
### Key Architecture Principles
|
|
18
|
+
|
|
19
|
+
#### 1. Stateless Request Building
|
|
20
|
+
- Uses `openapi-core` library for robust OpenAPI parameter serialization
|
|
21
|
+
- Builds HTTP requests on-demand with zero startup latency
|
|
22
|
+
- Offloads OpenAPI compliance to a well-tested library without code generation overhead
|
|
23
|
+
|
|
24
|
+
#### 2. Pre-calculated Optimization
|
|
25
|
+
- **Schema Pre-calculation**: Combined schemas calculated once during parsing
|
|
26
|
+
- **Parameter Mapping**: Collision resolution mapping calculated upfront
|
|
27
|
+
- **Zero Runtime Overhead**: All complex processing done during initialization
|
|
28
|
+
|
|
29
|
+
#### 3. Performance-First Design
|
|
30
|
+
- **No Code Generation**: Eliminates 100-200ms startup latency
|
|
31
|
+
- **Serverless Friendly**: Ideal for cold-start environments
|
|
32
|
+
- **Minimal Dependencies**: Uses lightweight `openapi-core` instead of full client generation
|
|
33
|
+
|
|
34
|
+
## Data Flow
|
|
35
|
+
|
|
36
|
+
### Initialization Process
|
|
37
|
+
|
|
38
|
+
```
|
|
39
|
+
OpenAPI Spec → Parser → HTTPRoute with Pre-calculated Fields → RequestDirector + SchemaPath
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
1. **Input**: Raw OpenAPI specification (dict)
|
|
43
|
+
2. **Parsing**: Extract operations to `HTTPRoute` models
|
|
44
|
+
3. **Pre-calculation**: Generate combined schemas and parameter maps during parsing
|
|
45
|
+
4. **Director Setup**: Create `RequestDirector` with `SchemaPath` for request building
|
|
46
|
+
|
|
47
|
+
### Request Processing
|
|
48
|
+
|
|
49
|
+
```
|
|
50
|
+
MCP Tool Call → RequestDirector.build() → httpx.Request → HTTP Response → Structured Output
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
1. **Tool Invocation**: FastMCP receives tool call with parameters
|
|
54
|
+
2. **Request Building**: RequestDirector builds HTTP request using parameter map
|
|
55
|
+
3. **Parameter Handling**: openapi-core handles all OpenAPI serialization rules
|
|
56
|
+
4. **Response Processing**: Parse response into structured format with proper error handling
|
|
57
|
+
|
|
58
|
+
## Key Features
|
|
59
|
+
|
|
60
|
+
### 1. High-Performance Request Building
|
|
61
|
+
- Zero startup latency - no code generation required
|
|
62
|
+
- Stateless request building scales infinitely
|
|
63
|
+
- Uses proven `openapi-core` library for OpenAPI compliance
|
|
64
|
+
- Perfect for serverless and cold-start environments
|
|
65
|
+
|
|
66
|
+
### 2. Comprehensive Parameter Support
|
|
67
|
+
- **Parameter Collisions**: Intelligent collision resolution with suffixing
|
|
68
|
+
- **DeepObject Style**: Full support for deepObject parameters with explode=true/false
|
|
69
|
+
- **Complex Schemas**: Handles nested objects, arrays, and all OpenAPI types
|
|
70
|
+
- **Pre-calculated Mapping**: Parameter location mapping done upfront for performance
|
|
71
|
+
|
|
72
|
+
### 3. Enhanced Error Handling
|
|
73
|
+
- HTTP status code mapping to MCP errors
|
|
74
|
+
- Structured error responses with detailed information
|
|
75
|
+
- Graceful handling of network timeouts and connection errors
|
|
76
|
+
- Proper error context preservation
|
|
77
|
+
|
|
78
|
+
### 4. Advanced Schema Processing
|
|
79
|
+
- **Pre-calculated Schemas**: Combined parameter and body schemas calculated once
|
|
80
|
+
- **Collision-aware**: Automatically handles parameter name collisions
|
|
81
|
+
- **Type Safety**: Full Pydantic model validation
|
|
82
|
+
- **Performance**: Zero runtime schema processing overhead
|
|
83
|
+
|
|
84
|
+
## Component Integration
|
|
85
|
+
|
|
86
|
+
### Server Components (`/server/openapi_new/`)
|
|
87
|
+
|
|
88
|
+
1. **`OpenAPITool`** - Simplified tool implementation using RequestDirector
|
|
89
|
+
2. **`OpenAPIResource`** - Resource implementation with RequestDirector
|
|
90
|
+
3. **`OpenAPIResourceTemplate`** - Resource template with RequestDirector support
|
|
91
|
+
4. **`FastMCPOpenAPI`** - Main server class with stateless request building
|
|
92
|
+
|
|
93
|
+
### RequestDirector Integration
|
|
94
|
+
|
|
95
|
+
All components use the same RequestDirector approach:
|
|
96
|
+
- Consistent parameter handling across all component types
|
|
97
|
+
- Uniform error handling and response processing
|
|
98
|
+
- Simplified architecture without fallback complexity
|
|
99
|
+
- High performance for all operation types
|
|
100
|
+
|
|
101
|
+
## Usage Examples
|
|
102
|
+
|
|
103
|
+
### Basic Server Setup
|
|
104
|
+
|
|
105
|
+
```python
|
|
106
|
+
import httpx
|
|
107
|
+
from fastmcp.server.openapi_new import FastMCPOpenAPI
|
|
108
|
+
|
|
109
|
+
# OpenAPI spec (can be loaded from file/URL)
|
|
110
|
+
openapi_spec = {...}
|
|
111
|
+
|
|
112
|
+
# Create HTTP client
|
|
113
|
+
async with httpx.AsyncClient() as client:
|
|
114
|
+
# Create server with stateless request building
|
|
115
|
+
server = FastMCPOpenAPI(
|
|
116
|
+
openapi_spec=openapi_spec,
|
|
117
|
+
client=client,
|
|
118
|
+
name="My API Server"
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
# Server automatically creates RequestDirector and pre-calculates schemas
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### Direct RequestDirector Usage
|
|
125
|
+
|
|
126
|
+
```python
|
|
127
|
+
from fastmcp.experimental.utilities.openapi.director import RequestDirector
|
|
128
|
+
from jsonschema_path import SchemaPath
|
|
129
|
+
|
|
130
|
+
# Create RequestDirector manually
|
|
131
|
+
spec = SchemaPath.from_dict(openapi_spec)
|
|
132
|
+
director = RequestDirector(spec)
|
|
133
|
+
|
|
134
|
+
# Build HTTP request
|
|
135
|
+
request = director.build(route, flat_arguments, base_url)
|
|
136
|
+
|
|
137
|
+
# Execute with httpx
|
|
138
|
+
async with httpx.AsyncClient() as client:
|
|
139
|
+
response = await client.send(request)
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
## Testing Strategy
|
|
143
|
+
|
|
144
|
+
Tests are located in `/tests/server/openapi_new/`:
|
|
145
|
+
|
|
146
|
+
### Test Categories
|
|
147
|
+
|
|
148
|
+
1. **Core Functionality**
|
|
149
|
+
- `test_server.py` - Server initialization and RequestDirector integration
|
|
150
|
+
|
|
151
|
+
2. **OpenAPI Features**
|
|
152
|
+
- `test_parameter_collisions.py` - Parameter name collision handling
|
|
153
|
+
- `test_deepobject_style.py` - DeepObject parameter style support
|
|
154
|
+
- `test_openapi_features.py` - General OpenAPI feature compliance
|
|
155
|
+
|
|
156
|
+
### Testing Philosophy
|
|
157
|
+
|
|
158
|
+
- **Real Objects**: Use real HTTPRoute models and OpenAPI specifications
|
|
159
|
+
- **Minimal Mocking**: Only mock external HTTP endpoints
|
|
160
|
+
- **Performance Focus**: Test that initialization is fast and stateless
|
|
161
|
+
- **Behavioral Testing**: Verify OpenAPI compliance without implementation details
|
|
162
|
+
|
|
163
|
+
## Migration Guide
|
|
164
|
+
|
|
165
|
+
### From Legacy Implementation
|
|
166
|
+
|
|
167
|
+
1. **Import Changes**:
|
|
168
|
+
```python
|
|
169
|
+
# Old
|
|
170
|
+
from fastmcp.server.openapi import FastMCPOpenAPI
|
|
171
|
+
|
|
172
|
+
# New
|
|
173
|
+
from fastmcp.server.openapi_new import FastMCPOpenAPI
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
2. **Constructor**: Same interface, no changes needed
|
|
177
|
+
|
|
178
|
+
3. **Automatic Benefits**:
|
|
179
|
+
- Eliminates startup latency (100-200ms improvement)
|
|
180
|
+
- Better OpenAPI compliance via openapi-core
|
|
181
|
+
- Serverless-friendly performance characteristics
|
|
182
|
+
- Simplified architecture without fallback complexity
|
|
183
|
+
|
|
184
|
+
### Performance Improvements
|
|
185
|
+
|
|
186
|
+
- **Cold Start**: Zero latency penalty for serverless deployments
|
|
187
|
+
- **Memory Usage**: Lower memory footprint without generated client code
|
|
188
|
+
- **Reliability**: No dynamic code generation failures
|
|
189
|
+
- **Maintainability**: Simpler architecture with fewer moving parts
|
|
190
|
+
|
|
191
|
+
## Future Enhancements
|
|
192
|
+
|
|
193
|
+
### Planned Features
|
|
194
|
+
|
|
195
|
+
1. **Response Streaming**: Handle streaming API responses
|
|
196
|
+
2. **Enhanced Authentication**: More auth provider integrations
|
|
197
|
+
3. **Advanced Metrics**: Detailed request/response monitoring
|
|
198
|
+
4. **Schema Validation**: Enhanced input/output validation
|
|
199
|
+
5. **Batch Operations**: Optimized multi-operation requests
|
|
200
|
+
|
|
201
|
+
### Performance Improvements
|
|
202
|
+
|
|
203
|
+
1. **Schema Caching**: More aggressive schema pre-calculation
|
|
204
|
+
2. **Memory Optimization**: Further reduce memory footprint
|
|
205
|
+
3. **Request Batching**: Smart batching for bulk operations
|
|
206
|
+
4. **Connection Optimization**: Enhanced connection pooling strategies
|
|
207
|
+
|
|
208
|
+
## Troubleshooting
|
|
209
|
+
|
|
210
|
+
### Common Issues
|
|
211
|
+
|
|
212
|
+
1. **RequestDirector Initialization Fails**
|
|
213
|
+
- Check OpenAPI spec validity with `jsonschema-path`
|
|
214
|
+
- Verify spec format is correct JSON/YAML
|
|
215
|
+
- Ensure all required OpenAPI fields are present
|
|
216
|
+
|
|
217
|
+
2. **Parameter Mapping Issues**
|
|
218
|
+
- Check parameter collision resolution in debug logs
|
|
219
|
+
- Verify parameter names match OpenAPI spec exactly
|
|
220
|
+
- Review pre-calculated parameter map in HTTPRoute
|
|
221
|
+
|
|
222
|
+
3. **Request Building Errors**
|
|
223
|
+
- Check network connectivity to target API
|
|
224
|
+
- Verify base URL configuration
|
|
225
|
+
- Review parameter validation and type mismatches
|
|
226
|
+
|
|
227
|
+
### Debugging
|
|
228
|
+
|
|
229
|
+
- Enable debug logging: `logger.setLevel(logging.DEBUG)`
|
|
230
|
+
- Check RequestDirector initialization logs
|
|
231
|
+
- Review parameter mapping in HTTPRoute models
|
|
232
|
+
- Monitor request building and API response patterns
|
|
233
|
+
|
|
234
|
+
## Dependencies
|
|
235
|
+
|
|
236
|
+
- `openapi-core` - OpenAPI specification processing and validation
|
|
237
|
+
- `httpx` - HTTP client library
|
|
238
|
+
- `pydantic` - Data validation and serialization
|
|
239
|
+
- `urllib.parse` - URL building and manipulation
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"""OpenAPI utilities for FastMCP - refactored for better maintainability."""
|
|
2
|
+
|
|
3
|
+
# Import from models
|
|
4
|
+
from .models import (
|
|
5
|
+
HTTPRoute,
|
|
6
|
+
HttpMethod,
|
|
7
|
+
JsonSchema,
|
|
8
|
+
ParameterInfo,
|
|
9
|
+
ParameterLocation,
|
|
10
|
+
RequestBodyInfo,
|
|
11
|
+
ResponseInfo,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
# Import from parser
|
|
15
|
+
from .parser import parse_openapi_to_http_routes
|
|
16
|
+
|
|
17
|
+
# Import from formatters
|
|
18
|
+
from .formatters import (
|
|
19
|
+
format_array_parameter,
|
|
20
|
+
format_deep_object_parameter,
|
|
21
|
+
format_description_with_responses,
|
|
22
|
+
format_json_for_description,
|
|
23
|
+
generate_example_from_schema,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
# Import from schemas
|
|
27
|
+
from .schemas import (
|
|
28
|
+
_combine_schemas,
|
|
29
|
+
extract_output_schema_from_responses,
|
|
30
|
+
clean_schema_for_display,
|
|
31
|
+
_replace_ref_with_defs,
|
|
32
|
+
_make_optional_parameter_nullable,
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
# Import from json_schema_converter
|
|
36
|
+
from .json_schema_converter import (
|
|
37
|
+
convert_openapi_schema_to_json_schema,
|
|
38
|
+
convert_schema_definitions,
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
# Export public symbols - maintaining backward compatibility
|
|
42
|
+
__all__ = [
|
|
43
|
+
# Models
|
|
44
|
+
"HTTPRoute",
|
|
45
|
+
"ParameterInfo",
|
|
46
|
+
"RequestBodyInfo",
|
|
47
|
+
"ResponseInfo",
|
|
48
|
+
"HttpMethod",
|
|
49
|
+
"ParameterLocation",
|
|
50
|
+
"JsonSchema",
|
|
51
|
+
# Parser
|
|
52
|
+
"parse_openapi_to_http_routes",
|
|
53
|
+
# Formatters
|
|
54
|
+
"format_array_parameter",
|
|
55
|
+
"format_deep_object_parameter",
|
|
56
|
+
"format_description_with_responses",
|
|
57
|
+
"format_json_for_description",
|
|
58
|
+
"generate_example_from_schema",
|
|
59
|
+
# Schemas
|
|
60
|
+
"_combine_schemas",
|
|
61
|
+
"extract_output_schema_from_responses",
|
|
62
|
+
"clean_schema_for_display",
|
|
63
|
+
"_replace_ref_with_defs",
|
|
64
|
+
"_make_optional_parameter_nullable",
|
|
65
|
+
# JSON Schema Converter
|
|
66
|
+
"convert_openapi_schema_to_json_schema",
|
|
67
|
+
"convert_schema_definitions",
|
|
68
|
+
]
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
"""Request director using openapi-core for stateless HTTP request building."""
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
from urllib.parse import urljoin
|
|
5
|
+
|
|
6
|
+
import httpx
|
|
7
|
+
from jsonschema_path import SchemaPath
|
|
8
|
+
|
|
9
|
+
from fastmcp.utilities.logging import get_logger
|
|
10
|
+
|
|
11
|
+
from .models import HTTPRoute
|
|
12
|
+
|
|
13
|
+
logger = get_logger(__name__)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class RequestDirector:
|
|
17
|
+
"""Builds httpx.Request objects from HTTPRoute and arguments using openapi-core."""
|
|
18
|
+
|
|
19
|
+
def __init__(self, spec: SchemaPath):
|
|
20
|
+
"""Initialize with a parsed SchemaPath object."""
|
|
21
|
+
self._spec = spec
|
|
22
|
+
|
|
23
|
+
def build(
|
|
24
|
+
self,
|
|
25
|
+
route: HTTPRoute,
|
|
26
|
+
flat_args: dict[str, Any],
|
|
27
|
+
base_url: str = "http://localhost",
|
|
28
|
+
) -> httpx.Request:
|
|
29
|
+
"""
|
|
30
|
+
Constructs a final httpx.Request object, handling all OpenAPI serialization.
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
route: HTTPRoute containing OpenAPI operation details
|
|
34
|
+
flat_args: Flattened arguments from LLM (may include suffixed parameters)
|
|
35
|
+
base_url: Base URL for the request
|
|
36
|
+
|
|
37
|
+
Returns:
|
|
38
|
+
httpx.Request: Properly formatted HTTP request
|
|
39
|
+
"""
|
|
40
|
+
logger.debug(
|
|
41
|
+
f"Building request for {route.method} {route.path} with args: {flat_args}"
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
# Step 1: Un-flatten arguments into path, query, body, etc. using parameter map
|
|
45
|
+
path_params, query_params, header_params, body = self._unflatten_arguments(
|
|
46
|
+
route, flat_args
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
logger.debug(
|
|
50
|
+
f"Unflattened - path: {path_params}, query: {query_params}, headers: {header_params}, body: {body}"
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
# Step 2: Build base URL with path parameters
|
|
54
|
+
url = self._build_url(route.path, path_params, base_url)
|
|
55
|
+
|
|
56
|
+
# Step 3: Prepare request data
|
|
57
|
+
request_data = {
|
|
58
|
+
"method": route.method.upper(),
|
|
59
|
+
"url": url,
|
|
60
|
+
"params": query_params if query_params else None,
|
|
61
|
+
"headers": header_params if header_params else None,
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
# Step 4: Handle request body
|
|
65
|
+
if body is not None:
|
|
66
|
+
if isinstance(body, dict) or isinstance(body, list):
|
|
67
|
+
request_data["json"] = body
|
|
68
|
+
else:
|
|
69
|
+
request_data["content"] = body
|
|
70
|
+
|
|
71
|
+
# Step 5: Create httpx.Request
|
|
72
|
+
return httpx.Request(**{k: v for k, v in request_data.items() if v is not None})
|
|
73
|
+
|
|
74
|
+
def _unflatten_arguments(
|
|
75
|
+
self, route: HTTPRoute, flat_args: dict[str, Any]
|
|
76
|
+
) -> tuple[dict[str, Any], dict[str, Any], dict[str, Any], Any]:
|
|
77
|
+
"""
|
|
78
|
+
Maps flat arguments back to their OpenAPI locations using the parameter map.
|
|
79
|
+
|
|
80
|
+
Args:
|
|
81
|
+
route: HTTPRoute with parameter_map containing location mappings
|
|
82
|
+
flat_args: Flat arguments from LLM call
|
|
83
|
+
|
|
84
|
+
Returns:
|
|
85
|
+
Tuple of (path_params, query_params, header_params, body)
|
|
86
|
+
"""
|
|
87
|
+
path_params = {}
|
|
88
|
+
query_params = {}
|
|
89
|
+
header_params = {}
|
|
90
|
+
body_props = {}
|
|
91
|
+
|
|
92
|
+
# Use parameter map to route arguments to correct locations
|
|
93
|
+
if hasattr(route, "parameter_map") and route.parameter_map:
|
|
94
|
+
for arg_name, value in flat_args.items():
|
|
95
|
+
if value is None:
|
|
96
|
+
continue # Skip None values for optional parameters
|
|
97
|
+
|
|
98
|
+
if arg_name not in route.parameter_map:
|
|
99
|
+
logger.warning(
|
|
100
|
+
f"Argument '{arg_name}' not found in parameter map for {route.operation_id}"
|
|
101
|
+
)
|
|
102
|
+
continue
|
|
103
|
+
|
|
104
|
+
mapping = route.parameter_map[arg_name]
|
|
105
|
+
location = mapping["location"]
|
|
106
|
+
openapi_name = mapping["openapi_name"]
|
|
107
|
+
|
|
108
|
+
if location == "path":
|
|
109
|
+
path_params[openapi_name] = value
|
|
110
|
+
elif location == "query":
|
|
111
|
+
query_params[openapi_name] = value
|
|
112
|
+
elif location == "header":
|
|
113
|
+
header_params[openapi_name] = value
|
|
114
|
+
elif location == "body":
|
|
115
|
+
body_props[openapi_name] = value
|
|
116
|
+
else:
|
|
117
|
+
logger.warning(
|
|
118
|
+
f"Unknown parameter location '{location}' for {arg_name}"
|
|
119
|
+
)
|
|
120
|
+
else:
|
|
121
|
+
# Fallback: try to map arguments based on parameter definitions
|
|
122
|
+
logger.debug("No parameter map available, using fallback mapping")
|
|
123
|
+
|
|
124
|
+
# Create a mapping from parameter names to their locations
|
|
125
|
+
param_locations = {}
|
|
126
|
+
for param in route.parameters:
|
|
127
|
+
param_locations[param.name] = param.location
|
|
128
|
+
|
|
129
|
+
# Map arguments to locations
|
|
130
|
+
for arg_name, value in flat_args.items():
|
|
131
|
+
if value is None:
|
|
132
|
+
continue
|
|
133
|
+
|
|
134
|
+
# Check if it's a suffixed parameter (e.g., id__path)
|
|
135
|
+
if "__" in arg_name:
|
|
136
|
+
base_name, location = arg_name.rsplit("__", 1)
|
|
137
|
+
if location in ["path", "query", "header"]:
|
|
138
|
+
if location == "path":
|
|
139
|
+
path_params[base_name] = value
|
|
140
|
+
elif location == "query":
|
|
141
|
+
query_params[base_name] = value
|
|
142
|
+
elif location == "header":
|
|
143
|
+
header_params[base_name] = value
|
|
144
|
+
continue
|
|
145
|
+
|
|
146
|
+
# Check if it's a known parameter
|
|
147
|
+
if arg_name in param_locations:
|
|
148
|
+
location = param_locations[arg_name]
|
|
149
|
+
if location == "path":
|
|
150
|
+
path_params[arg_name] = value
|
|
151
|
+
elif location == "query":
|
|
152
|
+
query_params[arg_name] = value
|
|
153
|
+
elif location == "header":
|
|
154
|
+
header_params[arg_name] = value
|
|
155
|
+
else:
|
|
156
|
+
# Assume it's a body property
|
|
157
|
+
body_props[arg_name] = value
|
|
158
|
+
|
|
159
|
+
# Handle body construction
|
|
160
|
+
body = None
|
|
161
|
+
if body_props:
|
|
162
|
+
# If we have body properties, construct the body object
|
|
163
|
+
if route.request_body and route.request_body.content_schema:
|
|
164
|
+
# Check if the request body expects an object with properties
|
|
165
|
+
content_type = next(iter(route.request_body.content_schema))
|
|
166
|
+
body_schema = route.request_body.content_schema[content_type]
|
|
167
|
+
|
|
168
|
+
if body_schema.get("type") == "object":
|
|
169
|
+
body = body_props
|
|
170
|
+
elif len(body_props) == 1:
|
|
171
|
+
# If body schema is not an object and we have exactly one property,
|
|
172
|
+
# use the property value directly
|
|
173
|
+
body = next(iter(body_props.values()))
|
|
174
|
+
else:
|
|
175
|
+
# Multiple properties but schema is not object - wrap in object
|
|
176
|
+
body = body_props
|
|
177
|
+
else:
|
|
178
|
+
body = body_props
|
|
179
|
+
|
|
180
|
+
return path_params, query_params, header_params, body
|
|
181
|
+
|
|
182
|
+
def _build_url(
|
|
183
|
+
self, path_template: str, path_params: dict[str, Any], base_url: str
|
|
184
|
+
) -> str:
|
|
185
|
+
"""
|
|
186
|
+
Build URL by substituting path parameters in the template.
|
|
187
|
+
|
|
188
|
+
Args:
|
|
189
|
+
path_template: OpenAPI path template (e.g., "/users/{id}")
|
|
190
|
+
path_params: Path parameter values
|
|
191
|
+
base_url: Base URL to prepend
|
|
192
|
+
|
|
193
|
+
Returns:
|
|
194
|
+
Complete URL with path parameters substituted
|
|
195
|
+
"""
|
|
196
|
+
# Substitute path parameters
|
|
197
|
+
url_path = path_template
|
|
198
|
+
for param_name, param_value in path_params.items():
|
|
199
|
+
placeholder = f"{{{param_name}}}"
|
|
200
|
+
if placeholder in url_path:
|
|
201
|
+
url_path = url_path.replace(placeholder, str(param_value))
|
|
202
|
+
|
|
203
|
+
# Combine with base URL
|
|
204
|
+
return urljoin(base_url.rstrip("/") + "/", url_path.lstrip("/"))
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
# Export public symbols
|
|
208
|
+
__all__ = ["RequestDirector"]
|