Appearance
MCP
VulnGraph is also a Model Context Protocol server, so an AI agent (Cursor, Claude, Grok and others) can ask about the fleet, read the plan and record progress with the same API key it would use for the API.
Endpoint and auth
| Endpoint | https://api.vulngraph.igentify.in/v1/mcp |
| Auth | Authorization: Bearer vg_live_… (an API key); a signed-in session also works |
| Transport | Streamable HTTP, POST only, one JSON-RPC message per request, JSON answers (no SSE, no sessions) |
| Protocol | 2025-06-18 (also accepts 2025-03-26) |
| Methods | initialize, notifications/initialized, ping, tools/list, tools/call |
- Send
Content-Type: application/jsonand anAcceptheader that allowsapplication/json. Afterinitialize, clients sendMCP-Protocol-Version; an unsupported value answers400. - Browser requests must come from the VulnGraph web app (
Origincheck); other origins get403. Requests without anOriginheader (CLI tools, agents) are fine. GETanswers405. Notifications answer202with no body.
Cursor
Put the key in an environment variable (never in the file), for example export VULNGRAPH_API_KEY=$(cat ~/.vulngraph/mcp-agent.key), then add this to ~/.cursor/mcp.json (or .cursor/mcp.json in a project):
json
{
"mcpServers": {
"vulngraph": {
"url": "https://api.vulngraph.igentify.in/v1/mcp",
"headers": { "Authorization": "Bearer ${env:VULNGRAPH_API_KEY}" }
}
}
}The key page in the web app shows the same snippet under Use with MCP.
Other clients (stdio)
Clients that only start local stdio servers can use the mcp-remote bridge:
bash
sh -c 'exec npx -y mcp-remote https://api.vulngraph.igentify.in/v1/mcp \
--header "Authorization:Bearer $(cat ~/.vulngraph/mcp-agent.key)"'To try the server from a terminal:
bash
npx -y @modelcontextprotocol/inspector@latest --cli https://api.vulngraph.igentify.in/v1/mcp \
--transport http --method tools/list \
--header "Authorization: Bearer $VULNGRAPH_API_KEY"Tools
There are 25 tools. Each one is one API endpoint and needs that endpoint's scope. tools/list shows only the tools your key's scopes allow, and every call is checked again. Read tools are marked readOnlyHint; write tools are marked non-destructive, and the two step tools are idempotent.
| Tool | Scope | Same as | Use for |
|---|---|---|---|
portfolio_summary | findings:read | GET /portfolio/summary | How bad is the fleet right now? |
portfolio_start_here | findings:read | GET /portfolio/start-here | What should we fix first? |
portfolio_service | findings:read | GET /portfolio/service | How exposed is service X? |
portfolio_advisory | findings:read | GET /portfolio/advisory | Are we affected by CVE/GHSA Y? |
portfolio_package | findings:read | GET /portfolio/package | Where do we use package Z? |
portfolio_advisories | findings:read | GET /portfolio/advisories | List critical CVEs, or those with no fix |
portfolio_next | findings:read | GET /portfolio/next | Next plan steps and what blocks them |
portfolio_resolve | findings:read | GET /portfolio/resolve | Unsure how a name is spelled |
portfolio_changes | findings:read | GET /portfolio/changes | What changed since the last scan? |
portfolio_trend | findings:read | GET /portfolio/trend | Are we getting better? |
list_profiles | profiles:read | GET /profiles | Valid profile names |
get_profile | profiles:read | GET /profiles/{name} | Repositories of a profile |
list_runs | runs:read | GET /runs | Scan history |
get_latest_run | runs:read | GET /runs/latest | The run answers come from; is a scan running? |
get_run | runs:read | GET /runs/{runId} | Poll one run |
list_events | runs:read | GET /events | Catch up on run and step changes |
get_plan | plans:read | GET /runs/{runId}/plan | The plan, paged; decide_only for open choices |
list_recorded_steps | plans:read | GET /runs/{runId}/steps | What has been decided, which PRs are open |
get_step | plans:read | GET /runs/{runId}/steps/{rank} | One step with its numbered Decide options |
get_sketch | sketches:read | GET /runs/{runId}/sketch | The draft PR for a step (rank), or paged |
get_run_logs | logs:read | GET /runs/{runId}/logs | Why did a run fail? |
download_artifact | artifacts:download | GET /runs/{runId}/artifacts/{path} | A whole run file |
record_decision | decisions:write | POST /runs/{runId}/steps/{rank}/decision | Record the chosen Decide option |
set_step_status | step-status:write | POST /runs/{runId}/steps/{rank}/status | Record in progress, PR opened or merged, skipped |
start_run | runs:create | POST /runs | Start a new scan (only when asked) |
Argument names follow the API parameters in snake_case (run_id, rank, pr_url, include_unfinished, decide_only, sketch_mode). Every tool publishes its JSON input schema in tools/list.
Results
- A result is one text item holding compact JSON, the same body the API endpoint returns.
- Portfolio tools keep the freshness fields (
data_as_of,served_at,run_id,cached) and stay under 8 KB. get_plan(up to 25 steps per page by default, up to 100) andget_sketch(10 per page, up to 50) returntotal_stepsandnext_cursor; passnext_cursorascursorfor the next page. A page holds fewer steps when they are large. The plan summary comes on the first page, without the per-repository CI time and reviewer maps (each step carries its own).get_run_logspages withnextCursorandprevCursor(100 lines by default).download_artifactreturns small text files (up to 32 KB) inline; for larger files it returns a download URL valid for 5 minutes.
Errors
| Case | What you get |
|---|---|
Unknown tool, invalid arguments (for example a bad rank or state) | JSON-RPC error -32602 with a message naming the problem |
| Tool not allowed for the key | JSON-RPC error -32602, this key cannot use <tool>: missing scope <scope> |
The endpoint answered an error (unknown run, 409, 429, …) | A normal result with isError: true and the API error body as text |
| Answer too large | isError: true with a hint to narrow the request or page |
| Unknown method | JSON-RPC error -32601 |
Bad JSON, batch request, bad jsonrpc | JSON-RPC error -32700 or -32600 with HTTP 400 |
| Bad or missing key | HTTP 401 before any JSON-RPC handling |
Limits and activity
Tool calls count against the key's rate limits exactly like the matching endpoint: reads as reads, record_decision and set_step_status as writes, start_run against runs per day. Protocol messages (initialize, tools/list, ping) do not count. Every tool call appears in the key's activity with the tool name.