From 4f8cde842aac9ec87645374e5f13a5c49bf4a075 Mon Sep 17 00:00:00 2001 From: pietrozullo Date: Sat, 28 Jun 2025 10:21:29 -0700 Subject: [PATCH 1/4] Add server tool --- examples/add_sever_tool.ts | 76 ++++++++++++++++++++++++++++++++ examples/browser_use.ts | 1 - package.json | 3 +- src/managers/server_manager.ts | 41 ++++++++++++++++- src/managers/tools/add_server.ts | 60 +++++++++++++++++++++++++ 5 files changed, 177 insertions(+), 4 deletions(-) create mode 100644 examples/add_sever_tool.ts create mode 100644 src/managers/tools/add_server.ts diff --git a/examples/add_sever_tool.ts b/examples/add_sever_tool.ts new file mode 100644 index 00000000..1db2e68f --- /dev/null +++ b/examples/add_sever_tool.ts @@ -0,0 +1,76 @@ +/** + * Dynamic server management example for mcp-use. + * + * This example demonstrates how to equip an MCPAgent with a tool + * to dynamically add and connect to MCP servers during a run. + */ + +import { ChatOpenAI } from '@langchain/openai' +import { config } from 'dotenv' +import { MCPAgent, MCPClient } from '../index.js' +import { LangChainAdapter } from '../src/adapters/langchain_adapter.js' +import { ServerManager } from '../src/managers/server_manager.js' + +// Load environment variables from .env file +config() + +async function main() { + // Create an empty MCPClient. It has no servers to start with. + const client = new MCPClient() + + // The LLM to power the agent + const llm = new ChatOpenAI({ model: 'gpt-4o', temperature: 0 }) + + // Create the agent, enabling the ServerManager + const agent = new MCPAgent({ + llm, + client, + maxSteps: 30, + useServerManager: true, + serverManagerFactory: client => new ServerManager(client, new LangChainAdapter()), + autoInitialize: true, + }) + + // Define the server configuration that the agent will be asked to add. + const serverConfigA = { + command: 'npx', + args: ['@playwright/mcp@latest'], + env: { + DISPLAY: ':1', + }, + } + const serverConfigB = { + command: 'npx', + args: ['-y', '@openbnb/mcp-server-airbnb', '--ignore-robots-txt'], + } + // We'll pass the config as a JSON string in the prompt. + const serverConfigStringA = JSON.stringify(serverConfigA, null, 2) + const serverConfigStringB = JSON.stringify(serverConfigB, null, 2) + + const query = `I need to browse the web. To do this, please add and connect to a new MCP server for Playwright. + The server name is 'playwright' and its configuration is: + \`\`\`json + ${serverConfigStringA} + \`\`\` + Once the server is ready, navigate to https://github.com/mcp-use/mcp-use, give a star to the project, and then provide a concise summary of the project's README. + + Then, please add and connect to a new MCP server for Airbnb. + The server name is 'airbnb' and its configuration is: + \`\`\`json + ${serverConfigStringB} + \`\`\` + and give me a house in the location of the company mcp-use. + ` + + // Run the agent. It will first use the AddMCPServerTool, then the tools from the new server. + const result = await agent.run(query) + + console.log(`\n✅ Final Result:\n${result}`) + + // Clean up the session created by the agent + await client.closeAllSessions() +} + +if (import.meta.url === `file://${process.argv[1]}`) { + main().catch(console.error) +} diff --git a/examples/browser_use.ts b/examples/browser_use.ts index 044db217..2d5ea70c 100644 --- a/examples/browser_use.ts +++ b/examples/browser_use.ts @@ -33,7 +33,6 @@ async function main() { } // Create MCPClient from config file const client = new MCPClient(config) - // Create LLM const llm = new ChatOpenAI({ model: 'gpt-4o' }) // const llm = init_chat_model({ model: "llama-3.1-8b-instant", model_provider: "groq" }) diff --git a/package.json b/package.json index 17df27f5..37f31522 100644 --- a/package.json +++ b/package.json @@ -65,7 +65,8 @@ "example:multi": "npm run build && node dist/examples/multi_server_example.js", "example:sandbox": "npm run build && node dist/examples/sandbox_everything.js", "example:oauth": "npm run build && node dist/examples/simple_oauth_example.js", - "example:blender": "npm run build && node dist/examples/blender_use.js" + "example:blender": "npm run build && node dist/examples/blender_use.js", + "example:add_server": "npm run build && node dist/examples/add_sever_tool.js" }, "dependencies": { "@dmitryrechkin/json-schema-to-zod": "^1.0.1", diff --git a/src/managers/server_manager.ts b/src/managers/server_manager.ts index baf34de5..785615a6 100644 --- a/src/managers/server_manager.ts +++ b/src/managers/server_manager.ts @@ -6,6 +6,7 @@ import type { MCPSession } from '../session.js' import { isEqual } from 'lodash-es' import { logger } from '../logging.js' import { AcquireActiveMCPServerTool } from './tools/acquire_active_mcp_server.js' +import { AddMCPServerTool } from './tools/add_server.js' import { ConnectMCPServerTool } from './tools/connect_mcp_server.js' import { ListMCPServersTool } from './tools/list_mcp_servers.js' import { ReleaseMCPServerConnectionTool } from './tools/release_mcp_server_connection.js' @@ -23,10 +24,31 @@ export class ServerManager { this.adapter = adapter } + public logState(context: string): void { + const allServerNames = this.client.getServerNames() + const activeSessionNames = Object.keys(this.client.getAllActiveSessions()) + + if (allServerNames.length === 0) { + logger.info('Server Manager State: No servers configured.') + return + } + + const tableData = allServerNames.map(name => ({ + 'Server Name': name, + 'Connected': activeSessionNames.includes(name) ? '✅' : '❌', + 'Initialized': this.initializedServers[name] ? '✅' : '❌', + 'Tool Count': this.serverTools[name]?.length ?? 0, + 'Active': this.activeServer === name ? '✅' : '❌', + })) + + logger.info(`Server Manager State: [${context}]`) + console.table(tableData) // eslint-disable-line no-console + } + initialize(): void { const serverNames = this.client.getServerNames?.() if (serverNames.length === 0) { - logger.warning('No MCP servers defined in client configuration') + logger.warn('No MCP servers defined in client configuration') } } @@ -83,11 +105,26 @@ export class ServerManager { } get tools(): StructuredToolInterface[] { - return [ + if (logger.level === 'debug') { + this.logState('Providing tools to agent') + } + + const managementTools = [ + new AddMCPServerTool(this), new ListMCPServersTool(this), new ConnectMCPServerTool(this), new AcquireActiveMCPServerTool(this), new ReleaseMCPServerConnectionTool(this), ] + + if (this.activeServer && this.serverTools[this.activeServer]) { + const activeTools = this.serverTools[this.activeServer] + logger.debug( + `Adding ${activeTools.length} tools from active server '${this.activeServer}'`, + ) + return [...managementTools, ...activeTools] + } + + return managementTools } } diff --git a/src/managers/tools/add_server.ts b/src/managers/tools/add_server.ts new file mode 100644 index 00000000..caa2101f --- /dev/null +++ b/src/managers/tools/add_server.ts @@ -0,0 +1,60 @@ +import type { StructuredToolInterface } from '@langchain/core/tools' +import type { ServerManager } from '../server_manager.js' +import { StructuredTool } from 'langchain/tools' +import { z } from 'zod' +import { logger } from '../../logging.js' + +export class AddMCPServerTool extends StructuredTool { + name = 'add_mcp_server' + description + = 'Adds a new MCP server to the client and connects to it, making its tools available.' + + schema = z.object({ + serverName: z.string().describe('The name for the new MCP server.'), + serverConfig: z + .any() + .describe( + 'The configuration object for the server. This should not include the top-level "mcpServers" key.', + ), + }) + + private manager: ServerManager + + constructor(manager: ServerManager) { + super() + this.manager = manager + } + + protected async _call({ + serverName, + serverConfig, + }: z.infer): Promise { + try { + this.manager.client.addServer(serverName, serverConfig) + let result = `Server '${serverName}' added to the client.` + logger.debug( + `Connecting to new server '${serverName}' and discovering tools.`, + ) + const session = await this.manager.client.createSession(serverName) + const connector = session.connector + const tools: StructuredToolInterface[] + = await this.manager.adapter.createToolsFromConnectors([connector]) + + this.manager.serverTools[serverName] = tools + this.manager.initializedServers[serverName] = true + this.manager.activeServer = serverName // Set as active server + + const numTools = tools.length + result += ` Session created and connected. '${serverName}' is now the active server with ${numTools} tools available.` + result += `\n\n${tools.map(t => t.name).join('\n')}` + logger.info(result) + return result + } + catch (e: any) { + logger.error( + `Failed to add or connect to server '${serverName}': ${e.message}`, + ) + return `Failed to add or connect to server '${serverName}': ${e.message}` + } + } +} From eaaabce7a3e5dfa80f4c2923d6f5f68737fba816 Mon Sep 17 00:00:00 2001 From: pietrozullo Date: Sat, 28 Jun 2025 10:21:47 -0700 Subject: [PATCH 2/4] pre-commit simplify --- .husky/pre-commit | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.husky/pre-commit b/.husky/pre-commit index d0a77842..a845b850 100755 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -1 +1 @@ -npx lint-staged \ No newline at end of file +npm run lint \ No newline at end of file From 95b94e89e9349d6f0d6fcce95718dd9d41a09449 Mon Sep 17 00:00:00 2001 From: Pietro Zullo <62951181+pietrozullo@users.noreply.github.com> Date: Sat, 28 Jun 2025 10:26:09 -0700 Subject: [PATCH 3/4] fix typo Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 37f31522..7c27dc22 100644 --- a/package.json +++ b/package.json @@ -66,7 +66,7 @@ "example:sandbox": "npm run build && node dist/examples/sandbox_everything.js", "example:oauth": "npm run build && node dist/examples/simple_oauth_example.js", "example:blender": "npm run build && node dist/examples/blender_use.js", - "example:add_server": "npm run build && node dist/examples/add_sever_tool.js" + "example:add_server": "npm run build && node dist/examples/add_server_tool.js" }, "dependencies": { "@dmitryrechkin/json-schema-to-zod": "^1.0.1", From 07ac087bbfc52bf7fa90b94106a2d707d8cc6caf Mon Sep 17 00:00:00 2001 From: pietrozullo Date: Sat, 28 Jun 2025 10:26:48 -0700 Subject: [PATCH 4/4] Fix filename --- examples/{add_sever_tool.ts => add_server_tool.ts} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename examples/{add_sever_tool.ts => add_server_tool.ts} (100%) diff --git a/examples/add_sever_tool.ts b/examples/add_server_tool.ts similarity index 100% rename from examples/add_sever_tool.ts rename to examples/add_server_tool.ts