Skip to content

add keenable as a built-in integration through its mcp server - #162

Open
ilya-bogin-keenable wants to merge 3 commits into
personalagentproxy:mainfrom
keenableai:feat/keenable-built-in
Open

ilya-bogin-keenable wants to merge 3 commits into
personalagentproxy:mainfrom
keenableai:feat/keenable-built-in

Conversation

@ilya-bogin-keenable

@ilya-bogin-keenable ilya-bogin-keenable commented Oct 29, 2026 •

Copy link
Copy Markdown

Adds Keenable as a built-in integration next to Exa and Parallel, built the same way: people sign in through Keenable's MCP server (https://api.keenable.ai/mcp-oauth, OAuth with PKCE and a client registered on the spot) or paste an API key, and agents get Search the web and Read web pages.

Merged with main after #167, so Keenable follows the catalog architecture: one module per integration, one connector per collection.

What changed

  • connections/keenable/: the server and its parsers (keenable-mcp.ts), its BuiltInMcp declaration (built-in.ts) and its module (integration.ts, builtInMcpIntegration with the web connectors), registered in connections/integrations.ts, with tests, mirroring parallel/. A key is checked by listing the tools with it, since Keenable turns a bad key down with a 401, and it is sent as the bearer token. Keenable says nothing of who signed in, so it is anonymous like Exa and Parallel.
  • records/web-connector.ts: keenableConnectors (searches and pages). KEENABLE_WEB asks search_web_pages and fetch_page_content for JSON and writes the results out as Exa writes its own ("Title / URL / Published / Highlights" blocks between --- lines), the page text from content. As with Exa, it asks for a little more of a page than is kept, so a page cut at 100,000 characters ends with the "Cut short" note, and an empty page fails.
  • Page failures, as pass on why a provider failed, in its words or with what to do next, and say when a web page was cut #165 does for Exa and Parallel: a page Keenable found nothing at is not_found and a site that rate-limited its fetcher is out of reach (429), both through pageError; a page it couldn't reach is provider_unreachable in Keenable's words; anything else, such as a site that denied access, stays a validation_error in Keenable's words.
  • Catalog entry through webIntegration, the id in IntegrationId, and the logo at web/public/integrations/keenable.svg.
  • Docs: connections/keenable.mdx (same shape as parallel.mdx, including pass on why a provider failed, in its words or with what to do next, and say when a web page was cut #165's note on cut and unreadable pages), nav, the index and the lists in custom-mcp.mdx; the comment lists of key-taking integrations in the web app; page-failure.ts's comment.
  • Takeover: custom connections to api.keenable.ai move over as the other built-in servers' do; one made with a token keeps it as its key, and one reached with nothing stays custom. Since instances have already run take-over-custom-connections, a second data migration, take-over-custom-keenable-connections, runs takeOverCustomConnections once more, as data-migrations/run.ts asks of an integration built in later. Drop it if you'd rather not.

What is sent to Keenable: the query or the page address, with the connection's OAuth token or API key as the bearer token. The server address carries ?keenable_title=personal-agent-proxy so Keenable can tell which app the requests come from; the OAuth resource is the address without it, as with Cloudflare's query.

Tested

  • bun install --frozen-lockfile, bun run typecheck, bun run test (api 1163, web 249, integrations 49, contracts 17 and the rest pass), bun run lint (no new warnings) and bun run build.
  • Live, through the api's own callMcpTool and the new parsers against Keenable's keyless server: a search returned 10 results in 611 ms and a page read took 265 ms; a missing page comes back as not_found, a 429 and an unreachable site as provider_unreachable, a site that denied access as validation_error; /mcp-oauth with no credential and a made-up key both come back as credentials_rejected.
  • Before the merge, with bun run dev and the seeded workspace: Keenable shows in the catalog with its key and plus, a made-up key in the dialog shows "Keenable turned the key down", and Sign in with Keenable registers a client and lands on Keenable's consent page. I did not finish a sign-in or use a real key.

Disclosure: I work at Keenable, which runs this search API.

Screenshot: Settings > Add connection, Keenable next to Exa and Parallel

keenable-catalog

Keenable is signed in to through its MCP server (api.keenable.ai/mcp-oauth, OAuth with PKCE and a client registered on the spot) or connected with an API key, sent as the bearer token, as Exa and Parallel are. Agents get Search the web and Read web pages, the results written out as Exa writes its own. Custom connections to api.keenable.ai move over as the other built-in servers' do, and one reached with nothing stays custom.
@arnestrickmann

Copy link
Copy Markdown
Contributor

Thanks for opening the PR, @ilya-bogin-keenable! We'll take a look and get back to you.

A page Keenable found nothing at is not found, and one whose site turned its fetcher away for too many requests is out of reach for now, each with the address and what to try next, as Exa's and Parallel's are since personalagentproxy#165; one it couldn't reach is out of reach in its own words, and anything else stays in its words. As Exa is, it is asked for a little more of a page than is kept, so a page cut at 100,000 characters ends with the note saying so, and an empty page fails. Custom connections to api.keenable.ai move over on the next start through a data migration of their own, since instances have already run the one that moved the others'.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants