{"slug": "mcp-vs-api-the-difference-for-developers", "title": "MCP vs API: The Difference for Developers", "summary": "Jamdesk, a customer support platform, reported that its MCP server enabled Claude Code to search its documentation within two minutes, whereas previously the AI could not answer questions about the API without the docs URL being provided each session. The Model Context Protocol (MCP), introduced by Anthropic in November 2024 and adopted by OpenAI, Google, and major IDEs, wraps APIs to let LLMs call them directly, with servers exposing tools, resources, and prompts over JSON-RPC 2.0.", "body_md": "# MCP vs API: The Difference for Developers\n\nA customer contacted us last month about their Jamdesk docs. Their team had switched to Claude Code, and Claude couldn't answer a single question about their own API. The docs were public and the search endpoint worked, but Claude just didn't know either of those things existed unless their specifically gave the docs URL at the start of every session.\n\nWe told them to add our MCP server to Claude Code's config. Two minutes later Claude was searching their docs and giving back results.\n\nThat gap, between \"the API exists\" and \"the LLM can use it\", is what MCP closes. MCP servers are wrappers around APIs, telling an LLM how to call them.\n\n## What an API is\n\nAn API is a contract between two pieces of software. REST, GraphQL, and gRPC are all APIs, and they primarily differ on the shape of the contract. (We wrote a longer primer on [what an API is](https://www.jamdesk.com/blog/what-is-an-api?utm_source=blog&utm_medium=article&utm_campaign=mcp-vs-api).)\n\nTraditionally, an API was built for humans, though that has changed with AI. A developer read your reference docs, picked an endpoint, and constructed a request. Jamdesk's [docs search API](https://www.jamdesk.com/docs/jamdesk-api/search?utm_source=blog&utm_medium=article&utm_campaign=mcp-vs-api) is an example:\n\n```\ncurl -X POST \"https://acme.jamdesk.app/_api/search\" \\\n  -H \"Authorization: Bearer $TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"query\": \"How do I authenticate?\", \"limit\": 5}'\n{\n  \"results\": [\n    {\n      \"title\": \"Authentication\",\n      \"section\": \"API\",\n      \"slug\": \"api/authentication\",\n      \"content\": \"Every request needs a Bearer token. Generate one in Settings...\",\n      \"url\": \"https://acme.jamdesk.app/api/authentication\",\n      \"score\": 0.95\n    }\n  ],\n  \"query\": \"How do I authenticate?\",\n  \"language\": \"en\",\n  \"total\": 1,\n  \"durationMs\": 72\n}\n```\n\nBack comes a stateless JSON response, readable by a human, with the definition living in your API docs or your OpenAPI spec.\n\nBut wait, can't an AI agent just build against that API? It can, and we'll come back in a minute.\n\n## What MCP is\n\nMCP, the [Model Context Protocol](https://modelcontextprotocol.io/), lets an LLM use your software without a human writing the client code. [Anthropic introduced it in November 2024](https://www.anthropic.com/news/model-context-protocol). OpenAI, Google, and most major IDEs and agent frameworks have adopted it since.\n\nYour server exposes three kinds of primitives:\n\n**Tools** are executable functions the LLM can call (the`searchDocs`\n\nbelow).**Resources** are read-only data the LLM can pull into context: files, database records, whole doc pages.**Prompts** are reusable templates your server offers, like saved recipes the LLM can invoke.\n\nThe LLM's host (Claude Desktop, Cursor, Claude Code, whatever) connects to your server, asks it some version of \"what can you do?\", and gets back a machine-readable menu that the model then reasons about in the middle of a conversation with a human who has never read your docs.\n\nUnderneath it's [JSON-RPC 2.0](https://www.jsonrpc.org/) over either stdio (local) or Streamable HTTP (remote). The same docs search, via MCP:\n\n```\n{\n  \"jsonrpc\": \"2.0\",\n  \"id\": 1,\n  \"method\": \"tools/call\",\n  \"params\": {\n    \"name\": \"searchDocs\",\n    \"arguments\": { \"query\": \"How do I authenticate?\", \"limit\": 5 }\n  }\n}\n```\n\nThis looks like a REST call, right? Structurally it is one. What's different sits upstream of the payload.\n\n## What an MCP server looks like\n\nAn MCP server is mostly a wrapper around an API you already shipped. It doesn't replace your backend, and no part of it talks to a model. It sits in front of endpoints that already exist and describes them in terms a model can act on.\n\nTwo handlers do the real work. The `ListTools`\n\nhandler answers the \"what can you do?\" question from the last section by giving back the menu of available tools. The `CallTool`\n\nhandler answers `tools/call`\n\n, taking the arguments the model composed, doing the work, and returning text. Everything else in the file is setup. Here it is wrapping the Jamdesk search API, in about fifty lines of TypeScript with the [official SDK](https://github.com/modelcontextprotocol/typescript-sdk):\n\n``` js\nimport { Server } from \"@modelcontextprotocol/sdk/server/index.js\";\nimport { StdioServerTransport } from \"@modelcontextprotocol/sdk/server/stdio.js\";\nimport {\n  CallToolRequestSchema,\n  ListToolsRequestSchema,\n} from \"@modelcontextprotocol/sdk/types.js\";\n\nconst server = new Server(\n  { name: \"docs\", version: \"1.0.0\" },\n  { capabilities: { tools: {} } }\n);\n\nserver.setRequestHandler(ListToolsRequestSchema, async () => ({\n  tools: [{\n    name: \"searchDocs\",\n    description:\n      \"Search the documentation for relevant pages, API references, \" +\n      \"and guides. Returns results ranked by relevance. Use this before \" +\n      \"getPage when you don't know the exact page path.\",\n    inputSchema: {\n      type: \"object\",\n      properties: { query: { type: \"string\" }, limit: { type: \"number\" } },\n      required: [\"query\"],\n    },\n  }],\n}));\n\nserver.setRequestHandler(CallToolRequestSchema, async (req) => {\n  // The registry is your product surface: reject anything not in it.\n  if (req.params.name !== \"searchDocs\") {\n    throw new Error(`Unknown tool: ${req.params.name}`);\n  }\n  const { query, limit = 5 } = req.params.arguments as {\n    query: string;\n    limit?: number;\n  };\n  const res = await fetch(\"https://acme.jamdesk.app/_api/search\", {\n    method: \"POST\",\n    headers: {\n      Authorization: `Bearer ${process.env.TOKEN}`,\n      \"Content-Type\": \"application/json\",\n    },\n    body: JSON.stringify({ query, limit }),\n  });\n  if (!res.ok) throw new Error(`Search failed: ${res.status}`);\n  return { content: [{ type: \"text\", text: JSON.stringify(await res.json()) }] };\n});\n\nawait server.connect(new StdioServerTransport());\n```\n\nYou just need to register the tools, handle the call by hitting the REST API you already have, and return the text. Notice how little of it is about AI. It's plumbing around a `fetch`\n\nto a backend that already existed, and the most product-critical line in the whole file is a `description`\n\nstring.\n\nFor more info, see the [official quickstart](https://modelcontextprotocol.io/quickstart/server) guide.\n\n## The core differences\n\n| REST API | MCP | |\n|---|---|---|\nConsumer | Human developers | LLMs and agents |\nDiscovery | A developer reads your docs | The client calls `tools/list` at runtime |\nThe contract | URL, params, and your reference docs | Tool name, description, and JSON Schema |\nProtocol | HTTP + JSON | JSON-RPC 2.0 over stdio or Streamable HTTP |\n\nDiscovery is the big one. With an API, the developer is the discovery mechanism. With MCP, the model is, at runtime:\n\n```\n{ \"jsonrpc\": \"2.0\", \"id\": 2, \"method\": \"tools/list\" }\n{\n  \"result\": {\n    \"tools\": [\n      {\n        \"name\": \"searchDocs\",\n        \"description\": \"Search the documentation for relevant pages, API references, and guides. Returns up to 50 results ranked by relevance.\",\n        \"inputSchema\": {\n          \"type\": \"object\",\n          \"properties\": {\n            \"query\": { \"type\": \"string\", \"description\": \"The search query (e.g., \\\"authentication\\\")\" },\n            \"limit\": { \"type\": \"number\", \"description\": \"Maximum number of results (default: 10, max: 50)\" },\n            \"type\": { \"type\": \"string\", \"enum\": [\"all\", \"api\", \"guide\", \"quickstart\", \"help\", \"component\"] }\n          },\n          \"required\": [\"query\"]\n        }\n      },\n      {\n        \"name\": \"getPage\",\n        \"description\": \"Get the full content of a specific documentation page by its URL path.\",\n        \"inputSchema\": {\n          \"type\": \"object\",\n          \"properties\": {\n            \"slug\": { \"type\": \"string\", \"description\": \"The page path (e.g., \\\"api/authentication\\\")\" }\n          },\n          \"required\": [\"slug\"]\n        }\n      }\n    ]\n  }\n}\n```\n\nThe `inputSchema`\n\ntells a model *how* to call the tool. The `description`\n\ndecides *whether* it calls at all.\n\nThat second job is the one that fails quietly. When a description is vague, nothing throws and nothing lands in your logs, because the call you wanted was never made. The model read your sentence, decided this wasn't the tool it needed, and did something else instead: reached for another tool, guessed at a URL, or answered from memory. The user gets a worse answer and never learns a better one was available.\n\n## The state question just changed\n\nPlenty of blog posts will tell you that MCP is stateful and REST is not. That was always too strong, and now it's wrong.\n\nThe session was real but optional. An MCP session opened with an `initialize`\n\nhandshake, client and server negotiated capabilities and a `protocolVersion`\n\n, and the session could stay open so follow-up calls skipped re-auth and re-discovery. Servers were free to skip all that and run stateless over Streamable HTTP. Ours does.\n\nThat option is now gone. The [2026-07-28 revision](https://blog.modelcontextprotocol.io/posts/2026-07-28/) removed the `initialize`\n\nhandshake and the `Mcp-Session-Id`\n\nheader outright.\n\nThe core is stateless. Any request can land on any server instance, so the sticky routing and shared session stores that horizontal deployments used are gone at the protocol layer. Long-running work moved to a Tasks extension, where a `tools/call`\n\nhands back a task handle the client polls.\n\nThe revision shipped on **July 28, 2026**. If you're writing a server now, write it stateless to save yourself a migration.\n\n## Side by side: same task, both protocols\n\nFetching one specific docs page. Both of these work right now, against our own live docs.\n\n**REST:**\n\n```\ncurl \"https://jamdesk.com/docs/ai/mcp-server.md\"\n```\n\nA developer wrote that. They knew the `.md`\n\nsuffix works on Jamdesk because they read the docs.\n\n**MCP:**\n\n```\n{\n  \"jsonrpc\": \"2.0\",\n  \"id\": 3,\n  \"method\": \"tools/call\",\n  \"params\": {\n    \"name\": \"getPage\",\n    \"arguments\": { \"slug\": \"ai/mcp-server\" }\n  }\n}\n```\n\nA user typed \"how do I set up the MCP server?\" into Claude Code, Claude read `getPage`\n\nout of `tools/list`\n\n, and inferred the argument from the conversation and the schema. The response comes back as a `content`\n\narray the model reads directly.\n\nSame functionality. What differs is who composed the call, and how they knew what to put in it.\n\n## But can't an agent just call my API?\n\nYes. Point a coding agent at your OpenAPI spec and it will write you a client, and for a one-off job that is usually the right answer.\n\nThe difference is who does the setup, and how many times. With a REST API, somebody finds the docs, configures credentials, and writes the glue, and then somebody does it again for the next tool and the next user. An MCP server does that work once, on your side, and every client that connects inherits it. Same endpoints, same backend. What changes is that discovery happens at runtime instead of in someone's editor.\n\n## When to use which\n\n**Ship a REST or GraphQL API when:**\n\n- Your frontend, mobile app, or customer SDKs consume the data.\n- Callers are deterministic software that already knows what it wants.\n- You need broad ecosystem compatibility (Postman, curl, every HTTP client ever written).\n\n**Ship an MCP server when:**\n\n- Your users want to work with your product through an AI assistant.\n- Your capabilities would earn their place inside someone else's agent workflow.\n- You want to be callable without customers writing an SDK first.\n\n**Ship both when** you have a real product. The REST API does the work. The MCP server exposes a thoughtful subset to LLMs, with descriptions tuned for a model rather than a human. They read from the same backend.\n\nWe see one mistake more than any other: teams treat MCP as a replacement for their API and expose every endpoint as a tool. Don't do that.\n\nWe've watched models get noticeably worse at picking the right tool as the registry grows, and a model staring at 200 of them is a model that guesses. Expose the handful of operations that match how users actually phrase requests.\n\n**Already have an OpenAPI spec?** Open-source generators ([snaggle-ai](https://github.com/snaggle-ai/openapi-mcp-server) and [janwilmake](https://github.com/janwilmake/openapi-mcp-server) both ship one) will convert it into a runnable MCP server, one tool per endpoint. Ship that first. Then rewrite the descriptions of the few tools that matter, by hand, and delete most of the rest.\n\nGenerated descriptions are technically correct and useless. They describe the endpoint, not the job the user is trying to do.\n\nOne gotcha is the model can only paginate if you let it. If your tool wraps an endpoint that returns the first hundred rows and your schema exposes no cursor or offset, the model has no way to ask for row 101, and it will rarely think to mention that the answer was truncated. Our own `searchDocs`\n\ntakes a `limit`\n\nand no offset, which is fine for docs search and would be a bug on a transactions API.\n\n## How we do it at Jamdesk\n\nWe build [Jamdesk](https://www.jamdesk.com/?utm_source=blog&utm_medium=article&utm_campaign=mcp-vs-api), a documentation platform for software teams. Every docs site gets a REST search API at [ /_api/search](https://www.jamdesk.com/docs/jamdesk-api/search?utm_source=blog&utm_medium=article&utm_campaign=mcp-vs-api) and a built-in MCP server at\n\n[. A customer with docs at](https://www.jamdesk.com/docs/ai/mcp-server?utm_source=blog&utm_medium=article&utm_campaign=mcp-vs-api)\n\n`/_mcp`\n\n`acme.jamdesk.app`\n\nruns one command:\n\n```\nclaude mcp add --transport http acme-docs https://acme.jamdesk.app/_mcp\n```\n\nAnd Claude Code can search and read Acme's documentation directly. Our REST endpoint needs an API key and exists for code your team writes, while the MCP server answers the model directly and needs no key at all.\n\nWe expose two tools: `searchDocs`\n\nand `getPage`\n\n. Most of our REST endpoints exist for humans building integrations, and only two operations are useful to an agent answering a question.\n\nThe hardest part of shipping MCP wasn't the protocol. It was the descriptions. \"Search the docs\" wasn't enough: Claude would skip the tool and guess a URL instead.\n\nWhat ships today spells out what gets searched and what comes back: \"Search the documentation for relevant pages, API references, and guides. Returns up to 50 results ranked by relevance.\"\n\n`getPage`\n\nneeded the same treatment, one level down. It takes a slug like `api/authentication`\n\n, not a full URL, and spelling that out in the *field* description is what stopped models from confidently passing `https://acme.jamdesk.app/api/authentication`\n\nand getting nothing back. Field descriptions are part of the contract too.\n\nWriting for a model is still writing. We went deeper on that in [how AI-friendly docs platforms actually are](https://www.jamdesk.com/blog/ai-friendly-docs-platforms-scored?utm_source=blog&utm_medium=article&utm_campaign=mcp-vs-api).\n\n## Summary\n\nAPIs are how software talks to software. MCP is how software talks to LLMs. If developers use your product, you need an API. If you want your product usable inside an AI assistant without customers writing glue code, you need an MCP server too.\n\nOur own docs run on [Jamdesk](https://www.jamdesk.com/?utm_source=blog&utm_medium=article&utm_campaign=mcp-vs-api), so their [MCP server](https://www.jamdesk.com/docs/ai/mcp-server?utm_source=blog&utm_medium=article&utm_campaign=mcp-vs-api) is public. Paste this into a terminal and ask Claude Code something about Jamdesk:\n\n```\nclaude mcp add --transport http jamdesk-docs https://jamdesk.com/docs/_mcp\n```\n\n", "url": "https://wpnews.pro/news/mcp-vs-api-the-difference-for-developers", "canonical_source": "https://www.jamdesk.com/blog/mcp-vs-api", "published_at": "2026-08-03 15:47:27+00:00", "updated_at": "2026-08-03 16:22:52.865282+00:00", "lang": "en", "topics": ["artificial-intelligence", "ai-tools", "ai-agents", "developer-tools"], "entities": ["Jamdesk", "Claude Code", "Anthropic", "OpenAI", "Google", "Model Context Protocol"], "alternates": {"html": "https://wpnews.pro/news/mcp-vs-api-the-difference-for-developers", "markdown": "https://wpnews.pro/news/mcp-vs-api-the-difference-for-developers.md", "text": "https://wpnews.pro/news/mcp-vs-api-the-difference-for-developers.txt", "jsonld": "https://wpnews.pro/news/mcp-vs-api-the-difference-for-developers.jsonld"}}