Skip to content
All skills
OPERATOR productivity v1.0.0 · Apache-2.0

Mcp Builder

Guide for building production-quality MCP servers in Python (FastMCP/Pydantic) or TypeScript (MCP SDK/Zod). 4-phase process — Research, Implement, Review, Evaluate.

Audited
Source
SHA-256
Last reviewed
How we audit →

Install in your agent

Tell your agent: "install the recipes skill, then add mcp-builder"
Or via curl: curl -sL https://recipes.wisechef.ai/skill -o ~/.claude/skills/recipes/SKILL.md

Full skill source · SKILL.md

MCP Builder — Build MCP Servers

4-phase guide for building production-quality MCP (Model Context Protocol) servers.

When to Use

  • Building a new MCP server for a tool/API integration
  • Wrapping an existing API as MCP tools
  • Need guidance on MCP best practices

Phase 1: Research

  1. Read the MCP spec: https://modelcontextprotocol.io/specification
  2. Check existing MCP servers for similar functionality
  3. Identify the tools, resources, and prompts needed
  4. Choose Python (FastMCP) or TypeScript (MCP SDK)

Phase 2: Implement

Python (FastMCP + Pydantic)

from fastmcp import FastMCP
from pydantic import BaseModel, Field

mcp = FastMCP("my-server", description="What it does")

class SearchParams(BaseModel):
    query: str = Field(description="Search query")
    limit: int = Field(default=10, description="Max results")

@mcp.tool()
def search(params: SearchParams) -> str:
    """Search for things."""
    # Implementation here
    return json.dumps(results)

if __name__ == "__main__":
    mcp.run()

TypeScript (MCP SDK + Zod)

import { Server } from "@modelcontextprotocol/sdk/server";
import { z } from "zod";

const server = new Server({ name: "my-server", version: "1.0.0" });

server.setRequestHandler("tools/call", async (request) => {
  const { name, arguments: args } = request.params;
  // Handle tool calls
});

Phase 3: Review

  • Each tool has a clear, actionable description
  • Error messages include what went wrong AND how to fix it
  • Workflow-oriented design (not just CRUD wrappers)
  • Input validation with helpful error messages
  • No hardcoded credentials (use env vars)

Phase 4: Evaluate

# Test compilation
python -m py_compile server.py

# Test startup (should start and accept stdio)
timeout 5s python server.py

# Test with mcporter
mcporter config add my-server python server.py
mcporter call --server my-server --tool search '{"query": "test"}'

Design Principles

  1. Workflow-oriented tools — not just data access, but task completion
  2. Actionable errors — tell the user what to do, not just what failed
  3. Sensible defaults — minimize required parameters
  4. Composable — tools should work together naturally
  5. Idempotent — safe to retry on failure

MCP SDK Docs

When NOT to Use

  • Wrapping a trivial single-function API — a direct tool call or shell command is simpler; don't build an MCP server for one endpoint
  • When the client already has a native plugin/extension — building a redundant MCP layer adds maintenance overhead with no benefit

Pitfalls

  • stdio vs HTTP transport confusion — FastMCP's mcp.run() defaults to stdio (for Claude Desktop / mcporter); passing transport="http" starts an HTTP server that Claude Desktop cannot connect to. Only use HTTP if you're targeting a remote deployment
  • FastMCP version conflicts — FastMCP < 1.0 and ≥ 1.0 have incompatible APIs (@mcp.tool() decorator changed behavior); pin the version in requirements.txt and check fastmcp.__version__ if tool registration silently fails
  • Returning raw Python objects — MCP tools must return strings (or structured content dicts); returning a dict or list directly causes a serialization error at runtime; always json.dumps() complex results
  • Missing if __name__ == "__main__" guard — without it, importing the module for testing also starts the server, blocking the test runner
  • Credential leakage in error messages — actionable errors are good, but never include env var values or API keys in error strings; log them server-side only
  • TypeScript: forgetting to call server.connect(transport) — the server object is initialized but never starts listening; tools/call handler is registered but never reachable

Verification

# Python: syntax check then startup test
python -m py_compile server.py && echo "Syntax OK"
timeout 5s python server.py && echo "Startup OK" || echo "Check exit code"

# Confirm a tool is callable end-to-end
mcporter config add my-server python server.py
mcporter call --server my-server --tool <tool_name> '{"key": "value"}'

# TypeScript: compile check
npx tsc --noEmit && echo "TS OK"
  • Confirm the returned JSON matches the expected schema before declaring the server ready