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 chromiumon 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
--navigateaccepts absolute or relative URLs; cross-origin targets are skipped (logged as a warning).- If nothing is captured, mcpmake tells you to add
--navigatepaths 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
- Loads
src/site-descriptor.json(old snapshot) andmcpmake.site.json(regeneration settings). - 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. - Diffs old vs new and reports added / removed / modified pages, forms, fields, buttons, and links — plus any selectors that broke.
- 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_KEYis unset; the diff/report still runs. - With
--write, regenerates the project from the healed snapshot. Likeupdate, 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_ORIGINcheck (DNS-rebinding protection) in addition to theMCP_AUTH_TOKENbearer check.