{"slug": "turn-an-existing-rest-api-into-an-mcp-server-without-writing-a-single-wrapper", "title": "Turn an Existing REST API into an MCP Server Without Writing a Single Wrapper", "summary": "A developer outlined a method for turning an existing OpenAPI-described REST API into an MCP server through generation rather than hand-written wrappers, so each API operation becomes a discoverable agent tool that calls the existing service instead of reimplementing its logic. The approach keeps the HTTP API as the system of record, letting OpenAPI changes regenerate both human-facing docs and agent-facing tools from one contract. The writeup recommends exposing read operations first and gating writes behind explicit user confirmation, least-privilege scopes and audit logging, noting that a discoverable tool does not mean the caller is authorized to execute it.", "body_md": "You already have working endpoints: order lookup, inventory, shipment\n\ntracking. Now the business wants an AI assistant — \"give it an order number and\n\nit summarizes the order and where it is in transit.\"\n\nThe API is done. The to-do list, somehow, is not:\n\nOne interface, two sources of truth, guaranteed to drift.\n\nIf your API is already described in OpenAPI, every one of those definitions\n\nalready exists. You should not be re-declaring it for agents.\n\nHere is the wrapper people end up writing by hand for a single endpoint:\n\n```\n// hand-maintained, and now it must track the OpenAPI doc forever\nserver.tool(\"get_order\", { orderId: z.string() }, async ({ orderId }) => {\n  const res = await fetch(`${BASE}/orders/${encodeURIComponent(orderId)}`, {\n    headers: { Authorization: `Bearer ${token}` },\n  });\n  return res.json();\n});\n```\n\nMultiply that by forty endpoints, add the inventory and logistics services, and\n\nthen keep the parameter names, nullable fields, enums and error codes in sync\n\nwith the HTTP API by hand. The moment the backend renames `warehouseId` or adds\n\na status, the agent tool lies.\n\nThis is pure transcription. The operation ID, the path parameters, the request\n\nand response schemas, the auth scheme — OpenAPI already carries all of it.\n\nOpenAPI-to-MCP generation turns each operation into a discoverable tool and\n\nreuses the contract you already maintain:\n\n`security` requirements carry over.\nConceptually, `GET /orders/{orderId}` becomes a tool the agent can discover:\n\n```\n{\n  \"name\": \"getOrder\",\n  \"description\": \"Fetch one order by id, including status and line items.\",\n  \"inputSchema\": {\n    \"type\": \"object\",\n    \"properties\": { \"orderId\": { \"type\": \"string\" } },\n    \"required\": [\"orderId\"]\n  }\n}\n```\n\nThe generated server **calls your existing service**. It doesn't reimplement\n\norder logic, hold a copy of the data, or stand up a new backend. Your API stays\n\nthe system of record; MCP is just another entry point to it. Change a field in\n\nOpenAPI, regenerate, and the human-facing docs and the agent-facing tools move\n\ntogether because they are built from the same file.\n\nResist the urge to expose everything on day one. For the customer-support\n\nassistant, open the **read** operations first — order lookup and shipment\n\ntracking — and let the agent answer real questions:\n\n\"Has this order shipped?\" → call `getOrder`, then `getShipment`, answer from\n\nthe actual responses.\n\nThis sequencing is practical, not cautious theater. It lets you verify the\n\nthings that actually break first:\n\nWrites — cancel order, issue refund, adjust inventory — are a different risk\n\nclass. Gate them behind explicit user confirmation, least-privilege scopes, and\n\naudit logging, and expose them only after the read path is proven.\n\nThis is the single most important security note in this whole setup: **a tool being discoverable does not mean the caller is allowed to execute it.** MCP\n\nA useful mental model: the OpenAPI-to-MCP layer is a typed, discoverable proxy\n\nin front of endpoints that keep enforcing every rule they enforce today.\n\nThe same generated server fits two deployment shapes:\n\n| Need | Shape | When you use it | \n|---|---|---|\n| Local agent / dev machine | stdio or local HTTP MCP server | You're wiring an agent to services on your own machine | \n| Remote agents and partners | Hosted MCP endpoint over HTTP | External agents or teammates need stable access without your laptop | \n| Humans, in parallel | Published API docs from the same spec | A developer wants to read, not call through an agent | \n\nHosting can also carry the documentation, so a partner gets a readable spec and\n\na callable MCP endpoint from one published version. Publish a **versioned snapshot** rather than your working draft: internal, half-built operations\n\nTeams hit confusion here because \"MCP for APIs\" shows up in two distinct\n\nmoments, and they answer different questions:\n\n|  | Development-time MCP | Runtime MCP (this article) | \n|---|---|---|\n| Who calls it | Your AI coding assistant | An end-user-facing AI agent | \n| What it does | Reads the contract, mocks and tests while you build | Calls the **running** API to get work done | \n| Answers | \"How should this endpoint be implemented and checked?\" | \"Call this endpoint and return the result\" | \n| Feeds on | The evolving local spec | A published, authenticated service | \n\nYou can use both against the same OpenAPI file; they just sit on opposite sides\n\nof \"the API exists.\"\n\nYou don't need an agent framework rewrite to start:\n\nThe consumers of an API used to be front ends, mobile apps and other services.\n\nAgents are now on that list — and they need the same contract, not a parallel,\n\nhand-written shadow of it.\n\nYou can generate an MCP server from an OpenAPI spec and run it locally or host\n\nit alongside your docs in the free web app at\n\n[powerduck.com/app](https://www.powerduck.com/app).\n\nIf you've already hand-rolled agent wrappers around a REST API: how did you keep\n\nthe tool schemas from drifting from the real endpoints? I'd love to hear the\n\napproach (and the war stories) in the comments.", "url": "https://wpnews.pro/news/turn-an-existing-rest-api-into-an-mcp-server-without-writing-a-single-wrapper", "canonical_source": "https://dev.to/jeff_pdc/turn-an-existing-rest-api-into-an-mcp-server-without-writing-a-single-wrapper-556b", "published_at": "2026-10-09 07:40:36+00:00", "updated_at": "2026-10-09 07:51:37.156321+00:00", "lang": "en", "topics": ["ai-agents", "agent-protocols", "developer-tools", "ai-tools"], "entities": ["OpenAPI", "MCP"], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/turn-an-existing-rest-api-into-an-mcp-server-without-writing-a-single-wrapper", "markdown": "https://wpnews.pro/news/turn-an-existing-rest-api-into-an-mcp-server-without-writing-a-single-wrapper.md", "text": "https://wpnews.pro/news/turn-an-existing-rest-api-into-an-mcp-server-without-writing-a-single-wrapper.txt", "jsonld": "https://wpnews.pro/news/turn-an-existing-rest-api-into-an-mcp-server-without-writing-a-single-wrapper.jsonld"}}