Skip to content

Website MCP servers — generate, run, and keep them fresh

mcpmake can turn a website with no API spec into an MCP server in two ways. Both drive a real browser; the difference is what they capture.

Command Captures Generates Runtime
mcpmake from url the site's network/API traffic while it loads/you interact an HTTP-fetch MCP server (one tool per API call) fetch — no browser
mcpmake from website the site's DOM (forms, buttons, links) a Playwright MCP server with site-specific tools Playwright/Chromium

If the site is backed by a clean JSON API, prefer from url (or from har) — the output is a lightweight HTTP server. Use from website when the only way to drive the site is the page itself.

Requires npx playwright install chromium on first use of either command.


from url — record API traffic

Point mcpmake at a URL; it opens a browser, you click around, and on close it builds an MCP server from the captured API calls.

mcpmake from url https://app.example.com -o ./app-mcp

Headless / CI capture

When no human can drive a window (CI, containers), run headless and list the same-origin paths to auto-visit. mcpmake loads each, captures its traffic, and exits — no manual interaction:

mcpmake from url https://app.example.com -o ./app-mcp \
  --headless --navigate /dashboard,/orders,/settings
  • --navigate accepts absolute or relative URLs; cross-origin targets are skipped (logged as a warning).
  • If nothing is captured, mcpmake tells you to add --navigate paths that actually trigger API calls.

from url also supports -o/--output (required), -n/--name, --timeout, -t/--transport, --target (node/cloudflare), -i/--include, -e/--exclude, --improve-names, --interactive, -f/--force, and --dry-run — see the CLI reference for the full list.


from website — generate site-specific tools

mcpmake from website https://app.example.com -o ./site-mcp

Unlike generic browser MCP servers (Playwright MCP, Puppeteer MCP) that make the model rediscover the page on every call, this analyzes the DOM once and emits named tools — login(email, password), search(query) — plus lifecycle tools (start_browser, stop_browser, take_screenshot).

Useful flags:

Flag Effect
--depth, --max-pages crawl bounds (default depth 2, 20 pages)
--timeout <seconds> idle timeout during analysis (default 300)
--goal "..." LLM-driven navigation toward a goal instead of BFS (needs ANTHROPIC_API_KEY)
--hybrid HTTP fetch for API-backed forms, Playwright for HTML-only forms
--improve-names LLM-inferred semantic tool names (needs ANTHROPIC_API_KEY)
--headless run the analysis browser headless
--max-sessions <n> max concurrent browser sessions in the generated server (default 10)
-t, --transport stdio (default) or http

Plus the usual -o/--output (required), -n/--name, -f/--force, and --dry-run. The website pipeline is --target node only. See the CLI reference for the complete list.

Resilient selectors at runtime

Each generated tool stores a ranked selector set (data-testid > id > aria-label > name > role > CSS path > XPath), not just one selector. At runtime the tool tries the primary selector and then falls back through the rest, so a small DOM change doesn't immediately break the tool. This happens with no LLM and no network — pure Playwright in the generated server.

What gets written

The generated project includes two files that make rescans possible:

  • src/site-descriptor.json — the snapshot of pages/forms/selectors captured at generation time.
  • mcpmake.site.json — regeneration metadata (server name, transport, browser config, env vars) so a rescan can rebuild without re-specifying CLI flags.

Keeping a website server fresh — mcpmake rescan

Sites change. rescan is the website counterpart to mcpmake update: it re-crawls the live site, diffs it against the embedded snapshot, heals fragile selectors, and can regenerate the project in place.

# Report what changed and which selectors are fragile (no writes)
mcpmake rescan ./site-mcp

# Heal low-confidence selectors from the live accessibility tree (needs ANTHROPIC_API_KEY)
ANTHROPIC_API_KEY=... mcpmake rescan ./site-mcp

# Regenerate the project in place from the fresh, healed snapshot
mcpmake rescan ./site-mcp --write

# Machine-readable diff (CI)
mcpmake rescan ./site-mcp --format json

What it does

  1. Loads src/site-descriptor.json (old snapshot) and mcpmake.site.json (regeneration settings).
  2. Re-crawls the site. Override the target with --url; crawl bounds default from the snapshot (with headroom so newly added pages are still found) and can be overridden with --depth / --max-pages.
  3. Diffs old vs new and reports added / removed / modified pages, forms, fields, buttons, and links — plus any selectors that broke.
  4. Self-heals: for each fragile (low-confidence) selector it asks Claude for a better selector based on the live accessibility tree, and applies it only if it actually resolves on the page. Healing is skipped (with a warning) if ANTHROPIC_API_KEY is unset; the diff/report still runs.
  5. With --write, regenerates the project from the healed snapshot. Like update, this overwrites the generated files — commit first, then review the diff.

Options

mcpmake rescan <project> [options]

  --url <url>        Override the base URL to rescan (default: from the snapshot)
  --depth <n>        Crawl depth (default: from the snapshot)
  --max-pages <n>    Max pages to crawl (default: snapshot page count + headroom)
  --no-heal          Disable LLM selector healing
  --write            Regenerate the project in place from the new snapshot
  --no-headless      Run the crawl/heal browser with a visible window
  --format <fmt>     "text" (default) or "json"

In CI

Use the JSON output to gate on drift — e.g. fail a scheduled job when selectors break so you can rescan-and-commit deliberately:

mcpmake rescan ./site-mcp --format json > rescan.json
# inspect .summary.brokenSelectors / .summary.totalChanges in your pipeline

Security notes

  • Generated tool selectors, link hrefs, and field-name keys are derived from the (untrusted) crawled page; mcpmake escapes them when emitting code so a crafted page cannot inject code into the generated server.
  • LLM-healed selectors are validated and rejected if they contain characters that could break out of a generated string literal.
  • The generated HTTP transport enforces an exact-host ALLOWED_ORIGIN check (DNS-rebinding protection) in addition to the MCP_AUTH_TOKEN bearer check.