Search Agentik

CtrlK

ArticlesMCP setup

How to add an MCP server to Cursor

Cursor reads MCP servers from a file called mcp.json, in your home folder or in the project. This piece gives you the exact JSON for both kinds of server, the three places you check that it is live, and a table that maps every failure you will hit to its cause.

Where Cursor keeps mcp.json

There are two files, and they have the same shape. One is ~/.cursor/mcp.json in your home folder, and the server in it is there in every project you open. The other is .cursor/mcp.json in the root of a repo, and it is there only for that repo. Cursor's docs name both. The CLI reads the same two files in the same order: the project one first, then the home one, then any folder above you.

Pick by blast radius, not by taste. A server that reads your own notes or your own mail belongs in the home file. A server that only means something inside one repo, like a local script that knows that repo's schema, belongs in the project file, where a teammate can see it in the diff and say no.

The root key is mcpServers. Under it, each key is the name you will see in chat, and each value is one server. That is the whole file format. Everything else on this page is which fields go in that value.

  • Home file: ~/.cursor/mcp.json. Every project sees it.
  • Project file: .cursor/mcp.json at the repo root. Only that project sees it.
  • Same key in both: the project entry is the one that wins.
  • The file is plain JSON. A trailing comma is enough to make the whole file load as nothing.

stdio or remote: which entry you are about to write

Cursor lists three transports: stdio, SSE, and Streamable HTTP. In practice you are choosing between two worlds. A stdio server is a program on your own machine. Cursor starts it, talks to it down a pipe, and kills it when you quit. A remote server is a URL that someone else is running, and Cursor talks to it over HTTP.

The MCP spec is short about what stdio means. The client starts the server as a subprocess. Both sides send JSON-RPC over stdin and stdout, one message per line, no newline inside a message. The server may write what it likes to stderr, and the client is told not to read stderr as a sign of failure. The rule that breaks the most home-made servers is the next one: the server must not write anything to stdout that is not a valid MCP message. One stray console.log in your own server, and the pipe is poisoned.

The remote side is the newer Streamable HTTP transport, which replaced the old HTTP plus SSE pair. The server gives out one path that answers both POST and GET. The client has to send an Accept header that names both application/json and text/event-stream, because the server is free to answer a single request with either a plain JSON body or an SSE stream. You do not write any of that by hand. It matters only when you are reading a log and trying to work out which side hung up.

So: a tool that has to touch your disk, your git history, or a local port is stdio. A tool that a team shares, that holds an account, and that you would rather not run on every laptop is a URL. Cursor's own table says the same thing in other words: stdio is single user and manual auth, the HTTP kinds are many users and OAuth.

One more rule of thumb, for the cases where both would work. If you would be cross to find that tool on a colleague's laptop, it is a URL. If you would be cross to find your own files on a server, it is stdio. Most of the time you know which way you lean before you know why.

Both choices cost you something and it is worth naming which cost you are taking on. A URL means you are trusting someone else's uptime, someone else's log, and a token that lives in a place you cannot grep. A local server means every laptop has its own copy, its own version, and its own way of being broken on a Tuesday. Pick the failure you would rather be the one to fix.

Add a local stdio server, field by field

Here is a whole ~/.cursor/mcp.json with one stdio server in it. Copy it, change the name and the args, save, and go back to Cursor.

{ "mcpServers": { "notes": { "type": "stdio", "command": "npx", "args": ["-y", "mcp-server-notes"], "env": { "NOTES_DIR": "${userHome}/notes" } } } }

Five fields exist and only two are required. type is stdio. command is the program to run, and Cursor's docs are firm that it has to be on your path or written as a full path. args is an array of strings. env is a map of names to values for that one process. envFile is a path to a dotenv file, and it is the one field here that a remote server cannot use.

Two things go wrong at this step, in our own experience of writing these entries, and both look like the server never existed. The first is command being a shell builtin or an alias. Cursor runs the command, not your shell, so source, a function from your zshrc, and a ~ that you meant the shell to expand will all fail. Write the full path. The second is a version manager. If npx or python only exists after nvm or pyenv has run, the app that Cursor launched from the Dock may not have it. /usr/bin/env in command does not save you either, because the PATH it reads is the one Cursor has, not the one your terminal has.

You can dodge a typed-out path with the variables Cursor fills in for you. It reads them in command, args, env, url and headers. The set is ${env:NAME}, ${userHome}, ${workspaceFolder}, ${pathSeparator} and ${/}. Those last two exist so that one line can be right on a Mac and on Windows at the same time.

