Skip to content

Repository files navigation

@bounded-systems/verbspec-mcp

Turn any @bounded-systems/verbspec verb Registry into a real MCP server.

verbspec authors each check/generator once as a typed VerbSpec (Zod input/output + a business-meaning summary) and projects it to CLI, Anthropic, OpenAPI, and OpenRPC surfaces — and to MCP tool descriptors. What it deliberately does not ship is a server: the MCP wire protocol, the transport, and result-wrapping. This package is that missing layer, built on the official @modelcontextprotocol/sdk. Hand it a registry, get a server:

import { serveStdio } from "@bounded-systems/verbspec-mcp";
import { registry } from "./registry";

await serveStdio(registry, { name: "spd", version: "0.1.0" });

Every verb becomes an MCP tool whose inputSchema/outputSchema are the verb's own Zod schemas (zero drift), and whose handler runs the verb. Object- shaped outputs are returned as MCP structuredContent (and advertised as an outputSchema); other outputs degrade to text. A verb that throws surfaces as an isError tool result, not a transport crash.

Where this sits

One verb spec, projected everywhere — and every MCP server in the org is the same shape: verbspec verbs → this base → an optional topic layer.

verbspec                author each verb once (Zod input/output + a summary)
  └── verbspec-mcp       the base — any verb Registry → MCP tools   (this package)
        └── <topic>-mcp  optional, domain-specific layers on top, e.g.:
              • verified static-site / website builds — adds read-only,
                Sigstore-verified static responses + a resource catalog
                (today: @bounded-systems/static-mcp)

verbspec-mcp is the only mandatory piece: it just runs the verbs (checks, generators, mutations — whatever they do). Anything domain-specific is an option stacked on top — still nothing but verbspec verbs plus this base plus its own middleware. "Serve a verified website build" is one such topic, not the core; use the base alone to expose any live verb registry as tools.

Install

Published to both registries so either ecosystem can consume it:

deno add jsr:@bounded-systems/verbspec-mcp        # Deno / JSR-native
npm install @bounded-systems/verbspec-mcp         # Node / npm (+ Bun)

@modelcontextprotocol/sdk is a normal dependency; @bounded-systems/verbspec is a peer — the server shares your project's verbspec instance (and its zod), so there's no dual-package split.

API

interface McpServerOptions {
  name?: string;                           // handshake server name (default "verbspec-mcp")
  version?: string;                        // handshake version     (default "0.0.0")
  instructions?: string;                   // handshake instructions (how to use the tools)
  filter?: (verb: AnyVerbSpec) => boolean; // restrict which verbs are exposed
  deps?: () => unknown;                    // inject a shared deps slice into every verb's run
  mapResult?: (out, verb, args) => ToolResult | Promise<ToolResult>; // shape the tool result
  dispatch?: boolean;                      // add a discover/dispatch pair covering the WHOLE registry
}

// Build the configured SDK server WITHOUT a transport — attach your own (e.g. HTTP),
// or register extra surfaces (resources) on the returned server.
function buildMcpServer(registry: Registry, opts?: McpServerOptions): McpServer;

// buildMcpServer + connectStdio. The one-liner most servers want.
function serveStdio(registry: Registry, opts?: McpServerOptions): Promise<void>;

// Connect a server you built yourself to stdio, with stdout hygiene. For servers that
// also register resources and can't use the serveStdio shortcut.
function connectStdio(server: McpServer): Promise<void>;

For an HTTP server, take buildMcpServer(...) and .connect() it to a Streamable HTTP transport from the SDK.

Topic layers: deps + mapResult

A domain-specific server stacks on the base through two seams instead of forking it. deps injects a shared capability slice into every verb's run(input, deps) (dependency injection — one client, or a test mock, for all verbs). mapResult takes control of how a verb's output becomes the tool result (content, structuredContent, _meta, isError). For example, the verified static-site layer threads its verifying client via deps and renders verified bytes + provenance _meta via mapResult — while the base still owns registration, schema validation, and running the verb.

A registry too big to register verb-by-verb: dispatch

Registering every verb directly means every verb's tool schema ships in every request of every session. Fine for a handful of verbs; for hundreds, it degrades a client's tool-selection accuracy and spends context nobody asked to spend. dispatch: true adds a fixed-size alternative — two tools, discover_verbs and dispatch_verb, that together reach the whole registry regardless of filter:

await serveStdio(registry, { name: "spd", version: "0.1.0", dispatch: true });
  • discover_verbs searches by id substring, summary keyword, or actor (the one grouping a VerbSpec already carries) and returns matching ids with their summary and input schema.
  • dispatch_verb runs any verb by id, validating args against that verb's own input schema, and returns the same result shape a directly-registered tool would.

This composes with filter rather than replacing it: register a small hot set directly (as today, unchanged) and turn on dispatch to keep the rest reachable without paying its per-tool context cost. Off by default — every existing behavior, including filter, is unchanged when omitted.

stdout hygiene

serveStdio redirects console.log/info/debug to stderr. The SDK writes JSON-RPC frames to process.stdout, so a verb that logs progress during run() would otherwise interleave and corrupt the stream (in Bun, console.log writes to fd 1 natively — patching process.stdout alone would miss it). Verbs must not write to process.stdout directly; use stderr for diagnostics.

License

MIT

About

Turn any @bounded-systems/verbspec verb Registry into an MCP server (official @modelcontextprotocol/sdk).

Resources

Code of conduct

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages