Skip to content

MCP Server

OCEANOFANYTHING edited this page Sep 3, 2026 · 1 revision

MCP Server

MailGrab ships a local MCP (Model Context Protocol) server, mailgrab_mcp_server.py, so AI coding agents — Claude Code, Claude Desktop, Cursor, GitHub Copilot in VS Code, OpenAI's Codex CLI, or any other MCP-speaking client — can harvest emails from a website as a tool call, instead of you (or the agent) shelling out to MailGrab.py by hand.

What it exposes

One tool: crawl_website.

crawl_website(
  url: str,                    # required
  depth: int = 30,
  same_domain: bool = True,
  max_hops: int | None = None,
  use_sitemap: bool = False,
  ignore_robots: bool = False,
  verify_mx: bool = False,
  delay: float = 0.0,
  timeout: float = 10.0,
  concurrency: int = 10,
  user_agent: str | None = None,
  proxy: str | None = None,
) -> {
  emails: [str],
  email_count: int,
  scrapped_urls: [str],
  url_count: int,
  sources: {email: [url]},
  social_links: [str],
}

Every parameter maps 1:1 to a CLI flag — see that page for what each one actually does. Two defaults are deliberately different from the CLI's own defaults, chosen for safer behavior when an agent (not a human who typed the command) decides to call this:

  • same_domain defaults to True here (the CLI defaults to False) — an agent-initiated crawl stays on the seed's own site unless explicitly told not to.
  • depth defaults to 30 — enough for a typical site's About/Team/Contact pages, without risking an unexpectedly long-running call from a vague request.

A crawl that completes but finds zero emails is a normal, successful result, not an error. A crawl that couldn't run at all (bad URL, invalid proxy, out-of-range depth, a timeout) raises a clear, specific error message instead.

How it actually works

Each tool call runs MailGrab.py as a real subprocess — python MailGrab.py --url ... --depth ... --quiet [your other options] — inside a fresh temporary directory, then reads that directory's _results.json back and returns it. It is not a thin in-process wrapper around the crawl engine. This is deliberate, for two reasons:

  1. MCP's stdio transport (what every client below uses for a local server) reserves this process's stdout exclusively for the JSON-RPC protocol stream. MailGrab.py's own crawl output goes to stdout too; running it in the same process would corrupt that stream. A subprocess has its own stdout, captured separately, so this can't happen.
  2. MailGrab.py writes to fixed filenames (_results.json and friends) in its working directory. Running each call in its own temp directory means two concurrent tool calls — a real scenario, nothing stops a client from calling the tool in parallel — can never clobber each other's output.

The subprocess timeout itself scales with your own depth/concurrency/timeout choices rather than a flat guess (see Limitations, on purpose), and a clean-but-empty result is retried once automatically — both fixes came out of an adversarial review during development, alongside the stdin-inheritance bug above. See Architecture and CLAUDE.md in the repo for the full story.

Setup

1. Install the dependency

The MCP server needs the mcp Python package, pinned to the 1.x line (the 2.x line renamed its main API and mailgrab_mcp_server.py is written against 1.x):

pip install mcp==1.29.1

This is already included if you ran MailGrab's own installer (Installation) or pip install -r requirements.txt.

2. Find the absolute path to the server

You'll point your AI tool's config at the full path to mailgrab_mcp_server.py inside your MailGrab checkout, e.g. /home/you/MailGrab/mailgrab_mcp_server.py or C:\Users\you\MailGrab\mailgrab_mcp_server.py.

3. Configure your client

Every client below launches the server the same way — as a local process via python <path> — just with different config file locations and key names.

Claude Code

Project-scoped config, checked into .mcp.json at your project root so the whole team gets it:

{
  "mcpServers": {
    "mailgrab": {
      "command": "python",
      "args": ["/absolute/path/to/mailgrab_mcp_server.py"]
    }
  }
}

Or register it without hand-editing JSON — note --scope project, without it claude mcp add defaults to a local, private-to-you scope (stored in ~/.claude.json, not shared via .mcp.json):

claude mcp add mailgrab --scope project -- python /absolute/path/to/mailgrab_mcp_server.py

(There's also --scope user for a private server available across all your projects.)

Claude Desktop

Edit claude_desktop_config.json (Settings → Developer → Edit Config, or find it directly at ~/Library/Application Support/Claude/claude_desktop_config.json on macOS / %APPDATA%\Claude\claude_desktop_config.json on Windows):

{
  "mcpServers": {
    "mailgrab": {
      "command": "python",
      "args": ["/absolute/path/to/mailgrab_mcp_server.py"]
    }
  }
}

Restart Claude Desktop after saving.

Cursor

Project-scoped .cursor/mcp.json (or the global ~/.cursor/mcp.json):

{
  "mcpServers": {
    "mailgrab": {
      "command": "python",
      "args": ["/absolute/path/to/mailgrab_mcp_server.py"]
    }
  }
}

Known quirk: Cursor doesn't always pick up config changes live. If mailgrab doesn't show up (or a config edit doesn't take effect), toggle it off/on under Settings → MCP, or run Developer: Reload Window, before assuming something's wrong with the server itself.

GitHub Copilot (VS Code)

.vscode/mcp.json in your workspace. The top-level key is "servers" (not "mcpServers") — this is the one client here that differs:

{
  "servers": {
    "mailgrab": {
      "type": "stdio",
      "command": "python",
      "args": ["/absolute/path/to/mailgrab_mcp_server.py"]
    }
  }
}

"type": "stdio" is technically optional (it's the default when you give a command), but spelling it out costs nothing and matches how non-stdio servers are declared. VS Code will prompt to start the server the first time; you can also run MCP: List Servers from the command palette to start/restart it manually.

GitHub Copilot CLI (standalone)

The standalone copilot terminal tool (separate from the VS Code extension above) uses its own config file, ~/.copilot/mcp-config.json, with its own schema — notably a tools allowlist with no equivalent in the VS Code version:

{
  "mcpServers": {
    "mailgrab": {
      "type": "local",
      "command": "python",
      "args": ["/absolute/path/to/mailgrab_mcp_server.py"],
      "tools": ["*"]
    }
  }
}

Codex CLI

TOML config at ~/.codex/config.toml:

[mcp_servers.mailgrab]
command = "python"
args = ["/absolute/path/to/mailgrab_mcp_server.py"]

4. Try it

Ask your agent something like "find contact emails for example.com" — it should recognize crawl_website as the right tool and call it.

Limitations, on purpose

  • No --append/--resume/--config. The tool is deliberately stateless and one-shot — every call gets a fresh temp directory, and there's no work_dir parameter (yet) for a caller that wants persistence across calls.
  • No streaming progress. A tool call is request/response; the agent waits for the full result rather than seeing live per-page progress. MCP supports progress notifications for long-running tools if this becomes worth the added complexity later.
  • Depth is still capped by the CLI's own validation (1–500). The subprocess itself is bounded by a timeout computed from your own depth, concurrency, and timeout arguments — roughly ceil(depth / concurrency) * (timeout + 5) + 30 seconds, clamped between 60s and 1800s — so a low-concurrency, high-per-request-timeout crawl (a deliberately polite, slow crawl) gets a proportionally longer budget instead of being killed early.

Clone this wiki locally