Skip to content

Build and share an MCP server

Give an agent a new ability — a tool it can call or a data source it can read — that it didn't ship with. You write a small program (an "MCP server") that exposes, say, "look up a grant in our database" or "file a ticket"; then anyone can plug it into their own agent and it gains that power. Share it two ways: hand over the code (they run it on their machine) or stand up a hosted version they point at by URL.

Time: ~2 min to your agent to scaffold one; then your real cost is the ability itself (the API or data it wraps) and, for the hosted route, deploying it. You'll need: Claude Code, and a clear idea of the one thing you want the agent to be able to do. Last verified: 2026-06-07 · commands checked against modelcontextprotocol.io and the Claude Code MCP docs. [confirmed]

New to all this? Do Set up Claude Code first (~10 min once), then come back. Want the agent to just follow a written procedure, not call live tools? A skill is lighter. Want a plain command people run in a terminal? Package a CLI tool.

Before you begin

  • Claude Code running in an empty folder — ~10 min once. It scaffolds the server, installs the SDK, and wires it in to test.
  • One clear ability. Name the single tool or data source: "search our donor list", "post to our Slack", "read today's forecast". One server can hold several tools, but start with one. [confirmed]
  • Whatever that ability needs — usually an API key or a database connection string for the thing you're wrapping. Keep it out of the code (see Secrets).
  • Decide the share shape now, because it changes what you build:
    • Code others run (stdio) — they download the files and their agent launches it locally. Simplest; no hosting; each recipient needs the SDK's runtime (Python or Node). [confirmed]
    • Hosted at a URL (HTTP) — you run it once online; recipients add one line pointing at the link. Best when the tool needs your keys or your database and you don't want to hand those out. [confirmed]

New to handing setup to an agent? Read How to ask your agent — one screen, then come back.

What an MCP server actually is

A plain background program that speaks one shared protocol (the Model Context Protocol), so any agent that speaks it — Claude Code, Claude Desktop, others — can discover and call the tools it offers. You describe each tool as a normal function with a name, a short description, and its inputs; the agent reads those descriptions and decides when to call it. You don't write agent-specific glue: write the server once, every MCP client can use it. [confirmed]

The official Python SDK (FastMCP) and TypeScript SDK turn an ordinary function into a tool from its type hints and docstring — so the work is mostly writing the function that does the thing, not protocol plumbing. [confirmed]

The default: ask your agent to scaffold it

With Claude Code running in an empty folder, say:

Scaffold a small MCP server in Python using the official SDK (FastMCP).
Give it one tool called "search_grants" that takes a query string and
returns matching rows from our Airtable base. Read the API key from an
environment variable, never hard-code it. Use stdio transport. Then add
it to my Claude Code with `claude mcp add` and confirm the tool shows up.

Swap in your own tool — its name, what it takes in, what it returns, and the API or database it talks to. Your agent installs the SDK (uv add "mcp[cli]" httpx for Python, or npm install @modelcontextprotocol/sdk zod@3 for TypeScript), writes the server file, registers your tool, and wires it into your own Claude Code so you can try it immediately. [confirmed]

If you'd rather it walk you through the design first, install Anthropic's scaffolding helper and let it interview you: in a Claude Code session, /plugin install mcp-server-dev@claude-plugins-official, then /mcp-server-dev:build-mcp-server — it asks about your use case and builds a stdio or hosted server to match. [confirmed]

Test it connects to your own agent

The fastest check is to plug the server into the Claude Code you already have and see the tool appear.

  1. Add it. For a local stdio server, point Claude Code at the command that runs it:
    claude mcp add my-tools -- uv run /ABSOLUTE/PATH/TO/server.py
    
    The -- matters: everything after it is the command that launches your server, kept separate from Claude's own flags. [confirmed]
  2. Confirm it loaded. Run claude mcp list — your server should report ✓ connected, not failed. [confirmed]
  3. See the tool. Start claude, then type /mcp — the panel shows each connected server with its tool count. If your tool's there, the agent can now call it. Ask it to, in plain language ("search grants for climate"). [confirmed]

That round-trip — added, connected, tool visible, agent calls it — is the whole test. If it works for you, it'll work for whoever you share it with.

Secrets: never bake a key into the code

If your tool needs an API key or a database password, don't put it in the files you share — read it from an environment variable instead (your agent does this if you ask, as in the sentence above). Then:

  • Sharing the code: the recipient supplies their own key. Ship a .env.example listing the names (AIRTABLE_API_KEY=), and your agent can pass it through when they add the server: claude mcp add --env AIRTABLE_API_KEY=their-key my-tools -- uv run server.py. [confirmed]
  • Hosting it: keep your key as a secret on the host, not in the repo — exactly the Fly secrets pattern. Recipients never see it; they just call your URL. [confirmed]

Share it, option A: hand over the code (they run it)

Best when the tool is self-contained or each person brings their own key. Put the server in a repo and include a tiny config snippet in the README so the recipient's agent knows how to launch it. The snippet is a .mcp.json file at the repo root:

{
  "mcpServers": {
    "my-tools": {
      "command": "uv",
      "args": ["run", "server.py"]
    }
  }
}

When they run the repo locally, their agent reads that file, launches the server, and the tool appears in their /mcp — Claude Code asks them to approve a project server once, for safety. Their cost: download the files, supply any key, approve once. ~2 min if they have an agent. [confirmed]

