typecheck
Validate source code types automatically through HooksMCP to detect errors before execution.
Instructions
Typecheck the source code
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
No arguments | |||
Implementation Reference
- hooks_mcp/server.py:207-277 (registration)Registers the MCP list_tools and call_tool handlers. list_tools returns tool definitions for all actions including 'typecheck'. call_tool handles execution by matching tool name to config action.@server.list_tools() async def list_tools() -> List[Tool]: return tools @server.call_tool() async 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/executor.py:23-80 (handler)Executes the tool logic for 'typecheck' by substituting parameters into the command (e.g. 'mypy src'), validating inputs, running subprocess securely without shell, capturing and processing output.def 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/server.py:63-153 (schema)Generates the input schema for the 'typecheck' tool based on its Action parameters, excluding env vars, defining properties and required fields.def create_tool_definitions( config: HooksMCPConfig, disable_prompt_tool: bool = False ) -> List[Tool]: """ Create MCP tool definitions from the HooksMCP configuration. Args: config: The HooksMCP configuration disable_prompt_tool: If True, don't expose the get_prompt tool Returns: List of MCP Tool definitions """ tools = [] for 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) # Add get_prompt tool if there are prompts and it should be exposed if config.prompts and not disable_prompt_tool: # Determine which prompts to expose based on get_prompt_tool_filter exposed_prompts = config.prompts if config.get_prompt_tool_filter is not None: # If filter is empty, don't expose the tool at all if not config.get_prompt_tool_filter: return tools # Otherwise, filter prompts by name filter_set = set(config.get_prompt_tool_filter) exposed_prompts = [p for p in config.prompts if p.name in filter_set] # Only add the tool if there are prompts to expose if exposed_prompts: # Build tool description with list of prompts prompt_list_desc = "\n".join( [f"- {prompt.name}: {prompt.description}" for prompt in exposed_prompts] ) tool_description = ( "Get a prompt designed for this codebase. The prompts include:\n" f"{prompt_list_desc}" ) # Create enum of prompt names for the tool parameter prompt_names = [prompt.name for prompt in exposed_prompts] get_prompt_tool = Tool( name="get_prompt", description=tool_description, inputSchema={ "type": "object", "properties": { "prompt_name": { "type": "string", "description": "The name of the prompt to retrieve", "enum": prompt_names, } }, "required": ["prompt_name"], }, ) tools.append(get_prompt_tool) return tools
- hooks_mcp/utils.py:21-49 (helper)Helper utility to process terminal output from commands like mypy typecheck, stripping ANSI codes and handling carriage returns for clean MCP response.def 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()