Skip to content

Commit 58e46aa

Browse files
authored
Merge pull request #9 from mcp-use/feat/add-server-tool
Feat/add server tool
2 parents e83c813 + 07ac087 commit 58e46aa

File tree

6 files changed

+178
-5
lines changed

6 files changed

+178
-5
lines changed

.husky/pre-commit

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
npx lint-staged
1+
npm run lint

examples/add_server_tool.ts

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
/**
2+
* Dynamic server management example for mcp-use.
3+
*
4+
* This example demonstrates how to equip an MCPAgent with a tool
5+
* to dynamically add and connect to MCP servers during a run.
6+
*/
7+
8+
import { ChatOpenAI } from '@langchain/openai'
9+
import { config } from 'dotenv'
10+
import { MCPAgent, MCPClient } from '../index.js'
11+
import { LangChainAdapter } from '../src/adapters/langchain_adapter.js'
12+
import { ServerManager } from '../src/managers/server_manager.js'
13+
14+
// Load environment variables from .env file
15+
config()
16+
17+
async function main() {
18+
// Create an empty MCPClient. It has no servers to start with.
19+
const client = new MCPClient()
20+
21+
// The LLM to power the agent
22+
const llm = new ChatOpenAI({ model: 'gpt-4o', temperature: 0 })
23+
24+
// Create the agent, enabling the ServerManager
25+
const agent = new MCPAgent({
26+
llm,
27+
client,
28+
maxSteps: 30,
29+
useServerManager: true,
30+
serverManagerFactory: client => new ServerManager(client, new LangChainAdapter()),
31+
autoInitialize: true,
32+
})
33+
34+
// Define the server configuration that the agent will be asked to add.
35+
const serverConfigA = {
36+
command: 'npx',
37+
args: ['@playwright/mcp@latest'],
38+
env: {
39+
DISPLAY: ':1',
40+
},
41+
}
42+
const serverConfigB = {
43+
command: 'npx',
44+
args: ['-y', '@openbnb/mcp-server-airbnb', '--ignore-robots-txt'],
45+
}
46+
// We'll pass the config as a JSON string in the prompt.
47+
const serverConfigStringA = JSON.stringify(serverConfigA, null, 2)
48+
const serverConfigStringB = JSON.stringify(serverConfigB, null, 2)
49+
50+
const query = `I need to browse the web. To do this, please add and connect to a new MCP server for Playwright.
51+
The server name is 'playwright' and its configuration is:
52+
\`\`\`json
53+
${serverConfigStringA}
54+
\`\`\`
55+
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.
56+
57+
Then, please add and connect to a new MCP server for Airbnb.
58+
The server name is 'airbnb' and its configuration is:
59+
\`\`\`json
60+
${serverConfigStringB}
61+
\`\`\`
62+
and give me a house in the location of the company mcp-use.
63+
`
64+
65+
// Run the agent. It will first use the AddMCPServerTool, then the tools from the new server.
66+
const result = await agent.run(query)
67+
68+
console.log(`\n✅ Final Result:\n${result}`)
69+
70+
// Clean up the session created by the agent
71+
await client.closeAllSessions()
72+
}
73+
74+
if (import.meta.url === `file://${process.argv[1]}`) {
75+
main().catch(console.error)
76+
}

