Skip to content

About

MCP server for live web search and clean-markdown page fetch over the Keenable web index (hosted; stdio bridge via @keenable/mcp).

Topics

Resources

Stars

7 stars

Watchers

0 watching

Forks

Repository files navigation

Keenable MCP

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 after a 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.

Tools

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).

exclude_domains

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.com covers finance.yahoo.com). Hosting suffixes such as github.io or blogspot.com are 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:// or https:// scheme, a leading www., a port, a trailing / or ., and a query string or fragment.
  • It rejects anything else with a 400 invalid_filter error whose field, value and reason name the entry (or the count, for a limit): an empty entry, a path, another scheme, a suffix such as .com or com, a wildcard, an IP address, a user@ 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.

Use it

Remote (recommended)

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 user

Cursor 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 project writes 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 ignores url entries; use the stdio wrapper there.
  • Cline (cline_mcp_settings.json): set "type": "streamableHttp". Without a type Cline falls back to the legacy SSE transport. See llms-install.md.
  • VS Code (.vscode/mcp.json): the top-level key is servers, not mcpServers, 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 the mode parameter).

Stdio (e.g. Claude Desktop's claude_desktop_config.json)

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.

API key (optional)

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" }
    }
  }
}

Agent stores

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.

How the wrapper works

@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 as X-API-Key.
  • KEENABLE_MCP_URL — override the endpoint (defaults to https://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.

Development

npm install
npm test

npm 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.

Releasing

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:

  1. Bump version in package.json and packages[].version in server.json, and merge to main.
  2. Create a GitHub Release tagged v<version> (for example v0.1.2) on that commit.
  3. publish-npm.yml checks that the tag matches package.json, runs the tests, and publishes to npm with Trusted Publishing (OIDC, no token). It ignores releases whose tag does not start with v.

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.

Links

License

MIT

About

MCP server for live web search and clean-markdown page fetch over the Keenable web index (hosted; stdio bridge via @keenable/mcp).

Topics

Resources

Stars

7 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages