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,324 @@
1
+ import { AIMessage, HumanMessage, SystemMessage, } from '@langchain/core/messages';
2
+ import { OutputParserException } from '@langchain/core/output_parsers';
3
+ import { ChatPromptTemplate, MessagesPlaceholder, } from '@langchain/core/prompts';
4
+ import { AgentExecutor, createToolCallingAgent, } from 'langchain/agents';
5
+ import { LangChainAdapter } from '../adapters/langchain_adapter.js';
6
+ import { logger } from '../logging.js';
7
+ import { createSystemMessage } from './prompts/system_prompt_builder.js';
8
+ import { DEFAULT_SYSTEM_PROMPT_TEMPLATE, SERVER_MANAGER_SYSTEM_PROMPT_TEMPLATE } from './prompts/templates.js';
9
+ export class MCPAgent {
10
+ llm;
11
+ client;
12
+ connectors;
13
+ maxSteps;
14
+ autoInitialize;
15
+ memoryEnabled;
16
+ disallowedTools;
17
+ useServerManager;
18
+ verbose;
19
+ systemPrompt;
20
+ systemPromptTemplateOverride;
21
+ additionalInstructions;
22
+ initialized = false;
23
+ conversationHistory = [];
24
+ agentExecutor = null;
25
+ sessions = {};
26
+ systemMessage = null;
27
+ tools = [];
28
+ adapter;
29
+ serverManager = null;
30
+ constructor(options) {
31
+ this.llm = options.llm;
32
+ this.client = options.client;
33
+ this.connectors = options.connectors ?? [];
34
+ this.maxSteps = options.maxSteps ?? 5;
35
+ this.autoInitialize = options.autoInitialize ?? false;
36
+ this.memoryEnabled = options.memoryEnabled ?? true;
37
+ this.systemPrompt = options.systemPrompt ?? null;
38
+ this.systemPromptTemplateOverride = options.systemPromptTemplate ?? null;
39
+ this.additionalInstructions = options.additionalInstructions ?? null;
40
+ this.disallowedTools = options.disallowedTools ?? [];
41
+ this.useServerManager = options.useServerManager ?? false;
42
+ this.verbose = options.verbose ?? false;
43
+ if (!this.client && this.connectors.length === 0) {
44
+ throw new Error('Either \'client\' or at least one \'connector\' must be provided.');
45
+ }
46
+ if (this.useServerManager) {
47
+ if (!this.client) {
48
+ throw new Error('\'client\' must be provided when \'useServerManager\' is true.');
49
+ }
50
+ if (options.serverManagerFactory) {
51
+ this.serverManager = options.serverManagerFactory(this.client);
52
+ }
53
+ else {
54
+ throw new Error('No serverManagerFactory passed to MCPAgent constructor.');
55
+ }
56
+ }
57
+ // Let consumers swap allowed tools dynamically
58
+ this.adapter = options.adapter ?? new LangChainAdapter(this.disallowedTools);
59
+ }
60
+ async initialize() {
61
+ logger.info('🚀 Initializing MCP agent and connecting to services...');
62
+ // If using server manager, initialize it
63
+ if (this.useServerManager && this.serverManager) {
64
+ await this.serverManager.initialize();
65
+ // Get server management tools
66
+ const managementTools = await this.serverManager.getServerManagementTools();
67
+ this.tools = managementTools;
68
+ logger.info(`🔧 Server manager mode active with ${managementTools.length} management tools`);
69
+ // Create the system message based on available tools
70
+ await this.createSystemMessageFromTools(this.tools);
71
+ }
72
+ else {
73
+ // Standard initialization - if using client, get or create sessions
74
+ if (this.client) {
75
+ // First try to get existing sessions
76
+ this.sessions = await this.client.getAllActiveSessions();
77
+ logger.info(`🔌 Found ${Object.keys(this.sessions).length} existing sessions`);
78
+ // If no active sessions exist, create new ones
79
+ if (Object.keys(this.sessions).length === 0) {
80
+ logger.info('🔄 No active sessions found, creating new ones...');
81
+ this.sessions = await this.client.createAllSessions();
82
+ logger.info(`✅ Created ${Object.keys(this.sessions).length} new sessions`);
83
+ }
84
+ // Create LangChain tools directly from the client using the adapter
85
+ this.tools = await LangChainAdapter.createTools(this.client);
86
+ logger.info(`🛠️ Created ${this.tools.length} LangChain tools from client`);
87
+ }
88
+ else {
89
+ // Using direct connector - only establish connection
90
+ logger.info(`🔗 Connecting to ${this.connectors.length} direct connectors...`);
91
+ for (const connector of this.connectors) {
92
+ if (!connector.isClientConnected) {
93
+ await connector.connect();
94
+ }
95
+ }
96
+ // Create LangChain tools using the adapter with connectors
97
+ this.tools = await this.adapter.createToolsFromConnectors(this.connectors);
98
+ logger.info(`🛠️ Created ${this.tools.length} LangChain tools from connectors`);
99
+ }
100
+ // Get all tools for system message generation
101
+ logger.info(`🧰 Found ${this.tools.length} tools across all connectors`);
102
+ // Create the system message based on available tools
103
+ await this.createSystemMessageFromTools(this.tools);
104
+ }
105
+ // Create the agent executor and mark initialized
106
+ this.agentExecutor = this.createAgent();
107
+ this.initialized = true;
108
+ logger.info('✨ Agent initialization complete');
109
+ }
110
+ async createSystemMessageFromTools(tools) {
111
+ const systemPromptTemplate = this.systemPromptTemplateOverride
112
+ ?? DEFAULT_SYSTEM_PROMPT_TEMPLATE;
113
+ this.systemMessage = createSystemMessage(tools, systemPromptTemplate, SERVER_MANAGER_SYSTEM_PROMPT_TEMPLATE, this.useServerManager, this.disallowedTools, this.systemPrompt ?? undefined, this.additionalInstructions ?? undefined);
114
+ if (this.memoryEnabled) {
115
+ this.conversationHistory = [
116
+ this.systemMessage,
117
+ ...this.conversationHistory.filter(m => !(m instanceof SystemMessage)),
118
+ ];
119
+ }
120
+ }
121
+ createAgent() {
122
+ const systemContent = this.systemMessage?.content ?? 'You are a helpful assistant.';
123
+ const prompt = ChatPromptTemplate.fromMessages([
124
+ ['system', systemContent],
125
+ new MessagesPlaceholder('chat_history'),
126
+ ['human', '{input}'],
127
+ new MessagesPlaceholder('agent_scratchpad'),
128
+ ]);
129
+ const agent = createToolCallingAgent({
130
+ llm: this.llm,
131
+ tools: this.tools,
132
+ prompt,
133
+ });
134
+ return new AgentExecutor({
135
+ agent,
136
+ tools: this.tools,
137
+ maxIterations: this.maxSteps,
138
+ verbose: this.verbose,
139
+ returnIntermediateSteps: true,
140
+ });
141
+ }
142
+ getConversationHistory() {
143
+ return [...this.conversationHistory];
144
+ }
145
+ clearConversationHistory() {
146
+ this.conversationHistory = this.memoryEnabled && this.systemMessage ? [this.systemMessage] : [];
147
+ }
148
+ addToHistory(message) {
149
+ if (this.memoryEnabled)
150
+ this.conversationHistory.push(message);
151
+ }
152
+ getSystemMessage() {
153
+ return this.systemMessage;
154
+ }
155
+ setSystemMessage(message) {
156
+ this.systemMessage = new SystemMessage(message);
157
+ if (this.memoryEnabled) {
158
+ this.conversationHistory = this.conversationHistory.filter(m => !(m instanceof SystemMessage));
159
+ this.conversationHistory.unshift(this.systemMessage);
160
+ }
161
+ if (this.initialized && this.tools.length) {
162
+ this.agentExecutor = this.createAgent();
163
+ logger.debug('Agent recreated with new system message');
164
+ }
165
+ }
166
+ setDisallowedTools(disallowedTools) {
167
+ this.disallowedTools = disallowedTools;
168
+ this.adapter = new LangChainAdapter(this.disallowedTools);
169
+ if (this.initialized) {
170
+ logger.debug('Agent already initialized. Changes will take effect on next initialization.');
171
+ }
172
+ }
173
+ getDisallowedTools() {
174
+ return this.disallowedTools;
175
+ }
176
+ async run(query, maxSteps, manageConnector = true, externalHistory) {
177
+ let result = '';
178
+ let initializedHere = false;
179
+ try {
180
+ if (manageConnector && !this.initialized) {
181
+ await this.initialize();
182
+ initializedHere = true;
183
+ }
184
+ else if (!this.initialized && this.autoInitialize) {
185
+ await this.initialize();
186
+ initializedHere = true;
187
+ }
188
+ if (!this.agentExecutor) {
189
+ throw new Error('MCP agent failed to initialize');
190
+ }
191
+ const steps = maxSteps ?? this.maxSteps;
192
+ this.agentExecutor.maxIterations = steps;
193
+ const display_query = query.length > 50 ? `${query.slice(0, 50).replace(/\n/g, ' ')}...` : query.replace(/\n/g, ' ');
194
+ logger.info(`💬 Received query: '${display_query}'`);
195
+ // —–– Record user message
196
+ if (this.memoryEnabled) {
197
+ this.addToHistory(new HumanMessage(query));
198
+ }
199
+ const historyToUse = externalHistory ?? this.conversationHistory;
200
+ const langchainHistory = [];
201
+ for (const msg of historyToUse) {
202
+ if (msg instanceof HumanMessage || msg instanceof AIMessage) {
203
+ langchainHistory.push(msg);
204
+ }
205
+ }
206
+ const intermediateSteps = [];
207
+ const inputs = { input: query, chat_history: langchainHistory };
208
+ let nameToToolMap = Object.fromEntries(this.tools.map(t => [t.name, t]));
209
+ logger.info(`🏁 Starting agent execution with max_steps=${steps}`);
210
+ for (let stepNum = 0; stepNum < steps; stepNum++) {
211
+ if (this.useServerManager && this.serverManager) {
212
+ const currentTools = await this.serverManager.getAllTools();
213
+ const currentToolNames = new Set(currentTools.map(t => t.name));
214
+ const existingToolNames = new Set(this.tools.map(t => t.name));
215
+ const changed = currentTools.length !== this.tools.length
216
+ || [...currentToolNames].some(n => !existingToolNames.has(n));
217
+ if (changed) {
218
+ logger.info(`🔄 Tools changed before step ${stepNum + 1}, updating agent. New tools: ${[...currentToolNames].join(', ')}`);
219
+ this.tools = currentTools;
220
+ await this.createSystemMessageFromTools(this.tools);
221
+ this.agentExecutor = this.createAgent();
222
+ this.agentExecutor.maxIterations = steps;
223
+ nameToToolMap = Object.fromEntries(this.tools.map(t => [t.name, t]));
224
+ }
225
+ }
226
+ logger.info(`🔍 Step ${stepNum + 1}/${steps}`);
227
+ try {
228
+ logger.debug('Starting agent step execution');
229
+ const nextStepOutput = await this.agentExecutor._takeNextStep(nameToToolMap, inputs, intermediateSteps);
230
+ if (nextStepOutput.returnValues) {
231
+ logger.info(`✅ Agent finished at step ${stepNum + 1}`);
232
+ result = nextStepOutput.returnValues?.output ?? 'No output generated';
233
+ break;
234
+ }
235
+ const stepArray = nextStepOutput;
236
+ intermediateSteps.push(...stepArray);
237
+ for (const step of stepArray) {
238
+ const { action, observation } = step;
239
+ const toolName = action.tool;
240
+ let toolInputStr = String(action.toolInput);
241
+ if (toolInputStr.length > 100)
242
+ toolInputStr = `${toolInputStr.slice(0, 97)}...`;
243
+ logger.info(`🔧 Tool call: ${toolName} with input: ${toolInputStr}`);
244
+ let outputStr = String(observation);
245
+ if (outputStr.length > 100)
246
+ outputStr = `${outputStr.slice(0, 97)}...`;
247
+ outputStr = outputStr.replace(/\n/g, ' ');
248
+ logger.info(`📄 Tool result: ${outputStr}`);
249
+ }
250
+ // Detect direct return
251
+ if (stepArray.length) {
252
+ const lastStep = stepArray[stepArray.length - 1];
253
+ const toolReturn = await this.agentExecutor._getToolReturn(lastStep);
254
+ if (toolReturn) {
255
+ logger.info(`🏆 Tool returned directly at step ${stepNum + 1}`);
256
+ result = toolReturn.returnValues?.output ?? 'No output generated';
257
+ break;
258
+ }
259
+ }
260
+ }
261
+ catch (e) {
262
+ if (e instanceof OutputParserException) {
263
+ logger.error(`❌ Output parsing error during step ${stepNum + 1}: ${e}`);
264
+ result = `Agent stopped due to a parsing error: ${e}`;
265
+ break;
266
+ }
267
+ logger.error(`❌ Error during agent execution step ${stepNum + 1}: ${e}`);
268
+ console.error(e);
269
+ result = `Agent stopped due to an error: ${e}`;
270
+ break;
271
+ }
272
+ }
273
+ // —–– Post‑loop handling
274
+ if (!result) {
275
+ logger.warn(`⚠️ Agent stopped after reaching max iterations (${steps})`);
276
+ result = `Agent stopped after reaching the maximum number of steps (${steps}).`;
277
+ }
278
+ if (this.memoryEnabled) {
279
+ this.addToHistory(new AIMessage(result));
280
+ }
281
+ logger.info('🎉 Agent execution complete');
282
+ return result;
283
+ }
284
+ catch (e) {
285
+ logger.error(`❌ Error running query: ${e}`);
286
+ if (initializedHere && manageConnector) {
287
+ logger.info('🧹 Cleaning up resources after initialization error in run');
288
+ await this.close();
289
+ }
290
+ throw e;
291
+ }
292
+ finally {
293
+ if (manageConnector && !this.client && initializedHere) {
294
+ logger.info('🧹 Closing agent after query completion');
295
+ await this.close();
296
+ }
297
+ }
298
+ }
299
+ async close() {
300
+ logger.info('🔌 Closing MCPAgent resources…');
301
+ try {
302
+ this.agentExecutor = null;
303
+ this.tools = [];
304
+ if (this.client) {
305
+ logger.info('🔄 Closing sessions through client');
306
+ await this.client.closeAllSessions();
307
+ this.sessions = {};
308
+ }
309
+ else {
310
+ for (const connector of this.connectors) {
311
+ logger.info('🔄 Disconnecting connector');
312
+ await connector.disconnect();
313
+ }
314
+ }
315
+ if ('connectorToolMap' in this.adapter) {
316
+ this.adapter = new LangChainAdapter();
317
+ }
318
+ }
319
+ finally {
320
+ this.initialized = false;
321
+ logger.info('👋 Agent closed successfully');
322
+ }
323
+ }
324
+ }
@@ -0,0 +1,40 @@
1
+ import { SystemMessage } from '@langchain/core/messages';
2
+ export function generateToolDescriptions(tools, disallowedTools) {
3
+ const disallowedSet = new Set(disallowedTools ?? []);
4
+ const descriptions = [];
5
+ for (const tool of tools) {
6
+ if (disallowedSet.has(tool.name))
7
+ continue;
8
+ const escaped = tool.description
9
+ .replace(/\{/g, '{{')
10
+ .replace(/\}/g, '}}');
11
+ descriptions.push(`- ${tool.name}: ${escaped}`);
12
+ }
13
+ return descriptions;
14
+ }
15
+ export function buildSystemPromptContent(template, toolDescriptionLines, additionalInstructions) {
16
+ const block = toolDescriptionLines.join('\n');
17
+ let content;
18
+ if (template.includes('{tool_descriptions}')) {
19
+ content = template.replace('{tool_descriptions}', block);
20
+ }
21
+ else {
22
+ console.warn('`{tool_descriptions}` placeholder not found; appending at end.');
23
+ content = `${template}\n\nAvailable tools:\n${block}`;
24
+ }
25
+ if (additionalInstructions) {
26
+ content += `\n\n${additionalInstructions}`;
27
+ }
28
+ return content;
29
+ }
30
+ export function createSystemMessage(tools, systemPromptTemplate, serverManagerTemplate, useServerManager, disallowedTools, userProvidedPrompt, additionalInstructions) {
31
+ if (userProvidedPrompt) {
32
+ return new SystemMessage({ content: userProvidedPrompt });
33
+ }
34
+ const template = useServerManager
35
+ ? serverManagerTemplate
36
+ : systemPromptTemplate;
37
+ const toolLines = generateToolDescriptions(tools, disallowedTools);
38
+ const finalContent = buildSystemPromptContent(template, toolLines, additionalInstructions);
39
+ return new SystemMessage({ content: finalContent });
40
+ }
@@ -0,0 +1,39 @@
1
+ export const DEFAULT_SYSTEM_PROMPT_TEMPLATE = `You are a helpful AI assistant.
2
+ You have access to the following tools:
3
+
4
+ {tool_descriptions}
5
+
6
+ Use the following format:
7
+
8
+ Question: the input question you must answer
9
+ Thought: you should always think about what to do
10
+ Action: the action to take, should be one of the available tools
11
+ Action Input: the input to the action
12
+ Observation: the result of the action
13
+ ... (this Thought/Action/Action Input/Observation can repeat N times)
14
+ Thought: I now know the final answer
15
+ Final Answer: the final answer to the original input question`;
16
+ export const SERVER_MANAGER_SYSTEM_PROMPT_TEMPLATE = `You are a helpful assistant designed to interact with MCP
17
+ (Model Context Protocol) servers. You can manage connections to different servers and use the tools
18
+ provided by the currently active server.
19
+
20
+ Important: The available tools change depending on which server is active.
21
+ If a request requires tools not listed below (e.g., file operations, web browsing,
22
+ image manipulation), you MUST first connect to the appropriate server using
23
+ 'connect_to_mcp_server'.
24
+ Use 'list_mcp_servers' to find the relevant server if you are unsure.
25
+ Only after successfully connecting and seeing the new tools listed in
26
+ the response should you attempt to use those server-specific tools.
27
+ Before attempting a task that requires specific tools, you should
28
+ ensure you are connected to the correct server and aware of its
29
+ available tools. If unsure, use 'list_mcp_servers' to see options
30
+ or 'get_active_mcp_server' to check the current connection.
31
+
32
+ When you connect to a server using 'connect_to_mcp_server',
33
+ you will be informed about the new tools that become available.
34
+ You can then use these server-specific tools in subsequent steps.
35
+
36
+ Here are the tools *currently* available to you (this list includes server management tools and will
37
+ change when you connect to a server):
38
+ {tool_descriptions}
39
+ `;
@@ -0,0 +1,152 @@
1
+ import { DynamicStructuredTool } from '@langchain/core/tools';
2
+ import { z } from 'zod';
3
+ import { logger } from '../logging.js';
4
+ const ServerActionInputSchema = z.object({
5
+ serverName: z.string().describe('The name of the MCP server'),
6
+ });
7
+ const DisconnectServerInputSchema = z.object({});
8
+ const ListServersInputSchema = z.object({});
9
+ const CurrentServerInputSchema = z.object({});
10
+ export class ServerManager {
11
+ client;
12
+ adapter;
13
+ activeServer = null;
14
+ initializedServers = {};
15
+ serverTools = {};
16
+ constructor(client, adapter) {
17
+ this.client = client;
18
+ this.adapter = adapter;
19
+ }
20
+ async initialize() {
21
+ if (!this.client.getServerNames?.().length) {
22
+ logger.warning('No MCP servers defined in client configuration');
23
+ }
24
+ }
25
+ async getServerManagementTools() {
26
+ const listServersTool = new DynamicStructuredTool({
27
+ name: 'list_mcp_servers',
28
+ description: 'Lists all available MCP (Model Context Protocol) servers that can be connected to, along with the tools available on each server. Use this tool to discover servers and see what functionalities they offer.',
29
+ schema: ListServersInputSchema,
30
+ func: async () => this.listServers(),
31
+ });
32
+ const connectServerTool = new DynamicStructuredTool({
33
+ name: 'connect_to_mcp_server',
34
+ description: 'Connect to a specific MCP (Model Context Protocol) server to use its tools. Use this tool to connect to a specific server and use its tools.',
35
+ schema: ServerActionInputSchema,
36
+ func: async ({ serverName }) => this.connectToServer(serverName),
37
+ });
38
+ const getActiveServerTool = new DynamicStructuredTool({
39
+ name: 'get_active_mcp_server',
40
+ description: 'Get the currently active MCP (Model Context Protocol) server.',
41
+ schema: CurrentServerInputSchema,
42
+ func: async () => this.getActiveServer(),
43
+ });
44
+ const disconnectServerTool = new DynamicStructuredTool({
45
+ name: 'disconnect_from_mcp_server',
46
+ description: 'Disconnect from the currently active MCP (Model Context Protocol) server.',
47
+ schema: DisconnectServerInputSchema,
48
+ func: async () => this.disconnectFromServer(),
49
+ });
50
+ return [
51
+ listServersTool,
52
+ connectServerTool,
53
+ getActiveServerTool,
54
+ disconnectServerTool,
55
+ ];
56
+ }
57
+ async listServers() {
58
+ const servers = this.client.getServerNames?.() ?? [];
59
+ if (!servers.length)
60
+ return 'No MCP servers are currently defined.';
61
+ let out = 'Available MCP servers:\n';
62
+ for (const [idx, serverName] of servers.entries()) {
63
+ const active = serverName === this.activeServer ? ' (ACTIVE)' : '';
64
+ out += `${idx + 1}. ${serverName}${active}\n`;
65
+ try {
66
+ const tools = await this.ensureToolsFetched(serverName);
67
+ out += tools.length
68
+ ? ` Tools: ${tools.map(t => t.name).join(', ')}\n`
69
+ : ' Tools: (Could not retrieve or none available)\n';
70
+ }
71
+ catch (err) {
72
+ logger?.error?.(`Error listing tools for server '${serverName}':`, err);
73
+ out += ' Tools: (Error retrieving tools)\n';
74
+ }
75
+ }
76
+ return out;
77
+ }
78
+ async connectToServer(serverName) {
79
+ const servers = this.client.getServerNames() ?? [];
80
+ if (!servers.includes(serverName)) {
81
+ return `Server '${serverName}' not found. Available servers: ${servers.join(', ') || 'none'}`;
82
+ }
83
+ if (this.activeServer === serverName) {
84
+ return `Already connected to MCP server '${serverName}'`;
85
+ }
86
+ try {
87
+ const session = await this.ensureSession(serverName, /* create */ true);
88
+ this.activeServer = serverName;
89
+ // Ensure tools cached
90
+ await this.ensureToolsFetched(serverName, session?.connector);
91
+ const tools = this.serverTools[serverName] ?? [];
92
+ const toolDetails = tools
93
+ .map((t, i) => `${i + 1}. ${t.name}: ${t.description}`)
94
+ .join('\n');
95
+ return (`Connected to MCP server '${serverName}'. ${tools.length} tools are now available.${tools.length ? `\nAvailable tools for this server:\n${toolDetails}` : ''}`);
96
+ }
97
+ catch (err) {
98
+ logger.error(`Error connecting to server '${serverName}':`, err);
99
+ return `Failed to connect to server '${serverName}': ${String(err)}`;
100
+ }
101
+ }
102
+ async getActiveServer() {
103
+ return this.activeServer
104
+ ? `Currently active MCP server: ${this.activeServer}`
105
+ : 'No MCP server is currently active. Use connect_to_mcp_server to connect.';
106
+ }
107
+ async disconnectFromServer() {
108
+ if (!this.activeServer) {
109
+ return 'No MCP server is currently active, so there\'s nothing to disconnect from.';
110
+ }
111
+ const was = this.activeServer;
112
+ this.activeServer = null;
113
+ return `Successfully disconnected from MCP server '${was}'.`;
114
+ }
115
+ async getActiveServerTools() {
116
+ return this.activeServer ? this.serverTools[this.activeServer] ?? [] : [];
117
+ }
118
+ async getAllTools() {
119
+ return [...(await this.getServerManagementTools()), ...(await this.getActiveServerTools())];
120
+ }
121
+ async ensureSession(serverName, createIfMissing = false) {
122
+ try {
123
+ return this.client.getSession(serverName);
124
+ }
125
+ catch {
126
+ if (!createIfMissing)
127
+ return undefined;
128
+ return this.client.createSession ? await this.client.createSession(serverName) : undefined;
129
+ }
130
+ }
131
+ async ensureToolsFetched(serverName, connector) {
132
+ if (this.serverTools[serverName])
133
+ return this.serverTools[serverName];
134
+ const session = connector ? { connector } : await this.ensureSession(serverName, true);
135
+ if (!session) {
136
+ this.serverTools[serverName] = [];
137
+ return [];
138
+ }
139
+ try {
140
+ const tools = await this.adapter.createToolsFromConnectors([session.connector]);
141
+ this.serverTools[serverName] = tools;
142
+ this.initializedServers[serverName] = true;
143
+ logger.debug(`Fetched ${tools.length} tools for server '${serverName}'.`);
144
+ return tools;
145
+ }
146
+ catch (err) {
147
+ logger.warning(`Could not fetch tools for server '${serverName}':`, err);
148
+ this.serverTools[serverName] = [];
149
+ return [];
150
+ }
151
+ }
152
+ }
@@ -0,0 +1,124 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { createConnectorFromConfig, loadConfigFile } from './config.js';
4
+ import { logger } from './logging.js';
5
+ import { MCPSession } from './session.js';
6
+ export class MCPClient {
7
+ config = {};
8
+ sessions = {};
9
+ activeSessions = [];
10
+ constructor(config) {
11
+ if (config) {
12
+ if (typeof config === 'string') {
13
+ this.config = loadConfigFile(config);
14
+ }
15
+ else {
16
+ this.config = config;
17
+ }
18
+ }
19
+ }
20
+ static fromDict(cfg) {
21
+ return new MCPClient(cfg);
22
+ }
23
+ static fromConfigFile(path) {
24
+ return new MCPClient(loadConfigFile(path));
25
+ }
26
+ addServer(name, serverConfig) {
27
+ this.config.mcpServers = this.config.mcpServers || {};
28
+ this.config.mcpServers[name] = serverConfig;
29
+ }
30
+ removeServer(name) {
31
+ if (this.config.mcpServers?.[name]) {
32
+ delete this.config.mcpServers[name];
33
+ this.activeSessions = this.activeSessions.filter(n => n !== name);
34
+ }
35
+ }
36
+ getServerNames() {
37
+ return Object.keys(this.config.mcpServers ?? {});
38
+ }
39
+ saveConfig(filepath) {
40
+ const dir = path.dirname(filepath);
41
+ if (!fs.existsSync(dir)) {
42
+ fs.mkdirSync(dir, { recursive: true });
43
+ }
44
+ fs.writeFileSync(filepath, JSON.stringify(this.config, null, 2), 'utf-8');
45
+ }
46
+ async createSession(serverName, autoInitialize = true) {
47
+ const servers = this.config.mcpServers ?? {};
48
+ if (Object.keys(servers).length === 0) {
49
+ throw new Error('No MCP servers defined in config');
50
+ }
51
+ if (!servers[serverName]) {
52
+ throw new Error(`Server '${serverName}' not found in config`);
53
+ }
54
+ const connector = createConnectorFromConfig(servers[serverName]);
55
+ const session = new MCPSession(connector);
56
+ if (autoInitialize) {
57
+ await session.initialize();
58
+ }
59
+ this.sessions[serverName] = session;
60
+ if (!this.activeSessions.includes(serverName)) {
61
+ this.activeSessions.push(serverName);
62
+ }
63
+ return session;
64
+ }
65
+ async createAllSessions(autoInitialize = true) {
66
+ const servers = this.config.mcpServers ?? {};
67
+ if (Object.keys(servers).length === 0) {
68
+ throw new Error('No MCP servers defined in config');
69
+ }
70
+ for (const name of Object.keys(servers)) {
71
+ await this.createSession(name, autoInitialize);
72
+ }
73
+ return this.sessions;
74
+ }
75
+ getSession(serverName) {
76
+ const session = this.sessions[serverName];
77
+ if (!session) {
78
+ throw new Error(`No session exists for server '${serverName}'`);
79
+ }
80
+ return session;
81
+ }
82
+ getAllActiveSessions() {
83
+ return Object.fromEntries(this.activeSessions.map(n => [n, this.sessions[n]]));
84
+ }
85
+ async closeSession(serverName) {
86
+ const session = this.sessions[serverName];
87
+ if (!session) {
88
+ logger.warn(`No session exists for server ${serverName}, nothing to close`);
89
+ return;
90
+ }
91
+ try {
92
+ logger.debug(`Closing session for server ${serverName}`);
93
+ await session.disconnect();
94
+ }
95
+ catch (e) {
96
+ logger.error(`Error closing session for server '${serverName}': ${e}`);
97
+ }
98
+ finally {
99
+ delete this.sessions[serverName];
100
+ this.activeSessions = this.activeSessions.filter(n => n !== serverName);
101
+ }
102
+ }
103
+ async closeAllSessions() {
104
+ const serverNames = Object.keys(this.sessions);
105
+ const errors = [];
106
+ for (const serverName of serverNames) {
107
+ try {
108
+ logger.debug(`Closing session for server ${serverName}`);
109
+ await this.closeSession(serverName);
110
+ }
111
+ catch (e) {
112
+ const errorMsg = `Failed to close session for server '${serverName}': ${e}`;
113
+ logger.error(errorMsg);
114
+ errors.push(errorMsg);
115
+ }
116
+ }
117
+ if (errors.length) {
118
+ logger.error(`Encountered ${errors.length} errors while closing sessions`);
119
+ }
120
+ else {
121
+ logger.debug('All sessions closed successfully');
122
+ }
123
+ }
124
+ }