Exposes the Hermes Pentest Lab's toolchain to MCP clients (Claude Code/Desktop, Cursor, etc.) over stdio. Routes all target-touching tools through enforcement gates (scope, VPN, rate limits, audit) to ensure guardrails are enforced in code rather than just documented.
Hermes exposes 11 tools with clear security intent (scope gating, audit trails, findings management). Tool names follow verb_noun convention (scope_check, port_scan, record_finding). Descriptions are substantive (100-250 chars) and explain WHEN to use each tool. However, input schemas lack formal constraints (no enums for 'kind' parameters, no min/max for numeric fields). Output schemas are undocumented, LLMs cannot predict response structure. Error handling is minimal; tools return dicts but lack recovery guidance. Parameter descriptions exist but omit format/range details (e.g., 'ports' accepts 'nmap port specification' but no examples or constraints). The server enforces security in code (scope_check, VPN gate, audit) but does not expose this via tool annotations (readOnlyHint, destructiveHint). Overall: solid domain modeling, weak LLM-facing schema rigor.
Return the last N entries of the engagement's tamper-evident audit chain.
Build a pentest report from findings.db + HTML template → PDF (via WeasyPrint). Renders findings, heatmaps, framework mappings, and remediation progress.
Summarize an engagement: scope, safety tier, finding count, audit depth.
Explain why destructive tools are not runnable through this server. sqlmap, hydra, metasploit, and responder can modify target state or cause denial of service. Per skills/safety-guards.md they require explicit operator approval and are run by hand, out of band — never autonomously by an agent.
List engagement names under $PENTEST_HOME/engagements.
Passive OSINT for ENGAGEMENT. kind = domain (theHarvester) | email (holehe) | username (sherlock) | crtsh (certificate-transparency subdomains). Passive: no packets to the client's scope, so no VPN gate — but every lookup is still audited.
No output schemas documented. LLMs cannot predict response structure (e.g., does engagement_status return a dict with 'findings' key? Is it always present?). Forces agents to guess field names and risks extraction errors.
Enum constraints missing for 'kind' parameters (web_scan: nuclei|ffuf|gobuster; osint_lookup: domain|email|username|crtsh). Free-form strings invite hallucinated values. LLMs cannot self-correct without explicit enum lists.
No tool annotations (readOnlyHint, destructiveHint, idempotentHint). Server enforces scope/VPN/audit in code, but LLMs cannot see which tools are safe to retry or which modify state. Agents may retry destructive operations.
| Scored | Grade | Overall | Spec posture | Rubric |
|---|---|---|---|---|
| 2026-09-23 | D | 56 | 2026-07-28+ | v2 |
Nmap service/version scan of TARGET for ENGAGEMENT. Enforced: scope gate → VPN gate → rate flags from the engagement's safety tier → audit. PORTS is an nmap port spec (default top range '1-1000').
Insert a finding into the engagement's findings.db. Optionally tag it with a MITRE ATT&CK technique (e.g. "T1190") and an OWASP Top 10 (2021) category (e.g. "A05:2021") — these drive the report's Framework Mapping section. The finding is written review_status='DRAFT'; it stays DRAFT until the verification pass (verify_findings) assigns a verdict. Returns its id.
Check whether TARGET (IP, CIDR, or domain) is authorized for ENGAGEMENT. Wraps the single scope resolver (scripts/scope-check.py). Returns {allowed, reason}. Call this before asking for any active tool — the active tools enforce it anyway, but checking first avoids an audited denial.
Run the verification pass on findings: mark them VERIFIED or REGRESSED based on retest results, and update confidence levels. Returns a summary of verdicts applied.
Web scan of URL. kind = nuclei (CVE/misconfig) | ffuf | gobuster (dir brute). The URL's host is scope-checked; then VPN gate → tier rate flags → audit. WORDLIST defaults to a SecLists common list for ffuf/gobuster.
Parameter descriptions lack format/range details. 'ports' says 'nmap port specification' but no examples (1-1000, 22,80,443, etc.). 'n' in audit_tail has no min/max. LLMs cannot validate inputs before calling.
Error handling is minimal. Tools return dicts with 'error' key but no recovery guidance. E.g., scope_check returns {allowed, reason} but if denied, LLM has no next step. No categorization (retryable vs fatal).