ci-triage-mcp
by keresifon
README.md
# ci-triage-mcp
An [MCP](https://modelcontextprotocol.io) server that exposes
[ci-triage-agent](https://github.com/kere-sifon/Ci-mvp)'s Trivy/Semgrep +
AWS Bedrock security-triage pipeline as on-demand chat tools — ask "what's
the triage status on repo X?" from Claude Desktop, no GitHub Actions run
required.
## Architecture
```
GitHub PR merged
│
▼
GitHub Action (Ci-mvp)
├─ Trivy scan
├─ Semgrep scan
├─ Bedrock/LangGraph triage → posts PR comment
└─ Upload scan-results to S3 (OIDC-assumed role, scoped s3:PutObject)
│
▼
s3://{bucket}/{repo_slug}/{sha}/trivy.json
s3://{bucket}/{repo_slug}/{sha}/semgrep.json
│
▼ (on-demand, read-only AWS identity, scoped s3:GetObject)
ci-triage-mcp (this project)
├─ list_scanned_repos — discover what's available
├─ list_findings — parse raw Trivy/Semgrep JSON
├─ triage_findings — real Bedrock/LangGraph call (imports
│ Ci-mvp's run_triage() directly, no subprocess)
└─ get_scan_summary — combined human-readable report
│
▼
Claude Desktop (stdio transport)
```
The MCP server never runs scans itself — it's a read-and-interpret layer
downstream of the GitHub Action, which remains the only place scanning
happens.
## Tools
| Tool | Purpose | Notes |
|---|---|---|
| `list_scanned_repos` | List repos with results in S3, each with latest SHA + timestamp | No args. Use this first if you don't already know a repo/sha. |
| `list_findings` | Raw severity counts, finding IDs, affected files | `sha="latest"` supported |
| `triage_findings` | Full Bedrock/LangGraph classification (true positive / needs review / false positive, with rationale) | `sha="latest"` supported |
| `get_scan_summary` | Combined plain-text report (raw counts + triage verdict) | `sha="latest"` supported |
## Setup
### 1. Install dependencies
```bash
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
```
### 2. Configure environment
Copy `.env.example` to `.env` and fill in:
```bash
AWS_REGION=us-east-1
AWS_PROFILE=default # local dev only; omit if using ambient creds
SCAN_OUTPUT_DIR=./scan-output # local cache directory
# Optional S3 automation — see "S3 hand-off" below
SCAN_RESULTS_S3_BUCKET=
SCAN_RESULTS_S3_PREFIX=
```
### 3. Register with Claude Desktop
Open Claude Desktop → **Settings → Developer → Edit Config**, and add:
```json
{
"mcpServers": {
"ci-triage": {
"command": "/absolute/path/to/.venv/bin/python",
"args": ["/absolute/path/to/server.py"],
"env": {
"AWS_PROFILE": "default",
"SCAN_OUTPUT_DIR": "/absolute/path/to/scan-output",
"SCAN_RESULTS_S3_BUCKET": "your-bucket-name",
"SCAN_RESULTS_S3_PREFIX": ""
}
}
}
}
```
Fully quit and restart Claude Desktop (config only loads on startup), then
enable the `ci-triage` connector via the **+** button in a conversation.
### 4. Point ci-triage-agent's Action at your bucket
In the caller workflow (e.g. `triage.yml`), add two inputs to the existing
`Ci-mvp` step:
```yaml
- uses: kere-sifon/Ci-mvp@v1.1
with:
aws-role-to-assume: ${{ secrets.CI_TRIAGE_AWS_ROLE_ARN }}
s3-bucket: your-bucket-name
s3-prefix: ""
```
**The `s3-prefix` value here must exactly match `SCAN_RESULTS_S3_PREFIX`
above** — a mismatch here is the single most likely thing to break the
hand-off (ask me how I know).
## IAM
Two separate identities, least-privilege on each side:
- **The Action's OIDC-assumed role** needs `s3:PutObject` scoped to
`arn:aws:s3:::{bucket}/*`, in addition to whatever `bedrock:InvokeModel`
scope it already has.
- **The MCP server's local AWS identity** (a named profile in local dev,
or its own role if deployed) needs only `s3:GetObject` on the same
bucket — it never writes to S3.
## Local testing without S3
If `SCAN_RESULTS_S3_BUCKET` is unset, the server falls back to
`SCAN_OUTPUT_DIR` only. Drop Trivy/Semgrep JSON manually at:
```
{SCAN_OUTPUT_DIR}/{repo_slug}/{sha}/trivy.json
{SCAN_OUTPUT_DIR}/{repo_slug}/{sha}/semgrep.json
```
where `repo_slug` replaces `/` with `__` (e.g. `myorg/my-service` →
`myorg__my-service`).
## Known limitations / roadmap
- **stdio transport only.** This runs as a local subprocess Claude Desktop
spawns directly — no remote/HTTP access, no OAuth. Fine for personal use;
would need the MCP 2026-07-28 spec's OAuth 2.1 resource-server pattern
for any multi-user or remote deployment.
- **`ci-triage-agent` isn't a packaged dependency.** Its `pyproject.toml`
has no `[build-system]` table, so this project imports it via a
`sys.path.insert()` at a hardcoded local path (`triage/bedrock_client.py`)
rather than `pip install -e`. Fine for a single-machine setup; would need
proper packaging to install this cleanly elsewhere.
- **`resolve_sha("latest")` depends on S3 object timestamps**, not git
history — if uploads ever land out of commit order, "latest" reflects
upload recency, not commit recency.This server cannot be deployed
Maintenance
ActivityStale
ResponsivenessNo issues