fastmcp 2.10.5__py3-none-any.whl → 2.11.0__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.
Files changed (65) hide show
  1. fastmcp/__init__.py +7 -2
  2. fastmcp/cli/cli.py +128 -33
  3. fastmcp/cli/install/__init__.py +2 -2
  4. fastmcp/cli/install/claude_code.py +42 -1
  5. fastmcp/cli/install/claude_desktop.py +42 -1
  6. fastmcp/cli/install/cursor.py +42 -1
  7. fastmcp/cli/install/{mcp_config.py → mcp_json.py} +51 -7
  8. fastmcp/cli/run.py +127 -1
  9. fastmcp/client/__init__.py +2 -0
  10. fastmcp/client/auth/oauth.py +68 -99
  11. fastmcp/client/oauth_callback.py +18 -0
  12. fastmcp/client/transports.py +69 -15
  13. fastmcp/contrib/component_manager/example.py +2 -2
  14. fastmcp/experimental/server/openapi/README.md +266 -0
  15. fastmcp/experimental/server/openapi/__init__.py +38 -0
  16. fastmcp/experimental/server/openapi/components.py +348 -0
  17. fastmcp/experimental/server/openapi/routing.py +132 -0
  18. fastmcp/experimental/server/openapi/server.py +466 -0
  19. fastmcp/experimental/utilities/openapi/README.md +239 -0
  20. fastmcp/experimental/utilities/openapi/__init__.py +68 -0
  21. fastmcp/experimental/utilities/openapi/director.py +208 -0
  22. fastmcp/experimental/utilities/openapi/formatters.py +355 -0
  23. fastmcp/experimental/utilities/openapi/json_schema_converter.py +340 -0
  24. fastmcp/experimental/utilities/openapi/models.py +85 -0
  25. fastmcp/experimental/utilities/openapi/parser.py +618 -0
  26. fastmcp/experimental/utilities/openapi/schemas.py +538 -0
  27. fastmcp/mcp_config.py +125 -88
  28. fastmcp/prompts/prompt.py +11 -1
  29. fastmcp/prompts/prompt_manager.py +1 -1
  30. fastmcp/resources/resource.py +21 -1
  31. fastmcp/resources/resource_manager.py +2 -2
  32. fastmcp/resources/template.py +20 -1
  33. fastmcp/server/auth/__init__.py +17 -2
  34. fastmcp/server/auth/auth.py +144 -7
  35. fastmcp/server/auth/providers/bearer.py +25 -473
  36. fastmcp/server/auth/providers/in_memory.py +4 -2
  37. fastmcp/server/auth/providers/jwt.py +538 -0
  38. fastmcp/server/auth/providers/workos.py +170 -0
  39. fastmcp/server/auth/registry.py +52 -0
  40. fastmcp/server/context.py +110 -26
  41. fastmcp/server/dependencies.py +9 -2
  42. fastmcp/server/http.py +62 -30
  43. fastmcp/server/middleware/middleware.py +3 -23
  44. fastmcp/server/openapi.py +26 -13
  45. fastmcp/server/proxy.py +89 -8
  46. fastmcp/server/server.py +170 -62
  47. fastmcp/settings.py +83 -18
  48. fastmcp/tools/tool.py +41 -6
  49. fastmcp/tools/tool_manager.py +39 -3
  50. fastmcp/tools/tool_transform.py +122 -6
  51. fastmcp/utilities/components.py +35 -2
  52. fastmcp/utilities/json_schema.py +136 -98
  53. fastmcp/utilities/json_schema_type.py +1 -3
  54. fastmcp/utilities/mcp_config.py +28 -0
  55. fastmcp/utilities/openapi.py +306 -30
  56. fastmcp/utilities/tests.py +54 -6
  57. fastmcp/utilities/types.py +89 -11
  58. {fastmcp-2.10.5.dist-info → fastmcp-2.11.0.dist-info}/METADATA +4 -3
  59. fastmcp-2.11.0.dist-info/RECORD +108 -0
  60. fastmcp/server/auth/providers/bearer_env.py +0 -63
  61. fastmcp/utilities/cache.py +0 -26
  62. fastmcp-2.10.5.dist-info/RECORD +0 -93
  63. {fastmcp-2.10.5.dist-info → fastmcp-2.11.0.dist-info}/WHEEL +0 -0
  64. {fastmcp-2.10.5.dist-info → fastmcp-2.11.0.dist-info}/entry_points.txt +0 -0
  65. {fastmcp-2.10.5.dist-info → fastmcp-2.11.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,266 @@
1
+ # OpenAPI Server Implementation (New)
2
+
3
+ This directory contains the next-generation FastMCP server implementation for OpenAPI integration, designed to replace the legacy implementation in `/server/openapi.py`.
4
+
5
+ ## Architecture Overview
6
+
7
+ The new implementation uses a **stateless request building approach** with `openapi-core` and `RequestDirector`, providing zero-latency startup and robust OpenAPI support optimized for serverless environments.
8
+
9
+ ### Core Components
10
+
11
+ 1. **`server.py`** - `FastMCPOpenAPI` main server class with RequestDirector integration
12
+ 2. **`components.py`** - Simplified component implementations using RequestDirector
13
+ 3. **`routing.py`** - Route mapping and component selection logic
14
+
15
+ ### Key Architecture Principles
16
+
17
+ #### 1. Stateless Performance
18
+ - **Zero Startup Latency**: No code generation or heavy initialization
19
+ - **RequestDirector**: Stateless HTTP request building using openapi-core
20
+ - **Pre-calculated Schemas**: All complex processing done during parsing
21
+
22
+ #### 2. Unified Implementation
23
+ - **Single Code Path**: All components use RequestDirector consistently
24
+ - **No Fallbacks**: Simplified architecture without hybrid complexity
25
+ - **Performance First**: Optimized for cold starts and serverless deployments
26
+
27
+ #### 3. OpenAPI Compliance
28
+ - **openapi-core Integration**: Leverages proven library for parameter serialization
29
+ - **Full Feature Support**: Complete OpenAPI 3.0/3.1 support including deepObject
30
+ - **Error Handling**: Comprehensive HTTP error mapping to MCP errors
31
+
32
+ ## Component Classes
33
+
34
+ ### RequestDirector-Based Components
35
+
36
+ #### `OpenAPITool`
37
+ - Executes operations using RequestDirector for HTTP request building
38
+ - Automatic parameter validation and OpenAPI-compliant serialization
39
+ - Built-in error handling and structured response processing
40
+ - **Advantages**: Zero latency, robust, comprehensive OpenAPI support
41
+
42
+ #### `OpenAPIResource` / `OpenAPIResourceTemplate`
43
+ - Provides resource access using RequestDirector
44
+ - Consistent parameter handling across all resource types
45
+ - Support for complex parameter patterns and collision resolution
46
+ - **Advantages**: High performance, simplified architecture, reliable error handling
47
+
48
+ ## Server Implementation
49
+
50
+ ### `FastMCPOpenAPI` Class
51
+
52
+ The main server class orchestrates the stateless request building approach:
53
+
54
+ ```python
55
+ class FastMCPOpenAPI(FastMCP):
56
+ def __init__(self, openapi_spec: dict, client: httpx.AsyncClient, **kwargs):
57
+ # 1. Parse OpenAPI spec to HTTP routes with pre-calculated schemas
58
+ self._routes = parse_openapi_to_http_routes(openapi_spec)
59
+
60
+ # 2. Initialize RequestDirector with openapi-core Spec
61
+ self._spec = Spec.from_dict(openapi_spec)
62
+ self._director = RequestDirector(self._spec)
63
+
64
+ # 3. Create components using RequestDirector
65
+ self._create_components()
66
+ ```
67
+
68
+ ### Component Creation Logic
69
+
70
+ ```python
71
+ def _create_tool(self, route: HTTPRoute) -> Tool:
72
+ # All tools use RequestDirector for consistent, high-performance request building
73
+ return OpenAPITool(
74
+ client=self._client,
75
+ route=route,
76
+ director=self._director,
77
+ name=tool_name,
78
+ description=description,
79
+ parameters=flat_param_schema
80
+ )
81
+ ```
82
+
83
+ ## Data Flow
84
+
85
+ ### Stateless Request Building
86
+
87
+ ```
88
+ OpenAPI Spec → HTTPRoute with Pre-calculated Fields → RequestDirector → HTTP Request → Structured Response
89
+ ```
90
+
91
+ 1. **Spec Parsing**: OpenAPI spec parsed to `HTTPRoute` models with pre-calculated schemas
92
+ 2. **RequestDirector Setup**: openapi-core Spec initialized for request building
93
+ 3. **Component Creation**: Create components with RequestDirector reference
94
+ 4. **Request Building**: RequestDirector builds HTTP request from flat parameters
95
+ 5. **Request Execution**: Execute request with httpx client
96
+ 6. **Response Processing**: Return structured MCP response
97
+
98
+ ## Key Features
99
+
100
+ ### 1. Enhanced Parameter Handling
101
+
102
+ #### Parameter Collision Resolution
103
+ - **Automatic Suffixing**: Colliding parameters get location-based suffixes
104
+ - **Example**: `id` in path and body becomes `id__path` and `id`
105
+ - **Transparent**: LLMs see suffixed parameters, implementation routes correctly
106
+
107
+ #### DeepObject Style Support
108
+ - **Native Support**: Generated client handles all deepObject variations
109
+ - **Explode Handling**: Proper support for explode=true/false
110
+ - **Complex Objects**: Nested object serialization works correctly
111
+
112
+ ### 2. Robust Error Handling
113
+
114
+ #### HTTP Error Mapping
115
+ - **Status Code Mapping**: HTTP errors mapped to appropriate MCP errors
116
+ - **Structured Responses**: Error details preserved in tool results
117
+ - **Timeout Handling**: Network timeouts handled gracefully
118
+
119
+ #### Request Building Error Handling
120
+ - **Parameter Validation**: Invalid parameters caught during request building
121
+ - **Schema Validation**: openapi-core validates all OpenAPI constraints
122
+ - **Graceful Degradation**: Missing optional parameters handled smoothly
123
+
124
+ ### 3. Performance Optimizations
125
+
126
+ #### Efficient Client Reuse
127
+ - **Connection Pooling**: HTTP connections reused across requests
128
+ - **Client Caching**: Generated clients cached for performance
129
+ - **Async Support**: Full async/await throughout
130
+
131
+ #### Request Optimization
132
+ - **Pre-calculated Schemas**: All complex processing done during initialization
133
+ - **Parameter Mapping**: Collision resolution handled upfront
134
+ - **Zero Latency**: No runtime code generation or complex schema processing
135
+
136
+ ## Configuration
137
+
138
+ ### Server Options
139
+
140
+ ```python
141
+ server = FastMCPOpenAPI(
142
+ openapi_spec=spec, # Required: OpenAPI specification
143
+ client=httpx_client, # Required: HTTP client instance
144
+ name="API Server", # Optional: Server name
145
+ route_map=custom_routes, # Optional: Custom route mappings
146
+ enable_caching=True, # Optional: Enable response caching
147
+ )
148
+ ```
149
+
150
+ ### Route Mapping Customization
151
+
152
+ ```python
153
+ from fastmcp.server.openapi_new.routing import RouteMap
154
+
155
+ custom_routes = RouteMap({
156
+ "GET:/users": "tool", # Force specific operations to be tools
157
+ "GET:/status": "resource", # Force specific operations to be resources
158
+ })
159
+ ```
160
+
161
+ ## Testing Strategy
162
+
163
+ ### Test Structure
164
+
165
+ Tests are organized by functionality:
166
+ - `test_server.py` - Server integration and RequestDirector behavior
167
+ - `test_parameter_collisions.py` - Parameter collision handling
168
+ - `test_deepobject_style.py` - DeepObject parameter style support
169
+ - `test_openapi_features.py` - General OpenAPI feature compliance
170
+
171
+ ### Testing Philosophy
172
+
173
+ 1. **Real Integration**: Test with real OpenAPI specs and HTTP clients
174
+ 2. **Minimal Mocking**: Only mock external API endpoints
175
+ 3. **Behavioral Focus**: Test behavior, not implementation details
176
+ 4. **Performance Focus**: Test that initialization is fast and stateless
177
+
178
+ ### Example Test Pattern
179
+
180
+ ```python
181
+ async def test_stateless_request_building():
182
+ """Test that server works with stateless RequestDirector approach."""
183
+
184
+ # Test server initialization is fast
185
+ start_time = time.time()
186
+ server = FastMCPOpenAPI(spec=valid_spec, client=client)
187
+ init_time = time.time() - start_time
188
+ assert init_time < 0.01 # Should be very fast
189
+
190
+ # Verify RequestDirector functionality
191
+ assert hasattr(server, '_director')
192
+ assert hasattr(server, '_spec')
193
+ ```
194
+
195
+ ## Migration Benefits
196
+
197
+ ### From Legacy Implementation
198
+
199
+ 1. **Eliminated Startup Latency**: Zero code generation overhead (100-200ms improvement)
200
+ 2. **Better OpenAPI Compliance**: openapi-core handles all OpenAPI features correctly
201
+ 3. **Serverless Friendly**: Perfect for cold-start environments
202
+ 4. **Simplified Architecture**: Single RequestDirector approach eliminates complexity
203
+ 5. **Enhanced Reliability**: No dynamic code generation failures
204
+
205
+ ### Backward Compatibility
206
+
207
+ - **Same Interface**: Public API unchanged from legacy implementation
208
+ - **Performance Improvement**: Significantly faster initialization
209
+ - **No Breaking Changes**: Existing code works without modification
210
+
211
+ ## Monitoring and Debugging
212
+
213
+ ### Logging
214
+
215
+ ```python
216
+ # Enable debug logging to see implementation choices
217
+ import logging
218
+ logging.getLogger("fastmcp.server.openapi_new").setLevel(logging.DEBUG)
219
+ ```
220
+
221
+ ### Key Log Messages
222
+ - **RequestDirector Initialization**: Success/failure of RequestDirector setup
223
+ - **Schema Pre-calculation**: Pre-calculated schema and parameter map status
224
+ - **Request Building**: Parameter mapping and URL construction details
225
+ - **Performance Metrics**: Request timing and error rates
226
+
227
+ ### Debugging Common Issues
228
+
229
+ 1. **RequestDirector Initialization Fails**
230
+ - Check OpenAPI spec validity with `openapi-core`
231
+ - Verify spec format is correct JSON/YAML
232
+ - Ensure all required OpenAPI fields are present
233
+
234
+ 2. **Parameter Issues**
235
+ - Enable debug logging for parameter processing
236
+ - Check for parameter collision warnings
237
+ - Verify OpenAPI spec parameter definitions
238
+
239
+ 3. **Performance Issues**
240
+ - Monitor RequestDirector request building timing
241
+ - Check HTTP client configuration
242
+ - Review response processing timing
243
+
244
+ ## Future Enhancements
245
+
246
+ ### Planned Features
247
+
248
+ 1. **Advanced Caching**: Intelligent response caching with TTL
249
+ 2. **Streaming Support**: Handle streaming API responses
250
+ 3. **Batch Operations**: Optimize multiple operation calls
251
+ 4. **Enhanced Monitoring**: Detailed metrics and health checks
252
+ 5. **Configuration Management**: Dynamic configuration updates
253
+
254
+ ### Performance Improvements
255
+
256
+ 1. **Enhanced Schema Caching**: More aggressive schema pre-calculation
257
+ 2. **Parallel Processing**: Concurrent operation execution
258
+ 3. **Memory Optimization**: Further reduce memory footprint
259
+ 4. **Request Optimization**: Smart request batching and deduplication
260
+
261
+ ## Related Documentation
262
+
263
+ - `/utilities/openapi_new/README.md` - Utility implementation details
264
+ - `/server/openapi/README.md` - Legacy implementation reference
265
+ - `/tests/server/openapi_new/` - Comprehensive test suite
266
+ - Project documentation on OpenAPI integration patterns
@@ -0,0 +1,38 @@
1
+ """OpenAPI server implementation for FastMCP - refactored for better maintainability."""
2
+
3
+ # Import from server
4
+ from .server import FastMCPOpenAPI
5
+
6
+ # Import from routing
7
+ from .routing import (
8
+ MCPType,
9
+ RouteMap,
10
+ RouteMapFn,
11
+ ComponentFn,
12
+ DEFAULT_ROUTE_MAPPINGS,
13
+ _determine_route_type,
14
+ )
15
+
16
+ # Import from components
17
+ from .components import (
18
+ OpenAPITool,
19
+ OpenAPIResource,
20
+ OpenAPIResourceTemplate,
21
+ )
22
+
23
+ # Export public symbols - maintaining backward compatibility
24
+ __all__ = [
25
+ # Server
26
+ "FastMCPOpenAPI",
27
+ # Routing
28
+ "MCPType",
29
+ "RouteMap",
30
+ "RouteMapFn",
31
+ "ComponentFn",
32
+ "DEFAULT_ROUTE_MAPPINGS",
33
+ "_determine_route_type",
34
+ # Components
35
+ "OpenAPITool",
36
+ "OpenAPIResource",
37
+ "OpenAPIResourceTemplate",
38
+ ]
@@ -0,0 +1,348 @@
1
+ """OpenAPI component implementations: Tool, Resource, and ResourceTemplate classes."""
2
+
3
+ import json
4
+ import re
5
+ from collections.abc import Callable
6
+ from typing import TYPE_CHECKING, Any
7
+
8
+ import httpx
9
+ from mcp.types import ToolAnnotations
10
+ from pydantic.networks import AnyUrl
11
+
12
+ # Import from our new utilities
13
+ from fastmcp.experimental.utilities.openapi import HTTPRoute
14
+ from fastmcp.experimental.utilities.openapi.director import RequestDirector
15
+ from fastmcp.resources import Resource, ResourceTemplate
16
+ from fastmcp.server.dependencies import get_http_headers
17
+ from fastmcp.tools.tool import Tool, ToolResult
18
+ from fastmcp.utilities.logging import get_logger
19
+
20
+ if TYPE_CHECKING:
21
+ from fastmcp.server import Context
22
+
23
+ logger = get_logger(__name__)
24
+
25
+
26
+ class OpenAPITool(Tool):
27
+ """Tool implementation for OpenAPI endpoints."""
28
+
29
+ def __init__(
30
+ self,
31
+ client: httpx.AsyncClient,
32
+ route: HTTPRoute,
33
+ director: RequestDirector,
34
+ name: str,
35
+ description: str,
36
+ parameters: dict[str, Any],
37
+ output_schema: dict[str, Any] | None = None,
38
+ tags: set[str] | None = None,
39
+ timeout: float | None = None,
40
+ annotations: ToolAnnotations | None = None,
41
+ serializer: Callable[[Any], str] | None = None,
42
+ ):
43
+ super().__init__(
44
+ name=name,
45
+ description=description,
46
+ parameters=parameters,
47
+ output_schema=output_schema,
48
+ tags=tags or set(),
49
+ annotations=annotations,
50
+ serializer=serializer,
51
+ )
52
+ self._client = client
53
+ self._route = route
54
+ self._director = director
55
+ self._timeout = timeout
56
+
57
+ def __repr__(self) -> str:
58
+ """Custom representation to prevent recursion errors when printing."""
59
+ return f"OpenAPITool(name={self.name!r}, method={self._route.method}, path={self._route.path})"
60
+
61
+ async def run(self, arguments: dict[str, Any]) -> ToolResult:
62
+ """Execute the HTTP request using RequestDirector for simplified parameter handling."""
63
+ try:
64
+ # Get base URL from client
65
+ base_url = (
66
+ str(self._client.base_url)
67
+ if hasattr(self._client, "base_url") and self._client.base_url
68
+ else "http://localhost"
69
+ )
70
+
71
+ # Get Headers from client
72
+ cli_headers = (
73
+ self._client.headers
74
+ if hasattr(self._client, "headers") and self._client.headers
75
+ else {}
76
+ )
77
+
78
+ # Build the request using RequestDirector
79
+ request = self._director.build(self._route, arguments, base_url)
80
+
81
+ # First add server headers (lowest precedence)
82
+ if cli_headers:
83
+ # Merge with existing headers, _client headers as base
84
+ if request.headers:
85
+ # Start with request headers, then add client headers
86
+ for key, value in cli_headers.items():
87
+ if key not in request.headers:
88
+ request.headers[key] = value
89
+ else:
90
+ # Create new headers from cli_headers
91
+ for key, value in cli_headers.items():
92
+ request.headers[key] = value
93
+
94
+ # Then add MCP client transport headers (highest precedence)
95
+ mcp_headers = get_http_headers()
96
+ if mcp_headers:
97
+ # Merge with existing headers, MCP headers take precedence over all
98
+ if request.headers:
99
+ request.headers.update(mcp_headers)
100
+ else:
101
+ # Create new headers from mcp_headers
102
+ for key, value in mcp_headers.items():
103
+ request.headers[key] = value
104
+ # print logger
105
+ logger.debug(f"run - sending request; headers: {request.headers}")
106
+
107
+ # Execute the request
108
+ # Note: httpx.AsyncClient.send() doesn't accept timeout parameter
109
+ # The timeout should be configured on the client itself
110
+ response = await self._client.send(request)
111
+
112
+ # Raise for 4xx/5xx responses
113
+ response.raise_for_status()
114
+
115
+ # Try to parse as JSON first
116
+ try:
117
+ result = response.json()
118
+
119
+ # Handle structured content based on output schema, if any
120
+ structured_output = None
121
+ if self.output_schema is not None:
122
+ if self.output_schema.get("x-fastmcp-wrap-result"):
123
+ # Schema says wrap - always wrap in result key
124
+ structured_output = {"result": result}
125
+ else:
126
+ structured_output = result
127
+ # If no output schema, use fallback logic for backward compatibility
128
+ elif not isinstance(result, dict):
129
+ structured_output = {"result": result}
130
+ else:
131
+ structured_output = result
132
+
133
+ return ToolResult(structured_content=structured_output)
134
+ except json.JSONDecodeError:
135
+ return ToolResult(content=response.text)
136
+
137
+ except httpx.HTTPStatusError as e:
138
+ # Handle HTTP errors (4xx, 5xx)
139
+ error_message = (
140
+ f"HTTP error {e.response.status_code}: {e.response.reason_phrase}"
141
+ )
142
+ try:
143
+ error_data = e.response.json()
144
+ error_message += f" - {error_data}"
145
+ except (json.JSONDecodeError, ValueError):
146
+ if e.response.text:
147
+ error_message += f" - {e.response.text}"
148
+
149
+ raise ValueError(error_message)
150
+
151
+ except httpx.RequestError as e:
152
+ # Handle request errors (connection, timeout, etc.)
153
+ raise ValueError(f"Request error: {str(e)}")
154
+
155
+
156
+ class OpenAPIResource(Resource):
157
+ """Resource implementation for OpenAPI endpoints."""
158
+
159
+ def __init__(
160
+ self,
161
+ client: httpx.AsyncClient,
162
+ route: HTTPRoute,
163
+ director: RequestDirector,
164
+ uri: str,
165
+ name: str,
166
+ description: str,
167
+ mime_type: str = "application/json",
168
+ tags: set[str] = set(),
169
+ timeout: float | None = None,
170
+ ):
171
+ super().__init__(
172
+ uri=AnyUrl(uri), # Convert string to AnyUrl
173
+ name=name,
174
+ description=description,
175
+ mime_type=mime_type,
176
+ tags=tags,
177
+ )
178
+ self._client = client
179
+ self._route = route
180
+ self._director = director
181
+ self._timeout = timeout
182
+
183
+ def __repr__(self) -> str:
184
+ """Custom representation to prevent recursion errors when printing."""
185
+ return f"OpenAPIResource(name={self.name!r}, uri={self.uri!r}, path={self._route.path})"
186
+
187
+ async def read(self) -> str | bytes:
188
+ """Fetch the resource data by making an HTTP request."""
189
+ try:
190
+ # Extract path parameters from the URI if present
191
+ path = self._route.path
192
+ resource_uri = str(self.uri)
193
+
194
+ # If this is a templated resource, extract path parameters from the URI
195
+ if "{" in path and "}" in path:
196
+ # Extract the resource ID from the URI (the last part after the last slash)
197
+ parts = resource_uri.split("/")
198
+
199
+ if len(parts) > 1:
200
+ # Find all path parameters in the route path
201
+ path_params = {}
202
+
203
+ # Find the path parameter names from the route path
204
+ param_matches = re.findall(r"\{([^}]+)\}", path)
205
+ if param_matches:
206
+ # Reverse sorting from creation order (traversal is backwards)
207
+ param_matches.sort(reverse=True)
208
+ # Number of sent parameters is number of parts -1 (assuming first part is resource identifier)
209
+ expected_param_count = len(parts) - 1
210
+ # Map parameters from the end of the URI to the parameters in the path
211
+ # Last parameter in URI (parts[-1]) maps to last parameter in path, and so on
212
+ for i, param_name in enumerate(param_matches):
213
+ # Ensure we don't use resource identifier as parameter
214
+ if i < expected_param_count:
215
+ # Get values from the end of parts
216
+ param_value = parts[-1 - i]
217
+ path_params[param_name] = param_value
218
+
219
+ # Replace path parameters with their values
220
+ for param_name, param_value in path_params.items():
221
+ path = path.replace(f"{{{param_name}}}", str(param_value))
222
+
223
+ # Filter any query parameters - get query parameters and filter out None/empty values
224
+ query_params = {}
225
+ for param in self._route.parameters:
226
+ if param.location == "query" and hasattr(self, f"_{param.name}"):
227
+ value = getattr(self, f"_{param.name}")
228
+ if value is not None and value != "":
229
+ query_params[param.name] = value
230
+
231
+ # Prepare headers with correct precedence: server < client transport
232
+ headers = {}
233
+ # Start with server headers (lowest precedence)
234
+ cli_headers = (
235
+ self._client.headers
236
+ if hasattr(self._client, "headers") and self._client.headers
237
+ else {}
238
+ )
239
+ headers.update(cli_headers)
240
+
241
+ # Add MCP client transport headers (highest precedence)
242
+ mcp_headers = get_http_headers()
243
+ headers.update(mcp_headers)
244
+
245
+ response = await self._client.request(
246
+ method=self._route.method,
247
+ url=path,
248
+ params=query_params,
249
+ headers=headers,
250
+ timeout=self._timeout,
251
+ )
252
+
253
+ # Raise for 4xx/5xx responses
254
+ response.raise_for_status()
255
+
256
+ # Determine content type and return appropriate format
257
+ content_type = response.headers.get("content-type", "").lower()
258
+
259
+ if "application/json" in content_type:
260
+ result = response.json()
261
+ return json.dumps(result)
262
+ elif any(ct in content_type for ct in ["text/", "application/xml"]):
263
+ return response.text
264
+ else:
265
+ return response.content
266
+
267
+ except httpx.HTTPStatusError as e:
268
+ # Handle HTTP errors (4xx, 5xx)
269
+ error_message = (
270
+ f"HTTP error {e.response.status_code}: {e.response.reason_phrase}"
271
+ )
272
+ try:
273
+ error_data = e.response.json()
274
+ error_message += f" - {error_data}"
275
+ except (json.JSONDecodeError, ValueError):
276
+ if e.response.text:
277
+ error_message += f" - {e.response.text}"
278
+
279
+ raise ValueError(error_message)
280
+
281
+ except httpx.RequestError as e:
282
+ # Handle request errors (connection, timeout, etc.)
283
+ raise ValueError(f"Request error: {str(e)}")
284
+
285
+
286
+ class OpenAPIResourceTemplate(ResourceTemplate):
287
+ """Resource template implementation for OpenAPI endpoints."""
288
+
289
+ def __init__(
290
+ self,
291
+ client: httpx.AsyncClient,
292
+ route: HTTPRoute,
293
+ director: RequestDirector,
294
+ uri_template: str,
295
+ name: str,
296
+ description: str,
297
+ parameters: dict[str, Any],
298
+ tags: set[str] = set(),
299
+ timeout: float | None = None,
300
+ ):
301
+ super().__init__(
302
+ uri_template=uri_template,
303
+ name=name,
304
+ description=description,
305
+ parameters=parameters,
306
+ tags=tags,
307
+ )
308
+ self._client = client
309
+ self._route = route
310
+ self._director = director
311
+ self._timeout = timeout
312
+
313
+ def __repr__(self) -> str:
314
+ """Custom representation to prevent recursion errors when printing."""
315
+ return f"OpenAPIResourceTemplate(name={self.name!r}, uri_template={self.uri_template!r}, path={self._route.path})"
316
+
317
+ async def create_resource(
318
+ self,
319
+ uri: str,
320
+ params: dict[str, Any],
321
+ context: "Context | None" = None,
322
+ ) -> Resource:
323
+ """Create a resource with the given parameters."""
324
+ # Generate a URI for this resource instance
325
+ uri_parts = []
326
+ for key, value in params.items():
327
+ uri_parts.append(f"{key}={value}")
328
+
329
+ # Create and return a resource
330
+ return OpenAPIResource(
331
+ client=self._client,
332
+ route=self._route,
333
+ director=self._director,
334
+ uri=uri,
335
+ name=f"{self.name}-{'-'.join(uri_parts)}",
336
+ description=self.description or f"Resource for {self._route.path}",
337
+ mime_type="application/json",
338
+ tags=set(self._route.tags or []),
339
+ timeout=self._timeout,
340
+ )
341
+
342
+
343
+ # Export public symbols
344
+ __all__ = [
345
+ "OpenAPITool",
346
+ "OpenAPIResource",
347
+ "OpenAPIResourceTemplate",
348
+ ]