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()
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries the full burden of behavioral disclosure. It mentions that the tool returns documentation as a string, which is helpful, but doesn't cover important behavioral aspects like error handling (what happens if the command doesn't exist), performance characteristics, or system dependencies. The description is functional but lacks operational context.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is well-structured with clear sections (Args, Returns) and gets straight to the point. The opening sentence states the purpose clearly, followed by parameter details. While efficient, the 'prefer_economic' explanation could be more concise or clearer about what 'economic approach' means in practice.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a tool with 3 parameters, 0% schema coverage, no annotations, and no output schema, the description does an adequate job. It explains the basic functionality and parameters, but lacks important context about error conditions, performance, and system requirements. The return type is mentioned but not the format or potential variations in output.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The description provides parameter documentation in the Args section, explaining what each parameter does. With 0% schema description coverage, this adds significant value beyond the bare schema. However, some explanations could be clearer - 'prefer_economic' is described but the meaning of 'economic approach' isn't fully explained, and 'man_section' range (1-9) is mentioned but not what different sections represent.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool's purpose: 'Get documentation for a command in Unix-like system.' It specifies the verb ('get documentation') and resource ('a command'), making it easy to understand what the tool does. However, it doesn't explicitly differentiate from sibling tools like 'check_command_exists' or 'list_common_commands' beyond the obvious functional difference.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides no guidance on when to use this tool versus its siblings. While the purpose is clear, there's no mention of alternatives like using 'check_command_exists' first or when 'list_common_commands' might be more appropriate. The description only explains what the tool does, not when to choose it over other options.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

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