agentfootprint
Version:
The explainable agent framework — backtrack a wrong answer to the exact context that caused it (evidence, not guesses). Built on footprintjs.
87 lines (86 loc) • 4.34 kB
TypeScript
/**
* mcpServe — expose your agentfootprint tools AS an MCP server.
*
* const handle = await mcpServe([lookupTool, refundTool], {
* name: 'support-desk',
* version: '1.0.0',
* });
*
* // …later
* await handle.close();
*
* Over HTTP the handle also reports the socket it bound, so `port: 0`
* ("any free port") is usable from the outside:
*
* const handle = await mcpServe(tools, {
* transport: { transport: 'http', port: 0, host: '127.0.0.1' },
* });
* connect(`http://127.0.0.1:${handle.port}/mcp`);
*
* `mcpClient` pulls someone else's tools in. This pushes yours out, so
* the tool you already wrote, tested and governed can be called by any
* MCP client — a desktop host, another team's agent, an IDE — without
* being rewritten against a second tool interface.
*
* Pattern: Adapter (GoF), pointed the other way. `mcpClient` translates
* MCP → `Tool`; this translates `Tool` → MCP. Same SDK, same
* lazy-require discipline, mirrored lifecycle (`close()`).
*
* ── The one promise worth stating plainly ────────────────────────────
* A served tool is THE SAME OBJECT you passed in. `mcpServe` holds your
* `Tool` by reference and calls `tool.execute(args, ctx)` — it never
* copies the schema and re-implements the body, never unwraps a
* decorator, never reaches past a wrapper to an inner tool. So whatever
* governance you composed around that tool — a permission check inside
* `execute`, a redaction step, an audit hook — is still the thing that
* runs when a remote client calls it. Serving is not a second door into
* your tool; it is the same door with a longer corridor.
*
* Two demands the library CANNOT keep over MCP are refused at
* construction rather than silently dropped:
* - `checkIn` (human consent before the tool runs) needs a pause
* channel that a request/response protocol does not have.
* - `needs` (declare-and-push credentials) needs a provider; without
* one the tool would run with `ctx.credential` undefined.
*
* ── Declarations travel; execution does not (9.71.0) ─────────────────
* A served tool's DECLARATIONS — `argumentsFrom`, `resultKind`, `owner`,
* `resultClass`, `resultCeiling` — are written into MCP's own `_meta` bag
* under one namespaced key, so an agentfootprint client arms the checks and
* rails that read them exactly as it would for a local `defineTool`. The
* execution-side fields never go: see `toolExtras.ts` for the bar and the
* reasoning. A tool declaring none of the five lists exactly as before.
*
* ── Schemas ──────────────────────────────────────────────────────────
* Tools already carry JSON Schema in `schema.inputSchema`, and MCP's
* `tools/list` wants JSON Schema, so the mapping is the identity
* function. That is also why this builds on the SDK's low-level
* `Server` rather than its `McpServer` convenience wrapper — the latter
* takes zod schemas, which would mean converting a JSON Schema you
* already have into zod (a new dependency) to convert it straight back.
*/
import type { Tool } from '../tools/tools.js';
import type { McpServeHandle, McpServeOptions } from './types.js';
/**
* Serve `tools` over MCP. Resolves once the transport is connected (for
* `http`, once the socket is listening), so holding the handle means the
* server is live.
*
* @throws at construction when `tools` is empty, when two tools share a
* name, when a tool declares `checkIn`, or when a tool declares `needs`
* without `opts.credentials` — see the module header. Also throws when
* `@modelcontextprotocol/sdk` is not installed.
*/
export declare function mcpServe(tools: readonly Tool[], opts?: McpServeOptions): Promise<McpServeHandle>;
/**
* Sentinels handed to an injected `_server` in place of the SDK's zod
* schemas. Exported so a test can tell the two handlers apart.
* @internal
*/
export declare const LIST_TOOLS_SENTINEL: Readonly<{
method: "tools/list";
}>;
/** @internal @see LIST_TOOLS_SENTINEL */
export declare const CALL_TOOL_SENTINEL: Readonly<{
method: "tools/call";
}>;