Architecture14 min readUpdated August 9, 2026

Seven Surfaces, One Catalog

Protocol Fabric is bidirectional across seven surfaces — MCP, HTTP, GraphQL, gRPC, WebSocket, CLI, and QR — with ClawQL Core ingesting them into MCP and mcp-api-adapter re-exposing the same catalog outbound.

Seven surfaces is not an mcp-api-adapter feature. It is the shape of the Protocol Fabric in both directions. For the short product map — protocol, memory, documents, Streams, security, flywheel — see Anything to MCP. MCP to Anything..

ClawQL Core turns inbound APIs into MCP tools (search / execute). mcp-api-adapter turns an MCP tool catalog back into APIs clients already speak. Same seven surfaces. Same intermediate representation. QR is the seventh on both sides — the channel that works when there is no network.

The seven surfaces are:

  1. MCP
  2. HTTP (OpenAPI, Google Discovery, Swagger — catalog formats for the same HTTP surface, not separate surfaces)
  3. GraphQL
  4. gRPC
  5. WebSocket
  6. CLI (gen-cli on the adapter; CLI custom sources on Core)
  7. QR

Six ship today on the adapter in [email protected]. QR is the planned seventh for the adapter and Core’s matching inbound source. Here is the symmetry, what already shipped, and why air-gap belongs in the fabric instead of a one-off.

Earlier draft of this post covered five adapter surfaces (through gen-cli). WebSocket landed as the sixth in 0.6.0. The missing half was Core’s matching inbound set — including QR as a first-class source.

The client fragmentation problem

MCP standardized how agents discover and call tools. That is good. What it did not standardize is how every other kind of consumer reaches those tools — or how every kind of upstream becomes tools in the first place.

A Cloudflare Worker wants POST /memory_recall with a JSON body. An OpenWebUI instance wants an OpenAPI URL. A GraphQL client wants a typed mutation. An SRE wants to grpcurl the mesh. An IDE wants Streamable HTTP /mcp. A data team wants a thin CLI. A Durable Object wants a hibernatable WebSocket. A regulated auditor standing in front of an air-gapped rack wants a camera pointed at a screen — not a VPN into the production VPC.

On the inbound side the fragmentation is the same problem flipped: HTTP APIs described by OpenAPI or Google Discovery, native GraphQL, gRPC services, upstream MCP servers, Streams WebSockets, CLI tools, and — once built — QR cameras and recorded optical sessions all need to land in one operation index with one search / execute pattern.

The current answer is custom glue for each direction and each surface. Or Python mcpo for OpenAPI outbound and figure out the rest yourself.

ClawQL’s answer is symmetry: anything to MCP, MCP to anything — including across a physical air gap.

Seven-surface symmetry

SurfaceClawQL Core (APIs → MCP)mcp-api-adapter (MCP → APIs)
MCPCLAWQL_MCP_SERVERS/mcp re-export
HTTPOpenAPI / Discovery / Swagger (CLAWQL_SPEC_*, CLAWQL_DISCOVERY_URL, …)POST /{toolName} + /docs
GraphQLCLAWQL_GRAPHQL_SOURCESPOST /graphql + /graphiql
gRPCCLAWQL_GRPC_SOURCES:50051
WebSocketvia clawql-streams/ws handler
CLICLI custom sourcesgen-cli
QRCLAWQL_QR_SOURCES (planned)--qr-output / --qr-input (planned)

OpenAPI and Google Discovery are HTTP catalog formats, not a seventh surface. Discovery documents describe HTTP APIs the same way OpenAPI does; Core loads them into the same HTTP lane. The CLI surface is gen-cli (and Core’s CLI custom sources) — not Discovery.

Core already covers MCP, HTTP, GraphQL, gRPC, WebSocket (via Streams), and CLI sources. QR is the seventh: a camera or recorded session becomes a queryable API with the same search / execute semantics as every other surface.

The adapter’s outbound set is the inverse: wrap any MCP server and mount MCP, HTTP, GraphQL, gRPC, WebSocket, CLI, and (planned) optical QR. No ClawQL install required for the six shipped adapter surfaces. Works against any MCP server.

In marketing, call this “seven surfaces, one catalog” or “Protocol Fabric.” Do not call the adapter “the OpenAPI gateway” — that phrase collides with Core’s inverse direction.

One process, seven outbound surfaces

Any MCP server
  ├─ stdio
  ├─ Streamable HTTP
  └─ gRPC


