UNPKG

agentfootprint

Version:

The explainable agent framework — backtrack a wrong answer to the exact context that caused it (evidence, not guesses). Built on footprintjs.

130 lines • 5.4 kB
"use strict"; /** * mockMcpClient — in-memory MCP client for development and tests. * * const slack = mockMcpClient({ * tools: [ * { * name: 'send_message', * description: 'Post a message to a channel', * inputSchema: { type: 'object' }, * handler: async ({ text }) => `Posted: ${text}`, * }, * ], * }); * * const agent = Agent.create({ provider: mock({ reply: 'ok' }) }) * .tools(await slack.tools()) * .build(); * * Pattern: Adapter (GoF) — produces an `McpClient` with the same shape * as `mcpClient(opts)` but driven by an in-memory tool table * instead of the MCP SDK + transport. Drop-in for development: * start with `mockMcpClient`, swap to `mcpClient` once the * real server is ready. * * Why public: `mcpClient`'s `_client` injection is `@internal` because * the SDK shape isn't a stable public surface. `mockMcpClient` exposes * a curated tool-handler shape that's tied to OUR Tool contract instead * — stable, documented, and the right level of abstraction for * mock-first development. */ Object.defineProperty(exports, "__esModule", { value: true }); exports.mockMcpClient = void 0; const toolExtras_js_1 = require("./toolExtras.js"); const terminate_js_1 = require("./terminate.js"); const toolResult_js_1 = require("./toolResult.js"); /** * Build an in-memory `McpClient`. Useful when you want to develop * against MCP semantics without spawning subprocesses, hitting the * network, or installing `@modelcontextprotocol/sdk`. Same `McpClient` * shape as `mcpClient(opts)` — code that consumes one accepts the other. */ function mockMcpClient(options) { const name = options.name ?? 'mock-mcp'; const resultMode = (0, toolResult_js_1.resultModeOf)(options.resultMode, name); const toolMap = new Map(options.tools.map((t) => [t.name, t])); let cache = null; let closed = false; let ended; const ensureOpen = (op) => { if (closed) { throw new Error(`mockMcpClient[${name}].${op}() called after close(). Construct a new client to reuse.`); } }; const buildTools = () => options.tools.map((mcp) => wrapMockTool(name, toolMap, mcp, resultMode)); return { name, async tools() { ensureOpen('tools'); if (!cache) cache = buildTools(); return cache; }, async refresh() { ensureOpen('refresh'); cache = buildTools(); return cache; }, async close() { if (closed) return; closed = true; cache = null; }, // In memory, no session id is ever issued: nothing remote to end, so the // ending is resolved, and the client is closed (`terminate.ts`). async terminate() { if (ended !== undefined) return ended; ensureOpen('terminate'); closed = true; cache = null; ended = (0, terminate_js_1.outcomeWithout)('no-session'); return ended; }, }; } exports.mockMcpClient = mockMcpClient; function wrapMockTool(serverName, toolMap, mcp, resultMode) { const tool = { schema: { name: mcp.name, description: mcp.description ?? `Mock MCP tool: ${mcp.name}`, inputSchema: mcp.inputSchema, }, // Same provenance a real `mcpClient` stamps, for the same reason: code that // governs by server name has to be testable against the mock, or the swap // from mock to real server changes what the policy sees. source: serverName, // …and the same declarations, through the same reader (9.71.0). A rail // that arms on a remotely-declared `argumentsFrom` must arm here too, or // the mock proves nothing about the server it stands in for. ...(0, toolExtras_js_1.readToolExtras)(mcp._meta, { server: serverName, tool: mcp.name, inputSchema: mcp.inputSchema, }), execute: async (args, ctx) => { const argsObj = args !== null && typeof args === 'object' && !Array.isArray(args) ? args : {}; // The same request `_meta` the real client sends (the time layer's `ctx.time`), as data. const request = ctx?.time !== undefined ? { _meta: structuredClone((0, toolExtras_js_1.callMetaOf)(ctx.time)) } : {}; // Look up by name at call time so mid-test handler swaps via a // mutable Map could be supported later. For now `toolMap` is // built once at factory time. const handler = toolMap.get(mcp.name)?.handler; try { const result = handler ? await handler(argsObj, request) : '[mock result]'; return (0, toolResult_js_1.readToolResult)(typeof result === 'string' ? { content: [{ type: 'text', text: result }] } : result, mcp.name, serverName, resultMode); } catch (err) { const msg = err instanceof Error ? err.message : String(err); throw new Error(`Mock MCP tool '${mcp.name}' (server '${serverName}') threw: ${msg}`); } }, }; return tool; } //# sourceMappingURL=mockMcpClient.js.map