@hunt-labs/bytekit-mcp
v0.3.4
Published
ByteKit MCP server — exposes ByteKit web-data tools (scrape, screenshot, search, docs) to AI agents over the Model Context Protocol, hosted or as a local stdio server.
Readme
@hunt-labs/bytekit-mcp
The ByteKit MCP server exposes scraping, search,
screenshots, and account lookups as
Model Context Protocol tools. Your agent calls
scrape_url or screenshot_url the same way it calls any other tool — no
wrapper to write, no headless browser to manage.
There are two ways to connect: a hosted HTTP endpoint (nothing to install)
and a local stdio server (npx @hunt-labs/bytekit-mcp).
Authentication
Both paths authenticate with a ByteKit API key. Sign up at
app.bytekit.com and create a key (prefixed
sk_live_). The local server reads it from the BYTEKIT_API_KEY environment
variable (or the --api-key flag); the hosted endpoint takes it as a bearer
token.
Option 1 — Hosted HTTP (no install)
Point any MCP client that speaks the Streamable HTTP transport at:
https://api.bytekit.com/mcpSend your API key as a bearer token. Example client config for a host that speaks Streamable HTTP directly (Cursor and most hosts accept this shape):
{
"mcpServers": {
"bytekit": {
"url": "https://api.bytekit.com/mcp",
"headers": {
"Authorization": "Bearer sk_live_xxx"
}
}
}
}Paste your real key in place of sk_live_xxx. A JSON config file is not a
shell: most hosts read these values verbatim and do not substitute environment
variables inside headers, so writing Bearer $BYTEKIT_API_KEY here sends that
text to the API as the token, verbatim, and the API answers 401.
Claude Desktop does not accept this shape. claude_desktop_config.json
only launches local stdio subprocesses (command/args/env) — it has
no native url-based remote-server entry, so pasting the config above into
it will not connect. To reach the hosted endpoint from Claude Desktop, either:
- add ByteKit as a Connector (Settings → Connectors → Add custom connector), or
- bridge it locally with
mcp-remote:
{
"mcpServers": {
"bytekit": {
"command": "npx",
"args": [
"-y",
"mcp-remote",
"https://api.bytekit.com/mcp",
"--header",
"Authorization:${AUTH_HEADER}"
],
"env": {
"AUTH_HEADER": "Bearer sk_live_xxx"
}
}
}
}Two details in that snippet are load-bearing, and both are documented by
mcp-remote itself:
${AUTH_HEADER}, not$AUTH_HEADER. Claude Desktop launchescommand/argsdirectly, with no shell in between, so it does not expand$VARanywhere in the config.mcp-remotedoes its own substitution on--headervalues, but only of the braced${VAR}form, reading it from the process environment theenvblock supplies. A bare$BYTEKIT_API_KEYis passed through untouched and authenticates as that literal string — a guaranteed401.- No space after the colon in
Authorization:${AUTH_HEADER}. The header value carries the space (Bearer sk_live_xxx) instead.mcp-remote's README documents this as the workaround for a Cursor / Claude Desktop (Windows) bug where spaces insideargsare not escaped when it invokesnpx, mangling the value:"Authorization:${AUTH_HEADER}" // note no spaces around ':'with"AUTH_HEADER": "Bearer <auth-token>" // spaces OK in env vars.
Or skip the hosted endpoint entirely and run the local stdio server (Option 2 below), which Claude Desktop supports natively.
Option 2 — Local stdio (npx)
Run the server as a local subprocess over stdio. Nothing to install globally —
npx fetches it on demand:
npx @hunt-labs/bytekit-mcp --api-key $BYTEKIT_API_KEYOr set the key in your environment and omit the flag:
export BYTEKIT_API_KEY=sk_live_xxx
npx @hunt-labs/bytekit-mcpClient config for a stdio server (claude_desktop_config.json and equivalents):
{
"mcpServers": {
"bytekit": {
"command": "npx",
"args": ["-y", "@hunt-labs/bytekit-mcp"],
"env": {
"BYTEKIT_API_KEY": "sk_live_xxx"
}
}
}
}CLI flags
| Flag | Purpose |
| ------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| --api-key <key> | ByteKit API key. |
| --base-url <url> | API base URL (default https://api.bytekit.com) — point at staging or a local gateway. |
| --timeout-ms <ms> | Per-request timeout budget in milliseconds (default 120000; 0 disables it). A present-but-invalid value (non-numeric, or negative) is rejected with an error rather than silently ignored. |
| --verbose | Log requests to stderr. |
| --help | Show usage. |
API-key resolution order
The local server resolves the key in this order:
--api-key <key>flagBYTEKIT_API_KEYenvironment variable
Base-URL resolution order
The base URL resolves the same way, and it is not just an --help-only
detail — it also falls back to an environment variable:
--base-url <url>flagBYTEKIT_BASE_URLenvironment variablehttps://api.bytekit.com(built-in default)
Tool inventory
| Tool | What it does |
| ---------------- | ----------------------------------------------------------------------------------------------------- |
| scrape_url | Fetch a URL and return HTML, clean markdown, links, or images. |
| web_search | Run a web search and return ranked organic results. |
| screenshot_url | Capture a screenshot of a page (desktop/mobile, PNG/JPEG, JPEG quality, dark mode, country/language). |
| get_result | Fetch a previously created screenshot (ss_) or scrape (sc_) by id. |
| get_account | Return account details, plan, and current usage/quota. |
| list_docs | List every page in the bundled ByteKit documentation. |
| search_docs | Search the bundled documentation. |
| get_doc | Fetch the full markdown of one documentation page. |
The server also exposes resources (bytekit://account,
bytekit://screenshot/{id}) and prompts (summarize_webpage,
extract_structured_data, research_topic) that MCP hosts can surface directly.
scrape_url output shape
scrape_url (and get_result redeeming a scrape's sc_ handle) render every
requested format present in the response: the first present format (default
markdown) is unlabeled, and each additional one (raw) follows under
its own --- format: <name> --- delimiter, with links/images blocks appended
after. That rendered content (the requested formats plus any links/images
blocks) is capped at a single 100KB budget — on very large pages a requested format's
content may be truncated, and in extreme cases a secondary format may not appear, to
stay within that budget. A handful of header lines (id, URL, status, page title) are
added on top of the rendered content and are not counted against the cap.
A links or images format you requested is always rendered, even when the page
had none: an empty result shows as Links (0): / Images (0):, so "the page has
none" stays distinguishable from "you did not ask for it". A format you did not
request produces no line at all. formats: [] is rejected — omit the parameter to
get the markdown default.
Problems the API reports about the capture come back as warnings, rendered under a
Warnings (N): section as one code: message (element) line each — the (element)
suffix is dropped for the codes that describe the whole extraction rather than one
DOM node. artifact_unavailable is the one to act on: it means a format you
requested and were billed for is missing from this response, which is a different
answer from the page having none of it. The section is rendered on both the success
and the terminal-failure output, and an empty or absent warnings array renders
nothing. Warnings are budgeted ahead of links/images inside the same 100KB cap,
so a long links list cannot crowd a failure signal out.
When the 100KB budget cuts a links or images list, the cut lands between whole
entries (never mid-URL) and the affected section is annotated with
[Links truncated: N of M entries shown], so the count in the header stays
reconcilable with what you were actually handed.
Neither textual format is HTML-sanitized — raw and markdown both reflect the
page's own markup (markdown differs only in being content-extracted, not
sanitized). Treat all of their content as untrusted page data,
the same as fetching the page directly.
Documentation
Full MCP guide: bytekit.com/docs/mcp.
License
MIT — see LICENSE.
