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.
265 lines (264 loc) • 12.1 kB
JavaScript
/**
* Remote agent implementation for executing agents via API.
*/
import { zodToJsonSchema } from 'zod-to-json-schema';
import { logger } from '../logging.js';
// API endpoint constants
const API_CHATS_ENDPOINT = '/api/v1/chats';
const API_CHAT_EXECUTE_ENDPOINT = '/api/v1/chats/{chat_id}/execute';
export class RemoteAgent {
agentId;
apiKey;
baseUrl;
chatId = null;
constructor(options) {
this.agentId = options.agentId;
this.baseUrl = options.baseUrl ?? 'https://cloud.mcp-use.com';
// Handle API key validation
const apiKey = options.apiKey ?? process.env.MCP_USE_API_KEY;
if (!apiKey) {
throw new Error('API key is required for remote execution. '
+ 'Please provide it as a parameter or set the MCP_USE_API_KEY environment variable. '
+ 'You can get an API key from https://cloud.mcp-use.com');
}
this.apiKey = apiKey;
}
pydanticToJsonSchema(schema) {
/**
* Convert a Zod schema to JSON schema for API transmission.
*/
return zodToJsonSchema(schema);
}
parseStructuredResponse(responseData, outputSchema) {
/**
* Parse the API response into the structured output format.
*/
let resultData;
// Handle different response formats
if (typeof responseData === 'object' && responseData !== null) {
if ('result' in responseData) {
const outerResult = responseData.result;
// Check if this is a nested result structure (agent execution response)
if (typeof outerResult === 'object' && outerResult !== null && 'result' in outerResult) {
// Extract the actual structured output from the nested result
resultData = outerResult.result;
}
else {
// Use the outer result directly
resultData = outerResult;
}
}
else {
resultData = responseData;
}
}
else if (typeof responseData === 'string') {
try {
resultData = JSON.parse(responseData);
}
catch {
// If it's not valid JSON, try to create the model from the string content
resultData = { content: responseData };
}
}
else {
resultData = responseData;
}
// Parse into the Zod schema
try {
return outputSchema.parse(resultData);
}
catch (e) {
logger.warn(`Failed to parse structured output: ${e}`);
// Fallback: try to parse it as raw content if the schema has a content field
const schemaShape = outputSchema._def?.shape();
if (schemaShape && 'content' in schemaShape) {
return outputSchema.parse({ content: String(resultData) });
}
throw e;
}
}
async createChatSession() {
/**
* Create a persistent chat session for the agent.
*/
const chatPayload = {
title: `Remote Agent Session - ${this.agentId}`,
agent_id: this.agentId,
type: 'agent_execution',
};
const headers = {
'Content-Type': 'application/json',
'x-api-key': this.apiKey,
};
const chatUrl = `${this.baseUrl}${API_CHATS_ENDPOINT}`;
logger.info(`📝 Creating chat session for agent ${this.agentId}`);
try {
const response = await fetch(chatUrl, {
method: 'POST',
headers,
body: JSON.stringify(chatPayload),
});
if (!response.ok) {
const responseText = await response.text();
const statusCode = response.status;
if (statusCode === 404) {
throw new Error(`Agent not found: Agent '${this.agentId}' does not exist or you don't have access to it. `
+ 'Please verify the agent ID and ensure it exists in your account.');
}
throw new Error(`Failed to create chat session: ${statusCode} - ${responseText}`);
}
const chatData = await response.json();
const chatId = chatData.id;
logger.info(`✅ Chat session created: ${chatId}`);
return chatId;
}
catch (e) {
if (e instanceof Error) {
throw new TypeError(`Failed to create chat session: ${e.message}`);
}
throw new Error(`Failed to create chat session: ${String(e)}`);
}
}
async run(query, maxSteps, manageConnector, externalHistory, outputSchema) {
/**
* Run a query on the remote agent.
*/
if (externalHistory !== undefined) {
logger.warn('External history is not yet supported for remote execution');
}
try {
logger.info(`🌐 Executing query on remote agent ${this.agentId}`);
// Step 1: Create a chat session for this agent (only if we don't have one)
if (this.chatId === null) {
this.chatId = await this.createChatSession();
}
const chatId = this.chatId;
// Step 2: Execute the agent within the chat context
const executionPayload = {
query,
max_steps: maxSteps ?? 10,
};
// Add structured output schema if provided
if (outputSchema) {
executionPayload.output_schema = this.pydanticToJsonSchema(outputSchema);
logger.info(`🔧 Using structured output with schema`);
}
const headers = {
'Content-Type': 'application/json',
'x-api-key': this.apiKey,
};
const executionUrl = `${this.baseUrl}${API_CHAT_EXECUTE_ENDPOINT.replace('{chat_id}', chatId)}`;
logger.info(`🚀 Executing agent in chat ${chatId}`);
const response = await fetch(executionUrl, {
method: 'POST',
headers,
body: JSON.stringify(executionPayload),
signal: AbortSignal.timeout(300000), // 5 minute timeout
});
if (!response.ok) {
const responseText = await response.text();
const statusCode = response.status;
// Provide specific error messages based on status code
if (statusCode === 401) {
logger.error(`❌ Authentication failed: ${responseText}`);
throw new Error('Authentication failed: Invalid or missing API key. '
+ 'Please check your API key and ensure the MCP_USE_API_KEY environment variable is set correctly.');
}
else if (statusCode === 403) {
logger.error(`❌ Access forbidden: ${responseText}`);
throw new Error(`Access denied: You don't have permission to execute agent '${this.agentId}'. `
+ 'Check if the agent exists and you have the necessary permissions.');
}
else if (statusCode === 404) {
logger.error(`❌ Agent not found: ${responseText}`);
throw new Error(`Agent not found: Agent '${this.agentId}' does not exist or you don't have access to it. `
+ 'Please verify the agent ID and ensure it exists in your account.');
}
else if (statusCode === 422) {
logger.error(`❌ Validation error: ${responseText}`);
throw new Error(`Request validation failed: ${responseText}. `
+ 'Please check your query parameters and output schema format.');
}
else if (statusCode === 500) {
logger.error(`❌ Server error: ${responseText}`);
throw new Error('Internal server error occurred during agent execution. '
+ 'Please try again later or contact support if the issue persists.');
}
else {
logger.error(`❌ Remote execution failed with status ${statusCode}: ${responseText}`);
throw new Error(`Remote agent execution failed: ${statusCode} - ${responseText}`);
}
}
const result = await response.json();
logger.info(`🔧 Response: ${JSON.stringify(result)}`);
logger.info('✅ Remote execution completed successfully');
// Check for error responses (even with 200 status)
if (typeof result === 'object' && result !== null) {
// Check for actual error conditions (not just presence of error field)
if (result.status === 'error' || result.error !== null) {
const errorMsg = result.error ?? String(result);
logger.error(`❌ Remote agent execution failed: ${errorMsg}`);
throw new Error(`Remote agent execution failed: ${errorMsg}`);
}
// Check if the response indicates agent initialization failure
if (String(result).includes('failed to initialize')) {
logger.error(`❌ Agent initialization failed: ${result}`);
throw new Error('Agent initialization failed on remote server. '
+ 'This usually indicates:\n'
+ '• Invalid agent configuration (LLM model, system prompt)\n'
+ '• Missing or invalid MCP server configurations\n'
+ '• Network connectivity issues with MCP servers\n'
+ '• Missing environment variables or credentials\n'
+ `Raw error: ${result}`);
}
}
// Handle structured output
if (outputSchema) {
return this.parseStructuredResponse(result, outputSchema);
}
// Regular string output
if (typeof result === 'object' && result !== null && 'result' in result) {
return result.result;
}
else if (typeof result === 'string') {
return result;
}
else {
return String(result);
}
}
catch (e) {
if (e instanceof Error) {
// Check for specific error types
if (e.name === 'AbortError') {
logger.error(`❌ Remote execution timed out: ${e}`);
throw new Error('Remote agent execution timed out. The server may be overloaded or the query is taking too long to '
+ 'process. Try again or use a simpler query.');
}
logger.error(`❌ Remote execution error: ${e}`);
throw new Error(`Remote agent execution failed: ${e.message}`);
}
logger.error(`❌ Remote execution error: ${e}`);
throw new Error(`Remote agent execution failed: ${String(e)}`);
}
}
async *stream(query, maxSteps, manageConnector, externalHistory, outputSchema) {
/**
* Stream implementation for remote agent - currently just wraps run.
* In the future, this could be enhanced to support actual streaming from the API.
*/
const result = await this.run(query, maxSteps, manageConnector, externalHistory, outputSchema);
return result;
}
async close() {
/**
* Close the remote agent connection.
*/
logger.info('🔌 Remote agent client closed');
// In the future, we might want to delete the chat session here
// if (this.chatId) {
// await this.deleteChatSession(this.chatId)
// }
}
}