How to Connect a Localhost API to Cursor with an MCP Server

By
Tom Dallimore
Published

You have an API running on localhost. You want Cursor to use it. So you paste the README into the chat, ask the agent to "create a todo for the deploy", and it confidently writes a fetch call to /api/v2/todo-items.
That route does not exist. It never has.
Lovely.
The agent is not being stupid. It is guessing, because the only thing it knows about your API is a blob of text you pasted ten minutes ago. The fix is to stop describing your API to the model and let it call the thing directly. That is what a cursor mcp server is for.
The short version: wrap your localhost API in a small MCP server, register it in Cursor's mcp.json, and the agent gets real tools with real schemas instead of vibes. You can write that server by hand in about 60 lines of Node, or, if your API already has an OpenAPI spec, you can skip the wrapper and point the open-source Fetch Hive MCP gateway at it. I will show both.
If you are new to MCP, start with my guide on how to build an MCP server. If you already know the basics, carry on.
What a Cursor MCP Server Actually Is
The Model Context Protocol (MCP) is a standard way for an AI client to discover and call tools. Cursor is the client. Your MCP server is a small process that tells Cursor "here are the tools I offer, here is what each one expects", and then does the work when the agent calls one.
An MCP server is not a replacement for your API. It is an adapter that sits in front of it. MCP defines how tools are exposed. Your server decides which operations exist and what they are allowed to do.
Cursor talks to MCP servers over three transports:
stdio. Cursor starts your server as a child process and talks to it over stdin and stdout. This is the simplest option for local development, and the one I use in Option 1.
Streamable HTTP. The server runs on its own and Cursor connects to a URL. This is what the gateway in Option 2 uses.
SSE. The older HTTP transport. It still works in Cursor, but new servers should use Streamable HTTP.
The same pattern works for APIs, databases and internal microservices. Once you have connected one localhost service, the next one takes minutes.
Two Ways to Connect a Localhost API to Cursor
Dimension | Option 1: hand-written stdio server | Option 2: Fetch Hive MCP gateway |
|---|---|---|
What you write | A Node.js file with one function per tool | Nothing. You point it at an OpenAPI spec |
Best for | Small APIs, custom logic, a handful of hand-picked tools | APIs that already publish OpenAPI (FastAPI, NestJS, Spring and friends) |
Transport to Cursor | stdio | Streamable HTTP on 127.0.0.1 |
Keeping tools in sync | You update the code when the API changes | Re-run add-spec when the spec changes |
Sharing with a teammate or cloud agent | Hard. It only runs on your machine | Add --tunnel for an authenticated public URL |
Cost | Free | Free and open source (Apache 2.0) |
My rule: if you want three carefully designed tools, write them. If you want the agent to see the API you already documented, do not hand-write 40 wrappers. That is a weekend you will not get back.

