mcp

SKILL.md

MCP

Build MCP servers that give Claude access to external tools and data.

What is MCP?

Model Context Protocol lets you create servers that Claude can call:

  • Query databases
  • Call APIs
  • Access file systems
  • Run custom tools

When to Build an MCP Server

  • You want Claude to access live data (not just files)
  • You're repeating the same API calls manually
  • You want Claude to take actions (create records, trigger workflows)

Project Setup

mkdir my-mcp-server && cd my-mcp-server
pnpm init
pnpm add @modelcontextprotocol/sdk zod
pnpm add -D typescript @types/node tsx
// package.json
{
  "type": "module",
  "scripts": {
    "dev": "tsx watch src/index.ts",
    "build": "tsc",
    "start": "node dist/index.js"
  }
}
// tsconfig.json
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "outDir": "dist",
    "strict": true,
    "esModuleInterop": true
  },
  "include": ["src"]
}

Basic Server Template

// src/index.ts
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',
})

// Define a tool
server.tool(
  'hello',
  'Say hello to someone',
  { name: z.string().describe('Name to greet') },
  async ({ name }) => ({
    content: [{ type: 'text', text: `Hello, ${name}!` }],
  })
)

// Start server
const transport = new StdioServerTransport()
await server.connect(transport)

Common Patterns

Database Access (Postgres)

import pg from 'pg'

const pool = new pg.Pool({
  connectionString: process.env.DATABASE_URL,
})

server.tool(
  'query',
  'Run a read-only SQL query',
  { sql: z.string().describe('SELECT query to run') },
  async ({ sql }) => {
    if (!sql.trim().toLowerCase().startsWith('select')) {
      return { content: [{ type: 'text', text: 'Only SELECT queries allowed' }] }
    }
    const result = await pool.query(sql)
    return { content: [{ type: 'text', text: JSON.stringify(result.rows, null, 2) }] }
  }
)

API Wrapper

server.tool(
  'get_weather',
  'Get current weather for a city',
  { city: z.string() },
  async ({ city }) => {
    const res = await fetch(`https://api.weather.com/current?city=${city}`)
    const data = await res.json()
    return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] }
  }
)

File System Access

import { readdir, readFile } from 'fs/promises'

server.tool(
  'list_projects',
  'List projects in studio-manager',
  {},
  async () => {
    const dirs = await readdir('/Users/brew/studio-manager', { withFileTypes: true })
    const projects = dirs.filter(d => d.isDirectory()).map(d => d.name)
    return { content: [{ type: 'text', text: projects.join('\n') }] }
  }
)

Claude Desktop Config

Add to ~/Library/Application Support/Claude/claude_desktop_config.json:

{
  "mcpServers": {
    "my-server": {
      "command": "node",
      "args": ["/path/to/my-mcp-server/dist/index.js"],
      "env": {
        "DATABASE_URL": "postgres://..."
      }
    }
  }
}

Restart Claude Desktop after changes.

Testing

# Run server directly to test
echo '{"jsonrpc":"2.0","id":1,"method":"tools/list"}' | pnpm start

Security Notes

  • Validate all inputs with Zod
  • Limit database access to SELECT only (unless intentional)
  • Don't expose secrets in tool responses
  • Consider rate limiting for external APIs

Reference

Weekly Installs
8
Repository
cerico/macfair
First Seen
Feb 24, 2026
Installed on
trae8
claude-code8
github-copilot8
codex8
windsurf8
kiro-cli8