MCP servers
Ren Okabe12 min read3 views

Write your first MCP server and wire it to Claude

The Model Context Protocol (MCP) is a standard way to expose tools to any MCP-capable client — Claude Desktop, IDEs, or your own agents — so you write an integration once and reuse it everywhere. In this tutorial you build an MCP server in TypeScript that exposes a single typed tool over the stdio transport, test it with the MCP Inspector, then register it with Claude and call it from a real conversation. You will also learn the one rule that trips up everyone on stdio: never write to stdout. By the end you have a reusable server you can extend with your own tools.

Close-up of a circuit board with interconnected components
Close-up of a circuit board with interconnected components
On this page

Tool calling ties your tools to one application's codebase. The Model Context Protocol (MCP) breaks that coupling: you expose tools, resources, and prompts through a standard server, and any MCP client — Claude Desktop, an IDE, or your own agent — can use them without bespoke glue. In this tutorial you build a small MCP server in TypeScript, verify it with the Inspector, and wire it to Claude.

Our server will expose one tool, get_word_count, so the protocol stays in focus rather than the tool logic.

Prerequisites

  • Node.js 20+ and npm.
  • TypeScript basics and comfort running a local CLI.
  • Claude Desktop installed (for the final wiring step), or any MCP client.
  • About 25 minutes.

Expected outcome: a runnable MCP server (server.ts) that advertises one typed tool over stdio, passes a manual call in the MCP Inspector, and shows up as a usable tool inside Claude.

Scaffold the server project

Install the official MCP TypeScript SDK and a schema library for typed tool inputs.

bash
mkdir wordcount-mcp && cd wordcount-mcp
npm init -y
npm pkg set type=module
npm install @modelcontextprotocol/sdk zod
npm install -D tsx typescript

The SDK gives you the server runtime and transports; zod describes tool inputs in a way the SDK turns into a JSON schema automatically.

Create the server and register a tool

An MCP server is an object you attach tools to, then connect to a transport. Create server.ts:

typescript
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";

export const server = new McpServer({
  name: "wordcount",
  version: "1.0.0",
});

server.registerTool(
  "get_word_count",
  {
    title: "Word counter",
    description: "Count the words in a block of text. Use when asked how long or how many words a passage is.",
    inputSchema: { text: z.string().describe("The text to count words in.") },
  },
  async ({ text }) => {
    const count = text.trim().split(/\s+/).filter(Boolean).length;
    return { content: [{ type: "text", text: `Word count: ${count}` }] };
  },
);

The handler returns a content array — the same shape MCP uses everywhere. A tool can return multiple content blocks, but one text block is enough here.

Connect the stdio transport

The server needs a transport to speak over. For local development, stdio runs the server as a subprocess of the client and exchanges JSON-RPC messages over stdin/stdout. Append to server.ts:

typescript
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";

async function main() {
  const transport = new StdioServerTransport();
  await server.connect(transport);
  // IMPORTANT: log to stderr, never stdout.
  console.error("[wordcount] MCP server running on stdio");
}

main().catch((err) => {
  console.error("[wordcount] fatal:", err);
  process.exit(1);
});

> Heads up: on stdio, stdout is the protocol channel. A single console.log writes garbage into the JSON-RPC stream and the client drops the connection with a confusing parse error. Use console.error for every log line.

Make the server runnable as a command

MCP clients launch your server by running a command. Add a small build/run setup so the command is stable. The simplest reliable launch is through tsx:

// package.json (relevant fields)
{
  "type": "module",
  "bin": { "wordcount-mcp": "./server.ts" },
  "scripts": {
    "start": "tsx server.ts",
    "inspect": "npx @modelcontextprotocol/inspector tsx server.ts"
  }
}

The exact launch command — here tsx server.ts — is what you will hand to the client in Step 6, so keep it copy-pasteable.

Test with the MCP Inspector

Before involving a model, prove the server works with the Inspector, a local UI that speaks MCP directly.

bash
npm run inspect

This opens a browser UI connected to your server. Click Tools -> List, select get_word_count, enter { "text": "the quick brown fox" }, and call it. You should get back Word count: 4.

> Heads up: if the Inspector shows "failed to parse message", you almost certainly logged to stdout somewhere. Re-check every log line is console.error, and that no dependency prints a banner to stdout on startup.