mcp-api-adapter

         ├── /mcp                 MCP — Streamable HTTP re-export for IDE / MCP SDK clients
         ├── POST /{toolName}     HTTP — OpenAPI + Swagger at /docs
         ├── POST /graphql        GraphQL mutations + GraphiQL at /graphiql
         ├── :50051               gRPC (upstream or locally scaffolded)
         ├── /ws                  WebSocket JSON tool calls
         ├── gen-cli              CLI — generated zero-dependency Node CLI
         └── QR stream            Optical channel (planned) — HDMI / camera / thermal

Point the adapter at one upstream. It calls ListTools at startup, builds the OpenAPI spec and GraphQL schema from each tool’s inputSchema, and mounts the network surfaces. The QR surface reuses the same catalog — it changes the wire, not the tools.

# Wrap a remote Streamable HTTP server
npx mcp-api-adapter --mcp-url http://127.0.0.1:8080/mcp

# Wrap a stdio package, expose everything including /mcp for IDEs
npx mcp-api-adapter --stdio -- npx -y @modelcontextprotocol/server-everything

# Front an existing gRPC MCP server
npx mcp-api-adapter --grpc-address 127.0.0.1:50051

# Generate a CLI from the tool catalog
npx mcp-api-adapter gen-cli --out ./my-cli --stdio -- \
  npx -y @modelcontextprotocol/server-everything

# Planned: unidirectional QR stream of tool results (air-gap)
# npx mcp-api-adapter --mcp-url http://127.0.0.1:8080/mcp \
#   --qr-output hdmi --qr-mode stream --qr-frame-interval-ms 500

The adapter surfaces, one by one

1. MCP — Streamable HTTP /mcp

Re-exports the upstream tool catalog as a standard MCP Streamable HTTP endpoint.

Use case: stdio-only local package → Cursor or Claude Desktop over https://your-host/mcp without SSH tunnels. Reverse case: gRPC-only production server → MCP SDK client in CI. The /mcp surface forwards into CallTool over gRPC, normalizing protobuf content oneofs into { type: "text", text } blocks so MCP SDK validation passes (v0.5.1).

MCP stays first-class. The adapter multiplies who can reach the same catalog; it does not demote the protocol.

2. HTTP — POST /{toolName} (+ OpenAPI /docs)

Every tool becomes a named HTTP route. Body matches inputSchema. Response collapses the MCP content array to JSON — preferring structuredContent, parsing text as JSON when possible, otherwise the content envelope.

Swagger UI at /docs publishes the OpenAPI description of that HTTP surface. Every path carries x-clawql-grpc extensions (endpoint, proto URL, example grpcurl). HTTP is an on-ramp, not a destination.

On the Core side, the same HTTP surface is how OpenAPI and Google Discovery documents enter the operation index. Different catalog dialects; one surface.

3. GraphQL — mutations per tool

Enterprise tooling is often GraphQL-native. Typed mutations from inputSchema mean those teams do not have to learn MCP. GraphiQL at /graphiql. Generic escape hatch: callTool(name: String!, arguments: JSON): ToolResult.

4. gRPC — the production path

If the upstream is already gRPC, HTTP and GraphQL forward into it. If the upstream is stdio or Streamable HTTP, the adapter starts a local mcp-grpc-transport server that delegates into the session.

Either way, :50051 is available for grpcurl and mesh routing. model_context_protocol.Mcp/CallTool takes a tool name and google.protobuf.Struct arguments. Argument schemas live in the HTTP OpenAPI and GraphQL surfaces — clients do not need generated stubs.

Google proposed gRPC as a first-class MCP transport in February 2026. ClawQL ships the production TypeScript implementation as mcp-grpc-transport. The adapter is how non-gRPC clients reach it.

5. WebSocket — /ws

Shipped in v0.6.0. Persistent JSON tool-call channel (default /ws; disable with --no-ws):

{ "id": "1", "tool": "memory_ingest", "arguments": { "title": "…", "insights": "…" } }

or MCP-shaped { "method": "tools/call", "params": { "name": "…", "arguments": { … } } }. Replies { "id", "ok", "result" | "error" }.

Prefer WebSocket for long-lived clients and Durable Object hibernation. Keep /mcp for IDE clients that cannot speak WS.

This surface also closes the Protocol Fabric loop cleanly: WS → execute a CLI custom source → gen-cli → memory_ingest — smoke-tested in-repo.

6. CLI — gen-cli

