DEV Community

WEDGE Method Dev
WEDGE Method Dev

Posted on

How I Built 13 MCP Server Integrations for Claude Code (With Source Code)

I've been using Claude Code as my primary development environment for months. Along the way, I built 13 MCP (Model Context Protocol) server integrations that give Claude access to everything from browser automation to payment processing. Here's how MCP works under the hood, and the source code for three of my most useful integrations.

What Are MCP Servers?

MCP (Model Context Protocol) is an open standard that lets AI assistants call external tools through a unified interface. Instead of the AI guessing at API responses, MCP servers provide real tool execution with real results.

Each MCP server:

  • Exposes a set of tools (functions the AI can call)
  • Communicates over stdio or SSE transport
  • Returns structured results the AI can reason about

My MCP Architecture

{
  "mcpServers": {
    "brave-search": {
      "command": "npx",
      "args": ["-y", "@anthropic/mcp-brave-search"],
      "env": { "BRAVE_API_KEY": "your-key-here" }
    },
    "stripe": {
      "command": "npx",
      "args": ["-y", "@anthropic/mcp-stripe"],
      "env": { "STRIPE_SECRET_KEY": "sk_live_..." }
    },
    "playwright": {
      "command": "npx",
      "args": ["-y", "@anthropic/mcp-playwright"]
    },
    "github": {
      "command": "npx",
      "args": ["-y", "@anthropic/mcp-github"],
      "env": { "GITHUB_PERSONAL_ACCESS_TOKEN": "ghp_..." }
    },
    "filesystem": {
      "command": "npx",
      "args": ["-y", "@anthropic/mcp-filesystem", "/Users/me/projects"]
    },
    "memory": {
      "command": "npx",
      "args": ["-y", "@anthropic/mcp-memory"]
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

This lives in ~/.claude.json (user scope) or .claude/settings.json (project scope).

Integration 1: Brave Search — Verified Web Research

The Brave Search MCP server lets Claude search the web and get real results instead of relying on training data.

// brave-search-mcp/src/index.ts
import { Server } from "@anthropic/mcp-server";
import { StdioServerTransport } from "@anthropic/mcp-server/stdio";

interface SearchResult {
  title: string;
  url: string;
  description: string;
  age?: string;
}

interface BraveSearchResponse {
  web: { results: SearchResult[] };
}

const server = new Server({
  name: "brave-search",
  version: "1.0.0",
});

server.tool(
  "brave_web_search",
  "Search the web using Brave Search API",
  {
    query: { type: "string", description: "Search query" },
    count: { type: "number", description: "Number of results (max 20)", default: 10 },
  },
  async ({ query, count = 10 }: { query: string; count?: number }) => {
    const apiKey = process.env.BRAVE_API_KEY;
    if (!apiKey) throw new Error("BRAVE_API_KEY not set");

    const url = new URL("https://api.search.brave.com/res/v1/web/search");
    url.searchParams.set("q", query);
    url.searchParams.set("count", String(Math.min(count, 20)));

    const response = await fetch(url.toString(), {
      headers: {
        Accept: "application/json",
        "Accept-Encoding": "gzip",
        "X-Subscription-Token": apiKey,
      },
    });

    if (!response.ok) {
      throw new Error(`Brave API error: ${response.status}`);
    }

    const data: BraveSearchResponse = await response.json();
    const results = data.web.results.map((r: SearchResult) => ({
      title: r.title,
      url: r.url,
      description: r.description,
      age: r.age,
    }));

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

const transport = new StdioServerTransport();
server.connect(transport);
Enter fullscreen mode Exit fullscreen mode

Why this matters: Before MCP, Claude would say "I don't have internet access." Now it performs real searches and cross-references multiple sources.

Integration 2: Stripe — Payment Operations from the Terminal

This integration lets Claude check balances, list customers, create payment links, and manage subscriptions without ever opening the Stripe dashboard.

// stripe-mcp/src/tools/payment-link.ts
import Stripe from "stripe";

interface PaymentLinkParams {
  product_name: string;
  price_cents: number;
  currency?: string;
  quantity_adjustable?: boolean;
}

interface PaymentLinkResult {
  url: string;
  id: string;
  active: boolean;
}

export async function createPaymentLink(
  stripe: Stripe,
  params: PaymentLinkParams
): Promise<PaymentLinkResult> {
  // Create the product
  const product = await stripe.products.create({
    name: params.product_name,
  });

  // Create the price
  const price = await stripe.prices.create({
    product: product.id,
    unit_amount: params.price_cents,
    currency: params.currency || "usd",
  });

  // Create the payment link
  const paymentLink = await stripe.paymentLinks.create({
    line_items: [
      {
        price: price.id,
        quantity: 1,
        adjustable_quantity: params.quantity_adjustable
          ? { enabled: true, minimum: 1, maximum: 10 }
          : undefined,
      },
    ],
    after_completion: {
      type: "redirect",
      redirect: { url: "https://thewedgemethodai.com/thank-you" },
    },
  });

  return {
    url: paymentLink.url,
    id: paymentLink.id,
    active: paymentLink.active,
  };
}

// Usage from the MCP tool handler:
// "Create a payment link for AI Consulting Session at $297"
// → Returns: https://buy.stripe.com/abc123
Enter fullscreen mode Exit fullscreen mode

Real usage: I tell Claude "create a payment link for a 1-hour AI consulting session at $350" and it creates the product, price, and link in one operation. No dashboard needed.

Integration 3: Playwright — Browser Automation

The Playwright MCP server gives Claude a real browser. It can navigate pages, fill forms, click buttons, take screenshots, and extract data.

// playwright-mcp/src/tools/navigate.ts
import { chromium, Browser, Page, BrowserContext } from "playwright";

interface BrowserState {
  browser: Browser | null;
  context: BrowserContext | null;
  page: Page | null;
}

const state: BrowserState = {
  browser: null,
  context: null,
  page: null,
};

export async function ensureBrowser(): Promise<Page> {
  if (!state.browser) {
    state.browser = await chromium.launch({
      headless: false, // Visible for debugging
    });
    state.context = await state.browser.newContext({
      viewport: { width: 1280, height: 720 },
      userAgent:
        "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) " +
        "AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
    });
    state.page = await state.context.newPage();
  }
  return state.page!;
}

interface SnapshotElement {
  role: string;
  name: string;
  ref: string;
  children?: SnapshotElement[];
}

export async function navigateAndSnapshot(
  url: string
): Promise<{ title: string; url: string; snapshot: SnapshotElement[] }> {
  const page = await ensureBrowser();
  await page.goto(url, { waitUntil: "networkidle" });

  // Get accessibility tree (what Claude actually "sees")
  const snapshot = await page.accessibility.snapshot();

  return {
    title: await page.title(),
    url: page.url(),
    snapshot: snapshot?.children || [],
  };
}

export async function fillAndSubmit(
  selector: string,
  value: string
): Promise<{ success: boolean; pageTitle: string }> {
  const page = await ensureBrowser();
  await page.fill(selector, value);
  await page.press(selector, "Enter");
  await page.waitForLoadState("networkidle");

  return {
    success: true,
    pageTitle: await page.title(),
  };
}
Enter fullscreen mode Exit fullscreen mode

Real usage: Claude can log into services, check dashboards, submit forms, and take screenshots — all from the terminal. I use this for monitoring Gumroad sales, checking deployment status, and automating repetitive web tasks.

How to Build Your Own MCP Server

The pattern is straightforward:

import { Server } from "@anthropic/mcp-server";
import { StdioServerTransport } from "@anthropic/mcp-server/stdio";

const server = new Server({ name: "my-tool", version: "1.0.0" });

// Register a tool
server.tool(
  "tool_name",           // Name Claude uses to call it
  "What this tool does", // Description for Claude
  {                      // Input schema (JSON Schema)
    param1: { type: "string", description: "..." },
  },
  async ({ param1 }) => {
    // Your logic here
    const result = await doSomething(param1);
    return {
      content: [{ type: "text", text: JSON.stringify(result) }],
    };
  }
);

// Connect via stdio
const transport = new StdioServerTransport();
server.connect(transport);
Enter fullscreen mode Exit fullscreen mode

Then register it in your Claude config:

{
  "mcpServers": {
    "my-tool": {
      "command": "node",
      "args": ["path/to/my-tool/dist/index.js"],
      "env": { "MY_API_KEY": "..." }
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Key Lessons from 13 Integrations

  1. Stdio transport is simpler than SSE for local tools. Use SSE only when you need remote server access.
  2. Return structured JSON, not prose. Claude reasons better about structured data.
  3. Validate inputs in the tool handler. Claude sometimes passes unexpected types.
  4. Keep tools focused. One tool per operation, not one tool that does everything.
  5. Error messages matter. Claude reads them to debug and retry, so be descriptive.

I compiled all 13 integrations — with 50+ complete code examples, configuration guides, and my full claude.json setup — in my Claude Code Mastery Guide. It covers everything from basic MCP setup to building custom tool chains.


Building your own MCP servers? I'd love to hear what tools you're connecting. Drop a comment.

Top comments (0)