Skip to content

Commit 6d6e629

Browse files
feat: item 8 (search-mcp cartridge) + ADR-0013 streamable HTTP transport (epic #87) (#99)
## Summary Two epic #87 Tier A items in one PR — **item 8 ships as code**, **item 14 ships as an RFC** (the impl is ~1-2 weeks of focused work per epic estimate; design-first per the Tier B pattern). ## Item 8 — \`search-mcp\` cartridge + \`boj_search\` bridge tool Multi-provider web search behind one cartridge: | Provider | Strength | Auth env | |---|---|---| | Tavily | LLM-optimised summaries; answer-mode; extract API | \`TAVILY_API_KEY\` | | Brave | Privacy-first index; no tracking | \`BRAVE_SEARCH_API_KEY\` | | Exa | Neural search over high-quality content; extract API | \`EXA_API_KEY\` | | Perplexity | Q&A-style with citations | \`PERPLEXITY_API_KEY\` | ### What's in the diff - \`cartridges/search-mcp/cartridge.json\` — manifest with 4 cartridge-level tools (\`search_authenticate\` / \`web\` / \`answer\` / \`extract\`) - \`cartridges/search-mcp/README.adoc\` — provider strengths matrix + bridge wiring note - \`mcp-bridge/lib/tools.js\` — single \`boj_search\` bridge tool dispatching via \`operation\` + \`provider\` args; AAA-tier description carries the provider matrix so the LLM picks correctly - \`mcp-bridge/main.js\` — dispatch case + hardeningGate requires \`operation\` + domain classification \`search\` - \`mcp-bridge/lib/offline-menu.js\` — \`search-mcp\` added to Teranga tier so it shows in \`boj_menu\` even offline ### Status Bridge surface captured — MCP clients discover and invoke today. Backend dispatch implementation in Elixir/Zig deferred (separate work). ### Tests - ✅ 15/15 existing tests pass - ✅ \`tools/list\` advertises \`boj_search\` with full 41+1 = 42 tools ## Item 14 — ADR-0013 Streamable HTTP transport RFC for adding MCP's HTTP+SSE transport alongside stdio. Same bridge binary; \`BOJ_TRANSPORT=stdio|http|both\` selects mode. **No existing stdio clients break.** ### Why this RFC matters JSR's runtime-compatibility question surfaced the gap: the user can't tick \"Browsers\" or \"Cloudflare Workers\" because the bridge legitimately doesn't run there. Item 14 is the right framing — *not* \"make stdio work in a browser\" (architecturally wrong) but \"add an HTTP transport for the contexts where stdio doesn't work.\" ### Key design points - **Single endpoint** \`POST /mcp\` + \`GET /mcp\` (SSE) per latest MCP spec - **Session state** via \`Mcp-Session-Id\` UUIDs (rate-limit counters, OTel trace context, in-flight async work) - **Four auth modes**: none (loopback-only), bearer, mTLS, OIDC - **Mounts on the same Cowboy listener** as webhooks (ADR-0004 honoured) - **Cartridge compat matrix**: ~60% work on Workers (HTTP-API-based: GitHub, GitLab, Cloudflare, ML, Search, Hugging Face); 40% don't (local-only: browser-mcp, container-mcp, local-coord-mcp, local sandbox). New \`boj_capabilities\` resource reports per-deployment availability so clients don't try locally-impossible operations. ### Three new deployment targets unlocked - Cloudflare Workers (with Durable Objects for session state) - Docker / Podman containers - Browser-based agent web apps (via \`fetch\` against a hosted BoJ) ### Implementation sketch 2 PRs over ~1-2 weeks: - PR 1: bridge HTTP transport + auth modes + session manager (~1 week) - PR 2: Workers deployment guide + Durable Objects shim + cold-start optimisation (~1 week) ### Federation alignment ADR-0010 (cross-machine federation) needs HTTPS transport anyway. ADR-0013's transport layer is reusable for federation. Webhook notifications (ADR-0011) fan out over this RFC's SSE stream. ## Sequencing Independent of all currently-open PRs. Item 8 lands as code; item 14 lands as RFC awaiting implementation in follow-up. After this lands, epic #87 Tier A remaining is **items 7 (vector DBs × 4) + 9 (multi-modal × 4)** — both multi-cartridge waves that warrant separate sessions, not a single rushed PR. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent d961a1a commit 6d6e629

6 files changed

Lines changed: 399 additions & 1 deletion

File tree

cartridges/search-mcp/README.adoc

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
// SPDX-License-Identifier: MPL-2.0
2+
= search-mcp — Multi-provider web search
3+
4+
Cartridge that exposes web search across four providers behind a single MCP tool surface:
5+
6+
|===
7+
| Provider | Strengths | Auth env var
8+
9+
| Tavily
10+
| LLM-optimised summaries; answer-mode; extract API
11+
| `TAVILY_API_KEY`
12+
13+
| Brave
14+
| Privacy-first index; broad freshness; no tracking
15+
| `BRAVE_SEARCH_API_KEY`
16+
17+
| Exa
18+
| Neural search over high-quality content (Twitter, Reddit, papers, blogs); extract API
19+
| `EXA_API_KEY`
20+
21+
| Perplexity
22+
| Q&A-style answers with citations; conversational search
23+
| `PERPLEXITY_API_KEY`
24+
|===
25+
26+
== Tools
27+
28+
* `search_authenticate` — store provider API key (per-provider; called once)
29+
* `search_web` — keyword/phrase search; returns ranked results
30+
* `search_answer` — single answer with citations (perplexity, tavily)
31+
* `search_extract` — structured content extraction from URLs (tavily, exa)
32+
33+
== Bridge tool
34+
35+
The MCP bridge exposes a single high-level `boj_search` tool that dispatches to `search-mcp` via `boj_cartridge_invoke`. See `boj_search` in `mcp-bridge/lib/tools.js`.
36+
37+
== Status
38+
39+
Cartridge surface declared (this manifest); backend implementation in the Elixir/Zig dispatch layer is tracked separately. Until backend lands, `search_*` operations route through the unified gateway and return `{error, hint}` for unimplemented providers.
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
{
2+
"$schema": "https://boj.dev/schemas/cartridge/v1.json",
3+
"spdx": "MPL-2.0",
4+
"copyright": "Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) <j.d.a.jewell@open.ac.uk>",
5+
"name": "search-mcp",
6+
"version": "0.1.0",
7+
"description": "Web search across multiple providers (Tavily, Brave, Exa, Perplexity) behind one cartridge.",
8+
"domain": "research",
9+
"tier": "Teranga",
10+
"protocols": [
11+
"MCP",
12+
"REST"
13+
],
14+
"auth": {
15+
"method": "api_key",
16+
"env_var": "SEARCH_API_KEY",
17+
"credential_source": "per-provider env var (TAVILY_API_KEY / BRAVE_SEARCH_API_KEY / EXA_API_KEY / PERPLEXITY_API_KEY); see operation 'authenticate'."
18+
},
19+
"api": {
20+
"base_url": "local://search-mcp",
21+
"content_type": "application/json"
22+
},
23+
"tools": [
24+
{
25+
"name": "search_authenticate",
26+
"description": "Store an API key for a search provider. Required before first use of that provider.",
27+
"inputSchema": {
28+
"type": "object",
29+
"properties": {
30+
"provider": {
31+
"type": "string",
32+
"enum": ["tavily", "brave", "exa", "perplexity"],
33+
"description": "Provider to authenticate."
34+
},
35+
"api_key": {
36+
"type": "string",
37+
"description": "API key from the provider's dashboard."
38+
}
39+
},
40+
"required": ["provider", "api_key"]
41+
}
42+
},
43+
{
44+
"name": "search_web",
45+
"description": "Run a web search query through the chosen provider. Read-only.",
46+
"inputSchema": {
47+
"type": "object",
48+
"properties": {
49+
"provider": {
50+
"type": "string",
51+
"enum": ["tavily", "brave", "exa", "perplexity"],
52+
"description": "Which provider to query. Each has different strengths: tavily = LLM-optimised summaries; brave = privacy-first index; exa = neural search over high-quality content; perplexity = Q&A-style answers with citations."
53+
},
54+
"query": {
55+
"type": "string",
56+
"description": "Search query string.",
57+
"minLength": 1
58+
},
59+
"max_results": {
60+
"type": "integer",
61+
"minimum": 1,
62+
"maximum": 50,
63+
"description": "Max results to return (default 10)."
64+
},
65+
"include_domains": {
66+
"type": "array",
67+
"items": { "type": "string" },
68+
"description": "Optional allow-list of domains."
69+
},
70+
"exclude_domains": {
71+
"type": "array",
72+
"items": { "type": "string" },
73+
"description": "Optional deny-list of domains."
74+
},
75+
"freshness": {
76+
"type": "string",
77+
"enum": ["day", "week", "month", "year", "any"],
78+
"description": "Recency filter (provider-best-effort; not all providers honour all values)."
79+
}
80+
},
81+
"required": ["provider", "query"]
82+
}
83+
},
84+
{
85+
"name": "search_answer",
86+
"description": "Get a single answer with citations for a question. Best for Q&A workflows. Read-only.",
87+
"inputSchema": {
88+
"type": "object",
89+
"properties": {
90+
"provider": {
91+
"type": "string",
92+
"enum": ["perplexity", "tavily"],
93+
"description": "Only providers that support answer-mode (perplexity, tavily)."
94+
},
95+
"question": {
96+
"type": "string",
97+
"description": "Natural-language question.",
98+
"minLength": 1
99+
},
100+
"model": {
101+
"type": "string",
102+
"description": "Provider-specific model identifier. Optional; defaults to provider's standard model."
103+
}
104+
},
105+
"required": ["provider", "question"]
106+
}
107+
},
108+
{
109+
"name": "search_extract",
110+
"description": "Extract structured content from one or more URLs (provider-side scraping + cleanup). Read-only.",
111+
"inputSchema": {
112+
"type": "object",
113+
"properties": {
114+
"provider": {
115+
"type": "string",
116+
"enum": ["tavily", "exa"],
117+
"description": "Only providers that support extraction (tavily, exa)."
118+
},
119+
"urls": {
120+
"type": "array",
121+
"items": { "type": "string", "format": "uri" },
122+
"minItems": 1,
123+
"maxItems": 20,
124+
"description": "URLs to extract content from."
125+
}
126+
},
127+
"required": ["provider", "urls"]
128+
}
129+
}
130+
]
131+
}
Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
<!-- SPDX-License-Identifier: MPL-2.0 -->
2+
<!-- Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) <j.d.a.jewell@open.ac.uk> -->
3+
4+
# 13. Streamable HTTP transport — Workers + remote browser-client deployment
5+
6+
Date: 2026-05-20
7+
8+
## Status
9+
10+
Proposed (RFC — implementation tracked in epic #87 item 14)
11+
12+
## Context
13+
14+
BoJ today speaks MCP over **stdio only**. The bridge process is launched as a child by an MCP client (Claude Code, Claude Desktop, Cursor, etc.), reads JSON-RPC from stdin, writes responses to stdout. This works perfectly for client-launched local deployments.
15+
16+
It does not work for:
17+
18+
- **Cloudflare Workers** — Workers have no stdio; they're request-response only with strict CPU/duration limits. The Workers MCP pattern (Cloudflare's own MCP servers + the Workers MCP guide) uses HTTP transport.
19+
- **Remote browser-based agents** — A web app that wants to use BoJ tools cannot spawn a subprocess; it has to call BoJ over HTTP.
20+
- **Federated multi-tenant deployments** — one BoJ instance serving multiple clients on different machines. Each client wants its own session over the network, not stdio.
21+
- **Long-lived sessions across stdio reconnects** — when an MCP client crashes and restarts, stdio state is lost. HTTP transport with session IDs lets the client resume.
22+
23+
The MCP specification added **Streamable HTTP transport** (also called HTTP+SSE) alongside stdio in 2024. The current bridge doesn't implement it. This is the gap.
24+
25+
JSR runtime-compatibility surfaced this directly: the user can't tick "Browsers" or "Cloudflare Workers" because the bridge legitimately doesn't run there. Item 14 is the right framing of that gap — not "make stdio work in a browser" (architecturally wrong) but "add an HTTP transport for the contexts where stdio doesn't work."
26+
27+
## Decision
28+
29+
Implement **Streamable HTTP transport** alongside stdio. Both transports coexist; the binary chooses at startup based on env. No existing stdio clients break.
30+
31+
### Transport mode selection
32+
33+
```
34+
BOJ_TRANSPORT=stdio # current behaviour, default
35+
BOJ_TRANSPORT=http # new — listen on BOJ_HTTP_PORT (default 7780)
36+
BOJ_TRANSPORT=both # listen on both simultaneously
37+
```
38+
39+
### HTTP endpoint shape
40+
41+
Two endpoints per the MCP spec:
42+
43+
```
44+
POST /mcp — submit a JSON-RPC request, get response
45+
GET /mcp — open SSE stream for server-initiated notifications
46+
```
47+
48+
Single endpoint design (per the latest spec revision); some earlier MCP HTTP implementations used `/mcp/sse` separately, but the spec consolidated.
49+
50+
Headers:
51+
```
52+
Mcp-Session-Id: <uuid> — required on every request after initialize
53+
Mcp-Protocol-Version: 2024-11-05
54+
```
55+
56+
The session ID is server-issued on `initialize`; client sends it on every subsequent request. Allows the server to maintain per-session state (rate-limit state, OTel trace context, in-flight quarantine entries) across requests in a stateless transport.
57+
58+
### Authorization
59+
60+
HTTP transport opens BoJ to the public internet (if so deployed), unlike stdio which is local-process-bound. Authorization is required, not optional.
61+
62+
Per ADR-0007 (trust-tier policy DSL), the bridge already routes through a PEP. HTTP transport adds an authentication layer in front of the PEP:
63+
64+
| Mode | Auth | When |
65+
|---|---|---|
66+
| `BOJ_HTTP_AUTH=none` | No auth | Loopback only (`127.0.0.1`); refuses non-loopback connections. Dev convenience. |
67+
| `BOJ_HTTP_AUTH=bearer` | `Authorization: Bearer <token>` | Token list in `BOJ_HTTP_AUTH_TOKENS` (CSV) or `~/.boj/http-tokens.a2ml` (per-peer). |
68+
| `BOJ_HTTP_AUTH=mtls` | Client cert verification | Production. Cert pool at `BOJ_HTTP_MTLS_CA`. |
69+
| `BOJ_HTTP_AUTH=oidc` | OIDC token verification | Federated; verifies token against an issuer config. Defers to coord-mcp's DID model when federation is active (ADR-0010). |
70+
71+
Default: **`bearer` if HTTP transport is enabled and the listener is non-loopback**. The bridge refuses to start with `BOJ_HTTP_AUTH=none` on a non-loopback bind.
72+
73+
### Deployment targets enabled
74+
75+
| Target | Works? | Caveats |
76+
|---|---|---|
77+
| Single-process stdio (today) | ✅ unchanged | Nothing breaks |
78+
| Single-process HTTP | ✅ new | Same bridge, listening on port |
79+
| Docker / Podman container | ✅ new | Already trivial; just expose the port |
80+
| Cloudflare Worker | ✅ new | With caveats: long-lived state requires Durable Objects; ~60% of tools (HTTP-API-based cartridges) work; local-only cartridges (browser-mcp, container-mcp, local-coord-mcp) don't |
81+
| AWS Lambda / Vercel Edge | ⚠️ limited | Stateless functions can serve `POST /mcp` but can't hold SSE streams long-term |
82+
| Browser-based agent web apps | ✅ new | Via `fetch` against a hosted BoJ instance |
83+
84+
### What does NOT change
85+
86+
- **stdio transport** — fully preserved; default mode unchanged
87+
- **Cartridge architecture** — Idris2 ABI + Zig FFI + Deno/JS adapter triple
88+
- **Tool surface** — same `tools/list`, `resources/list`, `prompts/list` outputs; HTTP just changes how requests arrive
89+
- **Security gate**`hardeningGate` runs identically on both transports
90+
- **OTel span emission** — every HTTP request emits a span (same as stdio tools/call)
91+
92+
### Cartridge compatibility under HTTP
93+
94+
For deployments where BoJ runs HTTP-transport-only (e.g. Cloudflare Worker), some cartridges become non-functional:
95+
96+
| Cartridge category | HTTP-only? | Reason |
97+
|---|---|---|
98+
| GitHub/GitLab/Cloudflare/Vercel/etc. | ✅ Works | Pure HTTP API calls outbound |
99+
| Hugging Face / ML / Semantic Scholar | ✅ Works | HTTP API |
100+
| Search (new from item 8) | ✅ Works | HTTP API |
101+
| Database cartridges (HTTP-API ones: Supabase, Neon, Turso) | ✅ Works | HTTP API |
102+
| Database cartridges (TCP: PostgreSQL, Redis, MongoDB) | ⚠️ Workers-limited | Cloudflare Hyperdrive workaround |
103+
| `browser-mcp` (Firefox via Marionette) | ❌ Local-only | Needs host browser |
104+
| `container-mcp`, `k8s-mcp` | ❌ Local-only | Needs Podman/kubectl |
105+
| `local-coord-mcp` | ❌ Local-only | Loopback bus (unless ADR-0010 federation lands) |
106+
| `sandbox-mcp` (per ADR-0009, local backend) | ❌ Local-only | Process isolation requires host |
107+
| `sandbox-mcp` (per ADR-0009, SaaS backends) | ✅ Works | Pure HTTP API |
108+
109+
A new `boj_capabilities` extension reports per-deployment which cartridges are actually available, so MCP clients don't try to invoke locally-impossible operations against a Workers-deployed BoJ.
110+
111+
## Consequences
112+
113+
### Positive
114+
115+
- **Three new deployment targets** unlocked: Workers, containers, browser-based agents
116+
- **JSR runtime-compatibility honest** — Browsers + Workers can be ticked once item 14 lands, with documented cartridge compatibility caveats
117+
- **Federation alignment** (ADR-0010) — cross-machine coord federation needs HTTPS transport anyway; this RFC's transport is reusable for the federation layer
118+
- **Session state across reconnects** — long-lived MCP sessions survive client crashes
119+
- **Same security model**`hardeningGate` + policy DSL (ADR-0007) work identically on both transports
120+
- **Webhook-inbound consolidation** (ADR-0011) — the existing Cowboy listener for webhooks can host the MCP HTTP endpoint too (single port, two paths: `/webhooks/*` and `/mcp`)
121+
- **OTel spans for free** — instrumentation from item 13 / PR #91 applies to HTTP requests with no extra work
122+
- **Zero stdio breakage** — opt-in transport
123+
124+
### Negative
125+
126+
- **Auth surface** — public-facing endpoint means real auth (bearer / mTLS / OIDC) needs implementation + careful review. Currently no public-facing surface; this is new attack surface.
127+
- **Session-state management** — stdio is stateful by virtue of being a single long-lived process; HTTP is stateless per request. Bridge needs per-session state machine (rate-limit counters, OTel trace context, in-flight async work). Bounded, but new code.
128+
- **Workers Durable Objects** — to hold session state in a Worker, Durable Objects are required. Adds complexity for Worker users; documented in the Workers-deployment guide.
129+
- **Two transport code paths to maintain** — though most of the code (handler logic) is shared; only the I/O layer differs.
130+
- **Cartridge incompatibility surfaces** — operators will discover at runtime that `browser-mcp` doesn't work on a Worker; `boj_capabilities` is the mitigation but it's a new contract clients have to learn.
131+
132+
## Non-goals
133+
134+
- **Not deprecating stdio** — stdio is the default and stays so. Most MCP clients use stdio; that's correct.
135+
- **Not implementing custom transports** (WebSocket, gRPC) — Streamable HTTP+SSE is the spec; we follow it. If the spec evolves to add others, revisit.
136+
- **Not building a hosted SaaS BoJ** — this RFC provides the transport; it doesn't run a public BoJ-as-a-service. That's a deployment choice.
137+
- **Not changing tool/resource/prompt surfaces** — same MCP capabilities; just a new transport.
138+
- **Not enabling cross-cartridge isolation** that doesn't exist on stdio. Cartridges that work locally work over HTTP; cartridges that need local resources don't suddenly become Worker-compatible.
139+
140+
## Open questions
141+
142+
1. **Session storage** — for a stdio process, session state is in-memory and disappears on process exit. For HTTP, do we persist session state across restarts? Recommend: ephemeral in-memory by default; opt-in to durable session storage (Redis / file) via `BOJ_HTTP_SESSION_STORE` env. Workers users use Durable Objects.
143+
144+
2. **Cold-start cost on Workers** — first request to a Worker initialises everything. Cartridge manifest loading + Nickel validator could be slow. Recommend: bundle the offline-menu + tool list into a static asset at build time so cold-start hits no I/O.
145+
146+
3. **SSE stream lifetime** — Workers have execution limits (~30s standard; longer with Durable Objects). What happens when the client expects a long-lived SSE stream and the Worker terminates? Recommend: documented as a Workers limitation; clients should reconnect on stream end.
147+
148+
4. **Backpressure under HTTP** — multiple clients to one BoJ instance can outpace cartridge dispatch capacity. Recommend: per-session rate limiting via ADR-0007 policy + global queue with bounded depth + 503 on overflow.
149+
150+
5. **TLS termination** — does the bridge speak TLS directly, or is it always behind a reverse proxy (nginx, Cloudflare, Caddy)? Recommend: behind a proxy by default; the bridge listens HTTP on localhost. TLS-direct mode opt-in via `BOJ_HTTP_TLS_*` env vars for operators who want a one-binary deployment.
151+
152+
6. **Compatibility with the existing `boj-rest` Cowboy listener** (port 7700) — the unreleased CHANGELOG mentions "boj-rest SSE surface: POST /cartridge/:name/sse on the same single Cowboy listener". Should the MCP HTTP transport mount on that same port (Cowboy already runs there), or a separate port? Recommend: same port; path-routed (`/mcp` vs `/cartridge/:name/*` vs `/webhooks/*`). Single listener honours ADR-0004.
153+
154+
## Implementation sketch
155+
156+
When this RFC is accepted (~1-2 weeks, two PRs):
157+
158+
**PR 1 — bridge HTTP transport** (~1 week)
159+
1. New `mcp-bridge/lib/http-transport.js` — Cowboy / Hono / native http (TBD per runtime). Listen on `BOJ_HTTP_PORT`, dispatch JSON-RPC.
160+
2. Session manager — issue UUIDs on initialize, maintain per-session state map, expire on timeout.
161+
3. Auth middleware — bearer / mTLS / OIDC backends.
162+
4. SSE handler — server-initiated notifications (per ADR-0011) fan out over the session's open SSE stream.
163+
5. `mcp-bridge/main.js` reads `BOJ_TRANSPORT` and starts stdio, http, or both.
164+
6. New env vars in `glama.json`: `BOJ_TRANSPORT`, `BOJ_HTTP_PORT`, `BOJ_HTTP_AUTH`, `BOJ_HTTP_AUTH_TOKENS`, `BOJ_HTTP_MTLS_CA`, `BOJ_HTTP_SESSION_STORE`.
165+
7. `boj_capabilities` resource (per-deployment cartridge availability).
166+
8. Tests: HTTP transport mirroring the stdio dispatch tests; auth-rejection tests; session-expiry tests.
167+
168+
**PR 2 — Workers deployment guide + Durable Objects shim** (~1 week)
169+
1. New `docs/deployment/CLOUDFLARE-WORKERS.md` with wrangler config example.
170+
2. Durable Object wrapper around the session manager.
171+
3. Static bundling of the cartridge manifest so cold-start is cheap.
172+
4. Example `wrangler.toml` showing minimum config.
173+
174+
Subsequent: native browser-client SDK (item 14 follow-up).
175+
176+
## Linked
177+
178+
- ADR-0002 (BoJ-only MCP) — HTTP transport is *within* BoJ, not a separate MCP server.
179+
- ADR-0004 (unified gateway) — same Cowboy listener mounts `/mcp` alongside existing paths.
180+
- ADR-0007 (policy DSL) — auth layer fronts the existing PEP; no policy changes.
181+
- ADR-0009 (sandbox cartridge) — local sandbox backend incompatible with Workers; SaaS sandbox backends compatible.
182+
- ADR-0010 (federation) — federation transport reuses this RFC's HTTP layer.
183+
- ADR-0011 (webhooks/notifications) — `notifications/event` fan-out over the SSE stream defined here.
184+
- Epic #87 item 14 (this).

mcp-bridge/lib/offline-menu.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ export const OFFLINE_MENU = {
2828
{ name: "comms-mcp", version: "0.1.0", domain: "Communications", protocols: ["MCP","REST"], status: "Available", available: true },
2929
{ name: "ml-mcp", version: "0.1.0", domain: "ML/AI", protocols: ["MCP","REST"], status: "Available", available: true },
3030
{ name: "research-mcp", version: "0.1.0", domain: "Research", protocols: ["MCP","REST"], status: "Available", available: true },
31+
{ name: "search-mcp", version: "0.1.0", domain: "Research", protocols: ["MCP","REST"], status: "Available", available: true, notes: "Multi-provider web search (Tavily, Brave, Exa, Perplexity) behind one cartridge" },
3132
{ name: "codeseeker-mcp", version: "0.1.0", domain: "Code Intelligence", protocols: ["MCP","REST"], status: "Available", available: true },
3233
{ name: "lang-mcp", version: "0.1.0", domain: "Languages", protocols: ["MCP","REST"], status: "Available", available: true },
3334
],

0 commit comments

Comments
 (0)