typecheck
Verify source code types to ensure correctness and prevent errors. Integrated with HooksMCP for automated type-checking without manual command intervention.
Instructions
Typecheck the source code
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
No arguments | |||
Implementation Reference
- hooks_mcp/server.py:212-277 (handler)Generic handler for calling any action tool (like 'typecheck') by matching name to config action and delegating to executorasync def call_tool(name: str, arguments: Dict[str, Any]) -> List[TextContent]: # Handle get_prompt tool specially if name == "get_prompt": prompt_name = arguments.get("prompt_name") if not prompt_name: raise ExecutionError( "HooksMCP Error: 'prompt_name' argument is required for get_prompt tool" ) # Enforce get_prompt_tool_filter if present if hooks_mcp_config.get_prompt_tool_filter is not None: # If filter is empty, don't allow any prompts (shouldn't happen since tool isn't exposed) if not hooks_mcp_config.get_prompt_tool_filter: raise ExecutionError( "HooksMCP Error: No prompts are available through get_prompt tool" ) # Otherwise, check if prompt is in the filter list if prompt_name not in hooks_mcp_config.get_prompt_tool_filter: available_prompts = ", ".join( hooks_mcp_config.get_prompt_tool_filter ) raise ExecutionError( f"HooksMCP Error: Prompt '{prompt_name}' is not available through get_prompt tool. " f"Available prompts: {available_prompts}" ) # Find the prompt by name config_prompt = next( (p for p in hooks_mcp_config.prompts if p.name == prompt_name), None ) if not config_prompt: raise ExecutionError( f"HooksMCP Error: Prompt '{prompt_name}' not found" ) # Get prompt content prompt_content = get_prompt_content(config_prompt, config_path) # Return the prompt content as text return [TextContent(type="text", text=prompt_content)] # Find the action by name action = next((a for a in hooks_mcp_config.actions if a.name == name), None) if not action: raise ExecutionError(f"HooksMCP Error: Action '{name}' not found") try: # Execute the action result = executor.execute_action(action, arguments) # Format the result output = f"Command executed: {action.command}\n" output += f"Exit code: {result['status_code']}\n" if result["stdout"]: output += f"STDOUT:\n{result['stdout']}\n" if result["stderr"]: output += f"STDERR:\n{result['stderr']}\n" return [TextContent(type="text", text=output)] except ExecutionError: raise except Exception as e: raise ExecutionError( f"HooksMCP Error: Unexpected error executing action '{name}': {str(e)}" )
- hooks_mcp/server.py:78-108 (schema)Generates the inputSchema for tool 'typecheck' from its action parameters (excluding env vars), defining string properties and required fieldsfor action in config.actions: # Convert action parameters to MCP tool parameters parameters: List[ActionParameter] = [] for param in action.parameters: # Skip required_env_var and optional_env_var as they're not provided by the client if param.type in [ ParameterType.REQUIRED_ENV_VAR, ParameterType.OPTIONAL_ENV_VAR, ]: continue parameters.append(param) tool = Tool( name=action.name, description=action.description, inputSchema={ "type": "object", "properties": { param.name: { "type": "string", "description": param.description, } for param in parameters }, "required": [ param.name for param in parameters if param.default is None ], }, ) tools.append(tool)
- hooks_mcp/server.py:207-209 (registration)Registers the list_tools method that returns all tools including 'typecheck' created from config@server.list_tools() async def list_tools() -> List[Tool]: return tools
- hooks_mcp/executor.py:23-80 (handler)Core execution logic for action tools like 'typecheck': substitutes parameters into command, sets env vars, runs subprocess securelydef execute_action( self, action: Action, parameters: Dict[str, Any] ) -> Dict[str, Any]: """ Execute an action with the given parameters. Args: action: The action to execute parameters: Parameters provided by the MCP client Returns: Dictionary containing stdout, stderr, and status code """ # Validate and prepare parameters env_vars = self._prepare_parameters(action, parameters) # Parse command and substitute parameters in command args command_args = self._substitute_parameters(action.command, env_vars) # Determine execution directory execution_dir = self.project_root if action.run_path: execution_dir = self.project_root / action.run_path # Validate run_path is within project boundaries if not validate_project_path(action.run_path, self.project_root): raise ExecutionError( f"HooksMCP Error: Invalid run_path '{action.run_path}' for action '{action.name}'. " f"Path must be within project boundaries and not contain directory traversal sequences." ) # Execute command try: # Use subprocess.run with shell=False for security # Pass environment variables separately to avoid shell injection result = subprocess.run( command_args, cwd=execution_dir, env={**os.environ, **env_vars}, capture_output=True, text=True, timeout=action.timeout, shell=False, ) return { "stdout": process_terminal_output(result.stdout), "stderr": process_terminal_output(result.stderr), "status_code": result.returncode, } except subprocess.TimeoutExpired: raise ExecutionError( f"HooksMCP Error: Command for action '{action.name}' timed out after {action.timeout} seconds" ) except Exception as e: raise ExecutionError( f"HooksMCP Error: Failed to execute command for action '{action.name}': {str(e)}" )
- hooks_mcp/utils.py:21-49 (helper)Helper function used to process and clean STDOUT/STDERR output from executed commands like mypy for 'typecheck' tooldef process_terminal_output(text: str) -> str: """ Process terminal output to simulate what would actually be visible after control characters like carriage returns are handled. Args: text: Raw terminal output Returns: Processed text that represents the final visible state """ # Strip ANSI codes first clean_text = strip_ansi_codes(text) # Handle carriage returns - they overwrite the current line lines = clean_text.split("\n") processed_lines = [] for line in lines: # Split by carriage return and keep only the last part (final state) parts = line.split("\r") if parts: # The last part is what would be visible after all carriage returns processed_lines.append(parts[-1]) else: processed_lines.append("") return "\n".join(processed_lines).strip()