Jake Shore 8d65417afe Add 11 MCP agent skills to repo — 550KB of encoded pipeline knowledge
Skills added:
- mcp-api-analyzer (43KB) — Phase 1: API analysis
- mcp-server-builder (88KB) — Phase 2: Server build
- mcp-server-development (31KB) — TS MCP patterns
- mcp-app-designer (85KB) — Phase 3: Visual apps
- mcp-apps-integration (20KB) — structuredContent UI
- mcp-apps-official (48KB) — MCP Apps SDK
- mcp-apps-merged (39KB) — Combined apps reference
- mcp-localbosses-integrator (61KB) — Phase 4: LocalBosses wiring
- mcp-qa-tester (113KB) — Phase 5: Full QA framework
- mcp-deployment (17KB) — Phase 6: Production deploy
- mcp-skill (exa integration)

These skills are the encoded knowledge that lets agents build
production-quality MCP servers autonomously through the pipeline.
2026-02-06 06:36:37 -05:00

20 KiB

MCP Server Blueprint — February 2026

This is the definitive template for building production-ready MCP servers in 2026.

Use this checklist for EVERY new MCP server. No skipping steps. These patterns ensure your server is:

  • Usable (not just functional)
  • Fast (lazy loading, efficient queries)
  • Discoverable (labels, descriptions)
  • Interactive (MCP Apps where appropriate)
  • Debuggable (logging, progress)
  • Production-ready (error handling, deployment)

Phase 1: Planning (Before Writing Code)

1.1 Define Server Scope

  • What API/service are you integrating?
  • What are the 5-10 most important operations?
  • Who is the target user? (developers, business users, etc.)
  • What data is most frequently accessed?

1.2 Identify Tool Categories

Label your tools by category. Common patterns:

  • CRUD operations (create, read, update, delete)
  • Search/Filter (find data with queries)
  • Analytics/Reporting (stats, dashboards, summaries)
  • Workflows (multi-step operations)
  • Admin (configuration, settings)

1.3 Identify UI Opportunities

Which operations benefit from visual display?

  • Data grids — Contact lists, search results, tables
  • Dashboards — Metrics, KPIs, analytics
  • Cards — Detail views (invoices, opportunities, profiles)
  • Timelines — Activity feeds, history
  • Forms — Quick actions (booking, creating records)
  • Kanban — Pipeline views, project boards

If you have 3+ UI opportunities, plan for MCP Apps.


Phase 2: Core Server Setup

2.1 Project Structure

mkdir mcp-server-myservice
cd mcp-server-myservice
npm init -y
npm install @modelcontextprotocol/sdk
npm install -D typescript @types/node tsx fs-extra @types/fs-extra

2.2 File Structure

mcp-server-myservice/
├── src/
│   ├── index.ts              # Main server (or server.ts)
│   ├── clients/
│   │   └── api-client.ts     # API client
│   ├── apps/                 # If using MCP Apps
│   │   └── index.ts          # Apps manager
│   ├── ui/                   # If using MCP Apps
│   │   ├── contact-grid.html
│   │   └── dashboard.html
│   └── types/
│       └── index.ts          # Shared types
├── dist/                     # Build output
├── scripts/
│   └── copy-ui.js            # UI build script
├── package.json
├── tsconfig.json
├── .env.example
├── .gitignore
├── .npmignore
├── Dockerfile
├── railway.json
└── README.md

2.3 Package Configuration

{
  "name": "mcp-server-myservice",
  "version": "1.0.0",
  "type": "module",
  "main": "dist/index.js",
  "bin": {
    "mcp-server-myservice": "dist/index.js"
  },
  "scripts": {
    "build": "npm run build:ts && npm run build:ui",
    "build:ts": "tsc",
    "build:ui": "node scripts/copy-ui.js",
    "dev": "tsx src/index.ts",
    "start": "node dist/index.js"
  },
  "files": ["dist", "README.md", "LICENSE"],
  "keywords": ["mcp", "mcp-server", "model-context-protocol", "myservice"]
}

Phase 3: Tool Design (The Most Important Phase)

3.1 Tool Naming Convention

Use: verb_noun (snake_case) Avoid: camelCase, PascalCase, kebab-case

CRUD patterns:

  • list_contacts (with pagination + filters)
  • get_contact (by ID)
  • create_contact (returns created object)
  • update_contact (partial updates)
  • delete_contact (confirm before delete)
  • search_contacts (full-text search if different from list)

Other patterns:

  • send_email, schedule_appointment, export_report, analyze_pipeline

3.2 Tool Metadata & Labels CRITICAL

Every tool MUST have _meta with labels:

{
  name: "search_contacts",
  description: "Search contacts with filters. Returns paginated results.",
  inputSchema: { /* ... */ },
  _meta: {
    labels: {
      category: "contacts",           // Group by feature
      access: "read",                 // read | write | delete
      complexity: "simple",           // simple | complex | batch
    },
  },
}

Label categories to use:

  • category: contacts, deals, analytics, calendar, email, admin, workflows
  • access: read, write, delete
  • complexity: simple (1 API call), complex (multiple calls), batch (loops)
  • sensitivity: public, internal, confidential (optional)

3.3 Input Schemas — Best Practices

inputSchema: {
  type: "object" as const,
  properties: {
    // Always describe parameters
    page: { 
      type: "number", 
      description: "Page number (default 1, starts at 1)" 
    },
    pageSize: { 
      type: "number", 
      description: "Results per page (default 50, max 100)" 
    },
    // Use enums for fixed options
    status: { 
      type: "string", 
      description: "Filter by status",
      enum: ["active", "inactive", "pending"],
    },
    // ISO 8601 for dates
    createdAfter: { 
      type: "string", 
      description: "Filter created after (ISO 8601: 2026-02-03T14:00:00Z)" 
    },
  },
  // Mark required fields explicitly
  required: ["contactId"],
}

3.4 Pagination (Mandatory for List Operations)

Every list_ or search_ tool MUST support pagination:

{
  name: "list_contacts",
  description: "List contacts with pagination and filters",
  inputSchema: {
    type: "object" as const,
    properties: {
      page: { type: "number", description: "Page number (default 1)" },
      pageSize: { type: "number", description: "Results per page (default 50, max 100)" },
      query: { type: "string", description: "Search query (optional)" },
    },
  },
  _meta: {
    labels: { category: "contacts", access: "read", complexity: "simple" },
  },
}

In handler:

case "list_contacts": {
  const { page = 1, pageSize = 50, query } = args;
  const params = new URLSearchParams();
  params.append("page", String(page));
  params.append("pageSize", String(Math.min(Number(pageSize), 100))); // Cap at API max
  if (query) params.append("query", query);
  
  return await client.get(`/contacts?${params}`);
}

Phase 4: Lazy-Loaded Resources NEW

4.1 When to Use Resources vs Tools

Use resources for:

  • Large datasets (contact lists, transaction history)
  • Frequently changing data (real-time dashboards)
  • Reference data (documentation, schemas)
  • User-specific data (per-user settings, dashboards)

Use tools for:

  • Operations with parameters (search, filter, create)
  • One-time fetches
  • Mutations (create, update, delete)

4.2 Resource Setup

import { ListResourcesRequestSchema, ReadResourceRequestSchema } from "@modelcontextprotocol/sdk/types.js";

// Declare resources capability
const server = new Server(
  { name: "myservice-mcp", version: "1.0.0" },
  { capabilities: { tools: {}, resources: {} } } // ✅ Enable resources
);

// List available resources (metadata only)
server.setRequestHandler(ListResourcesRequestSchema, async () => {
  return {
    resources: [
      {
        uri: "myservice://contacts/all",
        name: "All Contacts",
        description: "Complete contact database (lazy-loaded)",
        mimeType: "application/json",
      },
      {
        uri: "myservice://analytics/dashboard",
        name: "Analytics Dashboard",
        description: "Real-time analytics data",
        mimeType: "application/json",
      },
    ],
  };
});

// Fetch resource content on-demand
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
  const { uri } = request.params;

  switch (uri) {
    case "myservice://contacts/all": {
      const contacts = await client.get("/contacts?limit=1000"); // Fetch when requested
      return {
        contents: [{
          uri,
          mimeType: "application/json",
          text: JSON.stringify(contacts, null, 2),
        }],
      };
    }

    case "myservice://analytics/dashboard": {
      const analytics = await client.get("/analytics/dashboard");
      return {
        contents: [{
          uri,
          mimeType: "application/json",
          text: JSON.stringify(analytics, null, 2),
        }],
      };
    }

    default:
      throw new Error(`Unknown resource: ${uri}`);
  }
});

4.3 Resource Templates (Dynamic URIs)

server.setRequestHandler(ListResourcesRequestSchema, async () => {
  return {
    resourceTemplates: [
      {
        uriTemplate: "myservice://contact/{id}",
        name: "Contact Details",
        description: "Full contact record by ID",
        mimeType: "application/json",
      },
    ],
  };
});

server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
  const { uri } = request.params;
  
  const contactMatch = uri.match(/^myservice:\/\/contact\/(.+)$/);
  if (contactMatch) {
    const contactId = contactMatch[1];
    const contact = await client.get(`/contacts/${contactId}`);
    return {
      contents: [{
        uri,
        mimeType: "application/json",
        text: JSON.stringify(contact, null, 2),
      }],
    };
  }

  throw new Error(`Unknown resource: ${uri}`);
});

Phase 5: MCP Apps (If Applicable)

5.1 Should You Build Apps?

Build MCP Apps if you have:

  • Visual data (grids, cards, dashboards)
  • 3+ UI opportunities identified in Phase 1
  • Complex data relationships (better shown than described)
  • Interactive workflows (drag-drop, forms)

Skip apps if:

  • Simple CRUD operations only
  • All operations return small JSON objects
  • No visual benefit

5.2 App Architecture

See mcp-apps-integration skill for full details. Quick checklist:

  • Create src/apps/index.ts — MCPAppsManager class
  • Create src/ui/ directory — HTML components
  • Register resource handlers for UI files
  • Add app tools with _meta.ui.resourceUri
  • Implement ListResourcesRequestSchema handler
  • Implement ReadResourceRequestSchema handler
  • Add build:ui script to copy HTML to dist/app-ui/

5.3 App Tool Naming

Pattern: view_ or show_ prefix

{
  name: "view_contact_grid",
  description: "Display contact search results in a data grid (visual UI component)",
  inputSchema: { /* ... */ },
  _meta: {
    labels: { category: "contacts", access: "read", complexity: "simple" },
    ui: { resourceUri: "ui://myservice/contact-grid" },
  },
}

5.4 Common App Patterns

  • Contact Grid — Search results table
  • Dashboard — Multi-widget analytics view
  • Pipeline Board — Kanban with drag-drop
  • Opportunity Card — Detail view for single record
  • Calendar View — Appointment/event calendar
  • Timeline — Activity feed

Reference: 11 production GHL apps in /Users/jakeshore/.clawdbot/workspace/mcp-diagrams/ghl-mcp-apps-only/


Phase 6: Progress & Logging

6.1 Progress Notifications (For Long Operations)

Any operation taking >5 seconds MUST send progress updates:

if (name === "import_contacts") {
  const progressToken = request.params._meta?.progressToken;
  
  if (progressToken) {
    await server.notification({
      method: "notifications/progress",
      params: {
        progressToken,
        progress: 0.3, // 30%
        total: 1.0,
      },
    });
  }
  
  // ... do work
  
  if (progressToken) {
    await server.notification({
      method: "notifications/progress",
      params: { progressToken, progress: 1.0, total: 1.0 },
    });
  }
}

