feat(engine): gateway transport (config + hand-rolled MCP client)
Summary
First slice of the thin-engine re-scope: sealg becomes a thin transport that forwards tool calls to the SealGate gateway's per-user /mcp/{api_key}/ endpoint, where all policy / lethal-trifecta / PII enforcement lives. The binary carries no policy of its own.
This PR is additive — it adds the gateway transport module but does not yet wire it into the CLI surface, so nothing existing breaks. The follow-up slice rewires sealg call → the gateway, adds sealg list, and deletes the template's local demo commands.
Design context: dev-docs/architecture/multi-interface-design.md in edison-watch/edison-watch (§5A thin engine, §5B Centaur, B3 error mirroring).
Changes
crates/engine/src/gateway/config.rs—GatewayConfig::from_env()resolves gateway coordinates from the environment (so the binary stays stateless and drops into any sandbox):- gateway URL (
SEALGATE_URL, defaults to the dev endpoint — never a guessed prod host) - optional API key: present → embedded in the
/mcp/{key}/path; absent → keyless path, auth injected upstream (Centaur iron-proxy). Supports both credential models with no fork. sealgate_secret_keyheader value, conversation id (SEALGATE_CONVERSATION_ID, falling back to Centaur'sCENTAUR_THREAD_KEY), and a MITM CA bundle (SSL_CERT_FILE/REQUESTS_CA_BUNDLE/NODE_EXTRA_CA_CERTS) — reqwest+rustls does not read those automatically, so we load them or TLS fails behind an intercepting proxy.- written against a getter seam for deterministic tests.
- gateway URL (
crates/engine/src/gateway/client.rs— hand-rolled MCP Streamable-HTTP client (spec 2026-07-28), normcpdependency so the binary stays thin:initialize/tools_list/tools_call,Mcp-Session-Idcapture, JSON + SSE response parsing, and JSON-RPC errors mapped to a variant that mirrors the MCP error shape verbatim (no bespoke taxonomy).crates/engine/src/gateway/mod.rs+lib.rs— module wiring and re-exports.
Testing
- Tests pass —
cargo test -p engine gateway: 10 passed (env resolution, URL building, JSON/SSE/error parsing). The client's transport methods need a live gateway, so this slice unit-tests the pure pieces (config resolution, the response parser). - Linting —
cargo fmt --checkandcargo clippy -p engineclean locally. Fullmake ciruns here on CI.
Related Issues
None — tracked by the multi-interface design doc.
Generated by Claude Code
Summary by cubic
Turns sealg into a thin transport that forwards tool calls to the SealGate gateway's per-user /mcp/{api_key}/ endpoint, where all policy and PII enforcement lives. Additive only — the CLI surface is unchanged and nothing existing breaks.
GatewayConfig::from_env()reads gateway URL, API key, secret key, conversation id, and MITM CA bundle from the environment.- With an API key the path becomes
/mcp/{key}/; without one, auth is injected upstream and the keyless path is used; conversation id falls back toCENTAUR_THREAD_KEY. - Adds a hand-rolled MCP Streamable HTTP client (initialize, tools/list, tools/call) with JSON and SSE parsing and no
rmcpdependency. - CA bundle env vars are honored explicitly because reqwest+rustls does not read them, so TLS keeps working behind an intercepting proxy.
- JSON-RPC errors mirror the MCP error shape so callers can map them to exit codes directly; 10 unit tests cover config resolution, URL building, and response parsing.