feat: Add MCP (Model Context Protocol) development tools

🔧 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.
This commit is contained in:
RebeccaNod1 2025-10-02 11:16:14 -05:00
parent 381ac0d9d8
commit 23153cb5db
7 changed files with 709 additions and 0 deletions

60
MCP_README.md Normal file
View file

@ -0,0 +1,60 @@
# LSL Development MCP Server
This MCP (Model Context Protocol) server provides AI assistants with direct access to your LSL development tools and Peril Dice project.
## What it provides
### Available Tools:
1. **lint_lsl_file** - Lint/validate LSL syntax for a specific file using lslint
2. **lint_all_lsl** - Lint/validate all LSL files in the project using lslint
3. **list_lsl_files** - List all LSL files in the project (currently shows 20 files)
4. **git_status** - Get git status of the project
5. **analyze_script_architecture** - Analyze script architecture and communication patterns
6. **get_memory_usage_info** - Extract memory usage information from LSL files
### Available Resources:
- Project README.md
- CHANGELOG.md
- WARP.md (development guide)
## Files Created:
- `lsl_mcp_server.py` - The main MCP server
- `mcp_config.json` - Configuration for MCP clients
- `test_mcp_server.py` - Test script to verify server functionality
## How to Use:
### Testing the Server:
```bash
python3 test_mcp_server.py
```
### Manual Server Start:
```bash
python3 lsl_mcp_server.py
```
### Integration with AI Tools:
The `mcp_config.json` file can be used to configure MCP-compatible AI tools to connect to this server.
## Benefits:
With this MCP server, AI assistants can:
- Directly lint and validate your LSL code using lslint
- List and examine all 20 LSL files in your Peril project
- Check git status and project state
- Analyze your script architecture (Controllers, Handlers, Modules, Linkset scripts)
- Access project documentation
- Monitor memory usage information from script comments
This creates a much more interactive and helpful development experience, where the AI can actually run your development tools and provide real-time feedback on your Peril Dice project.
## Security:
The server only has access to:
- Your Peril project directory (`/home/richard/peril`)
- Your lslint tool (`/home/richard/lslint/lslint`)
- Git commands within the project directory
- Read-only access to project documentation
It cannot modify files outside the project scope or execute arbitrary system commands.

50
debug_mcp.py Normal file
View file

@ -0,0 +1,50 @@
#!/usr/bin/env python3
import sys
import json
import traceback
def debug_main():
"""Debug version to see what's happening"""
print('{"debug": "server started"}', flush=True)
try:
while True:
line = sys.stdin.readline()
print(f'{{"debug": "received line: {line.strip()[:100]}"}}', flush=True)
if not line:
print('{"debug": "EOF received, exiting"}', flush=True)
break
line = line.strip()
if not line:
continue
try:
request = json.loads(line)
print(f'{{"debug": "parsed request: {request.get("method", "unknown")}"}}', flush=True)
if request.get("method") == "initialize":
response = {
"jsonrpc": "2.0",
"id": request.get("id"),
"result": {
"protocolVersion": "2025-03-26",
"capabilities": {"tools": {}, "resources": {}},
"serverInfo": {"name": "Debug LSL Server", "version": "1.0.0"}
}
}
print(json.dumps(response), flush=True)
print('{"debug": "sent initialize response"}', flush=True)
except Exception as e:
print(f'{{"debug": "error parsing: {str(e)}"}}', flush=True)
except Exception as e:
print(f'{{"debug": "main loop error: {str(e)}"}}', flush=True)
traceback.print_exc(file=sys.stderr)
print('{"debug": "server exiting"}', flush=True)
if __name__ == "__main__":
debug_main()

414
lsl_mcp_server.py Executable file
View file

@ -0,0 +1,414 @@
#!/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()

11
mcp_config.json Normal file
View file

@ -0,0 +1,11 @@
{
"mcpServers": {
"lsl-dev": {
"command": "python3",
"args": ["/home/richard/peril/lsl_mcp_server.py"],
"env": {
"PYTHONPATH": "/home/richard/peril"
}
}
}
}

60
test_lslint_mcp.py Executable file
View file

@ -0,0 +1,60 @@
#!/usr/bin/env python3
"""
Test script to verify lslint functionality with MCP server
"""
import json
import subprocess
import sys
def test_lslint_functionality():
"""Test lslint functionality through MCP server"""
# Test list_lsl_files first
list_request = {
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "list_lsl_files",
"arguments": {}
}
}
try:
# Start the MCP server process
process = subprocess.Popen([
'python3', '/home/richard/peril/lsl_mcp_server.py'
], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
# Send the request
stdout, stderr = process.communicate(input=json.dumps(list_request) + '\n', timeout=10)
print("=== LSL MCP Server - LSLint Test ===")
print("\nListing LSL Files:")
lines = stdout.strip().split('\n')
for line in lines:
if line.strip():
try:
response = json.loads(line)
if 'content' in response:
print(response['content'][0]['text'])
elif 'tools' in response:
print("Server initialized successfully")
except json.JSONDecodeError:
print(f"Non-JSON output: {line}")
if stderr:
print(f"\nServer Errors:\n{stderr}")
print("\n=== Test Complete ===")
except subprocess.TimeoutExpired:
print("Server test timed out")
process.kill()
except Exception as e:
print(f"Test failed: {e}")
if __name__ == "__main__":
test_lslint_functionality()

52
test_mcp_server.py Executable file
View file

@ -0,0 +1,52 @@
#!/usr/bin/env python3
"""
Simple test script to verify the LSL MCP server is working
"""
import json
import subprocess
import sys
def test_mcp_server():
"""Test basic MCP server functionality"""
# Test tools/list
test_request = {
"jsonrpc": "2.0",
"id": 1,
"method": "tools/list",
"params": {}
}
try:
# Start the MCP server process
process = subprocess.Popen([
'python3', '/home/richard/peril/lsl_mcp_server.py'
], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
# Send the request
stdout, stderr = process.communicate(input=json.dumps(test_request) + '\n', timeout=10)
print("=== MCP Server Test Results ===")
print("\nServer Output:")
for line in stdout.strip().split('\n'):
if line.strip():
try:
response = json.loads(line)
print(json.dumps(response, indent=2))
except json.JSONDecodeError:
print(f"Non-JSON output: {line}")
if stderr:
print(f"\nServer Errors:\n{stderr}")
print("\n=== Test Complete ===")
except subprocess.TimeoutExpired:
print("Server test timed out")
process.kill()
except Exception as e:
print(f"Test failed: {e}")
if __name__ == "__main__":
test_mcp_server()

62
test_validate_lsl.py Executable file
View file

@ -0,0 +1,62 @@
#!/usr/bin/env python3
"""
Test script to validate a specific LSL file using the comprehensive validator
"""
import json
import subprocess
import sys
def test_validate_lsl():
"""Test LSL validation through MCP server"""
# Test validate_lsl_file with Main_Controller_Linkset.lsl
validate_request = {
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "validate_lsl_file",
"arguments": {
"file_path": "Main_Controller_Linkset.lsl"
}
}
}
try:
# Start the MCP server process
process = subprocess.Popen([
'python3', '/home/richard/peril/lsl_mcp_server.py'
], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
# Send the request
stdout, stderr = process.communicate(input=json.dumps(validate_request) + '\n', timeout=15)
print("=== LSL Comprehensive Validation Test ===")
print("\nValidating Main_Controller_Linkset.lsl:")
lines = stdout.strip().split('\n')
for line in lines:
if line.strip():
try:
response = json.loads(line)
if 'content' in response:
print(response['content'][0]['text'])
elif 'method' in response and response['method'] == 'server/initialized':
print("✅ Server initialized successfully")
except json.JSONDecodeError:
print(f"Non-JSON output: {line}")
if stderr:
print(f"\nServer Errors:\n{stderr}")
print("\n=== Test Complete ===")
except subprocess.TimeoutExpired:
print("Server test timed out")
process.kill()
except Exception as e:
print(f"Test failed: {e}")
if __name__ == "__main__":
test_validate_lsl()