Catch: a stdio server runs on the recipient's machine, so they need the runtime it's written in (Python or Node). Pick the language your audience already has.

Best when the tool needs your keys or your live data, or your recipients aren't set up to run code. Build the server with HTTP transport instead of stdio — one line: mcp.run(transport="streamable-http") in Python — then deploy it like any running app. Tell your agent:

Switch this server to streamable-http transport, set the secrets as host
secrets (not in the code), and deploy it. Give me the public URL at the end.

You get back a https://… link. That link is the share. The recipient adds it in one line — no download, no runtime, no key on their side:

claude mcp add --transport http my-tools https://your-app.fly.dev/mcp

If your server needs a login, recipients run /mcp inside Claude Code to approve it (the standard OAuth prompt), or you hand them a token to pass as a header. ~1 min on their side. [confirmed]

Catch: you now run a service — it costs something to keep up (a sleep-to-zero host keeps that near $0, see Deploy to Fly), and your URL is the front door to whatever data the tool can reach. Gate it if it touches anything sensitive.

You're done when

claude mcp list shows your server connected and /mcp lists its tool — for you, and for at least one recipient who added it. From there, anyone's agent calls the ability in plain language; you never demo it by hand. [confirmed]

If it doesn't work

  • claude mcp list shows it "failed" / won't connect → the launch command is wrong or the server crashes on start. Run the exact command from your config by hand (uv run server.py) and read the error. For stdio, the usual culprit is the next item. [confirmed]
  • Connects, but the tool never appears in /mcp → most often a stdio server printing to stdout. On stdio, the agent and server talk over stdout in a strict format, so any stray print() (Python) or console.log() (Node) corrupts the channel and the handshake fails. Send all logging to stderr instead (print(..., file=sys.stderr), or console.error(...)). Tell your agent "make sure nothing logs to stdout." [confirmed]
  • "It won't connect" but you built an HTTP server → you can't add an HTTP server with the bare stdio form, and vice-versa. Local file → claude mcp add NAME -- <command> (stdio). A URL → claude mcp add --transport http NAME <url>. Mixing the two is the most common confusion. [confirmed]
  • Tool shows up but calling it errors → the function itself failed — usually a missing key or a bad request to the API you're wrapping. Check the key is set (--env for local, host secret for hosted), then paste the error to your agent. [confirmed]
  • Hosted server says "needs authentication" / 401 → the server wants a login. Run /mcp in Claude Code to complete the OAuth flow, or add the token as a header: claude mcp add --transport http NAME URL --header "Authorization: Bearer TOKEN". [confirmed]
  • Recipient's Claude Code ignores the repo's .mcp.json → project servers need a one-time approval for safety. They should run claude in the folder and approve it when prompted (reset later with claude mcp reset-project-choices). [confirmed]
  • Anything else → the Claude Code MCP reference and the MCP server quickstart cover the failure modes step by step. Hand the exact error to your agent — reading and fixing these is what it's best at.

Prefer to do it by hand?

No agent — just you, following the official quickstart. The short version, Python:

  1. Set up the project. uv init my-tools && cd my-tools, then uv venv and uv add "mcp[cli]" httpx. [confirmed]
  2. Write the server. In server.py, create the instance and one tool — FastMCP reads the function's type hints and docstring to build the tool definition:
    from mcp.server.fastmcp import FastMCP
    
    mcp = FastMCP("my-tools")
    
    @mcp.tool()
    async def search_grants(query: str) -> str:
        """Search our grants database. Args: query — what to look for."""
        ...  # your lookup here
        return result
    
    if __name__ == "__main__":
        mcp.run(transport="stdio")   # or "streamable-http" to host it
    
  3. Run it with uv run server.py. It waits for an MCP client. [confirmed]
  4. Add it to Claude Code: claude mcp add my-tools -- uv run server.py, then check /mcp. [confirmed]

TypeScript follows the same shape with @modelcontextprotocol/sdk and server.registerTool(...) — the quickstart has the full TypeScript tab. To host either, swap to HTTP transport and deploy.

Watch / read

YouTube blocked transcript pulls on 2026-06-07, so these aren't verified line-by-line — judged on title, length, and channel; lean on the written quickstart below if a video drifts.

Best written walkthrough: the official Build an MCP server quickstart — Python and TypeScript side by side, the weather-tool example, the stdio rule, and the client-config step. The authoritative source these commands were checked against. For wiring into Claude Code specifically, the Claude Code MCP docs. [confirmed]

Sources

Good to know

  • stdio vs HTTP is the one decision that shapes everything. stdio = a local process the recipient's agent launches (they need the runtime); HTTP = a service you host and they reach by URL (you hold the keys). The same server can do either by swapping one transport line; the share changes, not the tool logic. [confirmed]
  • The no-stdout rule trips up most first servers. On stdio, a single stray print corrupts the protocol channel — log to stderr. HTTP servers don't have this constraint. [confirmed]
  • SSE transport is deprecated in Claude Code — use HTTP for hosted servers. SSE still works (--transport sse) but new servers should be HTTP. [confirmed]
  • A hosted MCP URL is a live entry point to whatever the tool can reach. Treat it like any deployed service: secrets stay host-side, and add a login if it touches anything you wouldn't post publicly. See Who can see it? and Can you trust the company?. [confirmed]