Claude-initial-setup mcp-server-creation

install
source · Clone the upstream repo
git clone https://github.com/VersoXBT/claude-initial-setup
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/VersoXBT/claude-initial-setup "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/mcp-development/mcp-server-creation" ~/.claude/skills/versoxbt-claude-initial-setup-mcp-server-creation && rm -rf "$T"
manifest: skills/mcp-development/mcp-server-creation/SKILL.md
source content

MCP Server Creation

How to build MCP servers from scratch in TypeScript and Python. Covers project setup, transport configuration, tool/resource/prompt registration, lifecycle hooks, and testing.

When to Use

  • User wants to create a new MCP server
  • User is adding tools, resources, or prompts to an MCP server
  • User needs to configure stdio or SSE transport
  • User is testing or debugging an MCP server
  • User asks about MCP server architecture or lifecycle

Core Patterns

TypeScript MCP Server Scaffold

# Initialize project
mkdir my-mcp-server && cd my-mcp-server
npm init -y
npm install @modelcontextprotocol/sdk zod
npm install -D typescript @types/node
npx tsc --init
// src/index.ts - Minimal MCP server with stdio transport
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: "my-server",
  version: "1.0.0",
});

// Register a tool
server.tool(
  "greet",
  "Greet a user by name. Use when the user wants a personalized greeting.",
  { name: z.string().describe("The person's name") },
  async ({ name }) => ({
    content: [{ type: "text", text: `Hello, ${name}!` }],
  })
);

// Start with stdio transport
async function main() {
  const transport = new StdioServerTransport();
  await server.connect(transport);
  console.error("MCP server running on stdio");
}

main().catch(console.error);

Python MCP Server Scaffold

# Initialize project
mkdir my-mcp-server && cd my-mcp-server
pip install mcp
# server.py - Minimal MCP server with stdio transport
import asyncio
from mcp.server import Server
from mcp.server.stdio import stdio_server
import mcp.types as types

server = Server("my-server")

@server.list_tools()
async def list_tools() -> list[types.Tool]:
    return [
        types.Tool(
            name="greet",
            description="Greet a user by name. Use when the user wants a personalized greeting.",
            inputSchema={
                "type": "object",
                "properties": {
                    "name": {"type": "string", "description": "The person's name"}
                },
                "required": ["name"]
            }
        )
    ]

@server.call_tool()
async def call_tool(name: str, arguments: dict) -> list[types.TextContent]:
    if name == "greet":
        return [types.TextContent(type="text", text=f"Hello, {arguments['name']}!")]
    raise ValueError(f"Unknown tool: {name}")

async def main():
    async with stdio_server() as (read_stream, write_stream):
        await server.run(read_stream, write_stream, server.create_initialization_options())

if __name__ == "__main__":
    asyncio.run(main())

SSE Transport (HTTP)

For remote MCP servers accessible over HTTP.

// TypeScript - SSE transport
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
import express from "express";

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

// Register tools on server...

app.get("/sse", async (req, res) => {
  const transport = new SSEServerTransport("/messages", res);
  await server.connect(transport);
});

app.post("/messages", async (req, res) => {
  // Handle incoming messages from the transport
  await transport.handlePostMessage(req, res);
});

app.listen(3001, () => console.error("MCP SSE server on port 3001"));

Lifecycle Hooks

Handle server initialization and shutdown gracefully.

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

// The server handles lifecycle automatically, but you can add cleanup
process.on("SIGINT", async () => {
  console.error("Shutting down MCP server...");
  await server.close();
  process.exit(0);
});

process.on("SIGTERM", async () => {
  await server.close();
  process.exit(0);
});

Testing MCP Servers

Test your MCP server using the MCP Inspector or programmatically with an in-memory client.

# Use MCP Inspector for interactive testing
npx @modelcontextprotocol/inspector node dist/index.js
// Programmatic testing with in-memory transport
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { InMemoryTransport } from "@modelcontextprotocol/sdk/inMemory.js";
import { Client } from "@modelcontextprotocol/sdk/client/index.js";

async function testServer() {
  const server = new McpServer({ name: "test", version: "1.0.0" });
  // Register tools...

  const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
  await server.connect(serverTransport);

  const client = new Client({ name: "test-client", version: "1.0.0" });
  await client.connect(clientTransport);

  // Call a tool
  const result = await client.callTool({ name: "greet", arguments: { name: "World" } });
  console.assert(result.content[0].text === "Hello, World!");

  await client.close();
  await server.close();
}

Claude Desktop Configuration

Register your MCP server in Claude Desktop's config.

// ~/Library/Application Support/Claude/claude_desktop_config.json (macOS)
{
  "mcpServers": {
    "my-server": {
      "command": "node",
      "args": ["/absolute/path/to/dist/index.js"],
      "env": {
        "API_KEY": "your-key-here"
      }
    }
  }
}

Anti-Patterns

  • Using console.log in stdio servers (stdout is the transport channel -- use console.error)
  • Not validating tool inputs with a schema (leads to runtime crashes)
  • Blocking the event loop in tool handlers (use async operations)
  • Hardcoding secrets in server code instead of reading from environment variables
  • Not handling unknown tool names in the call_tool handler
  • Missing error handling in tool execution (unhandled errors crash the server)
  • Creating one massive tool instead of composing multiple focused tools

Quick Reference

ComponentTypeScript Import
Server
@modelcontextprotocol/sdk/server/mcp.js
Stdio
@modelcontextprotocol/sdk/server/stdio.js
SSE
@modelcontextprotocol/sdk/server/sse.js
InMemory
@modelcontextprotocol/sdk/inMemory.js
Client
@modelcontextprotocol/sdk/client/index.js
TransportUse Case
stdioLocal tools, CLI integration, Claude Desktop
SSERemote servers, web-accessible tools
In-memoryTesting, same-process communication

MCP Inspector:

npx @modelcontextprotocol/inspector <command> <args>