[Blog](https://cohesivity.ai/blog)

MCP

Published Jul 7, 2026·Updated Aug 30, 2026

![](https://cohesivity.ai/authors/shouryamaan.webp)![](https://cohesivity.ai/authors/arag.webp)[Shouryamaan](https://www.linkedin.com/in/shouryamaanjain/) and [Arag](https://www.linkedin.com/in/aragagrawal/)

# How to Turn Any REST API Into an MCP Server

[Read as Markdown](https://cohesivity.ai/blog/how-to-turn-any-rest-api-into-an-mcp-server.md)

REST

Adapter

MCP tool

Agent

You can turn a REST API into an MCP server by placing a small translation layer in front of the API. That layer registers task-level tools, validates their inputs, calls the existing endpoints, and returns bounded, structured results. The REST API and its data model do not need to change.

Start with one read-only task. Do not generate a tool for every endpoint until you have checked whether those endpoint boundaries make sense to an agent.

## Start with a task, not an endpoint list

An OpenAPI document is useful inventory, but a mechanical one-endpoint-to-one-tool conversion often produces a noisy toolset. An agent trying to answer "Where is order 1842?" needs an order-status action. It does not need to reason across separate tools for an internal lookup, shipment fetch, and status-code translation.

Write down the task, the minimum inputs needed to complete it, and the smallest useful result. Then map that contract to one or more REST calls inside the handler. The companion guide on [designing reliable MCP tools](https://cohesivity.ai/blog/how-to-design-mcp-tools-agents-can-use-reliably) covers naming and tool boundaries in more detail.

## Register a typed MCP tool

The current MCP TypeScript SDK exposes `McpServer`, `registerTool`, and `createMcpHandler`. This example wraps `GET /orders/:id` as a read-only `get-order-status` tool.

```ts
import { createMcpHandler, McpServer } from '@modelcontextprotocol/server';
import * as z from 'zod/v4';

const OrderStatus = z.object({
  orderId: z.string(),
  status: z.enum(['processing', 'shipped', 'delivered', 'cancelled']),
  updatedAt: z.string(),
});

const handler = createMcpHandler(({ authInfo }) => {
  const server = new McpServer({ name: 'orders', version: '1.0.0' });

  server.registerTool(
    'get-order-status',
    {
      description: 'Get the current status of one order the caller can access.',
      inputSchema: z.object({
        orderId: z.string().min(1).describe('The order identifier, such as 1842'),
      }),
      outputSchema: OrderStatus,
    },
    async ({ orderId }) => {
      const response = await fetch(
        `${process.env.ORDERS_API_URL}/orders/${encodeURIComponent(orderId)}`,
        {
          headers: {
            Authorization: `Bearer ${process.env.ORDERS_API_TOKEN}`,
            'X-Caller-ID': authInfo?.clientId ?? 'unknown',
          },
        },
      );

      if (!response.ok) {
        return {
          isError: true,
          content: [{
            type: 'text',
            text: `Order lookup failed with HTTP ${response.status}`,
          }],
        };
      }

      const order = OrderStatus.parse(await response.json());
      return {
        structuredContent: order,
        content: [{ type: 'text', text: JSON.stringify(order) }],
      };
    },
  );

  return server;
});

export default handler;

```

The output schema gives the client a stable result shape. The text copy preserves compatibility with clients that have not adopted structured tool results. The [MCP tool specification](https://modelcontextprotocol.io/specification/2025-06-18/server/tools) recommends returning both when structured content is present.

## Put authentication in front of the handler

`createMcpHandler` does not verify a bearer token for you. Verify the caller before the request reaches the handler, then pass the verified identity through `authInfo`. The upstream REST service must still authorize that identity or a narrower service credential. A caller ID header is useful for attribution, but it is not authorization on its own.

Keep provider tokens on the server. Do not put them in a tool argument, tool result, prompt, or client-visible error. For write tools, scope the credential to the allowed action and require approval for destructive or financial operations. The [MCP infrastructure security guide](https://cohesivity.ai/blog/how-ai-agents-safely-manage-cloud-infrastructure-through-mcp) explains the permission boundary.

## Return errors an agent can act on

An HTTP status alone rarely tells an agent what to do next. Translate known failures into short, stable reasons:

| REST failure         | MCP result                      | Agent action                         |
| -------------------- | ------------------------------- | ------------------------------------ |
| 400 invalid order ID | invalid\_order\_id              | Correct the argument before retrying |
| 401 or 403           | not\_authorized                 | Stop and request a different scope   |
| 404                  | order\_not\_found               | Check the ID or ask the user         |
| 429                  | rate\_limited with a retry time | Wait once, then retry                |
| 5xx                  | upstream\_unavailable           | Retry only if the operation is safe  |

For mutations, accept an idempotency key and forward it to the REST API. A retry wrapper cannot make a non-idempotent endpoint safe after the server has completed a write but the response was lost.

## Test the tool through an MCP client

Test the registered contract rather than calling the REST handler directly. Cover one successful call and the failure classes that change agent behavior:

- missing or invalid input is rejected by the schema;
- one caller cannot retrieve another caller's order;
- upstream timeouts return a bounded error without secrets;
- repeated writes with one idempotency key produce one effect;
- destructive actions stop at an approval boundary.

The SDK's [HTTP serving guide](https://github.com/modelcontextprotocol/typescript-sdk/blob/main/docs/serving/http.md) also requires Host and Origin validation for local servers to prevent DNS rebinding. Its framework factories enable those guards on localhost; a plain Node server must add them explicitly.

## Validate one read-only tool before deployment

Choose an endpoint that answers one common user question. Define its input and output schemas, call it through a real MCP client, and inspect the complete tool result. Deploy that tool only after its authorization and failure tests pass. Add another tool when an observed task cannot be completed cleanly with the first contract.

If the first tool survives those checks, the prompt below hands deployment to your agent. One tool is plenty for today.

or send it to[Claude Code](https://claude.ai/new?q=Turn+the+most-used+read-only+REST+endpoint+in+this+project+into+one+MCP+tool+using+the+current+TypeScript+SDK.+Validate+its+input+and+output+schemas%2C+enforce+caller+authorization+in+the+downstream+API%2C+return+bounded+errors%2C+and+test+it+through+an+MCP+client.+Then+run+npx+%40cohesivity%2Finit%2C+provision+Cohesivity%27s+cloudflare-workers+offering%2C+deploy+the+handler%2C+and+return+the+public+MCP+endpoint.+Do+not+expose+provider+credentials+or+add+write+tools. "Send to Claude")[Cursor](https://cursor.com/link/prompt?text=Turn+the+most-used+read-only+REST+endpoint+in+this+project+into+one+MCP+tool+using+the+current+TypeScript+SDK.+Validate+its+input+and+output+schemas%2C+enforce+caller+authorization+in+the+downstream+API%2C+return+bounded+errors%2C+and+test+it+through+an+MCP+client.+Then+run+npx+%40cohesivity%2Finit%2C+provision+Cohesivity%27s+cloudflare-workers+offering%2C+deploy+the+handler%2C+and+return+the+public+MCP+endpoint.+Do+not+expose+provider+credentials+or+add+write+tools. "Send to Cursor")[Codex](https://chatgpt.com/codex?prompt=Turn+the+most-used+read-only+REST+endpoint+in+this+project+into+one+MCP+tool+using+the+current+TypeScript+SDK.+Validate+its+input+and+output+schemas%2C+enforce+caller+authorization+in+the+downstream+API%2C+return+bounded+errors%2C+and+test+it+through+an+MCP+client.+Then+run+npx+%40cohesivity%2Finit%2C+provision+Cohesivity%27s+cloudflare-workers+offering%2C+deploy+the+handler%2C+and+return+the+public+MCP+endpoint.+Do+not+expose+provider+credentials+or+add+write+tools. "Send to Codex")[opencode](https://opencode.ai/?q=Turn+the+most-used+read-only+REST+endpoint+in+this+project+into+one+MCP+tool+using+the+current+TypeScript+SDK.+Validate+its+input+and+output+schemas%2C+enforce+caller+authorization+in+the+downstream+API%2C+return+bounded+errors%2C+and+test+it+through+an+MCP+client.+Then+run+npx+%40cohesivity%2Finit%2C+provision+Cohesivity%27s+cloudflare-workers+offering%2C+deploy+the+handler%2C+and+return+the+public+MCP+endpoint.+Do+not+expose+provider+credentials+or+add+write+tools. "Send to OpenCode")
