Skip to main content
Glama
tizee

Unix Manual Server

by tizee

get_command_documentation

Retrieve Unix command documentation, including help pages and man pages, directly within Claude conversations to understand command usage without leaving the chat interface.

Instructions

Get documentation for a command in Unix-like system.

Args: command: The command to get documentation for (no arguments) prefer_economic: Whether to prefer the economic approach (--help/-h/help) [default: True] man_section: Specific manual section to look in (1-9) [optional]

Returns: The command documentation as a string

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
commandYes
prefer_economicNo
man_sectionNo

Implementation Reference

  • Primary handler function for the 'get_command_documentation' tool. Decorated with @mcp.tool() for MCP registration. Includes type hints and docstring defining the input/output schema. Implements logic to fetch help or man pages for Unix commands.
    @mcp.tool()
    def get_command_documentation(command: str, prefer_economic: bool = True, man_section: int = None) -> str:
        """
        Get documentation for a command in Unix-like system.
    
        Args:
            command: The command to get documentation for (no arguments)
            prefer_economic: Whether to prefer the economic approach (--help/-h/help) [default: True]
            man_section: Specific manual section to look in (1-9) [optional]
    
        Returns:
            The command documentation as a string
        """
        logger.info(f"Getting documentation for command: '{command}', prefer_economic={prefer_economic}, man_section={man_section}")
    
        # Parse the command input to separate main command from subcommands/arguments
        parts = command.strip().split()
        main_command = parts[0]  # Extract the base command
        logger.debug(f"Main command: {main_command}")
    
        # Check if there's a subcommand (at least 2 parts and not an option)
        has_subcommand = len(parts) > 1 and not parts[1].startswith('-')
        subcommand = parts[1] if has_subcommand else None
        cmd_with_subcommand = f"{main_command} {subcommand}" if has_subcommand else None
    
        if has_subcommand:
            logger.debug(f"Detected subcommand: '{subcommand}', will try '{cmd_with_subcommand}' first")
    
        # Validate command name (basic check to prevent injection)
        if not re.match(r'^[a-zA-Z0-9_\.-]+$', main_command):
            logger.warning(f"Invalid command name: '{main_command}'")
            return f"Invalid command name: '{main_command}'"
    
        # Get full path to command
        command_path = get_command_path(main_command)
        if not command_path:
            logger.warning(f"Command not found: '{main_command}'")
            return f"Command not found: '{main_command}'"
    
        # Try economic approach for subcommand first if available
        if has_subcommand and prefer_economic:
            logger.debug(f"Trying economic approach for subcommand: {cmd_with_subcommand}")
    
            # Try --help for subcommand
            help_cmd = safe_execute([command_path, subcommand, "--help"], timeout=5)
            if help_cmd and help_cmd.stdout and help_cmd.returncode < 2:
                logger.info(f"Found help docs using --help for subcommand {cmd_with_subcommand}")
                return f"Help output for '{cmd_with_subcommand}':\n\n{help_cmd.stdout.strip()}"
    
            # Try -h for subcommand
            help_cmd = safe_execute([command_path, subcommand, "-h"], timeout=5)
            if help_cmd and help_cmd.stdout and help_cmd.returncode < 2:
                logger.info(f"Found help docs using -h for subcommand {cmd_with_subcommand}")
                return f"Help output for '{cmd_with_subcommand}':\n\n{help_cmd.stdout.strip()}"
    
            # Try help subcommand for subcommand
            help_cmd = safe_execute([command_path, subcommand, "help"], timeout=5)
            if help_cmd and help_cmd.stdout and help_cmd.returncode < 2:
                logger.info(f"Found help docs using help subcommand for {cmd_with_subcommand}")
                return f"Help output for '{cmd_with_subcommand}':\n\n{help_cmd.stdout.strip()}"
    
            logger.debug(f"No help documentation found for subcommand {cmd_with_subcommand}, falling back to main command")
    
        # Try economic approach for the main command
        if prefer_economic:
            logger.debug(f"Trying economic approach for main command: {main_command}")
            help_result = search_help_documentation(main_command, command_path)
            if help_result:
                return help_result
    
            # Direct check if the previous function failed but command exists
            # Try direct approach for well-known patterns
            if command_path:
                # Try --help directly
                help_cmd = safe_execute([command_path, "--help"], timeout=5)
                if help_cmd and help_cmd.stdout and help_cmd.returncode < 2:
                    logger.info(f"Found help docs by direct --help check for {main_command}")
                    return f"Help output for '{main_command}':\n\n{help_cmd.stdout.strip()}"
    
        # Use man as fallback or if economic approach not preferred
        logger.debug(f"Trying man page for {main_command}")
        # Execute man directly without going through shell
        man_args = ["man"]
        if man_section is not None and 1 <= man_section <= 9:
            man_args.append(str(man_section))
            logger.debug(f"Using man section {man_section}")
        man_args.append(main_command)
    
        try:
            # Use col to strip formatting from man output
            logger.debug(f"Executing man command: {man_args}")
            man_result = subprocess.run(
                man_args,
                stdout=subprocess.PIPE,
                stderr=subprocess.PIPE,
                text=True,
                timeout=10
            )
    
            # Pipe the man output through col to remove formatting
            if man_result.returncode == 0:
                logger.debug("Man command succeeded, processing with col")
                col_result = subprocess.run(
                    ["col", "-b"],
                    input=man_result.stdout,
                    capture_output=True,
                    text=True
                )
                man_text = col_result.stdout
                logger.info(f"Successfully retrieved man page for {main_command}")
                return f"Manual page for '{main_command}':\n\n{man_text}"
            else:
                logger.warning(f"Man command failed with exit code {man_result.returncode}, stderr: {man_result.stderr}")
        except Exception as e:
            logger.error(f"Error executing man command for {main_command}: {str(e)}")
    
        # If we tried man first and it failed, try economic approach as fallback
        if not prefer_economic:
            logger.debug(f"Man failed, trying economic approach as fallback for {main_command}")
            help_result = search_help_documentation(main_command, command_path)
            if help_result:
                return help_result
    
        # If everything failed
        logger.warning(f"All documentation methods failed for '{command}'")
        return f"No documentation available for '{command}'"
  • Helper function used by the tool to locate the full path of the command using the user's shell.
    def get_command_path(command):
        """Get the full absolute path to a command by filtering shell output."""
        logger.debug(f"Searching for command path: {command}")
        try:
            # Use the user's shell (defaulting to /bin/zsh) with login to load the full environment.
            user_shell = os.environ.get('SHELL', '/bin/zsh')
            logger.debug(f"Using shell: {user_shell}")
            result = subprocess.run(
                [user_shell, "-l", "-c", f"command -v {command} 2>/dev/null"],
                capture_output=True,
                text=True
            )
            # Process the output line by line and return the first line that is a valid absolute path.
            for line in result.stdout.splitlines():
                if re.match(r'^/', line):
                    path = line.strip()
                    logger.debug(f"Found command path: {path}")
                    return path
            logger.warning(f"Command not found: {command}")
            return None
        except subprocess.SubprocessError as e:
            logger.error(f"Error finding command path for {command}: {str(e)}")
            return None
  • Helper function for safely executing subprocess calls without shell, used extensively in help fetching and man pages.
    def safe_execute(cmd_args, timeout=10):
        """Safely execute a command directly (not through shell) and return its output."""
        logger.debug(f"Executing command: {cmd_args} with timeout={timeout}")
        try:
            # Execute command directly without shell
            result = subprocess.run(
                cmd_args,
                capture_output=True,
                text=True,
                timeout=timeout,
                shell=False  # Explicitly set shell=False for security
            )
            logger.debug(f"Command exit code: {result.returncode}")
            # Add debug output to see first 100 chars of stdout
            if result.stdout:
                logger.debug(f"Command stdout first 100 chars: {result.stdout[:100].replace('\n', '\\n')}")
            return result
        except subprocess.TimeoutExpired:
            logger.warning(f"Command timed out after {timeout} seconds: {cmd_args}")
            return None
        except (subprocess.SubprocessError, FileNotFoundError, OSError) as e:
            logger.error(f"Error executing command {cmd_args}: {str(e)}")
            return None
  • Helper function to attempt fetching --help, -h, or 'help' output from the command, validating if it's actual help text.
    def search_help_documentation(main_command, command_path):
        """Search for help documentation using --help, -h, or help options."""
        logger.info(f"Searching for help documentation for command: {main_command} at {command_path}")
    
        # Try --help
        logger.debug(f"Trying --help for {main_command}")
        help_result = safe_execute([command_path, "--help"], timeout=5)
        if help_result and help_result.returncode < 2 and help_result.stdout.strip():
            # Verify this is actual help text, not just command execution output
            output = help_result.stdout.strip()
            # Most help text contains words like "usage", "options", or "help"
            # Adding additional terms "USAGE" and checking for version string patterns
            if (re.search(r'usage|options|help|Usage|Options|Help|USAGE|OPTIONS|HELP|USAGE:|VERSION|Version', output, re.IGNORECASE) or
                re.search(r'\d+\.\d+\.\d+', output)):  # Version number pattern
                logger.info(f"Found help documentation using --help for {main_command}")
                return f"Help output for '{main_command}':\n\n{output}"
            else:
                # Add debug output to see what we're getting
                logger.debug(f"--help output did not match help text pattern:\n{output[:200]}...")
    
        # Try -h
        logger.debug(f"Trying -h for {main_command}")
        help_result = safe_execute([command_path, "-h"], timeout=5)
        if help_result and help_result.returncode < 2 and help_result.stdout.strip():
            output = help_result.stdout.strip()
            if (re.search(r'usage|options|help|Usage|Options|Help|USAGE|OPTIONS|HELP|USAGE:|VERSION|Version', output, re.IGNORECASE) or
                re.search(r'\d+\.\d+\.\d+', output)):  # Version number pattern
                logger.info(f"Found help documentation using -h for {main_command}")
                return f"Help output for '{main_command}':\n\n{output}"
            else:
                logger.debug(f"-h output did not match help text pattern:\n{output[:200]}...")
    
        # Try help
        logger.debug(f"Trying help subcommand for {main_command}")
        help_result = safe_execute([command_path, "help"], timeout=5)
        if help_result and help_result.returncode < 2 and help_result.stdout.strip():
            output = help_result.stdout.strip()
            if re.search(r'usage|options|help|Usage|Options|Help|USAGE|OPTIONS|HELP', output, re.IGNORECASE):
                logger.info(f"Found help documentation using help subcommand for {main_command}")
                return f"Help output for '{main_command}':\n\n{output}"
            else:
                logger.debug(f"help subcommand output did not match help text pattern:\n{output[:200]}...")
    
        # If we get here, no valid help documentation was found
        logger.warning(f"No help documentation found for {main_command}")
        return ""
  • MCP tool registration decorator applied to the get_command_documentation handler.
    @mcp.tool()

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/tizee/mcp-unix-manual'

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