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.md and 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. See references/deploy.md.
Architecture Decisions
Determine these upfront:
Transport:
stdio -- local/CLI tools, single user, simple setup
Streamable 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, openWorldHint on 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) and structuredContent (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, retries
handleApiError -- maps HTTP status to MCP error codes with context
createToolResponse / createErrorResponse -- consistent response envelopes
withRetry -- 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:
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
initialize request, confirm server responds with protocolVersion, capabilities, and serverInfo.
- 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: true response (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
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.com or https://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
express dependency
- Add
/health endpoint (returns {"status":"ok"})
- Add
/mcp POST endpoint with StreamableHTTPServerTransport
- Use
PORT env 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 with docker build . before proceeding.
.dockerignore — Exclude node_modules, dist, .git, .env, tests
- Initialize deployment to get an ID:
uvx runlayer deploy init --secret <API_KEY> --host <TENANT_URL>
This generates runlayer.yaml with the deployment ID. Edit it to set service.port to 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)
1---2name: mcp-builder3description: 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.4---56# MCP Server Builder78Build, 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.910## Before You Start1112**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/`:1314- **TypeScript** -> read `references/typescript.md`15- **Python** -> read `references/python.md`16- **Other** -> use the language-agnostic patterns in `references/guidelines.md` and adapt to the chosen language1718Apply the language-specific conventions (naming, SDK, validation library, project structure, idioms) from the loaded reference throughout the entire build.1920## Development Process2122Follow six phases in order. **Phases 1-5 are mandatory. Do NOT skip any phase. Do NOT declare the server complete until Phase 5 passes.**23241. **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.252. **Implement** -- Build infrastructure first (API client, auth, errors, formatters), then tools incrementally.263. **Review & Build** -- Build, lint, fix all errors. Review for duplication, consistent errors, type coverage.274. **Evaluate** -- Create 10 complex realistic questions requiring multiple tool calls with stable verifiable answers.285. **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`.296. **Deploy (Optional)** -- When tests pass, offer to deploy to Runlayer via `uvx runlayer deploy`. See `references/deploy.md`.3031## Architecture Decisions3233Determine these upfront:3435**Transport:**36- `stdio` -- local/CLI tools, single user, simple setup37- `Streamable HTTP` -- remote/cloud, multi-client, serverless. Avoid SSE (deprecated).3839**State:**40- Stateless (recommended for remote) -- fresh server per request, horizontal scaling, no session leaks41- Stateful -- when session continuity is required4243**Auth:**44- OAuth 2.1 -- remote servers with user identity45- API key via env vars -- simpler integrations46- None -- local-only tools4748**Language conventions (applied after user selects language):**4950| Language | Server name | Validation | SDK |51|---|---|---|---|52| TypeScript | `{service}-mcp-server` | Zod | `@modelcontextprotocol/sdk` |53| Python | `{service}_mcp` | Pydantic | `mcp` (FastMCP) |54| Other | `{service}-mcp` | JSON Schema | Implement protocol directly |5556## Tool Design Rules57581. **Name**: `{service}_{action}_{resource}` in snake_case. Always prefix with service name.592. **Required fields**: `name`, `title`, `description`, `inputSchema`, `outputSchema`, `annotations`603. **Annotations**: Set `readOnlyHint`, `destructiveHint`, `idempotentHint`, `openWorldHint` on every tool.614. **Descriptions**: Must precisely match functionality. Include parameter docs, return schema, usage examples (when to use AND when NOT to use), and error conditions.625. **Schemas**: Use strict mode. Add constraints (min/max/pattern/enum) and `.describe()` on every field.636. **Responses**: Return both `content` (text) and `structuredContent` (typed data).6465For detailed patterns, validation examples, response formatting, error handling, pagination, auth, testing, security, logging, and deployment guidance, read `references/guidelines.md`.6667## Project Structure6869```70{service}-mcp-server/71├── src/72│ ├── index.ts # Entry point (dual-mode: local + serverless)73│ ├── tools/ # Tool implementations + registry74│ ├── resources/ # MCP resource handlers (if needed)75│ ├── services/ # API clients, external service wrappers76│ ├── schemas/ # Validation schemas77│ ├── utils/ # Shared helpers (retry, formatting, errors)78│ ├── auth/ # Auth, token storage, middleware79│ └── types/ # Type definitions80├── tests/81│ ├── unit/82│ └── integration/83└── README.md84```8586## Implementation Workflow87881. **Scaffold** the project structure above892. **Build shared utilities first:**90 - `makeApiRequest` -- centralized HTTP client with auth, timeout, retries91 - `handleApiError` -- maps HTTP status to MCP error codes with context92 - `createToolResponse` / `createErrorResponse` -- consistent response envelopes93 - `withRetry` -- exponential backoff (retry 429, 5xx, network errors; skip other 4xx)943. **Implement tools** one at a time, each with:95 - Schema-validated input (strict mode, constraints, descriptions)96 - Handler wrapped in `withRetry`97 - Both text + structured content in response98 - Pagination for any list operation (default 20-50 items, return `has_more` + `next_offset`)99 - Character limit enforcement (truncate at ~25K chars with message)1004. **Implement resources** if the service has URI-addressable data1015. **Set up transport** (stdio or Streamable HTTP based on deployment target)1026. **Add dual-mode entry point** (serverless handler export + local server start)1037. **Test** -- unit tests for handlers/validators, integration tests gated on env vars1048. **Evaluate** -- 10 QA pairs per the evaluation format105106## Quality Checklist107108Before declaring the server complete:109110- [ ] Every tool has name, title, description, inputSchema, outputSchema, annotations111- [ ] All annotations correctly set (readOnlyHint, destructiveHint, idempotentHint, openWorldHint)112- [ ] All inputs schema-validated with constraints and descriptions113- [ ] Error messages are actionable and safe (no internal details)114- [ ] Retry with exponential backoff on transient failures115- [ ] All list tools support pagination with `has_more` / `next_offset`116- [ ] Character limit enforced with truncation message117- [ ] No duplicated code -- shared utilities extracted118- [ ] Strict typing throughout, no `any` types119- [ ] Async/await for all I/O120- [ ] Auth validated on every request121- [ ] No secrets in logs122- [ ] Build completes without errors123124## Phase 5: Verify (MANDATORY -- DO NOT SKIP)125126**This phase is required. The server is NOT complete until these tests pass.** Read `references/testing.md` for detailed patterns.127128### Minimum Viable Test (always run, no credentials needed)129130Even without API credentials, you MUST verify the server works at the MCP protocol level by piping JSON-RPC messages to the server process:1311321. **Initialize** -- Send `initialize` request, confirm server responds with `protocolVersion`, `capabilities`, and `serverInfo`.1332. **List tools** -- Send `tools/list`, confirm all expected tools appear with correct names, schemas, and annotations.1343. **Error handling** -- Call a tool (e.g. a read tool) and verify it returns a graceful `isError: true` response (not a crash) when credentials are missing or the resource doesn't exist.135136Example for stdio servers:137138```bash139# Test 1: Initialize140echo '{"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/null141142# Test 2: List tools (send initialize + notification + list in sequence)143printf '{"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/null144145# Test 3: Call a tool and verify error handling146printf '{"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/null147```148149Adapt `node dist/index.js` for Python (`uv run server.py`) as needed.150151**If any test fails, fix the code and re-run. Do not proceed to Phase 6.**152153### Full Interactive Testing (when credentials are available)1541551. **Start MCP Inspector:**156 - TypeScript: `npx @anthropic-ai/mcp-inspector npx tsx src/index.ts`157 - Python: `npx @anthropic-ai/mcp-inspector uv run server.py`1581592. **Test each tool systematically:**160 - Start with read-only tools (lowest risk)161 - Test edge cases: empty inputs, pagination boundaries, max lengths162 - Verify error handling with invalid inputs163 - Test destructive operations last (with user confirmation)1641653. **On failure:**166 - Capture the error message from Inspector output167 - Fix the issue in code168 - Re-run the failing test169 - Continue until all tools pass1701714. **Exit criteria:** All tools respond correctly to valid inputs and return proper MCP errors for invalid inputs.172173## Phase 6: Deploy (Optional)174175When all interactive tests pass, ask the user:176177> "Your MCP server is working correctly. Would you like to deploy it to Runlayer?"178179If yes, read `references/deploy.md` for full details, then:180181### Step 1: Collect credentials182183Use AskQuestion to prompt for **both** values. Do not guess or skip.1841851. **Personal API Key** — "Go to your Runlayer dashboard → Settings → API Keys → Personal API Key. Paste it here."1862. **Runlayer tenant URL** — "What is your Runlayer tenant URL? (e.g. `https://mycompany.runlayer.com` or `https://ecs.prod.runlayer.com`)"187188### Step 2: Ensure Streamable HTTP transport189190If the server was built with stdio-only transport, **add dual-mode support before deploying**:191- Add `express` dependency192- Add `/health` endpoint (returns `{"status":"ok"}`)193- Add `/mcp` POST endpoint with `StreamableHTTPServerTransport`194- Use `PORT` env var to switch: stdio when no PORT, HTTP when PORT is set195- Rebuild, test health + MCP endpoints locally with `curl`196197### Step 3: Create deployment files1981991. **Dockerfile** — Use template from `references/deploy.md`. Test locally with `docker build .` before proceeding.2002. **`.dockerignore`** — Exclude `node_modules`, `dist`, `.git`, `.env`, `tests`2013. **Initialize deployment** to get an ID:202 ```bash203 uvx runlayer deploy init --secret <API_KEY> --host <TENANT_URL>204 ```205 This generates `runlayer.yaml` with the deployment ID. Edit it to set `service.port` to match your server.206207### Step 4: Deploy208209```bash210uvx runlayer deploy --secret <API_KEY> --host <TENANT_URL>211```212213### Step 5: Report success214215Show:216- Deployment ID217- MCP proxy URL: `https://<tenant>/api/v1/proxy/<deployment-id>/mcp`218- Cursor MCP config snippet for connecting to the deployed server219- Any env vars the user still needs to configure in Runlayer (API keys, credentials for the target service)