DEV Community

Bhavy Shekhaliya
Bhavy Shekhaliya

Posted on

How to Create an MCP Server for an API: From Operation Mapping to Tool Calls

If you already have a working API, you are most of the way toward an MCP server.

The API already knows how to create records, fetch data, enforce permissions, validate inputs, and return responses. The MCP server adds a different interface on top: tools an AI client can discover and call with structured arguments.

In this tutorial, I will walk through the practical mapping:

  • API endpoint to MCP tool
  • path, query, and body parameters to tool input schema
  • API authentication to runtime credential handling
  • API response to MCP tool result
  • API errors to useful tool errors
  • local or hosted MCP testing before production

I will use a small support-ticket API as the example because it is easy to understand and still covers the parts that matter.


The example API

Imagine your SaaS product has these API operations:

GET  /tickets/{ticket_id}
POST /tickets
GET  /customers/{customer_id}/tickets
Enter fullscreen mode Exit fullscreen mode

The user workflows are:

  • get the current status of one ticket;
  • create a new support ticket;
  • list tickets for a customer.

A basic OpenAPI-style summary might look like this:

paths:
  /tickets/{ticket_id}:
    get:
      operationId: getTicket
      summary: Get one support ticket
      parameters:
        - name: ticket_id
          in: path
          required: true
          schema:
            type: string
        - name: include_comments
          in: query
          required: false
          schema:
            type: boolean
      security:
        - bearerAuth: []

  /tickets:
    post:
      operationId: createTicket
      summary: Create a support ticket
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - customer_id
                - title
                - description
              properties:
                customer_id:
                  type: string
                title: ""
                  type: string
                description: ""
                  type: string
                priority:
                  type: string
                  enum: [low, normal, high]
      security:
        - bearerAuth: []
Enter fullscreen mode Exit fullscreen mode

This gives us enough to map real API behavior into tools.


Step 1: choose the operations that should become tools

Do not start by exposing the full API.

Start with one workflow and select the operations needed for that workflow. For the support example, the first MCP server might expose:

  • get_ticket
  • create_ticket
  • list_customer_tickets

It probably should not expose:

  • delete_ticket
  • bulk_export_tickets
  • admin_reassign_all_tickets
  • debug_ticket_index
  • login or token endpoints

This selection is part of the product design. An AI client has to choose from the tools you expose. A smaller, clearer tool list is easier to use than a huge list of overlapping endpoints.


Step 2: turn each API operation into a clear MCP tool

An MCP tool needs a name, description, and input schema. The API route is only the starting point.

For GET /tickets/{ticket_id}, a weak tool would look like this:

{
  "name": "getTicket",
  "description": "Get ticket",
  "inputSchema": {
    "type": "object"
  }
}
Enter fullscreen mode Exit fullscreen mode

The route is recognizable to a developer, but the AI client still has very little to work with.

A better tool is more explicit:

{
  "name": "get_ticket",
  "description": "Return the status, priority, requester, and latest update for one support ticket. Use this when the user already knows the ticket ID.",
  "inputSchema": {
    "type": "object",
    "properties": {
      "ticket_id": {
        "type": "string",
        "description": "The ID of the support ticket to retrieve."
      },
      "include_comments": {
        "type": "boolean",
        "description": "Whether to include recent ticket comments in the response.",
        "default": false
      }
    },
    "required": ["ticket_id"]
  }
}
Enter fullscreen mode Exit fullscreen mode

This gives the AI client enough information to choose the tool and build a valid call.


Step 3: map parameters into one input schema

HTTP APIs split inputs across different locations:

  • path parameters;
  • query parameters;
  • headers;
  • request bodies;
  • sometimes cookies or form fields.

An MCP tool should present the useful business inputs as one schema.

For GET /tickets/{ticket_id}?include_comments=true, the mapping is:

  • ticket_id comes from the path;
  • include_comments comes from the query string;
  • the Bearer token comes from authentication, not the tool input.

The tool input might be:

{
  "ticket_id": "tck_123",
  "include_comments": true
}
Enter fullscreen mode Exit fullscreen mode

The adapter then constructs the API request:

GET /tickets/tck_123?include_comments=true
Authorization: Bearer <token from runtime auth>
Enter fullscreen mode Exit fullscreen mode

For POST /tickets, the request body becomes the tool input:

{
  "customer_id": "cus_456",
  "title": "Cannot access dashboard",
  "description": "The user sees a 403 after logging in.",
  "priority": "high"
}
Enter fullscreen mode Exit fullscreen mode

Preserve API constraints when you build the schema. If priority only accepts low, normal, or high, keep that enum. If customer_id, title, and description are required, mark them required. If the API expects a date in ISO format, say so.

Bad schemas make the model guess. Good schemas reduce invalid calls.


Step 4: write descriptions that help tool selection

Descriptions are not decoration. They affect which tool the AI client chooses.

For similar tools, the description should explain the difference:

{
  "name": "list_customer_tickets",
  "description": "List support tickets for one customer. Use this when the user knows the customer ID and wants to review several tickets. For one known ticket ID, use get_ticket."
}
Enter fullscreen mode Exit fullscreen mode

Compare that with the vague version:

{
  "name": "list_customer_tickets",
  "description": "Lists tickets."
}
Enter fullscreen mode Exit fullscreen mode

A useful tool description often includes:

  • what the tool does;
  • when to use it;
  • what input must already be known;
  • whether it reads or changes data;
  • what it returns;
  • when another tool is a better fit.

The API endpoint tells you what route to call. The tool description tells the AI client why and when to call it.


Step 5: keep authentication out of normal tool inputs

Your API might use API keys, Bearer tokens, or OAuth.

Those credentials should not become ordinary tool arguments like this:

{
  "ticket_id": "tck_123",
  "api_key": "secret-key-here"
}
Enter fullscreen mode Exit fullscreen mode

That is a bad pattern. It makes credentials model-visible and easy to leak into prompts, logs, examples, or retries.

Instead, keep the tool input focused on the business operation:

{
  "ticket_id": "tck_123",
  "include_comments": true
}
Enter fullscreen mode Exit fullscreen mode

The MCP server should receive or access credentials through the runtime authentication path, then forward the credential to the original API.

The API still owns authorization. It should check:

  • which user or service identity is calling;
  • which tenant or workspace the caller belongs to;
  • which role or scope the caller has;
  • whether the caller can access the requested record;
  • whether the caller can perform the requested action.

The MCP layer should not become a shortcut around your API's permission model.


Step 6: implement the tool handler

The tool handler connects the MCP call to the API request.

Conceptually, a handler for get_ticket does this:

async function getTicketTool(input, auth) {
  validate(input, {
    required: ["ticket_id"],
    properties: {
      ticket_id: "string",
      include_comments: "boolean"
    }
  });

  const url = new URL(`/tickets/${input.ticket_id}`, API_BASE_URL);

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

  const response = await fetch(url, {
    method: "GET",
    headers: {
      Authorization: `Bearer ${auth.accessToken}`,
      Accept: "application/json"
    },
    signal: AbortSignal.timeout(10000)
  });

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

Keep the handler specific. The tool should call the known route for get_ticket. Avoid a generic handler that accepts any path or method from the model.

For create_ticket, the handler would:

  1. validate customer_id, title, description, and priority;
  2. create a JSON request body;
  3. call POST /tickets;
  4. forward the runtime credential;
  5. map the created ticket into a useful result.

The handler should also treat failures deliberately. A 401 should not look like "no tickets found." A validation error should tell the caller which input is wrong. A timeout should be visible as a timeout, not a generic failure.


Step 7: map API responses into useful tool results

Raw API responses are sometimes fine. But the tool result should help the AI client continue the workflow.

For get_ticket, the API might return:

{
  "id": "tck_123",
  "status": "open",
  "priority": "high",
  "title": "Cannot access dashboard",
  "requester": {
    "id": "usr_789",
    "email": "user@example.com"
  },
  "latest_update": "Customer sees a 403 after login."
}
Enter fullscreen mode Exit fullscreen mode

The MCP tool result should preserve the useful fields and avoid hiding the shape behind a vague string.

For example:

{
  "ticket_id": "tck_123",
  "status": "open",
  "priority": "high",
  "title": "Cannot access dashboard",
  "requester_id": "usr_789",
  "latest_update": "Customer sees a 403 after login."
}
Enter fullscreen mode Exit fullscreen mode

Do not include secrets, internal headers, stack traces, or private debugging fields. If the API returns more data than the AI workflow needs, consider filtering or documenting the output carefully.

For list operations, return pagination metadata when it matters:

{
  "tickets": [
    {
      "ticket_id": "tck_123",
      "status": "open",
      "priority": "high",
      "title": "Cannot access dashboard"
    }
  ],
  "next_cursor": "cursor_abc"
}
Enter fullscreen mode Exit fullscreen mode

The agent can now explain the result and continue if the user asks for more.


Step 8: test the MCP tool path

API tests usually start with a known route and a known payload. MCP testing should also check discovery and selection.

Before production, test these cases:

  • the MCP client can connect to the server;
  • get_ticket appears in the tool list;
  • the description makes it clear when to use the tool;
  • missing ticket_id fails before the API request;
  • invalid input types fail clearly;
  • a valid ticket ID reaches the right API route;
  • a missing ticket returns a clear not-found result;
  • missing credentials produce an authentication error;
  • insufficient permissions produce an authorization error;
  • create_ticket works with the minimum valid body;
  • invalid priority values are rejected or clearly reported;
  • timeouts and rate limits are visible.

Also test the agent workflow:

User: "Find ticket tck_123 and tell me whether it is still open."

The expected behavior is:

  1. the client chooses get_ticket;
  2. it sends ticket_id;
  3. the API authenticates the request;
  4. the tool returns ticket status;
  5. the client answers based on the returned data.

If the agent chooses the wrong tool, the problem might be the name, description, or tool overload. If the tool call fails, the problem might be schema mapping, authentication, authorization, or the upstream API.


Step 9: decide where the MCP server runs

For a prototype, a local server can be enough. For a SaaS product used by customers or remote AI clients, you need a hosted endpoint and an operational plan.

For a self-hosted MCP server, you own:

  • deployment;
  • transport;
  • TLS;
  • authentication handling;
  • secrets and credential rotation;
  • timeouts and retries;
  • logs;
  • metrics;
  • versioning;
  • rollback;
  • client compatibility testing.

For a managed workflow, the goal is to avoid maintaining the MCP infrastructure yourself while still controlling which API capabilities are exposed.

With 0mcp, the hosted workflow is:

  1. import a supported Swagger, OpenAPI, or Postman definition;
  2. review detected operations and validation feedback;
  3. select the API functions you want to expose;
  4. create or edit tools, resources, and prompts;
  5. use API key, Bearer token, or OAuth pass-through;
  6. test in the Playground;
  7. use the hosted Streamable HTTP endpoint from an MCP-compatible client;
  8. review logs, analytics, and configuration versions as the API changes.

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


A practical checklist before launch

Before you share the MCP endpoint, check:

  • The selected tools match real user workflows.
  • Tool names are clear and stable.
  • Descriptions explain when to use each tool.
  • Path, query, and body parameters are mapped into the schema.
  • Required fields, enums, defaults, and formats are accurate.
  • Credentials are passed through runtime auth, not exposed as tool inputs.
  • The upstream API enforces tenant, role, record, and action permissions.
  • Valid, invalid, missing-auth, and forbidden cases have been tested.
  • Tool results include enough data for the AI client to continue.
  • Logs and analytics are available for debugging after launch.
  • API changes have a versioning and rollback path.

If a tool fails several of these checks, fix the API contract or tool configuration before adding more capabilities.


Common mistakes

Exposing every endpoint

More tools can make selection harder. Start with a small workflow and add tools when tests or usage show a need.

Using vague tool names

api_request or manage_ticket forces the AI client to infer too much. Prefer names like get_ticket, create_ticket, and list_customer_tickets.

Putting credentials into the schema

Keep API keys, Bearer tokens, and OAuth credentials out of tool inputs. Pass them through the runtime authentication path.

Ignoring response shape

If the response is poorly documented, the agent may not know what it can safely say or do next. Keep response fields clear and predictable.

Testing only happy paths

Broken auth, invalid inputs, missing records, rate limits, and timeouts are part of production. Test them early.


Wrap up

Creating an MCP server from an API starts with careful mapping.

The endpoint becomes a tool. Parameters become the input schema. Authentication becomes runtime credential handling. The response becomes the tool result. Errors become diagnosable failure paths.

Once that mapping is clear, you can decide whether to build the server yourself or use a hosted workflow. If you want the detailed 0mcp version of this process, start with the guide on how to create an MCP server from an API.

Top comments (0)