Krep MCP Server

by bmorphism
Verified
const{exec:exec}=require("child_process");const path=require("path");const fs=require("fs");process.on("uncaughtException",(error=>{console.error(`[MCP Server] Uncaught exception: ${error.message}`);console.error(`[MCP Server] Stack trace: ${error.stack}`)}));process.on("unhandledRejection",(reason=>{console.error(`[MCP Server] Unhandled promise rejection: ${reason}`)}));function findKrepBinary(){const nodeDirectory=path.dirname(process.execPath);const possiblePaths=[path.resolve(__dirname,"../../krep-native/krep"),path.resolve(__dirname,"../krep-native/krep"),"/usr/local/bin/krep","/opt/homebrew/bin/krep",path.join(nodeDirectory,"krep"),path.join(process.env.HOME||"","krep-native/krep"),path.join(process.env.HOME||"","bin/krep")];console.error("Looking for krep binary in:");for(const p of possiblePaths){const exists=fs.existsSync(p);console.error(`- ${p} (${exists?"found":"not found"})`);if(exists){return p}}if(process.env.KREP_PATH){console.error(`Using KREP_PATH from environment: ${process.env.KREP_PATH}`);return process.env.KREP_PATH}return null}const KREP_PATH=process.env.KREP_PATH||findKrepBinary()||path.join(__dirname,"../../krep-native/krep");console.error(`[MCP Server] Using krep binary at: ${KREP_PATH}`);class KrepMcpServer{constructor(){this.startTime=Date.now();console.error(`[MCP Server] Initializing krep-mcp-server at ${(new Date).toISOString()}`);console.error(`[MCP Server] Node version: ${process.version}`);console.error(`[MCP Server] Working directory: ${process.cwd()}`);if(!fs.existsSync(KREP_PATH)&&!process.env.KREP_SKIP_CHECK){const errorMessage=`Error: krep binary not found at ${KREP_PATH}`;console.error(`[MCP Server] ${errorMessage}`);console.error("[MCP Server] Please install krep or set KREP_PATH environment variable");if(!process.env.KREP_TEST_MODE){this.sendLogMessage("error",{message:errorMessage,binaryPath:KREP_PATH,cwd:process.cwd(),env:{HOME:process.env.HOME,PATH:process.env.PATH}});setTimeout((()=>process.exit(1)),100);return}console.error("[MCP Server] Running in test mode, continuing despite missing krep binary")}else{console.error(`[MCP Server] Using krep binary at: ${KREP_PATH}`)}this.functions={krep:this.krepFunction.bind(this)};this.initialized=false;this.handleInput()}handleInput(){console.error("[MCP Server] Setting up stdin/stdout handlers");process.stdin.setEncoding("utf8");let buffer="";process.stdin.on("data",(chunk=>{console.error(`[MCP Server] Received chunk of ${chunk.length} bytes`);if(process.env.DEBUG){console.error(`[MCP Server] Chunk preview: ${chunk.substring(0,Math.min(50,chunk.length))}`)}buffer+=chunk;try{const messages=this.extractMessages(buffer);if(messages.extracted.length>0){buffer=messages.remainingBuffer;console.error(`[MCP Server] Processing ${messages.extracted.length} message(s), ${buffer.length} bytes remaining in buffer`);for(const message of messages.extracted){this.processMessage(message)}}}catch(error){console.error(`[MCP Server] Error processing input: ${error.message}`);console.error(`[MCP Server] Stack trace: ${error.stack}`);this.sendLogMessage("error",{message:"Error processing input",error:error.message});this.sendErrorResponse(null,`Error processing request: ${error.message}`);if(buffer.length>1e4){console.error("[MCP Server] Buffer too large, clearing for recovery");buffer=""}}}));process.stdin.on("end",(()=>{console.error("[MCP Server] stdin stream ended, shutting down");this.sendLogMessage("info","Server shutting down due to stdin close");console.error("[MCP Server] Not exiting process despite stdin close")}));process.stdin.on("error",(error=>{console.error(`[MCP Server] stdin error: ${error.message}`);this.sendLogMessage("error",{message:"stdin error",error:error.message});console.error("[MCP Server] Not exiting process despite stdin error")}))}extractMessages(buffer){console.error(`[MCP Server] Processing buffer of length: ${buffer.length}`);if(buffer.length>0){console.error(`[MCP Server] Buffer preview: ${buffer.substring(0,Math.min(50,buffer.length))}`)}const extracted=[];let startIdx=0;if(buffer.startsWith("{")&&buffer.includes('"method"')){try{const message=JSON.parse(buffer);console.error("[MCP Server] Successfully parsed direct JSON message");extracted.push(message);return{extracted:extracted,remainingBuffer:""}}catch(error){console.error(`[MCP Server] Failed to parse direct JSON: ${error.message}`)}}while(startIdx<buffer.length){let headerMatch=buffer.slice(startIdx).match(/Content-Length:\s*(\d+)\r\n\r\n/);if(!headerMatch){headerMatch=buffer.slice(startIdx).match(/Content-Length:\s*(\d+)\n\n/)}if(!headerMatch){headerMatch=buffer.slice(startIdx).match(/Content-Length:\s*(\d+)\n/)}if(!headerMatch){console.error("[MCP Server] No complete Content-Length header found in buffer");if(buffer.startsWith("{")&&buffer.endsWith("}")){try{const message=JSON.parse(buffer);console.error("[MCP Server] Successfully parsed direct JSON message");extracted.push(message);return{extracted:extracted,remainingBuffer:""}}catch(error){console.error(`[MCP Server] Failed to parse as direct JSON: ${error.message}`)}}break}const headerMatchLength=headerMatch[0].length;const headerMatchStart=startIdx+headerMatch.index;const contentStart=headerMatchStart+headerMatchLength;const contentLength=parseInt(headerMatch[1],10);console.error(`[MCP Server] Found header: Content-Length: ${contentLength}`);if(buffer.length<contentStart+contentLength){console.error(`[MCP Server] Incomplete message: have ${buffer.length-contentStart} of ${contentLength} bytes`);break}const jsonContent=buffer.slice(contentStart,contentStart+contentLength);try{const jsonStr=jsonContent.toString("utf8");const message=JSON.parse(jsonStr);extracted.push(message);console.error("[MCP Server] Successfully parsed message")}catch(error){console.error(`[MCP Server] Failed to parse JSON message: ${error.message}`);console.error(`[MCP Server] Problematic content: ${jsonContent.substring(0,100)}`)}startIdx=contentStart+contentLength}return{extracted:extracted,remainingBuffer:buffer.slice(startIdx)}}processMessage(message){console.error(`[MCP Server] Received message: ${JSON.stringify(message)}`);if(message.method==="initialize"){console.error("[MCP Server] Handling initialize message...");this.handleInitialize(message);console.error("[MCP Server] Initialize handler completed")}else if(this.initialized&&message.method==="executeFunction"){console.error("[MCP Server] Handling executeFunction message...");this.handleExecuteFunction(message)}else{console.error(`[MCP Server] Unknown method: ${message.method}`);this.sendErrorResponse(message.id,`Unknown or unsupported method: ${message.method}`)}}handleInitialize(message){this.initialized=true;const capabilities={functions:[{name:"krep",description:"Unified function for pattern searching in files or strings",parameters:{type:"object",properties:{pattern:{type:"string",description:"Pattern to search for"},target:{type:"string",description:"File path or string to search in"},mode:{type:"string",description:'Search mode: "file" (default), "string", or "count"',enum:["file","string","count"]},caseSensitive:{type:"boolean",description:"Case-sensitive search (default: true)"},threads:{type:"integer",description:"Number of threads to use (default: 4)"}},required:["pattern","target"]}}]};this.sendResponse(message.id,{capabilities:capabilities})}handleExecuteFunction(message){const{function:functionName,parameters:parameters}=message.params;if(!this.functions[functionName]){return this.sendErrorResponse(message.id,`Function not found: ${functionName}`)}try{this.functions[functionName](parameters,message.id)}catch(error){this.sendErrorResponse(message.id,`Error executing function: ${error.message}`)}}krepFunction(params,id){const{pattern:pattern,target:target,mode:mode="file",caseSensitive:caseSensitive=true,threads:threads=4}=params;console.error(`[MCP Server] krep called with pattern: ${pattern}, target: ${target}, mode: ${mode}`);if(!pattern||!target){console.error("[MCP Server] Missing required parameters");return this.sendErrorResponse(id,"Missing required parameters: pattern and target")}const caseFlag=caseSensitive?"":"-i";const threadFlag=`-t ${threads}`;let command="";if(mode==="string"){command=`${KREP_PATH} ${caseFlag} ${threadFlag} -s "${pattern}" "${target}"`}else if(mode==="count"){command=`${KREP_PATH} ${caseFlag} ${threadFlag} -c "${pattern}" "${target}"`}else{command=`${KREP_PATH} ${caseFlag} ${threadFlag} "${pattern}" "${target}"`}console.error(`[MCP Server] Executing command: ${command}`);if(!fs.existsSync(KREP_PATH)&&!process.env.KREP_SKIP_CHECK){console.error(`[MCP Server] krep binary not found at ${KREP_PATH}`);if(process.env.KREP_TEST_MODE){console.error("[MCP Server] In test mode, returning mock response");this.sendResponse(id,{pattern:pattern,target:target,mode:mode,results:`Found 0 matches for "${pattern}" in ${target}`,performance:{matchCount:0,searchTime:.001,searchSpeed:100,algorithmUsed:this.getAlgorithmInfo(pattern),threads:threads,caseSensitive:caseSensitive},success:true});return}return this.sendErrorResponse(id,`krep binary not found at ${KREP_PATH}`)}exec(command,{maxBuffer:1024*1024*10},((error,stdout,stderr)=>{if(error){console.error(`[MCP Server] Error executing krep: ${error.message}`);console.error(`[MCP Server] stderr: ${stderr}`);if(error.message.includes("No such file")||error.message.includes("Permission denied")||error.message.includes("not found")||error.message.includes("cannot access")){console.error("[MCP Server] Handling file access error gracefully");this.sendResponse(id,{pattern:pattern,target:target,mode:mode,results:`No matches found (${error.message})`,performance:{matchCount:0,searchTime:0,searchSpeed:0,algorithmUsed:this.getAlgorithmInfo(pattern),threads:threads,caseSensitive:caseSensitive},success:true});return}return this.sendErrorResponse(id,error.message,stderr)}console.error(`[MCP Server] krep executed successfully, stdout length: ${stdout.length}`);const matchCountMatch=stdout.match(/Found (\d+) matches/);const timeMatch=stdout.match(/Search completed in ([\d.]+) seconds/);const speedMatch=stdout.match(/([\d.]+) MB\/s/);const algorithmMatch=stdout.match(/Using ([^\\n]+) algorithm/);const matchCount=matchCountMatch?parseInt(matchCountMatch[1]):0;const searchTime=timeMatch?parseFloat(timeMatch[1]):null;const searchSpeed=speedMatch?parseFloat(speedMatch[1]):null;const algorithmUsed=algorithmMatch?algorithmMatch[1].trim():this.getAlgorithmInfo(pattern);const response={pattern:pattern,target:target,mode:mode,results:stdout,performance:{matchCount:matchCount,searchTime:searchTime,searchSpeed:searchSpeed,algorithmUsed:algorithmUsed,threads:threads,caseSensitive:caseSensitive},success:true};this.sendResponse(id,response)}))}getAlgorithmInfo(pattern){const patternLen=pattern.length;if(patternLen<3){return"KMP (Knuth-Morris-Pratt) - Optimized for very short patterns"}else if(patternLen>16){return"Rabin-Karp - Efficient for longer patterns with better hash distribution"}const isAppleSilicon=process.platform==="darwin"&&process.arch==="arm64";const isModernX64=process.platform!=="darwin"&&process.arch==="x64";if(isAppleSilicon){return"NEON SIMD - Hardware-accelerated search on Apple Silicon"}else if(isModernX64){return"SSE4.2/AVX2 - Hardware-accelerated search with vector instructions"}return"Boyer-Moore-Horspool - Efficient general-purpose string search"}sendResponse(id,result){console.error("Sending response for id:",id);const response={jsonrpc:"2.0",id:id,result:result};this.sendMessage(response)}sendErrorResponse(id,message,data=null){console.error("Sending error response for id:",id,"Message:",message);const response={jsonrpc:"2.0",id:id,error:{code:-32e3,message:message,data:data}};this.sendMessage(response)}sendMessage(message){try{const jsonMessage=JSON.stringify(message);const messageBuffer=Buffer.from(jsonMessage,"utf8");const contentLength=messageBuffer.length;const header=`Content-Length: ${contentLength}\r\n\r\n`;console.error(`[MCP Server] Sending response with length: ${contentLength}`);if(process.env.DEBUG){console.error(`[MCP Server] Response preview: ${jsonMessage.substring(0,Math.min(100,jsonMessage.length))}`)}process.stdout.write(header);process.stdout.write(jsonMessage);if(typeof process.stdout.flush==="function"){process.stdout.flush()}}catch(error){console.error(`[MCP Server] Error sending message: ${error.message}`);console.error(`[MCP Server] Stack trace: ${error.stack}`)}}sendLogMessage(level,data){const message={jsonrpc:"2.0",method:"log",params:{level:level||"info",data:data||{}}};this.sendMessage(message);console.error(`[MCP Server] Log message sent (${level}): ${typeof data==="string"?data:JSON.stringify(data)}`)}}if(require.main===module){new KrepMcpServer}module.exports=KrepMcpServer;