There are two kinds of MCP server, they solve different problems, and almost nothing tells you which one you are building until you are deep enough in to have already made the wrong choice.
I worked this out from a submission form. More on that below, because it turns out to be the clearest signal in the whole ecosystem and it is buried in a footnote.
The two shapes
Local (stdio). The server runs as a process on the user's own machine. The client — Claude Desktop, Cursor, whatever — spawns it and talks to it over stdin/stdout. It is a package the user installs.
Remote (Streamable HTTP). The server is a service you host. The client connects out to a URL with a token in an Authorization header. Nothing is installed locally.
That is the entire distinction, and it determines everything else.
What actually differs
Who runs the code. Local: the user, on their hardware. Remote: you, on yours. This is the real decision. Everything below follows from it.
Where secrets live. Local servers read credentials from the user's own environment — their shell profile, their config file. You never see them. Remote servers require the user to hold a token you issued, which means you own the entire credential lifecycle: issuing, scoping, rotating, revoking.
What the server can reach. A local server can read the user's filesystem, hit localhost, talk to their Docker daemon. A remote server can see none of that, and should not want to.
Update path. Remote: you deploy, everyone is on the new version immediately. Local: users run whatever version they installed, possibly forever.
Failure surface. A local server fails on one machine. A remote server fails for everyone at once. Pick your poison.
Choosing
Local if you need the user's filesystem, local processes, a local database, or hardware. Or if the data must not leave their machine.
Remote if the server fronts a service you already run. If your MCP server's job is to call your own API, making users install a process that proxies to your HTTP endpoint is pure overhead — you have shipped a local wrapper around a remote call, and now you maintain both.
Rule of thumb: if the data lives on your infrastructure, the server should be remote. If it lives on the user's machine, local.
The part that cost me an hour
Anthropic runs a directory of MCP servers, and there are two separate submission forms — one for local, one for remote. The local one is framed around Desktop Extensions, and its stated requirements are:
- Publicly available on GitHub
- MIT licensed
- Built with Node.js
- A valid
manifest.jsonwith theauthorfield pointing at your GitHub profile - A
.mcpbfile, as a required upload
That last one is the tell. A .mcpb is a packaged local extension that Claude Desktop installs and runs. If your server is remote, there is no bundle to produce — and the required-file field is where you discover you are on the wrong page.
The link to the remote form sits in a single line near the top of the local one, easy to skim straight past.
So: read the transport requirements before you fill anything in. If a form asks for a .mcpb, or names Node.js as a hard requirement, it wants a local server. A rejected submission is a worse outcome than a slower correct one.
Auth, if you are going remote
Remote means you own the credentials, so a few things stop being optional.
Scope tokens narrowly. A token that can only read context for a single task is a much smaller problem when it leaks than one that acts as the user's whole account.
Make expiry the default, not revocation. "Revocable" puts the burden on someone remembering. Expiring means neglect is harmless. Design for the person who forgets, because that person is you.
Never let a token land in a committed file. Reference an environment variable in config and export the real value from the shell profile. Once a credential reaches a remote branch, deleting the line does nothing — the value is in the history, and rotating is the only real fix.
Where I landed
I work on Wagglet, which is remote. The server fronts a shared task board, so the data is ours rather than the user's. Going local would have meant shipping a process whose only job was to proxy HTTP calls to our own API, plus a bundle to package it, plus a version-skew problem — in exchange for nothing.
The docs cover the connection and credential model if you want a worked example of the remote side.
Disclosure: I work on Wagglet. The local/remote decision above is the part worth taking regardless of what you are building.
Short version
Data on your infrastructure, go remote. Data on the user's machine, go local.
And before you fill in any directory form, check whether it wants a .mcpb. That is the fastest way to tell which door you are standing at.
Top comments (0)