MCTS can run as a REST API server for programmatic scans — useful when you want other tools or services to trigger scans without using the CLI directly.
Most users should use the CLI. The REST API is for automation and integration scenarios.
Scores & gates: Scoring developer guide —scoring_mode,gate_violations, v2 fields.
Instead of running mcts scan from the command line, you can start a local HTTP server with mcts serve and send scan requests via REST endpoints. The API runs the same scanner as the CLI and returns the same JSON report format.
Use cases:
- Integrate MCTS into an internal security platform
- Trigger scans from a web dashboard or orchestration tool
- Run scans from languages other than Python/shell
uv sync --extra apiAdds fastapi and uvicorn.
mcts serve --host 127.0.0.1 --port 8080
# OpenAPI docs: http://127.0.0.1:8080/docsWhen MCTS_API_KEY is set, every endpoint except /health requires an X-API-Key header. When unset, loopback binds (127.0.0.1) are allowed with a startup warning. Binding to a non-loopback address without a key requires --allow-unauthenticated.
| Risk | Mitigation |
|---|---|
| Unauthenticated remote access | Set MCTS_API_KEY; do not use --allow-unauthenticated in production |
| Live MCP probing via API | Set understand_live_risk: true, header X-MCTS-Live-Consent: 1, or server env MCTS_LIVE_OK=1 |
| Resource exhaustion | Rate limits and fan-out caps (MCTS_API_* env vars — see Rate limits) |
Only expose the API on trusted networks. /health stays public for load balancers.
| Method | Path | Auth | Description |
|---|---|---|---|
GET |
/health |
No | Health check { "status": "ok" } |
POST |
/scan |
Optional | Full security scan |
POST |
/scan-tool |
Optional | Scan a single tool by name |
POST |
/scan-all-tools |
Optional | Scan each tool separately |
POST |
/scan-prompt |
Optional | Scan a single prompt |
POST |
/scan-all-prompts |
Optional | Scan all prompts |
POST |
/scan-resource |
Optional | Scan a single resource URI |
POST |
/scan-all-resources |
Optional | Scan all resources |
POST |
/scan-instructions |
Optional | Scan server instructions |
POST |
/readiness |
Optional | Production readiness (HEUR + optional OPA) |
Set MCTS_API_KEY before starting the server:
export MCTS_API_KEY="your-secret-key"
mcts serveClients must send the header on POST requests:
curl -s -X POST http://127.0.0.1:8080/scan \
-H "Content-Type: application/json" \
-H "X-API-Key: your-secret-key" \
-d '{"target": "examples/vulnerable-mcp-server/server.py"}' | jq .scoreImplementation: api/auth.py (require_api_key).
Live or remote scans (live: true or url) require the same consent as the CLI:
- Request body:
"understand_live_risk": true - Header:
X-MCTS-Live-Consent: 1 - Server environment:
MCTS_LIVE_OK=1
Without consent the API returns HTTP 403.
All scan endpoints accept these fields (plus endpoint-specific fields where noted):
| Field | Type | Default | Description |
|---|---|---|---|
target |
string | "." |
Server file or repo path |
live |
bool | false |
Connect to live MCP server |
url |
string | — | Remote MCP URL (implies live) |
transport |
string | "streamable-http" |
streamable-http or sse |
bearer_token |
string | — | Bearer token for remote MCP |
surfaces |
string[] | all four | tool, prompt, resource, instruction |
resource_mime_allowlist |
string[] | [] |
MIME filter for resources |
pip_audit |
bool | false |
Run pip-audit |
protocol_probe |
bool | false |
Active MCPS HTTP checks on url |
hide_safe |
bool | false |
Omit low-severity informational findings |
tool_filter |
string[] | [] |
Limit scan to named tools |
analyzer_filter |
string[] | [] |
Limit output to named analyzers |
fanout_offset |
int | 0 |
Pagination offset for batch scan endpoints |
fanout_limit |
int | env max (50) | Page size for batch scan endpoints |
scoring_mode |
string | "both" |
legacy, v2, or both |
weights_profile |
string | "manual_v1" |
v2 weights profile when scoring is enabled |
corpus_stats_path |
string | — | Optional path to corpus stats JSON for v2 percentiles |
min_security_score |
int | — | Gate: fail when v2 security score below threshold (not enforced server-side by default) |
max_absolute_risk |
int | — | Gate: fail when v2 absolute risk above threshold |
max_risk_level |
string | — | Gate: fail when v2 risk level exceeds band |
min_category_score_v2 |
object | — | Map of OWASP category key → minimum tile score (100=good) |
assets_path |
string | — | Optional .mcts/assets.yaml path for v2 asset-value overrides |
Batch endpoints (/scan-all-tools, /scan-all-prompts, /scan-all-resources) run one full analyzer pass per item. Use fanout_offset and fanout_limit to paginate; responses include truncated and truncation_warning when more items remain.
| Variable | Default | Description |
|---|---|---|
MCTS_API_RATE_LIMIT_PER_MINUTE |
30 | Per-client POST rate limit |
MCTS_API_MAX_BODY_BYTES |
1048576 | Max request body size |
MCTS_API_MAX_CONCURRENT_SCANS |
2 | Concurrent scan workers |
MCTS_API_MAX_FANOUT_ITEMS |
50 | Max items per batch page |
MCTS_API_SCAN_TIMEOUT_SECONDS |
300 | Per-scan timeout |
| Endpoint | Extra fields |
|---|---|
/scan-tool |
tool_name (required) |
/scan-prompt |
prompt_name (required) |
/scan-resource |
resource_uri (required); default MIME allowlist text/plain, text/html |
/readiness |
enable_opa (bool, default false) — see ReadinessRequest |
{
"target": ".",
"live": false,
"url": "https://mcp.example.com/mcp",
"transport": "streamable-http",
"bearer_token": "optional",
"surfaces": ["tool", "prompt", "resource", "instruction"],
"resource_mime_allowlist": ["text/plain", "application/json"],
"pip_audit": false,
"protocol_probe": false
}Response: ScanResponse shape — full ScanReport fields plus echoed scoring_mode and gate_violations (string array). When scoring_mode is v2 or both, the payload includes score_v2 (absolute risk, dimension scores, top contributors) and scoring_version. Legacy score.overall is always populated (invariant I1). The REST API does not fail HTTP status on gate violations — consumers inspect gate_violations or use the CLI for exit-code enforcement.
Optional request field min_category_score_v2: map of OWASP category key → minimum health score (100=good).
| Capability | Status | GAP | Notes |
|---|---|---|---|
| Per-request analyzer/scoring knobs (full parity with CLI) | Shipped | GAP-213 | Expanded ScanRequest |
| OAuth object on REST auth | Shipped | GAP-093 | OAuth fields on ScanRequest |
| Taxonomy tree in API response | Missing | GAP-094 | Hierarchical MCTS-T |
| MCP server mode over stdio | Planned | GAP-115 | scan_mcp_target, explain_finding tools |
| Fleet upload / bootstrap ingest | Planned | GAP-116–118 | Enterprise defer |
See Feature Expansion Plan — REST API.
curl -s -X POST http://127.0.0.1:8080/scan \
-H "Content-Type: application/json" \
-d '{"target": "examples/vulnerable-mcp-server/server.py"}' | jq .score
curl -s -X POST http://127.0.0.1:8080/scan-prompt \
-H "Content-Type: application/json" \
-d '{"target": ".", "prompt_name": "support_template", "live": true, "url": "..."}'