MCP server
Connect Claude Desktop, Cursor, VS Code, or any MCP host to V12 for automated security audits.
The V12 MCP server lets any MCP host—Claude Desktop, Cursor, Codex, VS Code, Windsurf, or your own agent—run audits, watch runs, and triage findings.
Server URL
https://v12.sh/api/mcpSetup
OAuth
Paste the server URL into your host’s MCP configuration and complete the V12 sign-in prompt. Claude Desktop, Cursor, Codex, VS Code, and Windsurf handle this end-to-end—no PAT needed.
Personal access token
For hosts that take a JSON config with a bearer header, create the token at Settings → Developer:
{ "mcpServers": { "v12": { "url": "https://v12.sh/api/mcp", "headers": { "Authorization": "Bearer ${env:V12_PAT}" } } }}PATs are bound to the currently selected organization, so switch orgs before creating the token if you want the MCP client to see a different account.
Cursor with a PAT
Put the remote server in either project config (.cursor/mcp.json) or global config (~/.cursor/mcp.json):
{ "mcpServers": { "v12": { "url": "https://v12.sh/api/mcp", "headers": { "Authorization": "Bearer ${env:V12_PAT}", "Accept": "application/json, text/event-stream" } } }}Cursor only interpolates environment variables it inherited when the app started. If Cursor is launched from the macOS Dock or Finder, values in a project .env or shell-only profile may not be visible to it. Set the token in the GUI environment, then fully quit and reopen Cursor:
bash -lc 'read -rsp "V12 PAT: " V12_PAT; echo; launchctl setenv V12_PAT "$V12_PAT"; unset V12_PAT'If you launch Cursor from a terminal where V12_PAT is exported, restarting from that terminal works too. Use Output → MCP Logs in Cursor to distinguish authentication errors from connection errors.
stdio fallback (mcp-remote)
For stdio-only hosts with no Streamable HTTP support, bridge through mcp-remote:
{ "mcpServers": { "v12": { "command": "npx", "args": ["-y", "mcp-remote", "https://v12.sh/api/mcp"], "env": { "MCP_HEADERS": "Authorization: Bearer ${env:V12_PAT}" } } }}Browser-origin policy
Desktop and CLI MCP hosts usually don’t send an Origin header, so browser CORS rules don’t affect them. If you’re connecting from a browser-based MCP client or local inspector:
- loopback origins such as
http://localhost:*,http://127.0.0.1:*, andhttp://[::1]:*are allowed automatically, - the app’s exact origin (
https://v12.sh) is allowed automatically, and - any other browser origin must be listed exactly in
MCP_ALLOWED_ORIGINS; disallowed origins are rejected with HTTP403.
Rate limits
Every /api/mcp request charges mcp:protocol:ip first (3000 per minute, per client IP), applied before the browser-origin check, before authentication, and impossible to exempt. A request rejected by the origin check (403) or arriving without a valid bearer token (401) stops right there, having spent that counter and nothing else.
A request that clears both then charges mcp:protocol (1500 per minute, per user). So two protocol counters is the successfully authenticated, non-exempt, allowed-origin case — accounts exempt from rate limits (V12 staff) skip the per-user counter and are left with the per-IP one. A third, product-level counter is conditional — it is charged only when the request actually dispatches a rate-limited tool or resource.
GET and DELETE never reach a handler: for that same authenticated, non-exempt, allowed-origin case they return 405 immediately after the two protocol counters, and a call that fails the origin or bearer check returns 403 or 401 after the per-IP counter alone. A POST carrying only a protocol control message (initialize, ping, tools/list) stops after the protocol counters too. A read or write bucket is charged when you call tools/call or resources/read — and by resources/list, which charges reads because it enumerates your runs.
Read-only tools and resources are not all charged the same bucket: v12_repos charges reads:github, the report, finding-detail, PoC and fix resources charge reads:artifact, and the remaining metadata tools and resources charge reads. See rate limits for the full table.
The per-IP counter is the one that surprises people: a whole office behind one NAT, or a CI fleet on one egress address, shares it.
Tools
| Tool | Scope | Description |
|---|---|---|
v12_whoami |
user:read |
Profile and active scopes for the current token |
v12_repos |
repos:read |
List GitHub repos connected to V12. Pagination via search, limit, offset. |
v12_audit_github |
runs:write |
Start a GitHub audit. Pass exactly one of repoUid (preferred) or repoFullName. repoFullName works for any public repo even without the V12 app installed. branch and sha optional. branch/sha are full-audit-only; diff responses echo resolvedFromSha/resolvedToSha/patchUid instead of sha. |
v12_upload_zip |
runs:write |
Create a presigned upload slot for a ZIP archive and return { zipUid, uploadUrl } |
v12_audit_zip |
runs:write |
Start a ZIP-backed audit using the zipUid returned by v12_upload_zip. patchContent or patchUid is optional for a diff review. |
v12_estimate_cost |
runs:read |
Estimate cost without creating a run. Returns scope files and an estimate with billableFileCount, billableBytes, billableChangedLines, billableLoc, and priceCents. Full-audit pricing uses billable bytes, LOC, and file count. Diff-review pricing uses the post-patch bytes of the scoped files plus billableChangedLines (null when diff metadata is unavailable—the quote then falls back to bytes alone). The fixed USD-cent price is charged in full when the run starts. Inline patches return a patchUid for reuse; GitHub diff quotes return resolvedFromSha/resolvedToSha, the exact commits priced—pass them back as fromRef/toRef to audit exactly those commits. |
v12_watch |
runs:read |
Poll a run. Carries cost (total billable USD, final once terminal) and, when terminal: true, findingCounts. |
v12_findings |
runs:read |
List findings with severity/validity filters and pagination |
v12_triage_finding |
findings:write |
Atomic update of validity, severity, and/or comment |
v12_cancel_run |
runs:manage |
Cancel a non-terminal run |
ZIP upload workflow
-
Call
v12_upload_zipto receive{ zipUid, uploadUrl }. -
PUTthe raw ZIP bytes touploadUrl:Terminal window curl -X PUT \-H 'Content-Type: application/zip' \--data-binary @source.zip \"$UPLOAD_URL" -
Call
v12_audit_zipwith the returnedzipUid.
Resources
| URI | Description |
|---|---|
v12://runs |
Recent runs |
v12://runs/{uid} |
Run detail |
v12://runs/{uid}/report |
Report (JSON) |
v12://runs/{uid}/report.md |
Report (Markdown) |
v12://runs/{uid}/findings |
First page of findings. Use v12_findings to paginate. |
v12://runs/{uid}/findings/{findingUid} |
Full finding detail |
v12://runs/{uid}/findings/{findingUid}/poc |
Proof-of-concept (404 if none recorded) |
v12://runs/{uid}/findings/{findingUid}/fix |
Compact fix payload (404 if none recorded) |
Prompts
| Prompt | Arguments | Description |
|---|---|---|
audit_pull_request |
repo_full_name, pr_number |
Audit a pull request |
audit_repository |
repo_full_name, paths? |
Audit a repository or path subset |
review_findings |
run_uid |
Review and triage findings from a completed run |
triage_finding |
run_uid, finding_uid? |
Triage a single finding with explicit user confirmation before mutating |
Skill
V12 ships a short MCP skill that teaches your agent the audit workflow—repo selection, polling, presenting findings, triage. Hosts that surface MCP instructions (Claude Desktop, Cursor, Codex, Windsurf) load it on connect. If yours doesn’t, or you’re using V12 over REST with your own agent, fetch the skill from the server’s instructions field and paste it into the system prompt.