Cover image for Build an MCP Server: Start Local, Go Remote Only When You Need To

Build an MCP Server: Start Local, Go Remote Only When You Need To

A practical guide to building an MCP server with the official quickstart, choosing FastMCP or TypeScript, testing with Inspector, and knowing when to stay on stdio or move to a remote /mcp endpoint.

If you want to build an MCP server, do not start by choosing a framework. Start by choosing the client shape. A local server for Claude Desktop or an IDE should usually begin with stdio. A server you want to use from ChatGPT or other hosted clients should move to Streamable HTTP and expose a real /mcp endpoint. That is the fork that controls most of the work that follows.

The official Build an MCP server guide is still the right first stop because it teaches the smallest useful server shape. But it is a weather tutorial, not a decision guide. The practical job is to build one local server first, validate it with MCP Inspector, and only then decide whether the server should stay local, move to remote HTTP, or become a fuller app flow in ChatGPT. If you want the protocol background before the build path, start with What Is MCP?.

Start with the fork, not the framework

Most searches for "build mcp server" mix together three different jobs:

  • understand the protocol shape
  • create a local server that actually runs
  • publish a remote endpoint for hosted clients

Treat those as separate stages.

If your goal is...Start hereTransport
Learn the server shapeOfficial weather quickstartstdio
Compare other reference patternsMCP examplesusually stdio first
Connect from ChatGPT or another hosted clientRemote server guide plus OpenAI's MCP docsStreamable HTTP
Wrap a real API quicklyConnecting your first APIlocal first, then remote if needed

That ordering matters because the protocol is small, but the deployment choices are not. If you start with remote auth, tunnels, and hosted-client behavior before the server itself is stable, you are debugging the wrong layer.

The smallest useful MCP server

The official build guide defines the three core server capabilities as resources, tools, and prompts. It also states plainly that the tutorial itself focuses mostly on tools. That is the right scope for a first server. You do not need a large architecture to prove the loop works.

For Python, the first-build path is intentionally small. The guide uses FastMCP, which turns type hints and docstrings into tool definitions:

from mcp.server.fastmcp import FastMCP

mcp = FastMCP("weather")

@mcp.tool()
async def get_forecast(latitude: float, longitude: float) -> str:
    return f"Forecast for {latitude}, {longitude}"

if __name__ == "__main__":
    mcp.run(transport="stdio")

That shape is enough to teach the model-facing contract:

  • name the server
  • expose a tool with a clear purpose
  • define typed inputs
  • return something structured enough for a client to use
  • run it on a transport the host understands

The detail people skip is logging. The official quickstart warns that for stdio servers, writing to stdout will corrupt the JSON-RPC stream. Log to stderr instead. If your first local server mysteriously fails, that is one of the first things to check.

This is also the point where you should resist over-building. The first server should prove that discovery, invocation, and outputs work. It does not need production auth, retries, metrics, and five tools on day one.

Build the local loop first

The cleanest first milestone is not "my server is deployed." It is "my server responds correctly through the transport I chose."

That usually means:

  1. implement one or two tools
  2. run the server locally over stdio
  3. confirm the schemas and results are sane
  4. test it from a real host only after the server shape is stable

If your next step after the weather example is a real integration, move from the toy tool to an API-backed MCP tool, not straight to a full remote rollout. That keeps the problem narrow: first the contract, then the business logic, then the deployment surface.

Use Inspector before you blame the host

The MCP Inspector docs describe it as an interactive developer tool for testing and debugging MCP servers. That is exactly how it should be used.

Before you spend time on ChatGPT, Claude, Cursor, or any other client, verify:

  • the server starts cleanly
  • tools are listed with the names you expect
  • descriptions are specific enough to be useful
  • input schemas match the arguments you think the model should send
  • bad inputs fail in a readable way

This removes a lot of fake debugging. A surprising number of "the client is broken" reports are really one of these:

  • the tool schema is incomplete
  • the tool name is too generic
  • the output shape is inconsistent
  • the server is writing noise to stdout

If you want to compare where to plug the finished server in next, MCP client comparison is the better next read than randomly testing every host you can find.

When stdio is enough and when it is not

The current MCP transport spec defines two standard transports: stdio and Streamable HTTP. They solve different problems.

QuestionstdioStreamable HTTP
Who launches the server?The client starts it as a subprocessThe server runs independently
Best fitClaude Desktop, IDEs, local tools, fast iterationChatGPT, remote clients, shared deployments
Network surfaceNone by defaultReal HTTP endpoint
Operational complexityLowHigher
Security focusDo not corrupt stdoutOrigin validation, auth, endpoint hardening

The transport spec is explicit about the remote branch:

  • Streamable HTTP uses POST and GET
  • the server must expose a single MCP endpoint path
  • servers must validate Origin
  • local HTTP servers should bind to localhost, not 0.0.0.0
  • remote connections should use proper authentication

That is the real decision boundary. If a single user runs the tool locally from a desktop app or IDE, stdio is usually the right answer. If the client is hosted, multi-user, or browser-facing, you are in HTTP territory.

Mermaid diagram source:
flowchart LR
  goal["Need an MCP server"] --> local["Single-user or desktop/IDE host"]
  goal --> remote["Hosted or shared client"]
  local --> stdio["Use stdio first"]
  stdio --> inspect["Validate with Inspector"]
  remote --> http["Expose one /mcp endpoint"]
  http --> secure["Add auth + Origin validation"]
  secure --> chatgpt["Connect from ChatGPT or other remote clients"]

