Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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_server_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_server_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}`
}
}
}