OpenAPI Endpoint Parameter Mapping Design
This document outlines the design for passing OpenAPI parameter mapping details (path parameters, query parameters, headers, cookies, and body) from SpecUtil to the mcp-router in light-fabric. This enables the MCP router to correctly invoke the backend REST APIs based on flat tool call arguments provided by AI agents.
Context & Motivation
When the OpenAPI specification is parsed by SpecUtil.java, endpoints are registered in the Light Portal database, and a flat toolSchema is generated to represent the input schema.
For example, given a GET request with query parameters (like /offers) or path parameters (like /customers/{customerId}), the parameters are flattened into a single JSON schema structure:
{
"type": "object",
"properties": {
"segment": { "type": "string", "description": "Customer segment filter." },
"state": { "type": "string", "description": "Region or province filter." }
}
}
The AI agent invokes this tool by passing a flat map of arguments:
{
"segment": "premium",
"state": "ON"
}
Currently, the mcp-router in light-fabric does not know where each argument belongs (e.g., whether it should be placed in the URL path, query string, headers, or request body). For GET requests, it defaults to appending all arguments as query parameters. For POST/PUT/PATCH requests, it defaults to placing all arguments in the JSON body.
This leads to several failures:
- Path parameters (e.g.
{customerId}) are not substituted in the URL path. - Header parameters and Cookies are completely lost or put in the wrong place.
- Mixed requests (e.g. a
POSTwith a URL path parameter and a JSON body) cannot be assembled correctly.
Design Requirements
- Accuracy: The router must place every argument in the exact location (path, query, header, cookie, or body) defined by the OpenAPI specification.
- Efficiency / Cleanliness: The solution must not increase token usage for the LLM agents. Gateway-specific routing details should remain hidden from public tool schemas exposed to agents.
- Backward Compatibility: If no mapping metadata is provided, the router should fall back to its existing default routing rules.
Design Options for Storing Parameter Locations
We evaluated two options for conveying parameter mapping information from the Spec parser to the gateway router:
Option A: Schema-Level Annotations (toolSchema)
Inject custom attributes (such as "x-in": "query", "x-in": "path") directly into the JSON Schema properties:
{
"type": "object",
"properties": {
"customerId": {
"type": "string",
"x-in": "path"
}
}
}
- Pros: Self-contained schema where each field is annotated with its location.
- Cons: Increases the payload size of
inputSchemasent to the LLM agent viatools/list, leading to wasted token count and exposing gateway-internal routing details to the agent.
Option B: Metadata-Level Mappings (toolMetadata) - RECOMMENDED
Store the parameter locations in the private toolMetadata payload, which is saved in the database and loaded by the gateway, but is filtered out and never sent to the LLM agent.
{
"routing": {
"domain": "Offers",
"sourceProtocol": "openapi"
},
"parameters": {
"customerId": "path",
"segment": "query",
"X-Trace-Id": "header",
"body": "body"
}
}
- Pros:
- Keeps the public
toolSchemaclean and minimal. - Saves LLM token costs.
- Consistently places all gateway-internal routing decisions inside the private
toolMetadatastructure.
- Keeps the public
- Cons: Slightly split parsing (schema for validation, metadata for execution), but since the gateway already deserializes both, this has negligible overhead.
Detailed Solution
We will implement Option B. The design requires updates to two components: SpecUtil.java (spec parsing) and mcp.rs (routing execution in the Rust gateway).
1. Spec Parser Changes (SpecUtil.java)
When parsing an OpenAPI spec in SpecUtil.java, we will build a parameters location map of type Map<String, String> mapping each parameter name to its location:
path->"path"query->"query"header->"header"cookie->"cookie"- Request Body ->
"body"(mapped from the unified schema body property for body-capable HTTP methods)
This map will be attached to routingExtras during metadata enrichment under the "parameters" key, resulting in the following toolMetadata structure:
{
"routing": {
"domain": "Offers",
"sourceProtocol": "openapi",
"parameters": {
"segment": "query",
"state": "query",
"customerId": "path"
}
},
"safety": {
"read_only": true,
"destructive": false
}
}
2. Rust Gateway Router Changes (mcp.rs)
The mcp.rs module in light-pingora will be updated as follows:
- Extract Parameter Locations: When caching or loading tools, the router will deserialize the
parametersmap fromtool_metadata.routing.parameters. - Argument Placement: When executing an HTTP tool call, the router will partition the
argumentsmap into:- Path Map: Key-value pairs where location is
"path". - Query Map: Key-value pairs where location is
"query". - Header Map: Key-value pairs where location is
"header". - Cookie Map: Key-value pairs where location is
"cookie". - Body Val: The argument corresponding to the key mapped to
"body". If no explicit body mapping is defined but the HTTP method allows a body (POST/PUT/PATCH), any arguments not explicitly mapped to path/query/header/cookie will be packed into the JSON request body.
- Path Map: Key-value pairs where location is
- Build Outbound Request:
- Path Substitution: Iterate through the path map and replace
{key}placeholders in the tool URL path. - Query Serialization: Append the query map properties to the target URL’s query string using URL-encoding.
- Header Injection: Append header map values as HTTP headers.
- Cookie Injection: Format cookie map values into the
Cookieheader. - Body Serialization: Attach the JSON body payload to the outbound HTTP request.
- Path Substitution: Iterate through the path map and replace
Concrete Examples
Example 1: GET /offers (Query Filters)
Original OpenAPI Specification
/offers:
get:
operationId: searchOffers
parameters:
- name: segment
in: query
schema:
type: string
- name: state
in: query
schema:
type: string
Generated Database Artifacts
toolSchema:{ "type": "object", "properties": { "segment": { "type": "string" }, "state": { "type": "string" } } }toolMetadata:{ "routing": { "domain": "Offers", "sourceProtocol": "openapi", "parameters": { "segment": "query", "state": "query" } } }
Tool Call Arguments
{
"segment": "premium",
"state": "ON"
}
Outgoing REST Call
GET /offers?segment=premium&state=ON HTTP/1.1
Host: backend-service
Example 2: GET /customers/{customerId} (Path Parameter)
Original OpenAPI Specification
/customers/{customerId}:
get:
operationId: getCustomerProfile
parameters:
- name: customerId
in: path
required: true
schema:
type: string
Generated Database Artifacts
toolSchema:{ "type": "object", "properties": { "customerId": { "type": "string" } }, "required": ["customerId"] }toolMetadata:{ "routing": { "domain": "Customers", "sourceProtocol": "openapi", "parameters": { "customerId": "path" } } }
Tool Call Arguments
{
"customerId": "CUST-1001"
}
Outgoing REST Call
GET /customers/CUST-1001 HTTP/1.1
Host: backend-service
Example 3: PUT /customers/{customerId}/preferences (Mixed Path & Body)
Original OpenAPI Specification
/customers/{customerId}/preferences:
put:
operationId: updateCustomerPreferences
parameters:
- name: customerId
in: path
required: true
schema:
type: string
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
channel:
type: string
consent:
type: boolean
Generated Database Artifacts
toolSchema:{ "type": "object", "properties": { "customerId": { "type": "string" }, "body": { "type": "object", "properties": { "channel": { "type": "string" }, "consent": { "type": "boolean" } } } }, "required": ["customerId", "body"] }toolMetadata:{ "routing": { "domain": "Customers", "sourceProtocol": "openapi", "parameters": { "customerId": "path", "body": "body" } } }
Tool Call Arguments
{
"customerId": "CUST-1001",
"body": {
"channel": "portal",
"consent": true
}
}
Outgoing REST Call
PUT /customers/CUST-1001/preferences HTTP/1.1
Host: backend-service
Content-Type: application/json
{"channel":"portal","consent":true}