UNPKG

mcp-use

Version:

A utility library for integrating Model Context Protocol (MCP) with LangChain, Zod, and related tools. Provides helpers for schema conversion, event streaming, and SDK usage.

128 lines (127 loc) 4.39 kB
import { logger } from '../logging.js'; export class ConnectionManager { _readyPromise; _readyResolver; _donePromise; _doneResolver; _exception = null; _connection = null; _task = null; _abortController = null; constructor() { this.reset(); } /** * Start the connection manager and establish a connection. * * @returns The established connection. * @throws If the connection cannot be established. */ async start() { // Reset internal state before starting this.reset(); logger.debug(`Starting ${this.constructor.name}`); // Kick off the background task that manages the connection this._task = this.connectionTask(); // Wait until the connection is ready or an error occurs await this._readyPromise; // If an exception occurred during startup, re‑throw it if (this._exception) { throw this._exception; } if (this._connection === null) { throw new Error('Connection was not established'); } return this._connection; } /** * Stop the connection manager and close the connection. */ async stop() { if (this._task && this._abortController) { logger.debug(`Cancelling ${this.constructor.name} task`); this._abortController.abort(); try { await this._task; } catch (e) { if (e instanceof Error && e.name === 'AbortError') { logger.debug(`${this.constructor.name} task aborted successfully`); } else { logger.warn(`Error stopping ${this.constructor.name} task: ${e}`); } } } // Wait until the connection cleanup has completed await this._donePromise; logger.debug(`${this.constructor.name} task completed`); } /** * Reset all internal state. */ reset() { this._readyPromise = new Promise(res => (this._readyResolver = res)); this._donePromise = new Promise(res => (this._doneResolver = res)); this._exception = null; this._connection = null; this._task = null; this._abortController = new AbortController(); } /** * The background task responsible for establishing and maintaining the * connection until it is cancelled. */ async connectionTask() { logger.debug(`Running ${this.constructor.name} task`); try { // Establish the connection this._connection = await this.establishConnection(); logger.debug(`${this.constructor.name} connected successfully`); // Signal that the connection is ready this._readyResolver(); // Keep the task alive until it is cancelled await this.waitForAbort(); } catch (err) { this._exception = err; logger.error(`Error in ${this.constructor.name} task: ${err}`); // Ensure the ready promise resolves so that start() can handle the error this._readyResolver(); } finally { // Clean up the connection if it was established if (this._connection !== null) { try { await this.closeConnection(this._connection); } catch (closeErr) { logger.warn(`Error closing connection in ${this.constructor.name}: ${closeErr}`); } this._connection = null; } // Signal that cleanup is finished this._doneResolver(); } } /** * Helper that returns a promise which resolves when the abort signal fires. */ async waitForAbort() { return new Promise((_resolve, _reject) => { if (!this._abortController) { return; } const signal = this._abortController.signal; if (signal.aborted) { _resolve(); return; } const onAbort = () => { signal.removeEventListener('abort', onAbort); _resolve(); }; signal.addEventListener('abort', onAbort); }); } }