Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .husky/pre-commit
Original file line number Diff line number Diff line change
@@ -1 +1 @@
npx lint-staged
npm run lint
76 changes: 76 additions & 0 deletions examples/add_sever_tool.ts
Original file line number Diff line number Diff line change
@@ -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)
}
1 change: 0 additions & 1 deletion examples/browser_use.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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" })
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
41 changes: 39 additions & 2 deletions src/managers/server_manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand All @@ -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')
}
}

Expand Down Expand Up @@ -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
}
}
60 changes: 60 additions & 0 deletions src/managers/tools/add_server.ts
Original file line number Diff line number Diff line change
@@ -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<typeof this.schema>): Promise<string> {
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}`
}
}
}