Save the file and go back to the app. Cursor picks the change up on its own, and you do not have to quit and start again. If nothing at all shows up after a few seconds, that is your first real clue, and the next parts of this page are where you take it.

Add a remote HTTP server

A remote entry is smaller. There is no command to run, so there is a url and not much else.

{ "mcpServers": { "Agentik-OS": { "url": "https://mcp.agentik-os.com/api/mcp" } } }

That is the entry we publish for Cursor, and it is the whole thing. No key, no header, no client id. The name is written Agentik-OS with that exact case for a dull reason worth knowing if you publish your own: Cursor title-cases a hyphenated slug in the UI, and our own agentik-core came back to us as Agentik Core. If you care how your server reads in the tool list, write the name the way you want to see it.

Two optional fields matter. headers is a flat map, and it is where a bearer key goes if the server wants one. auth is for OAuth servers and takes CLIENT_ID, and then CLIENT_SECRET and scopes if they apply. You only need auth when the server hands you a fixed client id, which happens when the server does not do dynamic client registration or wants your redirect URL on a list first.

If you leave scopes out, Cursor's docs say it will read scopes_supported from the server's auth metadata and use what it finds. Know that before you guess at a scope string, because a wrong one gives you a consent screen that grants you nothing and looks like it worked.

Never paste a live key into a project mcp.json. Use ${env:MY_TOKEN} in headers and keep the value in your shell profile. envFile will not help you here, it is stdio only.

  • url: required. The one path that answers both POST and GET.
  • headers: optional. A flat map. Interpolation works here.
  • auth: optional. CLIENT_ID, plus CLIENT_SECRET and scopes when the server needs them.
  • envFile: not allowed on a remote server. Use ${env:NAME} instead.

How to verify the server is actually connected

Saving the file is not the check. There are three places to look, and they fail in different ways, which is exactly why you look at all three.

Open Customize in the sidebar. Your server should be in the list with a toggle. If it is missing, Cursor never read the entry, and the fault is in the file, not in the server. If it is there but off, nothing will load and no error will be shown, because a server that is off is a server you asked for. That is the one state that never looks like a bug and is the first one to check.

Then open the Output panel, which is Cmd+Shift+U, and pick MCP Logs from the dropdown. This is the only place that shows you what the server said while it was starting. Cursor's docs point at this panel for connection errors, auth problems and crashes, and it is where a bad command shows up as a spawn error instead of as silence.

Third, in chat, look under Available Tools. A server that is up but has an empty tool list is a real state, and it is not the same bug as a server that is down. That case is almost always auth: you are talking to the server, the server knows who you are, and it has decided you get no tools.

If you use the Cursor CLI, you get a faster loop. agent mcp list prints every server with its status, the file it came from, and its transport. agent mcp list-tools <name> prints the tools and their inputs, which is the flat answer to whether the handshake worked. agent mcp login <name> runs the sign-in on its own. The CLI reads the same mcp.json as the editor, so a fix in one is a fix in both.

Do these in that order every time. It is quick and it tells you which of the three layers broke: the file, the process, or the account. Skip it and you can lose an hour to a bug that was a comma in a file you never opened.

  • Customize panel: is the server listed, and is the toggle on?
  • Output panel, MCP Logs: what did it say while it started?
  • Chat, Available Tools: did any tools arrive?
  • agent mcp list and agent mcp list-tools <name> from a terminal.

What the first tool call looks like

Ask for the tool by name. If the server is called notes, ask the agent to use the notes server to find a file. Naming it takes the guessing out of the test.

Cursor asks before it runs an MCP tool. You get a prompt with the tool name and an arrow that opens the arguments. Read the arguments the first time. This is the one moment where you get to see what a tool you did not write is about to do with the account you just gave it.

That prompt is not a fixed law. MCP follows the same Run Modes as terminal commands, so in a mode like Auto-review an allowlisted tool runs at once and the rest goes through the classifier. The CLI has --approve-mcps to skip the prompts. Both are real choices with a real cost, and the cost is that you stop reading arguments.

The response lands in chat as a block you can open, with the arguments and the result in it. A server can also send back an image as a base64 string, and Cursor puts it in the chat for a model that can read one.

Make the first call a read. Ask it to list, to find, to show. Leave the writes for the second call, once you have seen what the first one did and how long it took. This is dull advice and it is the reason you will not have to explain a deleted row to anyone.

