#!/usr/bin/env python3 import json import subprocess import sys def handle_request(request): if request.get("method") == "initialize": return { "jsonrpc": "2.0", "id": request.get("id"), "result": { "capabilities": {"tools": {}}, "protocolVersion": "2024-11-05", "serverInfo": {"name": "leann-mcp", "version": "1.0.0"}, }, } elif request.get("method") == "tools/list": return { "jsonrpc": "2.0", "id": request.get("id"), "result": { "tools": [ { "name": "leann_search", "description": """🔍 Search code using natural language - like having a coding assistant who knows your entire codebase! 🎯 **Perfect for**: - "How does authentication work?" → finds auth-related code - "Error handling patterns" → locates try-catch blocks and error logic - "Database connection setup" → finds DB initialization code - "API endpoint definitions" → locates route handlers - "Configuration management" → finds config files and usage 💡 **Pro tip**: Use this before making any changes to understand existing patterns and conventions.""", "inputSchema": { "type": "object", "properties": { "index_name": { "type": "string", "description": "Name of the LEANN index to search. Use 'leann_list' first to see available indexes.", }, "query": { "type": "string", "description": "Search query - can be natural language (e.g., 'how to handle errors') or technical terms (e.g., 'async function definition')", }, "top_k": { "type": "integer", "default": 5, "minimum": 1, "maximum": 20, "description": "Number of search results to return. Use 5-10 for focused results, 15-20 for comprehensive exploration.", }, "complexity": { "type": "integer", "default": 32, "minimum": 16, "maximum": 128, "description": "Search complexity level. Use 16-32 for fast searches (recommended), 64+ for higher precision when needed.", }, }, "required": ["index_name", "query"], }, }, { "name": "leann_list", "description": "📋 Show all your indexed codebases - your personal code library! Use this to see what's available for search.", "inputSchema": {"type": "object", "properties": {}}, }, ] }, } elif request.get("method") == "tools/call": tool_name = request["params"]["name"] args = request["params"].get("arguments", {}) try: if tool_name == "leann_search": # Validate required parameters if not args.get("index_name") or not args.get("query"): return { "jsonrpc": "2.0", "id": request.get("id"), "result": { "content": [ { "type": "text", "text": "Error: Both index_name and query are required", } ] }, } # Build simplified command with non-interactive flag for MCP compatibility cmd = [ "leann", "search", args["index_name"], args["query"], f"--top-k={args.get('top_k', 5)}", f"--complexity={args.get('complexity', 32)}", "--non-interactive", ] result = subprocess.run(cmd, capture_output=True, text=True) elif tool_name == "leann_list": result = subprocess.run(["leann", "list"], capture_output=True, text=True) return { "jsonrpc": "2.0", "id": request.get("id"), "result": { "content": [ { "type": "text", "text": result.stdout if result.returncode == 0 else f"Error: {result.stderr}", } ] }, } except Exception as e: return { "jsonrpc": "2.0", "id": request.get("id"), "error": {"code": -1, "message": str(e)}, } def main(): for line in sys.stdin: try: request = json.loads(line.strip()) response = handle_request(request) if response: print(json.dumps(response)) sys.stdout.flush() except Exception as e: error_response = { "jsonrpc": "2.0", "id": None, "error": {"code": -1, "message": str(e)}, } print(json.dumps(error_response)) sys.stdout.flush() if __name__ == "__main__": main()