diff --git a/README.md b/README.md index 8bb5424..f75de12 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,17 @@ # LlamaCloud MCP Server -A MCP server connecting to a managed index on [LlamaCloud](https://cloud.llamaindex.ai/) +A MCP server connecting to multiple managed indexes on [LlamaCloud](https://cloud.llamaindex.ai/) -This is a TypeScript-based MCP server that implements a connection to a managed index on LlamaCloud. +This is a TypeScript-based MCP server that creates multiple tools, each connected to a specific managed index on LlamaCloud. Each tool is defined through command-line arguments. LlamaCloud Server MCP server ## Features ### Tools -- `get_information` - Get information from your knowledge base to answer questions. - - Takes query as required parameters +- Creates a separate tool for each index you define +- Each tool provides a `query` parameter to search its specific index +- Auto-generates tool names like `get_information_index_name` based on index names ## Development @@ -42,10 +43,11 @@ On Windows: `%APPDATA%/Claude/claude_desktop_config.json` "llamacloud": { "command": "node", "args": [ - "/path/to/llamacloud/build/index.js" + "/path/to/llamacloud/build/index.js", + "--index", "10k-SEC-Tesla", "--description", "10k SEC documents from 2023 for Tesla", + "--index", "10k-SEC-Apple", "--description", "10k SEC documents from 2023 for Apple" ], "env": { - "LLAMA_CLOUD_INDEX_NAME": "", "LLAMA_CLOUD_PROJECT_NAME": "", "LLAMA_CLOUD_API_KEY": "" } @@ -54,6 +56,24 @@ On Windows: `%APPDATA%/Claude/claude_desktop_config.json` } ``` +### Tool Definition Format + +You can define multiple tools by providing pairs of `--index` and `--description` arguments. Each tool definition follows this format: + +``` +--index "IndexName" --description "Description text" +``` + +For example: + +```bash +node build/index.js --index "10k-SEC-Tesla" --description "10k SEC documents from 2023 for Tesla" --index "10k-SEC-Apple" --description "10k SEC documents from 2023 for Apple" +``` + +This will create two tools: +1. `get_information_10k_sec_tesla` - For querying the 10k-SEC-Tesla index +2. `get_information_10k_sec_apple` - For querying the 10k-SEC-Apple index + ### Debugging Since MCP servers communicate over stdio, debugging can be challenging. We recommend using the [MCP Inspector](https://github.com/modelcontextprotocol/inspector), which is available as a package script: diff --git a/src/index.ts b/src/index.ts index d1c0673..72fd4d6 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,7 +1,8 @@ #!/usr/bin/env node /** - * This is a MCP server that connects to a managed index on LlamaCloud. + * This is a MCP server that connects to multiple managed indexes on LlamaCloud. + * Each index is exposed as a separate tool. */ import { Server } from "@modelcontextprotocol/sdk/server/index.js"; @@ -12,6 +13,58 @@ import { ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js"; +// Define the tool definition interface +interface ToolDefinition { + indexName: string; + description: string; + toolName?: string; +} + +// Parse command line arguments +function parseToolDefinitions(): ToolDefinition[] { + const args = process.argv.slice(2); + if (args.length === 0) { + console.error('No tool definitions provided. Use format: --index "IndexName" --description "Description"'); + process.exit(1); + } + + const toolDefinitions: ToolDefinition[] = []; + let currentIndexName: string | null = null; + + for (let i = 0; i < args.length; i++) { + if (args[i] === '--index' && i + 1 < args.length) { + // Save the current index name. We'll wait for the description to complete the definition + currentIndexName = args[i + 1].trim(); + i++; // Skip the next argument since we consumed it + } else if (args[i] === '--description' && i + 1 < args.length && currentIndexName) { + // We have both an index name and a description, so we can create a tool definition + const description = args[i + 1].trim(); + const toolName = `get_information_${currentIndexName.toLowerCase().replace(/[^a-z0-9]/g, '_')}`; + + toolDefinitions.push({ + indexName: currentIndexName, + description, + toolName + }); + + // Reset for the next pair + currentIndexName = null; + i++; // Skip the next argument since we consumed it + } + } + + // Check if we have an index without a description at the end + if (currentIndexName) { + console.warn(`Warning: Index '${currentIndexName}' was specified without a description.`); + } + + if (toolDefinitions.length === 0) { + console.error('No valid tool definitions found. Use format: --index "IndexName" --description "Description"'); + process.exit(1); + } + + return toolDefinitions; +} /** * Create an MCP server with capabilities for tools @@ -28,67 +81,80 @@ const server = new Server( } ); -const index = new LlamaCloudIndex({ - name: process.env.LLAMA_CLOUD_INDEX_NAME || (() => { throw new Error('LLAMA_CLOUD_INDEX_NAME is not set') })(), - projectName: process.env.LLAMA_CLOUD_PROJECT_NAME || (() => { throw new Error('LLAMA_CLOUD_PROJECT_NAME is not set') })(), - apiKey: process.env.LLAMA_CLOUD_API_KEY || (() => { throw new Error('LLAMA_CLOUD_API_KEY is not set') })(), -}); +// Get the project name and API key from environment variables +const projectName = process.env.LLAMA_CLOUD_PROJECT_NAME || (() => { throw new Error('LLAMA_CLOUD_PROJECT_NAME is not set') })(); +const apiKey = process.env.LLAMA_CLOUD_API_KEY || (() => { throw new Error('LLAMA_CLOUD_API_KEY is not set') })(); + +// Parse tool definitions from command line arguments +const toolDefinitions = parseToolDefinitions(); + +// Create indexes for each tool definition +const indexes = new Map(); + +for (const definition of toolDefinitions) { + const index = new LlamaCloudIndex({ + name: definition.indexName, + projectName, + apiKey, + }); + + indexes.set(definition.toolName!, index); + console.log(`Created index for tool ${definition.toolName}: ${definition.indexName} - ${definition.description}`); +} /** * Handler that lists available tools. - * Exposes a single "get_information" tool that lets clients retrieve information from the LlamaIndex. + * Exposes a tool for each index that lets clients retrieve information. */ server.setRequestHandler(ListToolsRequestSchema, async () => { return { - tools: [ - { - name: "get_information", - description: "Get information from your knowledge base to answer questions.", - inputSchema: { - type: "object", - properties: { - query: { - type: "string", - description: "The query used to get information about your knowledge base." - }, + tools: toolDefinitions.map(definition => ({ + name: definition.toolName!, + description: `Get information from the ${definition.indexName} index. The index contains ${definition.description}`, + inputSchema: { + type: "object", + properties: { + query: { + type: "string", + description: `The query used to get information from the ${definition.indexName} index.` }, - required: ["query"] - } + }, + required: ["query"] } - ] + })) }; }); /** - * Handler for the get_information tool. - * Retrieves information from the LlamaIndex and returns the result as text. + * Handler for tool calls. + * Routes requests to the appropriate index based on the tool name. */ server.setRequestHandler(CallToolRequestSchema, async (request) => { - switch (request.params.name) { - case "get_information": { - const query = String(request.params.arguments?.query); - if (!query) { - throw new Error("query parameter is required"); - } - - const retriever = index.asRetriever(); - const nodesWithScore = await retriever.retrieve({ query }); - - const nodes = nodesWithScore.map((node) => node.node); - const context = nodes.map((r) => r.getContent(MetadataMode.NONE)).join("\n\n"); - - return { - content: [{ - type: "text", - text: context, - }] - }; - } - - default: - throw new Error("Unknown tool"); + const toolName = request.params.name; + const index = indexes.get(toolName); + + if (!index) { + throw new Error(`Unknown tool: ${toolName}`); } -}); + + const query = String(request.params.arguments?.query); + if (!query) { + throw new Error("query parameter is required"); + } + + const retriever = index.asRetriever(); + const nodesWithScore = await retriever.retrieve({ query }); + + const nodes = nodesWithScore.map((node) => node.node); + const context = nodes.map((r) => r.getContent(MetadataMode.NONE)).join("\n\n"); + + return { + content: [{ + type: "text", + text: context, + }] + }; +}); @@ -97,6 +163,11 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => { * This allows the server to communicate via standard input/output streams. */ async function main() { + console.log(`Starting MCP server with ${toolDefinitions.length} tools:`); + toolDefinitions.forEach(def => { + console.log(`- ${def.toolName}: ${def.indexName} - ${def.description}`); + }); + const transport = new StdioServerTransport(); await server.connect(transport); }