Metadata-Version: 2.4
Name: northrail-redteam-mcp
Version: 0.1.0
Summary: Read-only local MCP server exposing RedTeam AI findings, Actions and guardrail config to a coding agent
Author: Northrail AI
License: Apache-2.0
Requires-Python: >=3.12
Description-Content-Type: text/markdown
Requires-Dist: mcp<2,>=1.28
Requires-Dist: httpx<1,>=0.27.1
Provides-Extra: dev
Requires-Dist: pytest>=8.0; extra == "dev"
Requires-Dist: setuptools>=61.0; extra == "dev"
Requires-Dist: wheel; extra == "dev"
Requires-Dist: PyYAML>=6.0; extra == "dev"

# northrail-redteam-mcp

A local, read-only MCP server that lets your coding agent read a RedTeam AI scan
run's findings, your approved Action briefs and the active guardrail configuration.
It runs on your machine over stdio with your own project-scoped API key and calls
only existing `/api/agent` routes. It resolves, approves, triages, re-tests and
scans nothing, and it enables nothing.

It is distributed on PyPI as `northrail-redteam-mcp` and is not listed in the MCP
Registry or on npm. The server stays local stdio; there is no hosted server.

## Install

Python 3.12 or newer. Either run it without installing, through [uv](https://docs.astral.sh/uv/):

```bash
uvx northrail-redteam-mcp
```

or install it with pip:

```bash
pip install northrail-redteam-mcp
```

Both provide the `northrail-redteam-mcp` console command. With no arguments it starts the
stdio server and waits for an MCP host, so do not run it by hand; configure it in your host
below.

Availability depends on the operator-owned first release (see Release): until the project
has been uploaded to PyPI, these install commands do not resolve.

## Install the coding-agent skill

The package ships a standard Agent Skills document,
[SKILL.md](redteam_mcp/skill/northrail-redteam-findings/SKILL.md), that tells your coding
agent how to use the five read tools, keep the unreviewed AI suggestion versus approved
Action brief labels, and re-check by reference. Print it, or save it into a skill folder
of your choice. The destination folder must be named `northrail-redteam-findings`:

```bash
northrail-redteam-mcp skill print
northrail-redteam-mcp skill install --destination ~/.claude/skills/northrail-redteam-findings
```

(`uvx northrail-redteam-mcp skill print` works the same way.) If the destination cannot be used,
the command says why and exits nonzero; fix the path and run it again. An existing
`SKILL.md` that differs is never overwritten. These commands need no API URL or key and make
no network call.

## Configure

| Variable | Required | Meaning |
|----------|----------|---------|
| `REDTEAM_API_URL` | yes | Your RedTeam API origin, `http(s)://host[:port]`, with no credentials, query or fragment |
| `REDTEAM_API_KEY` | yes | A project-scoped API key (see Scopes) |
| `REDTEAM_TIMEOUT_SECONDS` | no | Positive request timeout, default 30 |

If a required variable is missing or malformed the server logs which variable to fix
on stderr and exits with status 2 without contacting the API. Set it and restart the
host; nothing else is needed. The key is read from the environment only and is never
logged or stored.

## Add it to an MCP host

Most hosts take a command plus environment, for example:

```json
{
  "mcpServers": {
    "redteam": {
      "command": "uvx",
      "args": ["northrail-redteam-mcp"],
      "env": {
        "REDTEAM_API_URL": "https://your-redteam-host.example",
        "REDTEAM_API_KEY": "<your project-scoped key>"
      }
    }
  }
}
```

The same JSON is shown in the Coding agent keys dialog with your API origin and new key
filled in. If you installed with pip instead, use `"command": "northrail-redteam-mcp"` and
drop `args`, with the full path to it if the host does not inherit its `PATH`.

To get the key, a workspace owner opens Settings, then **Coding agent keys**
(`/client/settings/agent-keys`), creates a key with a name and a 30 or 90 day expiry, and copies
the key and the host setup from the dialog. The key is shown once, and the list shows its state
and expiry. The section appears only when the workspace's key list is readable.

## Scopes

The workspace owner mints an expiring key for the project from its API key list.
Give it only the scopes for the tools you want:

| Tool | Scope |
|------|-------|
| `list_findings` | `findings:read` |
| `list_actions`, `get_action`, `get_action_brief` | `brief:read` |
| `get_guardrail_config` | `guardrail:read` |

Minting scoped keys needs `security-scan.api_key_scopes`, and the findings read needs
`security-scan.agent_findings_read`; both are release capabilities that stay off until
the operator enables them. The Action tools (`list_actions`, `get_action`,
`get_action_brief`) additionally need both `main.governance_actions` and
`main.ai_system_identity` on for the project; if either is off the route answers 404,
the same as an unknown or foreign Action. While issuance is disabled the mint is refused
(`RestrictedApiKeyIssuanceDisabledError`), and a read answers 403 or 404. This package
cannot repair that: ask the operator to enable the capability, then retry. Legacy keys
without scopes are refused by the findings read.

## Tools

| Tool | Input | Result |
|------|-------|--------|
| `list_findings` | `run_id`, optional `cursor`, `limit` | One page of the run's allowlisted findings |
| `list_actions` | none | The Actions this key's project may pull |
| `get_action` | `action_id` | One Action's metadata |
| `get_action_brief` | `action_id` | The Action's brief |
| `get_guardrail_config` | none | The active guardrail configuration JSON |

No tool takes a project, key, URL or method. Results are the REST JSON unchanged,
including nulls: a finding may have `explanation: null` with its unavailable reason, a
suppressed category, null confidence or a degraded-triage flag. Those are honest
unknowns, not empty results.

A 2026 finding also carries `owasp_reference` (its code, edition, versioned code such
as `LLM03:2026` and official title) with `owasp_attribution`, the approved source and
licence notice to show beside it. This is a reference to the published list, not an
assessment: it is present whatever the category assessment, and `fix_location_hint`
stays null unless the backend permits it. Other findings read both as `null`.

### Where `run_id` comes from

The scan run id is yours: copy it from the run in the RedTeam app or from the
`redteam-gate` CI output of the scan you want to address. Nothing in this package
looks it up or starts a scan.

### Paging and recovery

`list_findings` returns `{data, meta: {next_cursor}}`. Call again with
`cursor=<next_cursor>` until `next_cursor` is `null`; the final page is complete.
A bad `limit` or cursor, or a cursor from another run, answers 422: drop the cursor
(or fix `limit`) and read again from page 1. Each call is a fresh read, so a later
call sees later triage changes. Errors reach the host as MCP `isError` results that
name the HTTP status and a short hint (401 key, 403 scope, 404 unknown run or
disabled capability, 503 unavailable), never the upstream body or the key.

### Provenance

- Finding `remediation_suggestion` carries `remediation_provenance:
  unreviewed_ai_suggestion`. It is not an approval.
- Action items get `provenance`: `approved_action_brief` for L1 and
  `l0_guidance_no_approval_required` for L0 guidance. L0 is never an approved brief.
- `get_action_brief` is the existing brief pull. The backend records the first
  delivery once; repeated pulls keep that behavior and this server stores nothing.
- Re-checking is by reference only (`recheck_reference`: finding id and signature),
  through `redteam-gate` or a server-side retest. Attack prompts and target replies are
  never returned.

## Release (operator)

Releases use PyPI trusted publishing from GitHub Actions, with no stored token. The first
release is done by the package owner, not by this repository's automation: they create the
PyPI project `northrail-redteam-mcp` and register a trusted publisher with these fixed
identifiers, then push the first matching tag.

| Setting | Value |
|---------|-------|
| Workflow file | `.github/workflows/release-redteam-mcp.yaml` |
| GitHub environment | `pypi` |
| Release tag | `redteam-mcp-v<version>`, for example `redteam-mcp-v0.1.0` |

The workflow builds and checks the wheel and sdist on every run but uploads only for a
pushed `redteam-mcp-v*` tag, and only when the tag's version equals the `version` in
`pyproject.toml`. Manual runs, branches and pull requests never upload.

## Develop and test

```bash
pip install -e "./mcp/redteam-mcp[dev]"
cd mcp/redteam-mcp && pytest tests/test_client.py tests/test_tools.py tests/test_server.py tests/test_distribution.py
```

Tests use mocked HTTP only; no API, key, scan or model call is involved. The
path-filtered workflow `.github/workflows/redteam-mcp-tests.yaml` runs them on pull
requests that touch this package.
