Skip to main content
Glama
minami110

GDScript Code Analyzer

by minami110
tools.py19.1 kB
"""MCP tools for GDScript analysis.""" import json from pathlib import Path from typing import Any, Optional from mcp.types import Tool, TextContent, CallToolResult from .parser import GDScriptParser class GDScriptTools: """Collection of tools for GDScript analysis.""" def __init__(self): """Initialize the tools.""" self.parser = GDScriptParser() self.project_root: Optional[Path] = None self._gdscript_files: list[Path] = [] def get_tools(self) -> list[Tool]: """Get all available tools. Returns: List of Tool definitions """ return [ Tool( name="analyze_gdscript_file", description="Analyze a GDScript file and extract its structure (classes, functions, signals, variables, enums). Returns a comprehensive overview without reading the entire file into context.", inputSchema={ "type": "object", "properties": { "file_path": { "type": "string", "description": "Path to the GDScript file to analyze", } }, "required": ["file_path"], }, ), Tool( name="get_gdscript_structure", description="Get a high-level structure view of a GDScript file, showing all classes, functions, signals, and variables with their line numbers.", inputSchema={ "type": "object", "properties": { "file_path": { "type": "string", "description": "Path to the GDScript file", } }, "required": ["file_path"], }, ), Tool( name="find_gdscript_symbol", description="Search for a specific symbol (class, function, signal, etc.) in a GDScript file and get its details.", inputSchema={ "type": "object", "properties": { "file_path": { "type": "string", "description": "Path to the GDScript file", }, "symbol_name": { "type": "string", "description": "Name of the symbol to find", }, }, "required": ["file_path", "symbol_name"], }, ), Tool( name="get_gdscript_dependencies", description="Extract dependencies from a GDScript file (extends, preload, import statements).", inputSchema={ "type": "object", "properties": { "file_path": { "type": "string", "description": "Path to the GDScript file", } }, "required": ["file_path"], }, ), Tool( name="analyze_gdscript_code", description="Analyze GDScript code provided directly and extract its structure.", inputSchema={ "type": "object", "properties": { "code": { "type": "string", "description": "GDScript source code to analyze", } }, "required": ["code"], }, ), Tool( name="set_project_root", description="Set the project root directory to enable project-wide analysis. This will index all .gd files in the project.", inputSchema={ "type": "object", "properties": { "project_root": { "type": "string", "description": "Path to the project root directory", } }, "required": ["project_root"], }, ), Tool( name="get_project_root", description="Get the current project root directory and count of indexed GDScript files.", inputSchema={ "type": "object", "properties": {}, "required": [], }, ), Tool( name="find_references", description="Find all references to a symbol across the project or in a specific file.", inputSchema={ "type": "object", "properties": { "symbol_name": { "type": "string", "description": "Name of the symbol to find references for", }, "file_path": { "type": "string", "description": "Optional: Limit search to a specific file. If not provided, searches entire project.", }, }, "required": ["symbol_name"], }, ), ] def handle_tool_call(self, tool_name: str, tool_input: dict[str, Any]) -> CallToolResult: """Handle a tool call. Args: tool_name: Name of the tool to execute tool_input: Input parameters for the tool Returns: CallToolResult with the output """ try: if tool_name == "analyze_gdscript_file": return self._analyze_file(tool_input["file_path"]) elif tool_name == "get_gdscript_structure": return self._get_structure(tool_input["file_path"]) elif tool_name == "find_gdscript_symbol": return self._find_symbol(tool_input["file_path"], tool_input["symbol_name"]) elif tool_name == "get_gdscript_dependencies": return self._get_dependencies(tool_input["file_path"]) elif tool_name == "analyze_gdscript_code": return self._analyze_code(tool_input["code"]) elif tool_name == "set_project_root": return self._set_project_root(tool_input["project_root"]) elif tool_name == "get_project_root": return self._get_project_root() elif tool_name == "find_references": return self._find_references(tool_input["symbol_name"], tool_input.get("file_path")) else: return CallToolResult( content=[TextContent(type="text", text=f"Unknown tool: {tool_name}")], isError=True, ) except Exception as e: return CallToolResult( content=[TextContent(type="text", text=f"Error: {str(e)}")], isError=True, ) def _analyze_file(self, file_path: str) -> CallToolResult: """Analyze a GDScript file. Args: file_path: Path to the file Returns: CallToolResult with analysis """ try: path = Path(file_path) if not path.exists(): return CallToolResult( content=[TextContent(type="text", text=f"File not found: {file_path}")], isError=True, ) if not path.suffix.lower() in [".gd", ".gdscript"]: return CallToolResult( content=[TextContent(type="text", text="File must be a .gd or .gdscript file")], isError=True, ) code = path.read_text(encoding="utf-8") tree = self.parser.parse(code) symbols = self.parser.get_symbols(tree) result = { "file": file_path, "symbols": symbols, "summary": { "total_classes": len(symbols["classes"]), "total_functions": len(symbols["functions"]), "total_signals": len(symbols["signals"]), "total_variables": len(symbols["variables"]), "total_enums": len(symbols["enums"]), }, } return CallToolResult( content=[TextContent(type="text", text=json.dumps(result, indent=2))], isError=False, ) except Exception as e: return CallToolResult( content=[TextContent(type="text", text=f"Error analyzing file: {str(e)}")], isError=True, ) def _get_structure(self, file_path: str) -> CallToolResult: """Get structure view of a GDScript file. Args: file_path: Path to the file Returns: CallToolResult with structure """ try: path = Path(file_path) if not path.exists(): return CallToolResult( content=[TextContent(type="text", text=f"File not found: {file_path}")], isError=True, ) code = path.read_text(encoding="utf-8") tree = self.parser.parse(code) structure = self.parser.get_structure(tree, code) return CallToolResult( content=[TextContent(type="text", text=structure)], isError=False, ) except Exception as e: return CallToolResult( content=[TextContent(type="text", text=f"Error getting structure: {str(e)}")], isError=True, ) def _find_symbol(self, file_path: str, symbol_name: str) -> CallToolResult: """Find a symbol in a GDScript file. Args: file_path: Path to the file symbol_name: Name of the symbol to find Returns: CallToolResult with symbol info """ try: path = Path(file_path) if not path.exists(): return CallToolResult( content=[TextContent(type="text", text=f"File not found: {file_path}")], isError=True, ) code = path.read_text(encoding="utf-8") tree = self.parser.parse(code) symbol = self.parser.find_symbol(tree, symbol_name) if symbol: return CallToolResult( content=[TextContent(type="text", text=json.dumps(symbol, indent=2))], isError=False, ) else: return CallToolResult( content=[TextContent(type="text", text=f"Symbol '{symbol_name}' not found")], isError=True, ) except Exception as e: return CallToolResult( content=[TextContent(type="text", text=f"Error finding symbol: {str(e)}")], isError=True, ) def _get_dependencies(self, file_path: str) -> CallToolResult: """Get dependencies from a GDScript file. Args: file_path: Path to the file Returns: CallToolResult with dependencies """ try: path = Path(file_path) if not path.exists(): return CallToolResult( content=[TextContent(type="text", text=f"File not found: {file_path}")], isError=True, ) code = path.read_text(encoding="utf-8") tree = self.parser.parse(code) dependencies = self.parser.get_dependencies(tree, code) result = { "file": file_path, "dependencies": dependencies, } return CallToolResult( content=[TextContent(type="text", text=json.dumps(result, indent=2))], isError=False, ) except Exception as e: return CallToolResult( content=[TextContent(type="text", text=f"Error getting dependencies: {str(e)}")], isError=True, ) def _analyze_code(self, code: str) -> CallToolResult: """Analyze GDScript code provided directly. Args: code: GDScript source code Returns: CallToolResult with analysis """ try: tree = self.parser.parse(code) symbols = self.parser.get_symbols(tree) structure = self.parser.get_structure(tree, code) result = { "structure": structure, "symbols": symbols, "summary": { "total_classes": len(symbols["classes"]), "total_functions": len(symbols["functions"]), "total_signals": len(symbols["signals"]), "total_variables": len(symbols["variables"]), "total_enums": len(symbols["enums"]), }, } return CallToolResult( content=[TextContent(type="text", text=json.dumps(result, indent=2))], isError=False, ) except Exception as e: return CallToolResult( content=[TextContent(type="text", text=f"Error analyzing code: {str(e)}")], isError=True, ) def _load_gdscript_files(self) -> None: """Load all .gd files from the project root.""" if not self.project_root: self._gdscript_files = [] return self._gdscript_files = [] for file_path in self.project_root.rglob("*.gd"): self._gdscript_files.append(file_path) def _set_project_root(self, project_root: str) -> CallToolResult: """Set the project root directory. Args: project_root: Path to the project root Returns: CallToolResult with status """ try: root_path = Path(project_root).resolve() if not root_path.exists(): return CallToolResult( content=[TextContent(type="text", text=f"Project root does not exist: {project_root}")], isError=True, ) if not root_path.is_dir(): return CallToolResult( content=[TextContent(type="text", text=f"Project root is not a directory: {project_root}")], isError=True, ) self.project_root = root_path self._load_gdscript_files() result = { "project_root": str(self.project_root), "gdscript_files_count": len(self._gdscript_files), "status": "success", } return CallToolResult( content=[TextContent(type="text", text=json.dumps(result, indent=2))], isError=False, ) except Exception as e: return CallToolResult( content=[TextContent(type="text", text=f"Error setting project root: {str(e)}")], isError=True, ) def _get_project_root(self) -> CallToolResult: """Get the current project root. Returns: CallToolResult with project root info """ try: if not self.project_root: return CallToolResult( content=[TextContent(type="text", text="No project root set")], isError=False, ) result = { "project_root": str(self.project_root), "gdscript_files_count": len(self._gdscript_files), } return CallToolResult( content=[TextContent(type="text", text=json.dumps(result, indent=2))], isError=False, ) except Exception as e: return CallToolResult( content=[TextContent(type="text", text=f"Error getting project root: {str(e)}")], isError=True, ) def _find_references(self, symbol_name: str, file_path: Optional[str] = None) -> CallToolResult: """Find references to a symbol. Args: symbol_name: Name of the symbol to find file_path: Optional specific file to search in Returns: CallToolResult with references """ try: files_to_search: list[Path] = [] if file_path: # Search in specific file path = Path(file_path) if not path.exists(): return CallToolResult( content=[TextContent(type="text", text=f"File not found: {file_path}")], isError=True, ) files_to_search = [path] elif self.project_root: # Search in project files_to_search = self._gdscript_files else: return CallToolResult( content=[TextContent(type="text", text="No project root set and no specific file provided")], isError=True, ) all_references = [] for file in files_to_search: try: code = file.read_text(encoding="utf-8") tree = self.parser.parse(code) references = self.parser.find_references(tree, symbol_name) for ref in references: all_references.append({ "file": str(file.relative_to(self.project_root) if self.project_root else file), "line": ref["line"], "column": ref["column"], "end_line": ref["end_line"], "end_column": ref["end_column"], }) except Exception as e: # Skip files that can't be parsed continue result = { "symbol": symbol_name, "total_references": len(all_references), "references": all_references, } return CallToolResult( content=[TextContent(type="text", text=json.dumps(result, indent=2))], isError=False, ) except Exception as e: return CallToolResult( content=[TextContent(type="text", text=f"Error finding references: {str(e)}")], isError=True, )

Implementation Reference

Latest Blog Posts

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/minami110/mcp-gdscript'

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