The MCP Server Guide

Build AI Tool Integrations That Sell

By SpunkArt • 2026 Edition • 10 Chapters • $24.99
10
Chapters
40+
Code Examples
MCP
Protocol Deep Dive
2026
Latest Spec

1 What Is MCP (Model Context Protocol)?

The Model Context Protocol, or MCP, is an open standard created by Anthropic that defines how AI assistants connect to external tools, data sources, and services. Think of MCP as the USB standard for AI. Before USB, every device needed its own proprietary cable. Before MCP, every AI integration needed its own custom API adapter. MCP creates a universal interface that any AI client can use to connect to any tool server.

The protocol was first released by Anthropic in late 2024 and has since been adopted across the AI industry. Claude, Cursor, Windsurf, Cline, Zed, and dozens of other AI tools support MCP. This means that when you build an MCP server, your integration works with every major AI platform automatically. Build once, run everywhere.

The Architecture

MCP follows a client-server architecture with three key roles:

# MCP Architecture Overview
# ============================

# User (human)
#   |
#   v
# MCP Host (Claude Desktop, Cursor, etc.)
#   |
#   +-- MCP Client 1 <--> MCP Server: Database Tools
#   |
#   +-- MCP Client 2 <--> MCP Server: GitHub Integration
#   |
#   +-- MCP Client 3 <--> MCP Server: Web Scraper
#   |
#   +-- MCP Client N <--> MCP Server: Your Custom Server

# Each server exposes:
# - Tools: Functions the AI can call (e.g., "query_database")
# - Resources: Data the AI can read (e.g., "file://config.json")
# - Prompts: Pre-built prompt templates for common tasks

Why MCP Matters

Before MCP, connecting an AI assistant to an external service required building a custom integration for every AI platform. If you wanted your database tool to work with Claude, Cursor, and GPT, you needed three separate integrations. Each platform had its own API format, authentication scheme, and tool-calling convention. This fragmentation was unsustainable.

MCP solves this by providing a single, standardized protocol. The benefits cascade:

  1. Build once, deploy everywhere. Your MCP server works with every MCP-compatible client without modification. One codebase serves Claude, Cursor, Windsurf, and any future AI tool that adopts the standard.
  2. Composability. Users can connect multiple MCP servers to the same AI session. A developer might connect a database server, a GitHub server, and a deployment server simultaneously. The AI sees all tools from all servers and can orchestrate complex workflows across them.
  3. Security by design. MCP servers run locally on the user's machine. Data never passes through a third-party cloud unless you explicitly design it that way. This is critical for enterprise adoption where data sovereignty matters.
  4. The ecosystem effect. As more developers build MCP servers, the value of every MCP-compatible client increases. This creates a flywheel that accelerates adoption and makes MCP the de facto standard for AI tool integration.

The MCP Primitives

MCP defines three core primitives that servers can expose:

First-Mover Advantage: MCP is still in its early stages. The marketplace for MCP servers is just emerging. Building and publishing useful MCP servers now positions you as an authority in a market that will explode over the next 12-24 months. The developers who build the best MCP servers today will own the ecosystem tomorrow.

This book teaches you everything from building your first MCP server to publishing it on marketplaces and generating revenue. By the end, you will have the skills to build MCP integrations for any service and the business model to turn those integrations into income.

2 Setting Up Your First MCP Server

Building an MCP server is surprisingly simple. The protocol handles the complexity of AI communication. You just need to define what tools your server provides and implement the logic behind them. This chapter walks you through building a complete, working MCP server from scratch in under 30 minutes.

Prerequisites

# Requirements:
# - Node.js 18+ (or Python 3.10+)
# - npm (comes with Node.js)
# - A text editor or IDE
# - Claude Desktop or Cursor (for testing)

# Verify your setup:
node --version    # Should be 18.x or higher
npm --version     # Should be 9.x or higher

Project Scaffolding

# Create a new MCP server project
mkdir my-mcp-server && cd my-mcp-server
npm init -y

# Install the MCP SDK
npm install @modelcontextprotocol/sdk

# Install TypeScript (recommended)
npm install -D typescript @types/node
npx tsc --init

# Project structure:
# my-mcp-server/
#   src/
#     index.ts        # Main server entry point
#   package.json
#   tsconfig.json

Your First MCP Server

Let us build a practical MCP server that provides weather data. This is small enough to understand completely but demonstrates every core concept you need.

// src/index.ts
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";

// Create the MCP server
const server = new McpServer({
  name: "weather-server",
  version: "1.0.0",
  description: "Provides weather data for any city"
});

// Define a tool: get_weather
server.tool(
  "get_weather",
  "Get the current weather for a city",
  {
    city: z.string().describe("The city name (e.g., 'New York')"),
    units: z.enum(["celsius", "fahrenheit"])
      .optional()
      .default("fahrenheit")
      .describe("Temperature units")
  },
  async ({ city, units }) => {
    // In production, call a real weather API here
    // For demo, we use mock data
    const weather = await fetchWeather(city, units);

    return {
      content: [{
        type: "text",
        text: JSON.stringify(weather, null, 2)
      }]
    };
  }
);

// Define a resource: weather://forecast
server.resource(
  "weather://forecast/{city}",
  "Get a 5-day forecast for a city",
  async (uri) => {
    const city = uri.pathname.split("/").pop();
    const forecast = await fetchForecast(city);

    return {
      contents: [{
        uri: uri.href,
        mimeType: "application/json",
        text: JSON.stringify(forecast, null, 2)
      }]
    };
  }
);

// Helper function (replace with real API call)
async function fetchWeather(city: string, units: string) {
  // Call OpenWeatherMap, WeatherAPI, or similar
  const response = await fetch(
    `https://api.weatherapi.com/v1/current.json?key=${process.env.WEATHER_API_KEY}&q=${city}`
  );
  const data = await response.json();

  return {
    city: data.location.name,
    country: data.location.country,
    temperature: units === "celsius"
      ? data.current.temp_c
      : data.current.temp_f,
    units: units,
    condition: data.current.condition.text,
    humidity: data.current.humidity,
    wind_mph: data.current.wind_mph,
    updated: data.current.last_updated
  };
}

async function fetchForecast(city: string) {
  // Simplified forecast data
  return { city, days: 5, note: "Replace with real API call" };
}

// Start the server using stdio transport
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("Weather MCP server running on stdio");

Building and Running

# Add build script to package.json
# "scripts": {
#   "build": "tsc",
#   "start": "node dist/index.js"
# }

# Build the server
npm run build

# Test it manually (the server communicates via stdin/stdout)
echo '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"test","version":"1.0"}}}' | node dist/index.js

Configuring Claude Desktop

To connect your MCP server to Claude Desktop, add it to the configuration file:

# On macOS, edit:
# ~/Library/Application Support/Claude/claude_desktop_config.json

# On Windows, edit:
# %APPDATA%\Claude\claude_desktop_config.json

{
  "mcpServers": {
    "weather": {
      "command": "node",
      "args": ["/absolute/path/to/my-mcp-server/dist/index.js"],
      "env": {
        "WEATHER_API_KEY": "your-api-key-here"
      }
    }
  }
}

# Restart Claude Desktop after editing the config.
# You should see a hammer icon indicating tools are available.
# Ask Claude: "What's the weather in Tokyo?"
# Claude will call your get_weather tool automatically.

The Python Alternative

# If you prefer Python, the setup is equally simple:

pip install mcp

# server.py
from mcp.server import Server
from mcp.server.stdio import stdio_server

app = Server("weather-server")

@app.tool()
async def get_weather(city: str, units: str = "fahrenheit") -> str:
    """Get the current weather for a city."""
    # Your weather logic here
    return f"Weather in {city}: 72F, Sunny"

async def main():
    async with stdio_server() as (read, write):
        await app.run(read, write)

import asyncio
asyncio.run(main())
Security Note: Never hardcode API keys in your MCP server source code. Always use environment variables passed through the MCP client configuration. The env field in the config is specifically designed for this. Anyone who can read your source code should not automatically have access to your API keys.

You now have a working MCP server. It took about 50 lines of code. The protocol handles all the communication complexity. In the next chapters, we will build more sophisticated tools and learn how to create server integrations that people will pay for.

3 Tool Definitions and Schemas

The tool definition is the contract between your MCP server and the AI client. It tells the AI what your tool does, what parameters it accepts, and what it returns. A well-defined tool is discoverable, predictable, and reliable. A poorly defined tool confuses the AI, produces unexpected results, and frustrates users. This chapter teaches you how to craft tool definitions that work perfectly every time.

The Anatomy of a Tool Definition

Every MCP tool has four components:

  1. Name: A unique identifier for the tool. Use snake_case, be descriptive. search_database not search.
  2. Description: A human-readable explanation of what the tool does. This is what the AI reads to decide when to use your tool. Make it clear and specific.
  3. Input Schema: A JSON Schema (or Zod schema) defining the parameters the tool accepts. Every parameter needs a type, description, and optionally a default value.
  4. Handler: The function that executes when the tool is called. Receives the validated parameters and returns a result.
// Comprehensive tool definition example
server.tool(
  // 1. Name - descriptive, snake_case
  "search_products",

  // 2. Description - tells the AI when and how to use this tool
  "Search the product catalog by keyword, category, or price range. " +
  "Returns matching products with name, price, rating, and URL. " +
  "Use this when the user asks about products, prices, or availability.",

  // 3. Input Schema - defines all parameters
  {
    query: z.string()
      .describe("Search keywords (e.g., 'wireless headphones')"),

    category: z.enum([
      "electronics", "clothing", "home", "books", "all"
    ])
      .optional()
      .default("all")
      .describe("Product category to filter by"),

    min_price: z.number()
      .min(0)
      .optional()
      .describe("Minimum price in USD"),

    max_price: z.number()
      .max(10000)
      .optional()
      .describe("Maximum price in USD"),

    sort_by: z.enum(["relevance", "price_low", "price_high", "rating"])
      .optional()
      .default("relevance")
      .describe("Sort order for results"),

    limit: z.number()
      .min(1)
      .max(50)
      .optional()
      .default(10)
      .describe("Maximum number of results to return")
  },

  // 4. Handler - the actual implementation
  async ({ query, category, min_price, max_price, sort_by, limit }) => {
    const results = await productDatabase.search({
      query, category, min_price, max_price, sort_by, limit
    });

    return {
      content: [{
        type: "text",
        text: JSON.stringify({
          total: results.length,
          products: results
        }, null, 2)
      }]
    };
  }
);

Schema Design Best Practices

The quality of your schema directly affects how well the AI uses your tool. Follow these rules:

Rule 1: Describe Everything

Every parameter must have a .describe() call with a clear explanation. The AI reads these descriptions to understand what values to pass. Without descriptions, the AI guesses and often guesses wrong.

// Bad: No descriptions
{
  q: z.string(),
  n: z.number().optional()
}

// Good: Clear descriptions
{
  query: z.string()
    .describe("The search query string, e.g., 'blue running shoes'"),
  max_results: z.number()
    .optional()
    .default(10)
    .describe("Maximum number of results to return (1-100)")
}

Rule 2: Use Enums for Fixed Options

// Bad: Free-form string for known values
{
  format: z.string().describe("Output format")
}

// Good: Enum with explicit options
{
  format: z.enum(["json", "csv", "xml", "markdown"])
    .describe("Output format for the exported data")
}

Rule 3: Set Sensible Defaults

// Bad: Required parameter that usually has the same value
{
  include_metadata: z.boolean()
    .describe("Whether to include metadata in results")
}

// Good: Optional with a default
{
  include_metadata: z.boolean()
    .optional()
    .default(true)
    .describe("Whether to include metadata in results")
}

Rule 4: Validate Ranges

// Bad: No validation
{
  page_size: z.number()
}

// Good: Range validation prevents abuse
{
  page_size: z.number()
    .min(1)
    .max(100)
    .default(20)
    .describe("Number of items per page (1-100)")
}

Return Value Patterns

How you structure return values affects how the AI interprets and presents results to the user:

// Pattern 1: Structured JSON (best for data)
return {
  content: [{
    type: "text",
    text: JSON.stringify({
      success: true,
      data: results,
      metadata: { total: 42, page: 1, pages: 5 }
    }, null, 2)
  }]
};

// Pattern 2: Human-readable text (best for summaries)
return {
  content: [{
    type: "text",
    text: `Found ${results.length} products matching "${query}":
${results.map(p => `- ${p.name}: $${p.price} (${p.rating} stars)`).join('\n')}

Showing page ${page} of ${totalPages}.`
  }]
};

// Pattern 3: Multiple content blocks (best for rich results)
return {
  content: [
    {
      type: "text",
      text: `Search results for "${query}"`
    },
    {
      type: "text",
      text: JSON.stringify(results, null, 2)
    }
  ]
};

// Pattern 4: Error handling
return {
  content: [{
    type: "text",
    text: JSON.stringify({
      error: true,
      message: "Database connection failed",
      code: "DB_CONN_ERROR",
      suggestion: "Check that the database server is running"
    })
  }],
  isError: true
};
Pro Tip: Include a suggestion field in error responses. When the AI sees a suggestion, it can relay actionable advice to the user instead of just reporting a generic error. This dramatically improves the user experience.

Well-crafted tool definitions are the foundation of every successful MCP server. Invest time here and everything downstream becomes easier: the AI uses your tools correctly, users get reliable results, and your server's reputation grows organically.

4 Connecting to Claude and Cursor

Your MCP server is built and your tools are defined. Now you need to connect it to the AI clients your users actually use. This chapter covers the configuration and integration details for Claude Desktop, Claude Code, Cursor, and other popular MCP hosts. Getting this right means your server works seamlessly. Getting it wrong means users give up and uninstall.

Claude Desktop Configuration

Claude Desktop is the most popular MCP host. Configuration is done through a JSON file that tells Claude where to find your server and how to launch it.

