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
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: []
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_ticketcreate_ticketlist_customer_tickets
It probably should not expose:
delete_ticketbulk_export_ticketsadmin_reassign_all_ticketsdebug_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"
}
}
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"]
}
}
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_idcomes from the path; -
include_commentscomes 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
}
The adapter then constructs the API request:
GET /tickets/tck_123?include_comments=true
Authorization: Bearer <token from runtime auth>
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"
}
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."
}
Compare that with the vague version:
{
"name": "list_customer_tickets",
"description": "Lists tickets."
}
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"
}
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
}
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);
}
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:
- validate
customer_id,title,description, andpriority; - create a JSON request body;
- call
POST /tickets; - forward the runtime credential;
- 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."
}
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."
}
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"
}
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_ticketappears in the tool list; - the description makes it clear when to use the tool;
- missing
ticket_idfails 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_ticketworks with the minimum valid body; - invalid
priorityvalues 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:
- the client chooses
get_ticket; - it sends
ticket_id; - the API authenticates the request;
- the tool returns ticket status;
- 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:
- import a supported Swagger, OpenAPI, or Postman definition;
- review detected operations and validation feedback;
- select the API functions you want to expose;
- create or edit tools, resources, and prompts;
- use API key, Bearer token, or OAuth pass-through;
- test in the Playground;
- use the hosted Streamable HTTP endpoint from an MCP-compatible client;
- 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)