A remote MCP server for writing penetration test reports in SysReptor. It lets Claude (or any MCP client) create projects, write and score findings, fill report sections, attach screenshots, quality-check the report and render the PDF, acting as the signed-in SysReptor user.
- Built-in OAuth 2.1 (PKCE, dynamic client registration, rotating refresh tokens with reuse detection, revocation). Users sign in with their own SysReptor API token, so every change is attributed to the right person.
- Design-aware: reads each project's field definitions and validates data before saving. SysReptor itself silently drops unknown fields and accepts malformed CVSS vectors; SRepMCP rejects them with a clear message.
- Report QA:
check_reportcombines SysReptor's checks with deeper linting: invalid CVSS, missing required fields, references to images that were never uploaded, embedded base64 images, duplicate titles. - Safe by default: deletions are approved by the human in the client UI (not by the model),
SREPMCP_READ_ONLYhides all write tools, and partial updates never wipe other fields.
MCP client ──(1) register + /authorize (PKCE)──▶ SRepMCP ──(2) redirect──▶ /login page
│ user pastes SysReptor API token
▼
(3) verified live: GET /api/v1/pentestusers/self/
stored Fernet-encrypted, single-use code issued
MCP client ◀──(4) code → /token: srm_at_… access + srm_rt_… refresh token (hashed at rest)
MCP client ──(5) Bearer srm_at_… ──▶ tools ──▶ SysReptor API (as that user)
Identity and tokens
- SysReptor API tokens never leave the server and are encrypted at rest. MCP tokens are opaque, stored only as SHA-256 hashes, and never forwarded to SysReptor (no token passthrough).
- Access tokens last 1 hour. Refresh tokens last 30 days and rotate on every use. If an already-rotated refresh token is presented again (a sign of theft), every token descending from that login is revoked.
- Tokens are bound to the OAuth client that obtained them and to this server (RFC 8707
resourceis validated). SREPMCP_ALLOWED_USERSis checked on every request, so removing a username cuts off that user's existing sessions.- If SysReptor rejects a stored token (expired, revoked, user disabled), that user's MCP sessions are revoked.
sign_outdeletes the stored token, all of the user's sessions and their pending downloads.
OAuth hardening
- Redirect URIs are restricted to known MCP clients by default (Claude.ai/Desktop, Claude Code and other loopback clients, Cursor, VS Code). This stops attackers from registering their own client and phishing a teammate into authorizing it.
- The login page shows the client's name and redirect host, uses a CSRF token, is single-use, cannot be framed, and sends no referrer. PKCE (S256) is mandatory, and authorization codes are single-use and expire after 5 minutes.
HTTP hardening
- Per-IP rate limits on
/login,/register,/authorize,/token,/revokeand/downloads, with bounded memory (IPv6 clients are grouped by /64). X-Forwarded-Foris only trusted when the connection comes fromSREPMCP_TRUSTED_PROXIES.- Request bodies are capped based on
SREPMCP_MAX_UPLOAD_MB. Access logs are off by default, because paths contain capability URLs.
Data
- Generated PDFs are encrypted on disk, served once through an unguessable link that expires after 10 minutes, and deleted after download or expiry (a background job also cleans up leftovers).
- The SQLite database contains no plaintext secrets; the data directory is owner-only (
0700). - Uploads are sanitized: filenames are cleaned, image types are checked from their content (SVG is rejected), and sizes are limited.
Input handling
- Every id placed in a SysReptor URL must be a UUID or slug. Percent-encoding alone isn't enough: an id of
..would be collapsed by URL normalization, turning e.g. a finding delete into a project delete. - Data written to findings and sections is validated against the project's design before saving. Scans of report markdown use linear-time patterns.
Destructive and data-exposing actions
delete_project,delete_finding,delete_note,sign_outandcreate_template_from_finding(which copies a client's finding into the template library every SysReptor user can read) ask the user for approval through MCP elicitation when the client supports it. The model'sconfirmflag is then ignored, so text injected into a finding cannot trick the model into deleting data. The approval is bound to the exact item being deleted. Clients without elicitation fall back toconfirm=true. Those clients should be set to require manual approval for tools annotateddestructiveHint.
- API tokens have the user's full SysReptor rights, because SysReptor tokens can't be scoped. Create a dedicated token with an expiry date for the MCP, and avoid using a superuser account for day-to-day reporting.
- Report content goes to the model provider. Everything the model reads (findings, notes, PDF passwords
passed to
render_report_pdf) is part of the LLM conversation. Follow your client-confidentiality rules. - Prompt injection in report content can still influence non-destructive writes (e.g. editing a finding),
just like any agent with write access. Keep
SREPMCP_READ_ONLY=truefor review-only use. - Rate limits are kept in memory per process. Run a single instance, or add limits at the proxy.
| Area | Tools |
|---|---|
| Account | whoami, sign_out |
| Designs | list_project_types, get_report_fields |
| Projects | list_projects, get_project, create_project, update_project, set_project_finished, delete_project |
| Sections | get_section, update_section |
| Findings | list_findings, get_finding, create_finding, update_finding, delete_finding, reorder_findings |
| Templates | search_finding_templates, get_finding_template, create_template_from_finding |
| Reference | search_cwes |
| Notes | list_notes, get_note, create_note, update_note, delete_note |
| Uploads | upload_image, upload_file, list_uploads |
| Delivery | check_report, render_report_pdf |
Prompts: write_finding (raw notes → complete finding), write_executive_summary, qa_report.
Tools carry MCP annotations (readOnlyHint, destructiveHint, ...) so clients can auto-approve reads and ask
before writes.
- DNS: point
mcp.shellvoide.comat the host. - Configure:
Set
cp .env.example .env docker compose run --rm --no-deps srepmcp genkey # paste into SREPMCP_ENCRYPTION_KEYSYSREPTOR_URL,MCP_BASE_URL=https://mcp.shellvoide.comandSREPMCP_ALLOWED_USERS. Don't setSYSREPTOR_API_TOKENon the server. - Start:
docker compose up -d --build, then check withcurl http://127.0.0.1:8000/healthz. - Reverse proxy (TLS is required): use deploy/Caddyfile or
deploy/nginx.conf. The proxy must not buffer responses (SSE) and should allow
uploads of about 40 MB. Turn off proxy access logs for
/downloads/and/login, or keep those logs private. - Verify:
curl https://mcp.shellvoide.com/.well-known/oauth-authorization-servershould return JSON.
Data (OAuth clients, encrypted tokens, temporary PDFs) lives in the srepmcp-data volume. Back up
SREPMCP_ENCRYPTION_KEY: without it, stored tokens can't be decrypted and every user has to sign in again.
To rotate the key, set SREPMCP_ENCRYPTION_KEY=NEW,OLD.
The container port is bound to
127.0.0.1only, so it's reachable only through the proxy. The Docker image pins every dependency to the tested versions in requirements.lock.
The MCP endpoint is https://mcp.shellvoide.com/mcp. Each client opens the SysReptor login page the first
time. Create an API token in SysReptor under Profile → API Tokens and paste it there.
- Claude.ai / Claude Desktop: Settings → Connectors → Add custom connector → paste the URL.
- Claude Code:
Then run
claude mcp add --transport http sysreptor https://mcp.shellvoide.com/mcp
/mcpinside Claude Code to authenticate. - Cursor / VS Code: add a remote (streamable HTTP) MCP server with the URL. OAuth is discovered automatically.
- Other clients: add their OAuth callback URL to
SREPMCP_ALLOWED_REDIRECT_URIS. If you don't, registration is rejected withinvalid_redirect_uri.
See .env.example for every option. The most important:
| Variable | Default | Purpose |
|---|---|---|
SYSREPTOR_URL |
(required) | SysReptor base URL |
MCP_BASE_URL |
http://localhost:8000 |
Public URL of this server (OAuth issuer). Must be https in production |
SREPMCP_ENCRYPTION_KEY |
(required) | Fernet key(s) for tokens and PDFs at rest |
SREPMCP_ALLOWED_USERS |
any active user | SysReptor usernames allowed to use the server (checked on every request) |
SREPMCP_ALLOWED_REDIRECT_URIS |
known MCP clients | OAuth redirect allowlist; * = any https client |
SREPMCP_TRUSTED_PROXIES |
loopback + private ranges | Networks whose X-Forwarded-For is trusted |
SREPMCP_READ_ONLY |
false |
Hide all write tools |
SREPMCP_DOWNLOAD_TTL / SREPMCP_DOWNLOAD_SINGLE_USE |
600 / true |
PDF link lifetime and reuse |
SREPMCP_ACCESS_TOKEN_TTL / SREPMCP_REFRESH_TOKEN_TTL |
3600 / 2592000 |
Token lifetimes (seconds) |
python -m venv .venv && .venv/Scripts/pip install -e ".[dev]" # Linux/macOS: .venv/bin/pip
python -m srepmcp check # verifies SYSREPTOR_URL and SYSREPTOR_API_TOKEN from .env
pytest # offline tests: tools, OAuth flow, security regressions (SysReptor mocked)Single-user stdio mode (no OAuth, uses SYSREPTOR_API_TOKEN) for quick local testing:
claude mcp add sysreptor-local --env SREPMCP_TRANSPORT=stdio -- .venv/Scripts/python -m srepmcp- Community edition: SysReptor's comments and spellcheck APIs require a Professional license, so they aren't exposed.
- CVSS 4.0 vectors are syntax-checked, but only SysReptor computes their scores. CVSS 3.0/3.1 are scored locally.
- SysReptor documents its API as unstable. SRepMCP was built and tested against the API of
sysreptor.shellvoide.com(September 2026). Re-run the tests after SysReptor upgrades. - New projects copy the design's default section values, which can include sample data from another client.
create_projectlists those fields and can clear them (clear_prefilled_sections=true).