DEV Community

Bhavy Shekhaliya
Bhavy Shekhaliya

Posted on

Mapping API Path, Query, Header, and Body Parameters to MCP Tool Schemas

An API operation can receive input from several places.

Path 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.

An MCP tool should give the AI client one clear input schema.

That is the mapping problem:

HTTP API inputs
  path + query + headers + body

become

MCP tool input
  one structured schema the AI client can understand
Enter fullscreen mode Exit fullscreen mode

This 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.


Example API operation

Imagine a project-management API with this endpoint:

PATCH /workspaces/{workspace_id}/projects/{project_id}/tasks/{task_id}
Enter fullscreen mode Exit fullscreen mode

It updates one task.

The API accepts:

  • path parameters for workspace_id, project_id, and task_id;
  • query parameters such as notify_assignee;
  • a request body with the fields to update;
  • authentication through a Bearer token header;
  • an optional request header such as Idempotency-Key.

A shortened OpenAPI-style version might look like this:

paths:
  /workspaces/{workspace_id}/projects/{project_id}/tasks/{task_id}:
    patch:
      operationId: updateTask
      summary: Update a task
      description: "Update the title, status, assignee, or due date for one task."
      parameters:
        - name: workspace_id
          in: path
          required: true
          schema:
            type: string
        - name: project_id
          in: path
          required: true
          schema:
            type: string
        - name: task_id
          in: path
          required: true
          schema:
            type: string
        - name: notify_assignee
          in: query
          required: false
          schema:
            type: boolean
            default: false
        - name: Idempotency-Key
          in: header
          required: false
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                title: ""
                  type: string
                status:
                  type: string
                  enum: [todo, in_progress, blocked, done]
                assignee_id:
                  type: string
                due_date:
                  type: string
                  format: date
              minProperties: 1
      security:
        - bearerAuth: []
Enter fullscreen mode Exit fullscreen mode

The API shape is split across the HTTP request. The MCP tool should present the editable parts in one schema.


Step 1: name the tool from the operation, not the route

The route is useful for the adapter, but it is not a good tool name.

This is weak:

{
  "name": "patch_workspaces_projects_tasks"
}
Enter fullscreen mode Exit fullscreen mode

This is clearer:

{
  "name": "update_task"
}
Enter fullscreen mode Exit fullscreen mode

If your API has several update operations, use the object and action to remove ambiguity:

  • update_task
  • update_task_status
  • assign_task
  • reschedule_task

The right name depends on what the endpoint actually does. If the endpoint updates many fields, update_task may be correct. If the endpoint only changes status, update_task_status is better.


Step 2: bring path parameters into the input schema

Path parameters usually identify the exact resource being addressed. In an MCP tool schema, they are usually required fields.

From the route:

/workspaces/{workspace_id}/projects/{project_id}/tasks/{task_id}
Enter fullscreen mode Exit fullscreen mode

The tool needs:

{
  "workspace_id": "wrk_123",
  "project_id": "prj_456",
  "task_id": "tsk_789"
}
Enter fullscreen mode Exit fullscreen mode

In the MCP tool schema:

{
  "type": "object",
  "properties": {
    "workspace_id": {
      "type": "string",
      "description": "The workspace that contains the project."
    },
    "project_id": {
      "type": "string",
      "description": "The project that contains the task."
    },
    "task_id": {
      "type": "string",
      "description": "The task to update."
    }
  },
  "required": ["workspace_id", "project_id", "task_id"]
}
Enter fullscreen mode Exit fullscreen mode

Keep the path identifiers explicit. Do not collapse them into one generic id field if the API needs all three values. The AI client should not guess which ID belongs to which level.


Step 3: map query parameters as filters and options

Query parameters often change how the operation behaves:

  • filtering;
  • sorting;
  • pagination;
  • flags;
  • optional behavior.

In the example, notify_assignee controls whether the API sends a notification after the update.