Every failure, and what causes it

The four symptoms you will run into are: the server is not in the list, the server is listed but no tools show up, sign-in loops forever, and calls time out. They have different causes and the fix for one will not touch the others.

Before any of that, do the cheap test. Change one thing, then look. Two edits at once and you will not know which one did the work, and MCP gives you very little help in sorting it out after the fact. There is no undo and there is no stack trace, only a log with what the last run said.

Server not listed is nearly always a parse failure. Cursor read the file, choked, and moved on. A comma after the last key, a smart quote from a blog post, or a // comment in a file that is plain JSON will all do it. Paste the file into any JSON parser before you blame anything else.

Listed but no tools has two causes, and the logs tell them apart. If MCP Logs shows a spawn error, the command is wrong. If it shows a clean handshake and then a short tool list, you are looking at a server that answered. Ours answers a POST without a token with a 401 and a WWW-Authenticate header that says where to go, so a host that does OAuth turns that into a sign-in prompt and a host that does not just stays empty.

The auth loop is the nastiest one because both sides look right. Cursor uses fixed redirect URLs for MCP OAuth: https://www.cursor.com/agents/mcp/oauth/callback for web and Agents, and http://localhost:8787/callback for the desktop app. If you are using static auth credentials against a server that only allows one redirect, sign-in from the desktop app will complete on the provider's side and come back to a URL the provider has never heard of. Register both.

Timeouts split by transport. On stdio, the process died and its last words are on stderr, which the MCP spec lets a server write freely and lets the client ignore, so you will only see them in MCP Logs. On a remote server, a timeout usually means a slow tool, not a broken link, because the handshake happened and the tool list arrived before the call went out.

One good thing: Cursor isolates a failing server. Its docs say an error shows in chat, that tool call is marked failed, and the other servers keep working. You can debug one entry without pulling the rest of the file out.

The real cost is tool clutter, not setup

Setup happens once. The thing you pay every day is that every tool on every enabled server is described to the model on every turn. Names, descriptions, and JSON schemas for arguments. Count them yourself: five servers with twenty tools each is a hundred tool definitions sitting in front of your actual question.

You feel it as the model picking a worse tool, or ignoring a tool you know is there, or a context window that fills up faster than it used to. Cursor's own advice on the disable toggle names reducing tool clutter as a reason to use it, right next to troubleshooting.

The fix is boring and it works. Keep the home file thin: the two or three servers you want everywhere. Put the rest in the project that needs them. Turn off what you are not using this week instead of deleting it, since the toggle keeps the config and drops the load. On teams, Cursor's enterprise allowlist can restrict which tools from an approved server may run automatically, which is the same idea with an admin behind it.

This is the tradeoff of MCP as a whole, and it is worth saying plainly. Every server you add makes the agent more capable and every server you add makes it harder for the agent to choose. There is no setting that gives you both.

  • Home file: two or three servers, no more.
  • Project file: the ones that only make sense in that repo.
  • Toggle off in Customize instead of deleting.
  • Check MCP Logs after a server update. A new version can add fifteen tools you did not ask for.

What the OAuth handshake is doing

You do not need this to install a server. You need it the moment sign-in fails and you have to decide whose bug it is.

The host POSTs to the MCP endpoint with no token. The server answers 401 and sets a WWW-Authenticate header that names a metadata URL. That header is the whole discovery step. Here is the exact one our endpoint returns, which you can pull yourself with curl and no account.

WWW-Authenticate: Bearer realm="mcp", resource_metadata="https://mcp.agentik-os.com/.well-known/oauth-protected-resource", scope="mcp:tools", error="invalid_token"

The host fetches that document, which is Protected Resource Metadata from RFC 9728. It learns which authorization server to go to. It fetches that server's metadata, registers itself or uses the client id you put in auth, and runs OAuth 2.1 authorization code with PKCE. You sign in, you press allow, and the host sends a bearer token on every later POST.

Three failures live in that chain and the log line tells you which. A 404 on the well-known URL means the server never published discovery, and no host will finish. A redirect mismatch means the callback is not registered, which is the loop described above. A token that arrives and still gets refused means you are past auth and into permissions, which is a different team's problem.

That last one is ours by design. An account with no plan can finish OAuth on our endpoint and still get an empty tool list, plus a JSON-RPC error saying a paid plan or an official OS unlock is required. Sign-in worked. Access did not. Any server with a paywall behind an open login will show you that shape, and reading it as an auth bug costs an afternoon.

