Agent-almanac scaffold-mcp-server
git clone https://github.com/pjt222/agent-almanac
T=$(mktemp -d) && git clone --depth=1 https://github.com/pjt222/agent-almanac "$T" && mkdir -p ~/.claude/skills && cp -r "$T/i18n/ja/skills/scaffold-mcp-server" ~/.claude/skills/pjt222-agent-almanac-scaffold-mcp-server-614efb && rm -rf "$T"
i18n/ja/skills/scaffold-mcp-server/SKILL.mdMCPサーバーのスキャフォールド
Generate a complete, runnable MCP server project from a tool specification document, using the official MCP SDK for TypeScript or Python.
使用タイミング
- You have a tool specification (from
or written manually) and need a working serveranalyze-codebase-for-mcp - Starting a new MCP server project and want correct structure from the start
- Migrating an existing tool integration to the MCP protocol
- Prototyping a tool surface to test with Claude Code before full implementation
- Need both the server scaffold and a test harness for CI
入力
- 必須: Tool specification document (YAML or JSON with tool names, parameters, return types)
- 必須: Target language (
ortypescript
)python - 必須: Transport type (
orstdio
)sse - 任意: Output directory (default: current directory)
- 任意: Package name and version
- 任意: Authentication method (
,none
,bearer-token
)api-key - 任意: Docker packaging (
ortrue
, default:false
)false
手順
ステップ1: Select SDK Language and Transport
1.1. Choose the implementation language based on project context:
- TypeScript: Best for Node.js ecosystems, web-adjacent tools, JSON-heavy workloads
- Python: Best for data science, ML, and scientific computing tool surfaces
1.2. Choose the transport mechanism:
- stdio: Default for local tool execution. Claude Code launches the server as a subprocess.
- SSE (Server-Sent Events): For remote/shared servers. Requires HTTP hosting.
1.3. Determine authentication requirements:
- none: Local stdio servers (process-level trust)
- bearer-token: Remote SSE servers with static tokens
- api-key: Remote servers with per-client keys
期待結果: Clear language, transport, and auth choices documented.
失敗時: If requirements are ambiguous, default to TypeScript + stdio + no auth for fastest time-to-working-server.
ステップ2: Initialize Project Structure
2.1. Create the project directory and initialize:
TypeScript:
mkdir -p $PROJECT_NAME && cd $PROJECT_NAME npm init -y npm install @modelcontextprotocol/sdk zod npm install -D typescript @types/node tsx npx tsc --init --target ES2022 --module nodenext --moduleResolution nodenext --outDir dist
Python:
mkdir -p $PROJECT_NAME && cd $PROJECT_NAME python -m venv .venv source .venv/bin/activate pip install mcp pydantic
2.2. Create the standard directory structure:
$PROJECT_NAME/ ├── src/ │ ├── index.ts|main.py # Server entry point │ ├── tools/ # One file per tool category │ │ ├── index.ts|__init__.py │ │ └── [category].ts|.py │ └── utils/ # Shared utilities │ └── validation.ts|.py ├── test/ │ ├── harness.ts|.py # MCP test harness │ └── tools/ │ └── [category].test.ts|.py ├── package.json|pyproject.toml ├── tsconfig.json # TypeScript only ├── Dockerfile # If Docker requested └── README.md
2.3. Add a bin entry for npm (TypeScript) or entry point for Python:
TypeScript package.json:
{ "name": "$PACKAGE_NAME", "version": "1.0.0", "type": "module", "bin": { "$PACKAGE_NAME": "./dist/index.js" }, "scripts": { "build": "tsc", "start": "node dist/index.js", "dev": "tsx src/index.ts", "test": "tsx test/harness.ts" } }
期待結果: A buildable project skeleton with all dependencies installed.
失敗時: If npm/pip install fails, check network connectivity and registry access. For TypeScript, ensure Node.js >= 18. For Python, ensure Python >= 3.10.
ステップ3: Implement Tool Handlers from Spec
3.1. Parse the tool specification document and for each tool, generate a handler:
TypeScript handler template:
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { z } from "zod"; export function registerTools(server: McpServer): void { server.tool( "tool_name", "Tool description from spec", { param1: z.string().describe("Parameter description"), param2: z.number().optional().default(10).describe("Optional param"), }, async ({ param1, param2 }) => { try { // TODO: Implement tool logic const result = await performAction(param1, param2); return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }], }; } catch (error) { return { content: [{ type: "text", text: `Error: ${(error as Error).message}` }], isError: true, }; } } ); }
Python handler template:
from mcp.server import Server from mcp.types import Tool, TextContent from pydantic import BaseModel class ToolNameParams(BaseModel): param1: str param2: int = 10 async def handle_tool_name(params: ToolNameParams) -> list[TextContent]: try: result = await perform_action(params.param1, params.param2) return [TextContent(type="text", text=json.dumps(result, indent=2))] except Exception as e: return [TextContent(type="text", text=f"Error: {e}")]
3.2. Generate one handler file per tool category from the specification.
3.3. Add input validation beyond type checking:
- String length limits
- Numeric range bounds
- Enum value constraints
- Required field enforcement
3.4. Add structured error responses for all anticipated failure modes.
期待結果: A handler file per category with typed parameters and error handling.
失敗時: If the spec contains ambiguous types, default to
string and add a TODO comment for manual refinement.
ステップ4: Configure Transport
4.1. Create the server entry point with the chosen transport:
stdio (TypeScript):
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { registerTools } from "./tools/index.js"; const server = new McpServer({ name: "$PACKAGE_NAME", version: "1.0.0", }); registerTools(server); const transport = new StdioServerTransport(); await server.connect(transport);
SSE (TypeScript):
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js"; import { registerTools } from "./tools/index.js"; const server = new McpServer({ name: "$PACKAGE_NAME", version: "1.0.0", }); registerTools(server); const transport = new SSEServerTransport("/messages", response); await server.connect(transport);
4.2. If authentication is required, add middleware:
- Bearer token: validate
headerAuthorization - API key: validate
headerX-API-Key
4.3. Add a shebang line for stdio servers to enable direct execution:
#!/usr/bin/env node
期待結果: A working entry point that starts the MCP server on the configured transport.
失敗時: If the SDK version does not match the import paths, check the
@modelcontextprotocol/sdk version and adjust imports. The SDK restructured paths between versions.
ステップ5: Create Test Harness
5.1. Build a test harness that validates every tool:
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 runTests(): Promise<void> { const server = createServer(); const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair(); await server.connect(serverTransport); const client = new Client({ name: "test-client", version: "1.0.0" }); await client.connect(clientTransport); // Test: tools/list returns all expected tools const tools = await client.listTools(); console.assert(tools.tools.length === EXPECTED_TOOL_COUNT); // Test: each tool with valid input for (const tool of tools.tools) { const result = await client.callTool({ name: tool.name, arguments: getTestInput(tool.name), }); console.assert(!result.isError, `${tool.name} failed`); } // Test: each tool with invalid input returns isError for (const tool of tools.tools) { const result = await client.callTool({ name: tool.name, arguments: getInvalidInput(tool.name), }); console.assert(result.isError, `${tool.name} should reject invalid input`); } console.log("All tests passed"); }
5.2. Create test fixtures for each tool: valid inputs, invalid inputs, and edge cases.
5.3. Add a
test script to package.json or pyproject.toml.
期待結果: A test harness that exercises every tool with both valid and invalid inputs.
失敗時: If
InMemoryTransport is not available in the SDK version, fall back to spawning the server as a subprocess and communicating via stdio pipes.
ステップ6: Generate Documentation and Configuration
6.1. Generate a
README.md with:
- Project description
- Installation instructions
- Claude Code configuration command
- Claude Desktop JSON configuration snippet
- Tool listing with descriptions and parameter schemas
- Development and testing instructions
6.2. Generate Claude Code registration command:
# stdio transport claude mcp add $PACKAGE_NAME stdio "node" "dist/index.js" # SSE transport claude mcp add $PACKAGE_NAME -e API_KEY=your_key -- mcp-remote http://localhost:3000/mcp
6.3. Generate Claude Desktop configuration snippet:
{ "mcpServers": { "$PACKAGE_NAME": { "command": "node", "args": ["path/to/dist/index.js"] } } }
6.4. If Docker was requested, generate a
Dockerfile:
FROM node:20-slim AS build WORKDIR /app COPY package*.json ./ RUN npm ci COPY . . RUN npm run build FROM node:20-slim WORKDIR /app COPY --from=build /app/dist ./dist COPY --from=build /app/node_modules ./node_modules COPY --from=build /app/package.json . ENTRYPOINT ["node", "dist/index.js"]
期待結果: Complete documentation and configuration files for immediate use.
失敗時: If the generated README has placeholder values, search the project for actual values to substitute. If Docker build fails, verify the base image matches the Node.js/Python version used.
バリデーション
- Project builds without errors (
or equivalent)npm run build - Server starts and responds to
JSON-RPC requesttools/list - Every tool from the specification is registered and discoverable
- Test harness passes for all tools with valid inputs
- Test harness confirms error responses for invalid inputs
- Claude Code can connect via
commandclaude mcp add - README includes working installation and configuration instructions
- All generated code passes linting (if configured)
よくある落とし穴
- SDK import path changes: The
package restructured its exports between versions. Always check the installed version's actual export paths.@modelcontextprotocol/sdk - Forgetting the shebang: stdio servers invoked directly need
as the first line to be executable.#!/usr/bin/env node - Blocking the event loop: Tool handlers in TypeScript must be
. Synchronous operations block all other tool calls on the server.async - Missing
in package.json: The MCP SDK uses ESM imports. Withouttype: "module"
, Node.js treats files as CommonJS and imports fail."type": "module" - Zod schema drift: If the tool spec evolves but Zod schemas are not updated, validation mismatches cause silent failures. Generate schemas from a single source of truth.
- stdout pollution: stdio transport uses stdout for JSON-RPC. Any
in tool handlers corrupts the protocol stream. Useconsole.log
or a file logger instead.console.error
関連スキル
- generate the tool specification this skill consumesanalyze-codebase-for-mcp
- manual server implementation for complex casesbuild-custom-mcp-server
- connect the scaffolded server to Claude Code/Desktopconfigure-mcp-server
- debug connectivity issues after deploymenttroubleshoot-mcp-connection
- package the server in Docker for distributioncontainerize-mcp-server