6.2 Structured Logging

Log important operations for debugging:

import { LoggingLevel } from "@modelcontextprotocol/sdk/types.js";

await server.notification({
  method: "notifications/message",
  params: {
    level: LoggingLevel.Info,
    logger: "myservice",
    data: {
      operation: "create_contact",
      contactId: newContact.id,
      timestamp: new Date().toISOString(),
    },
  },
});

When to log:

  • Info: Successful operations (create, update, delete)
  • Warning: Rate limits, retries, fallbacks
  • Error: API failures, validation errors
  • Debug: Detailed request/response data (dev only)

Phase 7: Error Handling (Production-Ready)

7.1 Tool Handler Error Wrapping

server.setRequestHandler(CallToolRequestSchema, async (request) => {
  const { name, arguments: args } = request.params;
  
  try {
    const result = await handleTool(client, name, args || {});
    return {
      content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
    };
  } catch (error) {
    const message = error instanceof Error ? error.message : String(error);
    
    // Log the error
    await server.notification({
      method: "notifications/message",
      params: {
        level: LoggingLevel.Error,
        logger: "myservice",
        data: { tool: name, error: message },
      },
    });
    
    return {
      content: [{ type: "text", text: `Error: ${message}` }],
      isError: true,
    };
  }
});

7.2 API Client Error Handling

async request(endpoint: string, options: RequestInit = {}) {
  const response = await fetch(url, options);

  if (!response.ok) {
    const errorText = await response.text();
    
    // Parse API error if JSON
    try {
      const errorJson = JSON.parse(errorText);
      throw new Error(
        `API error: ${response.status} - ${errorJson.message || errorJson.error || errorText}`
      );
    } catch {
      throw new Error(
        `API error: ${response.status} ${response.statusText} - ${errorText}`
      );
    }
  }

  return response.json();
}

8.1 When to Add Prompts

Add prompts for:

  • Common analysis workflows (e.g., "Analyze pipeline health")
  • Report generation (e.g., "Generate contact summary")
  • Quick actions (e.g., "Find overdue tasks")
  • Data exploration (e.g., "Show top performers")

8.2 Prompt Implementation

import { ListPromptsRequestSchema, GetPromptRequestSchema } from "@modelcontextprotocol/sdk/types.js";

server.setRequestHandler(ListPromptsRequestSchema, async () => {
  return {
    prompts: [
      {
        name: "contact_summary",
        description: "Generate comprehensive contact summary with recent activity",
        arguments: [
          { name: "contactId", description: "Contact ID", required: true },
        ],
      },
    ],
  };
});

server.setRequestHandler(GetPromptRequestSchema, async (request) => {
  const { name, arguments: args } = request.params;

  if (name === "contact_summary") {
    const { contactId } = args;
    const contact = await client.get(`/contacts/${contactId}`);
    const activities = await client.get(`/contacts/${contactId}/activities`);

    return {
      description: `Summary for ${contact.name}`,
      messages: [{
        role: "user",
        content: {
          type: "text",
          text: `Generate a comprehensive summary:\n\n${JSON.stringify({ contact, activities }, null, 2)}`,
        },
      }],
    };
  }

  throw new Error(`Unknown prompt: ${name}`);
});

Phase 9: Testing Checklist

9.1 Local Testing

  • All tools compile without errors (npm run build)
  • Server starts successfully (npm start)
  • Environment variables validated on startup
  • Test each tool in Claude Desktop
  • Test pagination (page 1, page 2)
  • Test error cases (invalid IDs, missing params)
  • Test apps render correctly (if applicable)
  • Check logs in Claude Desktop console

9.2 Performance Testing

  • List operations return in <2 seconds
  • Lazy-loaded resources only fetch when requested
  • No unnecessary API calls
  • Pagination caps at API maximum
  • Progress notifications for operations >5 seconds

Phase 10: Documentation

10.1 README.md Structure

# MCP Server for MyService

MCP integration for MyService. Enables Claude Desktop to [core value prop].

