🔧 MCP Development Infrastructure: - lsl_mcp_server.py (414 lines): Complete MCP server for LSL validation • LSL file listing and validation capabilities • Memory usage analysis and reporting • Git status integration • Architecture analysis tools 📋 Testing & Documentation: - test_lslint_mcp.py: MCP server testing framework - test_mcp_server.py: Server functionality tests - test_validate_lsl.py: LSL validation testing - debug_mcp.py: MCP debugging utilities - MCP_README.md: Complete documentation and setup guide - mcp_config.json: MCP server configuration 🎯 Development Benefits: - Enhanced LSL development workflow - Automated validation and analysis - Professional debugging capabilities - Integration with existing LSL validator - Support for AI-assisted development These tools were used during development of the GitHub Update System and provide foundation for future LSL development automation.
414 lines
No EOL
15 KiB
Python
Executable file
414 lines
No EOL
15 KiB
Python
Executable file
#!/usr/bin/env python3
|
|
"""
|
|
LSL Development MCP Server for Peril Dice Project
|
|
Provides tools for LSL validation, preprocessing, and project management.
|
|
"""
|
|
|
|
import json
|
|
import sys
|
|
import subprocess
|
|
import os
|
|
import glob
|
|
from pathlib import Path
|
|
from typing import Dict, List, Any, Optional
|
|
|
|
|
|
class LSLMCPServer:
|
|
def __init__(self, project_root: str = "/home/richard/peril"):
|
|
self.project_root = Path(project_root)
|
|
self.lsl_validator_path = self.project_root / "lsl_validator.py"
|
|
|
|
def handle_request(self, request: Dict[str, Any]) -> Dict[str, Any]:
|
|
"""Handle incoming MCP requests"""
|
|
method = request.get("method", "")
|
|
params = request.get("params", {})
|
|
|
|
if method == "initialize":
|
|
return self._handle_initialize(params)
|
|
elif method == "tools/list":
|
|
return self._list_tools()
|
|
elif method == "tools/call":
|
|
return self._call_tool(params)
|
|
elif method == "resources/list":
|
|
return self._list_resources()
|
|
elif method == "resources/read":
|
|
return self._read_resource(params)
|
|
else:
|
|
return {"error": f"Unknown method: {method}"}
|
|
|
|
def _handle_initialize(self, params: Dict[str, Any]) -> Dict[str, Any]:
|
|
"""Handle MCP initialization request"""
|
|
# Use the client's protocol version if provided
|
|
client_version = params.get("protocolVersion", "2024-11-05")
|
|
return {
|
|
"protocolVersion": client_version,
|
|
"capabilities": {
|
|
"tools": {},
|
|
"resources": {}
|
|
},
|
|
"serverInfo": {
|
|
"name": "LSL Development Server",
|
|
"version": "1.0.0"
|
|
}
|
|
}
|
|
|
|
def _list_tools(self) -> Dict[str, Any]:
|
|
"""List available LSL development tools"""
|
|
return {
|
|
"tools": [
|
|
{
|
|
"name": "validate_lsl_file",
|
|
"description": "Comprehensive LSL validation for a specific file (syntax, scope, style, performance, memory)",
|
|
"inputSchema": {
|
|
"type": "object",
|
|
"properties": {
|
|
"file_path": {"type": "string", "description": "Path to LSL file to validate"}
|
|
},
|
|
"required": ["file_path"]
|
|
}
|
|
},
|
|
{
|
|
"name": "validate_all_lsl",
|
|
"description": "Comprehensive LSL validation for all files in the project",
|
|
"inputSchema": {
|
|
"type": "object",
|
|
"properties": {}
|
|
}
|
|
},
|
|
{
|
|
"name": "list_lsl_files",
|
|
"description": "List all LSL files in the project",
|
|
"inputSchema": {
|
|
"type": "object",
|
|
"properties": {}
|
|
}
|
|
},
|
|
{
|
|
"name": "git_status",
|
|
"description": "Get git status of the project",
|
|
"inputSchema": {
|
|
"type": "object",
|
|
"properties": {}
|
|
}
|
|
},
|
|
{
|
|
"name": "analyze_script_architecture",
|
|
"description": "Analyze the script architecture and communication patterns",
|
|
"inputSchema": {
|
|
"type": "object",
|
|
"properties": {}
|
|
}
|
|
},
|
|
{
|
|
"name": "get_memory_usage_info",
|
|
"description": "Extract memory usage information from LSL files",
|
|
"inputSchema": {
|
|
"type": "object",
|
|
"properties": {}
|
|
}
|
|
}
|
|
]
|
|
}
|
|
|
|
def _call_tool(self, params: Dict[str, Any]) -> Dict[str, Any]:
|
|
"""Execute a specific tool"""
|
|
tool_name = params.get("name", "")
|
|
arguments = params.get("arguments", {})
|
|
|
|
try:
|
|
if tool_name == "validate_lsl_file":
|
|
return self._validate_lsl_file(arguments["file_path"])
|
|
elif tool_name == "validate_all_lsl":
|
|
return self._validate_all_lsl()
|
|
elif tool_name == "list_lsl_files":
|
|
return self._list_lsl_files()
|
|
elif tool_name == "git_status":
|
|
return self._git_status()
|
|
elif tool_name == "analyze_script_architecture":
|
|
return self._analyze_script_architecture()
|
|
elif tool_name == "get_memory_usage_info":
|
|
return self._get_memory_usage_info()
|
|
else:
|
|
return {"error": f"Unknown tool: {tool_name}"}
|
|
except Exception as e:
|
|
return {"error": f"Tool execution failed: {str(e)}"}
|
|
|
|
def _validate_lsl_file(self, file_path: str) -> Dict[str, Any]:
|
|
"""Validate a specific LSL file using the comprehensive LSL validator"""
|
|
full_path = self.project_root / file_path if not os.path.isabs(file_path) else Path(file_path)
|
|
|
|
if not full_path.exists():
|
|
return {"error": f"File not found: {full_path}"}
|
|
|
|
if not self.lsl_validator_path.exists():
|
|
return {"error": f"LSL validator not found at: {self.lsl_validator_path}"}
|
|
|
|
try:
|
|
result = subprocess.run([
|
|
"python3", str(self.lsl_validator_path), str(full_path)
|
|
], capture_output=True, text=True, cwd=str(self.project_root))
|
|
|
|
return {
|
|
"content": [
|
|
{
|
|
"type": "text",
|
|
"text": f"LSL Validation Result for {file_path}:\n\n"
|
|
f"Exit Code: {result.returncode}\n"
|
|
f"Output:\n{result.stdout}\n"
|
|
f"Errors:\n{result.stderr}"
|
|
}
|
|
]
|
|
}
|
|
except Exception as e:
|
|
return {"error": f"Failed to run LSL validator: {str(e)}"}
|
|
|
|
def _validate_all_lsl(self) -> Dict[str, Any]:
|
|
"""Validate all LSL files in the project using the comprehensive LSL validator"""
|
|
if not self.lsl_validator_path.exists():
|
|
return {"error": f"LSL validator not found at: {self.lsl_validator_path}"}
|
|
|
|
try:
|
|
result = subprocess.run([
|
|
"python3", str(self.lsl_validator_path), "."
|
|
], capture_output=True, text=True, cwd=str(self.project_root))
|
|
|
|
return {
|
|
"content": [
|
|
{
|
|
"type": "text",
|
|
"text": f"LSL Validation Results for All Files:\n\n"
|
|
f"Exit Code: {result.returncode}\n\n"
|
|
f"Output:\n{result.stdout}\n"
|
|
f"Errors:\n{result.stderr}"
|
|
}
|
|
]
|
|
}
|
|
except Exception as e:
|
|
return {"error": f"Failed to run LSL validator on all files: {str(e)}"}
|
|
|
|
def _list_lsl_files(self) -> Dict[str, Any]:
|
|
"""List all LSL files in the project"""
|
|
lsl_files = list(self.project_root.glob("*.lsl"))
|
|
file_list = [f.name for f in sorted(lsl_files)]
|
|
|
|
return {
|
|
"content": [
|
|
{
|
|
"type": "text",
|
|
"text": f"LSL Files in Project ({len(file_list)} total):\n\n" +
|
|
"\n".join([f"• {f}" for f in file_list])
|
|
}
|
|
]
|
|
}
|
|
|
|
def _git_status(self) -> Dict[str, Any]:
|
|
"""Get git status of the project"""
|
|
try:
|
|
result = subprocess.run([
|
|
"git", "status", "--porcelain"
|
|
], capture_output=True, text=True, cwd=str(self.project_root))
|
|
|
|
branch_result = subprocess.run([
|
|
"git", "branch", "--show-current"
|
|
], capture_output=True, text=True, cwd=str(self.project_root))
|
|
|
|
status_text = "Git Status:\n\n"
|
|
status_text += f"Current Branch: {branch_result.stdout.strip()}\n\n"
|
|
|
|
if result.stdout.strip():
|
|
status_text += "Modified Files:\n"
|
|
for line in result.stdout.strip().split('\n'):
|
|
status_text += f" {line}\n"
|
|
else:
|
|
status_text += "Working directory clean\n"
|
|
|
|
return {
|
|
"content": [
|
|
{
|
|
"type": "text",
|
|
"text": status_text
|
|
}
|
|
]
|
|
}
|
|
except Exception as e:
|
|
return {"error": f"Failed to get git status: {str(e)}"}
|
|
|
|
def _analyze_script_architecture(self) -> Dict[str, Any]:
|
|
"""Analyze the script architecture"""
|
|
lsl_files = list(self.project_root.glob("*.lsl"))
|
|
|
|
architecture_info = "Peril Dice Script Architecture Analysis:\n\n"
|
|
architecture_info += f"Total Scripts: {len(lsl_files)}\n\n"
|
|
|
|
# Categorize scripts by type
|
|
controllers = [f for f in lsl_files if "Controller" in f.name or "Manager" in f.name]
|
|
handlers = [f for f in lsl_files if "Handler" in f.name]
|
|
modules = [f for f in lsl_files if "Module" in f.name]
|
|
linkset_scripts = [f for f in lsl_files if "Linkset" in f.name]
|
|
|
|
architecture_info += f"Controllers/Managers ({len(controllers)}):\n"
|
|
for script in controllers:
|
|
architecture_info += f" • {script.name}\n"
|
|
|
|
architecture_info += f"\nHandlers ({len(handlers)}):\n"
|
|
for script in handlers:
|
|
architecture_info += f" • {script.name}\n"
|
|
|
|
architecture_info += f"\nModules ({len(modules)}):\n"
|
|
for script in modules:
|
|
architecture_info += f" • {script.name}\n"
|
|
|
|
architecture_info += f"\nLinkset-Specific Scripts ({len(linkset_scripts)}):\n"
|
|
for script in linkset_scripts:
|
|
architecture_info += f" • {script.name}\n"
|
|
|
|
return {
|
|
"content": [
|
|
{
|
|
"type": "text",
|
|
"text": architecture_info
|
|
}
|
|
]
|
|
}
|
|
|
|
def _get_memory_usage_info(self) -> Dict[str, Any]:
|
|
"""Extract memory usage information from comments in LSL files"""
|
|
lsl_files = list(self.project_root.glob("*.lsl"))
|
|
memory_info = "Memory Usage Information:\n\n"
|
|
|
|
for lsl_file in lsl_files:
|
|
try:
|
|
with open(lsl_file, 'r', encoding='utf-8') as f:
|
|
content = f.read()
|
|
# Look for memory usage comments
|
|
lines = content.split('\n')
|
|
memory_lines = [line for line in lines if 'memory' in line.lower() and ('%' in line or 'usage' in line.lower())]
|
|
|
|
if memory_lines:
|
|
memory_info += f"{lsl_file.name}:\n"
|
|
for line in memory_lines:
|
|
memory_info += f" {line.strip()}\n"
|
|
memory_info += "\n"
|
|
except Exception:
|
|
continue
|
|
|
|
return {
|
|
"content": [
|
|
{
|
|
"type": "text",
|
|
"text": memory_info if memory_info.strip() != "Memory Usage Information:" else "No memory usage information found in comments."
|
|
}
|
|
]
|
|
}
|
|
|
|
def _list_resources(self) -> Dict[str, Any]:
|
|
"""List available project resources"""
|
|
return {
|
|
"resources": [
|
|
{
|
|
"uri": "file:///home/richard/peril/README.md",
|
|
"name": "Project README",
|
|
"description": "Main project documentation",
|
|
"mimeType": "text/markdown"
|
|
},
|
|
{
|
|
"uri": "file:///home/richard/peril/CHANGELOG.md",
|
|
"name": "Changelog",
|
|
"description": "Project changelog",
|
|
"mimeType": "text/markdown"
|
|
},
|
|
{
|
|
"uri": "file:///home/richard/peril/WARP.md",
|
|
"name": "WARP Development Guide",
|
|
"description": "Development guidance for WARP",
|
|
"mimeType": "text/markdown"
|
|
}
|
|
]
|
|
}
|
|
|
|
def _read_resource(self, params: Dict[str, Any]) -> Dict[str, Any]:
|
|
"""Read a specific resource"""
|
|
uri = params.get("uri", "")
|
|
|
|
if uri.startswith("file://"):
|
|
file_path = Path(uri[7:]) # Remove file:// prefix
|
|
|
|
if not file_path.exists():
|
|
return {"error": f"File not found: {file_path}"}
|
|
|
|
try:
|
|
with open(file_path, 'r', encoding='utf-8') as f:
|
|
content = f.read()
|
|
|
|
return {
|
|
"contents": [
|
|
{
|
|
"uri": uri,
|
|
"mimeType": "text/markdown" if file_path.suffix == ".md" else "text/plain",
|
|
"text": content
|
|
}
|
|
]
|
|
}
|
|
except Exception as e:
|
|
return {"error": f"Failed to read file: {str(e)}"}
|
|
|
|
return {"error": f"Unsupported URI: {uri}"}
|
|
|
|
|
|
def main():
|
|
"""Main server loop"""
|
|
server = LSLMCPServer()
|
|
|
|
try:
|
|
# Read requests from stdin continuously
|
|
while True:
|
|
try:
|
|
line = sys.stdin.readline()
|
|
if not line: # EOF
|
|
break
|
|
|
|
line = line.strip()
|
|
if not line:
|
|
continue
|
|
|
|
request = json.loads(line)
|
|
response = server.handle_request(request)
|
|
|
|
# Wrap response in proper JSON-RPC format
|
|
json_rpc_response = {
|
|
"jsonrpc": "2.0",
|
|
"id": request.get("id", None)
|
|
}
|
|
|
|
if "error" in response:
|
|
json_rpc_response["error"] = response["error"]
|
|
else:
|
|
json_rpc_response["result"] = response
|
|
|
|
print(json.dumps(json_rpc_response))
|
|
sys.stdout.flush() # Ensure immediate output
|
|
|
|
except json.JSONDecodeError:
|
|
error_response = {
|
|
"jsonrpc": "2.0",
|
|
"error": {"code": -32700, "message": "Parse error"},
|
|
"id": None
|
|
}
|
|
print(json.dumps(error_response))
|
|
sys.stdout.flush()
|
|
except Exception as e:
|
|
error_response = {
|
|
"jsonrpc": "2.0",
|
|
"error": {"code": -32603, "message": str(e)},
|
|
"id": None
|
|
}
|
|
print(json.dumps(error_response))
|
|
sys.stdout.flush()
|
|
except KeyboardInterrupt:
|
|
pass
|
|
except EOFError:
|
|
pass
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main() |