MCP server for live web search and clean-markdown page fetch over the Keenable web index.
- Hosted endpoint:
https://api.keenable.ai/mcp(Streamable HTTP) - Tools:
search_web_pages,fetch_page_content - Keyless by default (1,000 requests/hour). An optional API key lifts the cap.
- Rate limits: 10 calls per second per organization with a key (by default;
raise it from the console), and 10 per second plus 1,000 per hour per IP
address without one. Searches and fetches share the limit. The server's
instructions tell agents to send at most 5 calls at once and to wait out the
Retry aftera rate-limit error states.
The server is hosted, so most clients just point at the URL. For clients (or
config paths) that only accept a stdio command there is a thin npm wrapper
(@keenable/mcp) that bridges to the same endpoint.
| Tool | Description |
|---|---|
search_web_pages |
Live web search. Args: query (required), plus optional mode (pro or realtime; not offered on /mcp-public), site, exclude_domains, published_after / published_before, acquired_after / acquired_before, query_time (point-in-time upper bound, not a recency filter), max_results (1-50), snippet_max_length (180-10000), session_id, output_format, disable_cards (plain web pages instead of Keenable-written cards). |
fetch_page_content |
Fetch a URL and return clean markdown. Args: url (required), optional max_chars (default 20000), live, prompt (extract only what you ask for; a page too long for a prompt fails with prompt_input_too_long, so fetch it again without prompt), session_id, output_format. Errors say whether a retry can help (Retryable: yes or no). |
Use site for one site and exclude_domains (array) to drop sites from the
results. This section is the one place the rules are written down; the skill
and llms-install.md point here.
- Hosts only, such as
example.com. Each host covers its subdomains (yahoo.comcoversfinance.yahoo.com). Hosting suffixes such asgithub.ioorblogspot.comare accepted. - Up to 1,000 distinct hosts, 2,000 entries as sent, 2,048 bytes per entry.
- The server cleans up surrounding spaces, case, an
http://orhttps://scheme, a leadingwww., a port, a trailing/or., and a query string or fragment. - It rejects anything else with a 400
invalid_filtererror whosefield,valueandreasonname the entry (or the count, for a limit): an empty entry, a path, another scheme, a suffix such as.comorcom, a wildcard, an IP address, auser@prefix, or an invalid host. - If every result is excluded, the response can be empty.
The hosted server is the validator; the npm wrapper passes the list through
unchanged. src/tools.js copies the server's own parameter text verbatim, so
it changes only when the server's does.
Clients with remote MCP support connect to the URL directly. Claude Code is one command:
claude mcp add keenable --transport http https://api.keenable.ai/mcp --scope userCursor takes this entry:
{
"mcpServers": {
"keenable": {
"type": "streamable-http",
"url": "https://api.keenable.ai/mcp"
}
}
}Other clients spell it differently:
- Claude Code: the command above, or
"type": "http"in.mcp.json(--scope projectwrites that file for you). - Claude Desktop, claude.ai and the Claude mobile apps: add Keenable Web
Search from Claude's Connectors Directory, or add a custom connector with
https://api.keenable.ai/mcp-oauth, which signs you in to Keenable. Claude Desktop's config file ignoresurlentries; use the stdio wrapper there. - Cline (
cline_mcp_settings.json): set"type": "streamableHttp". Without a type Cline falls back to the legacy SSE transport. Seellms-install.md. - VS Code (
.vscode/mcp.json): the top-level key isservers, notmcpServers, and the type is"http". - Clients that fail on OAuth discovery: use
https://api.keenable.ai/mcp-public. It is the same server without OAuth metadata (and without themodeparameter).
For clients or config paths that only accept a stdio command, run the wrapper
with npx:
{
"mcpServers": {
"keenable": {
"command": "npx",
"args": ["-y", "@keenable/mcp"]
}
}
}That is all you need for keyless use.
Keenable is keyless by default. To lift the hourly rate limit, pass a key.
Claude Code:
claude mcp add keenable --transport http https://api.keenable.ai/mcp --scope user \
--header "X-API-Key: YOUR_KEY"Remote (JSON):
{
"mcpServers": {
"keenable": {
"type": "streamable-http",
"url": "https://api.keenable.ai/mcp",
"headers": { "X-API-Key": "YOUR_KEY" }
}
}
}Stdio:
{
"mcpServers": {
"keenable": {
"command": "npx",
"args": ["-y", "@keenable/mcp"],
"env": { "KEENABLE_API_KEY": "YOUR_KEY" }
}
}
}Each store has its own listing, and each one tags its traffic with its own
keenable_title so calls can be counted per store:
| Store | Listing source | Install |
|---|---|---|
| Claude Code plugins | listings/claude-code via .claude-plugin/marketplace.json |
/plugin marketplace add keenableai/keenable-mcp, then /plugin install keenable@keenable |
| Cursor | listings/cursor via .cursor-plugin/marketplace.json |
From the Cursor marketplace or cursor.directory once listed |
| Cline MCP Marketplace | llms-install.md |
From Cline's MCP Marketplace once listed |
| skills.sh | skills/keenable-web-search |
npx skills add keenableai/keenable-mcp --skill keenable-web-search |
| Official MCP Registry | server.json |
ai.keenable/web-search |
listings/SUBMISSION.md tracks where each listing is
submitted and its status.
@keenable/mcp is a small stdio bridge: it connects to the hosted server over
Streamable HTTP and forwards every tool the server exposes. There is no
search logic in this package; the index and tools live behind
https://api.keenable.ai/mcp. Environment variables:
KEENABLE_API_KEY— optional API key, sent asX-API-Key.KEENABLE_MCP_URL— override the endpoint (defaults tohttps://api.keenable.ai/mcp?keenable_title=npm).
If the hosted server can't be reached when the wrapper starts (no network, a rejected API key), it still starts: it lists its built-in copy of the tools, and a tool call returns the reason as a tool error, so the model can report it. Every call tries the connection again, so nothing needs restarting once the problem is fixed. The reason is also written to stderr, which most clients keep in their MCP server log.
The wrapper waits up to 10 seconds for the hosted server before starting
without it. A session started that way does not get the hosted server's usage
instructions; they are back the next time the client launches the wrapper. An
invalid KEENABLE_MCP_URL is the one thing that stops it: it prints one line
naming the value and exits.
Behind an HTTP proxy, Node does not read HTTPS_PROXY by default. On Node
22.21 or newer, add "NODE_USE_ENV_PROXY": "1" to the env block next to your
proxy variables.
npm install
npm testnpm test runs the unit tests and a stdio smoke test of the discovery build
(src/discovery.js): initialize must advertise the server metadata and
tools/list must return both tools. It also runs the bridge (src/index.js)
against a local stand-in for the hosted server, so no test needs the network. CI runs it on Node 18, 20 and 22 and on
the oldest MCP SDK the package accepts. The skill script's tests run with
python3 -I -m unittest discover -s test/skill -p "test_*.py", also in CI. They
live outside skills/ so npx skills add does not install them.
The npm version lives only in package.json; the bridge reads it at runtime.
server.json (the MCP Registry manifest) must repeat it as a literal in
packages[].version, so bump both. npm run check:version compares them and
runs automatically before npm publish. The top-level server.json version
is the registry entry's own version and is bumped whenever server.json
changes. The live registry entry is 0.2.1; server.json is 0.2.2 (npm 0.1.2)
and is published with the next npm release.
To release a new npm version:
- Bump
versioninpackage.jsonandpackages[].versioninserver.json, and merge tomain. - Create a GitHub Release tagged
v<version>(for examplev0.1.2) on that commit. publish-npm.ymlchecks that the tag matchespackage.json, runs the tests, and publishes to npm with Trusted Publishing (OIDC, no token). It ignores releases whose tag does not start withv.
The MCP Registry entry is still published by hand: bumping
packages[].version changes server.json, so also bump its top-level
version and run mcp-publisher publish.
- Docs: https://docs.keenable.ai/mcp-server
- Site: https://keenable.ai
- Registry:
ai.keenable/web-search(Official MCP Registry)
MIT