# Pentest MCP [![smithery badge](https://smithery.ai/badge/@DMontgomery40/pentest-mcp)](https://smithery.ai/server/@DMontgomery40/pentest-mcp) [![Verified on MseeP](https://mseep.ai/badge.svg)](https://mseep.ai/app/fa558a10-f45c-4668-9bb6-15630dd51f27) Pentest MCP is a TypeScript MCP server for authorized security assessments. It gives a human researcher or a tool-using assessment agent one scoped interface for discovery, validation, capture, credential checks, local audit, evidence retrieval, and report preparation. Each active tool call is tied to an engagement and a durable record. The server checks the declared targets and time window; the researcher verifies findings before publication. ## October 2026 workflow 1. Create an engagement from signed authorization and rules of engagement, then save the assessment plan. The operator chooses a stable `engagementId`. 2. A researcher or fresh Codex process in the managed assessment container connects to the Pentest MCP server. It sees the complete catalog, while the plan's exact `allowedToolNames` list permits only selected calls. Active tools also pass the engagement target and time gate. 3. Review `recordId` values and correlated scope decisions with `listEngagementRecords` and `getEngagementRecord`. Records persist across restarts. Import external research with `recordResearchActivity`; the import does not replace MCP tool use. 4. Draft a report from the complete evidence ledger, inspect candidate findings against source records, and publish only analyst-verified findings. Retrieve Markdown or JSON later with `getClientReport`. The declared-target check is an operational guardrail, not a complete network boundary. DNS changes, redirects, Nmap scripts, Nuclei templates, and other third-party tool behavior can create secondary traffic. Review tool configuration and keep network controls aligned with the engagement's rules. ### Agentic assessment uses the MCP tools The assessment stage is a **tool-using agent session**. `runAgentAssessment` supports local Codex or Claude in a dedicated account profile, Codex in a pinned container, a loopback OpenAI-compatible server such as llama.cpp or Ollama, and OpenRouter. CLI workers receive a locked, single-engagement Pentest MCP server and discover the full catalog. Guarded API models receive only the exact plan allowlist intersected with the worker catalog. Every requested action still reaches the same worker-side plan, target, parent-status, and time checks before a handler starts. Optional `hostileModelProtection` adds duplicate-action, token-accounting, and finish-reason circuit breakers and defaults to false. An interrupted run with a durable start receipt and no terminal receipt blocks replay; `reconcileAgentAssessment` requires an operator to confirm that the prior process stopped, records reviewer and rationale, and revokes the prior worker scope when it is still available. The returned `toolCounts`, `importedRecordIds`, and `importMapRecordId` identify recorded actions and imported evidence. A run with no successful assessment-tool call fails. `draftAssessmentReport` is a later, context-quarantined report-writing step; a draft alone does not show that Pentest MCP helped the assessment. OpenAI, Anthropic, OpenRouter, OpenAI-compatible, local Codex, local Claude, container Codex, and configured SSH profiles can draft from a bounded evidence packet without scanner tools. `local-agent` accepts self-hosted CLI profiles and loopback OpenAI-compatible profiles while rejecting hosted providers before context is sent. The [guarded model guide](docs/guarded-models.md) gives llama.cpp and OpenRouter profiles, enforced stop conditions, audit fields, and hostile-model deployment boundaries. The [container assessment guide](docs/container-assessment.md) gives CLI profile fields, real acceptance command, and isolation limits. The [agentic workflow](docs/agentic-assessment-workflow.md) covers scope, evidence review, Daybreak/Glasswing access, and Faxbot planning. Release acceptance uses `scripts/run-real-acceptance.mjs`. It has no default target or authorization and refuses to pass unless installed Nmap and ProjectDiscovery HTTPX create successful evidence records. With `--profile`, the managed agent must invoke both through Pentest MCP. With `--report-profile`, a separate report context must produce the saved draft. Test doubles remain in automated tests only and are never shipped as acceptance receipts. ## Quick start Requires Node.js 22.19.0 or newer and the host binaries for the tools you intend to use. The Docker image includes the listed tools; native installations must provide them in `PATH`. ```bash npm install -g pentest-mcp pentest-mcp ``` Stdio is the default for local MCP clients. `pentest-mcp inspector` starts the bundled MCP Inspector against this server over stdio; Inspector flags can be forwarded, for example `pentest-mcp inspector --help`. Set `PENTEST_DATA_DIR` to choose the local evidence directory. Otherwise the server uses `$XDG_DATA_HOME/pentest-mcp` when that variable is set, or `~/.pentest-mcp`. Evidence and report files are stored with private directory and file permissions. Treat raw outputs, hashes, captured traffic, and reports as client-sensitive data; control access, retention, and backups accordingly. ### Register an engagement Call `createEngagement` before scanning. Use dates, targets, and references from the actual signed authorization; the values below are illustrative. ```json { "tool": "createEngagement", "arguments": { "engagementId": "client-2026-10", "authorizationReference": "Signed SoW section 4, approved 2026-10-24", "notBefore": "2026-10-25T09:00:00-06:00", "expiresAt": "2026-10-26T18:00:00-06:00", "allowedTargets": ["app.example.com", "192.0.2.0/28"], "excludedTargets": ["192.0.2.7"], "testingWindows": [ { "start": "2026-10-25T09:00:00-06:00", "end": "2026-10-25T17:00:00-06:00" } ] } } ``` Allowed targets accept exact hosts, CIDRs, wildcard subdomains, and HTTP(S) URL prefixes. Exclusions take precedence. All timestamps need an explicit timezone. `getEngagement` and `listEngagements` inspect stored registrations. `closeEngagement` revokes later execution, aborts managed assessments running in the same server process, and causes a separate managed runner to stop when it observes the parent registry closure. ### Run and review Use the same `engagementId` for active tools. The default non-root container supports a TCP connect scan; SYN and OS detection need additional privileges that are not granted by default. ```json { "tool": "nmapScan", "arguments": { "engagementId": "client-2026-10", "target": "app.example.com", "scanTechnique": "Connect", "serviceVersionDetection": true } } ``` A successful tool response includes a `recordId` for its stored evidence. Retrieve only the records you need: ```json { "tool": "listEngagementRecords", "arguments": { "engagementId": "client-2026-10", "limit": 50 } } ``` `listEngagementRecords` returns summaries and accepts `limit` and `offset` for paging through older records. `getEngagementRecord` takes both `engagementId` and `recordId` and returns stored evidence when permitted. Credential tools return counts and output digests. New unauthenticated Gobuster/Nikto records retain private scanner output and findings for owner-scoped reads; older scanner records remain redacted. Nmap XML and bounded ffuf/Nuclei machine output are retained alongside summaries. A record's `outcome` is `succeeded`, `failed`, or `cancelled`, with a start time and duration when tracked; subprocess failures create an audit record rather than a successful scan result. A preflight scope refusal starts no command. For authenticated HTTP, engagement and evidence access is scoped to the verified token owner; stdio data is local to the server installation. ### Give the agent a client-specific plan `saveAssessmentPlan` stores objectives, human-readable allowed and prohibited methods, exact `allowedToolNames`, data handling, stop conditions, report audience, work ownership, and model data policy as a versioned evidence record. `getAssessmentPlan` reads the latest version. The managed worker receives the full catalog but the server rejects any call omitted from `allowedToolNames` before its handler runs, leaving a failed audit receipt. When a plan exists, its allowlist also gates direct manual MCP calls to the 14 assessment methods; denials are audited before the handler or command runs. Direct calls on legacy engagements without a plan keep their previous behavior and still pass engagement scope gates. This plan cannot expand `createEngagement` targets or time. An external-client plan needs a provider client-work approval reference before model drafting, and `no-model` permits only the deterministic draft. See the [plan schema and example](docs/agentic-assessment-workflow.md#assessment-plan-example). After a plan with `modelDataPolicy: "local-agent"` or `"approved-provider"` and nonempty `allowedToolNames` is recorded, launch the tool-using run: ```json { "tool": "runAgentAssessment", "arguments": { "engagementId": "client-2026-10", "profileId": "daybreak-cli-assessment", "timeoutSeconds": 900 } } ``` The response includes a run ID, status, model, `toolCounts`, imported evidence IDs, `importMapRecordId`, and an ID for unreviewed model output. If the model, MCP child, or an assessment tool fails, inspect the retained failure and any imported records before retrying. If only a start receipt exists after a process interruption, verify that the old process stopped and call `reconcileAgentAssessment` with that run ID, the reviewer identity, and the reason for the decision. The server will not silently replay the run. A process that finishes without a successful assessment-tool call is rejected; its bounded output is retained privately with a failed outcome and linked hash for diagnosis, never as verified finding evidence. A completed agent process does not make its observations confirmed findings. The worker blocks engagement creation, closure, plan changes, recursive assessment launches, reconciliation, and report publication. ### Ask for useful wordlist clues Call `generateWordlist` with `engagementId` and omit `baseWords` when the MCP client supports form elicitation. The server asks the authorized operator for known names and aliases, organization or product terms, dates, location or address terms, and prefix or suffix patterns. They may skip unknown categories or decline. The prompt excludes current passwords and patient data. If the client lacks form elicitation, the tool returns the questions so the assistant can ask and retry with explicit `baseWords`, `dates`, `locationWords`, and `customPatterns`. The private wordlist record retains input counts and a SHA-256 digest rather than the supplied words or a sample of the list. Wordlist creation does not authorize password attempts; `hydraBruteforce`, `runJohnTheRipper`, and `runHashcat` must also fit the engagement methods. ### Produce an evidence-backed report `createClientReport` requires a registered engagement, client, title, assessment type, summary, and explicit `recordIds`. Use `[]` only when no tool evidence should be attached. Select the evidence records for each report deliberately; the server never silently chooses a limited subset. Audit, scope-gate, and report-workflow records cannot be attached as vulnerability evidence. Supply analyst-confirmed findings with severity, impact, remediation, verifier, timestamp, and evidence references. Scanner output remains an observation until a researcher verifies it. A report with an empty `findings` array explicitly says no analyst-verified vulnerability findings were included. ```json { "tool": "createClientReport", "arguments": { "engagementId": "client-2026-10", "client": "Example Client", "title": "External Assessment", "assessmentType": "External network", "summary": "Assessment activity and verified outcomes are summarized below.", "recordIds": [], "findings": [], "recommendations": ["Review the evidence index and validate remediation with a retest."], "methodology": "Authorized, scoped enumeration and manual verification.", "limitations": ["The example has no attached scan records."] } } ``` Replace `recordIds` with IDs from this engagement and add verified findings only after manual review. Each finding can include `evidenceRecordIds`, `externalEvidence`, affected assets, reproduction steps, references, CWE, CVSS vector, and retest status. Optional `scopeOfWork` text adds report context; it does not change executable scope. The report includes scope details from the registered engagement, verified severity counts, a completeness checklist, an evidence index with raw-output SHA-256 hashes, and limitations. Raw scanner output is kept in private evidence records rather than copied into the client-facing Markdown. `listClientReports` takes `engagementId`. `getClientReport` takes `engagementId` and `reportId`, with `format` set to `markdown` or `json`. The Markdown report is also available as the `mcp://pentest/clientReport/{reportId}` resource. For an agent-assisted report, call `draftAssessmentReport` after assessment records exist. Omit `profileId` for a deterministic evidence inventory or select a configured **draft** profile for model-written observations. `local-agent` permits CLI profiles and loopback `openai-compatible` profiles; `approved-provider` permits a configured API or CLI profile, including OpenRouter. Both receive bounded scope, plan and evidence summaries and may suggest unverified candidates. Under `metadata-only`, the model receives structural counts, tool names, evidence IDs and digests without client or plan prose, target names, authorization reference, evidence summaries or observations; candidate vulnerabilities are rejected. `publishAgentReport` requires a reviewer and explicit verified findings with evidence IDs and never promotes candidates automatically. ## Tool inventory | Work | Tools | | --- | --- | | Engagement lifecycle | `createEngagement`, `getEngagement`, `listEngagements`, `closeEngagement` | | Discovery and validation | `nmapScan`, `gobuster`, `nikto`, `subfinderEnum`, `httpxProbe`, `ffufScan`, `nucleiScan` | | Capture and credential checks | `trafficCapture`, `hydraBruteforce`, `runJohnTheRipper`, `runHashcat`, `generateWordlist` | | Local audit and extraction | `privEscAudit`, `extractionSweep` | | Scope, agent, and report workflow | `saveAssessmentPlan`, `getAssessmentPlan`, `listAgentProfiles`, `runAgentAssessment`, `reconcileAgentAssessment`, `recordResearchActivity`, `draftAssessmentReport`, `listAgentReportDrafts`, `getAgentReportDraft`, `publishAgentReport` | | Evidence and direct reports | `listEngagementRecords`, `getEngagementRecord`, `createClientReport`, `listClientReports`, `getClientReport` | | Runtime and scan control | `getRuntimeStatus`, `listActiveScans`, `cancelScan` | Gobuster and Nikto accept Basic Auth and cookie values only as literal command arguments in the installed CLI versions, so the MCP tools reject those fields, URL userinfo, and query strings. Their immediate responses and report drafts include counts but not scanner text; new successful records retain private raw output and finding details for the engagement owner. Failed runs retain output digests. Use an operator-reviewed execution method outside these tools when authenticated scanning is required. Historical Gobuster/Nikto records are redacted on MCP reads and report drafting; previously stored files should be handled under the engagement's retention policy. `trafficCapture` needs network capabilities and access to the interface being captured. In Docker, use the explicit Linux capture profile below. `privEscAudit` is available only over local stdio; inside a container it audits that container, so run the server natively when the authorized target is the host itself. Hashcat GPU acceleration also depends on host hardware and device passthrough, which the default container does not configure. `runHashcat` can use Hashcat 8's bundled PCFG attack and custom hash recipes when version 8 is installed on the execution host; v8-only requests are rejected on older runtimes. It also accepts OpenCL/backend device IDs; FPGA bcrypt requires Hashcat 8, Linux, a compatible bridge, and a supported board. The Dockerfile builds the signed 8.0.0 source release and includes CPU OpenCL, while the default container does not pass through GPU or FPGA hardware. See [Hashcat usage](HASHCAT_USAGE.md) for inputs, version checks, and evidence handling. `getRuntimeStatus` reports the active transport, remote execution gate, command limits, Nuclei template readiness, and expected binary availability. `listActiveScans` lists Nmap scans for one engagement; `cancelScan` needs that engagement ID and a returned scan ID. The server also warns at startup when expected host commands are missing. A missing binary or failed command is a tool error, not evidence that a target is clean. Keep tool output and test limitations with the engagement record. Nuclei requires an absolute `MCP_NUCLEI_TEMPLATES_DIR` containing operator-reviewed, signed HTTP templates. The server does not accept caller-selected template paths. It selects HTTP templates, excludes Interactsh templates, disables redirects, headless execution, automatic probing, stdin targets, and update checks. Its target also needs host-wide authorization, because templates may contact other ports on that host. Template review and a network egress policy remain necessary: an HTTP template can specify another destination. The bundled template library is available for review but is not selected automatically. ## Transport and authentication - `stdio` is the default local transport. - `http` uses Streamable HTTP at `/mcp` with a `/health` endpoint. Direct local use binds to `127.0.0.1` by default. - Legacy `sse` is disabled because its shared session cannot isolate clients. For direct local HTTP use: ```bash MCP_TRANSPORT=http MCP_SERVER_HOST=127.0.0.1 MCP_SERVER_PORT=8000 pentest-mcp ``` A non-loopback HTTP listener requires `MCP_AUTH_ENABLED=true`, an exact `MCP_ALLOWED_ORIGINS` list, `MCP_OIDC_ISSUER`, `MCP_AUTH_AUDIENCE`, and an HTTPS `MCP_PUBLIC_BASE_URL`. Issuer and audience are required for authenticated HTTP even when a reverse proxy publishes a loopback-bound listener. Bearer tokens must be verified through an OIDC JWKS URL or introspection endpoint. A request that supplies an `Origin` header is rejected unless it matches the allowed list. Put remote access behind TLS and an explicit network policy. Authenticated HTTP scanner execution has a separate default-off gate: `MCP_REMOTE_EXECUTION_ENABLED=true`. Enable it only for a dedicated, trusted deployment with the engagement's authorization and access policy in place. When false, authenticated clients cannot launch assessment commands. Bearer tokens need the `read` scope for discovery and evidence retrieval, `write` for engagement and report changes, and `execute` for scanners and command cancellation. Each tool checks its own required scope; batched calls need every scope they use. The scope names can be mapped to provider-specific tokens with `MCP_AUTH_READ_SCOPE`, `MCP_AUTH_WRITE_SCOPE`, and `MCP_AUTH_EXECUTE_SCOPE`. The old `MCP_AUTH_SCOPES` setting is ignored. Remote `gobuster`, `ffufScan`, and Hydra list inputs may read only wordlists generated for that token owner or files under an operator-configured absolute `MCP_SHARED_WORDLIST_DIR`. Mount a shared directory read-only in Docker. Hash cracking, host packet capture, and host privilege audit remain local stdio tools. ```bash MCP_TRANSPORT=http MCP_SERVER_HOST=127.0.0.1 MCP_SERVER_PORT=8000 MCP_AUTH_ENABLED=true MCP_AUTH_MODE=bearer MCP_ALLOWED_ORIGINS=https://research.example.com MCP_PUBLIC_BASE_URL=https://research.example.com MCP_OIDC_ISSUER=https://issuer.example.com MCP_OIDC_JWKS_URL=https://issuer.example.com/.well-known/jwks.json MCP_AUTH_AUDIENCE=pentest-mcp MCP_AUTH_READ_SCOPE=read MCP_AUTH_WRITE_SCOPE=write MCP_AUTH_EXECUTE_SCOPE=execute MCP_REMOTE_EXECUTION_ENABLED=true ``` `MCP_OIDC_INTROSPECTION_URL` can be used instead of JWKS, with `MCP_OAUTH_CLIENT_ID` and `MCP_OAUTH_CLIENT_SECRET` when the provider requires them. Legacy `MCP_OAUTH_*` aliases remain accepted but should be migrated. The Docker HTTP profile always enables authentication, including when it publishes only to loopback. ## Operational limits - `MCP_COMMAND_TIMEOUT_MS` and `MCP_COMMAND_OUTPUT_LIMIT_BYTES` bound command execution and captured output. A timed-out, nonzero-exit, or truncated command must be reviewed as incomplete. Tracked subprocess failures are recorded with a failed or cancelled `outcome` and appear as such in the report evidence index. - MCP request cancellation and closing an engagement abort active tracked commands. A managed assessment also checks the authoritative parent engagement before every MCP tool dispatch and polls it while Codex runs; closure cancels the container run and closes its worker scope. Scanner subprocesses use the captured scope deadline, including for Nmap, and recheck engagement status immediately before launch. - `rawOptions` and scanner features that can override declared targets are restricted, but externally maintained templates and scripts still need operator review. - Reports are analyst-authored; the server does not infer vulnerability severity from an open port or a scanner template match. - There is no automatic ticket sync, cloud upload, or proof that the declared authorization reference is valid. ## Docker The Compose image below runs the Pentest MCP server. The managed Codex assessment uses a **different** pinned image and Docker context; follow the [container assessment guide](docs/container-assessment.md) for that worker. Its Codex container has bridge networking for model access, while the scanner MCP child runs on the host. Neither side has a destination egress allowlist here, so apply host and VM network policy before client testing. Docker Compose defaults to a local stdio service with no published port. It runs as an unprivileged user, drops Linux capabilities, and persists scan records, reports, and the scan journal in the host data directory. Prepare that private directory before starting the container: ```bash cp .env.example .env mkdir -p .pentest-data chmod 700 .pentest-data docker compose run --build --rm -T pentest-mcp ``` The host directory is `PENTEST_DATA_HOST_DIR` (default `./.pentest-data`), mounted at the container's `PENTEST_DATA_DIR=/var/lib/pentest-mcp`. On Linux, make the host directory writable by container UID 1000 before the first run (for example, `sudo chown 1000:1000 .pentest-data`). Compose will refuse to auto-create a missing bind path. Protect and back up this directory as client evidence; it is excluded from Git and the Docker build context. ### HTTP access The HTTP profile is opt-in and publishes to `127.0.0.1` by default. It always enables bearer authentication. In `.env`, configure exact `MCP_ALLOWED_ORIGINS`, `MCP_OIDC_ISSUER`, `MCP_AUTH_AUDIENCE`, an HTTPS `MCP_PUBLIC_BASE_URL`, and either `MCP_OIDC_JWKS_URL` or `MCP_OIDC_INTROSPECTION_URL`. Set OAuth client credentials if introspection requires them. The server fails startup if these listener and verifier requirements are missing. Keep `MCP_REMOTE_EXECUTION_ENABLED=false` until this is a dedicated trusted scanner deployment, then explicitly set it to `true` to permit authenticated scanner calls. ```bash chmod 600 .env docker compose --profile http up --build pentest-mcp-http ``` The endpoint is `http://127.0.0.1:8000/mcp` unless you change `MCP_SERVER_PORT`. Compose uses that same port inside and outside the container. Compose binds the service to `0.0.0.0` inside its network namespace, so the HTTPS public base URL and identity settings are required even when the published host port remains on loopback. For remote clients, keep the Compose listener on loopback and put a TLS reverse proxy with access controls in front of it. Set `MCP_BIND_ADDRESS` to a non-loopback address only when that address and its firewall policy are intentional; bearer tokens must travel over TLS. ### Network capture and custom wordlists The default container can scan targets on its Docker network but cannot capture host interface traffic. On a Linux host, the separate capture profile opts in to host networking plus `NET_RAW` and `NET_ADMIN` for `tcpdump`: ```bash docker compose --profile capture run --build --rm -T pentest-mcp-capture ``` That profile can observe the host network namespace and should be used only when the engagement allows packet capture. Docker Desktop does not provide the same host interface access. To use private wordlists without adding them to the image, attach a read-only host directory to a one-off run: ```bash docker compose run --rm -T --volume /absolute/wordlists:/app/custom-wordlists:ro pentest-mcp ``` To enable reviewed, signed Nuclei HTTP templates in Compose, set `MCP_NUCLEI_TEMPLATES_HOST_DIR` in `.env` to an absolute host directory and use the opt-in override file. It mounts that directory read-only at `/reviewed-nuclei`: ```bash docker compose -f docker-compose.yml -f docker-compose.nuclei.yml run --build --rm -T pentest-mcp ``` The Docker image installs: - `nmap`, `john`, `hashcat`, `gobuster`, `nikto`, `ffuf`, `hydra`, `sqlmap`, `tcpdump` - `subfinder`, `httpx` + `httpx-toolkit` alias, `nuclei` ## Required Host Tools (non-Docker runs) Ensure the binaries are in `PATH`: ```bash which nmap john hashcat gobuster nikto subfinder httpx-toolkit ffuf nuclei hydra sqlmap tcpdump ``` If `httpx-toolkit` is not installed, a validated ProjectDiscovery `httpx` binary is accepted as fallback. ## MCP assessment contract The October 2026 server declares `@modelcontextprotocol/sdk@^1.32.2` and exposes the tool inventory above over stdio or authenticated Streamable HTTP. Tools have Zod schemas and MCP annotations. An assessment CLI should load the **entire catalog**, then select a method based on the engagement plan, available binaries, and observed results. It must treat tool output and target content as untrusted data. An MCP transport connection is not itself evidence of a test: the durable record and matching scope decision are the acceptance evidence. The server's target gate checks declared inputs before execution. It cannot prove that a third-party tool will never make secondary requests. Keep the agent's OS identity, filesystem mounts, credentials, outbound network policy, and the scanner's own configuration aligned with the signed scope. For a client engagement, separate the scope author, assessment agent, and report reviewer so the agent does not inherit repository-building conversations or another client's context. Keep client-specific deployment inventories, authorization gaps, host details, and evidence locations in private engagement storage outside the public repository and model-builder context. The [Daybreak and Glasswing note](docs/research/daybreak-glasswing-2026-10-25.md) identifies current program and client-work access requirements. An API key or CLI model label by itself is not a grant to run a customer assessment. ## Security notice Use the server only on systems covered by explicit written authorization. Keep evidence and client data private, and validate findings before reporting them.