Tutorial

MCP Servers in Production: Building and Deploying Model Context Protocol Tools for Claude and Codex

A senior engineer's guide to production MCP servers: transport, deployment, auth, and tool design for Claude Code and Codex—beyond hello world.

You've hit the wall every engineer hits with Claude Code or Codex eventually: the agent is genuinely useful until it needs something that lives outside the chat window — your internal API, your Postgres instance, your deployment pipeline — and suddenly you're back to copy-pasting JSON responses into a prompt like it's 2023. That copy-paste loop is the tell that you need a tool-calling layer, not a bigger context window, and the Model Context Protocol (MCP) is the standard that most of the ecosystem has converged on for building it.

This article is not "hello world MCP." It's the set of architectural decisions — transport, deployment model, auth, tool design — that separate a weekend demo from a server you'd actually point at production infrastructure, worked through with a concrete example wired into both Claude Code and Codex.

What an MCP server actually is, and what problem it solves

An MCP server is a small service that exposes a defined set of capabilities — tools, data, prompt templates — to an AI application using a shared protocol, so that any compliant AI client can discover and call those capabilities without a bespoke integration [[ki_019ee988-3472-7055-bd10-410546c04b6a]]. The problem it solves is sometimes called the N×M problem: before a shared protocol existed, connecting N different tools to M different AI assistants meant building and maintaining N×M custom integrations, because each assistant had its own plugin format and each tool had its own API shape. MCP collapses that to N+M — you write one server for your tool, and it works with any client that speaks MCP.

Anthropic introduced the Model Context Protocol and announced it as an open standard, positioning it as a way to connect AI assistants to data sources like content repositories, business tools, and development environments [[live_ce6dffa8]]. At launch, Anthropic shipped pre-built MCP servers for common enterprise systems — including Google Drive, Slack, GitHub, Git, and Postgres — and added local MCP server support to the Claude Desktop app, which is what let early adopters start wiring their own tools in without waiting on a vendor integration [[live_ce6dffa8]]. Early adopters included companies like Block and Apollo, and development tools vendors worked with Anthropic to bring MCP support into their own platforms [[live_ce6dffa8]].

What matters for you as a working engineer is what happened next: MCP didn't stay an Anthropic-only thing. It's been adopted widely enough across the ecosystem that building to the spec is a safer bet than building to any single vendor's plugin format. The protocol itself was also handed off to a vendor-neutral foundation structure rather than staying solely under one company's control, which is the kind of governance signal that makes it reasonable to invest engineering time in rather than treating it as a single-vendor fad [[serp_6621a73d,live_cf4971ea]].

Core architecture: hosts, clients, servers

MCP's architecture has three distinct roles, and getting the vocabulary straight matters once you're debugging a connection that isn't working:

  • MCP host — the application the human actually uses: Claude Desktop, Claude Code, or an IDE integration. The host manages the overall session and decides when to invoke the AI model.
  • MCP client — the piece embedded inside the host that speaks the MCP protocol to a specific server, handling the connection, message framing, and capability negotiation.
  • MCP server — the process you build, which exposes tools, resources, and prompts over a defined interface.

A host's MCP client maintains a direct connection to a server, and hosts can connect to multiple servers at once — for instance, separate servers for different systems. Under the hood, messages between client and server are JSON-RPC 2.0 calls, which gives you a predictable request/response/notification shape to reason about and debug rather than a bespoke wire format.

Protocol primitives: tools, resources, prompts

An MCP server can expose three kinds of capability, and conflating them is one of the most common design mistakes:

  • Tools are functions the model can call to take action or fetch something dynamic — query a database, trigger a deploy, create a ticket. Each tool declares a name, a description, and an input schema that defines the parameters it accepts.
  • Resources are data the model can read — files, database records, API responses, documentation — exposed more like addressable content than an action to invoke.
  • Prompts are predefined templates for common workflows, letting a server ship a reusable prompt structure rather than making every caller reconstruct it from scratch.