Option 1: Write a Small Cursor MCP Server by Hand
We will build the whole thing around one boring example: a todo API on http://127.0.0.1:5001 with GET /todos and POST /todos. Boring is good. It is representative of most internal APIs, and nothing about it will distract you from the MCP bits. Full code for the API and the cursor MCP server included.
You need Node 20 or later (it ships with a built-in fetch, so no extra HTTP library), a current version of Cursor, and a terminal you are not scared of. This works the same on macOS, Windows and Linux, with a couple of path gotchas I cover below.
Step 1: Run the Localhost API
If you already have an API, skip this. Otherwise, here is a minimal Express app to play with:
Note the '127.0.0.1' in app.listen. Without it, the API listens on every network interface, which means anyone on the café Wi-Fi can create your todos. Run it with node api.js (with "type": "module" in its package.json and express installed) and check that http://127.0.0.1:5001/health returns {"status":"ok"}.
Step 2: Build the MCP Server
Make a separate folder for the server. The package.json:
Zod is a real dependency here, not an optional extra. The SDK uses it to turn your argument definitions into the JSON Schema that Cursor sees. Run npm install, then create index.js:
A few things worth pointing out, because the Surfer-style tutorials tend to skip them:
registerTool is the current API. Older examples use server.tool(...). It still exists but is deprecated in the 1.x SDK.
Tool names use underscores. get_todos, not getTodos or todos.get. Some model providers reject dots in tool names, so underscores are the safe choice.
The descriptions are prompts. The agent picks tools based on them. "Only use this when the user asks to add a todo" stops it creating todos because it got excited.
Errors come back as tool results. If the API is down, the agent sees "Could not reach http://127.0.0.1:5001" and can tell you, instead of the tool call hanging until you give up.
The log line goes to stderr. More on why in the troubleshooting section. It matters a lot.
Step 3: Register It in mcp.json
Cursor reads MCP config from two places: ~/.cursor/mcp.json for servers you want in every project, and .cursor/mcp.json in a repo root for servers that belong to that project. For a localhost API tied to one codebase, I would use the project file and commit it.
That is the whole config: a type, a command, some args, and an env block. Every stdio server you add to Cursor follows the same shape.
Use an absolute path in args. A relative path is the most common reason a config fails, because Cursor does not start your server from the folder you think it does. If you commit the file to a repo, Cursor's ${workspaceFolder} variable keeps it portable: "args": ["${workspaceFolder}/tools/local-todo-mcp/index.js"].
Paths per OS:
OS | Path format | Example |
|---|---|---|
macOS | Forward slashes, absolute | /Users/you/mcp/index.js |
Linux | Forward slashes, absolute | /home/you/mcp/index.js |
Windows | Forward slashes or escaped backslashes | C:/Users/you/mcp/index.js or |
Pass secrets through env, not your shell profile. Cursor is a GUI app, so on macOS it often does not see the variables your .zshrc exports. If your localhost API needs api keys, put them in the env block, point envFile at a .env file, or reference a variable with ${env:TODO_API_KEY}. Just do not paste a real key into an mcp.json you are about to commit.
npx is fine for published servers. You will see plenty of configs that use "command": "npx" with a package name in args. That works. The usual problems are a slow first run while npx downloads the package, and an interactive install prompt if you forget -y. For your own server, node plus an absolute path is simpler and faster.
Step 4: Call the Tools from Cursor
Save the file, open Cursor's MCP settings and check that local-todo-api shows as enabled with two tools. Then, in agent mode, ask: "What is on my todo list?" followed by "Add a todo called Deploy v2."
By default Cursor asks for your approval before it runs an MCP tool. Keep it that way for anything that writes. You can allowlist read-only tools like get_todos later if the prompts get annoying.
Whether you click an add plugin style button in the UI or edit the file by hand, it all ends up in the same place: a named entry in mcp.json.

Option 2: Skip the Wrapper with the Fetch Hive MCP Gateway
WARNING: Shameless self-promotion incoming. But it is relevant, I promise.
Option 1 is great for three tools. It is rubbish for thirty. If your localhost API already publishes an OpenAPI spec (FastAPI does it at /openapi.json for free, and most frameworks have a plugin), you have already written the tool definitions. You just wrote them in a different format.

That is the problem we built the Fetch Hive MCP gateway to solve. It compiles an OpenAPI spec into MCP tools and serves them to Cursor, and it comes in two flavours:
Open source CLI. mcp-gateway is a single Rust binary, Apache 2.0, on GitHub. It runs on your machine, binds to 127.0.0.1 by default, and is the right choice for a localhost API.
Hosted gateway. The same compiler, running on Fetch Hive at mcp.fetchhive.com, with access tokens, rate limits, tool policies, request logs and a playground. It starts on the free Developer plan, no card needed. It is for APIs that are deployed somewhere (staging, production, a shared dev box), because a hosted service cannot reach the localhost on your laptop. Nothing can, without a tunnel.
For a localhost API, here is the open-source route. Install it with Homebrew (brew install Fetch-Hive/tap/mcp-gateway) or npx (npx --yes @fetch-hive/mcp-gateway), then:
Tool names come from each operation's operationId, so list_todos above is whatever your spec calls it. The flags look fussy for a reason: the gateway refuses loopback, private network and plain HTTP targets unless you opt in. That is a sensible default for something that makes HTTP requests on behalf of an AI agent (I wrote about why in my MCP server security guide).
The inspect command prints the fragment to paste into Cursor. It uses the HTTP transport instead of a command and args, so it looks roughly like this:
Two more things it does that you would otherwise build yourself. Before serving, it audits the spec and flags the bits that make bad tools: missing parameter descriptions, open-ended objects, too many required fields. And if a teammate or a cloud agent needs your local API, mcp-gateway serve todos --tunnel gives you a public https://<name>.mcp.fetchhive.com/mcp URL over one outbound connection, still behind the bearer token. No ports opened, and the relay only forwards /mcp.
If you want the full walkthrough of the compile step, my OpenAPI to MCP guide covers it. And to be clear: you do not need Fetch Hive to connect a localhost API to Cursor. Option 1 works perfectly well. The gateway just saves you from writing and maintaining the wrapper.

