Skip to content

Commit 89f36e4

Browse files
committed
Add stream functionality for steps and additional tools, removed servermanager factory
1 parent 771af21 commit 89f36e4

File tree

2 files changed

+64
-16
lines changed

2 files changed

+64
-16
lines changed

examples/add_server_tool.ts

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,6 @@
88
import { ChatOpenAI } from '@langchain/openai'
99
import { config } from 'dotenv'
1010
import { MCPAgent, MCPClient } from '../index.js'
11-
import { LangChainAdapter } from '../src/adapters/langchain_adapter.js'
12-
import { ServerManager } from '../src/managers/server_manager.js'
1311

1412
// Load environment variables from .env file
1513
config()
@@ -27,14 +25,13 @@ async function main() {
2725
client,
2826
maxSteps: 30,
2927
useServerManager: true,
30-
serverManagerFactory: client => new ServerManager(client, new LangChainAdapter()),
3128
autoInitialize: true,
3229
})
3330

3431
// Define the server configuration that the agent will be asked to add.
3532
const serverConfigA = {
3633
command: 'npx',
37-
args: ['@playwright/mcp@latest'],
34+
args: ['@playwright/mcp@latest', '--headless'],
3835
env: {
3936
DISPLAY: ':1',
4037
},
@@ -62,8 +59,19 @@ async function main() {
6259
and give me a house in the location of the company mcp-use.
6360
`
6461

65-
// Run the agent. It will first use the AddMCPServerTool, then the tools from the new server.
66-
const result = await agent.run(query)
62+
// Run the agent. We call `stream()` to get the async generator.
63+
const stepIterator = agent.stream(query)
64+
let result: string
65+
while (true) {
66+
const { done, value } = await stepIterator.next()
67+
if (done) {
68+
result = value
69+
break
70+
}
71+
// You can inspect the intermediate steps here.
72+
console.log('--- Agent Step ---')
73+
console.dir(value, { depth: 4 })
74+
}
6775

6876
console.log(`\n✅ Final Result:\n${result}`)
6977

src/agents/mcp_agent.ts

Lines changed: 50 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import type { StructuredToolInterface, ToolInterface } from '@langchain/core/too
66
import type { AgentFinish, AgentStep } from 'langchain/agents'
77
import type { MCPClient } from '../client.js'
88
import type { BaseConnector } from '../connectors/base.js'
9-
import type { ServerManager } from '../managers/server_manager.js'
109
import type { MCPSession } from '../session.js'
1110
import {
1211
AIMessage,
@@ -24,6 +23,7 @@ import {
2423
} from 'langchain/agents'
2524
import { LangChainAdapter } from '../adapters/langchain_adapter.js'
2625
import { logger } from '../logging.js'
26+
import { ServerManager } from '../managers/server_manager.js'
2727
import { createSystemMessage } from './prompts/system_prompt_builder.js'
2828
import { DEFAULT_SYSTEM_PROMPT_TEMPLATE, SERVER_MANAGER_SYSTEM_PROMPT_TEMPLATE } from './prompts/templates.js'
2929

@@ -35,6 +35,7 @@ export class MCPAgent {
3535
private autoInitialize: boolean
3636
private memoryEnabled: boolean
3737
private disallowedTools: string[]
38+
private additionalTools: StructuredToolInterface[]
3839
private useServerManager: boolean
3940
private verbose: boolean
4041
private systemPrompt?: string | null
@@ -61,10 +62,10 @@ export class MCPAgent {
6162
systemPromptTemplate?: string | null
6263
additionalInstructions?: string | null
6364
disallowedTools?: string[]
65+
additionalTools?: StructuredToolInterface[]
6466
useServerManager?: boolean
6567
verbose?: boolean
6668
adapter?: LangChainAdapter
67-
serverManagerFactory?: (client: MCPClient) => ServerManager
6869
}) {
6970
this.llm = options.llm
7071

@@ -77,6 +78,7 @@ export class MCPAgent {
7778
this.systemPromptTemplateOverride = options.systemPromptTemplate ?? null
7879
this.additionalInstructions = options.additionalInstructions ?? null
7980
this.disallowedTools = options.disallowedTools ?? []
81+
this.additionalTools = options.additionalTools ?? []
8082
this.useServerManager = options.useServerManager ?? false
8183
this.verbose = options.verbose ?? false
8284

@@ -88,15 +90,13 @@ export class MCPAgent {
8890
if (!this.client) {
8991
throw new Error('\'client\' must be provided when \'useServerManager\' is true.')
9092
}
91-
if (options.serverManagerFactory) {
92-
this.serverManager = options.serverManagerFactory(this.client)
93-
}
94-
else {
95-
throw new Error('No serverManagerFactory passed to MCPAgent constructor.')
96-
}
93+
this.adapter = options.adapter ?? new LangChainAdapter(this.disallowedTools)
94+
this.serverManager = new ServerManager(this.client, this.adapter)
9795
}
9896
// Let consumers swap allowed tools dynamically
99-
this.adapter = options.adapter ?? new LangChainAdapter(this.disallowedTools)
97+
else {
98+
this.adapter = options.adapter ?? new LangChainAdapter(this.disallowedTools)
99+
}
100100
}
101101

102102
public async initialize(): Promise<void> {
@@ -109,6 +109,7 @@ export class MCPAgent {
109109
// Get server management tools
110110
const managementTools = this.serverManager.tools
111111
this.tools = managementTools
112+
this.tools.push(...this.additionalTools)
112113
logger.info(
113114
`🔧 Server manager mode active with ${managementTools.length} management tools`,
114115
)
@@ -132,6 +133,7 @@ export class MCPAgent {
132133

133134
// Create LangChain tools directly from the client using the adapter
134135
this.tools = await LangChainAdapter.createTools(this.client)
136+
this.tools.push(...this.additionalTools)
135137
logger.info(`🛠️ Created ${this.tools.length} LangChain tools from client`)
136138
}
137139
else {
@@ -145,6 +147,7 @@ export class MCPAgent {
145147

146148
// Create LangChain tools using the adapter with connectors
147149
this.tools = await this.adapter.createToolsFromConnectors(this.connectors)
150+
this.tools.push(...this.additionalTools)
148151
logger.info(`🛠️ Created ${this.tools.length} LangChain tools from connectors`)
149152
}
150153

@@ -251,12 +254,47 @@ export class MCPAgent {
251254
return this.disallowedTools
252255
}
253256

257+
private async _consumeAndReturn(
258+
generator: AsyncGenerator<AgentStep, string, void>,
259+
): Promise<string> {
260+
// Manually iterate through the generator to consume the steps.
261+
// The for-await-of loop is not used because it discards the generator's
262+
// final return value. We need to capture that value when `done` is true.
263+
while (true) {
264+
const { done, value } = await generator.next()
265+
if (done) {
266+
return value
267+
}
268+
}
269+
}
270+
271+
/**
272+
* Runs the agent and returns a promise for the final result.
273+
*/
254274
public async run(
255275
query: string,
256276
maxSteps?: number,
257-
manageConnector = true,
277+
manageConnector?: boolean,
258278
externalHistory?: BaseMessage[],
259279
): Promise<string> {
280+
const generator = this.stream(
281+
query,
282+
maxSteps,
283+
manageConnector,
284+
externalHistory,
285+
)
286+
return this._consumeAndReturn(generator)
287+
}
288+
289+
/**
290+
* Runs the agent and yields intermediate steps as an async generator.
291+
*/
292+
public async* stream(
293+
query: string,
294+
maxSteps?: number,
295+
manageConnector = true,
296+
externalHistory?: BaseMessage[],
297+
): AsyncGenerator<AgentStep, string, void> {
260298
let result = ''
261299
let initializedHere = false
262300

@@ -315,6 +353,7 @@ export class MCPAgent {
315353
`🔄 Tools changed before step ${stepNum + 1}, updating agent. New tools: ${[...currentToolNames].join(', ')}`,
316354
)
317355
this.tools = currentTools
356+
this.tools.push(...this.additionalTools)
318357
await this.createSystemMessageFromTools(this.tools)
319358
this.agentExecutor = this.createAgent()
320359
this.agentExecutor.maxIterations = steps
@@ -342,6 +381,7 @@ export class MCPAgent {
342381
intermediateSteps.push(...stepArray)
343382

344383
for (const step of stepArray) {
384+
yield step
345385
const { action, observation } = step
346386
const toolName = action.tool
347387
let toolInputStr = String(action.toolInput)

0 commit comments

Comments
 (0)