That should appear as an optional tool input:

{
  "notify_assignee": {
    "type": "boolean",
    "description": "Whether to notify the assigned user after the task is updated.",
    "default": false
  }
}
Enter fullscreen mode Exit fullscreen mode

For list operations, query parameters may be the main tool inputs:

GET /customers/{customer_id}/tickets?status=open&limit=20&cursor=abc
Enter fullscreen mode Exit fullscreen mode

The MCP schema might expose:

{
  "type": "object",
  "properties": {
    "customer_id": {
      "type": "string",
      "description": "The customer whose tickets should be listed."
    },
    "status": {
      "type": "string",
      "enum": ["open", "pending", "closed"],
      "description": "Optional ticket status filter."
    },
    "limit": {
      "type": "integer",
      "minimum": 1,
      "maximum": 100,
      "default": 20,
      "description": "Maximum number of tickets to return."
    },
    "cursor": {
      "type": "string",
      "description": "Pagination cursor from a previous response."
    }
  },
  "required": ["customer_id"]
}
Enter fullscreen mode Exit fullscreen mode

Good query mapping keeps list tools bounded. If a search endpoint accepts unlimited free-form parameters, the agent may produce slow, broad, or invalid calls.


Step 4: treat authentication headers separately

Headers are tricky because some are normal inputs and some are credentials.

Authentication headers should not become normal tool inputs.

Do not expose this:

{
  "authorization": {
    "type": "string",
    "description": "Bearer token for the API request."
  }
}
Enter fullscreen mode Exit fullscreen mode

That would make the credential model-visible.

Instead, the tool input should stay focused on the business operation:

{
  "workspace_id": "wrk_123",
  "project_id": "prj_456",
  "task_id": "tsk_789",
  "status": "blocked"
}
Enter fullscreen mode Exit fullscreen mode

The adapter or hosted MCP runtime should receive credentials through the authentication path and forward them to the upstream API:

Authorization: Bearer <runtime credential>
Enter fullscreen mode Exit fullscreen mode

The upstream API still enforces identity, tenant, role, record, and action permissions. The MCP schema should not become a place where the model supplies secrets.

0mcp 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.


Step 5: decide what to do with non-auth headers

Some headers are not secrets. They may still matter.

Examples:

  • Idempotency-Key;
  • X-Request-Id;
  • Accept-Language;
  • If-Match;
  • X-Client-Version.

Do not automatically expose every header to the AI client. Ask what role the header plays.

Expose a header as a tool input when:

  • the caller can safely provide it;
  • it changes behavior in a useful way;
  • the value is not secret;
  • the format can be validated;
  • the AI client understands why it exists.

For Idempotency-Key, 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.

For Accept-Language, you might expose language as a business-friendly field rather than the raw HTTP header:

{
  "language": {
    "type": "string",
    "enum": ["en", "es", "fr"],
    "description": "Preferred language for localized response text."
  }
}
Enter fullscreen mode Exit fullscreen mode

Then the adapter maps it to:

Accept-Language: en
Enter fullscreen mode Exit fullscreen mode

The schema should describe the product-level input, not force the agent to think in low-level HTTP details when a cleaner field works.


Step 6: map the request body into structured inputs

For create and update operations, the request body often becomes the largest part of the tool schema.

From the PATCH /tasks/{task_id} example, the request body allows:

  • title;
  • status;
  • assignee_id;
  • due_date.

The combined MCP input schema can include path, query, and body fields together:

{
  "name": "update_task",
  "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.",
  "inputSchema": {
    "type": "object",
    "properties": {
      "workspace_id": {
        "type": "string",
        "description": "The workspace that contains the project."
      },
      "project_id": {
        "type": "string",
        "description": "The project that contains the task."
      },
      "task_id": {
        "type": "string",
        "description": "The task to update."
      },
      "title": {
        "type": "string",
        "description": "New task title."
      },
      "status": {
        "type": "string",
        "enum": ["todo", "in_progress", "blocked", "done"],
        "description": "New task status."
      },
      "assignee_id": {
        "type": "string",
        "description": "User ID of the new assignee."
      },
      "due_date": {
        "type": "string",
        "format": "date",
        "description": "New due date in YYYY-MM-DD format."
      },
      "notify_assignee": {
        "type": "boolean",
        "default": false,
        "description": "Whether to notify the assignee after the update."
      }
    },
    "required": ["workspace_id", "project_id", "task_id"]
  }
}
Enter fullscreen mode Exit fullscreen mode

Notice 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.

For example:

const updateFields = ["title", "status", "assignee_id", "due_date"];

if (!updateFields.some((field) => input[field] !== undefined)) {
  throw new Error("Provide at least one task field to update.");
}
Enter fullscreen mode Exit fullscreen mode

The tool should guide the model toward valid updates without making every field required.


Step 7: handle naming conflicts deliberately

Naming conflicts happen often when you combine path, query, header, and body inputs into one schema.

Example:

PATCH /projects/{id}
Enter fullscreen mode Exit fullscreen mode

Request body:

{
  "id": "external-project-id",
  "name": "New project name"
}
Enter fullscreen mode Exit fullscreen mode

Now there are two id values:

  • path id, which identifies the project being updated;
  • body id, which might represent an external ID or imported ID.

Do not expose both as id.

Use names that preserve meaning:

{
  "project_id": "prj_123",
  "external_project_id": "ext_999",
  "name": "New project name"
}
Enter fullscreen mode Exit fullscreen mode

Other common conflicts:

  • user_id in both path and body;
  • status in query and body;
  • version in header and body;
  • limit in query and nested request body;
  • id fields 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.


Step 8: build the API request from the tool input

After the AI client sends the MCP tool input, the handler maps it back to the API request.

For update_task, the handler might do this:

async function updateTaskTool(input, auth) {
  validateUpdateTaskInput(input);

  const url = new URL(
    `/workspaces/${input.workspace_id}/projects/${input.project_id}/tasks/${input.task_id}`,
    API_BASE_URL
  );

  if (input.notify_assignee !== undefined) {
    url.searchParams.set("notify_assignee", String(input.notify_assignee));
  }

  const body = {};

  for (const field of ["title", "status", "assignee_id", "due_date"]) {
    if (input[field] !== undefined) {
      body[field] = input[field];
    }
  }

  const response = await fetch(url, {
    method: "PATCH",
    headers: {
      Authorization: `Bearer ${auth.accessToken}`,
      "Content-Type": "application/json",
      Accept: "application/json"
    },
    body: JSON.stringify(body),
    signal: AbortSignal.timeout(10000)
  });

  return mapTaskResponse(response);
}
Enter fullscreen mode Exit fullscreen mode

This keeps the direction clear:

  • the tool schema receives product-level inputs;
  • the handler validates those inputs;
  • the handler places each value in the correct HTTP location;
  • authentication comes from runtime context;
  • the API response becomes the tool result.

Avoid handlers that accept arbitrary paths, methods, headers, or bodies from the model. That turns a structured MCP tool back into a generic API proxy.


Step 9: validate before calling the API

Validation should happen before the adapter sends the API request.

At minimum, check:

  • required path identifiers exist;
  • string, boolean, integer, array, and object types are correct;
  • enum values are allowed;
  • date, email, URL, and ID formats match expectations;
  • pagination limits stay within bounds;
  • at least one update field exists for patch operations;
  • body fields do not contain unsupported properties;
  • non-auth headers are safe and well formed;
  • authentication is present in runtime context.

Some validation belongs in the MCP schema. Some belongs in code. Some still belongs in the upstream API.

The 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.


Step 10: map responses and errors clearly

For a successful update, the API might return:

{
  "id": "tsk_789",
  "status": "blocked",
  "title": "Fix webhook retries",
  "assignee_id": "usr_456",
  "updated_at": "2026-08-26T10:00:00Z"
}
Enter fullscreen mode Exit fullscreen mode

The MCP tool result should preserve the useful fields:

{
  "task_id": "tsk_789",
  "status": "blocked",
  "title": "Fix webhook retries",
  "assignee_id": "usr_456",
  "updated_at": "2026-08-26T10:00:00Z"
}
Enter fullscreen mode Exit fullscreen mode

For errors, keep the categories distinct:

  • 400 means the request was invalid;
  • 401 means authentication failed;
  • 403 means the caller lacks permission;
  • 404 means the resource was not found;
  • 409 may mean a version or state conflict;
  • 429 means the API rate limit was hit;
  • 5xx means 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.


Step 11: test the mapping with real tool calls

Test each input location separately.

For path parameters:

  • omit workspace_id;
  • use a valid project_id with an invalid task_id;
  • try a task ID that belongs to another workspace.

For query parameters:

  • omit optional fields;
  • pass notify_assignee: true;
  • pass the wrong type, such as "yes" instead of true;
  • test pagination boundaries on list tools.

For headers:

  • test missing credentials;
  • test expired credentials;
  • test credentials without write permission;
  • test a generated idempotency key if the operation supports it.

For request bodies:

  • update one field;
  • update multiple fields;
  • send an invalid enum;
  • send an unsupported field;
  • send an empty patch body.

For responses:

  • verify the result contains the fields the AI client needs;
  • check empty and missing-resource cases;
  • check rate-limit and timeout behavior;
  • confirm sensitive headers, tokens, and internal debugging fields are not returned.

Testing 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.


How this works in 0mcp

With 0mcp, the same mapping starts from a supported API definition or Postman collection.

The hosted workflow is:

  1. import a Swagger 2.0, OpenAPI 3.0, OpenAPI 3.1, or Postman definition;
  2. review validation warnings and detected operations;
  3. select the API functions that should become AI-facing capabilities;
  4. create or update tools, resources, and prompts;
  5. edit tool names and descriptions where the imported wording needs work;
  6. use API key, Bearer token, or OAuth pass-through at runtime;
  7. test the hosted Streamable HTTP server in the Playground;
  8. review logs, analytics, and configuration versions as the API changes.

0mcp currently supports hosted Streamable HTTP servers, not local stdio servers. The original API remains responsible for business logic, authorization, pagination, rate limits, and data validation.

If your OpenAPI contract has weak parameter definitions, fix the source document first. The OpenAPI requirements guide covers the checks that matter before import.


Common mistakes

Exposing auth headers as tool inputs

Keep credentials out of the schema. Use runtime authentication and pass credentials to the upstream API from the server side.

Collapsing every identifier into id

Use workspace_id, project_id, task_id, and similar names when the hierarchy matters. The AI client should not guess which ID goes where.

Making patch fields required

For 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.

Ignoring query bounds

List and search tools need limits, cursors, allowed filters, and clear defaults. An unbounded query tool is hard to test and easy to misuse.

Returning vague error messages

Map authentication, authorization, validation, not-found, conflict, rate-limit, timeout, and upstream errors separately. Vague failures slow down debugging.


Checklist

Before publishing a parameter-mapped MCP tool, check:

  • path parameters are required and clearly named;
  • query parameters have defaults, enums, bounds, and descriptions;
  • authentication headers stay out of model-visible inputs;
  • safe non-auth headers are either generated server-side or exposed as product-level inputs;
  • body fields preserve required fields, types, formats, enums, and nested objects;
  • naming conflicts are resolved with meaningful field names;
  • validation runs before the API request;
  • API errors map to understandable tool errors;
  • response fields give the AI client enough information to continue;
  • valid, invalid, unauthorized, forbidden, missing-resource, timeout, and rate-limit cases are tested.

Good 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.


Top comments (0)