In practice, most custom servers built for internal infrastructure lean heavily on tools and lightly on resources and prompts, because the thing engineers actually want is "let the agent run this query" or "let the agent trigger this pipeline," not "let the agent browse this file tree". Tool design is also where OpenAI has published the most concrete, actionable guidance, and it's worth internalizing before you write your first handler [[live_8336816d]]:

  • Give your server a stable name and version so clients can reason about compatibility across upgrades rather than guessing [[live_8336816d]].
  • Define one focused tool per distinct user goal instead of a single tool with many unrelated modes jammed into its parameters — a create_deployment tool and a rollback_deployment tool, not one manage_deployment tool with a mode flag [[live_8336816d]].
  • Keep server instructions returned at initialization front-loaded — the most important context within roughly the first 512 characters — rather than re-explaining every tool in prose the model may not fully read [[live_8336816d]].
  • OpenAI's MCP server guidance advises: never put secrets, access tokens, or unnecessary personal data into tool results, and don't treat client-side hidden fields as a substitute for real authorization or secure storage [[live_8336816d]].

That last point deserves emphasis: it's tempting to stash a session token in a tool's return payload "just for this one debugging tool," and it's exactly the kind of shortcut that turns into an incident later.

Transport mechanisms: stdio vs. Streamable HTTP

MCP defines two transport mechanisms, and choosing between them is your first real architectural decision [[ki_019ee988-3472-7055-bd10-410546c04b6a]]:

  • stdio — the server runs as a local subprocess, and the client talks to it over standard input/output. This is the default for local development: no network, no auth complexity, the host just spawns your server binary.
  • Streamable HTTP — the server runs as a standalone network service, and the client connects over HTTP. This is what you need the moment more than one person, machine, or agent needs to reach the same server instance.

The decision is really a deployment decision wearing a transport costume. If your tool only ever needs to run on the engineer's own machine — reading local files, calling git, inspecting local Docker containers — stdio is simpler and has no attack surface beyond the local process. If your tool talks to shared infrastructure — a shared staging database, a deployment pipeline, an internal API multiple teammates need the agent to reach — you want Streamable HTTP so you run one server and everyone's agent connects to it, instead of every engineer's laptop needing local credentials to production systems.

Local vs. remote deployment

Local, stdio-based servers are still the right default for anything scoped to one developer's machine: fast to iterate on, nothing to deploy, and the blast radius of a bug is your own laptop. But the moment the tool needs to be shared, or needs credentials that shouldn't live on individual laptops, you're looking at a remote deployment: a long-running HTTP service, behind real authentication, that your team's agents connect to over a URL instead of a local binary path.

This is also where the ecosystem has been moving. A number of vendors now ship their MCP integrations as hosted, remote endpoints secured with OAuth rather than as something you install locally — the pattern is to point your agent at a URL and authorize once, rather than clone a repo and manage an API key file. The Roboflow MCP server is a concrete example of this shape: it connects coding agents like Claude and Codex to Roboflow's platform through a single hosted URL, letting an agent create projects, label images, pull datasets, and train models without the developer installing an SDK or pinning a server version locally [[src_8752e527]]. That's the model worth copying for your own internal tools once more than one person needs access: one hosted server, one auth flow, zero per-developer install steps.

Building your first server: SDKs and scaffolding

Both official SDKs — TypeScript and Python — follow the same conceptual shape: you instantiate a server object, register tools with a name, description, and input schema, and wire a handler function that executes when the model calls that tool. The TypeScript SDK fits naturally if your existing API or internal tooling is already a Node/TypeScript shop — pick whichever matches the language your target system (the API, the database client, the deploy script) is already written in, rather than introducing a second runtime just for the MCP layer.

A minimal worked example: imagine you maintain an internal API for checking deployment status across environments, and you want Claude Code or Codex to be able to answer "is staging healthy?" without you pasting curl output into the chat. The shape of that server is:

  1. Scaffold the server using the SDK's starter template for your language.
  2. Define one tool, get_deployment_status, with an input schema taking an environment string (staging, production) and nothing else — resist the urge to make it also trigger deploys; that's a second, separate tool.
  3. Write the handler to call your existing internal API, parse the response into a compact structure the model can reason about, and return it — without embedding any API key or auth token in the returned payload [[live_8336816d]].
  4. Add a clear, action-oriented description on the tool ("Returns current health and last-deploy timestamp for a named environment") so the model picks it correctly instead of confusing it with an unrelated logging tool.
  5. Run it locally over stdio first to confirm the handler logic works before you think about remote deployment at all.

This mirrors the real wall engineers hit: Claude Code's built-in tools are genuinely good for working with your local repo, but they have no way to reach an internal API, a database, or a deployment pipeline without a custom bridge — which is precisely the gap MCP is designed to close [[ki_019ee988-3472-7055-bd10-410546c04b6a]].

Connecting your server to Claude Code and Codex

Once your server runs — whether as a local stdio process or a deployed HTTP endpoint — both Claude Code and Codex can be configured to treat it as a tool provider, discovering its registered tools and making them available to the model during a session [[ki_019ee988-3472-7055-bd10-410546c04b6a]]. The practical workflow is the same regardless of client: register the server (pointing at either the local command to spawn, for stdio, or the URL, for HTTP), restart or reload the session so the client re-negotiates capabilities, and confirm the tool shows up in whatever "available tools" listing the client exposes.

A detail worth internalizing: MCP server configuration is intentionally client-agnostic at the protocol level, so the same server binary or endpoint can be registered with Claude Code, Codex, or any other compliant client without rewriting server code — only the client-side configuration differs. That's the entire point of the standard, and it's worth resisting any temptation to build client-specific hacks into your server just because you primarily use one agent today.

If you're also layering in a CLAUDE.md file for project-wide context, it's worth knowing the two mechanisms solve different problems: CLAUDE.md is loaded automatically at the start of a Claude Code session and persists project context and conventions, while an MCP server is what lets the agent act — call a live API, query a live database — rather than just read static instructions [[ki_019fbb8c-34da-71a8-a7d5-f301d4a41e5c]].

Authentication and authorization

A stdio server runs locally with your local permissions, so it can do anything you can do locally. The moment you go remote over HTTP, authentication becomes the central design question, because you're now exposing a network-reachable endpoint that can call internal systems on the model's behalf.

My recommendation: for a remote server, prefer OAuth-style delegated authorization with short-lived, scoped tokens over a long-lived static secret sitting in a config file, and validate the token on every request rather than trusting a connection once it's established. Scope tokens as narrowly as you can — read-only versus write permissions is the obvious starting split — and enforce that distinction at the tool level, not just at the connection level, so a token's permissions are actually checked at the point where an action happens. If you're building a server that only your own team will ever use, a simpler API-key-plus-environment-variable approach is defensible as a starting point — but treat it as a known shortcut to revisit once more than a handful of people, or any external party, needs access.

Security considerations beyond auth

Authentication gets a server in the door; the rest of production hardening is what keeps it safe once it's there. Worth treating as non-negotiable before you point a remote MCP server at anything that matters:

  • Rate limiting on tool calls, so a misbehaving agent loop (or a compromised client) can't hammer a downstream system or run up a cloud bill.
  • Audit logging of every tool invocation — who, what tool, what parameters, what result — because "the AI did it" is not an acceptable incident postmortem without a trail behind it.
  • Secrets management that keeps credentials in a proper secrets store rather than environment variables checked into a repo or embedded in tool code.
  • Access control at the tool level, not just the connection level — a token that authenticates successfully should still be checked against what that specific tool is allowed to do.
  • Treating tool inputs as untrusted, the same way you'd treat any external API input, since the model's output (including the parameters it chooses to pass) isn't guaranteed to be well-formed or safe by construction.

None of this is MCP-specific wisdom, exactly — it's the same production checklist you'd apply to any internet-facing service — but it's easy to skip when the thing in front of you feels like "just a tool for my AI assistant" rather than "a service with real blast radius".

Advanced protocol features worth knowing about

Beyond the basic tool-call-and-respond loop, the protocol supports richer interaction patterns that matter once you're running servers at any real scale: sampling (letting a server request a model completion as part of handling a tool call), progress notifications (so long-running operations can report status back to the client instead of blocking silently), a roots permission model for scoping what a server is allowed to touch, and a stateless HTTP configuration option specifically aimed at making servers easier to scale horizontally [[src_aa2bf030]]. You don't need all of these for a first internal tool, but stateless HTTP in particular is worth planning for early if you expect your server to ever run behind a load balancer with multiple instances — retrofitting statelessness onto a server built assuming a single persistent connection is a much bigger rewrite than designing for it from the start.

Production deployment and scaling

Treat a remote MCP server like any other production HTTP service, because that's what it is: it needs a deployment pipeline, not a developer running it in a terminal tab. Container it, put it behind your normal CI/CD process, and deploy it the same way you'd deploy any other internal API — the fact that its consumers are AI agents rather than browsers doesn't change the deployment discipline it needs. Monitoring matters just as much here as for any other service: track request volume, error rates, and latency per tool, because a tool that silently starts failing is worse with an AI client than a human one, since the agent may retry, misinterpret the failure, or hallucinate a plausible-looking result rather than surfacing a clear error to a human. If you expect concurrent load from multiple engineers' agents, favor the stateless HTTP configuration mentioned above so you can run multiple server instances behind a load balancer rather than being pinned to one process holding connection state [[src_aa2bf030]].

Real-world use cases to model yours on

Beyond the deployment-status example above, the kinds of MCP servers worth building against your own infrastructure tend to fall into a few recognizable shapes: pulling structured data out of your own ticketing or project system so an agent can triage work without you summarizing it manually; wrapping a database connection so an agent can answer "how many rows changed in that migration" without you running the query yourself; or wrapping a deployment or CI pipeline so an agent can check status or kick off a controlled action within tightly scoped permissions. The Roboflow example is useful precisely because it shows what a mature, hosted version of this looks like: a single URL, OAuth-backed, giving an agent real capability (create, label, train) against a real platform without any local install step [[src_8752e527]]. That's the target shape to build toward as your own internal servers mature past the local-stdio-for-one-developer stage.

Where to go from here

Start local, start with one tool, and resist the urge to build a general-purpose "do everything" server before you've proven a single focused tool earns its place in your agent's workflow. Once a tool is genuinely useful to more than one person, that's your signal to invest in the remote deployment, OAuth, and hardening work this article walked through — not before. The protocol is young enough that patterns are still settling, but the fundamentals — clear tool boundaries, proper auth, no secrets in results, real monitoring — are the same production instincts that have applied to every service you've ever shipped.

Frequently asked questions

Do I need to choose between stdio and Streamable HTTP permanently when I first build a server?

No — it isn't a permanent choice. Check your SDK's docs for how it supports both transports, since this varies by implementation. In practice it's reasonable to build and test locally over stdio and move to Streamable HTTP only once you need shared or remote access.

Can the same MCP server work with Claude Code and Codex without changes?

Yes, in principle — MCP is a shared protocol rather than a vendor-specific plugin format, so a compliant server should work across clients that implement the spec.

Is OAuth required for every remote MCP server?

Not strictly, but it's the stronger default for anything beyond a small trusted team, since it gives you scoped, revocable access tokens instead of a long-lived shared secret.

What's the biggest mistake teams make when designing MCP tools?

Building one large, multi-mode tool instead of several small, focused ones — it's harder for the model to use correctly and harder for you to secure and reason about [[live_8336816d]].

Does MCP replace things like CLAUDE.md or slash commands?

No — they solve different problems: MCP gives an agent the ability to act on live systems, while context files and reusable commands encode static project knowledge and repeatable workflows [[ki_019fbb8c-34da-71a8-a7d5-f301d4a41e5c,ki_01a0023c-e06b-71cd-9c2b-3ccddf7052c3]].