To build MCP server tools, pick local or remote first. A local server runs on the user's own machine over stdio and is working in under 100 lines with an official SDK. A remote one is an HTTPS service with OAuth that anyone connects to with a URL, and that takes days rather than hours.
Most tutorials show you the local kind, because it is the part that fits in one file. This covers both, and it is honest about which parts of the remote path cost real days, because we run one in production and those days are on the calendar.
What do you build when you build MCP server tools?#
An MCP server exposes tools, each one a named function with a described set of inputs, over a protocol an assistant already speaks. The assistant reads your tool list, decides which one answers the question in front of it, and calls it. What an MCP server is covers the idea without code.
The work splits in two, and only one half is interesting:
- Describing your tools. Names, inputs, what each returns. 2 or 3 hours if your API already exists.
- Letting someone connect. Transport and authentication. This is where the time goes.
Local or remote? Decide this before writing anything#

A local server runs on the user's own computer, talks over stdio, and is launched by the client from a config file. There is no network, no login, and no hosting, which is why every quick tutorial picks it. If your server wraps a local thing, their files, their database, their git repository, local is correct and you are done after the tools.
A remote server is an HTTPS endpoint anyone can reach. The user pastes a URL instead of editing a config file, you ship fixes without anyone updating anything, and you have to answer the question a local server never asks: who is this?
The difference is not difficulty for its own sake. It is who the server is for. Local serves one developer on one machine. Remote serves customers, which means accounts, which means OAuth. Remote MCP servers covers the user-facing side of that choice.
How do you get a local server working fastest?#

Use the official SDK for your language rather than writing protocol handling. TypeScript and Python both have one, both are maintained by the protocol's authors, and both get you to a running server in under 100 lines.
The order that wastes the least time, in 4 steps:
- One tool, hardcoded output. No API call, no arguments. Get the client to list it and call it.
- Add real arguments and validate them. Bad input from a model is normal, not exceptional.
- Connect your actual data once the plumbing is proven.
- Write the descriptions last, when you know what the tool really does.
That fourth step matters more than it looks. The assistant picks tools by reading descriptions, so a vague one gets called at the wrong moment or never at all. Describe when to use it, not what it does internally.
What makes a remote server harder?#

Three things, and the protocol is none of them.
Transport. Streamable HTTP is the current answer. Your endpoint accepts POSTed JSON-RPC and can stream responses back. That part is ordinary web work.
The unauthenticated 401. This is the piece people get wrong. When a request arrives with no token, you do not return 403 or a login page. You return 401 with a WWW-Authenticate header naming where your protected resource metadata lives. That header is how a client discovers it needs to authenticate and where to go. Get it wrong and clients report your server as simply broken, with no clue why.
Dynamic client registration. A human pre-registering an OAuth client is the normal web flow and it does not work here, because you do not know in advance which assistant is connecting. Clients register themselves at an open endpoint and get credentials back. Ours is deliberately unauthenticated for exactly this reason, and a server without it can only be used by clients you have personally onboarded.
What we got wrong, so you do not have to#

We run a remote MCP server in production, and two things cost us real time.
A dropped refresh response stranded a client for 3 days. The client asked to refresh its token, our side issued the new one and the response never arrived. The client still held a token we had already retired, so every retry failed and it looked, from their end, like we had revoked them. The fix was to record which token replaced which, so a retry with a just-superseded token is answered rather than rejected. Refresh is not a single moment; it is a window where both sides briefly disagree.
Being listed is its own project. A working server nobody can find is a hobby. The official registry, the assistant directories and the aggregator sites each want a different description format, and some read your server directly to list its tools, which means your tool descriptions become your marketing copy whether you intended that or not.
Questions people ask#
Is building an MCP server hard?#
The protocol is not. A local server with one working tool is an afternoon, and under 100 lines, with an official SDK.
Remote is a different question. The transport is ordinary web work, but OAuth with dynamic client registration is where most of the time goes, and it is the part no quick tutorial covers.
How do I build an MCP server for my existing API?#
Wrap it rather than rebuilding it. Each tool becomes a thin call to an endpoint you already have, and your existing authentication stays where it is.
The real work is deciding which endpoints deserve to be tools. An assistant reading 40 near-identical tools picks badly, so expose the handful that answer real questions and leave the rest to the API.
How do I make a remote MCP server?#
Serve streamable HTTP over HTTPS, answer unauthenticated requests with a 401 and a WWW-Authenticate header pointing at your protected resource metadata, and support dynamic client registration so clients can enroll themselves.
Everything else is the same server you would have built locally.
Do I need OAuth, or is an API key enough?#
A key is simpler and works for your own scripts. It fails for customers, because a key is a password they paste into someone else's software, and it cannot be scoped or revoked per client.
If people other than you will connect, use OAuth. We support a key as well, but the key is the convenience and OAuth is the product.
How do I test it while building?#
Call it from the client you expect people to use in the first 2 hours, not at the end. The protocol is specified but clients differ in which parts they exercise, and a server that works in one can fail in another on a detail neither of you considered.
Ours is live if you want a working example to point a client at: the address and what it exposes are on the developer docs, and what it lets an assistant do shows the finished shape.
If you would rather use one than build one, the free QR code generator is the same service our MCP server sits in front of, and connecting it to an assistant takes a URL and a sign-in.
