UNPKG

@tanstack/ai-mcp

Version:

Host-side Model Context Protocol client for TanStack AI: discover and run MCP server tools, resources, and prompts in any adapter's chat() loop, with generated end-to-end types.

132 lines (131 loc) 4.52 kB
import { DuplicateToolNameError, MCPConnectionError, MCPTaskRequiredToolError, MCPToolNotFoundError } from "./errors.js"; import { makeMcpExecute, requiresTaskExecution, toServerTools, toolMcpMetadata } from "./tools.js"; import { isTransportInstance, resolveTransport } from "./transport.js"; import { Client } from "@modelcontextprotocol/sdk/client/index.js"; //#region src/client.ts var MCPClientImpl = class { capabilities = {}; #client; #closed = false; prefix; #transport; #clientOptions; constructor(prefix, name = "tanstack-ai-mcp", version = "0.0.1", transport, clientOptions) { this.prefix = prefix; this.#transport = transport; this.#clientOptions = clientOptions; this.#client = new Client({ name, version }, clientOptions); } getInfo() { return { transport: this.#transport, prefix: this.prefix, ...this.#clientOptions ? { clientOptions: this.#clientOptions } : {} }; } async connect(transport) { try { await this.#client.connect(transport); this.capabilities = this.#client.getServerCapabilities() ?? {}; } catch (err) { throw new MCPConnectionError("Failed to connect to MCP server", err); } } async tools(defsOrOptions, maybeOptions = {}) { if (this.#closed) throw new MCPConnectionError("MCP client is closed"); const isDefs = Array.isArray(defsOrOptions); const options = isDefs ? maybeOptions : defsOrOptions ?? {}; let tools; if (isDefs) { const available = new Map((await this.#client.listTools()).tools.map((t) => [t.name, t])); tools = defsOrOptions.map((def) => { const serverTool = available.get(def.name); if (!serverTool) throw new MCPToolNotFoundError(def.name); if (requiresTaskExecution(serverTool)) throw new MCPTaskRequiredToolError(def.name); const bound = def.server(makeMcpExecute(this.#client, def.name, Boolean(def.outputSchema))); const existingMcp = bound.metadata?.mcp; const mcpBase = existingMcp !== null && typeof existingMcp === "object" ? existingMcp : {}; return { ...bound, ...this.prefix ? { name: `${this.prefix}_${def.name}` } : {}, ...options.lazy ? { lazy: true } : {}, metadata: { ...bound.metadata, mcp: { ...mcpBase, ...toolMcpMetadata(serverTool, this.prefix) } } }; }); } else { const defs = (await this.#client.listTools()).tools; tools = toServerTools(this.#client, defs, { prefix: this.prefix, lazy: options.lazy }); } const seen = /* @__PURE__ */ new Set(); for (const t of tools) { if (seen.has(t.name)) throw new DuplicateToolNameError(t.name); seen.add(t.name); } return tools; } async resources() { if (this.#closed) throw new MCPConnectionError("MCP client is closed"); return (await this.#client.listResources()).resources; } async readResource(uri) { if (this.#closed) throw new MCPConnectionError("MCP client is closed"); return this.#client.readResource({ uri }); } async resourceTemplates() { if (this.#closed) throw new MCPConnectionError("MCP client is closed"); return (await this.#client.listResourceTemplates()).resourceTemplates; } async prompts() { if (this.#closed) throw new MCPConnectionError("MCP client is closed"); return (await this.#client.listPrompts()).prompts; } async getPrompt(name, args) { if (this.#closed) throw new MCPConnectionError("MCP client is closed"); return this.#client.getPrompt({ name, arguments: args }); } async callTool(name, args) { if (this.#closed) throw new MCPConnectionError("MCP client is closed"); return this.#client.callTool({ name, arguments: args ?? {} }); } async close() { if (this.#closed) return; this.#closed = true; await this.#client.close(); } async [Symbol.asyncDispose]() { await this.close(); } }; async function createMCPClient(options) { const transport = await resolveTransport(options.transport); const impl = new MCPClientImpl(options.prefix, options.name, options.version, isTransportInstance(options.transport) ? void 0 : options.transport, options.clientOptions); await impl.connect(transport); return impl; } /** Test-only: connect directly from a transport instance (skips resolveTransport). */ async function createMCPClientFromTransport(transport, prefix, clientOptions) { const impl = new MCPClientImpl(prefix, void 0, void 0, void 0, clientOptions); await impl.connect(transport); return impl; } //#endregion export { createMCPClient, createMCPClientFromTransport }; //# sourceMappingURL=client.js.map