How to expose a surface to agents
An agent (Claude Code, Codex, opencode) drives a surface through the
Model Context Protocol: your cells and streams become readable resources, your
procedures become callable tools. @kolu/surface-mcp does the projection; you
decide what to expose.
1. Install and import
{ "dependencies": { "@kolu/surface-mcp": "workspace:*" } }
import { implementSurface } from "@kolu/surface/server";
import { directLink } from "@kolu/surface/links/direct";
import { serveSurfaceAsMcp } from "@kolu/surface-mcp";
2. Build a client for the surface
Choose one of two shapes:
- Serve fresh when the MCP server is the backend — wrap an in-process
implementSurfacein adirectLink. - Bridge a live surface when a server is already running — dial it over its socket or ssh stdio instead.
const { router } = implementSurface(surface, {
channel: inMemoryChannelByName(),
cells: { load: { store: inMemoryStore(ZERO) } },
collections: {
processes: {
readAll: () => table,
upsert: (pid, proc) => {
table.set(pid, proc);
},
remove: (pid) => {
table.delete(pid);
},
},
},
streams: {
nodeLog: {
source: async function* (nodeId) {
yield { kind: "snapshot", text: `opened ${nodeId}`, done: false };
},
},
},
events: { autosave: {} },
procedures: {
proc: {
kill: async ({ input, ctx }) => {
ctx.collections.processes.remove(input.pid);
return { ok: true };
},
},
},
});
const client = directLink<typeof surface.contract>(router); // serve-fresh
3. Name what to expose
expose is default-deny: an unlisted member never reaches the agent. Map each
key to "resource" (cells, collections, streams, events) or a tool (procedures).
await serveSurfaceAsMcp({
surface,
client: () => client,
expose: {
load: "resource", // cell → readable, subscribable
nodeLog: "resource", // stream → readable, subscribable
"proc.kill": { tool: { mutates: true } }, // procedure → mutating tool
// "proc.configure" omitted → never reaches the agent
},
});
4. Curate a dangerous surface (optional)
If the surface has procedures no agent should reach, do not rely on omission
alone — project a safe view with projectSurface (from @kolu/surface/project): drop
the dangerous procedures, bound the logs, derive read-only verdicts, and expose
that second surface. It is the difference between hiding a control and not
wiring it at all.
5. Point the agent at it
The server speaks stdio by default. Register it in the agent’s .mcp.json and
the tools and resources appear in its next session. For the full option list and
the resource-URI scheme, see the
@kolu/surface-mcp reference.