A browser-first client for browsing, configuring, and running executables from pluggable sources. OpenAPI is the built-in source adapter. Connected-source metadata and keys live in the browser; the server provides suggested sources, fetches source documents, and runs proxied invocations.
This repository is a pnpm/Turborepo workspace:
packages/appcontains the reusable Vite client application and TanStack Router. ItsmountAppexport accepts an API base URL.examples/holds four standalone TanStack Start shells that mount the client application:cloudflare,vercel,node, anddocker. See examples/README.md.packages/apiis a mountable Hono API with OpenAPI docs and Hono RPC. TanStack Start forwards/api/*into it.packages/clibuilds thehookfishnpm package and bundlesexamples/nodeas its web app.
pnpm install
pnpm devOpen http://localhost:3000. The launcher groups suggested sources into one pane per
category. Paste any other URL in the bar and press Enter; Hookfish probes the URL to
decide whether it is an MCP server or an OpenAPI document. Type a name instead of a URL to
filter the suggestions, or search the web for a remote MCP server (needs EXA_API_KEY).
@hookfish/app and @hookfish/api build as independently consumable packages. A deployment
repository can mount the client without importing this workspace's source:
import { mountApp } from '@hookfish/app'
import '@hookfish/app/styles.css'
mountApp(document.getElementById('app')!, {
apiBaseUrl: '/api',
})Mount @hookfish/api at the matching path in the deployment platform's request handler:
import { mountApi } from '@hookfish/api/app'
export default mountApi('/api', {
exaApiKey: process.env.EXA_API_KEY,
})A deployment owns only its HTML/document shell, static asset delivery, and platform
adapter. Neither package requires a platform runtime, and the four shells in examples/
are working proofs of that — CI builds all of them on every pull request, so a change that
breaks one runtime fails there rather than at deploy time.
The frontend consumes the protocol-neutral ExecutableSource and Executable types in
packages/app/src/lib/client-types.ts. Register source discovery/loading in
packages/app/src/lib/source-adapters.ts. The launcher infers whether a pasted URL is MCP or
OpenAPI. A source parser supplies executable names, badges, accent colors, JSON Schema
inputs, targets, credentials, and UI labels. Homepage suggestions come from
GET /api/registry/feed.
Register execution behavior with registerExecutableAdapter() in
packages/app/src/lib/executable-adapters.ts. An adapter builds a serializable invocation,
previews it, executes it, and can optionally export a code snippet. For example, an MCP
adapter can map tools/resources/prompts to executables, use their names and input schemas
directly, execute JSON-RPC through the MCP client and Hono RPC API, and export client/call setup code.
The list, form, keyboard navigation, theming, and result viewer do not need protocol-specific
changes.
Pick an MCP server from the launcher or enter a Streamable HTTP endpoint. Hookfish uses the official MCP TypeScript client with automatic protocol negotiation:
- MCP
2026-07-28discovery, request metadata, MRTR, pagination, and subscriptions - legacy Streamable HTTP
2025-03-26through2025-11-25, including initialization, browser-held session IDs, GET event streams, and session termination - OAuth 2.1 authorization-code flows with PKCE, dynamic client registration, and Client ID Metadata Documents; registrations and tokens stay in browser storage
- optional custom request headers (for example
AuthorizationorX-API-Key), entered as name and value rows under Headers next to the launcher's Connect button (pasting.env, JSON, orName: valuetext fills the rows), kept in browser storage, and sent only to the MCP server's origin; a customAuthorizationreplaces the OAuth token. OpenAPI sources send them on every request unless an operation parameter or auth scheme sets the same header - tools, resources, resource templates, and prompts rendered through the shared executable UI
- request/response and notification traces plus capability metadata
- sampling, elicitation, and roots requests with editable manual JSON responses
- MCP Apps (
io.modelcontextprotocol/ui): a tool that declares aui://resource renders its view in a sandboxed iframe after a call, with tool calls, resource reads, links, and messages from the view relayed through the host and recorded in the trace
The /api/mcp-proxy route streams requests and responses without retaining state. MCP
connections, OAuth credentials, legacy session identifiers, and cached listings remain in
the browser. The UI talks to the App component's apiBaseUrl through Hono RPC; the web
shell mounts it at /api. Auto-generated OpenAPI is served at /api/openapi.json.
Deprecated pre-Streamable-HTTP HTTP+SSE and stdio transports are intentionally not supported.
GET /api/registry/feed returns the suggested sources in packages/api/src/registry.ts,
keyed by category; the homepage renders each category as a pane in the listed order. Edit
that file to change the suggestions.
GET /api/registry/search?q= asks Exa for remote MCP servers matching the
query, using structured output: name, MCP URL, homepage, description, and how it
authenticates (none, oauth, or headers with the header names it needs). It needs an
EXA_API_KEY passed to mountApi as exaApiKey and answers 503 without one. The
homepage only calls it when you ask it to search the web. Picking a search result fills the
launcher with its URL and a blank row for each required header, for you to complete and
confirm with Connect.
Build every workspace package:
pnpm buildThe resulting CLI package runs the production app with Node—no Wrangler or Cloudflare runtime is required:
npm install --global hookfish
hookfish up --port 3000hookfish with no command prints help. pnpm cli builds the workspace and runs that same bare command:
pnpm cli
pnpm cli up --host 127.0.0.1 --port 4000
pnpm cli updateIf a newer version is on npm, commands print a warning to run hookfish update.
name and version in packages/cli/package.json are the npm identity. pnpm pack
and pnpm publish run prepack, which builds the Node example into web/ so the
tarball is self-contained:
pnpm --filter hookfish pack
pnpm --filter hookfish publish --access publicMIT. See LICENSE.