# Configuration file location:
# macOS: ~/Library/Application Support/Claude/claude_desktop_config.json
# Windows: %APPDATA%\Claude\claude_desktop_config.json
# Linux: ~/.config/Claude/claude_desktop_config.json

{
  "mcpServers": {
    "my-server": {
      "command": "node",
      "args": ["/Users/you/my-mcp-server/dist/index.js"],
      "env": {
        "API_KEY": "your-api-key",
        "DATABASE_URL": "postgresql://localhost:5432/mydb"
      }
    }
  }
}

# For npx-based servers (no local install needed):
{
  "mcpServers": {
    "my-server": {
      "command": "npx",
      "args": ["-y", "my-mcp-server-package"]
    }
  }
}

# For Python servers:
{
  "mcpServers": {
    "my-server": {
      "command": "python3",
      "args": ["/Users/you/my-mcp-server/server.py"],
      "env": {
        "API_KEY": "your-api-key"
      }
    }
  }
}

Claude Code Configuration

Claude Code, the terminal-based AI coding tool, also supports MCP servers. Configuration is managed through the claude mcp command:

# Add an MCP server to Claude Code
claude mcp add my-server node /path/to/my-mcp-server/dist/index.js

# Add with environment variables
claude mcp add my-server \
  -e API_KEY=your-key \
  -e DATABASE_URL=postgresql://localhost/mydb \
  -- node /path/to/server/dist/index.js

# List configured servers
claude mcp list

# Remove a server
claude mcp remove my-server

# Scopes: project-level vs global
# Project scope (stored in .mcp.json in project root):
claude mcp add --scope project my-server node ./dist/index.js

# Global scope (available in all projects):
claude mcp add --scope global my-server node /path/to/dist/index.js

Cursor Configuration

Cursor supports MCP servers through its settings interface and also through a JSON configuration file:

# Cursor MCP config location:
# ~/.cursor/mcp.json (global)
# .cursor/mcp.json (project-level)

{
  "mcpServers": {
    "my-server": {
      "command": "node",
      "args": ["/path/to/my-mcp-server/dist/index.js"],
      "env": {
        "API_KEY": "your-api-key"
      }
    }
  }
}

# Cursor also supports SSE (Server-Sent Events) transport
# for remote MCP servers:
{
  "mcpServers": {
    "remote-server": {
      "url": "https://my-mcp-server.example.com/sse",
      "headers": {
        "Authorization": "Bearer your-token"
      }
    }
  }
}

Transport Types

MCP supports two transport mechanisms. Choosing the right one matters for performance and deployment:

stdio Transport (Local Servers)

The default transport. The MCP host launches your server as a child process and communicates via stdin/stdout. Best for local development and distribution via npm/pip.

// stdio transport (default)
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";

const transport = new StdioServerTransport();
await server.connect(transport);

// Advantages:
// - No network configuration
// - No port conflicts
// - Secure (no external access)
// - Simple to distribute (npm package)

// Disadvantages:
// - Must be installed locally
// - One instance per client

SSE Transport (Remote Servers)

For servers that run on a remote machine or cloud service. Uses HTTP with Server-Sent Events for real-time communication.

// SSE transport (remote servers)
import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
import express from "express";

const app = express();

app.get("/sse", async (req, res) => {
  const transport = new SSEServerTransport("/messages", res);
  await server.connect(transport);
});

app.post("/messages", async (req, res) => {
  // Handle incoming messages
  await transport.handlePostMessage(req, res);
});

app.listen(3000, () => {
  console.log("MCP SSE server running on port 3000");
});

// Advantages:
// - No local installation required
// - Shared state across clients
// - Centralized updates

// Disadvantages:
// - Requires server hosting
// - Network latency
// - Must handle authentication

Testing Your Connection

# Test with the MCP Inspector (official debugging tool)
npx @modelcontextprotocol/inspector node dist/index.js

# This opens a web UI where you can:
# - See all registered tools
# - Test tool calls with custom parameters
# - View resources and prompts
# - Debug protocol messages
# - Inspect errors and responses

# Manual test via Claude Desktop:
# 1. Add your server to claude_desktop_config.json
# 2. Restart Claude Desktop
# 3. Look for the hammer icon (tools available)
# 4. Ask Claude to use your tool:
#    "Search for wireless headphones under $100"
# 5. Claude should call your search_products tool
Common Pitfall: After editing the Claude Desktop config file, you must restart Claude Desktop completely. Simply closing and reopening the chat window is not enough. On macOS, use Cmd+Q to fully quit, then relaunch. If your server still does not appear, check the logs at ~/Library/Logs/Claude/mcp*.log for error messages.

With your server connected to Claude, Cursor, or both, you can now test it in real-world conversations. In the next chapter, we build integrations that solve real problems and demonstrate the full power of the MCP protocol.

5 Building Useful Integrations

The difference between an MCP server that collects dust and one that gets thousands of installations is usefulness. Users install MCP servers that solve real, recurring problems. This chapter walks through five complete MCP server builds that solve common pain points. Each is production-ready and demonstrates patterns you can adapt for your own integrations.

Integration 1: Database Query Server

The most requested MCP integration. Let the AI query your database directly, safely, with read-only access.

// mcp-database-server
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
import pg from "pg";

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

const server = new McpServer({
  name: "database-query",
  version: "1.0.0"
});

// Tool: Run a read-only SQL query
server.tool(
  "query_database",
  "Execute a read-only SQL query against the PostgreSQL database. " +
  "Only SELECT statements are allowed. Returns results as JSON.",
  {
    sql: z.string().describe(
      "SQL SELECT query to execute. Must be read-only."
    ),
    limit: z.number().min(1).max(1000).default(100)
      .describe("Maximum rows to return")
  },
  async ({ sql, limit }) => {
    // Safety: Only allow SELECT statements
    const trimmed = sql.trim().toUpperCase();
    if (!trimmed.startsWith("SELECT")) {
      return {
        content: [{ type: "text", text: "Error: Only SELECT queries are allowed." }],
        isError: true
      };
    }

    // Add LIMIT if not present
    if (!trimmed.includes("LIMIT")) {
      sql = `${sql} LIMIT ${limit}`;
    }

    try {
      const result = await pool.query(sql);
      return {
        content: [{
          type: "text",
          text: JSON.stringify({
            rows: result.rows,
            rowCount: result.rowCount,
            fields: result.fields.map(f => f.name)
          }, null, 2)
        }]
      };
    } catch (error) {
      return {
        content: [{ type: "text", text: `Query error: ${error.message}` }],
        isError: true
      };
    }
  }
);

// Resource: Database schema
server.resource(
  "schema://tables",
  "List all tables and their columns in the database",
  async () => {
    const result = await pool.query(`
      SELECT table_name, column_name, data_type, is_nullable
      FROM information_schema.columns
      WHERE table_schema = 'public'
      ORDER BY table_name, ordinal_position
    `);

    return {
      contents: [{
        uri: "schema://tables",
        mimeType: "application/json",
        text: JSON.stringify(result.rows, null, 2)
      }]
    };
  }
);

const transport = new StdioServerTransport();
await server.connect(transport);

Integration 2: File Search and Analysis Server

// mcp-file-search - Search and analyze files in a directory
server.tool(
  "search_files",
  "Search for files by name, extension, or content. " +
  "Returns matching file paths, sizes, and modification dates.",
  {
    directory: z.string()
      .describe("Root directory to search in"),
    pattern: z.string()
      .describe("Glob pattern (e.g., '**/*.ts') or regex for content search"),
    search_type: z.enum(["filename", "content"])
      .default("filename")
      .describe("Search by filename pattern or file content"),
    max_results: z.number().min(1).max(100).default(20)
      .describe("Maximum number of files to return")
  },
  async ({ directory, pattern, search_type, max_results }) => {
    const results = search_type === "filename"
      ? await globSearch(directory, pattern, max_results)
      : await contentSearch(directory, pattern, max_results);

    return {
      content: [{
        type: "text",
        text: JSON.stringify(results, null, 2)
      }]
    };
  }
);

server.tool(
  "analyze_codebase",
  "Analyze a codebase and return statistics: file counts by type, " +
  "total lines of code, largest files, and dependency information.",
  {
    directory: z.string().describe("Root directory of the codebase")
  },
  async ({ directory }) => {
    const stats = await analyzeDirectory(directory);
    return {
      content: [{
        type: "text",
        text: JSON.stringify(stats, null, 2)
      }]
    };
  }
);

Integration 3: Web Scraping Server

// mcp-web-scraper - Fetch and extract data from web pages
server.tool(
  "scrape_page",
  "Fetch a web page and extract its content as clean text or " +
  "structured data. Useful for reading documentation, articles, " +
  "or any web content.",
  {
    url: z.string().url()
      .describe("The URL to scrape"),
    extract: z.enum(["text", "links", "images", "meta", "all"])
      .default("text")
      .describe("What to extract from the page"),
    selector: z.string().optional()
      .describe("CSS selector to extract specific elements")
  },
  async ({ url, extract, selector }) => {
    const response = await fetch(url);
    const html = await response.text();
    const extracted = parseHTML(html, extract, selector);

    return {
      content: [{
        type: "text",
        text: JSON.stringify({
          url,
          title: extracted.title,
          data: extracted.data
        }, null, 2)
      }]
    };
  }
);

Integration 4: API Monitoring Server

// mcp-api-monitor - Check API health and response times
server.tool(
  "check_api",
  "Check the health, response time, and status of an API endpoint. " +
  "Returns status code, response time, headers, and body preview.",
  {
    url: z.string().url().describe("API endpoint URL"),
    method: z.enum(["GET", "POST", "PUT", "DELETE"]).default("GET"),
    headers: z.record(z.string()).optional()
      .describe("Custom headers as key-value pairs"),
    body: z.string().optional()
      .describe("Request body (for POST/PUT)")
  },
  async ({ url, method, headers, body }) => {
    const start = Date.now();

    try {
      const response = await fetch(url, {
        method,
        headers: { "Content-Type": "application/json", ...headers },
        body: body ? body : undefined
      });

      const elapsed = Date.now() - start;
      const responseBody = await response.text();

      return {
        content: [{
          type: "text",
          text: JSON.stringify({
            status: response.status,
            statusText: response.statusText,
            response_time_ms: elapsed,
            headers: Object.fromEntries(response.headers),
            body_preview: responseBody.substring(0, 500),
            healthy: response.status >= 200 && response.status < 300
          }, null, 2)
        }]
      };
    } catch (error) {
      return {
        content: [{ type: "text", text: `Request failed: ${error.message}` }],
        isError: true
      };
    }
  }
);

Integration 5: Git Operations Server

// mcp-git - Safe git operations for AI assistants
server.tool(
  "git_status",
  "Get the current git status of a repository, including " +
  "modified files, staged changes, and branch information.",
  {
    repo_path: z.string().describe("Path to the git repository")
  },
  async ({ repo_path }) => {
    const status = execSync(`git -C ${repo_path} status --porcelain`);
    const branch = execSync(`git -C ${repo_path} branch --show-current`);
    const log = execSync(
      `git -C ${repo_path} log --oneline -5`
    );

    return {
      content: [{
        type: "text",
        text: JSON.stringify({
          branch: branch.toString().trim(),
          changes: status.toString().trim().split('\n').filter(Boolean),
          recent_commits: log.toString().trim().split('\n')
        }, null, 2)
      }]
    };
  }
);
The Key Pattern: Every successful MCP integration follows the same formula: take something the AI cannot do on its own (access a database, call an API, read local files) and expose it as a clean, well-documented tool. The simpler the tool interface, the more reliably the AI uses it.

6 Testing and Debugging

Shipping a buggy MCP server destroys trust instantly. When a user installs your server and the first tool call fails, they uninstall and never come back. Thorough testing is the difference between MCP servers that get recommended and shared versus ones that get abandoned. This chapter covers every testing and debugging technique you need.

The MCP Inspector

The official MCP Inspector is your primary debugging tool. It provides a visual interface for testing every aspect of your server without needing to connect to a real AI client.

# Launch the MCP Inspector
npx @modelcontextprotocol/inspector node dist/index.js

# Or with environment variables:
npx @modelcontextprotocol/inspector \
  -e API_KEY=test-key \
  -e DATABASE_URL=postgresql://localhost/test \
  node dist/index.js

# The Inspector lets you:
# 1. View all registered tools, resources, and prompts
# 2. Call tools with custom parameters
# 3. Read resources by URI
# 4. See raw protocol messages
# 5. Inspect errors with full stack traces
# 6. Test parameter validation

Unit Testing Your Tools

// test/tools.test.ts
import { describe, it, expect } from "vitest";
import { searchProducts } from "../src/tools/search.js";

describe("searchProducts", () => {
  it("returns results for valid query", async () => {
    const results = await searchProducts({
      query: "headphones",
      category: "electronics",
      limit: 5
    });

    expect(results).toBeDefined();
    expect(results.length).toBeLessThanOrEqual(5);
    expect(results[0]).toHaveProperty("name");
    expect(results[0]).toHaveProperty("price");
  });

  it("handles empty results gracefully", async () => {
    const results = await searchProducts({
      query: "xyznonexistentproduct123",
      category: "all",
      limit: 10
    });

    expect(results).toEqual([]);
  });

  it("validates price range", async () => {
    const results = await searchProducts({
      query: "laptop",
      min_price: 500,
      max_price: 1000,
      limit: 20
    });

    results.forEach(product => {
      expect(product.price).toBeGreaterThanOrEqual(500);
      expect(product.price).toBeLessThanOrEqual(1000);
    });
  });

  it("rejects SQL injection in query", async () => {
    const results = await searchProducts({
      query: "'; DROP TABLE products; --",
      limit: 10
    });

    // Should return empty results, not execute SQL
    expect(Array.isArray(results)).toBe(true);
  });
});

// Run tests:
// npx vitest run

Integration Testing

// test/integration.test.ts
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";

describe("MCP Server Integration", () => {
  let client: Client;

  beforeAll(async () => {
    const transport = new StdioClientTransport({
      command: "node",
      args: ["dist/index.js"],
      env: { ...process.env, API_KEY: "test-key" }
    });

    client = new Client({
      name: "test-client",
      version: "1.0.0"
    });

    await client.connect(transport);
  });

  afterAll(async () => {
    await client.close();
  });

  it("lists all tools", async () => {
    const tools = await client.listTools();
    expect(tools.tools.length).toBeGreaterThan(0);
    expect(tools.tools[0]).toHaveProperty("name");
    expect(tools.tools[0]).toHaveProperty("description");
    expect(tools.tools[0]).toHaveProperty("inputSchema");
  });

  it("calls search_products tool", async () => {
    const result = await client.callTool({
      name: "search_products",
      arguments: {
        query: "headphones",
        limit: 5
      }
    });

    expect(result.content[0].type).toBe("text");
    const data = JSON.parse(result.content[0].text);
    expect(data).toHaveProperty("products");
  });

  it("handles invalid parameters gracefully", async () => {
    const result = await client.callTool({
      name: "search_products",
      arguments: {
        query: "",
        limit: -1
      }
    });

    expect(result.isError).toBe(true);
  });
});

Debugging Common Issues

# Issue 1: Server not appearing in Claude Desktop
# Check the config file for JSON syntax errors:
python3 -m json.tool ~/Library/Application\ Support/Claude/claude_desktop_config.json

# Check MCP logs:
tail -f ~/Library/Logs/Claude/mcp*.log

# Issue 2: Tool calls failing silently
# Add logging to your server (use stderr, not stdout):
console.error("[DEBUG] Tool called:", toolName, params);
console.error("[DEBUG] Result:", JSON.stringify(result));

# Issue 3: Server crashes on startup
# Test the server standalone:
node dist/index.js
# Then send a test message via stdin:
echo '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}' | node dist/index.js

# Issue 4: Environment variables not passed
# Verify env vars are in the config:
cat ~/Library/Application\ Support/Claude/claude_desktop_config.json | jq '.mcpServers'

# Issue 5: Timeout errors
# MCP has a default timeout. For long-running operations:
# - Send progress notifications
# - Break large operations into smaller tool calls
# - Use async patterns with status polling

Error Handling Patterns

// Comprehensive error handling for MCP tools
async function safeToolHandler(fn, params) {
  try {
    const result = await fn(params);
    return {
      content: [{
        type: "text",
        text: JSON.stringify(result, null, 2)
      }]
    };
  } catch (error) {
    // Log the full error for debugging
    console.error(`[ERROR] Tool failed:`, error);

    // Return a user-friendly error
    return {
      content: [{
        type: "text",
        text: JSON.stringify({
          error: true,
          message: error.message,
          type: error.constructor.name,
          suggestion: getSuggestion(error)
        })
      }],
      isError: true
    };
  }
}

function getSuggestion(error) {
  if (error.code === "ECONNREFUSED") {
    return "The service appears to be down. Check that it is running.";
  }
  if (error.code === "ENOTFOUND") {
    return "DNS lookup failed. Check the URL and your internet connection.";
  }
  if (error.message.includes("timeout")) {
    return "The request timed out. Try again or use a smaller query.";
  }
  return "Try again. If the problem persists, check the server logs.";
}
Testing Checklist: Before publishing any MCP server, verify: (1) All tools respond to valid input, (2) All tools handle invalid input gracefully, (3) The server starts without errors, (4) The server works with both Claude Desktop and Cursor, (5) Error messages are helpful, (6) No sensitive data is logged or returned, (7) Long-running operations have timeouts.

7 Publishing to Marketplaces

A well-built MCP server sitting on your local machine helps no one. Publishing your server makes it discoverable, installable, and usable by thousands of developers. This chapter covers every publishing channel available in 2026 and how to maximize your server's visibility on each platform.

npm (Primary Distribution)

npm is the primary distribution channel for MCP servers. Most MCP hosts support npx as a launch command, which means users can run your server without installing it permanently.

# Prepare your package.json
{
  "name": "mcp-server-weather",
  "version": "1.0.0",
  "description": "MCP server for weather data",
  "main": "dist/index.js",
  "type": "module",
  "bin": {
    "mcp-server-weather": "dist/index.js"
  },
  "files": ["dist/"],
  "keywords": [
    "mcp", "mcp-server", "model-context-protocol",
    "weather", "ai-tools", "claude", "cursor"
  ],
  "license": "MIT",
  "repository": {
    "type": "git",
    "url": "https://github.com/you/mcp-server-weather"
  }
}

# Add shebang to your entry point
# First line of dist/index.js should be:
#!/usr/bin/env node

# Publish to npm
npm login
npm publish

# Users install with:
# npx -y mcp-server-weather
# Or configure in Claude Desktop:
# { "command": "npx", "args": ["-y", "mcp-server-weather"] }

GitHub Repository

A well-organized GitHub repository is essential for credibility, contributions, and discoverability. Here is the structure every MCP server repo should have:

# Repository structure
mcp-server-weather/
  src/
    index.ts
    tools/
    resources/
  dist/              # Built output (gitignored in source)
  test/
    tools.test.ts
    integration.test.ts
  README.md          # Critical: installation, usage, examples
  LICENSE            # MIT recommended
  CHANGELOG.md       # Version history
  package.json
  tsconfig.json
  .github/
    workflows/
      test.yml       # CI: run tests on every push
      publish.yml    # CD: publish to npm on tag

Writing a README That Converts

Your README is your sales page. It determines whether a developer installs your server or moves on. Follow this structure:

# mcp-server-weather

> MCP server that provides real-time weather data to AI assistants.
> Works with Claude Desktop, Cursor, Claude Code, and any MCP client.

## Quick Start

```bash
# Add to Claude Desktop config:
{
  "mcpServers": {
    "weather": {
      "command": "npx",
      "args": ["-y", "mcp-server-weather"],
      "env": {
        "WEATHER_API_KEY": "your-key"
      }
    }
  }
}
```

## Available Tools

| Tool | Description |
|------|-------------|
| `get_weather` | Current weather for any city |
| `get_forecast` | 5-day weather forecast |
| `weather_alerts` | Active weather alerts by region |

## Example Usage

Ask Claude: "What's the weather in Tokyo?"

Claude will automatically call `get_weather` and return:
- Temperature, humidity, wind speed
- Current conditions
- Feels-like temperature

## Configuration

| Env Variable | Required | Description |
|-------------|----------|-------------|
| `WEATHER_API_KEY` | Yes | API key from weatherapi.com |

## License

MIT

MCP Server Directories

Several directories and registries are emerging for MCP servers. List your server on all of them for maximum discoverability:

# Maximize discoverability:
# 1. Publish to npm with "mcp" and "mcp-server" keywords
# 2. Submit to mcp.so directory
# 3. Submit to Smithery registry
# 4. Submit to Glama directory
# 5. Post on X/Twitter with #MCP hashtag
# 6. Share in MCP Discord communities
# 7. Write a blog post showing your server in action
# 8. Create a demo video (2-3 minutes)

