mcp-builder
Build production-quality MCP (Model Context Protocol) servers that enable LLMs to interact with external services through well-designed tools. Use this skill when the user wants to create, scaffold, or implement an MCP server for any API or service, in any language. Triggers include requests to "build an MCP server", "create MCP tools", "implement MCP", "add tools for [service]", "connect [service] via MCP", or any task involving MCP server development, tool design, or MCP architecture decisions.
SKILL.md
| Name | mcp-builder |
| Description | Build production-quality MCP (Model Context Protocol) servers that enable LLMs to interact with external services through well-designed tools. Use this skill when the user wants to create, scaffold, or implement an MCP server for any API or service, in any language. Triggers include requests to "build an MCP server", "create MCP tools", "implement MCP", "add tools for [service]", "connect [service] via MCP", or any task involving MCP server development, tool design, or MCP architecture decisions. |
name: mcp-builder description: Build, test, and deploy production-quality MCP (Model Context Protocol) servers. Full lifecycle -- build locally, test interactively with MCP Inspector, iterate until working, then optionally deploy to Runlayer. Triggers include "build an MCP server", "create MCP tools", "implement MCP", "add tools for [service]", "connect [service] via MCP", or any MCP server development task.
MCP Server Builder
Build, test, and deploy MCP servers that enable LLMs to accomplish real-world tasks through well-designed tools. Server quality is measured by how effectively agents can use the tools, not by API coverage alone.
Before You Start
Ask the user which programming language they want to use before doing anything else. Use AskQuestion to prompt for their preferred language. Do not proceed until they answer. Then load the corresponding language-specific reference from references/:
- TypeScript -> read
references/typescript.md - Python -> read
references/python.md - Other -> use the language-agnostic patterns in
references/guidelines.mdand adapt to the chosen language
Apply the language-specific conventions (naming, SDK, validation library, project structure, idioms) from the loaded reference throughout the entire build.
Development Process
Follow six phases in order. Phases 1-5 are mandatory. Do NOT skip any phase. Do NOT declare the server complete until Phase 5 passes.
- Research & Plan -- Study the target API (endpoints, auth, data models, rate limits, pagination). Plan tool coverage. Prioritize comprehensive API coverage over workflow shortcuts when uncertain.
- Implement -- Build infrastructure first (API client, auth, errors, formatters), then tools incrementally.
- Review & Build -- Build, lint, fix all errors. Review for duplication, consistent errors, type coverage.
- Evaluate -- Create 10 complex realistic questions requiring multiple tool calls with stable verifiable answers.
- Verify (MANDATORY) -- Test the server by sending real MCP protocol messages. At minimum: initialize, tools/list, and one tool call. Iterate on failures. The server is NOT done until this passes. See
references/testing.md. - Deploy (Optional) -- When tests pass, offer to deploy to Runlayer via
uvx runlayer deploy. Seereferences/deploy.md.
Architecture Decisions
Determine these upfront:
Transport:
stdio-- local/CLI tools, single user, simple setupStreamable HTTP-- remote/cloud, multi-client, serverless. Avoid SSE (deprecated).
State:
- Stateless (recommended for remote) -- fresh server per request, horizontal scaling, no session leaks
- Stateful -- when session continuity is required
Auth:
- OAuth 2.1 -- remote servers with user identity
- API key via env vars -- simpler integrations
- None -- local-only tools
Language conventions (applied after user selects language):
| Language | Server name | Validation | SDK |
|---|---|---|---|
| TypeScript | {service}-mcp-server | Zod | @modelcontextprotocol/sdk |
| Python | {service}_mcp | Pydantic | mcp (FastMCP) |
| Other | {service}-mcp | JSON Schema | Implement protocol directly |
Tool Design Rules
- Name:
{service}_{action}_{resource}in snake_case. Always prefix with service name. - Required fields:
name,title,description,inputSchema,outputSchema,annotations - Annotations: Set
readOnlyHint,destructiveHint,idempotentHint,openWorldHinton every tool. - Descriptions: Must precisely match functionality. Include parameter docs, return schema, usage examples (when to use AND when NOT to use), and error conditions.
- Schemas: Use strict mode. Add constraints (min/max/pattern/enum) and
.describe()on every field. - Responses: Return both
content(text) andstructuredContent(typed data).
For detailed patterns, validation examples, response formatting, error handling, pagination, auth, testing, security, logging, and deployment guidance, read references/guidelines.md.
Project Structure
{service}-mcp-server/
├── src/
│ ├── index.ts # Entry point (dual-mode: local + serverless)
│ ├── tools/ # Tool implementations + registry
│ ├── resources/ # MCP resource handlers (if needed)
│ ├── services/ # API clients, external service wrappers
│ ├── schemas/ # Validation schemas
│ ├── utils/ # Shared helpers (retry, formatting, errors)
│ ├── auth/ # Auth, token storage, middleware
│ └── types/ # Type definitions
├── tests/
│ ├── unit/
│ └── integration/
└── README.md
Implementation Workflow
- Scaffold the project structure above
- Build shared utilities first:
makeApiRequest-- centralized HTTP client with auth, timeout, retrieshandleApiError-- maps HTTP status to MCP error codes with contextcreateToolResponse/createErrorResponse-- consistent response envelopeswithRetry-- exponential backoff (retry 429, 5xx, network errors; skip other 4xx)
- Implement tools one at a time, each with:
- Schema-validated input (strict mode, constraints, descriptions)
- Handler wrapped in
withRetry - Both text + structured content in response
- Pagination for any list operation (default 20-50 items, return
has_more+next_offset) - Character limit enforcement (truncate at ~25K chars with message)
- Implement resources if the service has URI-addressable data
- Set up transport (stdio or Streamable HTTP based on deployment target)
- Add dual-mode entry point (serverless handler export + local server start)
- Test -- unit tests for handlers/validators, integration tests gated on env vars
- Evaluate -- 10 QA pairs per the evaluation format
Quality Checklist
Before declaring the server complete:
- Every tool has name, title, description, inputSchema, outputSchema, annotations
- All annotations correctly set (readOnlyHint, destructiveHint, idempotentHint, openWorldHint)
- All inputs schema-validated with constraints and descriptions
- Error messages are actionable and safe (no internal details)
- Retry with exponential backoff on transient failures
- All list tools support pagination with
has_more/next_offset - Character limit enforced with truncation message
- No duplicated code -- shared utilities extracted
- Strict typing throughout, no
anytypes - Async/await for all I/O
- Auth validated on every request
- No secrets in logs
- Build completes without errors
Phase 5: Verify (MANDATORY -- DO NOT SKIP)
This phase is required. The server is NOT complete until these tests pass. Read references/testing.md for detailed patterns.
Minimum Viable Test (always run, no credentials needed)
Even without API credentials, you MUST verify the server works at the MCP protocol level by piping JSON-RPC messages to the server process:
- Initialize -- Send
initializerequest, confirm server responds withprotocolVersion,capabilities, andserverInfo. - List tools -- Send
tools/list, confirm all expected tools appear with correct names, schemas, and annotations. - Error handling -- Call a tool (e.g. a read tool) and verify it returns a graceful
isError: trueresponse (not a crash) when credentials are missing or the resource doesn't exist.
Example for stdio servers:
# Test 1: Initialize
echo '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"test","version":"1.0.0"}}}' | node dist/index.js 2>/dev/null
# Test 2: List tools (send initialize + notification + list in sequence)
printf '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"test","version":"1.0.0"}}}\n{"jsonrpc":"2.0","method":"notifications/initialized"}\n{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}\n' | node dist/index.js 2>/dev/null
# Test 3: Call a tool and verify error handling
printf '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"test","version":"1.0.0"}}}\n{"jsonrpc":"2.0","method":"notifications/initialized"}\n{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"SERVICE_TOOL_NAME","arguments":{}}}\n' | node dist/index.js 2>/dev/null
Adapt node dist/index.js for Python (uv run server.py) as needed.
If any test fails, fix the code and re-run. Do not proceed to Phase 6.
Full Interactive Testing (when credentials are available)
-
Start MCP Inspector:
- TypeScript:
npx @anthropic-ai/mcp-inspector npx tsx src/index.ts - Python:
npx @anthropic-ai/mcp-inspector uv run server.py
- TypeScript:
-
Test each tool systematically:
- Start with read-only tools (lowest risk)
- Test edge cases: empty inputs, pagination boundaries, max lengths
- Verify error handling with invalid inputs
- Test destructive operations last (with user confirmation)
-
On failure:
- Capture the error message from Inspector output
- Fix the issue in code
- Re-run the failing test
- Continue until all tools pass
-
Exit criteria: All tools respond correctly to valid inputs and return proper MCP errors for invalid inputs.
Phase 6: Deploy (Optional)
When all interactive tests pass, ask the user:
"Your MCP server is working correctly. Would you like to deploy it to Runlayer?"
If yes, read references/deploy.md for full details, then:
Step 1: Collect credentials
Use AskQuestion to prompt for both values. Do not guess or skip.
- Personal API Key — "Go to your Runlayer dashboard → Settings → API Keys → Personal API Key. Paste it here."
- Runlayer tenant URL — "What is your Runlayer tenant URL? (e.g.
https://mycompany.runlayer.comorhttps://ecs.prod.runlayer.com)"
Step 2: Ensure Streamable HTTP transport
If the server was built with stdio-only transport, add dual-mode support before deploying:
- Add
expressdependency - Add
/healthendpoint (returns{"status":"ok"}) - Add
/mcpPOST endpoint withStreamableHTTPServerTransport - Use
PORTenv var to switch: stdio when no PORT, HTTP when PORT is set - Rebuild, test health + MCP endpoints locally with
curl
Step 3: Create deployment files
- Dockerfile — Use template from
references/deploy.md. Test locally withdocker build .before proceeding. .dockerignore— Excludenode_modules,dist,.git,.env,tests- Initialize deployment to get an ID:
This generatesuvx runlayer deploy init --secret <API_KEY> --host <TENANT_URL>runlayer.yamlwith the deployment ID. Edit it to setservice.portto match your server.
Step 4: Deploy
uvx runlayer deploy --secret <API_KEY> --host <TENANT_URL>
Step 5: Report success
Show:
- Deployment ID
- MCP proxy URL:
https://<tenant>/api/v1/proxy/<deployment-id>/mcp - Cursor MCP config snippet for connecting to the deployed server
- Any env vars the user still needs to configure in Runlayer (API keys, credentials for the target service)