Guide
How to create a Claude MCP server
Starter code, plus what actually makes an MCP tool good
An MCP server is any process that speaks the Model Context Protocol. Claude — and every other MCP-compatible assistant — can call its tools. Anthropic maintains SDKs for TypeScript, Python, and other languages; the examples here use TypeScript.
Pick a transport
stdio is easiest for local, developer-focused servers. Claude Desktop launches your process, talks over stdin/stdout, kills it when Claude quits.
Streamable HTTP is what you want for a hosted server users can add remotely (OAuth, no local install). Use this if you're shipping the server to other people.
Minimal stdio server
Install the SDK:
npm install @modelcontextprotocol/sdk zod
server.ts:
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
const server = new McpServer({ name: "hello-mcp", version: "0.1.0" });
server.tool(
"greet",
"Greet a person by name.",
{ name: z.string().describe("The person's name") },
async ({ name }) => ({
content: [{ type: "text", text: `Hello, ${name}!` }],
}),
);
const transport = new StdioServerTransport();
await server.connect(transport);Wire it into Claude Desktop's claude_desktop_config.json with "command": "node", "args": ["/path/to/server.js"] and restart Claude.
Design tools an LLM will actually use well
- Name tools like functions.
search_notesbeatsnotesSearcher. The model reads names, so make them read like verbs. - Write descriptions for the model, not the human. Say when to use the tool and what shape the result has. The description is the prompt.
- Small, sharp inputs. One argument that maps cleanly to the task beats a dozen options. If you need a lot of knobs, split into two tools.
- Return text the model can quote. Structured JSON is fine, but include a short human-readable summary — the model will paste it into the answer.
- Fail loudly. Return an explicit error message in the content, not a silent empty result.
Going remote
To host the server publicly, swap StdioServerTransport for the SDK's HTTP transport, put it behind OAuth, and expose the endpoint at a URL. Users then add it in Claude via Connectors.
Doing this well — token issuance, scopes, revocation, rate limits, per-user data isolation — is real work. If you're building a product around private-data tools, it's often faster to buy a hosted MCP layer than build one from scratch. Muninn is exactly that for notes: a hosted MCP server with OAuth, row-level security, and a full tool suite you can wire straight into Claude.