{"slug": "mapping-api-path-query-header-and-body-parameters-to-mcp-tool-schemas", "title": "Mapping API Path, Query, Header, and Body Parameters to MCP Tool Schemas", "summary": "A developer explains how to map HTTP API path, query, header, and body parameters to MCP tool schemas, using a project-management API as an example. The tutorial demonstrates converting a PATCH endpoint into a single structured input schema, emphasizing clear tool naming and explicit path identifiers.", "body_md": "An API operation can receive input from several places.\n\nPath parameters identify the record. Query parameters filter or paginate the result. Headers carry metadata or authentication. The request body contains structured data for create and update operations.\n\nAn MCP tool should give the AI client one clear input schema.\n\nThat is the mapping problem:\n\n```\nHTTP API inputs\n  path + query + headers + body\n\nbecome\n\nMCP tool input\n  one structured schema the AI client can understand\n```\n\nThis tutorial walks through that mapping with practical examples. The goal is to make the tool easy for an AI client to call without hiding the real API contract.\n\nImagine a project-management API with this endpoint:\n\n```\nPATCH /workspaces/{workspace_id}/projects/{project_id}/tasks/{task_id}\n```\n\nIt updates one task.\n\nThe API accepts:\n\n`workspace_id`\n\n, `project_id`\n\n, and `task_id`\n\n;`notify_assignee`\n\n;`Idempotency-Key`\n\n.A shortened OpenAPI-style version might look like this:\n\n```\npaths:\n  /workspaces/{workspace_id}/projects/{project_id}/tasks/{task_id}:\n    patch:\n      operationId: updateTask\n      summary: Update a task\n      description: \"Update the title, status, assignee, or due date for one task.\"\n      parameters:\n        - name: workspace_id\n          in: path\n          required: true\n          schema:\n            type: string\n        - name: project_id\n          in: path\n          required: true\n          schema:\n            type: string\n        - name: task_id\n          in: path\n          required: true\n          schema:\n            type: string\n        - name: notify_assignee\n          in: query\n          required: false\n          schema:\n            type: boolean\n            default: false\n        - name: Idempotency-Key\n          in: header\n          required: false\n          schema:\n            type: string\n      requestBody:\n        required: true\n        content:\n          application/json:\n            schema:\n              type: object\n              properties:\n                title: \"\"\n                  type: string\n                status:\n                  type: string\n                  enum: [todo, in_progress, blocked, done]\n                assignee_id:\n                  type: string\n                due_date:\n                  type: string\n                  format: date\n              minProperties: 1\n      security:\n        - bearerAuth: []\n```\n\nThe API shape is split across the HTTP request. The MCP tool should present the editable parts in one schema.\n\nThe route is useful for the adapter, but it is not a good tool name.\n\nThis is weak:\n\n```\n{\n  \"name\": \"patch_workspaces_projects_tasks\"\n}\n```\n\nThis is clearer:\n\n```\n{\n  \"name\": \"update_task\"\n}\n```\n\nIf your API has several update operations, use the object and action to remove ambiguity:\n\n`update_task`\n\n`update_task_status`\n\n`assign_task`\n\n`reschedule_task`\n\nThe right name depends on what the endpoint actually does. If the endpoint updates many fields, `update_task`\n\nmay be correct. If the endpoint only changes status, `update_task_status`\n\nis better.\n\nPath parameters usually identify the exact resource being addressed. In an MCP tool schema, they are usually required fields.\n\nFrom the route:\n\n```\n/workspaces/{workspace_id}/projects/{project_id}/tasks/{task_id}\n```\n\nThe tool needs:\n\n```\n{\n  \"workspace_id\": \"wrk_123\",\n  \"project_id\": \"prj_456\",\n  \"task_id\": \"tsk_789\"\n}\n```\n\nIn the MCP tool schema:\n\n```\n{\n  \"type\": \"object\",\n  \"properties\": {\n    \"workspace_id\": {\n      \"type\": \"string\",\n      \"description\": \"The workspace that contains the project.\"\n    },\n    \"project_id\": {\n      \"type\": \"string\",\n      \"description\": \"The project that contains the task.\"\n    },\n    \"task_id\": {\n      \"type\": \"string\",\n      \"description\": \"The task to update.\"\n    }\n  },\n  \"required\": [\"workspace_id\", \"project_id\", \"task_id\"]\n}\n```\n\nKeep the path identifiers explicit. Do not collapse them into one generic `id`\n\nfield if the API needs all three values. The AI client should not guess which ID belongs to which level.\n\nQuery parameters often change how the operation behaves:\n\nIn the example, `notify_assignee`\n\ncontrols whether the API sends a notification after the update.\n\nThat should appear as an optional tool input:\n\n```\n{\n  \"notify_assignee\": {\n    \"type\": \"boolean\",\n    \"description\": \"Whether to notify the assigned user after the task is updated.\",\n    \"default\": false\n  }\n}\n```\n\nFor list operations, query parameters may be the main tool inputs:\n\n```\nGET /customers/{customer_id}/tickets?status=open&limit=20&cursor=abc\n```\n\nThe MCP schema might expose:\n\n```\n{\n  \"type\": \"object\",\n  \"properties\": {\n    \"customer_id\": {\n      \"type\": \"string\",\n      \"description\": \"The customer whose tickets should be listed.\"\n    },\n    \"status\": {\n      \"type\": \"string\",\n      \"enum\": [\"open\", \"pending\", \"closed\"],\n      \"description\": \"Optional ticket status filter.\"\n    },\n    \"limit\": {\n      \"type\": \"integer\",\n      \"minimum\": 1,\n      \"maximum\": 100,\n      \"default\": 20,\n      \"description\": \"Maximum number of tickets to return.\"\n    },\n    \"cursor\": {\n      \"type\": \"string\",\n      \"description\": \"Pagination cursor from a previous response.\"\n    }\n  },\n  \"required\": [\"customer_id\"]\n}\n```\n\nGood query mapping keeps list tools bounded. If a search endpoint accepts unlimited free-form parameters, the agent may produce slow, broad, or invalid calls.\n\nHeaders are tricky because some are normal inputs and some are credentials.\n\nAuthentication headers should not become normal tool inputs.\n\nDo not expose this:\n\n```\n{\n  \"authorization\": {\n    \"type\": \"string\",\n    \"description\": \"Bearer token for the API request.\"\n  }\n}\n```\n\nThat would make the credential model-visible.\n\nInstead, the tool input should stay focused on the business operation:\n\n```\n{\n  \"workspace_id\": \"wrk_123\",\n  \"project_id\": \"prj_456\",\n  \"task_id\": \"tsk_789\",\n  \"status\": \"blocked\"\n}\n```\n\nThe adapter or hosted MCP runtime should receive credentials through the authentication path and forward them to the upstream API:\n\n```\nAuthorization: Bearer <runtime credential>\n```\n\nThe upstream API still enforces identity, tenant, role, record, and action permissions. The MCP schema should not become a place where the model supplies secrets.\n\n0mcp supports API key, Bearer token, and OAuth pass-through. Credentials are supplied through the MCP client at request time and passed to the original API rather than stored by 0mcp. That keeps the generated tool schema focused on the task inputs.\n\nSome headers are not secrets. They may still matter.\n\nExamples:\n\n`Idempotency-Key`\n\n;`X-Request-Id`\n\n;`Accept-Language`\n\n;`If-Match`\n\n;`X-Client-Version`\n\n.Do not automatically expose every header to the AI client. Ask what role the header plays.\n\nExpose a header as a tool input when:\n\nFor `Idempotency-Key`\n\n, you may decide to generate it inside the MCP server instead of asking the AI client to provide it. That reduces friction and avoids duplicate-write bugs.\n\nFor `Accept-Language`\n\n, you might expose `language`\n\nas a business-friendly field rather than the raw HTTP header:\n\n```\n{\n  \"language\": {\n    \"type\": \"string\",\n    \"enum\": [\"en\", \"es\", \"fr\"],\n    \"description\": \"Preferred language for localized response text.\"\n  }\n}\n```\n\nThen the adapter maps it to:\n\n```\nAccept-Language: en\n```\n\nThe schema should describe the product-level input, not force the agent to think in low-level HTTP details when a cleaner field works.\n\nFor create and update operations, the request body often becomes the largest part of the tool schema.\n\nFrom the `PATCH /tasks/{task_id}`\n\nexample, the request body allows:\n\n`title`\n\n;`status`\n\n;`assignee_id`\n\n;`due_date`\n\n.The combined MCP input schema can include path, query, and body fields together:\n\n```\n{\n  \"name\": \"update_task\",\n  \"description\": \"Update the title, status, assignee, or due date for one task. Use this only after the user has identified the workspace, project, task, and requested change.\",\n  \"inputSchema\": {\n    \"type\": \"object\",\n    \"properties\": {\n      \"workspace_id\": {\n        \"type\": \"string\",\n        \"description\": \"The workspace that contains the project.\"\n      },\n      \"project_id\": {\n        \"type\": \"string\",\n        \"description\": \"The project that contains the task.\"\n      },\n      \"task_id\": {\n        \"type\": \"string\",\n        \"description\": \"The task to update.\"\n      },\n      \"title\": {\n        \"type\": \"string\",\n        \"description\": \"New task title.\"\n      },\n      \"status\": {\n        \"type\": \"string\",\n        \"enum\": [\"todo\", \"in_progress\", \"blocked\", \"done\"],\n        \"description\": \"New task status.\"\n      },\n      \"assignee_id\": {\n        \"type\": \"string\",\n        \"description\": \"User ID of the new assignee.\"\n      },\n      \"due_date\": {\n        \"type\": \"string\",\n        \"format\": \"date\",\n        \"description\": \"New due date in YYYY-MM-DD format.\"\n      },\n      \"notify_assignee\": {\n        \"type\": \"boolean\",\n        \"default\": false,\n        \"description\": \"Whether to notify the assignee after the update.\"\n      }\n    },\n    \"required\": [\"workspace_id\", \"project_id\", \"task_id\"]\n  }\n}\n```\n\nNotice that the body fields are optional in the schema because this is a patch operation. But the API should still reject a request that updates nothing. You can express that with validation logic if the schema format you use cannot represent it cleanly.\n\nFor example:\n\n``` js\nconst updateFields = [\"title\", \"status\", \"assignee_id\", \"due_date\"];\n\nif (!updateFields.some((field) => input[field] !== undefined)) {\n  throw new Error(\"Provide at least one task field to update.\");\n}\n```\n\nThe tool should guide the model toward valid updates without making every field required.\n\nNaming conflicts happen often when you combine path, query, header, and body inputs into one schema.\n\nExample:\n\n```\nPATCH /projects/{id}\n```\n\nRequest body:\n\n```\n{\n  \"id\": \"external-project-id\",\n  \"name\": \"New project name\"\n}\n```\n\nNow there are two `id`\n\nvalues:\n\n`id`\n\n, which identifies the project being updated;`id`\n\n, which might represent an external ID or imported ID.Do not expose both as `id`\n\n.\n\nUse names that preserve meaning:\n\n```\n{\n  \"project_id\": \"prj_123\",\n  \"external_project_id\": \"ext_999\",\n  \"name\": \"New project name\"\n}\n```\n\nOther common conflicts:\n\n`user_id`\n\nin both path and body;`status`\n\nin query and body;`version`\n\nin header and body;`limit`\n\nin query and nested request body;`id`\n\nfields inside nested objects.When in doubt, name the field by its role in the operation. The model should know whether it is selecting a resource, filtering a result, updating a value, or controlling request behavior.\n\nAfter the AI client sends the MCP tool input, the handler maps it back to the API request.\n\nFor `update_task`\n\n, the handler might do this:\n\n```\nasync function updateTaskTool(input, auth) {\n  validateUpdateTaskInput(input);\n\n  const url = new URL(\n    `/workspaces/${input.workspace_id}/projects/${input.project_id}/tasks/${input.task_id}`,\n    API_BASE_URL\n  );\n\n  if (input.notify_assignee !== undefined) {\n    url.searchParams.set(\"notify_assignee\", String(input.notify_assignee));\n  }\n\n  const body = {};\n\n  for (const field of [\"title\", \"status\", \"assignee_id\", \"due_date\"]) {\n    if (input[field] !== undefined) {\n      body[field] = input[field];\n    }\n  }\n\n  const response = await fetch(url, {\n    method: \"PATCH\",\n    headers: {\n      Authorization: `Bearer ${auth.accessToken}`,\n      \"Content-Type\": \"application/json\",\n      Accept: \"application/json\"\n    },\n    body: JSON.stringify(body),\n    signal: AbortSignal.timeout(10000)\n  });\n\n  return mapTaskResponse(response);\n}\n```\n\nThis keeps the direction clear:\n\nAvoid handlers that accept arbitrary paths, methods, headers, or bodies from the model. That turns a structured MCP tool back into a generic API proxy.\n\nValidation should happen before the adapter sends the API request.\n\nAt minimum, check:\n\nSome validation belongs in the MCP schema. Some belongs in code. Some still belongs in the upstream API.\n\nThe upstream API remains the final enforcement point for business rules. The MCP layer should prevent obvious bad calls and make errors easier to understand, but it should not replace the API's authorization and data validation.\n\nFor a successful update, the API might return:\n\n```\n{\n  \"id\": \"tsk_789\",\n  \"status\": \"blocked\",\n  \"title\": \"Fix webhook retries\",\n  \"assignee_id\": \"usr_456\",\n  \"updated_at\": \"2026-08-26T10:00:00Z\"\n}\n```\n\nThe MCP tool result should preserve the useful fields:\n\n```\n{\n  \"task_id\": \"tsk_789\",\n  \"status\": \"blocked\",\n  \"title\": \"Fix webhook retries\",\n  \"assignee_id\": \"usr_456\",\n  \"updated_at\": \"2026-08-26T10:00:00Z\"\n}\n```\n\nFor errors, keep the categories distinct:\n\n`400`\n\nmeans the request was invalid;`401`\n\nmeans authentication failed;`403`\n\nmeans the caller lacks permission;`404`\n\nmeans the resource was not found;`409`\n\nmay mean a version or state conflict;`429`\n\nmeans the API rate limit was hit;`5xx`\n\nmeans the upstream API failed.Do not turn every error into \"Tool failed.\" The agent and the developer both need the failure to say what kind of problem happened.\n\nTest each input location separately.\n\nFor path parameters:\n\n`workspace_id`\n\n;`project_id`\n\nwith an invalid `task_id`\n\n;For query parameters:\n\n`notify_assignee: true`\n\n;`\"yes\"`\n\ninstead of `true`\n\n;For headers:\n\nFor request bodies:\n\nFor responses:\n\nTesting should answer a larger question than \"does the API return 200?\" An AI client has to discover the tool, send valid structured input, get a useful result, and understand failures.\n\nWith 0mcp, the same mapping starts from a supported API definition or Postman collection.\n\nThe hosted workflow is:\n\n0mcp currently supports hosted Streamable HTTP servers, not local `stdio`\n\nservers. The original API remains responsible for business logic, authorization, pagination, rate limits, and data validation.\n\nIf your OpenAPI contract has weak parameter definitions, fix the source document first. The [OpenAPI requirements guide](https://0mcp.io/blog/openapi-requirements-for-mcp?utm_source=devto) covers the checks that matter before import.\n\nKeep credentials out of the schema. Use runtime authentication and pass credentials to the upstream API from the server side.\n\n`id`\n\nUse `workspace_id`\n\n, `project_id`\n\n, `task_id`\n\n, and similar names when the hierarchy matters. The AI client should not guess which ID goes where.\n\nFor update operations, identifiers are usually required, but editable fields may be optional. Add validation that requires at least one change instead of requiring every possible update field.\n\nList and search tools need limits, cursors, allowed filters, and clear defaults. An unbounded query tool is hard to test and easy to misuse.\n\nMap authentication, authorization, validation, not-found, conflict, rate-limit, timeout, and upstream errors separately. Vague failures slow down debugging.\n\nBefore publishing a parameter-mapped MCP tool, check:\n\nGood MCP tool schemas do not make the AI client think in raw HTTP. They give the client a clear set of product-level inputs, then let the adapter place each value in the correct part of the API request.", "url": "https://wpnews.pro/news/mapping-api-path-query-header-and-body-parameters-to-mcp-tool-schemas", "canonical_source": "https://dev.to/bhavyshekhaliya/mapping-api-path-query-header-and-body-parameters-to-mcp-tool-schemas-48k5", "published_at": "2026-08-29 03:57:47+00:00", "updated_at": "2026-08-29 04:18:18.426074+00:00", "lang": "en", "topics": ["developer-tools"], "entities": [], "alternates": {"html": "https://wpnews.pro/news/mapping-api-path-query-header-and-body-parameters-to-mcp-tool-schemas", "markdown": "https://wpnews.pro/news/mapping-api-path-query-header-and-body-parameters-to-mcp-tool-schemas.md", "text": "https://wpnews.pro/news/mapping-api-path-query-header-and-body-parameters-to-mcp-tool-schemas.txt", "jsonld": "https://wpnews.pro/news/mapping-api-path-query-header-and-body-parameters-to-mcp-tool-schemas.jsonld"}}