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.mdsource 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
| Component | TypeScript Import |
|---|---|
| Server | |
| Stdio | |
| SSE | |
| InMemory | |
| Client | |
| Transport | Use Case |
|---|---|
| stdio | Local tools, CLI integration, Claude Desktop |
| SSE | Remote servers, web-accessible tools |
| In-memory | Testing, same-process communication |
MCP Inspector:
npx @modelcontextprotocol/inspector <command> <args>