Versioning and Updates

# Semantic versioning for MCP servers:
# MAJOR.MINOR.PATCH

# PATCH (1.0.0 -> 1.0.1): Bug fixes, no API changes
# MINOR (1.0.0 -> 1.1.0): New tools added, backward compatible
# MAJOR (1.0.0 -> 2.0.0): Breaking changes to existing tools

# Automated publishing with GitHub Actions:
# .github/workflows/publish.yml
name: Publish to npm
on:
  push:
    tags:
      - 'v*'
jobs:
  publish:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          registry-url: https://registry.npmjs.org
      - run: npm ci
      - run: npm test
      - run: npm run build
      - run: npm publish
        env:
          NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

# Release workflow:
git tag v1.1.0
git push origin v1.1.0
# GitHub Actions automatically publishes to npm
Pro Tip: The first impression matters enormously. Before publishing, have three people install and test your server from scratch using only your README. If any of them get stuck, your documentation needs work. The install-to-first-tool-call experience should take under five minutes.

8 Monetization Strategies

The MCP ecosystem is brand new. The monetization models are still forming. This is exactly the right time to establish yourself as a premium MCP server developer. This chapter covers every viable monetization strategy, from free-with-premium-upgrade to enterprise licensing. The key insight is that MCP servers solve real business problems, and businesses pay for solutions.

The MCP Revenue Stack

# MCP Monetization Models
# ============================

# Model 1: Open Source + Premium
# Free: Basic server with core tools
# Paid: Advanced tools, priority support, custom integrations
# Example: Free database query tool, paid analytics dashboard

# Model 2: SaaS Backend
# Free: MCP server (client-side)
# Paid: Cloud API the server connects to
# Example: Free scraping server, paid proxy/API service

# Model 3: Marketplace Listing
# Sell ready-made MCP servers on Gumroad or your own store
# Price: $19-99 per server or bundle
# Example: "Enterprise Database Server" for $49

# Model 4: Custom Development
# Build bespoke MCP servers for companies
# Price: $500-5,000 per custom server
# Example: Company needs MCP server for their internal API

# Model 5: Consulting
# Teach teams how to build and deploy MCP servers
# Price: $100-300/hour
# Example: Half-day workshop for a dev team

# Combined annual potential:
# Open source donations: $500-2,000
# Premium upgrades: $2,000-20,000
# Marketplace sales: $1,000-10,000
# Custom development: $5,000-50,000
# Consulting: $5,000-30,000

Open Source + Premium Model

This is the most sustainable model. Give away a genuinely useful free version that builds your reputation and audience. Charge for advanced features that power users and businesses need.

// Free tier: Basic database query
server.tool("query_database", ...);  // Free
server.tool("list_tables", ...);     // Free
server.tool("describe_table", ...);  // Free

// Premium tier: Advanced analytics
if (isLicensed()) {
  server.tool("query_analytics", ...);     // Premium
  server.tool("generate_report", ...);     // Premium
  server.tool("export_dashboard", ...);    // Premium
  server.tool("schedule_query", ...);      // Premium
  server.tool("query_optimization", ...);  // Premium
}

function isLicensed() {
  const key = process.env.LICENSE_KEY;
  if (!key) return false;
  // Validate license key against your server
  return validateLicense(key);
}

// License validation
async function validateLicense(key) {
  try {
    const res = await fetch("https://your-api.com/validate", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ key })
    });
    const data = await res.json();
    return data.valid === true;
  } catch {
    // If validation server is down, allow a grace period
    return checkOfflineCache(key);
  }
}

Selling on Gumroad

For standalone MCP server products, Gumroad is the simplest sales channel. Package your server with documentation, configuration guides, and support.

# Gumroad MCP Server Product Structure
# =======================================

# What to include in the download:
# 1. The MCP server source code
# 2. Pre-built binaries (npm package)
# 3. Installation guide (step-by-step with screenshots)
# 4. Configuration examples for Claude, Cursor, and others
# 5. Video walkthrough (5-10 minutes)
# 6. Support email for setup issues

# Pricing guidelines:
# Simple utility server: $9-19
# Professional tool server: $29-49
# Enterprise integration: $79-149
# Bundle of 5+ servers: $99-199

# Gumroad listing template:
# Title: "MCP Database Pro: Advanced Database Tools for AI"
# Description: Full feature list, screenshots, demo video
# Price: $49 (with "pay what you want" at $49 minimum)
# Files: ZIP containing server + docs + video
# License: Commercial use, single user

Custom Development Pricing

Companies need MCP servers for their internal tools, APIs, and databases. This is the highest-revenue opportunity in the MCP ecosystem right now because demand far exceeds supply.

# Custom MCP Server Pricing Guide
# ====================================

# Simple Integration (1-3 tools, single API)
# Time: 2-4 hours
# Price: $500-1,000
# Example: MCP server for company's internal CRM API

# Medium Integration (5-10 tools, multiple APIs)
# Time: 1-2 days
# Price: $1,500-3,000
# Example: MCP server for project management + CI/CD pipeline

# Complex Integration (10+ tools, database + APIs + custom logic)
# Time: 3-5 days
# Price: $3,000-5,000
# Example: Full data warehouse MCP server with analytics

# Enterprise Package (custom server + training + support)
# Time: 1-2 weeks
# Price: $5,000-15,000
# Example: Complete MCP infrastructure for a dev team

# How to find clients:
# 1. Post examples on X/Twitter (#MCP, #AItools)
# 2. Contribute to popular MCP repositories
# 3. Write tutorials on your blog
# 4. Answer questions in MCP Discord/forums
# 5. Create a "Hire me" page with your MCP portfolio

Building Recurring Revenue

// MCP server with cloud backend = recurring revenue
// The server is free. The API it connects to is paid.

// Example: MCP Analytics Server
// Free server connects to your analytics API
// Users pay $9/month for the API access

server.tool(
  "analyze_traffic",
  "Analyze website traffic patterns using AI",
  { url: z.string().url() },
  async ({ url }) => {
    // This calls YOUR paid API
    const response = await fetch("https://api.your-service.com/analyze", {
      headers: {
        "Authorization": `Bearer ${process.env.API_KEY}`,
        "Content-Type": "application/json"
      },
      method: "POST",
      body: JSON.stringify({ url })
    });

    const data = await response.json();
    return {
      content: [{
        type: "text",
        text: JSON.stringify(data, null, 2)
      }]
    };
  }
);

// Revenue model:
// Free tier: 100 API calls/month
// Pro: $9/month (1,000 calls)
// Business: $29/month (10,000 calls)
// Enterprise: $99/month (unlimited)
The Monetization Timeline: Month 1-2: Build free servers, establish reputation. Month 3-4: Launch first premium product on Gumroad. Month 5-6: Land first custom development client. Month 7+: Multiple revenue streams active. Do not try to monetize before you have credibility. Free servers are your portfolio. Premium products are your revenue. Custom development is your highest margin.

9 Real-World MCP Server Examples

Theory only takes you so far. This chapter presents five complete, real-world MCP server examples that solve genuine problems developers face every day. Each example is production-ready, well-documented, and demonstrates advanced MCP patterns you can adapt for your own servers.

Example 1: Notion Integration Server

Connect AI assistants to Notion for reading, creating, and searching pages and databases.

// mcp-notion-server
const server = new McpServer({
  name: "notion-integration",
  version: "1.0.0"
});

server.tool(
  "notion_search",
  "Search Notion pages and databases by keyword",
  {
    query: z.string().describe("Search query"),
    filter: z.enum(["page", "database", "all"]).default("all")
  },
  async ({ query, filter }) => {
    const response = await fetch("https://api.notion.com/v1/search", {
      method: "POST",
      headers: {
        "Authorization": `Bearer ${process.env.NOTION_TOKEN}`,
        "Notion-Version": "2022-06-28",
        "Content-Type": "application/json"
      },
      body: JSON.stringify({
        query,
        filter: filter !== "all" ? { property: "object", value: filter } : undefined
      })
    });

    const data = await response.json();
    const results = data.results.map(item => ({
      id: item.id,
      type: item.object,
      title: extractTitle(item),
      url: item.url,
      last_edited: item.last_edited_time
    }));

    return {
      content: [{
        type: "text",
        text: JSON.stringify({ total: results.length, results }, null, 2)
      }]
    };
  }
);

server.tool(
  "notion_create_page",
  "Create a new page in Notion with the given title and content",
  {
    parent_id: z.string().describe("Parent page or database ID"),
    title: z.string().describe("Page title"),
    content: z.string().describe("Page content in markdown")
  },
  async ({ parent_id, title, content }) => {
    const blocks = markdownToNotionBlocks(content);

    const response = await fetch("https://api.notion.com/v1/pages", {
      method: "POST",
      headers: {
        "Authorization": `Bearer ${process.env.NOTION_TOKEN}`,
        "Notion-Version": "2022-06-28",
        "Content-Type": "application/json"
      },
      body: JSON.stringify({
        parent: { page_id: parent_id },
        properties: {
          title: { title: [{ text: { content: title } }] }
        },
        children: blocks
      })
    });

    const page = await response.json();
    return {
      content: [{
        type: "text",
        text: `Created page: ${page.url}`
      }]
    };
  }
);

Example 2: Stripe Payments Server

// mcp-stripe-server - Read-only Stripe dashboard for AI
const stripe = require("stripe")(process.env.STRIPE_SECRET_KEY);

server.tool(
  "stripe_revenue_summary",
  "Get a revenue summary for a given time period. " +
  "Returns total revenue, number of charges, average order value.",
  {
    period: z.enum(["today", "week", "month", "year"])
      .describe("Time period to summarize")
  },
  async ({ period }) => {
    const now = Math.floor(Date.now() / 1000);
    const periods = {
      today: now - 86400,
      week: now - 604800,
      month: now - 2592000,
      year: now - 31536000
    };

    const charges = await stripe.charges.list({
      created: { gte: periods[period] },
      limit: 100
    });

    const successful = charges.data.filter(c => c.status === "succeeded");
    const totalRevenue = successful.reduce((sum, c) => sum + c.amount, 0) / 100;
    const avgOrder = successful.length > 0 ? totalRevenue / successful.length : 0;

    return {
      content: [{
        type: "text",
        text: JSON.stringify({
          period,
          total_revenue: `$${totalRevenue.toFixed(2)}`,
          total_charges: successful.length,
          average_order: `$${avgOrder.toFixed(2)}`,
          currency: "USD"
        }, null, 2)
      }]
    };
  }
);

server.tool(
  "stripe_recent_customers",
  "List recent customers with their purchase history",
  {
    limit: z.number().min(1).max(50).default(10)
  },
  async ({ limit }) => {
    const customers = await stripe.customers.list({ limit });
    const summary = customers.data.map(c => ({
      name: c.name || c.email,
      email: c.email,
      total_spent: `$${(c.balance * -1 / 100).toFixed(2)}`,
      created: new Date(c.created * 1000).toISOString()
    }));

    return {
      content: [{
        type: "text",
        text: JSON.stringify(summary, null, 2)
      }]
    };
  }
);

Example 3: SEO Analysis Server

// mcp-seo-analyzer - Check SEO health of any page
server.tool(
  "analyze_seo",
  "Analyze the SEO health of a web page. Returns title, meta, " +
  "headings, images, links, and improvement suggestions.",
  {
    url: z.string().url().describe("URL to analyze")
  },
  async ({ url }) => {
    const response = await fetch(url);
    const html = await response.text();

    const analysis = {
      url,
      title: extractTitle(html),
      title_length: extractTitle(html).length,
      meta_description: extractMeta(html, "description"),
      meta_description_length: extractMeta(html, "description").length,
      h1_count: (html.match(/<h1/gi) || []).length,
      h2_count: (html.match(/<h2/gi) || []).length,
      image_count: (html.match(/<img/gi) || []).length,
      images_without_alt: countImagesWithoutAlt(html),
      internal_links: countInternalLinks(html, url),
      external_links: countExternalLinks(html, url),
      has_canonical: html.includes('rel="canonical"'),
      has_og_tags: html.includes('og:title'),
      has_twitter_card: html.includes('twitter:card'),
      has_schema: html.includes('schema.org'),
      word_count: html.replace(/<[^>]+>/g, ' ').split(/\s+/).length,
      suggestions: []
    };

    // Generate improvement suggestions
    if (analysis.title_length > 60) analysis.suggestions.push("Title too long (>60 chars)");
    if (analysis.title_length < 30) analysis.suggestions.push("Title too short (<30 chars)");
    if (analysis.h1_count === 0) analysis.suggestions.push("Missing H1 tag");
    if (analysis.h1_count > 1) analysis.suggestions.push("Multiple H1 tags (use only one)");
    if (analysis.images_without_alt > 0) analysis.suggestions.push(`${analysis.images_without_alt} images missing alt text`);
    if (!analysis.has_canonical) analysis.suggestions.push("Missing canonical URL");
    if (!analysis.has_og_tags) analysis.suggestions.push("Missing Open Graph tags");
    if (!analysis.has_schema) analysis.suggestions.push("Missing Schema.org markup");

    return {
      content: [{
        type: "text",
        text: JSON.stringify(analysis, null, 2)
      }]
    };
  }
);

Example 4: Email Campaign Server

// mcp-email-campaign - Manage email campaigns via AI
server.tool(
  "create_email_draft",
  "Create an email campaign draft with subject, body, and audience segment",
  {
    subject: z.string().describe("Email subject line"),
    body_html: z.string().describe("Email body in HTML"),
    segment: z.string().optional()
      .describe("Audience segment ID to target"),
    schedule: z.string().optional()
      .describe("ISO date to schedule send, or omit for draft")
  },
  async ({ subject, body_html, segment, schedule }) => {
    const campaign = await emailService.createCampaign({
      subject,
      html: body_html,
      segment_id: segment || "all",
      status: schedule ? "scheduled" : "draft",
      send_at: schedule || null
    });

    return {
      content: [{
        type: "text",
        text: `Campaign created: ${campaign.id}\n` +
              `Status: ${campaign.status}\n` +
              `Audience: ${campaign.recipient_count} subscribers`
      }]
    };
  }
);

Example 5: Deployment Pipeline Server

// mcp-deploy - Safe deployment operations
server.tool(
  "deploy_preview",
  "Create a preview deployment for testing before going to production",
  {
    repo: z.string().describe("GitHub repository (owner/repo)"),
    branch: z.string().default("main").describe("Branch to deploy")
  },
  async ({ repo, branch }) => {
    // Trigger a preview deployment via GitHub API
    const response = await fetch(
      `https://api.github.com/repos/${repo}/deployments`,
      {
        method: "POST",
        headers: {
          "Authorization": `Bearer ${process.env.GITHUB_TOKEN}`,
          "Accept": "application/vnd.github+json"
        },
        body: JSON.stringify({
          ref: branch,
          environment: "preview",
          auto_merge: false
        })
      }
    );

    const deployment = await response.json();
    return {
      content: [{
        type: "text",
        text: `Preview deployment created.\n` +
              `ID: ${deployment.id}\n` +
              `URL: Will be available in ~60 seconds\n` +
              `Status: ${deployment.statuses_url}`
      }]
    };
  }
);
Pattern Recognition: Every successful MCP server follows the same pattern: identify something the AI cannot do on its own, build a secure bridge to that capability, document it clearly, and test it thoroughly. The specific domain (payments, SEO, email, deployment) does not matter. The pattern is universal.

10 The Future of AI Tool Ecosystems

MCP is the beginning, not the end. The protocol is the foundation for an entirely new ecosystem of AI tool integrations that will reshape how software is built, deployed, and monetized. This chapter maps the trajectory of AI tool ecosystems from where we are today to where we will be in three years, and positions you to profit from every phase of the evolution.

Phase 1: The Platform Phase (Now - Late 2026)

We are currently in the platform phase. The major AI companies are building and standardizing the infrastructure. MCP adoption is accelerating. Claude, Cursor, Windsurf, Zed, and dozens of smaller tools support the protocol. The developer community is beginning to build servers, but the supply is still far below demand. This is the highest-opportunity phase for individual developers.

# Current State of the MCP Ecosystem (Early 2026)
# ==================================================

# Platforms supporting MCP:
# - Claude Desktop (Anthropic)
# - Claude Code (Anthropic)
# - Cursor (Anysphere)
# - Windsurf (Codeium)
# - Cline (VS Code extension)
# - Zed (editor)
# - Continue (VS Code extension)
# - Sourcegraph Cody
# - Amazon Q Developer (AWS)

# Community servers on npm: ~2,000
# Active MCP developers: ~10,000
# Companies using MCP internally: ~500

# Opportunity: massive demand, limited supply
# Action: Build and publish servers NOW

Phase 2: The Marketplace Phase (Late 2026 - 2027)

Dedicated MCP marketplaces will emerge, similar to browser extension stores or WordPress plugin directories. These marketplaces will feature ratings, reviews, one-click installation, and monetization infrastructure. Server developers will list premium servers and earn revenue through marketplace transactions.

# Predicted MCP Marketplace Features (2027)
# =============================================

# Discovery:
# - Search by category, rating, popularity
# - AI-recommended servers based on user workflow
# - "Install with one click" for any MCP client

# Quality:
# - Verified publisher badges
# - Automated security scanning
# - User reviews and ratings
# - Usage statistics

# Monetization:
# - Free, paid, and subscription models
# - In-server purchases (premium features)
# - Revenue sharing (70/30 developer/marketplace)
# - Enterprise licensing

# What this means for you:
# - Build your reputation NOW (before marketplaces launch)
# - Be first-to-market in valuable niches
# - Established servers will rank higher than new entrants
# - Portfolio size matters: 10+ servers = authority status

Phase 3: The Composition Phase (2027-2028)

MCP servers will compose together automatically. Instead of users manually connecting five separate servers, an AI orchestrator will discover, install, and chain servers together to accomplish complex tasks. Ask the AI to "analyze my competitor's pricing strategy," and it will automatically compose a web scraper, data analyzer, and report generator into a single workflow.

Phase 4: The Agent Phase (2028+)

AI agents will build, test, publish, and maintain MCP servers autonomously. An agent could identify a gap in the ecosystem, build a server to fill it, test it against quality standards, publish it to a marketplace, and iterate based on user feedback. The role of human developers shifts from building servers to designing ecosystems and setting quality standards.

# The Agent-Built MCP Ecosystem (Conceptual, 2028)
# ====================================================

# Human provides:
# - "I need an MCP server that connects to Salesforce"

# AI Agent:
# 1. Researches the Salesforce API
# 2. Designs tool schemas for common operations
# 3. Generates server code with full test coverage
# 4. Tests against a Salesforce sandbox
# 5. Generates documentation with examples
# 6. Publishes to npm and MCP marketplaces
# 7. Monitors user feedback and iterates

# Human role: Review, approve, and strategic direction
# Agent role: All implementation and maintenance

Your 12-Month MCP Roadmap

  1. Month 1-2: Learn and build. Complete this book. Build 3-5 MCP servers covering different domains. Publish them to npm. Get comfortable with the protocol.
  2. Month 3-4: Establish presence. List your servers on all directories. Write blog posts and tutorials. Build a reputation as an MCP developer. Land your first paid customer (Gumroad or custom development).
  3. Month 5-6: Specialize. Pick 1-2 niches (e.g., database tools, DevOps integrations) and build the best servers in those categories. Premium features, excellent documentation, responsive support.
  4. Month 7-9: Scale. Automate your publishing pipeline. Build template-based server generation. Create a portfolio of 10+ servers. Launch a premium bundle.
  5. Month 10-12: Monetize aggressively. Multiple revenue streams: Gumroad products, custom development, consulting, premium subscriptions. Target $2,000-10,000/month from MCP-related income.

The First-Mover Advantage

The developers who build MCP servers in 2026 will own the ecosystem in 2028. Here is why:

The Bottom Line: MCP is the most significant new standard in the AI developer ecosystem since API REST conventions became mainstream. It creates an entirely new category of software products that did not exist two years ago. The window of opportunity is wide open today and will narrow rapidly as adoption accelerates. Build your first MCP server this week. Publish it this month. By this time next year, you will be glad you started early.
"The best MCP servers are not the most complex. They are the most useful. A simple server that solves a genuine problem will always outperform a complex server that solves a theoretical one. Build what developers actually need, and they will pay for it."

Get the Complete SpunkArt Ebook Library

All ebooks covering MCP servers, vibe coding, AI agents, automation, passive income, and more. Save 60%+ with the bundle.

Get the Full Ebook Bundle →

Subscribe for Updates

New tools, ebooks, and behind-the-scenes content. No spam.

SPUNK.CODES