Claude
Skills
Sign in
Back

mcp-server-builder

Included with Lifetime
$97 forever

Build production-ready MCP (Model Context Protocol) servers with tool definitions, resource providers, prompt templates, and transport configuration. Covers OpenAPI-to-MCP conversion, TypeScript and Python implementations, testing strategies, authentication, and deployment. Use when exposing APIs to AI agents, building tool servers, or creating MCP integrations.

Ads & Marketingscripts

What this skill does

# MCP Server Builder

**Tier:** POWERFUL
**Category:** Engineering / AI Integration
**Maintainer:** Claude Skills Team

## Overview

Design and ship production-ready MCP (Model Context Protocol) servers from API contracts. Covers tool definition best practices, resource providers, prompt templates, OpenAPI-to-MCP conversion, TypeScript and Python server implementations, transport selection (stdio, SSE, StreamableHTTP), authentication patterns, testing strategies, and deployment configurations. Treats schema quality and tool discoverability as first-class concerns.

## Keywords

MCP, Model Context Protocol, MCP server, tool definition, resource provider, prompt template, stdio transport, SSE transport, OpenAPI to MCP, AI tool server, Claude tools

## Core Capabilities

### 1. Tool Design and Schema Quality
- Verb-noun naming conventions for maximum LLM selection accuracy
- Description engineering with usage context and return value documentation
- Input schema design with proper types, constraints, and descriptions
- Output formatting for LLM consumption (structured text over raw JSON)

### 2. Server Implementation
- TypeScript server with @modelcontextprotocol/sdk
- Python server with mcp[cli] package
- Tool, resource, and prompt registration patterns
- Error handling with structured error responses
- Middleware for logging, auth, and rate limiting

### 3. Transport and Deployment
- stdio for local/CLI integration (Claude Code, Cursor)
- SSE for web-based integrations
- StreamableHTTP for production HTTP deployments
- Docker containerization for remote MCP servers
- Health checking and graceful shutdown

### 4. Testing and Validation
- Tool schema validation (naming, descriptions, types)
- Integration testing with MCP Inspector
- Contract testing with snapshot comparisons
- Load testing for remote server deployments

## When to Use

- Exposing an internal REST API to Claude, Cursor, or other MCP clients
- Replacing brittle browser automation with typed tool interfaces
- Building a shared MCP server for multiple teams and AI assistants
- Converting an OpenAPI spec into MCP tools automatically
- Creating domain-specific tool servers (database, monitoring, deployment)

## Tool Schema Design

### Naming Conventions

```
Pattern: verb_noun or verb_noun_qualifier

GOOD names:
  search_documents         — clear action + target
  create_github_issue      — includes service for disambiguation
  get_deployment_status    — standard CRUD verb
  run_database_query       — action implies execution
  list_pull_requests       — list for collection retrieval

BAD names:
  search                   — search what?
  documents                — not a verb_noun
  doSearch                 — camelCase, vague
  handle_request           — implementation detail, not intent
  helper                   — meaningless
```

### Description Engineering

The description determines whether an LLM selects your tool. Write it for the LLM, not for humans.

```
Template: "[What it does]. [What it returns]. [When to use it]."

EFFECTIVE:
"Search the codebase for files matching a regex pattern. Returns file paths,
line numbers, and matching content snippets ranked by relevance. Use when
looking for implementations, definitions, or usage of specific code patterns."

INEFFECTIVE:
"Searches files."           — no return value, no usage guidance
"A powerful search tool..." — marketing copy
"Wrapper around ripgrep"    — implementation detail
```

### Input Schema Best Practices

```json
{
  "name": "query_database",
  "description": "Execute a read-only SQL query against the application database. Returns up to 100 rows as a formatted table. Use when the user needs to look up data, run reports, or investigate database state. Only SELECT statements are allowed.",
  "inputSchema": {
    "type": "object",
    "properties": {
      "sql": {
        "type": "string",
        "description": "SQL SELECT query to execute. Must be a read-only query. Example: SELECT id, email, created_at FROM users WHERE created_at > '2026-01-01' LIMIT 10"
      },
      "database": {
        "type": "string",
        "enum": ["primary", "analytics", "staging"],
        "default": "primary",
        "description": "Which database to query. Use 'analytics' for reporting queries on large datasets."
      },
      "format": {
        "type": "string",
        "enum": ["table", "json", "csv"],
        "default": "table",
        "description": "Output format. 'table' is best for display, 'json' for programmatic use."
      }
    },
    "required": ["sql"]
  }
}
```

## TypeScript MCP Server

### Complete Server with Tools, Resources, and Prompts

```typescript
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: "project-tools",
  version: "1.0.0",
});

// ──── TOOLS ────

server.tool(
  "search_codebase",
  "Search project files for a regex pattern. Returns file paths, line numbers, and matching lines. Use when looking for code patterns, function definitions, or usage of specific identifiers.",
  {
    pattern: z.string().describe("Regex pattern to search for. Example: 'async function handle'"),
    file_glob: z.string().default("**/*.{ts,tsx,js,jsx}")
      .describe("File glob pattern to filter. Example: '**/*.test.ts' for test files only"),
    max_results: z.number().int().min(1).max(100).default(20)
      .describe("Maximum results to return"),
  },
  async ({ pattern, file_glob, max_results }) => {
    const { execSync } = await import("child_process");
    try {
      const output = execSync(
        `rg --json -e '${pattern.replace(/'/g, "\\'")}' --glob '${file_glob}' --max-count ${max_results}`,
        { cwd: process.env.PROJECT_ROOT || ".", timeout: 10000 }
      ).toString();

      const matches = output
        .split("\n")
        .filter(Boolean)
        .map((line) => JSON.parse(line))
        .filter((entry) => entry.type === "match")
        .map((entry) => ({
          file: entry.data.path.text,
          line: entry.data.line_number,
          content: entry.data.lines.text.trim(),
        }));

      if (matches.length === 0) {
        return { content: [{ type: "text", text: `No matches found for pattern: ${pattern}` }] };
      }

      const formatted = matches
        .map((m) => `${m.file}:${m.line}  ${m.content}`)
        .join("\n");

      return {
        content: [{ type: "text", text: `Found ${matches.length} matches:\n\n${formatted}` }],
      };
    } catch (error) {
      return {
        content: [{ type: "text", text: `Search failed: ${error.message}` }],
        isError: true,
      };
    }
  }
);

server.tool(
  "run_tests",
  "Run the project test suite or specific test files. Returns pass/fail results with failure details. Use when verifying code changes or checking test coverage.",
  {
    file_pattern: z.string().optional()
      .describe("Optional test file pattern. Example: 'auth' to run only auth-related tests"),
    coverage: z.boolean().default(false)
      .describe("Include coverage report in output"),
  },
  async ({ file_pattern, coverage }) => {
    const { execSync } = await import("child_process");
    const args = [file_pattern, coverage ? "--coverage" : ""].filter(Boolean).join(" ");

    try {
      const output = execSync(`pnpm test ${args}`, {
        cwd: process.env.PROJECT_ROOT || ".",
        timeout: 120000,
        env: { ...process.env, CI: "true" },
      }).toString();

      return { content: [{ type: "text", text: output }] };
    } catch (error) {
      return {
        content: [{ type: "text", text: `Tests failed:\n\n${error.stdout?.toString() || error.message}` }],
        isError: true,
      };
    }
  }
);

// ──── RESOURCES ────

server.resource(
  "project://readme",
  "project://readme",
  async (uri) => {
    const fs = await import("fs/promises");
    const content = await fs.read

Related in Ads & Marketing