Hosted clients raise the bar

Once the server moves from local testing to a hosted client, the build problem changes.

OpenAI's current MCP guide treats the remote server as the thing you configure and connect in ChatGPT or through API-side workflows. That means the server is now responsible for stable transport behavior, authentication, and clear tool boundaries. The same guide recommends OAuth for remote servers and says ChatGPT app setup happens through the server URL.

For remote HTTP, there are a few practical consequences:

  • your endpoint contract matters more than your demo prompt
  • write actions need more scrutiny than read-only tools
  • tool descriptions need to survive model selection in a hosted environment
  • auth and trust are now part of the product, not just the code

OpenAI also notes that ChatGPT currently requires manual confirmation before write actions can run in a conversation. That is a useful safety boundary, but it is not a license to be sloppy with tool scope. Write-capable tools still need careful naming, accurate behavior, and trusted server deployment.

If your goal is specifically to take the remote server into ChatGPT, use How to add MCP tools to ChatGPT once the endpoint is ready.

FastMCP or the TypeScript SDK

This choice is less ideological than people make it sound.

Choose FastMCP when:

  • you want the fastest path to a first server
  • Python is already comfortable for the team
  • you like type hints and docstrings generating tool definitions
  • you want one path that can start local and later run over Streamable HTTP

The current Python SDK repo shows both modes. It uses FastMCP for the quickstart shape, and its Streamable HTTP section calls that transport the recommended production option.

Choose the TypeScript SDK when:

  • your real services already live in a Node or TypeScript stack
  • you want tighter alignment with existing web runtimes
  • you expect to wire directly into HTTP servers or frameworks
  • you prefer one language across server logic, validation, and surrounding app code

The current TypeScript SDK repo also exposes a useful distinction: the SDK covers server libraries, client libraries, Streamable HTTP helpers, stdio support, auth helpers, and thin middleware for runtimes like Express and Hono.

The important caution is versioning. The repository main branch currently says it contains v2 in pre-alpha, and that v1.x remains the recommended production line. So do not copy code blindly from mixed examples and only then decide what version you meant to use. Pin the SDK family first. Then follow the matching docs for that family.

That is a real operational choice, not a documentation nit.

The remote edge cases most first builds miss

The protocol details matter more once browsers and hosted clients enter the picture.

The Python SDK docs call out a few of these directly for Streamable HTTP:

  • expose the Mcp-Session-Id header if browser-based clients need it
  • allow the MCP HTTP methods your client needs
  • understand that the default HTTP mount path is often /mcp

Those are small details, but they are exactly the kind that create "it works on localhost" confusion later.

Other common misses:

Copying an example without deciding the host

Examples teach server shape. They do not decide your deployment model for you.

Shipping remote HTTP before local inspection

If Inspector has not validated the server shape yet, your remote deployment has too many moving parts.

Treating tool descriptions as filler

Hosted clients rely on those descriptions for routing. Generic names and vague descriptions degrade selection quality fast.

Building transport and auth by hand when you already know the real goal is an app

If you already know the real task is "wrap this API, expose it cleanly, and publish an endpoint the team can use," you may be past the point where hand-coding every layer is the best use of time.

If the next job is a deployable app, not a transport exercise

There is a clear handoff point where building the server manually stops being the highest-value work. That point usually arrives when you need all of these at once:

  • real upstream API wiring
  • repeatable auth configuration
  • a stable remote endpoint
  • widgets or richer UI in MCP-capable clients
  • a non-engineer-friendly way to iterate on the app

That is where a visual builder starts making more sense than another week of transport and deployment plumbing.

If that is your situation, the practical path is:

This does not replace understanding the protocol. It replaces re-implementing the same glue once you already understand it.

Summary

Building an MCP server is easier when you make one decision early: is this server staying local, or does it need to become a remote /mcp endpoint?

Start with the official quickstart, keep the first server small, validate it with Inspector, and use stdio until you have a real reason to move to Streamable HTTP. When you do move, treat auth, origin validation, endpoint shape, and tool descriptions as part of the build, not cleanup work. Choose FastMCP when you want the shortest first server path. Choose the TypeScript SDK when your real environment is already Node or TypeScript. And if the goal is already a deployable API-backed app, stop polishing the weather tutorial and move to the app path.

FAQ

Should my first MCP server use stdio or Streamable HTTP?

Use stdio first unless you already know the client is hosted and needs a remote endpoint. It is the lower-complexity path and the one the official first-build tutorial uses to teach the server shape.

Does ChatGPT require a remote /mcp endpoint?

Yes for real ChatGPT app-style connections. OpenAI's current MCP docs treat the remote server URL as the thing you configure in ChatGPT, which means local-only subprocess setups are not the final connection model there.

Is FastMCP better than the TypeScript SDK?

Not categorically. FastMCP is usually faster for a first server. The TypeScript SDK is often the better fit when your existing services, validation, and runtime tooling already live in Node or TypeScript.

What is the biggest TypeScript SDK gotcha right now?

Version drift. The current GitHub main branch is already on pre-alpha v2 and still says v1.x is the recommended production line. Decide the version family first, then use matching docs and examples.

When should I stop hand-coding the server and switch to a builder?

When the hard part is no longer the protocol. If you need durable API wiring, shared deployment, auth configuration, endpoint publishing, and UI-rich client output, the bottleneck has shifted from "learn MCP" to "ship and maintain an app."