Middleware lets a compatible agent harness reach tools through a common MCP endpoint and send lifecycle events to policy hooks. It is useful when you want to connect an existing harness while keeping its own model loop.
Connect an upstream tool server
Start with an HTTP MCP server you already run. Replace the example URL with that server's address. Middleware also supports stdio upstreams; this example uses HTTP to keep the connection visible.
export CONDENSATE_HOST=127.0.0.1
export CONDENSATE_MCP_UPSTREAMS='[{"name":"tools","url":"http://127.0.0.1:9000/mcp","auth":"none"}]'
export CONDENSATE_LOCAL_PRINCIPAL_ID=local-dev
export CONDENSATE_LOCAL_API_KEY="$(openssl rand -hex 32)"
bun apps/middleware/backend/index.ts
Run this from the repository root. The backend's default port is 4787. The key authenticates your client to middleware. In this example, auth: "none" describes the connection from middleware to the upstream server; choose the appropriate authentication for your own upstream.
Inspect the tool catalog
In another terminal, set CONDENSATE_LOCAL_API_KEY to the same key used by the backend. Then request the catalog:
curl --fail-with-body http://127.0.0.1:4787/mcp \
-H "x-condensate-key: $CONDENSATE_LOCAL_API_KEY" \
-H 'content-type: application/json' \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}'
A successful JSON-RPC response contains result.tools. Aggregated tool names take the form <upstream>__<tool>, so a read tool on the tools upstream becomes tools__read.
Configure your harness's HTTP MCP client with:
| Setting | Value |
|---|---|
| URL | http://127.0.0.1:4787/mcp |
| Header | x-condensate-key: YOUR_LOCAL_KEY |
The client configuration format depends on the harness. Use its supported HTTP MCP setup rather than copying one client's configuration into another.
Add lifecycle hooks
MCP supplies tool access. A hook integration separately connects the harness's events to middleware policy. The hook shim translates the event, receives a verdict, and returns it using the harness's own contract.
Required policy checks fail closed; optional enhancements can fail open according to their hook implementation. The TypeSafe-backed steering judge participates in this policy path as a classifier. The task's generative model and Condensate's model router are separate components.
Batch tool work with code mode
Code mode provides tool-catalog search, generated TypeScript signatures, and a QuickJS execution sandbox. A program can call several tools and return a concise result, avoiding a separate model round trip for every intermediate value.
Without batching:
model → tool A → model → tool B → model → tool C → model
With a code-mode program:
model → program calls A, B, C → selected result → model
This is an execution sketch, not code to paste into the sandbox. Code mode is off by default and gated by CONDENSATE_CODE_MODE and CONDENSATE_CODE_MODE_PRINCIPALS. Calls through its call(tool, args) bridge pass through normal MCP policy and accounting. Native SDK namespaces have their own paths and caps.
Choose the right connection
Use middleware to add a tool and policy layer to an existing harness. Use the runtime SDK when your application needs durable Condensate threads, event recovery, and task controls.