UNPKG

mcp-ai-agent

Version:

An AI agentic tool on typescript that can use MCP servers

655 lines (632 loc) 24.5 kB
import { experimental_createMCPClient, tool, generateText, generateObject } from 'ai'; import { Experimental_StdioMCPTransport } from 'ai/mcp-stdio'; import { z } from 'zod'; import { openai } from '@ai-sdk/openai'; import { zodToJsonSchema } from 'zod-to-json-schema'; var __defProp = Object.defineProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; // src/utils.ts function filterTools(tools, filter = () => true) { return Object.fromEntries( Object.entries(tools).filter(([_, tool2]) => filter(tool2)) ); } var DEFAULT_MODEL = openai("gpt-4o"); var DEFAULT_MAX_STEPS = 20; // src/utils/textUtils.ts function toSnakeCase(str) { if (!str) return Math.random().toString(36).substring(2, 15); return str.toLowerCase().replace(" ", "_"); } // src/AIAgent.ts var AIAgent = class { constructor({ name, description, toolsConfigs, systemPrompt, model, verbose }) { this.clients = {}; this.tools = {}; this.agents = {}; this.initialized = false; this.verbose = false; this.config = toolsConfigs || []; this.name = name; this.description = description; this.system = systemPrompt; this.model = model || DEFAULT_MODEL; this.verbose = !!verbose; } log(level, message, ...args) { if (this.verbose || level === "error") { console[level](message, ...args); } } async initializeSdtioServer(name, serverConfig) { this.log("info", "Initializing stdio server", name); const transport = new Experimental_StdioMCPTransport(serverConfig); this.clients[name] = await experimental_createMCPClient({ transport }); } async initializeSSEServer(name, serverConfig) { this.log("info", "Initializing sse server", name); this.clients[name] = await experimental_createMCPClient({ transport: serverConfig }); } async initializeAutoServer(name, autoConfig) { this.log("info", "Initializing auto server", name); const missingEnvVars = Object.entries(autoConfig.parameters).filter( ([key, value]) => value.required && !process.env[key] ); if (missingEnvVars.length > 0) { throw new Error( `Missing environment variables: ${missingEnvVars.map(([key]) => key).join(", ")}` ); } const envVars = Object.entries(autoConfig.parameters).reduce( (acc, [key, _]) => { if (process.env[key]) acc[key] = process.env[key]; return acc; }, {} ); const serverConfig = Array.isArray(autoConfig.mcpConfig) ? autoConfig.mcpConfig[0] : autoConfig.mcpConfig; const type = "type" in serverConfig ? serverConfig.type : "stdio"; switch (type) { case "stdio": await this.initializeSdtioServer(name, { ...serverConfig, env: envVars }); break; case "sse": await this.initializeSSEServer(name, serverConfig); break; } } async initializeAgentConfig(config) { const name = config.name ? toSnakeCase(config.name) : toSnakeCase(config.agent.getInfo?.()?.name); this.log("info", "Initializing agent config", name); this.agents[name] = { agent: config.agent, config }; this.tools[`agent_${toSnakeCase(name)}`] = tool({ description: config.description || config.agent.getInfo?.()?.description || `Call the ${config.name} agent for specialized tasks`, parameters: z.object({ context: z.string().describe( "The context to send to the agent. Add any relevant information the agent needs to know for the task." ), prompt: z.string().describe("The prompt to send to the agent") }), execute: async ({ context, prompt }) => { const response = await config.agent.generateResponse({ model: config.model || config.agent.getInfo?.()?.model || this.model, prompt: `<Context>${context}</Context> <Prompt>${prompt}</Prompt>`, system: config.system || config.agent.getInfo?.()?.system || "", tools: config.tools, toolChoice: config.toolChoice, maxSteps: config.maxSteps, messages: config.messages, providerOptions: config.providerOptions, onStepFinish: config.onStepFinish, filterMCPTools: config.filterMCPTools }); this.log("info", "Agent response", response.text); return response.text; } }); } async initializeMcpConfig(config) { try { await Promise.all( Object.entries(config.mcpServers).map(async ([name, serverConfig]) => { const type = "type" in serverConfig ? serverConfig.type : "stdio"; switch (type) { case "stdio": await this.initializeSdtioServer( name, serverConfig ); break; case "sse": await this.initializeSSEServer(name, serverConfig); break; case "auto": await this.initializeAutoServer( name, serverConfig ); break; default: throw new Error(`Unsupported server type for server ${name}`); } }) ); } catch (error) { this.log("error", "Error initializing MCP clients:", error); await this.close(); throw error; } } async initializeConfigRouter(config) { if ("type" in config && config.type === "auto") { this.log("info", "Config Router: Initializing auto server", config.name); await this.initializeAutoServer(config.name, config); } else if ("type" in config && config.type === "agent") { this.log( "info", "Config Router: Initializing agent config", config.agent.getInfo?.()?.name ); await this.initializeAgentConfig(config); } else if ("type" in config && config.type === "tool") { this.log("info", "Config Router: Initializing tool", config.name); this.tools[toSnakeCase(config.name)] = tool({ description: config.description, parameters: config.parameters, execute: config.execute }); } else { this.log( "info", "Config Router: Initializing MCP config", Object.keys(config.mcpServers) ); await this.initializeMcpConfig(config); } } async initialize() { await Promise.all( this.config.map((config) => this.initializeConfigRouter(config)) ); await Promise.all( Object.entries(this.clients).map(async ([_, client]) => { const serverTools = await client.tools(); this.tools = { ...this.tools, ...serverTools }; }) ); this.initialized = true; } async generateResponse(args) { if (!this.initialized) { await this.initialize(); } const filteredTools = filterTools(this.tools, args.filterMCPTools); const allTools = { ...filteredTools, ...args.tools }; const model = args.model || this.model; this.log( "info", "Generating response with ", JSON.stringify( { name: this.name, prompt: args.prompt, model: model.modelId, allTools: Object.keys(allTools), maxSteps: args.maxSteps }, null, 2 ) ); try { const response = await generateText({ ...args, system: args.system || this.system, model, tools: allTools, maxSteps: args.maxSteps || DEFAULT_MAX_STEPS }); const finalResponse = { ...response }; if (!response.text && response.finishReason === "tool-calls") { finalResponse.text = "The AI completed with tool calls, but no final text was generated. Check if the requested resources were found."; } return finalResponse; } catch (error) { this.log( "error", "Error generating response:", JSON.stringify(error, null, 2) ); throw error; } } async generateObject(args) { const filteredTools = filterTools(this.tools, args.filterMCPTools); const allTools = { ...filteredTools, ...args.tools }; const model = args.model || this.model; let schemaJson; if (typeof args.schema === "object" && "jsonSchema" in args.schema) { schemaJson = args.schema.jsonSchema; } else { schemaJson = zodToJsonSchema(args.schema); } const textResponse = await this.generateResponse({ ...args, system: (args.system || this.system) + ` <Response Format> <Schema Name>${args.schemaName}</Schema Name> <Schema Description>${args.schemaDescription}</Schema Description> <Schema>${JSON.stringify(schemaJson)}</Schema> </Response Format> `, model, tools: allTools, maxSteps: args.maxSteps || DEFAULT_MAX_STEPS }); const response = await generateObject({ ...args, prompt: `Create an object that matches the schema from the response: <Response>${textResponse.text}</Response>`, model }); response.usage.completionTokens += textResponse.usage.completionTokens; response.usage.promptTokens += textResponse.usage.promptTokens; response.usage.totalTokens += textResponse.usage.totalTokens; return { object: response.object, textGenerationResult: textResponse, objectGenerationResult: response, usage: { promptTokens: textResponse.usage.promptTokens + response.usage.promptTokens, completionTokens: textResponse.usage.completionTokens + response.usage.completionTokens, totalTokens: textResponse.usage.totalTokens + response.usage.totalTokens } }; } async close() { const closePromises = [ ...Object.values(this.clients).map( (client) => client ? client.close() : Promise.resolve() ), ...Object.values(this.agents).map( ({ agent }) => agent ? agent.close() : Promise.resolve() ) ]; await Promise.all(closePromises); this.clients = {}; this.agents = {}; } getInfo() { return { name: this.name, description: this.description, tools: Object.keys(this.tools), agents: Object.keys(this.agents), model: this.model, system: this.system }; } }; // src/utils/CrewStyleHelpers.ts var CrewStyleHelpers_exports = {}; __export(CrewStyleHelpers_exports, { executeTask: () => executeTask }); var crewAIStylePrompt = (agent, previousTasks, task) => { return ` <role> ${agent.name}. </role> <backstory> ${agent.backstory} </backstory> ${previousTasks ? ` <previous_tasks> ${Object.entries(previousTasks).map(([key, value]) => `<${key}>${value}</${key}>`).join("\n")} </previous_tasks> ` : ""} <goal> ${agent.goal} </goal> <current_task> ${task.description} </current_task> <expected_output> ${task.expected_output} </expected_output> `; }; var executeTask = async ({ agent, task, previousTasks, schema }) => { const prompt = crewAIStylePrompt(agent, previousTasks, task); if (schema) { const response = await agent.agent.generateObject({ prompt, model: agent.model, schema }); return { object: response.object, textGenerationResult: response.textGenerationResult, objectGenerationResult: response.objectGenerationResult, usage: response.usage }; } else { const response = await agent.agent.generateResponse({ prompt, model: agent.model }); return { text: response.text, textGenerationResult: response, usage: response.usage }; } }; // src/servers/index.ts var servers_exports = {}; __export(servers_exports, { awsKbRetrieval: () => awsKbRetrieval, braveSearch: () => braveSearch, everart: () => everart, fetch: () => fetch, fileSystem: () => fileSystem, firecrawlMcp: () => firecrawlMcp, memory: () => memory, sequentialThinking: () => sequentialThinking, sqlite: () => sqlite }); // src/servers/sequentialThinking.ts var sequentialThinking = { type: "auto", name: "sequential-thinking", description: "An MCP server implementation that provides a tool for dynamic and reflective problem-solving through a structured thinking process. Features include breaking down complex problems into manageable steps, revising thoughts as understanding deepens, branching into alternative reasoning paths, and adjusting the total number of thoughts dynamically.", toolsDescription: { sequential_thinking: "Facilitates a detailed, step-by-step thinking process for problem-solving and analysis. Inputs include the current thought, thought number, total thoughts needed, and options for revisions and branching. Designed for breaking down complex problems, planning with room for revision, and maintaining context over multiple steps." }, gitHubRepo: "https://github.com/modelcontextprotocol/servers", license: "MIT", parameters: {}, mcpConfig: { command: "npx", args: ["-y", "@modelcontextprotocol/server-sequential-thinking"] } }; // src/servers/firecrawlMcp.ts var firecrawlMcp = { type: "auto", name: "firecrawl-mcp", description: "A MCP server implementation that integrates with Firecrawl for web scraping capabilities", toolsDescription: { firecrawl_scrape: "Scrape content from a single URL with advanced options like content filtering, mobile/desktop viewport, and custom wait times.", firecrawl_batch_scrape: "Scrape multiple URLs efficiently with built-in rate limiting and parallel processing.", firecrawl_check_batch_status: "Check the status of a batch scraping operation.", firecrawl_search: "Search the web and optionally extract content from search results.", firecrawl_crawl: "Start an asynchronous crawl with advanced options like depth control and link filtering.", firecrawl_extract: "Extract structured information from web pages using LLM capabilities.", firecrawl_deep_research: "Conduct deep web research on a query using intelligent crawling, search, and LLM analysis.", firecrawl_generate_llmstxt: "Generate a standardized llms.txt file for a given domain defining how LLMs should interact with the site." }, gitHubRepo: "https://github.com/mendableai/firecrawl-mcp-server", license: "MIT", parameters: { FIRECRAWL_API_KEY: { description: "Your FireCrawl API key (required for cloud API)", required: true }, FIRECRAWL_API_URL: { description: "Custom API endpoint for self-hosted instances", required: false }, FIRECRAWL_RETRY_MAX_ATTEMPTS: { description: "Maximum number of retry attempts", required: false }, FIRECRAWL_RETRY_INITIAL_DELAY: { description: "Initial delay in milliseconds before first retry", required: false }, FIRECRAWL_RETRY_MAX_DELAY: { description: "Maximum delay in milliseconds between retries", required: false }, FIRECRAWL_RETRY_BACKOFF_FACTOR: { description: "Exponential backoff multiplier", required: false }, FIRECRAWL_CREDIT_WARNING_THRESHOLD: { description: "Credit usage warning threshold", required: false }, FIRECRAWL_CREDIT_CRITICAL_THRESHOLD: { description: "Credit usage critical threshold", required: false } }, mcpConfig: { command: "npx", args: ["-y", "firecrawl-mcp"] } }; // src/servers/memory.ts var memory = { type: "auto", name: "memory", description: "A basic implementation of persistent memory using a local knowledge graph", toolsDescription: { create_entities: "Create multiple new entities in the knowledge graph with names, types, and observations. Ignores entities with existing names.", create_relations: "Create multiple new relations between entities with source, target, and relationship type. Skips duplicate relations.", add_observations: "Add new observations to existing entities. Returns added observations per entity. Fails if entity doesn't exist.", delete_entities: "Remove entities and their relations with cascading deletion. Silent operation if entity doesn't exist.", delete_observations: "Remove specific observations from entities. Silent operation if observation doesn't exist.", delete_relations: "Remove specific relations from the graph. Silent operation if relation doesn't exist.", read_graph: "Read the entire knowledge graph structure with all entities and relations.", search_nodes: "Search for nodes based on query across entity names, types, and observation content. Returns matching entities and their relations.", open_nodes: "Retrieve specific nodes by name, returning requested entities and relations between them. Silently skips non-existent nodes." }, gitHubRepo: "https://github.com/modelcontextprotocol/servers", license: "MIT", parameters: {}, mcpConfig: { command: "npx", args: ["-y", "@modelcontextprotocol/server-memory"] } }; // src/servers/fetch.ts var fetch = { type: "auto", name: "fetch", description: "A Model Context Protocol server that provides web content fetching capabilities. Enables LLMs to retrieve and process content from web pages, converting HTML to markdown for easier consumption. Supports content truncation and pagination through start_index parameter.", toolsDescription: { fetch: "Fetches a URL from the internet and extracts its contents as markdown. Parameters include url (required), max_length (optional, default: 5000), start_index (optional, default: 0), and raw (optional, default: false)." }, gitHubRepo: "https://github.com/modelcontextprotocol/servers", license: "MIT", parameters: {}, mcpConfig: { command: "uvx", args: ["mcp-server-fetch"] } }; // src/servers/braveSearch.ts var braveSearch = { type: "auto", name: "brave-search", description: "An MCP server implementation that integrates the Brave Search API, providing both web and local search capabilities. Features include web search with pagination and filtering, local search for businesses and services, flexible content filtering, and smart fallbacks from local to web search when needed.", toolsDescription: { brave_web_search: "Execute web searches with pagination and filtering. Inputs include query (string), count (optional number, max 20), and offset (optional number, max 9).", brave_local_search: "Search for local businesses and services. Inputs include query (string) and count (optional number, max 20). Automatically falls back to web search if no local results found." }, gitHubRepo: "https://github.com/modelcontextprotocol/servers", license: "MIT", parameters: { BRAVE_API_KEY: { description: "API key for Brave Search API", required: true } }, mcpConfig: { command: "npx", args: ["-y", "@modelcontextprotocol/server-brave-search"] } }; // src/servers/awsKbRetrieval.ts var awsKbRetrieval = { type: "auto", name: "aws-kb-retrieval", description: "An MCP server implementation for retrieving information from the AWS Knowledge Base using the Bedrock Agent Runtime. Provides RAG (Retrieval-Augmented Generation) capabilities to retrieve context from AWS Knowledge Bases based on queries.", toolsDescription: { retrieve_from_aws_kb: "Perform retrieval operations using the AWS Knowledge Base. Inputs include query (string), knowledgeBaseId (string), and n (optional number, default: 3) for number of results to retrieve." }, gitHubRepo: "https://github.com/modelcontextprotocol/servers", license: "MIT", parameters: { AWS_ACCESS_KEY_ID: { description: "AWS access key ID for authentication", required: true }, AWS_SECRET_ACCESS_KEY: { description: "AWS secret access key for authentication", required: true }, AWS_REGION: { description: "AWS region where the Knowledge Base is located", required: true } }, mcpConfig: { command: "npx", args: ["-y", "@modelcontextprotocol/server-aws-kb-retrieval"] } }; // src/servers/everart.ts var everart = { type: "auto", name: "everart", description: "An MCP server implementation that integrates with EverArt's API for image generation. Supports multiple AI models for image creation with customizable parameters. Generated images are opened in the browser and URLs are returned for reference.", toolsDescription: { generate_image: "Generates images based on text prompts with multiple model options. Parameters include prompt (required), model ID (optional), and image count (optional). Opens the generated image in the browser and returns the URL. All images are generated at 1024x1024 resolution." }, gitHubRepo: "https://github.com/modelcontextprotocol/servers", license: "MIT", parameters: { EVERART_API_KEY: { description: "API key for EverArt image generation API", required: true } }, mcpConfig: { command: "npx", args: ["-y", "@modelcontextprotocol/server-everart"] } }; // src/servers/fileSystem.ts var fileSystem = { type: "auto", name: "filesystem", description: "An MCP server implementation for filesystem operations, providing capabilities to read/write files, create/list/delete directories, move files/directories, search files, and get file metadata. The server only allows operations within directories specified via arguments.", toolsDescription: { read_file: "Read complete contents of a file with UTF-8 encoding. Input: path (string).", read_multiple_files: "Read multiple files simultaneously. Input: paths (string[]). Failed reads won't stop the entire operation.", write_file: "Create new file or overwrite existing. Inputs: path (string) for file location, content (string) for file content.", edit_file: "Make selective edits using advanced pattern matching and formatting. Supports line-based and multi-line content matching, whitespace normalization with indentation preservation, and more.", create_directory: "Create new directory or ensure it exists. Input: path (string). Creates parent directories if needed.", list_directory: "List directory contents with [FILE] or [DIR] prefixes. Input: path (string).", move_file: "Move or rename files and directories. Inputs: source (string), destination (string).", search_files: "Recursively search for files/directories. Inputs: path (string) for starting directory, pattern (string) for search pattern, excludePatterns (string[]) for excluding patterns.", get_file_info: "Get detailed file/directory metadata including size, timestamps, type, and permissions. Input: path (string).", list_allowed_directories: "List all directories the server is allowed to access. No input required." }, gitHubRepo: "https://github.com/modelcontextprotocol/servers", license: "MIT", parameters: {}, mcpConfig: { command: "npx", args: ["-y", "@modelcontextprotocol/server-filesystem", "~/Documents"] } }; // src/servers/sqlite.ts var sqlite = { type: "auto", name: "sqlite", description: "A Model Context Protocol (MCP) server implementation that provides database interaction and business intelligence capabilities through SQLite. This server enables running SQL queries, analyzing business data, and automatically generating business insight memos. Features include executing read/write queries, managing database schema, and generating business insights.", toolsDescription: { read_query: "Execute SELECT queries to read data from the database.", write_query: "Execute INSERT, UPDATE, or DELETE queries to modify data.", create_table: "Create new tables in the database.", list_tables: "Get a list of all tables in the database.", describe_table: "View schema information for a specific table.", append_insight: "Add new business insights to the memo resource." }, gitHubRepo: "https://github.com/modelcontextprotocol/servers", license: "MIT", parameters: { SQLITE_DB_PATH: { description: "Path to the SQLite database file", required: true } }, mcpConfig: { command: "docker", args: [ "run", "--rm", "-i", "-v", "mcp-test:/mcp", "mcp/sqlite", "--db-path", "${SQLITE_DB_PATH}" ] } }; export { AIAgent, CrewStyleHelpers_exports as CrewStyleHelpers, servers_exports as Servers }; //# sourceMappingURL=index.js.map //# sourceMappingURL=index.js.map