## Features
- ✅ List/search/CRUD contacts
- ✅ Analytics dashboard (MCP App)
- ✅ Pipeline visualization (MCP App)
- ✅ Progress tracking for imports

## Installation
[npx / manual / docker options]

## Configuration
[Environment variables with .env.example]

## Available Tools
[List of tools with descriptions]

## MCP Apps (Rich UI)
[List of app tools with screenshots]

## Development
[Build/dev instructions]

10.2 .env.example

# MyService API Credentials
MY_SERVICE_API_KEY=your_api_key_here
MY_SERVICE_API_SECRET=your_secret_here

# Optional: Override base URL
# MY_SERVICE_BASE_URL=https://sandbox.api.myservice.com

# Optional: Logging
# LOG_LEVEL=debug

Phase 11: Deployment

11.1 Docker

  • Multi-stage Dockerfile
  • .dockerignore file
  • Test build locally
  • Test run locally

11.2 Railway

  • railway.json with build + start commands
  • Environment variables documented
  • Test deployment

11.3 npm Publishing

  • bin field in package.json
  • files field includes only dist/
  • .npmignore excludes src/, .env
  • Keywords for discoverability
  • Test npx installation locally

11.4 GitHub

  • README.md complete
  • LICENSE file
  • .gitignore excludes node_modules, dist, .env
  • GitHub Actions for CI/CD (optional)

Production Checklist (Final Review)

Code Quality

  • All tools have _meta.labels
  • All parameters have descriptions
  • Required fields marked explicitly
  • Pagination implemented for list operations
  • Error handling in all tool handlers
  • No hardcoded API keys or secrets
  • Logging for important operations

Features

  • Lazy-loaded resources for large datasets
  • Progress notifications for long operations (>5s)
  • MCP Apps for visual data (if applicable)
  • Prompts for common workflows (if applicable)

Documentation

  • README with installation instructions
  • .env.example with all required variables
  • Tool descriptions clear and helpful
  • Examples in README

Deployment

  • Compiles without errors
  • Runs in Claude Desktop
  • Docker image builds (if using Docker)
  • Railway deploys successfully (if using Railway)
  • npm package installs via npx (if publishing)

Anti-Patterns to Avoid

No labels on tools — Always add _meta.labels Loading all data upfront — Use lazy-loaded resources No pagination — Every list operation needs page/pageSize Silent failures — Always log errors and return clear messages No progress for slow ops — Add progress notifications for >5s operations Building apps when not needed — Only build apps if visually beneficial Missing descriptions — Every parameter needs a description No environment validation — Check env vars on startup Skipping error handling — Wrap all tool handlers in try-catch Generic error messages — Be specific ("Contact not found" not "Error")


Reference Materials

  • Skills:

    • mcp-server-development — Full TypeScript patterns
    • mcp-apps-integration — MCP Apps guide
    • mcp-deployment — Docker/Railway/npm
  • Example Servers:

    • /Users/jakeshore/.clawdbot/workspace/mcp-diagrams/mcp-servers/
    • 30 production servers with all patterns
  • Example Apps:

    • /Users/jakeshore/.clawdbot/workspace/mcp-diagrams/ghl-mcp-apps-only/
    • 11 production apps with UI components

TL;DR — The Golden Rules

  1. Labels on every tool — category, access, complexity
  2. Lazy-load large datasets — Use resources, not tools
  3. Paginate everything — page/pageSize on all lists
  4. Progress for slow ops — >5 seconds = progress notifications
  5. Apps for visual data — Grids, dashboards, cards, timelines
  6. Log important operations — Info, Warning, Error levels
  7. Handle errors gracefully — Clear messages, no silent failures
  8. Document thoroughly — README, .env.example, descriptions
  9. Test before shipping — All tools work in Claude Desktop
  10. Deploy with confidence — Docker, Railway, npm ready to go

Follow this blueprint and your MCP servers will be production-ready, usable, and optimized for February 2026.