kolu
Docs

@kolu/surface-mcp reference

Re-expose any @kolu/surface as an MCP server, so a coding agent drives it with structured tool calls. The adapter owns the generic parts — the resources/subscribe lifecycle, the zod → JSON-Schema bridge, stdio discipline — and leaves the consumer in control of what is exposed.

serveSurfaceAsMcp

const { server, close } = await serveSurfaceAsMcp({
  surface,
  client: () => client,
  expose: {
    /* … */
  },
});

serveSurfaceAsMcp(opts): Promise<{ server, close }>, imported from the root @kolu/surface-mcp subpath.

Option Meaning
surface the surface spec being projected
client a factory () => client for a live implementation
expose the default-deny allowlist (see below)
tools? hand-authored bespoke tools
serverInfo? MCP server identity
transport? defaults to StdioServerTransport; inject an in-memory half for tests

The projection

expose is default-deny: nothing crosses to the host until named, and every key is checked against the surface spec at boot.

Surface member Exposed as URI / wire name
cell resource surface://cells/<k>
collection resource (+ template) surface://collections/<k> · …/<k>/{id}
stream resource surface://streams/<k>
event resource surface://events/<k>
procedure "<ns>.<verb>" tool <ns>_<verb> (. is illegal in a tool name)

Resources are readable and subscribable; procedures become callable tools.

Two shapes

  • Serve freshclient is a directLink over an in-process implementSurface; the MCP server is the surface’s backend.
  • Bridge a live surfaceclient dials an already-running served surface over a socket or ssh stdio; the MCP server is a face on an existing server (odu’s odu mcp dials .ci/odu.sock).

Bespoke tools and curation

  • tools: — hand-authored, call-shaped capabilities (spawn-and-wait, blocking polls). Each is { description, input: z.object(...), mutates, handler }; the handler(args, client, signal) composes over the live client and still rides the JSON-Schema bridge and lifecycle.
  • projectSurface (from @kolu/surface/project) is the cleanest curation: project the live surface into an observer-safe second surface — drop dangerous procedures, bound logs, derive verdicts — and serve that.

Other exports

Export Role
toInputSchema(schema?) zod → JSON Schema (draft 2020-12), $ref-dereferenced, top-level-object enforced
resolveExpose(spec, expose) the default-deny resolver → { resources, resourceTemplates, tools }
ExposeMap · BespokeTool · ToolResult · ServeSurfaceAsMcpOptions types

The step-by-step is in How to expose a surface to agents.