MCP Chat Analysis Server

  • src
#!/usr/bin/env node import process from 'process'; if (process.env.NODE_ENV !== 'development') { process.removeAllListeners('warning'); } const originalConsoleLog = console.log; console.log = (message, ...args) => { if (!message.startsWith("Warning: ")) { originalConsoleLog(message, ...args); } }; import { Server } from "@modelcontextprotocol/sdk/server/index.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { CallToolRequestSchema, ListResourcesRequestSchema, ListToolsRequestSchema, ReadResourceRequestSchema, } from "@modelcontextprotocol/sdk/types.js"; import { BoxClient, BoxDeveloperTokenAuth, BoxJwtAuth, JwtConfig } from "box-typescript-sdk-gen"; import * as pdfjsLib from "pdfjs-dist/legacy/build/pdf.mjs"; import * as mammoth from "mammoth"; console.log = originalConsoleLog; // Initialize Box client based on auth method let auth: BoxDeveloperTokenAuth | BoxJwtAuth; if (process.env.BOX_DEV_TOKEN) { auth = new BoxDeveloperTokenAuth({ token: process.env.BOX_DEV_TOKEN }); } else if (process.env.BOX_JWT_CONFIG_PATH && process.env.BOX_USER_ID) { const jwtConfig = JwtConfig.fromConfigFile(process.env.BOX_JWT_CONFIG_PATH); const jwtAuth = new BoxJwtAuth({ config: jwtConfig }); const userAuth = jwtAuth.withUserSubject(process.env.BOX_USER_ID); auth = userAuth; } else if (process.env.BOX_JWT && process.env.BOX_USER_ID) { const jwtConfig = JwtConfig.fromConfigJsonString(process.env.BOX_JWT); const jwtAuth = new BoxJwtAuth({ config: jwtConfig }); const userAuth = jwtAuth.withUserSubject(process.env.BOX_USER_ID); auth = userAuth; } else if (process.env.BOX_JWT_BASE64 && process.env.BOX_USER_ID) { const decodedJwt = Buffer.from(process.env.BOX_JWT_BASE64, "base64").toString("utf-8"); const jwtConfig = JwtConfig.fromConfigJsonString(decodedJwt); const jwtAuth = new BoxJwtAuth({ config: jwtConfig }); const userAuth = jwtAuth.withUserSubject(process.env.BOX_USER_ID); auth = userAuth; } else { throw new Error("BOX_DEV_TOKEN or BOX_USER_ID && (BOX_JWT || BOX_JWT_CONFIG_PATH) must be set as environment variable(s)"); } const client = new BoxClient({ auth }); // Shared file reading functionality async function readBoxFile(fileId: string) { // Get file info to check type const fileInfo = await client.files.getFileById(fileId); const fileType = fileInfo.name?.split(".").pop()?.toLowerCase(); if (!fileType) { throw new Error("Failed to determine file type"); } // Download file content const fileContentStream = await client.downloads.downloadFile(fileId); if (!fileContentStream) { throw new Error("Failed to download file content"); } const fileContent = await new Promise<Buffer>((resolve, reject) => { const chunks: Buffer[] = []; fileContentStream.on("data", (chunk) => chunks.push(chunk)); fileContentStream.on("end", () => resolve(Buffer.concat(chunks))); fileContentStream.on("error", reject); }); let textContent; // Process based on file type switch (fileType) { case "pdf": // Load PDF document const loadingTask = pdfjsLib.getDocument({ data: new Uint8Array(fileContent.buffer), useSystemFonts: true, verbosity: 0, }); const pdfDocument = await loadingTask.promise; // Extract text from all pages let extractedPages = []; for (let i = 1; i <= pdfDocument.numPages; i++) { const page = await pdfDocument.getPage(i); const content = await page.getTextContent(); const pageText = content.items .map((item) => ("str" in item ? item.str : "")) .join(" "); extractedPages.push(pageText); } textContent = extractedPages.join("\n\n"); break; case "doc": case "docx": const result = await mammoth.extractRawText({ buffer: Buffer.from(fileContent.buffer), }); textContent = result.value; break; case "txt": case "md": case "json": case "csv": // For text files, convert buffer to string textContent = fileContent.toString(); break; default: throw new Error(`Unsupported file type: ${fileType}`); } return textContent; } // Initialize the MCP server const server = new Server( { name: 'box-mcp-server', version: '0.3.1', }, { capabilities: { resources: {}, tools: {}, }, } ); // List available resources server.setRequestHandler(ListResourcesRequestSchema, async () => { try { const rootFolder = await client.folders.getFolderItems("0"); const resources = rootFolder.entries?.map((entry) => ({ uri: `box://${entry.type}/${entry.id}`, name: entry.name || "Untitled", description: entry.type === "folder" ? `Box folder - use this URI to list contents` : `Box file - use this URI to read contents`, mimeType: entry.type === "file" ? "text/plain" : undefined, })) || []; return { resources, description: "Shows items in Box root folder. Use folder URIs to traverse deeper, or use the search tool to find specific items.", }; } catch (error) { console.error("Error listing resources:", error); throw error; } }); // Read resource content server.setRequestHandler(ReadResourceRequestSchema, async (request) => { const uri = request.params.uri as string; // Validate URI format if (!uri.startsWith("box://file/")) { throw new Error( "Invalid URI format. Must start with box://file/ but was " + uri ); } // Extract file ID from URI const fileId = uri.replace("box://file/", ""); try { const textContent = await readBoxFile(fileId); return { contents: [ { type: "text", text: textContent, uri: request.params.uri, }, ], }; } catch (error) { console.error("Error reading file:", error); throw error; } }); // Add search tool listing server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools: [ { name: "search", description: "Search for files and folders across all of Box (not limited to root folder). Returns URIs that can be used with resource commands.", inputSchema: { type: "object", properties: { query: { type: "string", description: "Search query - finds items anywhere in Box, not just root folder", }, }, required: ["query"], }, }, { name: "read", description: "Read the content of a Box file directly", inputSchema: { type: "object", properties: { uri: { type: "string", description: "Box resource URI of the file to read (must start with box://file/)", }, }, required: ["uri"], }, }, ], }; }); // Add tool handlers server.setRequestHandler(CallToolRequestSchema, async (request) => { // read tool if (request.params.name === "read") { const uri = request.params.arguments?.uri as string; // Validate URI format if (!uri.startsWith("box://file/")) { throw new Error( "Invalid URI format. Must start with box://file/ but was " + uri ); } // Extract file ID from URI const fileId = uri.replace("box://file/", ""); try { const textContent = await readBoxFile(fileId); return { content: [ { type: "text", text: textContent, }, ], }; } catch (error) { console.error("Error reading file:", error); throw error; } } // search tool if (request.params.name === "search") { const query = request.params.arguments?.query as string; try { const searchResults = await client.search.searchForContent({ query, limit: 20, }); const resources = searchResults.entries?.reduce( (acc, entry) => { if ("id" in entry && "type" in entry) { acc.push({ uri: `box://${entry.type}/${entry.id}`, name: entry.name || `Untitled ${entry.type}`, description: `Box ${entry.type}`, mimeType: entry.type === "file" ? "text/plain" : undefined, }); } return acc; }, [] as Array<{ uri: string; name: string; description: string; mimeType?: string; }> ) || []; // Format the results as text and include resource URIs const formattedResults = resources .map((resource) => `- ${resource.name} (${resource.uri})`) .join("\n"); return { content: [ { type: "text", text: `Found ${resources.length} items matching "${query}":\n\n${formattedResults}`, }, ], }; } catch (error) { console.error("Error performing search:", error); return { content: [ { type: "text", text: `Error performing search: ${error instanceof Error ? error.message : "Unknown error"}`, }, ], }; } } throw new Error(`Unknown tool: ${request.params.name}`); }); const transport = new StdioServerTransport(); await server.connect(transport);