⌘K
Submit a package or repository for security scanning.
Submit a package, repository, or MCP server for security scanning.
/api/v1/scanStarts a new scan and returns the scan_id for polling or streaming.
curl -X POST https://api.mcpsafe.io/api/v1/scan \
-H "Content-Type: application/json" \
-d '{"input": "@modelcontextprotocol/server-github", "mode": "fast"}'For private scans or higher rate limits, authenticate with an API key:
curl -X POST https://api.mcpsafe.io/api/v1/scan \
-H "Authorization: Bearer mcpsafe_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{"input": "github:owner/repo", "mode": "deep", "scan_visibility": "private"}'To scan a private GitHub repository, npm package, or PyPI package, pass a read-only token in the credentials object. The token is used once for the download and never logged or stored:
curl -X POST https://api.mcpsafe.io/api/v1/scan \
-H "Authorization: Bearer mcpsafe_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"input": "github:my-org/private-mcp",
"mode": "deep",
"scan_visibility": "private",
"credentials": { "type": "github", "token": "ghp_xxxxxxxxxxxxxxxxxxxx" }
}'The type field must match the input target — submitting an npm token against a GitHub URL returns 400 CREDENTIAL_TYPE_MISMATCH before a scan slot is consumed. See Private scans for token formats per registry, what's logged, and rotation guidance.
{
"success": true,
"data": {
"scan_id": "cAOvXioPjoEEP6g=",
"canonical_id": "npm:@modelcontextprotocol/server-github@latest"
}
}| Field | Type | Description |
|---|---|---|
scan_id | string | Unique ID for this scan execution |
canonical_id | string | Normalised package identifier |
The full reference (with every variant — npm, PyPI, GitHub, Docker, Official MCP Registry) is at Getting Started — Input formats. Common examples:
@modelcontextprotocol/server-github # npm scoped
pypi:requests==2.31.0 # PyPI pinned
github.com/owner/repo # GitHub HEAD
docker:nginx:1.27-alpine # Docker tag
io.github.punkpeye/fastmcp # MCP registry
After receiving a scan_id, either:
Option A — SSE stream (recommended for real-time UI):
const es = new EventSource(`https://api.mcpsafe.io/scan/${scanId}/stream`);
es.onmessage = (e) => {
const event = JSON.parse(e.data);
if (event.type === "scan_complete") {
console.log(event.scan_result);
es.close();
}
};Event types: mode_selected, cache_hit, stage_started, stage_completed, rule_started, rule_completed, judge_verdict (deep scans), scan_complete, scan_failed. See GET /scan/:id for the full event schema.
Option B — poll (for CI/CD):
# Poll until status === "complete"
while true; do
STATUS=$(curl -s https://api.mcpsafe.io/scan/$SCAN_ID | jq -r '.status')
[ "$STATUS" = "complete" ] && break
sleep 3
done| Code | HTTP | Meaning |
|---|---|---|
INVALID_REQUEST | 400 | Malformed JSON, missing input, or unparseable target |
INVALID_CREDENTIALS | 400 | credentials object has the wrong shape or an empty token |
CREDENTIAL_TYPE_MISMATCH | 400 | credentials.type doesn't match the resolved target (e.g. an npm token on a GitHub URL) |
CREDENTIALS_PUBLIC_SCAN | 400 | credentials was supplied on a scan_visibility: "public" scan — credentials are private-scan only |
REPOSITORY_NOT_FOUND | 400 | Target resolves to a package or repo that doesn't exist on the upstream registry |
NOT_AN_MCP_SERVER | 400 | Target exists but doesn't look like an MCP server (no manifest, no MCP SDK imports) |
PRIVATE_REQUIRES_SIGNIN | 401 | scan_visibility: "private" was requested without authentication |
SIGNIN_REQUIRED_FOR_DEEP | 401 | Anonymous user attempted a mode: "deep" scan |
FORCE_RESCAN_REQUIRES_SIGNIN | 401 | force_rescan: true was set without authentication |
SUBSCRIPTION_REQUIRED | 402 | Free-tier account hit a paid-only feature (private scan, API key) |
RATE_LIMITED | 429 | Per-user cap reached for the current window |
ORG_RATE_LIMITED | 429 | Org-shared cap reached (Team and Business plans share quota across members) |
FORCE_RESCAN_RATE_LIMITED | 429 | Force-rescan throttle hit (10/h per user, 3/h per repo) |
SCAN_START_FAILED | 500 | Internal error starting the scan pipeline |