mcp-use 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,30 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { HttpConnector } from './connectors/http.js';
3
+ import { StdioConnector } from './connectors/stdio.js';
4
+ import { WebSocketConnector } from './connectors/websocket.js';
5
+ export function loadConfigFile(filepath) {
6
+ const raw = readFileSync(filepath, 'utf-8');
7
+ return JSON.parse(raw);
8
+ }
9
+ export function createConnectorFromConfig(serverConfig) {
10
+ if ('command' in serverConfig && 'args' in serverConfig) {
11
+ return new StdioConnector({
12
+ command: serverConfig.command,
13
+ args: serverConfig.args,
14
+ env: serverConfig.env,
15
+ });
16
+ }
17
+ if ('url' in serverConfig) {
18
+ return new HttpConnector(serverConfig.url, {
19
+ headers: serverConfig.headers,
20
+ authToken: serverConfig.auth_token,
21
+ });
22
+ }
23
+ if ('ws_url' in serverConfig) {
24
+ return new WebSocketConnector(serverConfig.ws_url, {
25
+ headers: serverConfig.headers,
26
+ authToken: serverConfig.auth_token,
27
+ });
28
+ }
29
+ throw new Error('Cannot determine connector type from config');
30
+ }
@@ -0,0 +1,129 @@
1
+ import { logger } from '../logging.js';
2
+ /**
3
+ * Base class for MCP connectors.
4
+ */
5
+ export class BaseConnector {
6
+ client = null;
7
+ connectionManager = null;
8
+ toolsCache = null;
9
+ connected = false;
10
+ opts;
11
+ constructor(opts = {}) {
12
+ this.opts = opts;
13
+ }
14
+ /** Disconnect and release resources. */
15
+ async disconnect() {
16
+ if (!this.connected) {
17
+ logger.debug('Not connected to MCP implementation');
18
+ return;
19
+ }
20
+ logger.debug('Disconnecting from MCP implementation');
21
+ await this.cleanupResources();
22
+ this.connected = false;
23
+ logger.debug('Disconnected from MCP implementation');
24
+ }
25
+ /** Check if the client is connected */
26
+ get isClientConnected() {
27
+ return this.client != null;
28
+ }
29
+ /**
30
+ * Initialise the MCP session **after** `connect()` has succeeded.
31
+ *
32
+ * In the SDK, `Client.connect(transport)` automatically performs the
33
+ * protocol‑level `initialize` handshake, so we only need to cache the list of
34
+ * tools and expose some server info.
35
+ */
36
+ async initialize(defaultRequestOptions = this.opts.defaultRequestOptions ?? {}) {
37
+ if (!this.client) {
38
+ throw new Error('MCP client is not connected');
39
+ }
40
+ logger.debug('Caching server capabilities & tools');
41
+ // Cache server capabilities for callers who need them.
42
+ const capabilities = this.client.getServerCapabilities();
43
+ // Fetch and cache tools
44
+ const listToolsRes = await this.client.listTools(undefined, defaultRequestOptions);
45
+ this.toolsCache = (listToolsRes.tools ?? []);
46
+ logger.debug(`Fetched ${this.toolsCache.length} tools from server`);
47
+ return capabilities;
48
+ }
49
+ /** Lazily expose the cached tools list. */
50
+ get tools() {
51
+ if (!this.toolsCache) {
52
+ throw new Error('MCP client is not initialized; call initialize() first');
53
+ }
54
+ return this.toolsCache;
55
+ }
56
+ /** Call a tool on the server. */
57
+ async callTool(name, args, options) {
58
+ if (!this.client) {
59
+ throw new Error('MCP client is not connected');
60
+ }
61
+ logger.debug(`Calling tool '${name}' with args`, args);
62
+ const res = await this.client.callTool({ name, arguments: args }, undefined, options);
63
+ logger.debug(`Tool '${name}' returned`, res);
64
+ return res;
65
+ }
66
+ /** List resources from the server. */
67
+ async listResources(options) {
68
+ if (!this.client) {
69
+ throw new Error('MCP client is not connected');
70
+ }
71
+ logger.debug('Listing resources');
72
+ return await this.client.listResources(undefined, options);
73
+ }
74
+ /** Read a resource by URI. */
75
+ async readResource(uri, options) {
76
+ if (!this.client) {
77
+ throw new Error('MCP client is not connected');
78
+ }
79
+ logger.debug(`Reading resource ${uri}`);
80
+ const res = await this.client.readResource({ uri }, options);
81
+ return { content: res.content, mimeType: res.mimeType };
82
+ }
83
+ /** Send a raw request through the client. */
84
+ async request(method, params = null, options) {
85
+ if (!this.client) {
86
+ throw new Error('MCP client is not connected');
87
+ }
88
+ logger.debug(`Sending raw request '${method}' with params`, params);
89
+ return await this.client.request({ method, params: params ?? {} }, undefined, options);
90
+ }
91
+ /**
92
+ * Helper to tear down the client & connection manager safely.
93
+ */
94
+ async cleanupResources() {
95
+ const issues = [];
96
+ if (this.client) {
97
+ try {
98
+ if (typeof this.client.close === 'function') {
99
+ await this.client.close();
100
+ }
101
+ }
102
+ catch (e) {
103
+ const msg = `Error closing client: ${e}`;
104
+ logger.warn(msg);
105
+ issues.push(msg);
106
+ }
107
+ finally {
108
+ this.client = null;
109
+ }
110
+ }
111
+ if (this.connectionManager) {
112
+ try {
113
+ await this.connectionManager.stop();
114
+ }
115
+ catch (e) {
116
+ const msg = `Error stopping connection manager: ${e}`;
117
+ logger.warn(msg);
118
+ issues.push(msg);
119
+ }
120
+ finally {
121
+ this.connectionManager = null;
122
+ }
123
+ }
124
+ this.toolsCache = null;
125
+ if (issues.length) {
126
+ logger.warn(`Resource cleanup finished with ${issues.length} issue(s)`);
127
+ }
128
+ }
129
+ }
@@ -0,0 +1,51 @@
1
+ import { Client } from '@modelcontextprotocol/sdk/client/index.js';
2
+ import { logger } from '../logging.js';
3
+ import { SseConnectionManager } from '../task_managers/sse.js';
4
+ import { BaseConnector } from './base.js';
5
+ export class HttpConnector extends BaseConnector {
6
+ baseUrl;
7
+ headers;
8
+ timeout;
9
+ sseReadTimeout;
10
+ clientInfo;
11
+ constructor(baseUrl, opts = {}) {
12
+ super(opts);
13
+ this.baseUrl = baseUrl.replace(/\/$/, '');
14
+ this.headers = { ...(opts.headers ?? {}) };
15
+ if (opts.authToken) {
16
+ this.headers.Authorization = `Bearer ${opts.authToken}`;
17
+ }
18
+ this.timeout = opts.timeout ?? 5;
19
+ this.sseReadTimeout = opts.sseReadTimeout ?? 60 * 5;
20
+ this.clientInfo = opts.clientInfo ?? { name: 'http-connector', version: '1.0.0' };
21
+ }
22
+ /** Establish connection to the MCP implementation via SSE. */
23
+ async connect() {
24
+ if (this.connected) {
25
+ logger.debug('Already connected to MCP implementation');
26
+ return;
27
+ }
28
+ logger.debug(`Connecting to MCP implementation via HTTP/SSE: ${this.baseUrl}`);
29
+ try {
30
+ // Build the SSE URL (root of server endpoint)
31
+ const sseUrl = this.baseUrl;
32
+ // Create and start the connection manager -> returns an SSE transport
33
+ this.connectionManager = new SseConnectionManager(sseUrl, {
34
+ requestInit: {
35
+ headers: this.headers,
36
+ },
37
+ });
38
+ const transport = await this.connectionManager.start();
39
+ // Create and connect the client
40
+ this.client = new Client(this.clientInfo, this.opts.clientOptions);
41
+ await this.client.connect(transport);
42
+ this.connected = true;
43
+ logger.debug(`Successfully connected to MCP implementation via HTTP/SSE: ${this.baseUrl}`);
44
+ }
45
+ catch (err) {
46
+ logger.error(`Failed to connect to MCP implementation via HTTP/SSE: ${err}`);
47
+ await this.cleanupResources();
48
+ throw err;
49
+ }
50
+ }
51
+ }
@@ -0,0 +1,4 @@
1
+ export { BaseConnector } from './base.js';
2
+ export { HttpConnector } from './http.js';
3
+ export { StdioConnector } from './stdio.js';
4
+ export { WebSocketConnector } from './websocket.js';
@@ -0,0 +1,49 @@
1
+ import process from 'node:process';
2
+ import { Client } from '@modelcontextprotocol/sdk/client/index.js';
3
+ import { logger } from '../logging.js';
4
+ import { StdioConnectionManager } from '../task_managers/stdio.js';
5
+ import { BaseConnector } from './base.js';
6
+ export class StdioConnector extends BaseConnector {
7
+ command;
8
+ args;
9
+ env;
10
+ errlog;
11
+ clientInfo;
12
+ constructor({ command = 'npx', args = [], env, errlog = process.stderr, ...rest } = {}) {
13
+ super(rest);
14
+ this.command = command;
15
+ this.args = args;
16
+ this.env = env;
17
+ this.errlog = errlog;
18
+ this.clientInfo = rest.clientInfo ?? { name: 'stdio-connector', version: '1.0.0' };
19
+ }
20
+ /** Establish connection to the MCP implementation. */
21
+ async connect() {
22
+ if (this.connected) {
23
+ logger.debug('Already connected to MCP implementation');
24
+ return;
25
+ }
26
+ logger.debug(`Connecting to MCP implementation via stdio: ${this.command}`);
27
+ try {
28
+ // 1. Build server parameters for the transport
29
+ const serverParams = {
30
+ command: this.command,
31
+ args: this.args,
32
+ env: this.env,
33
+ };
34
+ // 2. Start the connection manager -> returns a live transport
35
+ this.connectionManager = new StdioConnectionManager(serverParams, this.errlog);
36
+ const transport = await this.connectionManager.start();
37
+ // 3. Create & connect the MCP client
38
+ this.client = new Client(this.clientInfo, this.opts.clientOptions);
39
+ await this.client.connect(transport);
40
+ this.connected = true;
41
+ logger.debug(`Successfully connected to MCP implementation: ${this.command}`);
42
+ }
43
+ catch (err) {
44
+ logger.error(`Failed to connect to MCP implementation: ${err}`);
45
+ await this.cleanupResources();
46
+ throw err;
47
+ }
48
+ }
49
+ }
@@ -0,0 +1,151 @@
1
+ import { v4 as uuidv4 } from 'uuid';
2
+ import { logger } from '../logging.js';
3
+ import { WebSocketConnectionManager } from '../task_managers/websocket.js';
4
+ import { BaseConnector } from './base.js';
5
+ export class WebSocketConnector extends BaseConnector {
6
+ url;
7
+ headers;
8
+ connectionManager = null;
9
+ ws = null;
10
+ receiverTask = null;
11
+ pending = new Map();
12
+ toolsCache = null;
13
+ constructor(url, opts = {}) {
14
+ super();
15
+ this.url = url;
16
+ this.headers = { ...(opts.headers ?? {}) };
17
+ if (opts.authToken)
18
+ this.headers.Authorization = `Bearer ${opts.authToken}`;
19
+ }
20
+ async connect() {
21
+ if (this.connected) {
22
+ logger.debug('Already connected to MCP implementation');
23
+ return;
24
+ }
25
+ logger.debug(`Connecting via WebSocket: ${this.url}`);
26
+ try {
27
+ this.connectionManager = new WebSocketConnectionManager(this.url, this.headers);
28
+ this.ws = await this.connectionManager.start();
29
+ this.receiverTask = this.receiveLoop();
30
+ this.connected = true;
31
+ logger.debug('WebSocket connected successfully');
32
+ }
33
+ catch (e) {
34
+ logger.error(`Failed to connect: ${e}`);
35
+ await this.cleanupResources();
36
+ throw e;
37
+ }
38
+ }
39
+ async disconnect() {
40
+ if (!this.connected) {
41
+ logger.debug('Not connected to MCP implementation');
42
+ return;
43
+ }
44
+ logger.debug('Disconnecting …');
45
+ await this.cleanupResources();
46
+ this.connected = false;
47
+ }
48
+ sendRequest(method, params = null) {
49
+ if (!this.ws)
50
+ throw new Error('WebSocket is not connected');
51
+ const id = uuidv4();
52
+ const payload = JSON.stringify({ id, method, params: params ?? {} });
53
+ return new Promise((resolve, reject) => {
54
+ this.pending.set(id, { resolve, reject });
55
+ this.ws.send(payload, (err) => {
56
+ if (err) {
57
+ this.pending.delete(id);
58
+ reject(err);
59
+ }
60
+ });
61
+ });
62
+ }
63
+ async receiveLoop() {
64
+ if (!this.ws)
65
+ return;
66
+ const socket = this.ws; // Node.ws or browser WS
67
+ const onMessage = (msg) => {
68
+ let data;
69
+ try {
70
+ data = JSON.parse(msg.data ?? msg);
71
+ }
72
+ catch (e) {
73
+ logger.warn('Received non‑JSON frame');
74
+ return;
75
+ }
76
+ const id = data.id;
77
+ if (id && this.pending.has(id)) {
78
+ const { resolve, reject } = this.pending.get(id);
79
+ this.pending.delete(id);
80
+ if ('result' in data)
81
+ resolve(data.result);
82
+ else if ('error' in data)
83
+ reject(data.error);
84
+ }
85
+ else {
86
+ logger.debug('Received unsolicited message', data);
87
+ }
88
+ };
89
+ socket.addEventListener ? socket.addEventListener('message', onMessage) : socket.on('message', onMessage);
90
+ // keep promise pending until close
91
+ return new Promise((resolve) => {
92
+ const onClose = () => {
93
+ socket.removeEventListener ? socket.removeEventListener('message', onMessage) : socket.off('message', onMessage);
94
+ this.rejectAll(new Error('WebSocket closed'));
95
+ resolve();
96
+ };
97
+ socket.addEventListener ? socket.addEventListener('close', onClose) : socket.on('close', onClose);
98
+ });
99
+ }
100
+ rejectAll(err) {
101
+ for (const { reject } of this.pending.values())
102
+ reject(err);
103
+ this.pending.clear();
104
+ }
105
+ async initialize() {
106
+ logger.debug('Initializing MCP session over WebSocket');
107
+ const result = await this.sendRequest('initialize');
108
+ const toolsList = await this.listTools();
109
+ this.toolsCache = toolsList.map(t => t);
110
+ logger.debug(`Initialized with ${this.toolsCache.length} tools`);
111
+ return result;
112
+ }
113
+ async listTools() {
114
+ const res = await this.sendRequest('tools/list');
115
+ return res.tools ?? [];
116
+ }
117
+ async callTool(name, args, options) {
118
+ return await this.sendRequest('tools/call', { name, arguments: args });
119
+ }
120
+ async listResources(options) {
121
+ const resources = await this.sendRequest('resources/list');
122
+ return { resources: Array.isArray(resources) ? resources : [] };
123
+ }
124
+ async readResource(uri, options) {
125
+ const res = await this.sendRequest('resources/read', { uri });
126
+ return { content: res.content, mimeType: res.mimeType };
127
+ }
128
+ async request(method, params = null, options) {
129
+ return await this.sendRequest(method, params);
130
+ }
131
+ get tools() {
132
+ if (!this.toolsCache)
133
+ throw new Error('MCP client is not initialized');
134
+ return this.toolsCache;
135
+ }
136
+ async cleanupResources() {
137
+ // Stop receiver
138
+ if (this.receiverTask)
139
+ await this.receiverTask.catch(() => { });
140
+ this.receiverTask = null;
141
+ // Reject pending
142
+ this.rejectAll(new Error('WebSocket disconnected'));
143
+ // Stop connection manager → closes socket
144
+ if (this.connectionManager) {
145
+ await this.connectionManager.stop();
146
+ this.connectionManager = null;
147
+ this.ws = null;
148
+ }
149
+ this.toolsCache = null;
150
+ }
151
+ }
@@ -0,0 +1,59 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { createLogger, format, transports } from 'winston';
4
+ const { combine, timestamp, label, printf } = format;
5
+ let MCP_USE_DEBUG = 0;
6
+ const debugEnv = process.env.DEBUG?.toLowerCase();
7
+ if (debugEnv === '2') {
8
+ MCP_USE_DEBUG = 2;
9
+ }
10
+ else if (debugEnv === '1') {
11
+ MCP_USE_DEBUG = 1;
12
+ }
13
+ const defaultFormat = printf(({ level, message, label, timestamp }) => `${timestamp} [${label}] ${level.toUpperCase()}: ${message}`);
14
+ export class Logger {
15
+ static loggers = {};
16
+ static getLogger(name = 'mcp_use') {
17
+ if (!this.loggers[name]) {
18
+ this.loggers[name] = createLogger({
19
+ level: 'warn',
20
+ format: combine(label({ label: name }), timestamp(), defaultFormat),
21
+ transports: [],
22
+ });
23
+ }
24
+ return this.loggers[name];
25
+ }
26
+ static configure(level, toConsole = true, toFile) {
27
+ const root = this.getLogger();
28
+ if (!level) {
29
+ level
30
+ = MCP_USE_DEBUG === 2
31
+ ? 'debug'
32
+ : MCP_USE_DEBUG === 1 ? 'info' : 'warn';
33
+ }
34
+ root.level = level;
35
+ root.clear();
36
+ if (toConsole) {
37
+ root.add(new transports.Console());
38
+ }
39
+ if (toFile) {
40
+ const dir = path.dirname(toFile);
41
+ if (dir && !fs.existsSync(dir)) {
42
+ fs.mkdirSync(dir, { recursive: true });
43
+ }
44
+ root.add(new transports.File({ filename: toFile }));
45
+ }
46
+ }
47
+ static setDebug(debugLevel = 2) {
48
+ MCP_USE_DEBUG = debugLevel;
49
+ process.env.LANGCHAIN_VERBOSE = debugLevel >= 1 ? 'true' : 'false';
50
+ const newLevel = debugLevel === 2
51
+ ? 'debug'
52
+ : debugLevel === 1 ? 'info' : 'warn';
53
+ Object.values(this.loggers).forEach((log) => {
54
+ log.level = newLevel;
55
+ });
56
+ }
57
+ }
58
+ Logger.configure();
59
+ export const logger = Logger.getLogger();
@@ -0,0 +1,52 @@
1
+ import { logger } from './logging.js';
2
+ export class MCPSession {
3
+ connector;
4
+ autoConnect;
5
+ _sessionInfo = null;
6
+ _tools = [];
7
+ constructor(connector, autoConnect = true) {
8
+ this.connector = connector;
9
+ this.autoConnect = autoConnect;
10
+ }
11
+ async open() {
12
+ await this.connect();
13
+ return this;
14
+ }
15
+ async close() {
16
+ await this.disconnect();
17
+ }
18
+ async connect() {
19
+ await this.connector.connect();
20
+ }
21
+ async disconnect() {
22
+ await this.connector.disconnect();
23
+ }
24
+ async initialize() {
25
+ if (!this.isConnected && this.autoConnect) {
26
+ await this.connect();
27
+ }
28
+ this._sessionInfo = await this.connector.initialize() ?? {};
29
+ await this.discoverTools();
30
+ return this._sessionInfo;
31
+ }
32
+ get isConnected() {
33
+ return this.connector && this.connector.isClientConnected;
34
+ }
35
+ get sessionInfo() {
36
+ return this._sessionInfo;
37
+ }
38
+ get tools() {
39
+ return this._tools;
40
+ }
41
+ async discoverTools() {
42
+ this._tools = this.connector.tools;
43
+ return this._tools;
44
+ }
45
+ async callTool(name, args) {
46
+ if (!this.isConnected && this.autoConnect) {
47
+ await this.connect();
48
+ }
49
+ logger.debug(`MCPSession calling tool '${name}'`);
50
+ return await this.connector.callTool(name, args);
51
+ }
52
+ }
@@ -0,0 +1,127 @@
1
+ import { logger } from '../logging.js';
2
+ export class ConnectionManager {
3
+ _readyPromise;
4
+ _readyResolver;
5
+ _donePromise;
6
+ _doneResolver;
7
+ _exception = null;
8
+ _connection = null;
9
+ _task = null;
10
+ _abortController = null;
11
+ constructor() {
12
+ this.reset();
13
+ }
14
+ /**
15
+ * Start the connection manager and establish a connection.
16
+ *
17
+ * @returns The established connection.
18
+ * @throws If the connection cannot be established.
19
+ */
20
+ async start() {
21
+ // Reset internal state before starting
22
+ this.reset();
23
+ logger.debug(`Starting ${this.constructor.name}`);
24
+ // Kick off the background task that manages the connection
25
+ this._task = this.connectionTask();
26
+ // Wait until the connection is ready or an error occurs
27
+ await this._readyPromise;
28
+ // If an exception occurred during startup, re‑throw it
29
+ if (this._exception) {
30
+ throw this._exception;
31
+ }
32
+ if (this._connection === null) {
33
+ throw new Error('Connection was not established');
34
+ }
35
+ return this._connection;
36
+ }
37
+ /**
38
+ * Stop the connection manager and close the connection.
39
+ */
40
+ async stop() {
41
+ if (this._task && this._abortController) {
42
+ logger.debug(`Cancelling ${this.constructor.name} task`);
43
+ this._abortController.abort();
44
+ try {
45
+ await this._task;
46
+ }
47
+ catch (e) {
48
+ if (e instanceof Error && e.name === 'AbortError') {
49
+ logger.debug(`${this.constructor.name} task aborted successfully`);
50
+ }
51
+ else {
52
+ logger.warn(`Error stopping ${this.constructor.name} task: ${e}`);
53
+ }
54
+ }
55
+ }
56
+ // Wait until the connection cleanup has completed
57
+ await this._donePromise;
58
+ logger.debug(`${this.constructor.name} task completed`);
59
+ }
60
+ /**
61
+ * Reset all internal state.
62
+ */
63
+ reset() {
64
+ this._readyPromise = new Promise(res => (this._readyResolver = res));
65
+ this._donePromise = new Promise(res => (this._doneResolver = res));
66
+ this._exception = null;
67
+ this._connection = null;
68
+ this._task = null;
69
+ this._abortController = new AbortController();
70
+ }
71
+ /**
72
+ * The background task responsible for establishing and maintaining the
73
+ * connection until it is cancelled.
74
+ */
75
+ async connectionTask() {
76
+ logger.debug(`Running ${this.constructor.name} task`);
77
+ try {
78
+ // Establish the connection
79
+ this._connection = await this.establishConnection();
80
+ logger.debug(`${this.constructor.name} connected successfully`);
81
+ // Signal that the connection is ready
82
+ this._readyResolver();
83
+ // Keep the task alive until it is cancelled
84
+ await this.waitForAbort();
85
+ }
86
+ catch (err) {
87
+ this._exception = err;
88
+ logger.error(`Error in ${this.constructor.name} task: ${err}`);
89
+ // Ensure the ready promise resolves so that start() can handle the error
90
+ this._readyResolver();
91
+ }
92
+ finally {
93
+ // Clean up the connection if it was established
94
+ if (this._connection !== null) {
95
+ try {
96
+ await this.closeConnection(this._connection);
97
+ }
98
+ catch (closeErr) {
99
+ logger.warn(`Error closing connection in ${this.constructor.name}: ${closeErr}`);
100
+ }
101
+ this._connection = null;
102
+ }
103
+ // Signal that cleanup is finished
104
+ this._doneResolver();
105
+ }
106
+ }
107
+ /**
108
+ * Helper that returns a promise which resolves when the abort signal fires.
109
+ */
110
+ async waitForAbort() {
111
+ return new Promise((_resolve, _reject) => {
112
+ if (!this._abortController) {
113
+ return;
114
+ }
115
+ const signal = this._abortController.signal;
116
+ if (signal.aborted) {
117
+ _resolve();
118
+ return;
119
+ }
120
+ const onAbort = () => {
121
+ signal.removeEventListener('abort', onAbort);
122
+ _resolve();
123
+ };
124
+ signal.addEventListener('abort', onAbort);
125
+ });
126
+ }
127
+ }
@@ -0,0 +1,4 @@
1
+ export { ConnectionManager } from './base.js';
2
+ export { SseConnectionManager } from './sse.js';
3
+ export { StdioConnectionManager } from './stdio.js';
4
+ export { WebSocketConnectionManager } from './websocket.js';