examples/browser_use.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@ async function main() {
3333
}
3434
// Create MCPClient from config file
3535
const client = new MCPClient(config)
36-
3736
// Create LLM
3837
const llm = new ChatOpenAI({ model: 'gpt-4o' })
3938
// const llm = init_chat_model({ model: "llama-3.1-8b-instant", model_provider: "groq" })

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,8 @@
6565
"example:multi": "npm run build && node dist/examples/multi_server_example.js",
6666
"example:sandbox": "npm run build && node dist/examples/sandbox_everything.js",
6767
"example:oauth": "npm run build && node dist/examples/simple_oauth_example.js",
68-
"example:blender": "npm run build && node dist/examples/blender_use.js"
68+
"example:blender": "npm run build && node dist/examples/blender_use.js",
69+
"example:add_server": "npm run build && node dist/examples/add_server_tool.js"
6970
},
7071
"dependencies": {
7172
"@dmitryrechkin/json-schema-to-zod": "^1.0.1",

src/managers/server_manager.ts

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import type { MCPSession } from '../session.js'
66
import { isEqual } from 'lodash-es'
77
import { logger } from '../logging.js'
88
import { AcquireActiveMCPServerTool } from './tools/acquire_active_mcp_server.js'
9+
import { AddMCPServerTool } from './tools/add_server.js'
910
import { ConnectMCPServerTool } from './tools/connect_mcp_server.js'
1011
import { ListMCPServersTool } from './tools/list_mcp_servers.js'
1112
import { ReleaseMCPServerConnectionTool } from './tools/release_mcp_server_connection.js'
@@ -23,10 +24,31 @@ export class ServerManager {
2324
this.adapter = adapter
2425
}
2526

27+
public logState(context: string): void {
28+
const allServerNames = this.client.getServerNames()
29+
const activeSessionNames = Object.keys(this.client.getAllActiveSessions())
30+
31+
if (allServerNames.length === 0) {
32+
logger.info('Server Manager State: No servers configured.')
33+
return
34+
}
35+
36+
const tableData = allServerNames.map(name => ({
37+
'Server Name': name,
38+
'Connected': activeSessionNames.includes(name) ? '✅' : '❌',
39+
'Initialized': this.initializedServers[name] ? '✅' : '❌',
40+
'Tool Count': this.serverTools[name]?.length ?? 0,
41+
'Active': this.activeServer === name ? '✅' : '❌',
42+
}))
43+
44+
logger.info(`Server Manager State: [${context}]`)
45+
console.table(tableData) // eslint-disable-line no-console
46+
}
47+
2648
initialize(): void {
2749
const serverNames = this.client.getServerNames?.()
2850
if (serverNames.length === 0) {
29-
logger.warning('No MCP servers defined in client configuration')
51+
logger.warn('No MCP servers defined in client configuration')
3052
}
3153
}
3254

@@ -83,11 +105,26 @@ export class ServerManager {
83105
}
84106

85107
get tools(): StructuredToolInterface[] {
86-
return [
108+
if (logger.level === 'debug') {
109+
this.logState('Providing tools to agent')
110+
}
111+
112+
const managementTools = [
113+
new AddMCPServerTool(this),
87114
new ListMCPServersTool(this),
88115
new ConnectMCPServerTool(this),
89116
new AcquireActiveMCPServerTool(this),
90117
new ReleaseMCPServerConnectionTool(this),
91118
]
119+
120+
if (this.activeServer && this.serverTools[this.activeServer]) {
121+
const activeTools = this.serverTools[this.activeServer]
122+
logger.debug(
123+
`Adding ${activeTools.length} tools from active server '${this.activeServer}'`,
124+
)
125+
return [...managementTools, ...activeTools]
126+
}
127+
128+
return managementTools
92129
}
93130
}

src/managers/tools/add_server.ts

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import type { StructuredToolInterface } from '@langchain/core/tools'
2+
import type { ServerManager } from '../server_manager.js'
3+
import { StructuredTool } from 'langchain/tools'
4+
import { z } from 'zod'
5+
import { logger } from '../../logging.js'
6+
7+
export class AddMCPServerTool extends StructuredTool {
8+
name = 'add_mcp_server'
9+
description
10+
= 'Adds a new MCP server to the client and connects to it, making its tools available.'
11+
12+
schema = z.object({
13+
serverName: z.string().describe('The name for the new MCP server.'),
14+
serverConfig: z
15+
.any()
16+
.describe(
17+
'The configuration object for the server. This should not include the top-level "mcpServers" key.',
18+
),
19+
})
20+
21+
private manager: ServerManager
22+
23+
constructor(manager: ServerManager) {
24+
super()
25+
this.manager = manager
26+
}
27+
28+
protected async _call({
29+
serverName,
30+
serverConfig,
31+
}: z.infer<typeof this.schema>): Promise<string> {
32+
try {
33+
this.manager.client.addServer(serverName, serverConfig)
34+
let result = `Server '${serverName}' added to the client.`
35+
logger.debug(
36+
`Connecting to new server '${serverName}' and discovering tools.`,
37+
)
38+
const session = await this.manager.client.createSession(serverName)
39+
const connector = session.connector
40+
const tools: StructuredToolInterface[]
41+
= await this.manager.adapter.createToolsFromConnectors([connector])
42+
43+
this.manager.serverTools[serverName] = tools
44+
this.manager.initializedServers[serverName] = true
45+
this.manager.activeServer = serverName // Set as active server
46+
47+
const numTools = tools.length
48+
result += ` Session created and connected. '${serverName}' is now the active server with ${numTools} tools available.`
49+
result += `\n\n${tools.map(t => t.name).join('\n')}`
50+
logger.info(result)
51+
return result
52+
}
53+
catch (e: any) {
54+
logger.error(
55+
`Failed to add or connect to server '${serverName}': ${e.message}`,
56+
)
57+
return `Failed to add or connect to server '${serverName}': ${e.message}`
58+
}
59+
}
60+
}

0 commit comments

Comments
 (0)