Skip to main content
Glama

MCP Agent TypeScript Port

by waldzellai
agent.ts12 kB
/** * Main Agent class integrating MCP capabilities with LLM interactions */ import { v4 as uuidv4 } from 'uuid'; import { Tool, CallToolResult, TextContent, ImageContent, EmbeddedResource } from '@modelcontextprotocol/sdk/types.js'; import { Context } from '../core/context'; import { ContextDependentBase } from '../core/contextDependent'; import { ValidationError } from '../core/exceptions'; import { MCPAggregator } from '../mcp/aggregator'; import { NamespacedTool, NamespacedPrompt, NamespacedResource } from '../mcp/types'; import { AgentConfig, AgentMessage, AgentResponse, AugmentedLLM, ToolCall, HumanInputRequest, HumanInputResponse } from './types'; /** * Agent class that manages MCP server connections and LLM interactions */ export class Agent extends ContextDependentBase { public readonly id: string; public readonly name: string; private instruction?: string; private serverNames: string[]; private connectionPersistence: boolean; private aggregator?: MCPAggregator; private llm?: AugmentedLLM; private initialized: boolean = false; // Conversation history private messages: AgentMessage[] = []; constructor( context: Context, config: AgentConfig ) { super(context); this.id = uuidv4(); this.name = config.name; this.instruction = config.instruction; this.serverNames = config.server_names || []; this.connectionPersistence = config.connection_persistence ?? true; // Log agent creation this.logger.info(`Created agent '${this.name}' with ID: ${this.id}`); } /** * Initialize the agent and its MCP connections */ public async initialize(): Promise<void> { if (this.initialized) { return; } this.logger.info(`Initializing agent '${this.name}'...`); try { // Create and initialize MCP aggregator if servers are configured if (this.serverNames.length > 0) { this.aggregator = new MCPAggregator( this.context, this.serverNames, { connectionPersistence: this.connectionPersistence, name: this.name } ); await this.aggregator.initialize(); } // Add system instruction if provided if (this.instruction) { this.messages.push({ role: 'system', content: this.instruction }); } this.initialized = true; this.logger.info(`Agent '${this.name}' initialized successfully`); } catch (error) { this.logger.error(`Failed to initialize agent '${this.name}'`, error); throw error; } } /** * Clean up agent resources */ public async cleanup(): Promise<void> { this.logger.info(`Cleaning up agent '${this.name}'...`); try { if (this.aggregator) { await this.aggregator.close(); } this.initialized = false; this.logger.info(`Agent '${this.name}' cleaned up successfully`); } catch (error) { this.logger.error(`Error cleaning up agent '${this.name}'`, error); throw error; } } /** * Context manager support */ public async __aenter__(): Promise<this> { await this.initialize(); return this; } public async __aexit__(excType?: any, excVal?: any, excTb?: any): Promise<void> { await this.cleanup(); } /** * Set the LLM for this agent */ public setLLM(llm: AugmentedLLM): void { this.llm = llm; this.logger.debug(`Set LLM for agent '${this.name}': ${llm.model}`); } /** * Get available tools from connected MCP servers */ public async getTools(): Promise<Tool[]> { if (!this.aggregator) { return []; } const result = await this.aggregator.listTools(); return result.tools; } /** * Get available prompts from connected MCP servers */ public async getPrompts(): Promise<NamespacedPrompt[]> { if (!this.aggregator) { return []; } const result = await this.aggregator.listPrompts(); return result.prompts as NamespacedPrompt[]; } /** * Get available resources from connected MCP servers */ public async getResources(): Promise<NamespacedResource[]> { if (!this.aggregator) { return []; } const result = await this.aggregator.listResources(); return result.resources as NamespacedResource[]; } /** * Call a tool through the MCP aggregator */ public async callTool(name: string, args: any): Promise<CallToolResult> { if (!this.aggregator) { throw new ValidationError('No MCP servers configured'); } this.logger.debug(`Agent '${this.name}' calling tool: ${name}`); return await this.aggregator.callTool(name, args); } /** * Send a message to the agent and get a response */ public async sendMessage( content: string, options?: { tools?: boolean; temperature?: number; max_tokens?: number; } ): Promise<AgentResponse> { await this.ensureInitialized(); if (!this.llm) { throw new ValidationError('No LLM configured for this agent'); } // Add user message to history this.messages.push({ role: 'user', content }); // Get available tools if requested let tools: Tool[] | undefined; if (options?.tools && this.aggregator) { tools = await this.getTools(); } // Generate response const response = await this.llm.generate( this.messages, tools, { temperature: options?.temperature, max_tokens: options?.max_tokens } ); // Handle tool calls if present if (response.tool_calls && response.tool_calls.length > 0) { // Add assistant message with tool calls this.messages.push({ role: 'assistant', content: response.content, tool_calls: response.tool_calls }); // Execute tool calls for (const toolCall of response.tool_calls) { await this.executeToolCall(toolCall); } // Get final response after tool execution return await this.llm.generate(this.messages, tools, options); } // Add assistant response to history this.messages.push({ role: 'assistant', content: response.content }); return response; } /** * Execute a tool call */ private async executeToolCall(toolCall: ToolCall): Promise<void> { try { const args = JSON.parse(toolCall.function.arguments); const result = await this.callTool(toolCall.function.name, args); // Add tool result to messages this.messages.push({ role: 'tool', content: this.formatToolResult(result), tool_call_id: toolCall.id, name: toolCall.function.name }); } catch (error) { // Add error to messages this.messages.push({ role: 'tool', content: `Error calling tool: ${error}`, tool_call_id: toolCall.id, name: toolCall.function.name }); } } /** * Format tool result for message history */ private formatToolResult(result: CallToolResult): string { if (!result.content || result.content.length === 0) { return 'Tool executed successfully with no output'; } // Convert content array to string return result.content .map(item => { if (item.type === 'text') { return (item as TextContent).text; } else if (item.type === 'image') { const img = item as ImageContent; return `[Image: ${img.mimeType}]`; } else if (item.type === 'resource') { const res = item as EmbeddedResource; return `[Resource: ${res.resource.uri}]`; } return '[Unknown content type]'; }) .join('\n'); } /** * Get conversation history */ public getMessages(): AgentMessage[] { return [...this.messages]; } /** * Clear conversation history (keeping system instruction) */ public clearHistory(): void { this.messages = this.messages.filter(msg => msg.role === 'system'); } /** * Request human input */ public async requestHumanInput(request: HumanInputRequest): Promise<HumanInputResponse> { if (!this.context.human_input_callback) { throw new ValidationError('No human input callback configured'); } this.logger.debug(`Agent '${this.name}' requesting human input`); return await this.context.human_input_callback(request); } /** * Get a prompt by name */ public async getPrompt(name: string, args?: any): Promise<string> { if (!this.aggregator) { throw new ValidationError('No MCP servers configured'); } const result = await this.aggregator.getPrompt(name, args); // Extract text content from messages const messages = result.messages || []; return messages .map(msg => { if (msg.content && typeof msg.content === 'string') { return msg.content; } else if (msg.content && Array.isArray(msg.content)) { return msg.content .filter(item => item.type === 'text') .map(item => (item as TextContent).text) .join('\n'); } return ''; }) .join('\n\n'); } /** * Read a resource by URI */ public async readResource(uri: string): Promise<string> { if (!this.aggregator) { throw new ValidationError('No MCP servers configured'); } const result = await this.aggregator.readResource(uri); // Extract text content return result.contents .filter(item => 'type' in item && item.type === 'text') .map(item => 'text' in item ? (item as any).text : '') .join('\n'); } /** * Ensure agent is initialized */ private async ensureInitialized(): Promise<void> { if (!this.initialized) { await this.initialize(); } } /** * Get agent configuration */ public getConfig(): AgentConfig { return { name: this.name, instruction: this.instruction, server_names: this.serverNames, connection_persistence: this.connectionPersistence }; } /** * Get agent statistics */ public getStats(): { id: string; name: string; messageCount: number; serverCount: number; initialized: boolean; } { return { id: this.id, name: this.name, messageCount: this.messages.length, serverCount: this.serverNames.length, initialized: this.initialized }; } }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/waldzellai/mcp-agent-ts'

If you have feedback or need assistance with the MCP directory API, please join our Discord server