Phase 0 exploration for alknet-http (greenfield crate, no existing arch): HTTP server (axum, ProtocolHandler for h2/http1.1, h3 deferred), HTTP client (reqwest, the from_openapi/from_mcp forwarding handlers), MCP streamable HTTP (feature-gated, stdio excluded as security position), to_openapi/to_mcp projections. Records: 8 design points (DH-3 HTTP→call operation mapping as the load-bearing one), the settled adapter location map (from alknet-call gap analysis), the no-env-vars invariant (Capabilities → from_openapi handler → HTTP header as the credential injection point), and the prerequisite on alknet-call's OperationAdapter trait being defined first.
20 KiB
status, last_updated
| status | last_updated |
|---|---|
| draft | 2026-06-25 |
alknet-http — Phase 0 Research Findings
This document captures Phase 0 (Exploration) findings for the alknet-http
crate. Unlike alknet-call (which has existing architecture being completed),
alknet-http has zero architecture docs and zero implementation — this is a
true greenfield exploration. It will need iteration; the goal here is to surface
the design space, the settled decisions, and the open questions so Phase 1 can
proceed with direction.
Vision
alknet-http is the HTTP protocol handler for the ALPN-as-service architecture.
It serves two roles:
- HTTP server — a
ProtocolHandlerthat accepts HTTP/2, HTTP/1.1, and optionally HTTP/3 (WebTransport) connections on standard ALPNs (h2,http/1.1,h3), serving REST APIs, dashboards, MCP endpoints, and theto_openapi/to_mcpprojections of local call-protocol operations. - HTTP client host — the home of the HTTP-transport-backed adapters
(
from_openapi,from_mcp,to_openapi,to_mcp) that use reqwest (client) and axum (server) to bridge between the call protocol and external HTTP/MCP systems.
The key architectural insight: HTTP is both a server surface and a client transport for adapters. Both directions share the same HTTP dependencies (axum for serving, reqwest for calling out), which is why they live in one crate rather than being split.
Sources Investigated
| Source | Path | Note |
|---|---|---|
| alknet-core types | docs/architecture/crates/core/* |
ProtocolHandler, Connection, AuthContext, IdentityProvider |
| alknet-call specs | docs/architecture/crates/call/*, ADR-017/022/024 |
OperationAdapter trait (where from_openapi/from_mcp implement), HandlerRegistration, Capabilities |
| alknet-call gap analysis | docs/research/alknet-call-completion/gap-analysis.md |
Adapter location map, the no-env-vars invariant |
| TS @alkdev/operations | /workspace/@alkdev/operations/src/ |
from_openapi.ts, from_mcp.ts, from_schema.ts — prior art for adapter patterns |
| MCP Rust SDK (rmcp) | /workspace/rust-sdk/ |
Streamable HTTP transport (client + server), the simple_auth_streamhttp.rs example |
| aisdk | /workspace/aisdk/ |
Rust port of Vercel AI SDK; 75 providers; the no-env-vars problem this crate's credential injection solves |
| overview.md ALPN Registry | docs/architecture/overview.md |
h2/http/1.1 → HttpAdapter, h3 → HttpAdapter (WebTransport) |
| endpoint.md stealth section | docs/architecture/crates/core/endpoint.md |
HTTP handler on standard ALPNs serves decoy — the "stealth mode" mapping |
What's Settled
These are confirmed by existing ADRs or our prior conversation and should not be re-litigated in Phase 1.
1. alknet-http implements ProtocolHandler for standard HTTP ALPNs
From overview.md's ALPN Registry:
| ALPN | Handler | Use case |
|---|---|---|
h2 |
HttpAdapter | HTTP/2 for browsers, curl, standard clients |
http/1.1 |
HttpAdapter | HTTP/1.1 fallback for legacy clients |
h3 |
HttpAdapter (WebTransport upgrade) | HTTP/3 / WebTransport for browser streaming |
Unlike custom alknet ALPNs (alknet/ssh, alknet/call), HTTP uses the
standard IANA ALPN strings. This means any HTTP client (browser, curl,
axios) can connect without knowing about alknet — the TLS handshake negotiates
h2 or http/1.1 normally, and the HttpAdapter serves HTTP.
2. The adapter implementations live here, the trait lives in alknet-call
From the gap analysis adapter location map:
alknet-call owns: alknet-http owns:
OperationAdapter trait from_openapi (parse + reqwest forwarding)
from_call (QUIC) to_openapi (generate OpenAPI doc)
from_jsonschema (pure) from_mcp (streamable HTTP client — reqwest)
to_mcp (streamable HTTP server — axum)
alknet-http depends on alknet-call for the types (OperationSpec, Handler,
HandlerRegistration, OperationAdapter). This is a protocol-foundation
dependency (alknet-call is not a peer handler), within the spirit of ADR-003.
Phase 1 should note this explicitly and possibly amend ADR-003 to clarify
alknet-call's dual role.
3. The no-env-vars invariant — credential injection point
The from_openapi/from_mcp forwarding handlers are the credential injection
point for the no-env-vars architecture. The path (from the gap analysis):
vault → assembly layer → Capabilities → HandlerRegistration.capabilities
→ OperationContext.capabilities → from_openapi handler reads
context.capabilities.get("openai") → injects into HTTP Authorization
header → reqwest request goes out with vault-derived credential
This makes aisdk's std::env::var("OPENAI_API_KEY") reads unreachable —
the assembly layer never calls Default::default() on a provider; it
constructs them with vault-derived credentials, or routes HTTP calls through
from_openapi operations that carry the credential in Capabilities.
This is a spec-level invariant: no handler reads outbound credentials from
any source other than OperationContext.capabilities. The from_openapi/
from_mcp implementations in alknet-http are verified against this invariant.
4. MCP stdio is explicitly excluded (security position)
From our conversation: MCP stdio transport (transport-child-process in rmcp)
= spawn an arbitrary executable, pipe JSON-RPC over stdin/stdout. Downloading
untrusted MCP servers and running them via stdio is indistinguishable from
curl | sh with extra steps. The "download untrusted and probably poorly
written mcp servers" model is an RCE vector.
alknet-http supports only streamable HTTP for MCP. Stdio is not built. If
someone wants stdio MCP, they run it themselves outside alknet. This is an
explicit security position, recorded as an ADR-level constraint in the
alknet-http spec. The streamable HTTP transport is the supported path; it's
network-isolated, auth-gatable (Bearer token middleware, per the rmcp
simple_auth_streamhttp.rs example), and runs under alknet's auth/identity/
capabilities machinery.
5. Stealth mode = HTTP handler on standard ALPNs
From endpoint.md: the reference implementation's "stealth mode" (SSH-over-TLS on port 443 with a fake nginx 404 for non-SSH traffic) maps to the HTTP handler serving a decoy website or fake 404 on standard HTTP ALPNs. Clients that don't offer alknet ALPNs get the HTTP handler — just like port scanners in stealth mode. No byte-peeking; ALPN does the routing.
Design Space (Decision Points)
These are genuine architectural choices for a greenfield crate. Each is tagged with door type per ADR-009. Phase 1 resolves these (some via ADRs, some as two-way-door defaults).
DH-1: HTTP framework — axum
(Recommended: two-way door — axum is the obvious choice but not locked)
The overview.md and the rmcp examples all use axum. axum is built on hyper,
integrates with tower (middleware), and is the de facto Rust web framework.
Alternatives (actix-web, warp, raw hyper) exist but axum is the path of least
resistance and aligns with the rmcp streamable HTTP server (which uses axum's
Router + StreamableHttpService).
Recommendation: axum. The rmcp StreamableHttpService is a tower
service that nests into an axum Router directly (see
simple_auth_streamhttp.rs:134-159), so using axum makes the MCP server
integration trivial. Two-way door — switching frameworks later is painful but
not architecturally locked.
DH-2: HTTP/3 + WebTransport — in v1 or deferred?
(Recommended: two-way door — defer h3/WebTransport past v1)
HTTP/3 (QUIC-based) and WebTransport are the browser-streaming path. Browsers don't support RFC 7250 raw keys (ADR-027), so WebTransport requires X.509 certs — meaning it's a domain-hosted-service concern, not a P2P concern. quinn already speaks QUIC; HTTP/3 is a framing layer on top.
The question is whether v1 needs WebTransport or whether HTTP/2 + HTTP/1.1 suffices. For the runner pattern and the call-protocol HTTP projection, HTTP/2 is sufficient. WebTransport is the browser path for the agent service (alknet-agent), which is downstream of alknet-http anyway.
Recommendation: defer h3/WebTransport past v1. Start with h2 +
http/1.1. The h3 ALPN is reserved in the registry but the implementation
lands as a fast-follow when the agent service needs browser streaming. This
keeps v1 focused on the adapter + REST surface. Two-way door.
DH-3: How does HTTP map to call-protocol operations?
(One-way door — needs an ADR)
The core question: when an HTTP request arrives at alknet/http://api.example.com/container/exec,
how does it become a call-protocol operation? Options:
- (a) Direct path mapping:
POST /{service}/{op}→call.requestedfor/{service}/{op}. Matches the call protocol's/{service}/{op}path format (OQ-13). The HTTP handler is a thin bridge: parse the HTTP request body as the operation input, sendcall.requested, return the response as JSON. - (b) OpenAPI-defined routes: the HTTP surface is defined by the
to_openapiprojection — routes, methods, schemas are generated from the registry'sExternaloperations. The HTTP handler dispatches based on the generated OpenAPI spec's path mapping. - (c) Explicit route registration: the assembly layer registers HTTP routes explicitly, mapping URL paths to operations. Most flexible, most boilerplate.
Recommendation: (a) direct path mapping as the default, with (b) as the
discovery/projection layer. The HTTP handler receives POST /container/exec,
constructs {operationId: "container/exec", input: <parsed body>}, and
dispatches it through the call protocol. to_openapi generates the spec that
describes this surface for external consumers; it doesn't define separate
routes. This keeps the HTTP surface a thin projection of the call protocol,
not a parallel routing layer. Needs an ADR.
DH-4: Auth — how does HTTP auth map to AuthContext?
(One-way door — needs an ADR, but largely settled by existing ADRs)
The HttpAdapter extracts credentials and resolves identity through
IdentityProvider (ADR-004). The credential source for HTTP is the
Authorization: Bearer <token> header. The handler calls
resolve_from_token(&AuthToken { raw: token_bytes }). This is already in
auth.md's table:
| Handler | Credential source | Resolution method |
|---|---|---|
| HttpAdapter | Authorization: Bearer header |
resolve_from_token() |
Recommendation: this is settled by ADR-004 + auth.md. The HttpAdapter
constructor-injects Arc<dyn IdentityProvider> (same pattern as SshAdapter).
No new ADR needed — Phase 1 documents the existing model. The one sub-question
is whether HTTP supports multiple auth mechanisms (Bearer + basic + API key in
query param) or Bearer-only. Recommendation: Bearer-only for v1 (matches
the call protocol's AuthToken model); other mechanisms are two-way-door
additions.
DH-5: MCP streamable HTTP — server and/or client?
(Recommended: both, feature-gated — confirmed in conversation)
From our conversation + rmcp survey:
from_mcp(import remote MCP tools): uses rmcp'sStreamableHttpClientTransport(reqwest-based,transport-streamable-http-client-reqwestfeature). Discovers MCP tools, registers them asFromMCP-provenance operations with forwarding handlers.to_mcp(expose local ops as MCP tools): uses rmcp'sStreamableHttpService(axum-based,transport-streamable-http-serverfeature). Serves localExternaloperations as MCP tools over streamable HTTP.
Both are feature-gated (the rmcp dependency is optional). The MCP server
auth uses Bearer token middleware (the simple_auth_streamhttp.rs example
shows the pattern).
Recommendation: both, behind an mcp feature gate. The rmcp
dependency (rmcp = { version = "1.8", features = [...] }) is optional;
enabling the mcp feature pulls in rmcp with the streamable HTTP transport
features. Without the feature, alknet-http is a pure HTTP server + OpenAPI
adapter with no MCP support. Two-way door on the feature gate; one-way door
on stdio exclusion.
DH-6: Dashboard / health / metrics surface
(Two-way door — start minimal, extend later)
An HTTP server typically needs operational endpoints: health check, metrics,
maybe a dashboard. The question is whether these are call-protocol operations
(/health/check, /metrics/list) or raw HTTP routes outside the call
protocol.
Recommendation: health check as a raw HTTP route (GET /healthz)
outside the call protocol (no auth, no operation registration — it's an
infrastructure endpoint for load balancers). Metrics and dashboard are
call-protocol operations (/metrics/list, /dashboard/view) if built at all.
Start with just /healthz in v1. Two-way door.
DH-7: HTTP client — reqwest config and connection pooling
(Two-way door — implementation detail)
The from_openapi/from_mcp forwarding handlers use reqwest to make outbound
HTTP calls. The aisdk core/client.rs shows a pattern worth referencing: a
shared reqwest::Client with connection pooling (OnceLock<reqwest::Client>),
retry logic (exponential backoff, Retry-After header), and separate streaming
vs non-streaming clients.
Recommendation: alknet-http maintains its own shared reqwest::Client
(constructed once, reused across all forwarding handlers). The retry/pooling
config comes from StaticConfig or DynamicConfig (hot-reloadable). Don't
inherit aisdk's client — alknet-http owns its HTTP client. The credential
injection happens per-request (from OperationContext.capabilities), not at
client construction. Two-way door on the exact pooling/retry config.
DH-8: TLS for outbound HTTP calls
(Two-way door — implementation detail, connects to ADR-027)
The from_openapi/from_mcp handlers make outbound HTTPS calls. The TLS
config for these calls: do they use the system trust store (default reqwest
behavior), or a custom CA bundle, or vault-derived client certs?
Recommendation: system trust store by default (standard HTTPS to
external APIs like OpenAI, Anthropic, etc.). Custom CA bundle + client certs
as an optional config for self-hosted API gateways. This is a two-way-door
implementation detail; the credential (API key/token) comes from
Capabilities, the TLS trust comes from the system. No ADR needed.
Tentative Recommended Approach (Convergence)
-
Crate:
alknet-http, depends onalknet-core(ProtocolHandler, AuthContext, IdentityProvider),alknet-call(OperationAdapter trait, OperationSpec, HandlerRegistration, Capabilities),axum(HTTP server),reqwest(HTTP client), and optionallyrmcp(MCP, feature-gated). -
HTTP server (
h2+http/1.1): axumRouterwrapped as aProtocolHandler. Receives the QUICConnection, accepts a bistream, hands the duplex stream to hyper/axum's connection handler. Direct path mapping (POST /{service}/{op}→call.requested) for the default surface. Bearer auth viaIdentityProvider::resolve_from_token(). -
HTTP client (
from_openapi/from_mcpforwarding): sharedreqwest::Clientwith pooling + retry. Credentials fromOperationContext.capabilitiesper the no-env-vars invariant. Thefrom_openapihandler readscontext.capabilities.get("openai")and injects into theAuthorizationheader. -
MCP (feature-gated
mcp):from_mcpvia rmcpStreamableHttpClientTransport(reqwest);to_mcpvia rmcpStreamableHttpService(axum). Stdio excluded (security position). -
to_openapi: generates an OpenAPI spec from the local registry'sExternaloperations. Pure serialization — no HTTP client, no HTTP server. Served atGET /openapi.json(or similar) by the HTTP server. -
h3/WebTransport: deferred past v1. ALPN reserved, implementation lands as a fast-follow for the agent service's browser-streaming path. -
Health:
GET /healthzas a raw HTTP route (no auth, no call protocol). -
Stealth: the HTTP handler on
h2/http/1.1serves a decoy/fake 404 for unknown paths, matching the endpoint.md stealth-mode mapping. Real services usealknet/ssh,alknet/call, etc.
Open Questions to Carry into Phase 1
- OQ-HTTP-01 (HTTP → call operation mapping): direct path mapping
(
POST /{service}/{op}→call.requested) as the default — confirmed approach, needs an ADR (DH-3). - OQ-HTTP-02 (h3/WebTransport timeline): deferred past v1 (DH-2). Two-way door; lands when the agent service needs browser streaming.
- OQ-HTTP-03 (MCP feature gate shape): the exact rmcp features to enable
and the
mcpfeature gate definition (DH-5). Two-way door. - OQ-HTTP-04 (dashboard/metrics surface): minimal
/healthzin v1, metrics/dashboard as call-protocol operations if built (DH-6). Two-way door. - OQ-HTTP-05 (ADR-003 amendment): clarify that alknet-http depending on alknet-call is permitted (alknet-call is protocol-foundation, not a peer handler). One-line amendment.
- OQ-HTTP-06 (to_openapi published-spec versioning): ADR-017 Consequences
notes that published
to_*specs are compatibility contracts. The versioning strategy for generated OpenAPI specs (tied to the registry's External operation set version) needs specifying. One-way door after first publication.
Next Steps (Phase 0 → Phase 1)
- You decide on DH-3 (HTTP → call operation mapping) — this is the load-bearing architectural choice. The others (DH-1, DH-2, DH-4, DH-5, DH-6, DH-7, DH-8) are two-way-door defaults I recommend accepting as-is.
- alknet-call completion prerequisite: the
OperationAdaptertrait must be defined in alknet-call before alknet-http'sfrom_openapi/from_mcpcan be implemented. Phase 1 for alknet-http can proceed in parallel with alknet-call completion (the spec can be written against the trait shape from ADR-017), but implementation of the HTTP-backed adapters blocks on the trait landing. - Phase 1 (Architect): produce
docs/architecture/crates/http/README.md- component specs (e.g.,
http-server.md,http-client.md,http-adapters.md,http-mcp.md), ADRs for DH-3 (HTTP→call mapping) and the MCP stdio exclusion (security position), and the OQs above. Updatedocs/architecture/README.mdindex and ADR table.
- component specs (e.g.,
References
docs/sdd_process.md— Phase 0 process definitiondocs/architecture/overview.md— ALPN Registry (h2/http1.1/h3 → HttpAdapter)docs/architecture/crates/core/core-types.md— ProtocolHandler, Connectiondocs/architecture/crates/core/auth.md— HttpAdapter auth (Bearer → resolve_from_token)docs/architecture/crates/core/endpoint.md— stealth mode as ALPN dispatchdocs/architecture/decisions/017-call-protocol-client-and-adapter-contract.md— OperationAdapter trait, to_openapi/to_mcpdocs/architecture/decisions/022-handler-registration-provenance-and-composition-authority.md— Capabilities injection, FromOpenAPI/FromMCP provenancedocs/architecture/decisions/027-tls-identity-redesign-acme-rawkey-decoupling.md— browser limitation (no RFC 7250), WebTransport needs X.509docs/research/alknet-call-completion/gap-analysis.md— adapter location map, no-env-vars invariant, exchange-of-operations pattern/workspace/@alkdev/operations/src/— TS prior art:from_openapi.ts,from_mcp.ts,from_schema.ts,scanner.ts/workspace/rust-sdk/— MCP Rust SDK (rmcp v1.8.0); streamable HTTP transport/workspace/rust-sdk/examples/servers/src/simple_auth_streamhttp.rs— streamable HTTP MCP server with Bearer auth (the pattern forto_mcp)/workspace/rust-sdk/examples/clients/src/streamable_http.rs— streamable HTTP MCP client (the pattern forfrom_mcp)/workspace/aisdk/— Rust port of Vercel AI SDK; 75 providers with env-var reads that the no-env-vars invariant makes unreachable/workspace/aisdk/src/core/client.rs— HTTP client reference (pooling, retry, streaming vs non-streaming)