Skip to main content
Glama
teodorio95-portofolio

mcp-security-toolkit

README.md
# mcp-security-toolkit

> Project **#6** of the DevSecOps portfolio: a **local, free** [MCP](https://modelcontextprotocol.io) server that exposes the portfolio's security scanners (Trivy, Gitleaks, Checkov, Semgrep) as tools an AI agent can call — turning "run a scan" into something an LLM does for you, with **zero API cost**.

This is project **#6**. It makes the rest of the portfolio *agent-accessible*: instead of a human typing `trivy fs`, an MCP-aware agent calls the `trivy_fs_scan` tool and reasons over the JSON it gets back.

| # | Project | Role |
|---|---------|------|
| 1 | secure-k8s-lab | Reproducible cluster + GitOps + isolated vulnerable target |
| 2 | devsecops-pipeline | Scanning in CI (SAST/DAST/deps/IaC) |
| 3 | supply-chain-security | Image signing, SBOM, admission control |
| 4 | offensive-writeups | Documented attacks against the lab |
| 5 | runtime-security | Falco + Cilium detecting those attacks |
| **6** | **mcp-security-toolkit** *(this repo)* | **Security scanners exposed as MCP tools for AI agents** |

## The story this repo tells

> "My security tooling isn't just CLI I run by hand — it's an MCP server. Any agent (a local Ollama model, Cline, or Claude) can call `trivy_fs_scan`, `gitleaks_scan`, `checkov_scan` and `semgrep_scan` and reason over the findings. And it's all free and reproducible: a local Python server, open-source scanners, no paid API required."

## Architecture

```mermaid
flowchart LR
    subgraph clients["MCP clients"]
        I["MCP Inspector<br/>(no AI, free)"]
        O["Ollama model<br/>via Cline / ollmcp"]
        C["Claude<br/>(1 free connector)"]
    end
    subgraph server["mcp-security-toolkit (stdio)"]
        T1[trivy_fs_scan]
        T2[gitleaks_scan]
        T3[checkov_scan]
        T4[semgrep_scan]
    end
    subgraph scanners["open-source scanners"]
        TR[(Trivy)]
        GL[(Gitleaks)]
        CK[(Checkov)]
        SG[(Semgrep)]
    end
    I --> server
    O --> server
    C -->|"MCP / JSON-RPC over stdio"| server
    T1 --> TR
    T2 --> GL
    T3 --> CK
    T4 --> SG
    TR -->|JSON report| server
    GL -->|JSON report| server
    CK -->|compact JSON| server
    SG -->|compact JSON| server
```

## Tools

| Tool | Wraps | Returns |
|------|-------|---------|
| `trivy_fs_scan` | `trivy fs --format json` | Dependency/OS CVEs (+ secrets) in a local path |
| `gitleaks_scan` | `gitleaks dir --report-format json` | Hard-coded secrets / credentials in a local path |
| `checkov_scan` | `checkov -d --output json --compact` | IaC misconfigurations (Terraform / K8s / Dockerfile / Helm) |
| `semgrep_scan` | `semgrep scan --json` | SAST findings in source code |

All four wrap **open-source** scanners and return **structured JSON**, so the agent gets machine-readable findings instead of scraping CLI text. The server speaks MCP over **stdio**, so it runs anywhere a client can spawn a subprocess — no ports, no daemon, no cloud.

**Keeping the JSON small.** Checkov and Semgrep can emit *huge* reports. To stay
within an LLM's context window, `checkov_scan` and `semgrep_scan` return a
**compact, filtered summary** rather than raw output: only failures, trimmed to a
few fields, filtered by severity, and capped at `max_findings` (default 50). The
full counts are always reported, and a `truncated` flag tells you when findings
were omitted. `trivy_fs_scan` likewise defaults to `HIGH,CRITICAL` +
`--ignore-unfixed` to cut noise at the source.

> Semgrep's default `config="auto"` fetches rules from the registry on first use
> (needs network once); pass a local rules path or a pinned pack like
> `p/security-audit` for fully offline runs.

## Scanning a GitHub repo (clone first)

The tools take a **local filesystem path, not a GitHub URL**. To scan a remote
repo it must first be cloned to disk; you then hand the tool the local path:

```bash
git clone https://github.com/owner/repo.git
# then ask the agent: "scan ./repo with trivy and gitleaks"
```

**The server itself never clones.** Whether a clone happens automatically depends
entirely on the *client* you connect it to:

| Client | Can it clone for you? |
|--------|----------------------|
| Agent **with a shell/terminal tool** (e.g. Claude Code, Cline with command execution) | ✅ Yes — it runs `git clone` with its *own* tool, then calls `trivy_fs_scan` on the result. Two steps, two different tools (one the agent's, one ours). |
| Agent connected to **only this server** (e.g. the MCP Inspector, or a model wired to this server alone) | ❌ No — clone manually first, then pass the local path. |
| **This server's tools** | ❌ Never — there is no clone tool here; the path must already exist on disk (a missing path returns a clear error). |

> **History caveat:** `gitleaks_scan` uses `gitleaks dir`, which scans the
> **working tree as it is now** — it does *not* walk git history, so a secret that
> was committed and later deleted won't be caught (that would need `gitleaks git`).

## Prerequisites

- [Python](https://www.python.org/) ≥ 3.10
- [uv](https://docs.astral.sh/uv/) — `curl -LsSf https://astral.sh/uv/install.sh | sh`
- [Node.js](https://nodejs.org/) (only for `npx`, used by the MCP Inspector)
- [Trivy](https://aquasecurity.github.io/trivy/) — `brew install trivy`
- [Gitleaks](https://github.com/gitleaks/gitleaks) — `brew install gitleaks`
- [Checkov](https://www.checkov.io/) — `brew install checkov` (or `pip install checkov`)
- [Semgrep](https://semgrep.dev/) — `brew install semgrep` (or `pip install semgrep`)

The scanners are looked up on `PATH` at call time; a clear error is returned if one is missing.

## Quick start — no AI, free (MCP Inspector)

The fastest way to see the tools work, with **no model and no API key**: the
[MCP Inspector](https://github.com/modelcontextprotocol/inspector) is a browser
UI that connects to the server and lets you call tools by hand.

```bash
make up        # create the venv + install the server (uv sync)
make inspect   # launch the Inspector against the server -> http://localhost:6274
```

In the Inspector: open **Tools → List Tools**, pick `trivy_fs_scan`, set
`path` to `.`, and **Run** — you'll get the raw Trivy JSON back.

```bash
make test      # run the smoke tests
make down      # remove the venv + caches
```

## As an agent (free) — a local LLM with Ollama

Drive the tools with a **fully local, free** model via [Ollama](https://ollama.com/).
Use a model that supports tool calling:

```bash
ollama pull qwen2.5:7b      # tool-calling capable, runs on a laptop
```

**Option A — [ollmcp](https://github.com/jonigl/mcp-client-for-ollama)** (a terminal MCP client for Ollama):

```bash
pipx install ollmcp
ollmcp --model qwen2.5:7b --mcp-server "uv run mcp-security-toolkit"
```

**Option B — [Cline](https://github.com/cline/cline)** (VS Code extension): set the
API provider to **Ollama**, then add this server to `cline_mcp_settings.json`:

```json
{
  "mcpServers": {
    "security-toolkit": {
      "command": "uv",
      "args": ["run", "mcp-security-toolkit"],
      "cwd": "/absolute/path/to/mcp-security-toolkit"
    }
  }
}
```

Then just ask: *"Scan `./` with trivy and summarise the critical findings."*

## With Claude (free tier = 1 connector)

Claude's free tier allows a **single custom connector**, which is plenty for this
local (stdio) server.

**Claude Code:**

```bash
claude mcp add security-toolkit -- uv run mcp-security-toolkit
```

**Claude Desktop** — add to `claude_desktop_config.json`:

```json
{
  "mcpServers": {
    "security-toolkit": {
      "command": "uv",
      "args": ["run", "mcp-security-toolkit"],
      "cwd": "/absolute/path/to/mcp-security-toolkit"
    }
  }
}
```

Then ask: *"Use `trivy_fs_scan` on this repo and tell me what to fix first."*

## Repository layout

```
mcp-security-toolkit/
├── pyproject.toml               # uv project: deps (mcp[cli]) + console script
├── .python-version              # pinned interpreter for uv
├── src/
│   └── mcp_security_toolkit/
│       ├── __init__.py
│       └── server.py            # FastMCP server: trivy / gitleaks / checkov / semgrep
├── tests/
│   └── test_server.py           # smoke tests (no scanners required)
├── Makefile                     # up / inspect / test / down / help
├── .pre-commit-config.yaml      # local gates (ruff, gitleaks, hygiene)
├── .markdownlint.yaml
├── .vscode/                     # recommended extensions + format-on-save
├── .github/
│   └── workflows/
│       └── mirror-to-gitlab.yml # GitHub -> GitLab mirror (+ workflow_dispatch)
└── docs/
    └── architecture.md          # why MCP, server design, the three clients
```

## ⚠️ Note

The tools run real scanners against **whatever path you give them** and shell out
to local binaries. Only point them at code/paths **you own or are authorised to
scan**, and only connect the server to clients you trust — an agent that can call
these tools can read any file under the paths it scans.