UNPKG

@toolbox-sdk/core

Version:
285 lines 18.1 kB
// Copyright 2025 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) { if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter"); if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it"); return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver); }; var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) { if (kind === "m") throw new TypeError("Private method is not writable"); if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter"); if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it"); return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value; }; var _ToolboxClient_instances, _ToolboxClient_transport, _ToolboxClient_clientHeaders, _ToolboxClient_session, _ToolboxClient_baseUrl, _ToolboxClient_supportedProtocols, _ToolboxClient_createTransport, _ToolboxClient_createTransportWithProtocols, _ToolboxClient_resolveClientHeaders, _ToolboxClient_createToolInstance, _ToolboxClient_executeWithFallback; import { ToolboxTool } from './tool.js'; import { createZodSchemaFromParams, Protocol, getSupportedMcpVersions, } from './protocol.js'; import { McpHttpTransportV20241105 } from './mcp/v20241105/mcp.js'; import { McpHttpTransportV20250618 } from './mcp/v20250618/mcp.js'; import { McpHttpTransportV20250326 } from './mcp/v20250326/mcp.js'; import { McpHttpTransportV20251125 } from './mcp/v20251125/mcp.js'; import { McpHttpTransportV20260728 } from './mcp/v20260728/mcp.js'; import { ProtocolNegotiationError } from './errorUtils.js'; import { identifyAuthRequirements, resolveValue, warnIfHttpAndHeaders, } from './utils.js'; /** * An asynchronous client for interacting with a Toolbox service. */ class ToolboxClient { /** * The negotiated protocol version currently in use. */ get protocolVersion() { return __classPrivateFieldGet(this, _ToolboxClient_transport, "f").protocolVersion; } /** * Initializes the ToolboxClient. * @param {string} url - The base URL for the Toolbox service API (e.g., "http://localhost:5000"). * @param {AxiosInstance} [session] - Optional Axios instance for making HTTP * requests. If not provided, a new one will be created. * @param {ClientHeadersConfig} [clientHeaders] - Optional initial headers to * be included in each request. * @param {string} [clientName] - Optional name of the client package. * @param {string} [clientVersion] - Optional version of the client package. */ constructor(url, session, clientHeaders, protocol = Protocol.MCP, clientName, clientVersion) { _ToolboxClient_instances.add(this); _ToolboxClient_transport.set(this, void 0); _ToolboxClient_clientHeaders.set(this, void 0); _ToolboxClient_session.set(this, void 0); _ToolboxClient_baseUrl.set(this, void 0); _ToolboxClient_supportedProtocols.set(this, void 0); __classPrivateFieldSet(this, _ToolboxClient_baseUrl, url, "f"); __classPrivateFieldSet(this, _ToolboxClient_clientHeaders, clientHeaders || {}, "f"); __classPrivateFieldSet(this, _ToolboxClient_session, session || undefined, "f"); warnIfHttpAndHeaders(url, __classPrivateFieldGet(this, _ToolboxClient_clientHeaders, "f")); let initialProtocol; if (Array.isArray(protocol)) { if (protocol.length === 0) { throw new Error('Protocol array cannot be empty'); } const globalSupported = getSupportedMcpVersions(); for (const p of protocol) { if (!globalSupported.includes(p)) { throw new Error(`Invalid protocol version '${p}'. Must be one of: ${globalSupported.join(', ')}`); } } const requestedSet = new Set(protocol); const sorted = globalSupported.filter(globalVer => requestedSet.has(globalVer)); if (sorted.length === 0) { throw new Error('None of the provided protocols are supported'); } __classPrivateFieldSet(this, _ToolboxClient_supportedProtocols, sorted, "f"); initialProtocol = sorted[0]; // Start with the highest requested version } else { initialProtocol = protocol; const globalSupported = getSupportedMcpVersions(); if (!globalSupported.includes(initialProtocol)) { throw new Error(`Invalid protocol version '${initialProtocol}'`); } __classPrivateFieldSet(this, _ToolboxClient_supportedProtocols, globalSupported, "f"); } __classPrivateFieldSet(this, _ToolboxClient_transport, __classPrivateFieldGet(this, _ToolboxClient_instances, "m", _ToolboxClient_createTransportWithProtocols).call(this, url, session || undefined, initialProtocol, clientName, clientVersion), "f"); } /** * Asynchronously loads a tool from the server. * Retrieves the schema for the specified tool from the Toolbox server and * returns a callable (`ToolboxTool`) that can be used to invoke the * tool remotely. * * @param {string} name - The unique name or identifier of the tool to load. * @param {AuthTokenGetters | null} [authTokenGetters] - Optional map of auth service names to token getters. * @param {BoundParams | null} [boundParams] - Optional parameters to pre-bind to the tool. * @returns {Promise<ToolboxTool>} A promise that resolves * to a ToolboxTool function, ready for execution. * @throws {Error} If the tool is not found in the manifest, the manifest structure is invalid, * or if there's an error fetching data from the API. */ async loadTool(name, authTokenGetters = {}, boundParams = {}) { warnIfHttpAndHeaders(__classPrivateFieldGet(this, _ToolboxClient_transport, "f").baseUrl, authTokenGetters); const headers = await __classPrivateFieldGet(this, _ToolboxClient_instances, "m", _ToolboxClient_resolveClientHeaders).call(this); const manifest = await __classPrivateFieldGet(this, _ToolboxClient_instances, "m", _ToolboxClient_executeWithFallback).call(this, () => __classPrivateFieldGet(this, _ToolboxClient_transport, "f").toolGet(name, headers)); if (manifest.tools && Object.prototype.hasOwnProperty.call(manifest.tools, name)) { const specificToolSchema = manifest.tools[name]; const { tool, usedAuthKeys, usedBoundKeys } = __classPrivateFieldGet(this, _ToolboxClient_instances, "m", _ToolboxClient_createToolInstance).call(this, name, specificToolSchema, authTokenGetters || undefined, boundParams || {}); const providedAuthKeys = new Set(authTokenGetters ? Object.keys(authTokenGetters) : []); const providedBoundKeys = new Set(boundParams ? Object.keys(boundParams) : []); const unusedAuth = [...providedAuthKeys].filter(key => !usedAuthKeys.has(key)); const unusedBound = [...providedBoundKeys].filter(key => !usedBoundKeys.has(key)); const errorMessages = []; if (unusedAuth.length > 0) { errorMessages.push(`unused auth tokens: ${unusedAuth.join(', ')}`); } if (unusedBound.length > 0) { errorMessages.push(`unused bound parameters: ${unusedBound.join(', ')}`); } if (errorMessages.length > 0) { throw new Error(`Validation failed for tool '${name}': ${errorMessages.join('; ')}.`); } return tool; } else { throw new Error(`Tool "${name}" not found in manifest from ${__classPrivateFieldGet(this, _ToolboxClient_transport, "f").baseUrl}/api/tool/${name}.`); } } /** * Asynchronously fetches a toolset and loads all tools defined within it. * * @param {string | null} [name] - Name of the toolset to load. If null or undefined, loads the default toolset. * @param {AuthTokenGetters | null} [authTokenGetters] - Optional map of auth service names to token getters. * @param {BoundParams | null} [boundParams] - Optional parameters to pre-bind to the tools in the toolset. * @param {boolean} [strict=false] - If true, throws an error if any provided auth token or bound param is not used by at least one tool. * @returns {Promise<ToolboxTool[]>} A promise that resolves * to a list of ToolboxTool functions, ready for execution. * @throws {Error} If the manifest structure is invalid or if there's an error fetching data from the API. */ async loadToolset(name, authTokenGetters = {}, boundParams = {}, strict = false) { warnIfHttpAndHeaders(__classPrivateFieldGet(this, _ToolboxClient_transport, "f").baseUrl, authTokenGetters); const toolsetName = name || ''; const headers = await __classPrivateFieldGet(this, _ToolboxClient_instances, "m", _ToolboxClient_resolveClientHeaders).call(this); const manifest = await __classPrivateFieldGet(this, _ToolboxClient_instances, "m", _ToolboxClient_executeWithFallback).call(this, () => __classPrivateFieldGet(this, _ToolboxClient_transport, "f").toolsList(toolsetName, headers)); const tools = []; const overallUsedAuthKeys = new Set(); const overallUsedBoundParams = new Set(); const providedAuthKeys = new Set(authTokenGetters ? Object.keys(authTokenGetters) : []); const providedBoundKeys = new Set(boundParams ? Object.keys(boundParams) : []); for (const [toolName, toolSchema] of Object.entries(manifest.tools)) { const { tool, usedAuthKeys, usedBoundKeys } = __classPrivateFieldGet(this, _ToolboxClient_instances, "m", _ToolboxClient_createToolInstance).call(this, toolName, toolSchema, authTokenGetters || {}, boundParams || {}); tools.push(tool); if (strict) { const unusedAuth = [...providedAuthKeys].filter(key => !usedAuthKeys.has(key)); const unusedBound = [...providedBoundKeys].filter(key => !usedBoundKeys.has(key)); const errorMessages = []; if (unusedAuth.length > 0) { errorMessages.push(`unused auth tokens: ${unusedAuth.join(', ')}`); } if (unusedBound.length > 0) { errorMessages.push(`unused bound parameters: ${unusedBound.join(', ')}`); } if (errorMessages.length > 0) { throw new Error(`Validation failed for tool '${toolName}': ${errorMessages.join('; ')}.`); } } else { usedAuthKeys.forEach(key => overallUsedAuthKeys.add(key)); usedBoundKeys.forEach(key => overallUsedBoundParams.add(key)); } } if (!strict) { const unusedAuth = [...providedAuthKeys].filter(key => !overallUsedAuthKeys.has(key)); const unusedBound = [...providedBoundKeys].filter(key => !overallUsedBoundParams.has(key)); const errorMessages = []; if (unusedAuth.length > 0) { errorMessages.push(`unused auth tokens could not be applied to any tool: ${unusedAuth.join(', ')}`); } if (unusedBound.length > 0) { errorMessages.push(`unused bound parameters could not be applied to any tool: ${unusedBound.join(', ')}`); } if (errorMessages.length > 0) { throw new Error(`Validation failed for toolset '${name || 'default'}': ${errorMessages.join('; ')}.`); } } return tools; } } _ToolboxClient_transport = new WeakMap(), _ToolboxClient_clientHeaders = new WeakMap(), _ToolboxClient_session = new WeakMap(), _ToolboxClient_baseUrl = new WeakMap(), _ToolboxClient_supportedProtocols = new WeakMap(), _ToolboxClient_instances = new WeakSet(), _ToolboxClient_createTransport = function _ToolboxClient_createTransport(url, session, protocol, clientName, clientVersion) { switch (protocol) { case Protocol.MCP_v20241105: return new McpHttpTransportV20241105(url, session, protocol, clientName, clientVersion); case Protocol.MCP_v20250326: return new McpHttpTransportV20250326(url, session, protocol, clientName, clientVersion); case Protocol.MCP_v20250618: return new McpHttpTransportV20250618(url, session, protocol, clientName, clientVersion); case Protocol.MCP_v20251125: return new McpHttpTransportV20251125(url, session, protocol, clientName, clientVersion); case Protocol.MCP_v20260728: return new McpHttpTransportV20260728(url, session, protocol, clientName, clientVersion); default: throw new Error(`Unsupported MCP protocol version: ${protocol}`); } }, _ToolboxClient_createTransportWithProtocols = function _ToolboxClient_createTransportWithProtocols(url, session, protocol, clientName, clientVersion) { const transport = __classPrivateFieldGet(this, _ToolboxClient_instances, "m", _ToolboxClient_createTransport).call(this, url, session, protocol, clientName, clientVersion); if (__classPrivateFieldGet(this, _ToolboxClient_supportedProtocols, "f")) { transport.supportedProtocols = __classPrivateFieldGet(this, _ToolboxClient_supportedProtocols, "f"); } return transport; }, _ToolboxClient_resolveClientHeaders = /** * Resolves client headers from their provider functions. * @returns {Promise<Record<string, string>>} A promise that resolves to the resolved headers. */ async function _ToolboxClient_resolveClientHeaders() { const resolvedEntries = await Promise.all(Object.entries(__classPrivateFieldGet(this, _ToolboxClient_clientHeaders, "f")).map(async ([key, value]) => { const resolved = await resolveValue(value); return [key, String(resolved)]; })); return Object.fromEntries(resolvedEntries); }, _ToolboxClient_createToolInstance = function _ToolboxClient_createToolInstance(toolName, toolSchema, authTokenGetters = {}, boundParams = {}) { const params = []; const authParams = {}; const currBoundParams = {}; for (const p of toolSchema.parameters) { if (p.authSources && p.authSources.length > 0) { authParams[p.name] = p.authSources; } else if (boundParams && p.name in boundParams) { currBoundParams[p.name] = boundParams[p.name]; } else { params.push(p); } } const [remainingAuthnParams, remainingAuthzTokens, usedAuthKeys] = identifyAuthRequirements(authParams, toolSchema.authRequired || [], authTokenGetters ? Object.keys(authTokenGetters) : []); const paramZodSchema = createZodSchemaFromParams(params); const tool = ToolboxTool(__classPrivateFieldGet(this, _ToolboxClient_transport, "f"), toolName, toolSchema.description, paramZodSchema, authTokenGetters, remainingAuthnParams, remainingAuthzTokens, currBoundParams, __classPrivateFieldGet(this, _ToolboxClient_clientHeaders, "f")); const usedBoundKeys = new Set(Object.keys(currBoundParams)); return { tool, usedAuthKeys, usedBoundKeys }; }, _ToolboxClient_executeWithFallback = async function _ToolboxClient_executeWithFallback(action) { while (true) { try { return await action(); } catch (e) { if (e instanceof ProtocolNegotiationError) { const serverVersion = e.fallbackVersion; let mutuallySupported = null; if (__classPrivateFieldGet(this, _ToolboxClient_supportedProtocols, "f").includes(serverVersion)) { mutuallySupported = [serverVersion]; } else { const allVersions = getSupportedMcpVersions(); if (allVersions.includes(serverVersion)) { const idx = allVersions.indexOf(serverVersion); const serverSupported = allVersions.slice(idx); mutuallySupported = __classPrivateFieldGet(this, _ToolboxClient_supportedProtocols, "f").filter(v => serverSupported.includes(v)); } } if (!mutuallySupported || mutuallySupported.length === 0) { throw new Error('No mutually supported protocol version'); } const fallbackProtocol = mutuallySupported[0]; if (fallbackProtocol === __classPrivateFieldGet(this, _ToolboxClient_transport, "f").protocolVersion) { throw e; } __classPrivateFieldSet(this, _ToolboxClient_transport, __classPrivateFieldGet(this, _ToolboxClient_instances, "m", _ToolboxClient_createTransportWithProtocols).call(this, __classPrivateFieldGet(this, _ToolboxClient_baseUrl, "f"), __classPrivateFieldGet(this, _ToolboxClient_session, "f"), fallbackProtocol), "f"); } else { throw e; } } } }; export { ToolboxClient }; //# sourceMappingURL=client.js.map