SKIP THE HAND-WRITTEN WRAPPER
Give Cursor your whole API, not three hand-picked tools.
If your API has an OpenAPI spec, generate its MCP tools in Fetch Hive and review exactly what Cursor can call. Test calls in the playground, then manage access tokens, limits and request logs in one place.
Why Cursor Says "Loading Tools…" Forever
You add the server, Cursor shows a spinner, and nothing happens. Here is the order I check things in:
Run the exact command yourself. Copy the command and args from mcp.json into a terminal. If node /absolute/path/to/index.js crashes, Cursor was never going to work. A stdio server that is working correctly just sits there waiting for input, which looks like nothing is happening. That is correct.
Read Cursor's MCP logs. Open the Output panel (Cmd+Shift+U on macOS, Ctrl+Shift+U on Windows and Linux) and pick "MCP Logs" from the dropdown. A "No server info found" message usually means the initialize handshake never completed.
Check for anything printing to stdout. This is the big one. With stdio, stdout belongs to the protocol. One stray console.log("Server started!") puts non-JSON text into the stream and the handshake falls over. Log to stderr instead.
Test it outside Cursor. The MCP Inspector (npx @modelcontextprotocol/inspector node /absolute/path/to/index.js) lets you list and call tools directly. If it works there and not in Cursor, the problem is your config, not your server.
Check paths and env. Relative paths, a node binary that is not on the PATH Cursor sees (give the full path to node if so), or environment variables that only exist in your shell profile. This is the usual macOS culprit.
Restart the server. After editing mcp.json, toggle the server off and on in Cursor's MCP settings, or restart Cursor.
A simple, timestamped stderr logger saves a lot of guessing:

Connecting Several Localhost APIs Through One Server
A separate MCP server for every internal service gets unwieldy quickly. If you have a users service on http://localhost:6001 and an orders service on http://localhost:6002, one server can route to both with prefixed tool names:
(Same callApi helper as before, with the base URL passed in as the first argument.)
One env variable per target (USERS_API_URL, ORDERS_API_URL) keeps dev and staging configurable without code changes. With the gateway, the equivalent is one add-spec per service.
Then be stingy. Every tool you expose is another option the agent has to choose between, and every schema eats context. Past a few dozen tools across all your servers, tool selection gets noticeably worse, in Cursor and everywhere else. Turn off servers you are not using in Cursor's MCP settings, and do not expose an endpoint just because it exists.
Keep It Safe, Even on Localhost
"It is only localhost" is how a test API ends up with a delete-everything route that an agent can call.
Bind to 127.0.0.1. For the API and for the MCP server. Not 0.0.0.0.
Start read-only. Ship get_todos first. Add write tools once you trust the descriptions.
Make dangerous tools obvious. Anything that deletes records, changes prices or sends an email should say so in its description, and should never be on an auto-run allowlist.
Keep secrets out of committed config. Use envFile or ${env:...}, not a pasted key.
Do not let the agent pick the URL. The base URL comes from env, not from a tool argument. Otherwise you have built an SSRF machine with a chat interface.
If you want to go deeper on any of this, my guide to running a self-hosted MCP server covers internal APIs, and the MCP server security article covers SSRF and credentials properly.
Related Plugins and Related Topics
The stdio-plus-mcp.json pattern is everywhere. Database servers (Postgres, MySQL, Supabase) expose schema inspection and queries as tools. Billing servers let an agent view or describe subscription status and prices for a client. Design-system servers give the agent component docs so it can build UI without you pasting documentation. Same command, args and env shape every time.
Surfer also insists I mention "column headers with buttons", "topic list column headers" and "buttons are sortable" here. I have no idea which version of Cursor's UI those came from. I am keeping them for the robots.
Practical Checklist
Do more of this:
Bind the localhost API to 127.0.0.1 and check /health before touching Cursor.
Use registerTool, underscore tool names, and descriptions that say when to use each tool.
Put absolute paths in args, or ${workspaceFolder} in a committed project config.
Pass env variables and api keys through env, envFile or ${env:...}.
Log to stderr, never stdout.
Test with the MCP Inspector (or mcp-gateway test) before blaming Cursor.
Use the gateway when an OpenAPI spec already exists, and hand-write tools when you need custom logic.
Avoid this:
Relative paths in mcp.json.
console.log in a stdio server.
Exposing every endpoint because you can.
Letting tool arguments decide which host gets called.
Auto-running tools that write, delete or spend money.
The Takeaway
Connecting a localhost API to Cursor is not hard. It is a thin adapter, one config entry and a few boring rules about paths, stdout and env.
Build the todo example first. Get the handshake working. Then point it at your real services, either with your own cursor MCP server code or with the gateway if a spec already exists.
Once it works, you stop explaining your API to the agent and let it read the real thing. That is the whole point.
Share this post



