#!/usr/bin/env node /** * Tool Compression MCP Server * * Provides compressed versions of Read/Grep/Glob operations * to reduce context token usage by 40-50% * * Tools: * - compressed_read: Read file with comment/blank removal * - compressed_grep: Search with grouped/deduped results * - compressed_glob: File listing with collapsed directories */ import { Server } from "@modelcontextprotocol/sdk/server/index.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js"; import { readFileSync, existsSync, statSync } from "fs"; import { execSync } from "child_process"; import { compressRead } from "./compressors/read.js"; import { compressGrep } from "./compressors/grep.js"; import { compressGlob } from "./compressors/glob.js"; // Configuration from environment const COMPRESSION_LEVEL = process.env.COMPRESSION_LEVEL || "medium"; // Compression presets const PRESETS = { light: { read: { removeBlankLines: true, removeComments: false, collapseImports: false, maxLines: 1000 }, grep: { maxMatchesPerFile: 5, maxTotalMatches: 50, dedupeAdjacent: false }, glob: { maxFiles: 50, collapseDepth: 6 }, }, medium: { read: { removeBlankLines: true, removeComments: true, collapseImports: true, maxLines: 500 }, grep: { maxMatchesPerFile: 3, maxTotalMatches: 20, dedupeAdjacent: true }, glob: { maxFiles: 30, collapseDepth: 4 }, }, aggressive: { read: { removeBlankLines: true, removeComments: true, collapseImports: true, maxLines: 200 }, grep: { maxMatchesPerFile: 2, maxTotalMatches: 10, dedupeAdjacent: true }, glob: { maxFiles: 15, collapseDepth: 3 }, }, }; const preset = PRESETS[COMPRESSION_LEVEL] || PRESETS.medium; // Create MCP server const server = new Server({ name: "tool-compression-mcp", version: "1.0.0", }, { capabilities: { tools: {}, }, }); // List available tools server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools: [ { name: "compressed_read", description: "Read a file with compression: removes blank lines, comments, and collapses imports. " + "Use this instead of Read for large files to save context tokens. " + "Returns content with line numbers preserved for reference.", inputSchema: { type: "object", properties: { path: { type: "string", description: "Absolute path to the file to read", }, maxLines: { type: "number", description: "Maximum lines to return (default: 500)", }, keepComments: { type: "boolean", description: "Keep comment lines (default: false)", }, }, required: ["path"], }, }, { name: "compressed_grep", description: "Search files with compressed results: groups by file, shows top matches, dedupes adjacent. " + "Use this instead of Grep when expecting many matches. " + "Returns summary with match counts per file.", inputSchema: { type: "object", properties: { pattern: { type: "string", description: "Regex pattern to search for", }, path: { type: "string", description: "Directory or file to search in", }, glob: { type: "string", description: "File glob pattern (e.g., '*.ts')", }, maxMatchesPerFile: { type: "number", description: "Max matches to show per file (default: 3)", }, }, required: ["pattern"], }, }, { name: "compressed_glob", description: "List files with compression: collapses deep paths, groups by directory, shows file type distribution. " + "Use this instead of Glob when expecting many files. " + "Returns structured summary with counts.", inputSchema: { type: "object", properties: { pattern: { type: "string", description: "Glob pattern (e.g., '**/*.ts')", }, path: { type: "string", description: "Base directory to search from", }, maxFiles: { type: "number", description: "Max files to list (default: 30)", }, }, required: ["pattern"], }, }, ], }; }); // Handle tool calls server.setRequestHandler(CallToolRequestSchema, async (request) => { const { name, arguments: args } = request.params; try { switch (name) { case "compressed_read": { const path = args?.path; if (!path) { return { content: [{ type: "text", text: "Error: path is required" }] }; } if (!existsSync(path)) { return { content: [{ type: "text", text: `Error: File not found: ${path}` }] }; } const stats = statSync(path); if (stats.isDirectory()) { return { content: [{ type: "text", text: `Error: Path is a directory: ${path}` }] }; } const content = readFileSync(path, "utf-8"); const options = { ...preset.read, maxLines: args?.maxLines || preset.read.maxLines, removeComments: !args?.keepComments, }; const result = compressRead(content, path, options); return { content: [ { type: "text", text: `📄 ${path}\n` + `[Compressed: ${result.originalLines} → ${result.compressedLines} lines (${result.savings} saved)]\n\n` + result.content, }, ], }; } case "compressed_grep": { const pattern = args?.pattern; if (!pattern) { return { content: [{ type: "text", text: "Error: pattern is required" }] }; } const searchPath = args?.path || "."; const glob = args?.glob; // Build ripgrep command let cmd = `rg -n "${pattern.replace(/"/g, '\\"')}"`; if (glob) { cmd += ` --glob "${glob}"`; } cmd += ` "${searchPath}" 2>/dev/null || true`; let output; try { output = execSync(cmd, { encoding: "utf-8", maxBuffer: 10 * 1024 * 1024 }); } catch { output = ""; } const options = { ...preset.grep, maxMatchesPerFile: args?.maxMatchesPerFile || preset.grep.maxMatchesPerFile, }; const result = compressGrep(output, options); return { content: [ { type: "text", text: `🔍 Search: "${pattern}"${glob ? ` (${glob})` : ""}\n` + `[Found ${result.originalMatches} matches in ${result.filesMatched} files, showing ${result.compressedMatches} (${result.savings} compressed)]\n\n` + result.content, }, ], }; } case "compressed_glob": { const pattern = args?.pattern; if (!pattern) { return { content: [{ type: "text", text: "Error: pattern is required" }] }; } const basePath = args?.path || "."; // Use find or fd for globbing let cmd; try { // Try fd first (faster) execSync("which fd", { encoding: "utf-8" }); cmd = `fd --type f "${pattern}" "${basePath}" 2>/dev/null || true`; } catch { // Fall back to find cmd = `find "${basePath}" -type f -name "${pattern}" 2>/dev/null || true`; } let output; try { output = execSync(cmd, { encoding: "utf-8", maxBuffer: 10 * 1024 * 1024 }); } catch { output = ""; } const paths = output.split("\n").filter((p) => p.trim()); const options = { ...preset.glob, maxFiles: args?.maxFiles || preset.glob.maxFiles, }; const result = compressGlob(paths, options); return { content: [ { type: "text", text: `📁 Glob: "${pattern}" in ${basePath}\n` + `[${result.savings} compression]\n\n` + result.content, }, ], }; } default: return { content: [{ type: "text", text: `Unknown tool: ${name}` }], }; } } catch (error) { const message = error instanceof Error ? error.message : String(error); return { content: [{ type: "text", text: `Error: ${message}` }], }; } }); // Start server async function main() { const transport = new StdioServerTransport(); await server.connect(transport); console.error("Tool Compression MCP server started"); } main().catch(console.error);