What is MCP?
Model Context Protocol (MCP) lets Claude access external services in a safe, standardized way. Build the server once and it works in Claude Code, Claude Desktop, and other MCP-compatible IDEs.
Pick a transport
| Transport | Good for | Auth |
|---|---|---|
| HTTP | Remote SaaS, multi-user | OAuth, API keys |
| stdio | Local CLI tools, system access | Env vars, tokens |
| SSE | Some legacy remote servers | OAuth |
New remote servers usually pick HTTP.
Prerequisites
- Node.js 18+ (or Python 3.10+)
- Claude Code installed
- A service or data source to expose
Initialize (TypeScript example)
mkdir my-mcp-server && cd my-mcp-server
npm init -y
npm install @modelcontextprotocol/sdk
npm install -D typescript @types/node
npx tsc --init
Server building blocks
An MCP server has three primitives:
- Tools — functions Claude can call
- Resources — data Claude can read (files, DB rows, etc.)
- Prompts — predefined prompt templates
Most servers only need Tools.
Define a Tool (weather example)
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";
const server = new McpServer({
name: "weather-server",
version: "1.0.0",
});
server.registerTool(
"get-weather",
{
title: "Get current weather",
description: "Returns current weather for a city",
inputSchema: {
city: z.string().describe("City name in English"),
},
},
async ({ city }) => {
const weather = await fetchWeather(city);
return {
content: [{ type: "text", text: JSON.stringify(weather, null, 2) }],
};
}
);
The description is the key signal Claude uses to decide when to call the tool. Make it specific.
Run on stdio transport
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
const transport = new StdioServerTransport();
await server.connect(transport);
Build and run:
npx tsc
node dist/index.js
Connect to Claude Code
1. Local stdio server
claude mcp add weather -- node /absolute/path/to/dist/index.js
Add --scope user to make it available across all projects, or --scope project to share with the team via .mcp.json.
2. Remote HTTP server
claude mcp add --transport http weather https://mcp.example.com/mcp
If OAuth is required, run /mcp in Claude Code to get the auth link.
3. Share at the project level (.mcp.json)
A .mcp.json at the project root lets your team share MCP config:
{
"mcpServers": {
"weather": {
"type": "http",
"url": "https://mcp.example.com/mcp"
}
}
}
Connect to Claude Desktop
Claude Desktop loads stdio MCP servers from its own config file. Instead of claude mcp add, you edit the JSON directly.
- macOS:
~/Library/Application Support/Claude/claude_desktop_config.json - Windows:
%APPDATA%\Claude\claude_desktop_config.json
Create the file if it doesn’t exist.
{
"mcpServers": {
"weather": {
"command": "node",
"args": ["/absolute/path/to/dist/index.js"]
}
}
}
After saving, fully quit (Cmd+Q) and relaunch the Desktop app — closing the window is not enough. Once it’s back, you can see the exposed tools from the tools panel below the chat input.
Designing permission-sensitive tools
When you expose anything powerful — shell execution, the file system, outbound network — split the surface into narrow tools. Claude Desktop’s permission model is per-tool allow/deny, so the wider one tool’s input schema is, the more dangerous a single “always allow” click becomes.
Bad — exposing a generic shell
server.registerTool("run_shell", {
description: "Runs an arbitrary shell command",
inputSchema: { command: z.string() },
}, async ({ command }) => { /* ... */ });
This one tool covers everything from ls to rm -rf and curl under the same permission grant. Once a user clicks “always allow,” it’s effectively unrestricted shell access.
Good — split by intent
server.registerTool("list_files", {
description: "Lists files in a directory",
inputSchema: { path: z.string() },
}, /* ... */);
server.registerTool("read_file", {
description: "Reads a text file",
inputSchema: { path: z.string() },
}, /* ... */);
server.registerTool("git_status", {
description: "Returns git status for the current repo",
inputSchema: {},
}, /* ... */);
Each tool has a constrained input schema, so unintended operations don’t slip through, and users can set different approval policies per tool.
Design principles
- Constrain with the schema, not the prompt — narrow
inputSchemabeats telling the model “don’t do dangerous things” indescription. - Make tools with side effects (writes, executes) visually distinct from read-only ones by name and return value.
- Re-validate path and URL arguments server-side against an allowlist — don’t trust whatever Claude passes in.
Test
claude
/mcp
The added server’s status and exposed tools show up. Then ask Claude in plain language to call the tool and confirm the result.
Distribution patterns
- Official SaaS — host an HTTP MCP server at your domain (e.g.
https://mcp.notion.com/mcp) with OAuth login. - Personal tool — publish as an npm package; document
claude mcp addin the README. - Marketplace listing — list on claude.com/plugins for discoverability and one-click install.
Next steps
- Read the implementations of Notion MCP, GitHub MCP, and Slack MCP for patterns.
- See the official MCP docs for advanced features (Resources, Prompts, OAuth providers).
- Token usage and latency matter — keep tool responses short and return structured JSON.
Frequently Asked Questions
What is an MCP server?
A server that lets Claude access external services (DBs, APIs, SaaS) through a standardized protocol. You define Tools (functions), Resources (data), and Prompts (templates) that Claude can call.
Which transport should I pick?
HTTP for remote services, stdio for local tools. HTTP fits remotely hosted, multi-user services. Use stdio for personal tooling or anything that needs local system access.
How do I connect it to Claude Code?
Remote HTTP: `claude mcp add --transport http <name> <url>`. Local stdio: `claude mcp add <name> -- <command>`. Project-level sharing goes in `.mcp.json`.
Can I use the same server in Claude Desktop?
For stdio MCP servers, add the same `mcpServers` block to `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) or `%APPDATA%\Claude\claude_desktop_config.json` (Windows). Fully quit and relaunch the Desktop app to pick up changes.
How do I handle OAuth?
The MCP SDK standardizes OAuth 2.1. Expose `/.well-known/oauth-authorization-server` metadata, then `/mcp` in Claude Code surfaces the auth link for browser sign-in.
Why publish my tool as MCP?
One server works in Claude Code, Claude Desktop, and any other MCP-compatible IDE. You don't have to build per-IDE integrations, which cuts maintenance cost.