Reads the catalog, generates a thin Node CLI with one subcommand per tool, POSTs to the HTTP surface. PrintingPress will handle signed binary distribution when ready.

npx mcp-api-adapter gen-cli --out ./my-cli --stdio -- \
  npx -y @modelcontextprotocol/server-everything

./my-cli echo --message "hello"
# → { content: [{ type: "text", text: "hello" }] }

Ops scripts, CI steps, and “I just need to call one tool from bash” land here. gen-cli is build-time (disk), not a Durable Object runtime surface. On Core, the matching inbound lane is CLI custom sources — shell-shaped tools in the same search / execute index.

7. QR — optical stream (planned, both directions)

The first six surfaces assume a network (or local process). The seventh does not — and it is not adapter-only.

QR stream transport encodes MCP traffic (and Streams events) as QR frames — CBOR + zstd, Merkle-chained, HMAC’d, optionally bound to a TEE attestation. A display shows frames; a camera scans them. Unidirectional by default. Bidirectional (two screens, two cameras) when you need interactive MCP without a wire.

Outbound (adapter): --qr-output / --qr-input — same tool catalog on an optical wire. Structural air gap: the channel cannot receive instructions the network can rewrite. Streams sourceType: "qr" and TEE air-gap audit share the frame format.

Inbound (Core): CLAWQL_QR_SOURCES registers a QR stream as a first-class source beside MCP, HTTP, GraphQL, gRPC, WebSocket, and CLI. Header-frame discovery yields the usual merged operation index. Per source you get tools like audit_station_qr_read, qr_decode, qr_verify, and qr_search — then the same agent pattern as every other surface:

search("outcome records program PROG-001")
  → finds audit_station_qr_decode

execute(audit_station_qr_decode, {
  schema: "government.OutcomeRecord",
  filters: { programId: { eq: "PROG-001" } },
  verifyChain: true
})
  → structured, Merkle-verified records from an air-gapped stream

qr_decode with schema + filters routes through the same ontology predicate path as memory_recall — in-memory index over decoded frames, identical filter syntax and result shape. Optional peer dependency on the clawql-web QR provider: if it is not installed, CLAWQL_QR_SOURCES warns and is ignored; existing Core users are unaffected.

That is the point of symmetry. An auditor points a camera at an agency display and runs structured execute with no network path into the agency. An election observer verifies ballot commitments and Merkle chains without touching the voting machine’s network. A recorded file:///evidence/session.mp4 replays the same tools post-hoc. The adapter side can emit those frames from an MCP catalog; Core can ingest them as just another source.

Status: spec’d for both directions; not in the 0.6.0 adapter binary yet. The six non-QR adapter surfaces are production path today. Core’s non-QR inbound surfaces are already the live half of the table.

What shipped (and what is next)

VersionWhat landed
0.3.xAny MCP upstream + HTTP (OpenAPI) + GraphQL + gRPC scaffold
0.4.0Renamed from mcp-openapi-gateway to mcp-api-adapter
0.5.0MCP /mcp + CLI gen-cli
0.5.1gRPC → /mcp content normalization for MCP SDK clients
0.6.0WebSocket /ws + Protocol Fabric loop smoke
NextQR — seventh surface both ways: adapter optical I/O + Core CLAWQL_QR_SOURCES

Six outbound surfaces are live in-repo today ([email protected]; npm publish on its own cadence). QR is the draft that completes the table past the network.

When to use the adapter

Write your own when: you need significant custom auth logic, or the adapter’s generality works against you.

Use the adapter when: you have a working MCP server and you want to expose it to clients that do not speak MCP — or cannot use a network — without writing glue per consumer type.

Use Core when: you need the inverse — many upstream API shapes collapsed into one MCP tool index for agents.

The five-minute path to verifying the shipped outbound surfaces:

npx mcp-api-adapter --stdio -- your-mcp-server
# Open http://127.0.0.1:8090/docs
# Try POST /your_tool_name with the right body
# Open http://127.0.0.1:8090/graphiql and run a mutation
# Point an IDE at http://127.0.0.1:8090/mcp
# grpcurl -plaintext 127.0.0.1:50051 list
# Connect a WS client to ws://127.0.0.1:8090/ws

Further reading

About the author

Daniel Smith builds ClawQL, an agent operating system for token-efficient discovery and execution over APIs — with observability, hardened tool boundaries, and production routing for LLM workloads. He writes here about the systems problems behind shipping agents.