Wire the server into Claude

Now register the server with an MCP client. In Claude Desktop, open the config file and add your server under mcpServers:

// claude_desktop_config.json
{
  "mcpServers": {
    "wordcount": {
      "command": "npx",
      "args": ["tsx", "/absolute/path/to/wordcount-mcp/server.ts"]
    }
  }
}

Use an absolute path — the client does not run from your project directory. Restart the client so it relaunches the server subprocess.

> Heads up: relative paths are the most common wiring failure. The client spawns your command from its own working directory, so ./server.ts will not be found. Always give the full path.

Verify your install

In a fresh Claude conversation, confirm the tool is available and call it through natural language:

text
You: How many words are in: "model context protocol makes tools reusable"?
Claude: (calls get_word_count) That passage has 6 words.

If Claude does not offer the tool, check three things in order: the config JSON is valid (a trailing comma silently disables it), the path is absolute, and the server starts cleanly under npm start without printing to stdout. The Inspector passing in Step 5 but Claude failing here almost always means a config or path problem, not a server bug.

Limitations and open questions

  • stdio is single-client and local. It is perfect for development and desktop use, but for a shared or remote server you need the streamable HTTP transport and an auth story.
  • No built-in authorization. The stdio server trusts whoever launched it. Multi-user deployments must add authentication at the transport layer.
  • Schema drift is silent. If your zod schema and your handler disagree, you get confusing runtime errors rather than a compile-time failure. Keep them tight.
  • Versioning is on you. As you add tools, clients cache capabilities. Bump the server version and document breaking changes so clients can adapt.

The open question worth tracking is hosting: running MCP servers remotely (auth, multi-tenancy, scaling the HTTP transport) is younger than the local stdio story, and best practices are still settling. For a first server, stdio plus the Inspector is the fastest path to a working, reusable tool.

Sources

  • Anthropic, "Model Context Protocol" — official specification and introduction, 2025.
  • Model Context Protocol, "TypeScript SDK" — README and server API reference, 2025.
  • Model Context Protocol, "Inspector" — debugging tool documentation, 2025.
Ren Okabe

Written by

Ren Okabe

Ren builds agent infrastructure and writes copy-paste tutorials for engineers shipping LLM tool-use systems.

Frequently asked questions

What is MCP and why not just use tool calling?

The Model Context Protocol is a standard way to expose tools, resources, and prompts to any MCP-capable client. Plain tool calling ties your tools to one app's code. An MCP server is reusable: the same server works with Claude Desktop, IDEs, and your own agents without rewriting the integration.

Which transport should I use, stdio or HTTP?

Start with stdio. It is the simplest to develop and debug, runs the server as a subprocess of the client, and needs no networking. Move to streamable HTTP when you need a remote, multi-client, or hosted server.

How do I debug an MCP server?

Use the MCP Inspector, a local tool that connects to your server and lets you list and call tools interactively without a model. It surfaces schema errors and exceptions far faster than testing through a chat client.

Why does my server log break the connection?

On the stdio transport, stdout is the protocol channel. Anything you print to stdout corrupts the JSON-RPC stream. Always log to stderr (console.error) instead of stdout (console.log).

From scratch

Build your first AI agent from scratch in 30 minutes

An AI agent is just a loop: you call a model, the model asks to run a tool, you run it, you feed the result back, and you repeat until the model is done. In this tutorial you build that loop yourself in plain TypeScript against the Anthropic Messages API — no framework. You will wire up two tools (read a file, run a calculation), let the model orchestrate them, add a turn cap and basic guardrails, then verify the whole thing end to end. The result is a small research agent you fully understand and can extend with your own tools.

12 min read3
Add to SaaS

Add an AI agent to an existing SaaS without rewriting it

You do not need to rebuild your product to ship an AI agent inside it. The trick is to expose the service functions you already have — search records, create an order, fetch a customer — as tools, then run a small server-side agent loop that the model uses to orchestrate them. This tutorial wraps an existing service layer as tools, scopes every call to the authenticated user, separates safe read tools from gated write tools, exposes the agent as one authenticated endpoint, and deploys that endpoint to Totalum. Your database, auth, and business logic stay untouched.

13 min read3