A stdio MCP server runs as a subprocess on your machine: the host starts it, talks to it over its standard streams, and kills it when the session ends. That shape is simple and safe, and it is also local-only. The moment a second machine needs the same server, or a second person does, stdio stops working. Hosting is the answer, and it is a smaller change than it sounds. The tools stay the same, the handlers stay the same, and the protocol stays the same. What changes is the transport: the server stops being a subprocess and becomes an HTTP endpoint, and two decisions arrive with that change, who may connect and how they prove it. This guide walks through both, using the 2026-07-28 revision of the specification and the official TypeScript and Python SDKs, both of which now ship that revision as their current line.
Start from the decision, not the deployment
Before any code changes, answer one question: who connects to this server? There are three answers, and each one picks a different setup. Only you, from your own machines: a private server with a simple credential in front of it is enough, and the main risk is the server leaking onto a network you did not intend. A small team of people you know: the same shape works, with one credential per person so you can revoke one without breaking the rest. Anyone on the internet: now you need the full authorization flow from the specification, with scopes and discovery, because you are serving strangers. If the answer is only you, on this machine, stop here. Stdio already does that, and hosting adds exposure for nothing. The most common hosting mistake is deploying a server nobody remote needed.
The second question is what the tools can do. A server whose tools only read is forgiving: the worst case is someone reading what the tools can see. A server with a write tool, a delete tool, or a tool that spends money is a different object, and it needs auth before it needs a URL. The security guide on this site covers the tool-level checks; this guide covers the network side. Read them as a pair before you expose anything that writes.
What actually changes: the transport
The specification defines two standard transports, and the protocol means the same thing on both. Stdio carries newline-delimited JSON-RPC messages over the standard streams of a client-launched subprocess. Streamable HTTP carries each message as its own HTTP POST to a single MCP endpoint, and the server answers each request with a single JSON object or a request-scoped Server-Sent Events stream. A client sends every JSON-RPC request or notification as its own POST, with an Accept header listing both application/json and text/event-stream, and notifications get back a bare 202 Accepted. The semantics are identical on every transport: the binding only changes how messages are framed and delivered.
The 2026-07-28 revision changed one behaviour that matters to anyone hosting. In earlier revisions a server could send its own JSON-RPC requests, for example to ask the client for a sample or an elicitation, on the SSE stream. In 2026-07-28 it cannot. When the server needs input back from the client mid-call, that input request travels inside the result, as an input request the client answers by retrying the original request. Long-lived change notifications, such as a tools list changing, travel on a separate long-lived stream the client opens with a subscriptions/listen request. If you built against an older revision, or you read a tutorial written for one, check which era its transport advice belongs to before you follow it.
Three smaller rules from the same page are easy to miss and cause most first-deployment failures. Every POST must carry an MCP-Protocol-Version header whose value matches the protocolVersion field in the request body, or the server must reject it with 400 Bad Request. A request for a method the server does not implement gets a 404 with JSON-RPC error code -32601, method not found. And the server must validate the Origin header on every connection and answer an invalid one with 403 Forbidden, because without that check a remote website could reach a local server through DNS rebinding. These are not polish. They are the transport's own defences, and a server that skips them is broken in ways that look like client bugs.
The switch in the TypeScript SDK
The official TypeScript SDK is now on its v2 line, split into two packages, @modelcontextprotocol/server and @modelcontextprotocol/client, implementing the 2026-07-28 specification. The v1 package, @modelcontextprotocol/sdk, still gets bug fixes and security updates, but new servers should start on v2. The part that matters for hosting is what does not change: you build the same McpServer, register the same tools with the same Zod schemas, and write the same handlers. Only the transport at the bottom changes, from a stdio transport to a Streamable HTTP one. The build guide's greet server shows the stdio shape; the hosted shape keeps the server definition and swaps the last few lines.
The SDK also ships small middleware packages that wire the Streamable HTTP transport into a framework you already run: Express, Fastify, Hono, or plain Node.js HTTP. They are thin adapters on purpose, with no new MCP behaviour, and they add the host checks the spec wants, such as Host header validation. Pick the one that matches the framework the server already runs in, or plain Node HTTP if it runs standalone. The full wiring lives in the SDK documentation site, and that is the place to copy it from rather than from a tutorial, because the transport surface moved between v1 and v2 and older examples wire it differently. The build guide's advice still applies here: follow the current quickstart, and confirm with the Inspector that your server answers clients from both protocol eras before you share the URL.
The switch in the Python SDK
The Python side is the same story with less code. The SDK's v2 line uses the MCPServer class, and the same server file serves over either transport. The README's fifteen-line demo server, one tool and one templated resource, runs over stdio by default and over HTTP with one command:
uv run mcp run server.py --transport streamable-http
That serves the server at http://localhost:8000/mcp, and a client connects by URL instead of by launching a subprocess:
import asyncio
from mcp import Client
async def main() -> None:
async with Client("http://localhost:8000/mcp") as client:
result = await client.call_tool("add", {"a": 1, "b": 2})
print(result.structured_content) # {'result': 3}
asyncio.run(main())Two details from the README matter here. First, the SDK needs Python 3.11 or newer, and the command-line tool comes from the mcp[cli] extra, installed with uv add "mcp[cli]" or pip install "mcp[cli]". Second, a URL means Streamable HTTP: the Client decides the transport from the address, so the same client class connects to a local server on localhost and a hosted one on your domain with no code change. That is the whole point of the transport split. Your server file does not care where it runs; the address does.
The three rules the spec sets for any HTTP server
The Streamable HTTP page states three requirements for servers, and they are worth reading as a checklist rather than as background.
- Validate the Origin header on every incoming connection, and answer an invalid Origin with 403 Forbidden. Without this, DNS rebinding lets a remote website talk to a server that was meant to be local.
- When running locally, bind only to localhost (127.0.0.1), not to all interfaces (0.0.0.0). A server bound to every interface is reachable from the local network, which is rarely what a local test intends.
- Implement proper authentication for all connections. The spec says SHOULD, not MUST, and this guide treats that SHOULD as a requirement for anything beyond your own machine. A server with no auth and a public URL is a public API, whether you meant it to be one or not.
Add the client-side rules to the same list, because you will debug them as server bugs otherwise. Clients must send the Accept header with both content types, must include the request metadata headers on each POST, and must support both JSON and SSE replies. A client that sends only one Accept value, or none, will fail in ways that look like the server is broken.
Auth: the spec's answer, and the minimum that works
Authorization is optional in the specification, and that word misleads people. Optional means a server may run without it, not that a public server should. The spec's mechanism, for servers that do authenticate, is OAuth 2.1. The MCP server acts as the resource server, the client acts on behalf of the resource owner, and an authorization server issues the access tokens. Servers advertise their authorization server through OAuth 2.0 Protected Resource Metadata, clients discover it from there, and the token travels in the Authorization header on every request, never in the query string. Invalid or expired tokens get a 401. Servers must check that a token was issued for them as the intended audience, and must not accept tokens issued for someone else's resources.
That is the full flow, and it is the right answer when strangers will connect. For a private server with one or two known users, a simpler bearer check in front of the endpoint is a reasonable first step: a shared secret the client sends as a Bearer token, checked by your own code before the MCP handler runs, rotated when someone leaves. Say plainly what it is not. It is not the spec's OAuth flow, it does not give you per-user scopes, and it does not scale to people you do not know. When the audience grows past people you can phone, implement the spec flow or put the server behind an identity-aware proxy that speaks it for you. Either way, the rule from the security guide holds: a server whose tools write, delete, or spend gets auth before it gets a URL, and the auth gets tested from a second machine before the URL gets shared.
One more detail from the authorization page matters for planning. The authorization server may be hosted with the resource server or as a separate service, and clients discover it through the protected resource metadata either way. That means you can start with a single deployment and split auth out later without changing how clients find it, as long as the metadata document stays correct.
A deployment checklist
Work through this list in order, on the machine the server will run on, before you tell anyone the URL.
- One endpoint, one path. The server exposes a single MCP endpoint that accepts POST, for example https://example.com/mcp. Everything else on that host is a separate concern.
- HTTPS in front. Tokens travel in the Authorization header, and a header sent over plain HTTP is a header anyone on the path can read. Terminate TLS at your proxy and refuse plain HTTP to the endpoint.
- Origin validation on, localhost binding checked. Confirm the server answers 403 to a bad Origin and is not listening on 0.0.0.0 unless you meant it to be.
- Auth on, tested from a second machine. A request with no token gets a 401; a request with a wrong token gets a 401; a request with a revoked token gets a 401. If any of those returns data, stop.
- Logs on. Record who called which tool and when, at minimum. The observability guide's run-record checklist applies here too: a hosted server you cannot audit is a server you cannot defend.
- Timeouts and caps on expensive tools. A hosted tool that calls a slow API or a large model needs a ceiling, or one client can hold the server for everyone else.
- Keep the stdio version working. The local server is your fallback and your test bench. If the hosted server misbehaves, compare it against the stdio one before you change the tools.
- Write the URL down in one place, with the auth method beside it. Clients need both, and a URL shared without its auth story will be tried without auth.
Prove it works before you share the URL
Test in three stages, and do not skip the second one. First, on the server machine, connect a client to the local address, the same Python Client from the SDK README pointed at http://localhost:8000/mcp, and call one tool. That proves the server runs. Second, from a different machine on a different network, make the same call to the public URL with a valid token, then again with no token and with a wrong token. That proves the auth works where it matters, not only on localhost. Third, check the failure modes the spec defines: a bad Origin should get a 403, an unknown method should get a 404 with error code -32601, and a request without the MCP-Protocol-Version header should get a 400. If those three behave, the transport is wired right; if a tool then misbehaves, the problem is in the tool, and the stdio version is the control to compare against.
One more check belongs here because it catches the error people blame on hosting. Connect the same client to the stdio version and the hosted version, call the same tool with the same arguments, and compare the results. If they differ, the difference came from the deployment: environment variables the hosted process does not have, a file path that exists locally and not on the server, a credential the hosted process cannot read. The protocol is identical on both transports, so a difference in output is a difference in environment, and the fix belongs in the deployment, not in the tools.
When not to host
Hosting is not an upgrade. It is a trade: reach for exposure. Keep the server on stdio when only you use it, when its tools touch secrets or money and you have no time to maintain auth, when the data it reads must never leave the machine, or when the server exists for one job on one project. A hosted server also needs maintenance a local one does not: the auth story, the logs, the dependency updates, and someone to answer when it stops responding. If nobody owns that work, the server will rot in public, which is worse than a local server nobody maintains. The right time to host is when a second person or a second machine genuinely needs the tools, the auth is tested, and someone has agreed to keep it running. Until then, stdio is the finished product, not a stepping stone.
From the AgentsUse catalog
Published profiles for the tools and frameworks this guide discusses, with setup, limits, and maintenance signals checked against primary sources.
Frequently asked questions
Is Streamable HTTP the only way to host an MCP server?
No. The specification defines stdio and Streamable HTTP as the standard transports, and it allows custom transports as long as they preserve the JSON-RPC message format and the per-request metadata model. Streamable HTTP is the standard HTTP transport in the 2026-07-28 revision. Older HTTP+SSE servers still exist for clients built against earlier revisions, and the spec's backward-compatibility section describes how the two eras detect each other. Start with Streamable HTTP unless you have a client that only speaks the older transport.
Do I need OAuth to host a server?
The specification makes authorization optional, and says HTTP servers should implement proper authentication. For a private server with one or two known users, a bearer check in front of the endpoint is a reasonable first step. For anyone you do not know, use the spec's OAuth 2.1 flow: protected resource metadata, discovery, and tokens in the Authorization header. The line between the two is the audience, not the tool count.
Can any MCP client connect to my hosted server?
Any client that speaks Streamable HTTP can. The Messages API has an MCP connector that connects to remote MCP servers without a separate MCP client, so a hosted server works there directly. A host that only launches local stdio servers cannot connect to a remote URL on its own; it needs a bridge or a client that speaks HTTP. Check the client's transport list before you promise it the URL.
Does hosting change my tools?
No. The tools, their schemas, and their handlers are identical on both transports. The specification says protocol semantics are identical on every transport; only framing and delivery change. If a tool behaves differently hosted, the cause is the deployment environment: a missing variable, a path that does not exist on the server, or a credential the hosted process cannot read. Compare against the stdio version to find it.
What breaks most often on a first deployment?
Four things, in rough order. Origin validation rejecting a client you did not expect, the server bound to localhost while you test from another machine, a missing MCP-Protocol-Version header on the request, and auth that works from your browser but not from the client. Check those four before you touch the tools. The spec defines the expected response for each: 403 for a bad Origin, 400 for a header mismatch, 401 for a bad token.
Should the authorization server live on the same machine as the MCP server?
Either arrangement works. The specification says the authorization server may be hosted with the resource server or as a separate entity, and clients find it through the protected resource metadata in both cases. Start combined if that is simpler, and split it out later without changing how clients discover it, as long as the metadata document stays correct.
Sources
- Model Context Protocol specification, Transports overview (2026-07-28)Read 2026-11-11
- Model Context Protocol specification, Streamable HTTP (2026-07-28)Read 2026-11-11
- Model Context Protocol specification, Authorization (2026-07-28)Read 2026-11-11
- Model Context Protocol TypeScript SDK, README (v2, @modelcontextprotocol/server and @modelcontextprotocol/client)Read 2026-11-11
- Model Context Protocol Python SDK, README (v2, MCPServer and mcp run --transport streamable-http)Read 2026-11-11
- Anthropic, Tool use overview (MCP connector for remote servers)Read 2026-11-11
Keep reading
Guide
How to Build Your First MCP Server
Build a working MCP server from scratch in Python or TypeScript: one tool, one resource, tested in the Inspector, then connected to Claude for Desktop.
2026-11-09
Guide
What Is MCP (Model Context Protocol)? How Agents Connect to Tools
MCP explained from its own specification: hosts, clients, and servers; tools, resources, and prompts; stdio and Streamable HTTP transports; a first-server walkthrough; and how to judge a server before you install it.
2026-10-17
Guide
MCP Security Best Practices in Plain English
A plain-English reading of the official MCP security page: local servers, token passthrough, scopes and SSRF, plus what to check before installing a server.
2026-10-29
Accounts and the planned features are not open yet. See what is planned. The catalog and the browser workspace are free and need no account.