Skills mcp-server-builder
git clone https://github.com/TerminalSkills/skills
T=$(mktemp -d) && git clone --depth=1 https://github.com/TerminalSkills/skills "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/mcp-server-builder" ~/.claude/skills/terminalskills-skills-mcp-server-builder && rm -rf "$T"
skills/mcp-server-builder/SKILL.md- pip install
MCP Server Builder
Overview
Create Model Context Protocol (MCP) servers that expose tools, resources, and prompts to AI agents like Claude, Cursor, and other MCP-compatible clients. This skill scaffolds complete MCP server projects with proper typing, error handling, input validation, and deployment configuration.
Instructions
When a user asks to build an MCP server, follow these steps:
Step 1: Define the server's purpose and capabilities
Determine what the MCP server will expose:
- Tools — Functions the AI can call (e.g., query a database, call an API, run a search)
- Resources — Data the AI can read (e.g., file contents, database records, config)
- Prompts — Reusable prompt templates with parameters
Ask: What service or data source should the AI agent connect to?
Step 2: Scaffold the project
TypeScript (recommended):
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 --target ES2022 --module NodeNext --moduleResolution NodeNext --outDir dist
Create the entry point structure:
my-mcp-server/ ├── src/ │ └── index.ts # Server entry point ├── package.json └── tsconfig.json
Update
package.json:
{ "type": "module", "bin": { "my-mcp-server": "./dist/index.js" }, "scripts": { "build": "tsc", "start": "node dist/index.js" } }
Python:
mkdir my-mcp-server && cd my-mcp-server python -m venv .venv && source .venv/bin/activate pip install mcp[cli]
Step 3: Implement the server
TypeScript MCP server template:
#!/usr/bin/env node 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-mcp-server', version: '1.0.0', }); // Define a tool server.tool( 'search_issues', 'Search GitHub issues by query. Returns title, number, state, and URL.', { query: z.string().describe('Search query for issues'), repo: z.string().describe('Repository in owner/repo format'), state: z.enum(['open', 'closed', 'all']).default('open').describe('Issue state filter'), }, async ({ query, repo, state }) => { const response = await fetch( `https://api.github.com/search/issues?q=${encodeURIComponent(query)}+repo:${repo}+state:${state}` ); if (!response.ok) { return { content: [{ type: 'text', text: `Error: ${response.statusText}` }], isError: true }; } const data = await response.json(); const results = data.items.slice(0, 10).map((issue: any) => `#${issue.number} [${issue.state}] ${issue.title}\n ${issue.html_url}` ).join('\n\n'); return { content: [{ type: 'text', text: results || 'No issues found.' }] }; } ); // Define a resource server.resource( 'repo-readme', 'github://readme/{owner}/{repo}', async (uri) => { const [owner, repo] = uri.pathname.split('/').filter(Boolean); const response = await fetch(`https://api.github.com/repos/${owner}/${repo}/readme`, { headers: { Accept: 'application/vnd.github.raw' }, }); return { contents: [{ uri: uri.href, text: await response.text(), mimeType: 'text/markdown' }] }; } ); // Start the server const transport = new StdioServerTransport(); await server.connect(transport);
Python MCP server template:
from mcp.server.fastmcp import FastMCP import httpx mcp = FastMCP("my-mcp-server") @mcp.tool() async def search_issues(query: str, repo: str, state: str = "open") -> str: """Search GitHub issues by query. Returns title, number, state, and URL.""" async with httpx.AsyncClient() as client: resp = await client.get( "https://api.github.com/search/issues", params={"q": f"{query} repo:{repo} state:{state}"}, ) resp.raise_for_status() items = resp.json()["items"][:10] return "\n\n".join( f"#{i['number']} [{i['state']}] {i['title']}\n {i['html_url']}" for i in items ) or "No issues found." @mcp.resource("github://readme/{owner}/{repo}") async def repo_readme(owner: str, repo: str) -> str: """Fetch the README for a GitHub repository.""" async with httpx.AsyncClient() as client: resp = await client.get( f"https://api.github.com/repos/{owner}/{repo}/readme", headers={"Accept": "application/vnd.github.raw"}, ) return resp.text if __name__ == "__main__": mcp.run()
Step 4: Add client configuration
Generate the MCP client config for the user:
Claude Desktop (
):claude_desktop_config.json
{ "mcpServers": { "my-mcp-server": { "command": "node", "args": ["/absolute/path/to/dist/index.js"], "env": { "GITHUB_TOKEN": "your-token-here" } } } }
For Python servers:
{ "mcpServers": { "my-mcp-server": { "command": "python", "args": ["/absolute/path/to/server.py"] } } }
Step 5: Test the server
# TypeScript — build and test npm run build echo '{"jsonrpc":"2.0","id":1,"method":"tools/list"}' | node dist/index.js # Python — test with MCP inspector mcp dev server.py
Verify:
- All tools appear in
responsetools/list - Tool inputs are validated (try invalid params)
- Error responses are graceful, not stack traces
Examples
Example 1: MCP server for a PostgreSQL database
User request: "Build an MCP server so Claude can query my Postgres database"
Actions taken:
- Scaffolded TypeScript MCP project with
driverpg - Created tools:
(read-only SQL),run_query
,list_tablesdescribe_table - Added connection pooling and query timeout (30s)
- Restricted to SELECT queries only (safety)
Key tool implementation:
server.tool( 'run_query', 'Execute a read-only SQL query against the database. Only SELECT statements allowed.', { sql: z.string().describe('SQL SELECT query to execute') }, async ({ sql }) => { if (!/^\s*SELECT\b/i.test(sql)) { return { content: [{ type: 'text', text: 'Error: Only SELECT queries are allowed.' }], isError: true }; } const result = await pool.query({ text: sql, timeout: 30000 }); const formatted = result.rows.map(r => JSON.stringify(r)).join('\n'); return { content: [{ type: 'text', text: `${result.rowCount} rows:\n${formatted}` }] }; } );
Result: 3 tools created, tested with MCP inspector, config generated for Claude Desktop.
Example 2: MCP server wrapping a REST API (Jira)
User request: "Create an MCP server for Jira so I can manage tickets from Claude"
Actions taken:
- Created Python FastMCP server with httpx
- Implemented tools:
,search_tickets
,create_ticket
,update_statusadd_comment - Added authentication via environment variable
JIRA_API_TOKEN - Input validation with proper error messages
Tools created:
- search_tickets(jql: str, max_results: int = 10) → Search Jira issues using JQL - create_ticket(project: str, summary: str, description: str, type: str) → Create a new issue - update_status(ticket_key: str, status: str) → Transition issue status - add_comment(ticket_key: str, body: str) → Add a comment to an issue
Result: 4 tools, all tested. User can now ask Claude: "Find all open bugs in PROJECT assigned to me" and Claude calls
search_tickets with the appropriate JQL.
Guidelines
- Always validate tool inputs with Zod (TypeScript) or type hints (Python). Never trust raw input.
- Return structured, readable text from tools — the AI will interpret it for the user.
- Use
in tool responses for failures so the AI knows to retry or report the error.isError: true - Keep tool descriptions clear and specific — the AI uses them to decide when to call each tool.
- For database tools, enforce read-only access by default. Only allow writes if explicitly requested.
- Store API keys and credentials in environment variables, never hardcode them.
- Add timeouts to all external calls (HTTP requests, database queries) to prevent hangs.
- Limit response sizes — if a query returns 10,000 rows, paginate or truncate.
- Use stdio transport for local development, SSE/HTTP transport for remote deployment.
- Test every tool with the MCP inspector (
) before shipping.npx @modelcontextprotocol/inspector - Include a README with setup instructions, required env vars, and example Claude Desktop config.