Skip to main content
Glama

typecheck

Validate source code types automatically through HooksMCP to detect errors before execution.

Instructions

Typecheck the source code

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault

No arguments

Implementation Reference

  • 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)}"
            )
  • 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)}"
            )
  • 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
  • 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()

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/scosman/actions_mcp'

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