@tanstack/ai
Version:
Type-safe TypeScript AI SDK for streaming chat, tool calling, agents, structured outputs, and multimodal generation.
81 lines (80 loc) • 2.57 kB
JavaScript
function bindReadResource(tool, source) {
if (!source.readResource) return;
const meta = tool.metadata?.mcp;
if (!meta?.uiResourceUri) return;
meta.readResource = source.readResource.bind(source);
}
class MCPDuplicateToolNameError extends Error {
constructor(toolName) {
super(
`Duplicate MCP tool name "${toolName}" in chat({ mcp.clients }). Set a unique \`prefix\` on one of the MCP clients (or use a pool, which auto-prefixes) to disambiguate.`
);
this.toolName = toolName;
this.name = "MCPDuplicateToolNameError";
}
toolName;
}
class MCPManager {
static from(options) {
return new MCPManager(options);
}
#sources;
#shouldClose;
#lazyTools;
#onDiscoveryError;
constructor(options) {
this.#sources = options?.clients ?? [];
this.#shouldClose = options ? options.connection !== "keep-alive" : false;
this.#lazyTools = options?.lazyTools ?? false;
this.#onDiscoveryError = options?.onDiscoveryError;
}
/**
* Discover + merge tools from all sources. Throws on a fatal discovery error
* (no `onDiscoveryError`, or it re-threw) or a duplicate tool name; in that
* case it first closes any connected sources when the policy is 'close'.
*/
async discover() {
if (this.#sources.length === 0) return [];
try {
const settled = await Promise.allSettled(
this.#sources.map((s) => s.tools({ lazy: this.#lazyTools }))
);
const tools = [];
const zipped = this.#sources.map(
(source, i) => [source, settled[i]]
);
for (const [source, result] of zipped) {
if (result === void 0) continue;
if (result.status === "fulfilled") {
for (const t of result.value) {
bindReadResource(t, source);
tools.push(t);
}
} else if (this.#onDiscoveryError) {
await this.#onDiscoveryError(result.reason, source);
} else {
throw result.reason;
}
}
const seen = /* @__PURE__ */ new Set();
for (const t of tools) {
if (seen.has(t.name)) throw new MCPDuplicateToolNameError(t.name);
seen.add(t.name);
}
return tools;
} catch (err) {
await this.dispose();
throw err;
}
}
/** Close sources iff policy is 'close'. Idempotent; never throws. */
async dispose() {
if (!this.#shouldClose || this.#sources.length === 0) return;
await Promise.allSettled(this.#sources.map((s) => s.close()));
}
}
export {
MCPDuplicateToolNameError,
MCPManager
};
//# sourceMappingURL=manager.js.map