MCP Server Examples
This guide provides working MCP server examples you can copy and customize. Each example is a complete, runnable server that demonstrates a different use case -- from wrapping REST APIs to querying databases to file operations.
Quickstart: Minimal MCP Server
The smallest possible MCP server with one tool. This uses the official @modelcontextprotocol/sdk with stdio transport.
Setup
mkdir my-mcp-server && cd my-mcp-server
npm init -y
npm install @modelcontextprotocol/sdk zod
npm install -D typescript tsx
npx tsc --init
src/index.ts
#!/usr/bin/env node
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-world",
version: "1.0.0",
});
server.tool(
"greet",
"Say hello to someone",
{ name: z.string().describe("Name to greet") },
async ({ name }) => ({
content: [{ type: "text", text: `Hello, ${name}!` }],
})
);
const transport = new StdioServerTransport();
await server.connect(transport);
Test it
# Run directly with tsx
npx tsx src/index.ts
# Or add to Claude Code
claude mcp add hello -- npx tsx src/index.ts
Example: REST API Wrapper
The most common MCP server pattern: wrapping an existing REST API so Claude can query it. This example wraps a weather API.
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: "weather",
version: "1.0.0",
});
server.tool(
"get_weather",
"Get current weather for a city",
{
city: z.string().describe("City name (e.g. 'London', 'New York')"),
units: z.enum(["metric", "imperial"]).optional()
.describe("Temperature units (default: metric)"),
},
async ({ city, units }) => {
const u = units || "metric";
const res = await fetch(
`https://api.weatherapi.com/v1/current.json?key=${process.env.WEATHER_API_KEY}&q=${encodeURIComponent(city)}`
);
if (!res.ok) throw new Error(`Weather API error: ${res.status}`);
const data = await res.json();
const temp = u === "imperial"
? `${data.current.temp_f}°F`
: `${data.current.temp_c}°C`;
return {
content: [{
type: "text",
text: JSON.stringify({
city: data.location.name,
country: data.location.country,
temperature: temp,
condition: data.current.condition.text,
humidity: `${data.current.humidity}%`,
wind: `${data.current.wind_kph} km/h`,
}, null, 2),
}],
};
}
);
const transport = new StdioServerTransport();
await server.connect(transport);
Pattern: Use z.string().describe() for every parameter. The description helps Claude understand what values to pass. Be specific -- "City name (e.g. 'London')" is better than just "City".
Example: Database Query Server
An MCP server that gives Claude read-only access to a SQLite database. Includes schema inspection and parameterized queries.
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
import Database from "better-sqlite3";
const DB_PATH = process.env.DB_PATH || "./data.db";
const db = new Database(DB_PATH, { readonly: true });
const server = new McpServer({
name: "sqlite-query",
version: "1.0.0",
});
server.tool(
"list_tables",
"List all tables in the database",
{},
async () => {
const tables = db.prepare(
"SELECT name FROM sqlite_master WHERE type='table' ORDER BY name"
).all();
return {
content: [{ type: "text", text: JSON.stringify(tables, null, 2) }],
};
}
);
server.tool(
"describe_table",
"Show column names and types for a table",
{ table: z.string().describe("Table name") },
async ({ table }) => {
// Validate table name (prevent injection)
const exists = db.prepare(
"SELECT 1 FROM sqlite_master WHERE type='table' AND name=?"
).get(table);
if (!exists) throw new Error(`Table '${table}' not found`);
const cols = db.prepare(`PRAGMA table_info('${table}')`).all();
return {
content: [{ type: "text", text: JSON.stringify(cols, null, 2) }],
};
}
);
server.tool(
"query",
"Run a read-only SQL query (SELECT only)",
{
sql: z.string().describe("SQL SELECT query"),
limit: z.number().int().min(1).max(100).optional()
.describe("Max rows to return (default: 20)"),
},
async ({ sql, limit }) => {
// Only allow SELECT statements
const trimmed = sql.trim().toUpperCase();
if (!trimmed.startsWith("SELECT")) {
throw new Error("Only SELECT queries are allowed");
}
const maxRows = limit || 20;
const finalSql = sql.includes("LIMIT")
? sql
: `${sql} LIMIT ${maxRows}`;
const rows = db.prepare(finalSql).all();
return {
content: [{
type: "text",
text: JSON.stringify({ rows, count: rows.length }, null, 2),
}],
};
}
);
const transport = new StdioServerTransport();
await server.connect(transport);
Example: File Operations Server
A server that provides sandboxed file operations within a specified directory. Useful for giving Claude access to project assets, config files, or data directories.
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
import { readdir, readFile, stat } from "fs/promises";
import { join, resolve, relative } from "path";
const ROOT = resolve(process.argv[2] || ".");
// Ensure path stays within ROOT
function safePath(p: string): string {
const full = resolve(ROOT, p);
if (!full.startsWith(ROOT)) throw new Error("Path outside allowed directory");
return full;
}
const server = new McpServer({ name: "files", version: "1.0.0" });
server.tool(
"list_files",
"List files in a directory",
{
path: z.string().optional().describe("Relative path (default: root)"),
},
async ({ path }) => {
const dir = safePath(path || ".");
const entries = await readdir(dir, { withFileTypes: true });
const files = entries.map((e) => ({
name: e.name,
type: e.isDirectory() ? "directory" : "file",
}));
return {
content: [{ type: "text", text: JSON.stringify(files, null, 2) }],
};
}
);
server.tool(
"read_file",
"Read the contents of a file",
{
path: z.string().describe("Relative path to the file"),
},
async ({ path }) => {
const file = safePath(path);
const content = await readFile(file, "utf-8");
return {
content: [{ type: "text", text: content }],
};
}
);
server.tool(
"file_info",
"Get file metadata (size, modified date)",
{
path: z.string().describe("Relative path to the file"),
},
async ({ path }) => {
const file = safePath(path);
const s = await stat(file);
return {
content: [{
type: "text",
text: JSON.stringify({
path: relative(ROOT, file),
size: s.size,
modified: s.mtime.toISOString(),
isDirectory: s.isDirectory(),
}, null, 2),
}],
};
}
);
const transport = new StdioServerTransport();
await server.connect(transport);
Example: Server with Resources
MCP servers can also expose resources -- named pieces of data that Claude can read. Resources are like files or documents the server makes available.
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
const server = new McpServer({ name: "docs", version: "1.0.0" });
// Expose a resource
server.resource(
"project-readme",
"readme://project",
async (uri) => ({
contents: [{
uri: uri.href,
mimeType: "text/markdown",
text: "# My Project\n\nThis is the project documentation...",
}],
})
);
// Expose a dynamic resource with a template
server.resource(
"config",
"config://{name}",
async (uri) => {
const name = uri.pathname;
const configs: Record<string, string> = {
"database": JSON.stringify({ host: "localhost", port: 5432 }),
"cache": JSON.stringify({ ttl: 3600, maxSize: 1000 }),
};
const content = configs[name] || "Config not found";
return {
contents: [{
uri: uri.href,
mimeType: "application/json",
text: content,
}],
};
}
);
const transport = new StdioServerTransport();
await server.connect(transport);
Testing Your Server
Test your MCP server locally before publishing:
# Test with Claude Code (recommended)
claude mcp add my-server -- npx tsx src/index.ts
# Then ask Claude to use the tools:
# "Use the my-server tools to..."
# Inspect the server
npx @modelcontextprotocol/inspector npx tsx src/index.ts
Publishing to npm
To share your MCP server, publish it to npm:
# package.json
{
"name": "@yourscope/mcp-server-name",
"version": "1.0.0",
"type": "module",
"bin": { "mcp-server-name": "./dist/index.js" },
"scripts": { "build": "tsc", "start": "node dist/index.js" },
"dependencies": { "@modelcontextprotocol/sdk": "^1.12.1" }
}
# Build and publish
npm run build
npm publish --access public
Once published, anyone can install it:
claude mcp add server-name -- npx -y @yourscope/mcp-server-name
After publishing, submit your MCP server to Skills Playground to get listed in the directory with install tracking and a README badge.
Browse the MCP Servers directory to see 5,000+ published MCP servers, or read the MCP Servers Guide for installation instructions.