@agentpaid/mcp-use
Version:
A utility library for integrating Model Context Protocol (MCP) with LangChain, Zod, and related tools. Provides helpers for schema conversion, event streaming, and SDK usage.
540 lines (539 loc) β’ 25.8 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.MCPAgent = void 0;
const messages_1 = require("@langchain/core/messages");
const output_parsers_1 = require("@langchain/core/output_parsers");
const prompts_1 = require("@langchain/core/prompts");
const agents_1 = require("langchain/agents");
const langchain_adapter_js_1 = require("../adapters/langchain_adapter.js");
const logging_js_1 = require("../logging.js");
const server_manager_js_1 = require("../managers/server_manager.js");
const index_js_1 = require("../telemetry/index.js");
const system_prompt_builder_js_1 = require("./prompts/system_prompt_builder.js");
const templates_js_1 = require("./prompts/templates.js");
class MCPAgent {
llm;
client;
connectors;
maxSteps;
autoInitialize;
memoryEnabled;
disallowedTools;
additionalTools;
useServerManager;
verbose;
systemPrompt;
systemPromptTemplateOverride;
additionalInstructions;
_initialized = false;
conversationHistory = [];
_agentExecutor = null;
sessions = {};
systemMessage = null;
_tools = [];
adapter;
serverManager = null;
telemetry;
modelProvider;
modelName;
constructor(options) {
this.llm = options.llm;
this.client = options.client;
this.connectors = options.connectors ?? [];
this.maxSteps = options.maxSteps ?? 5;
this.autoInitialize = options.autoInitialize ?? false;
this.memoryEnabled = options.memoryEnabled ?? true;
this.systemPrompt = options.systemPrompt ?? null;
this.systemPromptTemplateOverride = options.systemPromptTemplate ?? null;
this.additionalInstructions = options.additionalInstructions ?? null;
this.disallowedTools = options.disallowedTools ?? [];
this.additionalTools = options.additionalTools ?? [];
this.useServerManager = options.useServerManager ?? false;
this.verbose = options.verbose ?? false;
if (!this.client && this.connectors.length === 0) {
throw new Error('Either \'client\' or at least one \'connector\' must be provided.');
}
if (this.useServerManager) {
if (!this.client) {
throw new Error('\'client\' must be provided when \'useServerManager\' is true.');
}
this.adapter = options.adapter ?? new langchain_adapter_js_1.LangChainAdapter(this.disallowedTools);
this.serverManager = options.serverManagerFactory?.(this.client) ?? new server_manager_js_1.ServerManager(this.client, this.adapter);
}
// Let consumers swap allowed tools dynamically
else {
this.adapter = options.adapter ?? new langchain_adapter_js_1.LangChainAdapter(this.disallowedTools);
}
// Initialize telemetry
this.telemetry = index_js_1.Telemetry.getInstance();
// Track model info for telemetry
const [provider, name] = (0, index_js_1.extractModelInfo)(this.llm);
this.modelProvider = provider;
this.modelName = name;
// Make getters configurable for test mocking
Object.defineProperty(this, 'agentExecutor', {
get: () => this._agentExecutor,
configurable: true,
});
Object.defineProperty(this, 'tools', {
get: () => this._tools,
configurable: true,
});
Object.defineProperty(this, 'initialized', {
get: () => this._initialized,
configurable: true,
});
}
async initialize() {
logging_js_1.logger.info('π Initializing MCP agent and connecting to services...');
// If using server manager, initialize it
if (this.useServerManager && this.serverManager) {
await this.serverManager.initialize();
// Get server management tools
const managementTools = this.serverManager.tools;
this._tools = managementTools;
this._tools.push(...this.additionalTools);
logging_js_1.logger.info(`π§ Server manager mode active with ${managementTools.length} management tools`);
// Create the system message based on available tools
await this.createSystemMessageFromTools(this._tools);
}
else {
// Standard initialization - if using client, get or create sessions
if (this.client) {
// First try to get existing sessions
this.sessions = await this.client.getAllActiveSessions();
logging_js_1.logger.info(`π Found ${Object.keys(this.sessions).length} existing sessions`);
// If no active sessions exist, create new ones
if (Object.keys(this.sessions).length === 0) {
logging_js_1.logger.info('π No active sessions found, creating new ones...');
this.sessions = await this.client.createAllSessions();
logging_js_1.logger.info(`β
Created ${Object.keys(this.sessions).length} new sessions`);
}
// Create LangChain tools directly from the client using the adapter
this._tools = await langchain_adapter_js_1.LangChainAdapter.createTools(this.client);
this._tools.push(...this.additionalTools);
logging_js_1.logger.info(`π οΈ Created ${this._tools.length} LangChain tools from client`);
}
else {
// Using direct connector - only establish connection
logging_js_1.logger.info(`π Connecting to ${this.connectors.length} direct connectors...`);
for (const connector of this.connectors) {
if (!connector.isClientConnected) {
await connector.connect();
}
}
// Create LangChain tools using the adapter with connectors
this._tools = await this.adapter.createToolsFromConnectors(this.connectors);
this._tools.push(...this.additionalTools);
logging_js_1.logger.info(`π οΈ Created ${this._tools.length} LangChain tools from connectors`);
}
// Get all tools for system message generation
logging_js_1.logger.info(`π§° Found ${this._tools.length} tools across all connectors`);
// Create the system message based on available tools
await this.createSystemMessageFromTools(this._tools);
}
// Create the agent executor and mark initialized
this._agentExecutor = this.createAgent();
this._initialized = true;
logging_js_1.logger.info('β¨ Agent initialization complete');
}
async createSystemMessageFromTools(tools) {
const systemPromptTemplate = this.systemPromptTemplateOverride
?? templates_js_1.DEFAULT_SYSTEM_PROMPT_TEMPLATE;
this.systemMessage = (0, system_prompt_builder_js_1.createSystemMessage)(tools, systemPromptTemplate, templates_js_1.SERVER_MANAGER_SYSTEM_PROMPT_TEMPLATE, this.useServerManager, this.disallowedTools, this.systemPrompt ?? undefined, this.additionalInstructions ?? undefined);
if (this.memoryEnabled) {
this.conversationHistory = [
this.systemMessage,
...this.conversationHistory.filter(m => !(m instanceof messages_1.SystemMessage)),
];
}
}
createAgent() {
const systemContent = this.systemMessage?.content ?? 'You are a helpful assistant.';
const prompt = prompts_1.ChatPromptTemplate.fromMessages([
['system', systemContent],
new prompts_1.MessagesPlaceholder('chat_history'),
['human', '{input}'],
new prompts_1.MessagesPlaceholder('agent_scratchpad'),
]);
const agent = (0, agents_1.createToolCallingAgent)({
llm: this.llm,
tools: this._tools,
prompt,
});
return new agents_1.AgentExecutor({
agent,
tools: this._tools,
maxIterations: this.maxSteps,
verbose: this.verbose,
returnIntermediateSteps: true,
});
}
getConversationHistory() {
return [...this.conversationHistory];
}
clearConversationHistory() {
this.conversationHistory = this.memoryEnabled && this.systemMessage ? [this.systemMessage] : [];
}
addToHistory(message) {
if (this.memoryEnabled)
this.conversationHistory.push(message);
}
getSystemMessage() {
return this.systemMessage;
}
setSystemMessage(message) {
this.systemMessage = new messages_1.SystemMessage(message);
if (this.memoryEnabled) {
this.conversationHistory = this.conversationHistory.filter(m => !(m instanceof messages_1.SystemMessage));
this.conversationHistory.unshift(this.systemMessage);
}
if (this._initialized && this._tools.length) {
this._agentExecutor = this.createAgent();
logging_js_1.logger.debug('Agent recreated with new system message');
}
}
setDisallowedTools(disallowedTools) {
this.disallowedTools = disallowedTools;
this.adapter = new langchain_adapter_js_1.LangChainAdapter(this.disallowedTools);
if (this._initialized) {
logging_js_1.logger.debug('Agent already initialized. Changes will take effect on next initialization.');
}
}
getDisallowedTools() {
return this.disallowedTools;
}
async _consumeAndReturn(generator) {
// Manually iterate through the generator to consume the steps.
// The for-await-of loop is not used because it discards the generator's
// final return value. We need to capture that value when `done` is true.
while (true) {
const { done, value } = await generator.next();
if (done) {
return value;
}
}
}
/**
* Runs the agent and returns a promise for the final result.
*/
async run(query, maxSteps, manageConnector, externalHistory) {
const generator = this.stream(query, maxSteps, manageConnector, externalHistory);
return this._consumeAndReturn(generator);
}
/**
* Runs the agent and yields intermediate steps as an async generator.
*/
async *stream(query, maxSteps, manageConnector = true, externalHistory) {
let result = '';
let initializedHere = false;
const startTime = Date.now();
const toolsUsedNames = [];
let stepsTaken = 0;
let success = false;
try {
if (manageConnector && !this._initialized) {
await this.initialize();
initializedHere = true;
}
else if (!this._initialized && this.autoInitialize) {
await this.initialize();
initializedHere = true;
}
if (!this._agentExecutor) {
throw new Error('MCP agent failed to initialize');
}
const steps = maxSteps ?? this.maxSteps;
this._agentExecutor.maxIterations = steps;
const display_query = query.length > 50 ? `${query.slice(0, 50).replace(/\n/g, ' ')}...` : query.replace(/\n/g, ' ');
logging_js_1.logger.info(`π¬ Received query: '${display_query}'`);
// βββ Record user message
if (this.memoryEnabled) {
this.addToHistory(new messages_1.HumanMessage(query));
}
const historyToUse = externalHistory ?? this.conversationHistory;
const langchainHistory = [];
for (const msg of historyToUse) {
if (msg instanceof messages_1.HumanMessage || msg instanceof messages_1.AIMessage) {
langchainHistory.push(msg);
}
}
const intermediateSteps = [];
const inputs = { input: query, chat_history: langchainHistory };
let nameToToolMap = Object.fromEntries(this._tools.map(t => [t.name, t]));
logging_js_1.logger.info(`π Starting agent execution with max_steps=${steps}`);
for (let stepNum = 0; stepNum < steps; stepNum++) {
stepsTaken = stepNum + 1;
if (this.useServerManager && this.serverManager) {
const currentTools = this.serverManager.tools;
const currentToolNames = new Set(currentTools.map(t => t.name));
const existingToolNames = new Set(this._tools.map(t => t.name));
const changed = currentTools.length !== this._tools.length
|| [...currentToolNames].some(n => !existingToolNames.has(n));
if (changed) {
logging_js_1.logger.info(`π Tools changed before step ${stepNum + 1}, updating agent. New tools: ${[...currentToolNames].join(', ')}`);
this._tools = currentTools;
this._tools.push(...this.additionalTools);
await this.createSystemMessageFromTools(this._tools);
this._agentExecutor = this.createAgent();
this._agentExecutor.maxIterations = steps;
nameToToolMap = Object.fromEntries(this._tools.map(t => [t.name, t]));
}
}
logging_js_1.logger.info(`π£ Step ${stepNum + 1}/${steps}`);
try {
logging_js_1.logger.debug('Starting agent step execution');
const nextStepOutput = await this._agentExecutor._takeNextStep(nameToToolMap, inputs, intermediateSteps);
if (nextStepOutput.returnValues) {
logging_js_1.logger.info(`β
Agent finished at step ${stepNum + 1}`);
result = nextStepOutput.returnValues?.output ?? 'No output generated';
break;
}
const stepArray = nextStepOutput;
intermediateSteps.push(...stepArray);
for (const step of stepArray) {
yield step;
const { action, observation } = step;
const toolName = action.tool;
toolsUsedNames.push(toolName);
let toolInputStr = typeof action.toolInput === 'string'
? action.toolInput
: JSON.stringify(action.toolInput, null, 2);
if (toolInputStr.length > 100)
toolInputStr = `${toolInputStr.slice(0, 97)}...`;
logging_js_1.logger.info(`π§ Tool call: ${toolName} with input: ${toolInputStr}`);
let outputStr = String(observation);
if (outputStr.length > 100)
outputStr = `${outputStr.slice(0, 97)}...`;
outputStr = outputStr.replace(/\n/g, ' ');
logging_js_1.logger.info(`π Tool result: ${outputStr}`);
}
// Detect direct return
if (stepArray.length) {
const lastStep = stepArray[stepArray.length - 1];
const toolReturn = await this._agentExecutor._getToolReturn(lastStep);
if (toolReturn) {
logging_js_1.logger.info(`π Tool returned directly at step ${stepNum + 1}`);
result = toolReturn.returnValues?.output ?? 'No output generated';
break;
}
}
}
catch (e) {
if (e instanceof output_parsers_1.OutputParserException) {
logging_js_1.logger.error(`β Output parsing error during step ${stepNum + 1}: ${e}`);
result = `Agent stopped due to a parsing error: ${e}`;
break;
}
logging_js_1.logger.error(`β Error during agent execution step ${stepNum + 1}: ${e}`);
console.error(e);
result = `Agent stopped due to an error: ${e}`;
break;
}
}
// βββ Postβloop handling
if (!result) {
logging_js_1.logger.warn(`β οΈ Agent stopped after reaching max iterations (${steps})`);
result = `Agent stopped after reaching the maximum number of steps (${steps}).`;
}
if (this.memoryEnabled) {
this.addToHistory(new messages_1.AIMessage(result));
}
logging_js_1.logger.info('π Agent execution complete');
success = true;
return result;
}
catch (e) {
logging_js_1.logger.error(`β Error running query: ${e}`);
if (initializedHere && manageConnector) {
logging_js_1.logger.info('π§Ή Cleaning up resources after initialization error in run');
await this.close();
}
throw e;
}
finally {
// Track comprehensive execution data
const executionTimeMs = Date.now() - startTime;
let serverCount = 0;
if (this.client) {
serverCount = Object.keys(await this.client.getAllActiveSessions()).length;
}
else if (this.connectors) {
serverCount = this.connectors.length;
}
const conversationHistoryLength = this.memoryEnabled ? this.conversationHistory.length : 0;
await this.telemetry.trackAgentExecution({
executionMethod: 'stream',
query,
success,
modelProvider: this.modelProvider,
modelName: this.modelName,
serverCount,
serverIdentifiers: this.connectors.map(connector => connector.publicIdentifier),
totalToolsAvailable: this._tools.length,
toolsAvailableNames: this._tools.map(t => t.name),
maxStepsConfigured: this.maxSteps,
memoryEnabled: this.memoryEnabled,
useServerManager: this.useServerManager,
maxStepsUsed: maxSteps ?? null,
manageConnector,
externalHistoryUsed: externalHistory !== undefined,
stepsTaken,
toolsUsedCount: toolsUsedNames.length,
toolsUsedNames,
response: result,
executionTimeMs,
errorType: success ? null : 'execution_error',
conversationHistoryLength,
});
if (manageConnector && !this.client && initializedHere) {
logging_js_1.logger.info('π§Ή Closing agent after query completion');
await this.close();
}
}
}
async close() {
logging_js_1.logger.info('π Closing MCPAgent resourcesβ¦');
try {
this._agentExecutor = null;
this._tools = [];
if (this.client) {
logging_js_1.logger.info('π Closing sessions through client');
await this.client.closeAllSessions();
this.sessions = {};
}
else {
for (const connector of this.connectors) {
logging_js_1.logger.info('π Disconnecting connector');
await connector.disconnect();
}
}
if ('connectorToolMap' in this.adapter) {
this.adapter = new langchain_adapter_js_1.LangChainAdapter();
}
}
finally {
this._initialized = false;
logging_js_1.logger.info('π Agent closed successfully');
}
}
/**
* Yields LangChain StreamEvent objects from the underlying streamEvents() method.
* This provides token-level streaming and fine-grained event updates.
*/
async *streamEvents(query, maxSteps, manageConnector = true, externalHistory) {
let initializedHere = false;
const startTime = Date.now();
let success = false;
let eventCount = 0;
let totalResponseLength = 0;
try {
// Initialize if needed
if (manageConnector && !this._initialized) {
await this.initialize();
initializedHere = true;
}
else if (!this._initialized && this.autoInitialize) {
await this.initialize();
initializedHere = true;
}
const agentExecutor = this.agentExecutor;
if (!agentExecutor) {
throw new Error('MCP agent failed to initialize');
}
// Set max iterations
const steps = maxSteps ?? this.maxSteps;
agentExecutor.maxIterations = steps;
const display_query = query.length > 50 ? `${query.slice(0, 50).replace(/\n/g, ' ')}...` : query.replace(/\n/g, ' ');
logging_js_1.logger.info(`π¬ Received query for streamEvents: '${display_query}'`);
// Add user message to history if memory enabled
if (this.memoryEnabled) {
this.addToHistory(new messages_1.HumanMessage(query));
}
// Prepare history
const historyToUse = externalHistory ?? this.conversationHistory;
const langchainHistory = [];
for (const msg of historyToUse) {
if (msg instanceof messages_1.HumanMessage || msg instanceof messages_1.AIMessage) {
langchainHistory.push(msg);
}
}
// Prepare inputs
const inputs = { input: query, chat_history: langchainHistory };
// Stream events from the agent executor
const eventStream = agentExecutor.streamEvents(inputs, { version: 'v2' });
// Yield each event
for await (const event of eventStream) {
eventCount++;
// Skip null or invalid events
if (!event || typeof event !== 'object') {
continue;
}
// Track response length for telemetry
if (event.event === 'on_chat_model_stream' && event.data?.chunk?.content) {
totalResponseLength += event.data.chunk.content.length;
}
yield event;
// Handle final message for history
if (event.event === 'on_chain_end' && event.data?.output) {
const output = event.data.output;
if (typeof output === 'string' && this.memoryEnabled) {
this.addToHistory(new messages_1.AIMessage(output));
}
else if (output?.output && typeof output.output === 'string' && this.memoryEnabled) {
this.addToHistory(new messages_1.AIMessage(output.output));
}
}
}
logging_js_1.logger.info(`π StreamEvents complete - ${eventCount} events emitted`);
success = true;
}
catch (e) {
logging_js_1.logger.error(`β Error during streamEvents: ${e}`);
if (initializedHere && manageConnector) {
logging_js_1.logger.info('π§Ή Cleaning up resources after initialization error in streamEvents');
await this.close();
}
throw e;
}
finally {
// Track telemetry
const executionTimeMs = Date.now() - startTime;
let serverCount = 0;
if (this.client) {
serverCount = Object.keys(await this.client.getAllActiveSessions()).length;
}
else if (this.connectors) {
serverCount = this.connectors.length;
}
const conversationHistoryLength = this.memoryEnabled ? this.conversationHistory.length : 0;
await this.telemetry.trackAgentExecution({
executionMethod: 'streamEvents',
query,
success,
modelProvider: this.modelProvider,
modelName: this.modelName,
serverCount,
serverIdentifiers: this.connectors.map(connector => connector.publicIdentifier),
totalToolsAvailable: this._tools.length,
toolsAvailableNames: this._tools.map(t => t.name),
maxStepsConfigured: this.maxSteps,
memoryEnabled: this.memoryEnabled,
useServerManager: this.useServerManager,
maxStepsUsed: maxSteps ?? null,
manageConnector,
externalHistoryUsed: externalHistory !== undefined,
response: `[STREAMED RESPONSE - ${totalResponseLength} chars]`,
executionTimeMs,
errorType: success ? null : 'streaming_error',
conversationHistoryLength,
});
// Clean up if needed
if (manageConnector && !this.client && initializedHere) {
logging_js_1.logger.info('π§Ή Closing agent after streamEvents completion');
await this.close();
}
}
}
}
exports.MCPAgent = MCPAgent;