MCP Server Examples

Updated February 2026 · TypeScript, Python & Go

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.

Contents
  1. Quickstart: Minimal MCP Server
  2. Example: REST API Wrapper
  3. Example: Database Query Server
  4. Example: File Operations Server
  5. Example: Server with Resources
  6. Testing Your Server
  7. Publishing to npm

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.