What adding an MCP server does not do

It does not give the model judgement about when to use the thing. A tool description is a sentence. If two servers both expose something called search, the model will pick wrong some of the time, and no amount of config fixes that.

It does not make the work safe. An MCP server runs code on your behalf against real accounts. Cursor's security page says the plain version: check the source, check what it can reach, use restricted keys, read the code for anything critical. Approval prompts help exactly as much as you actually read them.

It does not give you memory. MCP moves tools and data in. It has no opinion about what should still be true tomorrow. If you want the agent to remember a constraint across sessions, something on the other side of the protocol has to hold it, and a tool list is not that thing.

It does not survive an update on its own. Cursor's documented path for refreshing an npm-based server is to remove it in Customize, run npm cache clean --force, and add it back. A pinned version in args is the honest alternative: you choose when to move.

What we publish for Cursor, and why it is one line

Agentik {OS} installs on the host you already pay, and Cursor is one of six: Claude, Claude Code, Cursor, ChatGPT, Codex, Hermes. The Cursor entry is the remote JSON above, and the site also carries a cursor://anysphere.cursor-deeplink/mcp/install link that writes it for you. Or npx @agentikos/os install --host cursor from a terminal. Three routes, one file, same result.

The reason there is no key in that entry is that auth is OAuth 2.1 with PKCE against https://mcp.agentik-os.com/api/mcp, and Cursor does that flow on its own. The host runs the model and pays for the tokens. We never buy tokens.

On tool clutter we had to make a choice, and this is the one thing here that is a design decision rather than a fact about Cursor. An OS like Growth OS compiles 48 agents. Exposing them as tools would be a roster of about 110 KB in front of every turn. So there is one tool, talk, that takes a normal conversation turn and returns a compact pack of about 2 KB for the project you are in. Work that publishes, sends, spends or resets stops and waits for approve. The cost of that choice is real: you cannot see the whole roster in Cursor's tool list, because it is not there. /docs/mcp has the longer version.

Cursor MCP failures, their cause, and the check that proves it
What you seeMost likely causeWhere to confirmFix
Server missing from Customizemcp.json did not parsePaste the file into a JSON parserRemove the trailing comma, the comment, or the smart quote
Listed, toggle offDisabled, not brokenCustomize panelTurn the toggle on
Listed, no tools in chatServer started but returned noneMCP Logs: clean handshake, short listCheck auth and plan on the server side
Spawn or ENOENT in MCP Logscommand not on Cursor's PATHMCP LogsWrite the full path, or use ${env:NAME}
Sign-in opens, never returnsRedirect URL not registeredProvider's OAuth app settingsRegister both Cursor callbacks, web and localhost:8787
401 on every call after sign-inToken fine, permission refusedcurl the endpoint and read the bodyA plan or entitlement problem, not an auth one
Tool call times outstdio: process died. Remote: slow toolMCP Logs for stderr, then the server's own logsFix the crash, or raise the tool's own timeout
Tools appeared, then stoppedServer update changed the tool listagent mcp list-tools <name>Pin the version in args

Sources

Questions

Where is the Cursor mcp.json file?

Either ~/.cursor/mcp.json in your home folder for every project, or .cursor/mcp.json at a repo root for that project only. Both use the same mcpServers root key.

Why are my MCP tools not showing up in Cursor?

Check the three places in order: is the server listed in Customize, is its toggle on, and what does MCP Logs in the Output panel say. A listed server with no tools is usually auth or permissions, not a broken config.

Do I need a command or a URL?

A command if the server is a program on your machine, with type set to stdio. A URL if it is a remote server someone runs for you, and then Cursor handles OAuth itself.

How do I put an API key in mcp.json without committing it?

Put the value in your shell environment and write ${env:MY_TOKEN} in headers or env. For stdio servers you can also point envFile at a dotenv file, but remote servers do not support that field.

Why does sign-in keep looping?

Cursor uses fixed OAuth redirect URLs, one for web and Agents and one at http://localhost:8787/callback for the desktop app. If the provider only has one of them registered, the callback lands nowhere and the flow restarts.

How many MCP servers should I run at once?

Fewer than you want to. Every enabled tool is described to the model on every turn, so keep the home file to two or three and push the rest into the projects that need them.

#MCP#AI OS