{"openapi":"3.1.0","info":{"title":"MCPTriage API","version":"0.6.1","description":"Static analysis of MCP servers and agent skills before install, by MoorAI's open-source engine. Nothing is installed or run. Content-free: submitted text is scanned in a temporary directory and deleted; only a hash, the verdict and content-free findings are stored. No API key. CORS: /api/v1/* and this document answer any origin (Access-Control-Allow-Origin: *); /mcp does not. Per IP address, shared by /api/v1/* and /mcp: 60 requests per minute; 30 paste scans and 10 package scans per hour that are not served from cache (token buckets, burst equal to the hourly budget). Bodies are capped at 1 MB. Package analyses run one at a time; when 5 are waiting the API answers 503 with Retry-After: 30."},"servers":[{"url":"https://mcptriage.moorai.dev"}],"paths":{"/api/v1/scan":{"post":{"summary":"Scan a pasted file (a skill, an MCP config, any text file)","description":"The filename sets how the text is analysed (SKILL.md, AGENTS.md, .mcp.json, config.toml, ...). A config file (.json, .toml) also gets per-launch pinning advice. An identical text is answered from cache.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PasteRequest"}}}},"responses":{"200":{"description":"Verdict","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PasteResult"}}}},"400":{"description":"The body is not a JSON object, or its fields name nothing scannable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Called from a browser page on another site; call it from a server or script","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"The body is larger than 1 MB","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded; see Retry-After","headers":{"Retry-After":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"The scan engine failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/scan-package":{"post":{"summary":"Scan a published package","description":"Downloads that exact artifact from npm, PyPI or a GitHub archive, verifies the registry checksum and analyses it. npm and PyPI packages also get the ten MCP pre-install checks. A completed analysis is reused for 24 hours.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PackageRequest"}}}},"responses":{"200":{"description":"Verdict","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PackageResult"}}}},"400":{"description":"The body is not a JSON object, or its fields name nothing scannable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Called from a browser page on another site; call it from a server or script","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"The body is larger than 1 MB","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded; see Retry-After","headers":{"Retry-After":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"The scanner is busy; see Retry-After","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/verdict":{"post":{"summary":"Look up a published catalogue verdict","description":"The published verdict for an MCP server package on npm or PyPI, from the same rows as /api/reputation-feed.json. found:false means no published verdict, not a clean result. This request names the package; download the feed to match without naming it.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VerdictRequest"}}}},"responses":{"200":{"description":"Lookup result","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VerdictResult"}}}},"400":{"description":"The body is not a JSON object, or its fields name nothing scannable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"The body is larger than 1 MB","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded; see Retry-After","headers":{"Retry-After":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/mcp":{"post":{"summary":"MCP endpoint (Streamable HTTP, JSON-RPC 2.0)","description":"Stateless MCP server. Protocol versions 2026-07-28 (per-request _meta, server/discover) and 2025-11-25, 2025-06-18, 2025-03-26 (initialize). Tools: scan_package, scan_mcp_config, scan_skill, lookup_verdict. JSON responses only; no sessions. Shares the /api/v1 rate limits.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","description":"One JSON-RPC 2.0 request or notification"}}}},"responses":{"200":{"description":"JSON-RPC response","content":{"application/json":{"schema":{"type":"object"}}}},"202":{"description":"Notification accepted"},"400":{"description":"JSON-RPC error: parse error, invalid request, header mismatch (-32020) or unsupported protocol version (-32022)","content":{"application/json":{"schema":{"type":"object"}}}},"403":{"description":"Origin not allowed","content":{"application/json":{"schema":{"type":"object"}}}},"404":{"description":"Method not found (-32601), protocol 2026-07-28","content":{"application/json":{"schema":{"type":"object"}}}},"413":{"description":"Body larger than 1 MB","content":{"application/json":{"schema":{"type":"object"}}}},"429":{"description":"Rate limit exceeded (-31429); see Retry-After","content":{"application/json":{"schema":{"type":"object"}}}}}},"get":{"summary":"Not offered","description":"There is no GET stream.","responses":{"405":{"description":"Method Not Allowed; Allow: POST"}}}},"/api/scan":{"post":{"summary":"Browser form: scan a pasted file","description":"Used by the landing page. Requires a Cloudflare Turnstile token when the service has one configured; programs use /api/v1/scan.","requestBody":{"required":true,"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/PasteRequest"},{"type":"object","properties":{"turnstileToken":{"type":"string"}}}]}}}},"responses":{"200":{"description":"Verdict","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PasteResult"}}}},"400":{"description":"Invalid request or failed bot check","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"Text over 1 MB","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/scan-package":{"post":{"summary":"Browser form: scan a published package","description":"Used by the landing page. Requires a Cloudflare Turnstile token when the service has one configured; programs use /api/v1/scan-package.","requestBody":{"required":true,"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/PackageRequest"},{"type":"object","properties":{"turnstileToken":{"type":"string"}}}]}}}},"responses":{"200":{"description":"Verdict","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PackageResult"}}}},"400":{"description":"Invalid request or failed bot check","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"Busy","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/verify":{"post":{"summary":"Recompute an Evidence Pack's compositeDigest","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","description":"An Evidence Pack from /scan/{id}.json or /package/{id}.json"}}}},"responses":{"200":{"description":"Integrity result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"computed":{"type":"string"},"provided":{"type":["string","null"]}}}}}},"400":{"description":"Not a JSON object","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/reputation-feed.json":{"get":{"summary":"Every published MCP verdict for npm and PyPI packages","description":"Download it whole and match on your side, so no request names a server you run.","responses":{"200":{"description":"The feed","content":{"application/json":{"schema":{"type":"object","properties":{"feed":{"type":"string"},"v":{"type":"integer"},"generatedAt":{"type":"string"},"entries":{"type":"array","items":{"$ref":"#/components/schemas/FeedEntry"}}}}}}}}}},"/scan/{id}.json":{"get":{"summary":"A paste scan's Evidence Pack","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Evidence Pack","content":{"application/json":{"schema":{"type":"object"}}}},"404":{"description":"No such scan","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/package/{id}.json":{"get":{"summary":"A package scan's Evidence Pack","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Evidence Pack","content":{"application/json":{"schema":{"type":"object"}}}},"404":{"description":"No such scan","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/health":{"get":{"summary":"Health","responses":{"200":{"description":"Up","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"version":{"type":"string"}}}}}}}}},"/openapi.json":{"get":{"summary":"This document","responses":{"200":{"description":"OpenAPI 3.1","content":{"application/json":{"schema":{"type":"object"}}}}}}}},"components":{"schemas":{"Error":{"type":"object","properties":{"error":{"type":"string"},"retryAfterSeconds":{"type":"integer"}},"required":["error"]},"Verdict":{"type":"string","enum":["CLEAN","CAUTION","REVIEW","DO-NOT-INSTALL","NAME-NOT-PUBLISHED"]},"Finding":{"type":"object","description":"Content-free: never a snippet or file contents","properties":{"threatId":{"type":["integer","string"]},"category":{"type":["string","null"]},"tier":{"type":["string","null"]},"relativePath":{"type":["string","null"]},"contentHash":{"type":["string","null"]},"intentLabels":{"type":"array","items":{"type":"string"}}}},"PasteRequest":{"type":"object","properties":{"text":{"type":"string","maxLength":1000000},"filename":{"type":"string","description":"Default SKILL.md"}},"required":["text"]},"PasteResult":{"type":"object","properties":{"id":{"type":"string"},"verdict":{"$ref":"#/components/schemas/Verdict"},"findings":{"type":"array","items":{"$ref":"#/components/schemas/Finding"}},"mcp":{"type":["object","null"],"description":"Pinning advice for a config file: { launches: [{ index, ecosystem, runner, pin, status, codes }] }"},"summary":{"type":"object"},"permalink":{"type":"string"},"cached":{"type":"boolean"}}},"PackageRequest":{"type":"object","properties":{"ref":{"type":"string","maxLength":300,"description":"npm:<name>[@version], pypi:<name>[==version] or github:<owner>/<repo>/<path>[@ref]"}},"required":["ref"]},"PackageResult":{"type":"object","properties":{"id":{"type":"string"},"ref":{"type":"string"},"version":{"type":["string","null"]},"analysed":{"type":"boolean"},"complete":{"type":"boolean"},"verdict":{"$ref":"#/components/schemas/Verdict"},"knownBad":{"type":"boolean"},"notes":{"type":"array","items":{"type":"object"}},"findings":{"type":"array","items":{"$ref":"#/components/schemas/Finding"}},"mcp":{"type":["object","null"],"description":"MCP pre-install checks and reputation (npm, PyPI)"},"permalink":{"type":"string"},"cached":{"type":"boolean"}}},"VerdictRequest":{"type":"object","properties":{"ecosystem":{"type":"string","enum":["npm","pypi"]},"name":{"type":"string","maxLength":214},"version":{"type":"string"}},"required":["ecosystem","name"]},"FeedEntry":{"type":"object","properties":{"ecosystem":{"type":"string"},"name":{"type":"string"},"version":{"type":["string","null"]},"verdict":{"$ref":"#/components/schemas/Verdict"},"reasons":{"type":"array","items":{"type":"string"}},"scannedAt":{"type":["string","null"]},"analysisRev":{"type":["string","null"]}}},"VerdictResult":{"type":"object","properties":{"found":{"type":"boolean"},"ecosystem":{"type":"string"},"name":{"type":"string"},"version":{"type":["string","null"]},"verdict":{"$ref":"#/components/schemas/Verdict"},"reasons":{"type":"array","items":{"type":"string"}},"scannedAt":{"type":["string","null"]},"analysisRev":{"type":["string","null"]},"versionMatches":{"type":["boolean","null"]},"page":{"type":"string"}},"required":["found"]}}}}