Skip to main content
Glama
ingeno
by ingeno
director.py8.08 kB
"""Request director using openapi-core for stateless HTTP request building.""" from typing import Any from urllib.parse import urljoin import httpx from jsonschema_path import SchemaPath from fastmcp.utilities.logging import get_logger from .models import HTTPRoute logger = get_logger(__name__) class RequestDirector: """Builds httpx.Request objects from HTTPRoute and arguments using openapi-core.""" def __init__(self, spec: SchemaPath): """Initialize with a parsed SchemaPath object.""" self._spec = spec def build( self, route: HTTPRoute, flat_args: dict[str, Any], base_url: str = "http://localhost", ) -> httpx.Request: """ Constructs a final httpx.Request object, handling all OpenAPI serialization. Args: route: HTTPRoute containing OpenAPI operation details flat_args: Flattened arguments from LLM (may include suffixed parameters) base_url: Base URL for the request Returns: httpx.Request: Properly formatted HTTP request """ logger.debug( f"Building request for {route.method} {route.path} with args: {flat_args}" ) # Step 1: Un-flatten arguments into path, query, body, etc. using parameter map path_params, query_params, header_params, body = self._unflatten_arguments( route, flat_args ) logger.debug( f"Unflattened - path: {path_params}, query: {query_params}, headers: {header_params}, body: {body}" ) # Step 2: Build base URL with path parameters url = self._build_url(route.path, path_params, base_url) # Step 3: Prepare request data request_data = { "method": route.method.upper(), "url": url, "params": query_params if query_params else None, "headers": header_params if header_params else None, } # Step 4: Handle request body if body is not None: if isinstance(body, dict) or isinstance(body, list): request_data["json"] = body else: request_data["content"] = body # Step 5: Create httpx.Request return httpx.Request( method=request_data["method"], url=request_data["url"], params=request_data.get("params"), headers=request_data.get("headers"), json=request_data.get("json"), content=request_data.get("content"), ) def _unflatten_arguments( self, route: HTTPRoute, flat_args: dict[str, Any] ) -> tuple[dict[str, Any], dict[str, Any], dict[str, Any], Any]: """ Maps flat arguments back to their OpenAPI locations using the parameter map. Args: route: HTTPRoute with parameter_map containing location mappings flat_args: Flat arguments from LLM call Returns: Tuple of (path_params, query_params, header_params, body) """ path_params = {} query_params = {} header_params = {} body_props = {} # Use parameter map to route arguments to correct locations if hasattr(route, "parameter_map") and route.parameter_map: for arg_name, value in flat_args.items(): if value is None: continue # Skip None values for optional parameters if arg_name not in route.parameter_map: logger.warning( f"Argument '{arg_name}' not found in parameter map for {route.operation_id}" ) continue mapping = route.parameter_map[arg_name] location = mapping["location"] openapi_name = mapping["openapi_name"] if location == "path": path_params[openapi_name] = value elif location == "query": query_params[openapi_name] = value elif location == "header": header_params[openapi_name] = value elif location == "body": body_props[openapi_name] = value else: logger.warning( f"Unknown parameter location '{location}' for {arg_name}" ) else: # Fallback: try to map arguments based on parameter definitions logger.debug("No parameter map available, using fallback mapping") # Create a mapping from parameter names to their locations param_locations = {} for param in route.parameters: param_locations[param.name] = param.location # Map arguments to locations for arg_name, value in flat_args.items(): if value is None: continue # Check if it's a suffixed parameter (e.g., id__path) if "__" in arg_name: base_name, location = arg_name.rsplit("__", 1) if location in ["path", "query", "header"]: if location == "path": path_params[base_name] = value elif location == "query": query_params[base_name] = value elif location == "header": header_params[base_name] = value continue # Check if it's a known parameter if arg_name in param_locations: location = param_locations[arg_name] if location == "path": path_params[arg_name] = value elif location == "query": query_params[arg_name] = value elif location == "header": header_params[arg_name] = value else: # Assume it's a body property body_props[arg_name] = value # Handle body construction body = None if body_props: # If we have body properties, construct the body object if route.request_body and route.request_body.content_schema: # Check if the request body expects an object with properties content_type = next(iter(route.request_body.content_schema)) body_schema = route.request_body.content_schema[content_type] if body_schema.get("type") == "object": body = body_props elif len(body_props) == 1: # If body schema is not an object and we have exactly one property, # use the property value directly body = next(iter(body_props.values())) else: # Multiple properties but schema is not object - wrap in object body = body_props else: body = body_props return path_params, query_params, header_params, body def _build_url( self, path_template: str, path_params: dict[str, Any], base_url: str ) -> str: """ Build URL by substituting path parameters in the template. Args: path_template: OpenAPI path template (e.g., "/users/{id}") path_params: Path parameter values base_url: Base URL to prepend Returns: Complete URL with path parameters substituted """ # Substitute path parameters url_path = path_template for param_name, param_value in path_params.items(): placeholder = f"{{{param_name}}}" if placeholder in url_path: url_path = url_path.replace(placeholder, str(param_value)) # Combine with base URL return urljoin(base_url.rstrip("/") + "/", url_path.lstrip("/")) # Export public symbols __all__ = ["RequestDirector"]

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/ingeno/mcp-openapi-lambda'

If you have feedback or need assistance with the MCP directory API, please join our Discord server