diff --git a/.coderabbit.yaml b/.coderabbit.yaml index 9cf4d5e0..9b317840 100644 --- a/.coderabbit.yaml +++ b/.coderabbit.yaml @@ -12,12 +12,13 @@ reviews: collapse_walkthrough: true poem: false request_changes_workflow: false - high_level_summary: true + high_level_summary: false high_level_summary_placeholder: "@coderabbitai summary" abort_on_close: true + review_status: false auto_review: - enabled: true + enabled: false drafts: false base_branches: - main diff --git a/.github/workflows/build-publish.yml b/.github/workflows/build-publish.yml index 8f07214a..c8aeb5fe 100644 --- a/.github/workflows/build-publish.yml +++ b/.github/workflows/build-publish.yml @@ -14,7 +14,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v5.0.0 + uses: actions/checkout@v6.0.1 with: fetch-depth: 0 @@ -36,7 +36,7 @@ jobs: run: python -m build --sdist --wheel - name: Upload artifacts - uses: actions/upload-artifact@v4.6.2 + uses: actions/upload-artifact@v6.0.0 with: name: artifacts path: dist @@ -57,7 +57,7 @@ jobs: steps: - name: Download artifacts - uses: actions/download-artifact@v5.0.0 + uses: actions/download-artifact@v7.0.0 with: name: artifacts path: dist diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0e02546d..6111cd7a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -38,15 +38,15 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v5.0.0 + uses: actions/checkout@v6.0.1 - name: Install Python - uses: actions/setup-python@v6.0.0 + uses: actions/setup-python@v6.1.0 with: python-version-file: pyproject.toml - name: Install uv - uses: astral-sh/setup-uv@v7.1.0 + uses: astral-sh/setup-uv@v7.1.6 with: enable-cache: true prune-cache: false @@ -83,15 +83,15 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v5.0.0 + uses: actions/checkout@v6.0.1 - name: Install Python - uses: actions/setup-python@v6.0.0 + uses: actions/setup-python@v6.1.0 with: python-version-file: pyproject.toml - name: Install uv - uses: astral-sh/setup-uv@v7.1.0 + uses: astral-sh/setup-uv@v7.1.6 with: enable-cache: true prune-cache: false @@ -112,7 +112,7 @@ jobs: run: uv run --locked pytest --cov-report=xml - name: Upload coverage report - uses: codecov/codecov-action@v5.5.1 + uses: codecov/codecov-action@v5.5.2 with: env_vars: OS,PYTHON disable_search: true diff --git a/.github/workflows/ci_installation.yaml b/.github/workflows/ci_installation.yaml index acdcbe94..3c403654 100644 --- a/.github/workflows/ci_installation.yaml +++ b/.github/workflows/ci_installation.yaml @@ -7,6 +7,7 @@ on: paths: - 'src/linux_mcp_server/**' - 'scripts/verify_installation.sh' + - 'scripts/verify_container_integration.sh' pull_request: branches: @@ -14,6 +15,7 @@ on: paths: - 'src/linux_mcp_server/**' - 'scripts/verify_installation.sh' + - 'scripts/verify_container_integration.sh' env: PIP_DISABLE_PIP_VERSION_CHECK: 1 @@ -41,15 +43,15 @@ jobs: steps: - name: Checkout Code - uses: actions/checkout@v5.0.0 + uses: actions/checkout@v6.0.1 - name: Install Python ${{ matrix.python-version }} - uses: actions/setup-python@v6.0.0 + uses: actions/setup-python@v6.1.0 with: python-version: ${{ matrix.python-version }} - name: Install uv - uses: astral-sh/setup-uv@v7.1.0 + uses: astral-sh/setup-uv@v7.1.6 with: enable-cache: true prune-cache: false @@ -60,5 +62,13 @@ jobs: METHOD: ${{ matrix.method }} UV_PYTHON: python run: | - chmod +x scripts/verify_installation.sh - ./scripts/verify_installation.sh + bash ./scripts/verify_installation.sh + + container_integration_qa: + name: Container Integration - Linux + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6.0.1 + - name: Run Container Integration Test + run: | + bash ./scripts/verify_container_integration.sh diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 00000000..c77d551c --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,63 @@ +name: Documentation + +on: + push: + branches: + - main + paths: + - "docs/**" + - "src/**" + - "mkdocs.yml" + - "pyproject.toml" + - ".github/workflows/docs.yml" + workflow_dispatch: + +permissions: + contents: read + pages: write + id-token: write + +concurrency: + group: pages + cancel-in-progress: false + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v6.0.1 + + - name: Install Python + uses: actions/setup-python@v6.1.0 + with: + python-version-file: pyproject.toml + + - name: Install uv + uses: astral-sh/setup-uv@v7.1.6 + with: + enable-cache: true + prune-cache: false + + - name: Install dependencies + run: uv sync --locked --group docs + + - name: Build documentation + run: uv run --locked mkdocs build --strict + + - name: Upload artifact + uses: actions/upload-pages-artifact@v4.0.0 + with: + path: site + + deploy: + if: github.event.repository.fork == false + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-latest + needs: build + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4.0.5 diff --git a/.gitignore b/.gitignore index b34ccac4..971f2dcb 100644 --- a/.gitignore +++ b/.gitignore @@ -37,6 +37,9 @@ coverage/ htmlcov/ .tox/ +# Documentation build +site/ + # IDEs .vscode/ .idea/ diff --git a/.tekton/integration-lifecycle.yaml b/.tekton/integration-lifecycle.yaml new file mode 100644 index 00000000..2852b842 --- /dev/null +++ b/.tekton/integration-lifecycle.yaml @@ -0,0 +1,18 @@ +apiVersion: appstudio.redhat.com/v1alpha1 +kind: IntegrationTestScenario +metadata: + name: container-lifecycle-test +spec: + application: linux-mcp-server + resolverRef: + resolver: git + params: + - name: url + value: https://github.com/rhel-lightspeed/linux-mcp-server.git + - name: revision + value: main + - name: pathInRepo + value: .tekton/task-verify-container.yaml + params: + - name: qa_image_url + value: "quay.io/redhat-user-workloads/rhel-lightspeed-tenant/linux-mcp-server-test-suite:latest" diff --git a/.tekton/integration-protocol.yaml b/.tekton/integration-protocol.yaml new file mode 100644 index 00000000..d15fad2c --- /dev/null +++ b/.tekton/integration-protocol.yaml @@ -0,0 +1,18 @@ +apiVersion: appstudio.redhat.com/v1alpha1 +kind: IntegrationTestScenario +metadata: + name: mcp-protocol-audit +spec: + application: linux-mcp-server + resolverRef: + resolver: git + params: + - name: url + value: https://github.com/rhel-lightspeed/linux-mcp-server.git + - name: revision + value: main + - name: pathInRepo + value: .tekton/task-mcp-protocol-audit.yaml + params: + - name: qa_image_url + value: "quay.io/redhat-user-workloads/rhel-lightspeed-tenant/linux-mcp-server-test-suite:latest" diff --git a/.tekton/pipeline-build-multiarch.yaml b/.tekton/pipeline-build-multiarch.yaml index a77252ad..8e444177 100644 --- a/.tekton/pipeline-build-multiarch.yaml +++ b/.tekton/pipeline-build-multiarch.yaml @@ -226,6 +226,28 @@ spec: taskRef: name: get-version + - name: build-qa-toolbox + params: + - name: IMAGE + # We use the same output-image param but append a suffix + value: $(params.output-image)-test-suite + - name: DOCKERFILE + value: Containerfile.qa + - name: CONTEXT + value: . + - name: SOURCE_ARTIFACT + value: $(tasks.clone-repository.results.SOURCE_ARTIFACT) + runAfter: + - clone-repository + taskRef: + resolver: bundles + params: + - name: name + value: buildah-remote-oci-ta + - name: bundle + # Using the same bundle version you use for build-images + value: quay.io/konflux-ci/tekton-catalog/task-buildah-remote-oci-ta:0.7@sha256:ee5e01eb59a3f70bb1012950fbc4081bac96d3f3517e6d204314484cd2e0059b + - name: build-images matrix: params: diff --git a/.tekton/task-mcp-protocol-audit.yaml b/.tekton/task-mcp-protocol-audit.yaml new file mode 100644 index 00000000..a04d74f5 --- /dev/null +++ b/.tekton/task-mcp-protocol-audit.yaml @@ -0,0 +1,34 @@ +apiVersion: tekton.dev/v1 +kind: Task +metadata: + name: mcp-protocol-audit +spec: + description: Audits the MCP server for protocol compliance, tool discovery, and stdout hygiene. + params: + - name: image_url + type: string + [cite_start]description: The URL of the image to audit. [cite: 4] + - name: qa_image_url + default: "quay.io/redhat-user-workloads/rhel-lightspeed-tenant/linux-mcp-server-test-suite:latest" + + sidecars: + - name: mcp-server + [cite_start]image: $(params.image_url) [cite: 1] + command: ["/bin/sh", "-c"] + args: + - | + microdnf install -y socat + socat TCP-LISTEN:8080,reuseaddr,fork EXEC:linux-mcp-server,stderr + + steps: + - name: validate-tool-discovery + image: $(params.qa_image_url) + script: | + #!/usr/bin/env bash + /usrl/local/bin/scripts/mcp_protocol_audit.sh [cite: 4] + + - name: verify-stdout-hygiene + image: $(params.qa_image_url) + script: | + #!/usr/bin/env bash + /usr/local/bin/scripts/verify_stdout_hygiene.sh [cite: 4] diff --git a/.tekton/task-verify-container.yaml b/.tekton/task-verify-container.yaml new file mode 100644 index 00000000..2756a974 --- /dev/null +++ b/.tekton/task-verify-container.yaml @@ -0,0 +1,33 @@ +apiVersion: tekton.dev/v1 +kind: Task +metadata: + name: verify-container-lifecycle +spec: + [cite_start]description: Verifies MCP server startup, permissions, and handshake via sidecar. [cite: 4] + params: + - name: image_url + type: string + - name: qa_image_url + default: "quay.io/redhat-user-workloads/rhel-lightspeed-tenant/linux-mcp-server-test-suite:latest" + + sidecars: + - name: server + image: $(params.image_url) + command: ["/bin/sh", "-c"] + args: + - | + socat TCP-LISTEN:8080,reuseaddr,fork EXEC:linux-mcp-server,stderr + + steps: + - name: verify-user-and-startup + image: $(params.qa_image_url) + script: | + #!/usr/bin/env bash + # The QA container probes the sidecar's network port + /usr/local/bin/scripts/verify_container_lifecycle.sh + + - name: verify-mcp-handshake + image: $(params.qa_image_url) + script: | + #!/usr/bin/env bash + /usr/local/bin/scripts/verify_mcp_handshake.sh diff --git a/AGENTS.md b/AGENTS.md index c6349ea9..044ff45f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,12 +1,54 @@ # Linux MCP Server -## Development Guidelines - -- Always run tests, linters, and type checkers before committing code with `make verify`. -- Extend existing tests using parameterized tests rather than adding new test cases. -- Use fixtures to deduplicate setup code across tests. -- If a fixture could be used in multiple test modules, place it in `conftest.py`. -- Use mocks sparingly and try to pass objects to the code under test instead. -- Use `autospec=True` when patching to verify arguments match the real function signature. -- Use `spec=` with MagicMock to restrict attributes to those of the real object. -- Prefer Pydantic models over dataclasses. +Read-only MCP server for Linux system diagnostics. + +## Commands + +```bash +uv sync # Install dependencies +uv run pytest # Run tests +uv run ruff check src tests # Lint +uv run pyright # Type check +make verify # All checks (required before commit) +``` + +## Project Layout + +- `src/linux_mcp_server/tools/` - MCP tools (logs, network, processes, services, storage, system_info) +- `src/linux_mcp_server/commands.py` - Command definitions +- `src/linux_mcp_server/formatters.py` / `parsers.py` - Output formatting and parsing +- `tests/` - Mirrors src structure + +## Rules + +**Code:** PEP 8, type hints required, async/await for I/O, 120 char max, prefer Pydantic over dataclasses + +**Testing:** +- Run `make verify` before committing +- Use parameterized tests and fixtures (shared fixtures go in `conftest.py`) +- Use `autospec=True` when patching; `spec=` with MagicMock +- 100% patch coverage for new code + +**Security (Critical):** +- All tools must be read-only with `readOnlyHint=True` +- Validate all input, use allowlists for file paths, sanitize shell params + +## Adding Tools + +1. Create tool in `src/linux_mcp_server/tools/` using `@mcp.tool()`, `@log_tool_call`, `@disallow_local_execution_in_containers` decorators +2. Register command in `commands.py` +3. Write tests in `tests/tools/` + +See `src/linux_mcp_server/tools/processes.py` for reference. + +## Commits & PRs + +Use [Conventional Commits](https://www.conventionalcommits.org/): `(): ` + +Types: `feat`, `fix`, `docs`, `test`, `refactor`, `perf`, `chore` + +**PRs must be small and focused** - one logical change per PR. Split large changes into incremental PRs. + +## Docs + +Full details: `docs/contributing.md` | Architecture: `docs/architecture.md` | API: `docs/api/` diff --git a/Containerfile.qa b/Containerfile.qa new file mode 100644 index 00000000..eecaaf81 --- /dev/null +++ b/Containerfile.qa @@ -0,0 +1,11 @@ +FROM registry.access.redhat.com/ubi10/ubi-minimal:latest + +RUN microdnf -y install nc jq bash socat openssh && microdnf clean all + +RUN mkdir -p /usr/local/bin/scripts + +# Copy all scripts into the test image +COPY scripts/ /usr/local/bin/scripts/ +RUN chmod +x /usr/local/bin/scripts/*.sh + +ENTRYPOINT ["/bin/bash"] diff --git a/Makefile b/Makefile index 39b0e4fd..5a614224 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: help sync lint format types test ci verify fix clean +.PHONY: help sync lint format types test ci verify fix clean docs docs-serve # Default target help: @@ -14,6 +14,10 @@ help: @echo " make sync - Install/sync all dependencies" @echo " make fix - Auto-fix lint and format issues" @echo " make clean - Remove build artifacts and caches" + @echo "" + @echo "📚 Documentation Targets:" + @echo " make docs - Build documentation" + @echo " make docs-serve - Serve docs locally with live reload" sync: uv sync --locked @@ -41,6 +45,12 @@ fix: uv run --locked ruff format clean: - rm -rf .pytest_cache .ruff_cache .pyright coverage dist build + rm -rf .pytest_cache .ruff_cache .pyright coverage dist build site rm -rf src/*.egg-info find . -type d -name __pycache__ -exec rm -rf {} + 2>/dev/null || true + +docs: + uv run --locked --group docs mkdocs build + +docs-serve: + uv run --locked --group docs mkdocs serve diff --git a/README.md b/README.md index 8e29b42e..7532db43 100644 --- a/README.md +++ b/README.md @@ -18,249 +18,12 @@ A Model Context Protocol (MCP) server for read-only Linux system administration, - **RHEL/systemd Focused**: Optimized for Red Hat Enterprise Linux systems -## Installation +## Installation and Usage -Python 3.10 or later is required. - -### Install with `pip` - -Create and activate a virtual environment, then install using `pip`: -```bash -pip install linux-mcp-server -``` - -Or install in the Python user directory -```bash -pip install --user linux-mcp-server -``` - -### Install with `uv` - -Install using `uv` -```bash -uv tool install linux-mcp-server -``` - -See the [complete installation guide](https://github.com/rhel-lightspeed/linux-mcp-server/blob/main/docs/Install.md) for more details. - -## Running from a container - -A container runtime such as [Podman] or [Docker] is required. - -Since the MCP server uses SSH to connect to remote hosts, SSH keys need to be available inside the container. If the SSH key is encrypted, a passphrase needs to be provided to decrypt the key. - - -## Configuration - -Key environment variables: -- `LINUX_MCP_ALLOWED_LOG_PATHS` - Comma-separated list of log files that can be accessed -- `LINUX_MCP_LOG_LEVEL` - Logging level (DEBUG, INFO, WARNING, ERROR, CRITICAL) -- `LINUX_MCP_SSH_KEY_PATH` - Path to SSH private key for remote execution -- `LINUX_MCP_USER` - Username used for SSH connections (optional) - -See [Environment Variables](https://github.com/rhel-lightspeed/linux-mcp-server/blob/main/docs/Install.md#environment-variables) for more details. - -### SSH Configuration - -The SSH configuration can be used to configure per-host connection settings. See [ssh_config][] for details. - -If `LINUX_MCP_USER` is set, it will be used for all connections to remote hosts. If per-host connections settings are required, use SSH config. - -Here is an example of per-host settings using SSH config: - -``` -# ~/.ssh/config -Host server1 - HostName 10.0.0.64 - User millie - -Host server2 - HostName 10.0.0.128 - User bob - Port 2237 -``` - - -### Example Configurations - -For the following example configurations, make sure to provide real paths to SSH key and log files. - - -> [!NOTE] -> If `ssh-agent` is configured, any keys loaded into the session will be used automatically when running natively. - -> [!NOTE] -> When using Docker -> - the SSH key must be owned by UID 1001 -> - the log directory must be created beforehand and owned by 1001 -> - remove the `--userns` paramater - -Here is an example of setting up the files on the container host so they are accessible in the running container. -``` -ORIGINAL_UMASK=$(umask) - -umask 0002 -mkdir -p ~/.local/share/linux-mcp-server/logs -sudo chown -R 1001 ~/.local/share/linux-mcp-server/logs - -cp ~/.ssh/id_ed25519 ~/.local/share/linux-mcp-server/ -sudo chown 1001 ~/.local/share/linux-mcp-server/id_ed25519 - -umask ${ORIGINAL_UMASK} -``` - -#### Claude Desktop Configuration - -
- Container - -```shell -{ - "mcpServers": { - "Linux Tools": { - "command": "podman", - "args": [ - "run", - "--rm", - "--interactive", - "--userns", "keep-id:uid=1001,gid=0", - "-e", "LINUX_MCP_KEY_PASSPHRASE", - "-e", "LINUX_MCP_USER", - "-v", "/home/tljones/.ssh/id_ed25519:/var/lib/mcp/.ssh/id_ed25519:ro,Z", - "-v", "/home/tljones/.local/share/linux-mcp-server/logs:/var/lib/mcp/.local/share/linux-mcp-server/logs:rw,Z", - "quay.io/redhat-services-prod/rhel-lightspeed-tenant/linux-mcp-server:latest" - ], - "env": { - "LINUX_MCP_KEY_PASSPHRASE": "", - "LINUX_MCP_USER": "tljones" - } - } - } -} - -``` -
- -
- Native - -```shell -{ - "mcpServers": { - "Linux Tools": { - "command": "[venv]/bin/linux-mcp-server", - } - } -} -``` -
- -#### Goose configuration - -
- Container - -```shell -extensions: - linux-tools: - enabled: true - type: stdio - name: linux-tools - description: Linux tools - cmd: podman - args: - - run - - --rm - - --interactive - --userns, - "keep-id:uid=1001,gid=0", - - -e - - LINUX_MCP_KEY_PASSPHRASE - - -e - - LINUX_MCP_USER - - -v - - /home/tljones/.ssh/id_ed25519:/var/lib/mcp/.ssh/id_ed25519:ro - - -v - - /home/tljones/.local/share/linux-mcp-server/logs:/var/lib/mcp/.local/share/linux-mcp-server/logs:rw - - quay.io/redhat-services-prod/rhel-lightspeed-tenant/linux-mcp-server:latest - envs: {} - env_keys: - - LINUX_MCP_KEY_PASSPHRASE - - LINUX_MCP_USER - timeout: 30 - bundled: null - available_tools: [] -``` -
- -
- Native - -```shell -extensions: - linux-tools: - enabled: true - type: stdio - name: linux-tools - description: Linux tools - cmd: [venv]/bin/linux-mcp-server - envs: {} - env_keys: - - LINUX_MCP_KEY_PASSPHRASE - - LINUX_MCP_USER - timeout: 30 - bundled: null - available_tools: [] -``` -
- -## Audit Logging - -All server operations are logged in both human-readable and JSON formats with automatic daily rotation and configurable retention. Logs are stored in `~/.local/share/linux-mcp-server/logs/`. - - -## Tool execution - -All tools support an optional `host` parameter for remote execution via SSH: - -- **Authentication**: SSH key-based authentication only (no password support) -- **Key Discovery**: Automatically discovers SSH keys from `~/.ssh/` or use `LINUX_MCP_SSH_KEY_PATH` -- **Connection Pooling**: Reuses SSH connections for efficiency -- **Multi-Host**: Each tool call can target a different remote host - -**Requirements**: -- SSH key-based authentication must be configured on remote hosts -- Remote user must have appropriate permissions for diagnostic commands - - -## Usage - -### Running the Server - -#### Installed using `pip` - -Run from within the virtual environment: -```bash -[path to virtual environment]/bin/linux-mcp-server -``` - -Or run from the Python user directory: -```bash -~/.local/bin/linux-mcp-server -``` - -#### Installed using `uv` - -```bash -uv tool run linux-mcp-server -``` - -For detailed usage instructions, available tools, and example troubleshooting sessions, see [Usage](https://github.com/rhel-lightspeed/linux-mcp-server/blob/main/docs/Usage.md). - -### Using with Claude Desktop - -For complete Claude Desktop integration instructions including platform-specific config file locations and alternative configurations, see [Claude Desktop Integration](https://github.com/rhel-lightspeed/linux-mcp-server/blob/main/docs/Install.md#claude-desktop-integration). +For detailed instructions on setting up and using the Linux MCP Server, please refer to our official documentation: +- **[Installation Guide](https://rhel-lightspeed.github.io/linux-mcp-server/install/)**: Detailed steps for pip, uv, and container-based deployments. +- **[Usage Guide](https://rhel-lightspeed.github.io/linux-mcp-server/usage/)**: Information on running the server, configuring AI agents (Claude, Goose), and troubleshooting. ## Available Tools @@ -302,7 +65,3 @@ For complete Claude Desktop integration instructions including platform-specific - **Audit Logger**: Comprehensive logging in both human-readable and JSON formats with automatic rotation - **Multi-Target Execution**: Single server instance can execute commands on local system or multiple remote hosts - -[Podman]: https://podman-desktop.io -[Docker]: https://www.docker.com -[ssh_config]: https://www.man7.org/linux/man-pages/man5/ssh_config.5.html diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md deleted file mode 100644 index 6d910b81..00000000 --- a/docs/CONTRIBUTING.md +++ /dev/null @@ -1,362 +0,0 @@ -# Contributing to Linux MCP Server - -Thank you for your interest in contributing! This document provides guidelines for contributing to the Linux MCP Server project. - -### Prerequisites - -- **Python 3.10 or higher** -- **git** -- **pip** -- **uv** - https://github.com/astral-sh/uv#installation - -### Method 1: Setup with pip and a virtual environment - -**Step 1: Clone the repository** - -```bash -git clone https://github.com/rhel-lightspeed/linux-mcp-server.git -cd linux-mcp-server -``` - -**Step 2: Create and activate virtual environment** - -```bash -python -m venv .venv -source .venv/bin/activate # On Linux/macOS -# OR -.venv\Scripts\activate # On Windows -``` - -**Step 3: Install the package in editable mode with dev dependencies** - -```bash -pip install -e . --group dev -``` - -**Step 4: Verify the installation** - -```bash -python -m linux_mcp_server -``` - -**Step 5: Run the tests** - -```bash -pytest -``` - -All tests should pass. - -### Method 2: Setup with uv - -**Step 1: Clone the repository** - -```bash -git clone https://github.com/rhel-lightspeed/linux-mcp-server.git -cd linux-mcp-server -``` - -**Step 2: Create virtual environment and install dev dependencies** - -Note that by default `uv` creates an editable install as well as installs all packages in the `dev` dependency group. - -```bash -uv sync -``` - -**Step 3: Verify the installation** - -```bash -uv run linux-mcp-server -``` - -**Step 5: Run the tests** - -```bash -uv run pytest -``` - -All tests should pass. - - -## Development Workflow - -We follow Test-Driven Development (TDD) principles: - -### 1. RED - Write a Failing Test -```python -# tests/test_new_feature.py -import pytest -from linux_mcp_server.tools import new_module - -async def test_new_feature(): - result = await new_module.new_function() - assert "expected" in result -``` - -### 2. GREEN - Implement Minimal Code to Pass -```python -# src/linux_mcp_server/tools/new_module.py -async def new_function(): - return "expected result" -``` - -### 3. REFACTOR - Improve Code Quality -- Improve readability -- Remove duplication -- Ensure all tests still pass - -### 4. Commit -```bash -git add . -git commit -m "feat: add new feature - -- Detailed description of what was added -- Tests included -- All tests passing" -``` - -## Code Standards - -### Style Guidelines -- Follow PEP 8 for Python code -- Use type hints for function parameters and return values -- Use async/await for I/O operations -- Maximum line length: 120 characters - -### Documentation -- Add docstrings to all public functions -- Use clear, descriptive variable names -- Comment complex logic - -### Testing -- Write tests for all new features -- Maintain project test coverage above 70%, patch test coverage must be 100%. -- Use descriptive test names that explain what is being tested - -## Adding New Tools - -When adding a new diagnostic tool: - -1. **Create the tool function in appropriate module:** - ```python - # src/linux_mcp_server/tools/my_tool.py - import typing as t - - @mcp.tool( - title="Useful Tool", - description="Description for LLM to understand the tool.", - annotations=ToolAnnotations(readOnlyHint=True), - ) - @log_tool_call - async def my_tool_name( - param1: str, - ) -> str: - """Documentation string further describing the tool if necessary. - """ - returncode, stdout, _ = await execute_command(["ps", "aux", "--sort=-%cpu"], host=host) - if returncode != 0: - raise ToolError - - return stdout - ``` - -2. **Write tests:** - ```python - # tests/test_my_tool.py - import pytest - from linux_mcp_server.tools import my_tool - - async def test_my_tool(): - result = await my_tool.my_diagnostic_function() - assert isinstance(result, str) - assert "expected content" in result.lower() - - # Test server integration - async def test_server_has_my_tool(): - from linux_mcp_server.server import mcp - tools = await mcp.list_tools() - tool_names = [t.name for t in tools] - assert "my_tool_name" in tool_names - ``` - -3. **Update documentation:** - - Add tool description to README.md - - Add usage examples to USAGE.md - -## Commit Message Format - -We use [Conventional Commits](https://www.conventionalcommits.org/): - -``` -(): - - - -