Skip to main content
Glama

Tree-Hugger-JS MCP Server

by qckfx
index.ts42.1 kB
#!/usr/bin/env node 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 { parse, TreeHugger, NodeWrapper, FunctionInfo, ClassInfo, ImportInfo, NodeInfo, TransformOperation } from "tree-hugger-js"; import { readFileSync, existsSync } from "fs"; import { resolve } from "path"; interface ParsedAST { tree: TreeHugger; filePath?: string; sourceCode: string; language: string; timestamp: Date; functions?: FunctionInfo[]; classes?: ClassInfo[]; imports?: ImportInfo[]; } interface AnalysisResult { functions: FunctionInfo[]; classes: ClassInfo[]; imports: ImportInfo[]; exports: NodeInfo[]; variables: NodeInfo[]; comments: NodeInfo[]; jsxComponents?: NodeInfo[]; timestamp: Date; } interface TransformResult { operation: string; parameters: TransformOperation["parameters"]; preview: string; timestamp: Date; } class TreeHuggerMCPServer { private server: Server; private currentAST: ParsedAST | null = null; private lastAnalysis: AnalysisResult | null = null; private transformHistory: TransformResult[] = []; constructor() { this.server = new Server( { name: "tree-hugger-js-mcp", version: "1.0.0", }, { capabilities: { resources: {}, tools: {}, }, } ); this.setupHandlers(); } private setupHandlers() { this.server.setRequestHandler(ListResourcesRequestSchema, async () => ({ resources: [ { uri: "ast://current", mimeType: "application/json", name: "Current AST State", description: "Currently parsed AST with metadata and basic statistics", }, { uri: "ast://analysis", mimeType: "application/json", name: "Last Analysis Results", description: "Results from the most recent code analysis (functions, classes, imports, etc.)", }, { uri: "ast://transforms", mimeType: "application/json", name: "Transformation History", description: "History of code transformations and available operations", }, ], })); this.server.setRequestHandler(ReadResourceRequestSchema, async (request) => { const { uri } = request.params; switch (uri) { case "ast://current": if (!this.currentAST) { return { contents: [ { uri, mimeType: "application/json", text: JSON.stringify({ error: "No AST currently loaded" }, null, 2), }, ], }; } const astData = { filePath: this.currentAST.filePath, language: this.currentAST.language, timestamp: this.currentAST.timestamp, lineCount: this.currentAST.sourceCode.split('\n').length, characterCount: this.currentAST.sourceCode.length, hasParseErrors: this.currentAST.tree.root.hasError, rootNodeType: this.currentAST.tree.root.type, childrenCount: this.currentAST.tree.root.children.length, }; return { contents: [ { uri, mimeType: "application/json", text: JSON.stringify(astData, null, 2), }, ], }; case "ast://analysis": if (!this.lastAnalysis) { return { contents: [ { uri, mimeType: "application/json", text: JSON.stringify({ error: "No analysis results available" }, null, 2), }, ], }; } return { contents: [ { uri, mimeType: "application/json", text: JSON.stringify(this.lastAnalysis, null, 2), }, ], }; case "ast://transforms": const transformData = { history: this.transformHistory, availableOperations: [ "rename_identifier", "remove_unused_imports", "transform_code", "insert_code" ], }; return { contents: [ { uri, mimeType: "application/json", text: JSON.stringify(transformData, null, 2), }, ], }; default: throw new Error(`Unknown resource: ${uri}`); } }); this.server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: [ { name: "parse_code", description: "Parse JavaScript/TypeScript code from file or string and load it into the AST state. Must be called before using other analysis tools.\n\nExamples:\n• Parse a React component: parse_code('./src/UserProfile.jsx')\n• Parse code string: parse_code('function hello() { return \"world\"; }')\n• Parse with explicit language: parse_code('./config.js', language='javascript')\n• Analyze legacy code: parse_code('./old-script.js') then use other tools to understand structure\n• Code review prep: parse_code('./feature.ts') then get_functions() to review all functions", inputSchema: { type: "object", properties: { source: { type: "string", description: "File path (./src/app.js) or code string ('const x = 1;')" }, isFilePath: { type: "boolean", description: "Whether source is a file path (true) or code string (false). Defaults to auto-detect." }, language: { type: "string", description: "Language to use (javascript, typescript, jsx, tsx). Auto-detected if not provided." } }, required: ["source"], }, }, { name: "find_pattern", description: "Find first node matching the specified pattern using tree-hugger-js intuitive syntax. Use for targeted searches when you need one specific match.\n\nExamples:\n• Find main function: find_pattern('function[name=\"main\"]')\n• Find React component: find_pattern('function[name=\"UserProfile\"]')\n• Find async functions: find_pattern('function[async]')\n• Find specific class: find_pattern('class[name=\"UserManager\"]')\n• Find error handling: find_pattern('call[text*=\"catch\"]')\n• Find JSX with props: find_pattern('jsx:has(jsx-attribute[name=\"className\"])')\n• Debug specific calls: find_pattern('call[text*=\"console.log\"]')", inputSchema: { type: "object", properties: { pattern: { type: "string", description: "Pattern using intuitive syntax: 'function', 'class[name=\"MyClass\"]', 'function[async]', 'call[text*=\"fetch\"]'" } }, required: ["pattern"], }, }, { name: "find_all_pattern", description: "Find all nodes matching the specified pattern. Use for comprehensive analysis when you need all matches.\n\nExamples:\n• Audit all functions: find_all_pattern('function')\n• Find all TODO comments: find_all_pattern('comment[text*=\"TODO\"]')\n• Security audit: find_all_pattern('call[text*=\"eval\"]')\n• Performance review: find_all_pattern('call[text*=\"console.log\"]') to find debug logs\n• API usage: find_all_pattern('call[text*=\"fetch\"]') to find all API calls\n• React hooks: find_all_pattern('call[text*=\"use\"]') for hooks usage\n• Error patterns: find_all_pattern('string[text*=\"error\"]') for error messages\n• Database queries: find_all_pattern('string[text*=\"SELECT\"]') for SQL\n• Event handlers: find_all_pattern('function[text*=\"onClick\"]')", inputSchema: { type: "object", properties: { pattern: { type: "string", description: "Pattern to match: 'function', 'call[text*=\"console.log\"]', 'string[text*=\"TODO\"]'" }, limit: { type: "number", description: "Maximum number of matches to return (default: no limit). Use for large codebases." } }, required: ["pattern"], }, }, { name: "get_functions", description: "Get all functions with metadata including name, type, location, and async status. Includes class methods, arrow functions, and declarations.\n\nExamples:\n• Code review: get_functions() to see all functions in a file\n• Find async operations: get_functions({asyncOnly: true})\n• API analysis: get_functions() then look for functions with 'fetch' or 'api' in names\n• Test coverage: get_functions() to identify functions needing tests\n• Refactoring prep: get_functions({includeAnonymous: false}) to focus on named functions\n• Performance audit: get_functions() to find large/complex functions by line count", inputSchema: { type: "object", properties: { includeAnonymous: { type: "boolean", description: "Include anonymous functions (default: true). Set false to focus on named functions only." }, asyncOnly: { type: "boolean", description: "Only return async functions (default: false). Use for async/await pattern analysis." } }, }, }, { name: "get_classes", description: "Get all classes with comprehensive method and property analysis. Perfect for OOP code review.\n\nExamples:\n• Architecture review: get_classes() to understand class structure\n• API design: get_classes() to see public method interfaces\n• Inheritance analysis: get_classes() to identify class hierarchies\n• Method-only view: get_classes({includeProperties: false}) to focus on behavior\n• Property audit: get_classes({includeMethods: false}) to review state management\n• Testing prep: get_classes() to identify methods needing unit tests", inputSchema: { type: "object", properties: { includeProperties: { type: "boolean", description: "Include class properties (default: true). Set false to focus only on methods." }, includeMethods: { type: "boolean", description: "Include class methods (default: true). Set false to focus only on properties." } }, }, }, { name: "get_imports", description: "Get all import statements with detailed module and specifier information. Essential for dependency analysis.\n\nExamples:\n• Dependency audit: get_imports() to see all external dependencies\n• Bundle analysis: get_imports() to identify heavy imports\n• Security audit: get_imports() to check for suspicious packages\n• TypeScript analysis: get_imports({includeTypeImports: false}) to focus on runtime imports\n• Refactoring prep: get_imports() to understand module structure before changes\n• License compliance: get_imports() to generate dependency list", inputSchema: { type: "object", properties: { includeTypeImports: { type: "boolean", description: "Include TypeScript type-only imports (default: true). Set false for runtime dependency analysis." } }, }, }, { name: "rename_identifier", description: "Intelligently rename all occurrences of an identifier throughout the code. Avoids renaming in strings/comments.\n\nExamples:\n• Refactor function names: rename_identifier('fetchData', 'fetchUserData')\n• Improve variable names: rename_identifier('data', 'userData')\n• Update class names: rename_identifier('Manager', 'UserManager')\n• API consistency: rename_identifier('getUserInfo', 'fetchUserInfo')\n• Preview first: rename_identifier('oldName', 'newName', {preview: true})\n• Legacy code update: rename_identifier('XMLHttpRequest', 'fetch')", inputSchema: { type: "object", properties: { oldName: { type: "string", description: "Current identifier name to find and replace" }, newName: { type: "string", description: "New identifier name (should be valid JavaScript identifier)" }, preview: { type: "boolean", description: "Return preview only without applying changes (default: false). Always preview first for safety." } }, required: ["oldName", "newName"], }, }, { name: "remove_unused_imports", description: "Automatically remove unused import statements to clean up code. Safely detects which imports are actually used.\n\nExamples:\n• Bundle size optimization: remove_unused_imports() to reduce bundle size\n• Code cleanup: remove_unused_imports() after refactoring\n• Linting compliance: remove_unused_imports() to fix ESLint warnings\n• Before deployment: remove_unused_imports({preview: true}) to see what will be removed\n• Legacy cleanup: remove_unused_imports() after removing old code\n• Development workflow: remove_unused_imports() during feature development", inputSchema: { type: "object", properties: { preview: { type: "boolean", description: "Return preview only without applying changes (default: false). Use to see what will be removed." } }, }, }, { name: "transform_code", description: "Apply multiple transformations in a single operation. Most powerful tool for complex refactoring workflows.\n\nExamples:\n• API refactor: [{type: 'rename', parameters: {oldName: 'getData', newName: 'fetchData'}}, {type: 'removeUnusedImports'}]\n• Environment update: [{type: 'replaceIn', parameters: {nodeType: 'string', pattern: /localhost/g, replacement: 'api.production.com'}}, {type: 'removeUnusedImports'}]\n• Add logging: [{type: 'insertAfter', parameters: {pattern: 'function_declaration', text: 'console.log(\"Function called\");'}}, {type: 'removeUnusedImports'}]\n• Bulk rename: [{type: 'rename', parameters: {oldName: 'user', newName: 'customer'}}, {type: 'rename', parameters: {oldName: 'id', newName: 'customerId'}}]\n• Legacy migration: [{type: 'replaceIn', parameters: {nodeType: 'call_expression', pattern: /XMLHttpRequest/g, replacement: 'fetch'}}, {type: 'removeUnusedImports'}]", inputSchema: { type: "object", properties: { operations: { type: "array", items: { type: "object", properties: { type: { type: "string", enum: ["rename", "removeUnusedImports", "replaceIn", "insertBefore", "insertAfter"] }, parameters: { type: "object", description: "Parameters: rename{oldName,newName}, replaceIn{nodeType,pattern,replacement}, insert{pattern,text}" } }, required: ["type"] }, description: "Array of transformation operations applied in sequence. Use preview:true first!" }, preview: { type: "boolean", description: "Return preview only without applying changes (default: false). ALWAYS preview complex transformations first." } }, required: ["operations"], }, }, { name: "get_node_at_position", description: "Get detailed AST node information at a specific cursor position. Perfect for debugging and precise analysis.\n\nExamples:\n• Debug syntax errors: get_node_at_position(15, 23) to understand what's at error location\n• Understand code structure: get_node_at_position(line, col) to see AST node type at cursor\n• Refactoring assistance: get_node_at_position(line, col) to identify exact node before transformation\n• IDE integration: get_node_at_position(line, col) for hover information\n• Pattern development: get_node_at_position(line, col) to understand node structure for pattern writing", inputSchema: { type: "object", properties: { line: { type: "number", description: "Line number (1-based) - the line where cursor is positioned" }, column: { type: "number", description: "Column number (0-based) - the character position within the line" } }, required: ["line", "column"], }, }, { name: "analyze_scopes", description: "Analyze variable scopes, bindings, and potential naming conflicts. Advanced tool for code quality analysis.\n\nExamples:\n• Variable shadowing detection: analyze_scopes() to find naming conflicts\n• Closure analysis: analyze_scopes() to understand variable capture\n• Refactoring safety: analyze_scopes() before variable renames\n• Code review: analyze_scopes() to identify scope-related issues\n• Learning aid: analyze_scopes({includeBuiltins: true}) to see all identifiers\n• Dead code detection: analyze_scopes() to find unused variables", inputSchema: { type: "object", properties: { includeBuiltins: { type: "boolean", description: "Include built-in identifiers (default: false). Set true for comprehensive analysis including globals." } }, }, }, { name: "insert_code", description: "Insert code before or after nodes with smart formatting. Professional-quality code insertion with proper indentation.\n\nExamples:\n• Add logging: insert_code('function_declaration', 'console.log(\"Function started\");', 'after')\n• Add validation: insert_code('method_definition[name=\"save\"]', 'if (!this.isValid()) return;', 'after')\n• Add comments: insert_code('class_declaration', '// Main user management class', 'before')\n• Add error handling: insert_code('function[async]', 'try {', 'after') + insert_code('function[async]', '} catch(e) { console.error(e); }', 'after')\n• Add metrics: insert_code('function[name*=\"api\"]', 'performance.mark(\"api-start\");', 'after')\n• Debug mode: insert_code('call[text*=\"fetch\"]', 'console.log(\"API call:\", url);', 'before')", inputSchema: { type: "object", properties: { pattern: { type: "string", description: "Pattern to match: 'function_declaration', 'class[name=\"MyClass\"]', 'method_definition[async]'" }, code: { type: "string", description: "Code to insert. Will be formatted with proper indentation automatically." }, position: { type: "string", enum: ["before", "after"], description: "Insert position: 'before' (above) or 'after' (below) the matched nodes" }, preview: { type: "boolean", description: "Return preview only without applying changes (default: false). Always preview first!" } }, required: ["pattern", "code", "position"], }, }, ], })); this.server.setRequestHandler(CallToolRequestSchema, async (request) => { const { name, arguments: args } = request.params; switch (name) { case "parse_code": return await this.parseCode(args as { source: string; isFilePath?: boolean; language?: string }); case "find_pattern": return await this.findPattern(args as { pattern: string }); case "find_all_pattern": return await this.findAllPattern(args as { pattern: string; limit?: number }); case "get_functions": return await this.getFunctions(args as { includeAnonymous?: boolean; asyncOnly?: boolean }); case "get_classes": return await this.getClasses(args as { includeProperties?: boolean; includeMethods?: boolean }); case "get_imports": return await this.getImports(args as { includeTypeImports?: boolean }); case "rename_identifier": return await this.renameIdentifier(args as { oldName: string; newName: string; preview?: boolean }); case "remove_unused_imports": return await this.removeUnusedImports(args as { preview?: boolean }); case "transform_code": return await this.transformCode(args as { operations: TransformOperation[]; preview?: boolean }); case "get_node_at_position": return await this.getNodeAtPosition(args as { line: number; column: number }); case "analyze_scopes": return await this.analyzeScopes(args as { includeBuiltins?: boolean }); case "insert_code": return await this.insertCode(args as { pattern: string; code: string; position: "before" | "after"; preview?: boolean }); default: throw new Error(`Unknown tool: ${name}`); } }); } private async parseCode(args: { source: string; isFilePath?: boolean; language?: string }) { try { let sourceCode: string; let filePath: string | undefined; let isFile: boolean = args.isFilePath ?? false; // Auto-detect if it's a file path if (args.isFilePath === undefined) { isFile = !args.source.includes('\n') && !args.source.includes(';') && args.source.length < 200; } let tree: TreeHugger; if (isFile) { const resolvedPath = resolve(args.source); if (!existsSync(resolvedPath)) { return { content: [{ type: "text", text: `File not found: ${resolvedPath}`, }], isError: true, }; } // Let tree-hugger handle file reading and language detection tree = parse(resolvedPath, { language: args.language }); sourceCode = readFileSync(resolvedPath, 'utf-8'); filePath = resolvedPath; } else { sourceCode = args.source; tree = parse(sourceCode, { language: args.language }); } this.currentAST = { tree, filePath, sourceCode, language: args.language || 'auto-detected', timestamp: new Date(), }; return { content: [{ type: "text", text: `Successfully parsed ${filePath || 'code string'}\n` + `Language: ${this.currentAST.language}\n` + `Lines: ${sourceCode.split('\n').length}\n` + `Characters: ${sourceCode.length}\n` + `Parse errors: ${tree.root.hasError ? 'Yes' : 'No'}\n` + `Root node type: ${tree.root.type}`, }], }; } catch (error) { return { content: [{ type: "text", text: `Error parsing code: ${error instanceof Error ? error.message : String(error)}`, }], isError: true, }; } } private async findPattern(args: { pattern: string }) { if (!this.currentAST) { return { content: [{ type: "text", text: "No AST loaded. Please use parse_code first.", }], isError: true, }; } try { const result = this.currentAST.tree.find(args.pattern); if (!result) { return { content: [{ type: "text", text: `No match found for pattern: ${args.pattern}`, }], }; } const nodeInfo = { type: result.type, text: result.text.length > 200 ? result.text.slice(0, 200) + '...' : result.text, line: result.line, column: result.column, name: result.name, startPosition: result.startPosition, endPosition: result.endPosition, childrenCount: result.children.length, }; return { content: [{ type: "text", text: `Found match for pattern "${args.pattern}":\n${JSON.stringify(nodeInfo, null, 2)}`, }], }; } catch (error) { return { content: [{ type: "text", text: `Error finding pattern: ${error instanceof Error ? error.message : String(error)}`, }], isError: true, }; } } private async findAllPattern(args: { pattern: string; limit?: number }) { if (!this.currentAST) { return { content: [{ type: "text", text: "No AST loaded. Please use parse_code first.", }], isError: true, }; } try { const results = this.currentAST.tree.findAll(args.pattern); const limitedResults = args.limit ? results.slice(0, args.limit) : results; const matches = limitedResults.map((node: NodeWrapper) => ({ type: node.type, text: node.text.length > 100 ? node.text.slice(0, 100) + '...' : node.text, line: node.line, column: node.column, name: node.name, })); return { content: [{ type: "text", text: `Found ${results.length} matches for pattern "${args.pattern}"${args.limit ? ` (showing first ${limitedResults.length})` : ''}:\n${JSON.stringify(matches, null, 2)}`, }], }; } catch (error) { return { content: [{ type: "text", text: `Error finding pattern: ${error instanceof Error ? error.message : String(error)}`, }], isError: true, }; } } private async getFunctions(args: { includeAnonymous?: boolean; asyncOnly?: boolean }) { if (!this.currentAST) { return { content: [{ type: "text", text: "No AST loaded. Please use parse_code first.", }], isError: true, }; } try { // Use enhanced library methods for detailed function analysis const functionData: FunctionInfo[] = this.currentAST.tree.getFunctionDetails() .filter(fn => { if (args.asyncOnly && !fn.async) return false; if (args.includeAnonymous === false && !fn.name) return false; return true; }) .map(fn => ({ ...fn, text: fn.text.length > 150 ? fn.text.slice(0, 150) + '...' : fn.text, })); this.lastAnalysis = { ...this.lastAnalysis, functions: functionData, timestamp: new Date(), } as AnalysisResult; return { content: [{ type: "text", text: `Found ${functionData.length} functions:\n${JSON.stringify(functionData, null, 2)}`, }], }; } catch (error) { return { content: [{ type: "text", text: `Error getting functions: ${error instanceof Error ? error.message : String(error)}`, }], isError: true, }; } } private async getClasses(args: { includeProperties?: boolean; includeMethods?: boolean }) { if (!this.currentAST) { return { content: [{ type: "text", text: "No AST loaded. Please use parse_code first.", }], isError: true, }; } try { // Use enhanced library methods for detailed class analysis const classData: ClassInfo[] = this.currentAST.tree.getClassDetails() .map(cls => ({ ...cls, text: cls.text.length > 150 ? cls.text.slice(0, 150) + '...' : cls.text, methods: args.includeMethods !== false ? cls.methods.map(method => ({ ...method, text: method.text.length > 100 ? method.text.slice(0, 100) + '...' : method.text, })) : [], properties: args.includeProperties !== false ? cls.properties : [], })); this.lastAnalysis = { ...this.lastAnalysis, classes: classData, timestamp: new Date(), } as AnalysisResult; return { content: [{ type: "text", text: `Found ${classData.length} classes:\n${JSON.stringify(classData, null, 2)}`, }], }; } catch (error) { return { content: [{ type: "text", text: `Error getting classes: ${error instanceof Error ? error.message : String(error)}`, }], isError: true, }; } } private async getImports(args: { includeTypeImports?: boolean }) { if (!this.currentAST) { return { content: [{ type: "text", text: "No AST loaded. Please use parse_code first.", }], isError: true, }; } try { let imports = this.currentAST.tree.imports(); if (args.includeTypeImports === false) { imports = imports.filter(imp => !imp.text.includes('type ')); } const importData = imports.map(imp => ({ module: this.extractModuleName(imp.text), specifiers: this.extractImportSpecifiers(imp.text), line: imp.line, column: imp.column, isTypeOnly: imp.text.includes('type '), text: imp.text, })); this.lastAnalysis = { ...this.lastAnalysis, imports: importData, timestamp: new Date(), } as AnalysisResult; return { content: [{ type: "text", text: `Found ${importData.length} imports:\n${JSON.stringify(importData, null, 2)}`, }], }; } catch (error) { return { content: [{ type: "text", text: `Error getting imports: ${error instanceof Error ? error.message : String(error)}`, }], isError: true, }; } } private extractModuleName(importText: string): string { const match = importText.match(/from\s+['"]([^'"]+)['"]/); return match ? match[1] : 'unknown'; } private extractImportSpecifiers(importText: string): string[] { const defaultMatch = importText.match(/import\s+(\w+)(?=\s*[,{]|\s+from)/); const namedMatch = importText.match(/import\s*(?:\w+\s*,\s*)?{([^}]+)}/); const namespaceMatch = importText.match(/import\s*\*\s*as\s+(\w+)/); const specifiers: string[] = []; if (defaultMatch) { specifiers.push(defaultMatch[1]); } if (namedMatch) { const named = namedMatch[1].split(',').map(s => s.trim().split(' as ')[0].trim()); specifiers.push(...named); } if (namespaceMatch) { specifiers.push(`* as ${namespaceMatch[1]}`); } return specifiers; } private async renameIdentifier(args: { oldName: string; newName: string; preview?: boolean }) { if (!this.currentAST) { return { content: [{ type: "text", text: "No AST loaded. Please use parse_code first.", }], isError: true, }; } try { const transformed = this.currentAST.tree.transform() .rename(args.oldName, args.newName); const result = transformed.toString(); if (!args.preview) { this.currentAST.sourceCode = result; this.currentAST.tree = parse(result); this.currentAST.timestamp = new Date(); } const transformResult: TransformResult = { operation: "rename_identifier", parameters: { oldName: args.oldName, newName: args.newName }, preview: result.slice(0, 500) + (result.length > 500 ? '...' : ''), timestamp: new Date(), }; this.transformHistory.push(transformResult); return { content: [{ type: "text", text: `${args.preview ? 'Preview: ' : ''}Renamed "${args.oldName}" to "${args.newName}"\n\n${args.preview ? 'Preview:\n' : 'Result:\n'}${result}`, }], }; } catch (error) { return { content: [{ type: "text", text: `Error renaming identifier: ${error instanceof Error ? error.message : String(error)}`, }], isError: true, }; } } private async removeUnusedImports(args: { preview?: boolean }) { if (!this.currentAST) { return { content: [{ type: "text", text: "No AST loaded. Please use parse_code first.", }], isError: true, }; } try { const transformed = this.currentAST.tree.transform() .removeUnusedImports(); const result = transformed.toString(); if (!args.preview) { this.currentAST.sourceCode = result; this.currentAST.tree = parse(result); this.currentAST.timestamp = new Date(); } const transformResult: TransformResult = { operation: "remove_unused_imports", parameters: {}, preview: result.slice(0, 500) + (result.length > 500 ? '...' : ''), timestamp: new Date(), }; this.transformHistory.push(transformResult); return { content: [{ type: "text", text: `${args.preview ? 'Preview: ' : ''}Removed unused imports\n\n${args.preview ? 'Preview:\n' : 'Result:\n'}${result}`, }], }; } catch (error) { return { content: [{ type: "text", text: `Error removing unused imports: ${error instanceof Error ? error.message : String(error)}`, }], isError: true, }; } } private async transformCode(args: { operations: TransformOperation[]; preview?: boolean }) { if (!this.currentAST) { return { content: [{ type: "text", text: "No AST loaded. Please use parse_code first.", }], isError: true, }; } try { let transformer = this.currentAST.tree.transform(); for (const op of args.operations) { switch (op.type) { case "rename": if (!op.parameters.oldName || !op.parameters.newName) { throw new Error("Rename operation requires oldName and newName parameters"); } transformer = transformer.rename(op.parameters.oldName, op.parameters.newName); break; case "removeUnusedImports": transformer = transformer.removeUnusedImports(); break; case "replaceIn": if (!op.parameters.nodeType || !op.parameters.pattern || !op.parameters.replacement) { throw new Error("ReplaceIn operation requires nodeType, pattern, and replacement parameters"); } transformer = transformer.replaceIn(op.parameters.nodeType, op.parameters.pattern, op.parameters.replacement); break; case "insertBefore": if (!op.parameters.pattern || !op.parameters.text) { throw new Error("InsertBefore operation requires pattern and text parameters"); } transformer = transformer.insertBefore(String(op.parameters.pattern), op.parameters.text); break; case "insertAfter": if (!op.parameters.pattern || !op.parameters.text) { throw new Error("InsertAfter operation requires pattern and text parameters"); } transformer = transformer.insertAfter(String(op.parameters.pattern), op.parameters.text); break; default: throw new Error(`Unknown operation type: ${op.type}`); } } const result = transformer.toString(); if (!args.preview) { this.currentAST.sourceCode = result; this.currentAST.tree = parse(result); this.currentAST.timestamp = new Date(); } const transformResult: TransformResult = { operation: "transform_code", parameters: {}, // TransformOperation parameters don't include operations array preview: result.slice(0, 500) + (result.length > 500 ? '...' : ''), timestamp: new Date(), }; this.transformHistory.push(transformResult); return { content: [{ type: "text", text: `${args.preview ? 'Preview: ' : ''}Applied ${args.operations.length} transformations\n\n${args.preview ? 'Preview:\n' : 'Result:\n'}${result}`, }], }; } catch (error) { return { content: [{ type: "text", text: `Error transforming code: ${error instanceof Error ? error.message : String(error)}`, }], isError: true, }; } } private async getNodeAtPosition(args: { line: number; column: number }) { if (!this.currentAST) { return { content: [{ type: "text", text: "No AST loaded. Please use parse_code first.", }], isError: true, }; } try { const node = this.currentAST.tree.nodeAt(args.line, args.column); if (!node) { return { content: [{ type: "text", text: `No node found at position ${args.line}:${args.column}`, }], }; } const nodeInfo = { type: node.type, text: node.text.length > 200 ? node.text.slice(0, 200) + '...' : node.text, line: node.line, column: node.column, name: node.name, startPosition: node.startPosition, endPosition: node.endPosition, parent: node.parent ? { type: node.parent.type, name: node.parent.name, } : null, childrenCount: node.children.length, }; return { content: [{ type: "text", text: `Node at position ${args.line}:${args.column}:\n${JSON.stringify(nodeInfo, null, 2)}`, }], }; } catch (error) { return { content: [{ type: "text", text: `Error getting node at position: ${error instanceof Error ? error.message : String(error)}`, }], isError: true, }; } } private async analyzeScopes(args: { includeBuiltins?: boolean }) { if (!this.currentAST) { return { content: [{ type: "text", text: "No AST loaded. Please use parse_code first.", }], isError: true, }; } try { const scopes = this.currentAST.tree.analyzeScopes(); // Note: This is a simplified scope analysis implementation // The actual tree-hugger-js library may have more sophisticated scope analysis const scopeInfo = { message: "Scope analysis completed", scopeCount: "Scope analysis functionality depends on tree-hugger-js implementation", note: "This is a placeholder - actual implementation would use tree-hugger-js scope analysis features", }; return { content: [{ type: "text", text: `Scope Analysis Results:\n${JSON.stringify(scopeInfo, null, 2)}`, }], }; } catch (error) { return { content: [{ type: "text", text: `Error analyzing scopes: ${error instanceof Error ? error.message : String(error)}`, }], isError: true, }; } } private async insertCode(args: { pattern: string; code: string; position: "before" | "after"; preview?: boolean }) { if (!this.currentAST) { return { content: [{ type: "text", text: "No AST loaded. Please use parse_code first.", }], isError: true, }; } try { const transformer = this.currentAST.tree.transform(); const transformed = args.position === "before" ? transformer.insertBefore(args.pattern, args.code) : transformer.insertAfter(args.pattern, args.code); const result = transformed.toString(); if (!args.preview) { this.currentAST.sourceCode = result; this.currentAST.tree = parse(result); this.currentAST.timestamp = new Date(); } const transformResult: TransformResult = { operation: "insert_code", parameters: { pattern: args.pattern, text: args.code }, preview: result.slice(0, 500) + (result.length > 500 ? '...' : ''), timestamp: new Date(), }; this.transformHistory.push(transformResult); return { content: [{ type: "text", text: `${args.preview ? 'Preview: ' : ''}Inserted code ${args.position} pattern "${args.pattern}"\n\n${args.preview ? 'Preview:\n' : 'Result:\n'}${result}`, }], }; } catch (error) { return { content: [{ type: "text", text: `Error inserting code: ${error instanceof Error ? error.message : String(error)}`, }], isError: true, }; } } async run() { const transport = new StdioServerTransport(); await this.server.connect(transport); } } const server = new TreeHuggerMCPServer(); server.run().catch(console.error);

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/qckfx/tree-hugger-js-mcp'

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