MCP server
Tools reference
The eight Arachne MCP tools: input schemas, the scope and role each needs, which are metered, and a worked JSON-RPC request and response for every one.
All tools are called with tools/call. Arguments are checked before anything runs: types, sizes, required
fields, and no unknown keys (additionalProperties: false). Ids are UUIDs. The examples use
obviously fake ids; replace them with yours. Every successful result is a JSON document in result.content[0].text,
shown below already decoded.
| Tool | Token scope | Role must allow | Cost |
|---|---|---|---|
advisories_latest | mcp:advisories.read | findings:read | free |
advisories_search | mcp:advisories.read | findings:read | free |
advisory_explain | mcp:advisories.read | findings:read | free |
repos_affected | mcp:repos.read | projects:read | free |
propose_version | mcp:repos.read | projects:read | free |
job_status | mcp:repos.read | jobs:read | free |
fix_submit | mcp:fix.submit | jobs:submit | reserves 1 use |
pr_open | mcp:pr.open | pr:open | 1 PR = 1 use |
With a personal access token the token scopes are derived from your role, so the "role must allow" column is the one
that matters: owners and admins get all eight, members everything except pr_open, viewers the six read tools.
advisories_latest#
Newest advisories in Arachne's database, ordered by (modified, id). Without a cursor you get the newest
limit; pass the returned next_cursor back as since_cursor to poll for anything newer.
| Argument | Type | |
|---|---|---|
since_cursor | string | optional; from a previous next_cursor |
ecosystem | string | optional; e.g. PyPI, npm, Maven, crates.io, Go |
limit | integer 1–100 | optional; default 20 |
{"jsonrpc":"2.0","id":10,"method":"tools/call",
"params":{"name":"advisories_latest","arguments":{"ecosystem":"PyPI","limit":2}}}
{"items":[
{"id":"GHSA-xxxx-xxxx-xxxx","aliases":["CVE-2026-00000"],"summary":"Example summary","severity":"HIGH",
"modified":"2026-11-13T09:12:00Z","ecosystems":["PyPI"],"packages":["example-pkg"],"kev":false,"epss":0.0012},
{"id":"GHSA-yyyy-yyyy-yyyy","aliases":[],"summary":"Another example","severity":"MODERATE",
"modified":"2026-11-14T07:40:00Z","ecosystems":["PyPI"],"packages":["other-pkg"],"kev":false,"epss":0.0}],
"next_cursor":"WyIyMDI2LTExLTE0VDA3OjQwOjAwWiIsIkdIU0EteXl5eS15eXl5LXl5eXkiXQ"}
advisories_search#
Look an advisory up by id or alias (CVE, GHSA or OSV id), or list a package's advisories by
ecosystem + package. With a version, it says whether that version is affected, drops advisories that do
not affect it, and lists the fixed versions.
| Argument | Type | |
|---|---|---|
query | string | an advisory id or alias; or give ecosystem + package instead |
ecosystem, package | string | together |
version | string | optional; evaluates affected |
limit, cursor | integer 1–100, string | optional paging |
{"jsonrpc":"2.0","id":11,"method":"tools/call",
"params":{"name":"advisories_search","arguments":{"ecosystem":"PyPI","package":"requests","version":"2.30.0"}}}
{"items":[
{"id":"GHSA-j8r2-6x86-q33q","aliases":["CVE-2023-32681"],
"summary":"Unintended leak of Proxy-Authorization header in requests","severity":"MODERATE",
"modified":"…","ecosystems":["PyPI"],"packages":["requests"],"kev":false,"epss":0.06,
"affected":true,"fixed_versions":["2.31.0"],"fix_state":"fixed"}],
"next_cursor":null}
fix_state is fixed when at least one fixed version is known, otherwise no_fix_known.
affected is null when no version was given. Values such as severity, modified and epss above are illustrative.
advisory_explain#
One advisory in full: impact line (severity, KEV, EPSS), affected ranges per package, fix guidance, references
(up to 20), details (up to 4000 characters), source and licence. With project_id it adds that project's recorded
findings for the advisory. The advisory part is cached and shared; your project findings are fetched per call and never cached.
| Argument | Type | |
|---|---|---|
advisory_id | string | required; id or alias |
project_id | string (UUID) | optional |
{"jsonrpc":"2.0","id":12,"method":"tools/call",
"params":{"name":"advisory_explain","arguments":{"advisory_id":"GHSA-j8r2-6x86-q33q",
"project_id":"11111111-2222-4333-8444-555555555555"}}}
{"advisory_id":"GHSA-j8r2-6x86-q33q","aliases":["CVE-2023-32681"],
"summary":"Unintended leak of Proxy-Authorization header in requests","details":"…","severity":"MODERATE",
"affected":[{"ecosystem":"PyPI","package":"requests",
"ranges":[[{"introduced":"2.3.0"},{"fixed":"2.31.0"}]],"fixed_versions":["2.31.0"]}],
"fix_guidance":["PyPI/requests: upgrade to 2.31.0 or later"],
"references":["https://github.com/psf/requests/security/advisories/GHSA-j8r2-6x86-q33q", "…"],
"source":"…","license":"…",
"impact":"Severity MODERATE. Not in CISA KEV. EPSS 0.060.","kev":false,"epss":0.06,
"generated_by":"template","cached":true,
"project":{"project_id":"11111111-2222-4333-8444-555555555555",
"findings":[{"package":"requests","version":"2.30.0","fix_state":"fixed","fixed_versions":["2.31.0"]}],"note":null}}
If the project has no recorded finding, findings is empty and note suggests running
repos_affected on it first.
repos_affected#
Scans your organisation's connected projects now (clone, read manifests, match) and records the findings. It scans a
few projects per call: up to limit (1–5, default 5) per page, or exactly one with project_id. One project
failing does not hide the others; it appears in errors.
| Argument | Type | |
|---|---|---|
project_id | string (UUID) | optional; scan only this project |
kev_only | boolean | optional; only findings in CISA KEV |
limit | integer 1–5 | optional; projects per page |
cursor | string | optional; the previous next_cursor |
{"jsonrpc":"2.0","id":13,"method":"tools/call","params":{"name":"repos_affected","arguments":{}}}
{"items":[
{"project_id":"11111111-2222-4333-8444-555555555555","repo":"aadhi0612/arachne-demo",
"package":"requests","version":"2.30.0","direct":true,"advisory":"GHSA-j8r2-6x86-q33q",
"summary":"Unintended leak of Proxy-Authorization header in requests","fix_state":"fixed",
"fixed_versions":["2.31.0"],"kev":false,"epss":0.06,"source":"…","license":"…"}],
"errors":[{"project_id":"66666666-7777-4888-9999-000000000000","repo":"example-org/private-repo","error":"scan_failed"}],
"projects_scanned":2,"next_cursor":null}
Note
scan_failed usually means the repository could not be cloned (private, renamed, or deleted). See
Troubleshooting.
propose_version#
The deterministic resolver's answer for one package in one project: the lowest version that fixes every known advisory, is old enough to pass the cooldown (7 days by default), and is not a major bump unless approved, with every rejected candidate and the reason. It does not change anything.
| Argument | Type | |
|---|---|---|
project_id | string (UUID) | required |
package | string | required; as declared in the project |
{"jsonrpc":"2.0","id":14,"method":"tools/call",
"params":{"name":"propose_version","arguments":{"project_id":"11111111-2222-4333-8444-555555555555","package":"requests"}}}
{"package":"requests","ecosystem":"PyPI","current":"2.30.0","status":"fix_found","chosen":"2.31.0",
"major_bump":false,"advisories":["GHSA-j8r2-6x86-q33q"],"rejected":[],"reason":"…","notes":[]}
status is one of fix_found, no_fix_known, no_safe_candidate, not_vulnerable.
job_status#
Where a fix job is and what it found.
| Argument | Type | |
|---|---|---|
job_id | string (UUID) | required |
{"jsonrpc":"2.0","id":15,"method":"tools/call",
"params":{"name":"job_status","arguments":{"job_id":"aaaaaaaa-bbbb-4ccc-8ddd-eeeeeeeeeeee"}}}
{"job_id":"aaaaaaaa-bbbb-4ccc-8ddd-eeeeeeeeeeee","project_id":"11111111-2222-4333-8444-555555555555",
"status":"done","outcome":"verified","confidence":"high: deterministic dependency-only change",
"versions":{"requests":"2.31.0"},"error":null,
"gates":[{"name":"clean_room_reinstall","status":"pass"},{"name":"build","status":"pass"},
{"name":"independent_rescan","status":"pass"},{"name":"test_delta","status":"pass"},
{"name":"policy","status":"pass"}],
"approvals":{"open_pr":"pending"},"needs_approval_for":[]}
While running, status is queued or running and outcome is null. approvals
maps each approval kind (open_pr, major_bump) to pending, approved or rejected.
fix_submit metered#
Queues a sandboxed fix job for a project and reserves one use of your monthly allowance. The use is
released again unless a PR is eventually opened. The idempotency key makes retries safe: the same key returns the same
job (created: false) and never reserves twice.
| Argument | Type | |
|---|---|---|
project_id | string (UUID) | required |
idempotency_key | string, 8–128 chars | required; one per intended fix, e.g. fix-requests-2026-11-14 |
{"jsonrpc":"2.0","id":16,"method":"tools/call",
"params":{"name":"fix_submit","arguments":{"project_id":"11111111-2222-4333-8444-555555555555",
"idempotency_key":"fix-requests-2026-11-14"}}}
{"job_id":"aaaaaaaa-bbbb-4ccc-8ddd-eeeeeeeeeeee","created":true,
"usage":{"allowance":3,"consumed":0,"reserved":1,"remaining":2,"resets_at":"2026-12-01T00:00:00+00:00","plan":"hatchling"}}
When the allowance is used up, the call returns a tool error instead:
{"jsonrpc":"2.0","id":16,"result":{"isError":true,"content":[{"type":"text",
"text":"{\"error\": \"quota_exhausted\", \"resets_at\": \"2026-12-01T00:00:00+00:00\", \"options\": [\"upgrade\", \"request_more\", \"wait\"]}"}]}}
pr_open 1 PR = 1 use#
Opens the pull request for a job that is verified and whose open_pr approval a person
approved in the web app. Pushes a remediation/… branch with exactly the reviewed files and opens the PR with the evidence
bundle as its body. This is when the reserved use is consumed. Calling it again for the same job returns the existing PR
(created: false) and counts nothing.
| Argument | Type | |
|---|---|---|
job_id | string (UUID) | required |
{"jsonrpc":"2.0","id":17,"method":"tools/call",
"params":{"name":"pr_open","arguments":{"job_id":"aaaaaaaa-bbbb-4ccc-8ddd-eeeeeeeeeeee"}}}
{"url":"https://github.com/aadhi0612/arachne-demo/pull/1","number":1,"created":true,
"usage":{"allowance":3,"consumed":1,"reserved":0,"remaining":2,"resets_at":"2026-12-01T00:00:00+00:00","plan":"hatchling"}}
Without an approval it refuses:
{"error":"not_approved","detail":"open_pr requires an approved human decision"}
Errors#
Tool-level problems come back as a normal JSON-RPC result with isError: true and a JSON body
{"error": "<code>", …} in content[0].text, so your model can read and explain them:
| Code | Meaning |
|---|---|
invalid_argument | Missing required field, unknown field, wrong type or size, bad cursor. detail says which. |
not_found | No such project, job, advisory or package, including anything that belongs to another organisation (Arachne never says whether it exists elsewhere). |
quota_exhausted / payments_not_enabled | Allowance used up; carries resets_at and options. |
not_approved | pr_open on a job that is not verified or not approved. |
rate_limited | Per-person or action limit; carries retry_after_seconds. |
org_frozen, not_a_member, account_required | The organisation is frozen, you are not a member, or the action needs a person account. |
busy | Storage contention; retry after retry_after_seconds. |
internal_error | Something failed on Arachne's side; the call was logged. |
Protocol-level problems are JSON-RPC errors instead: -32700 parse error, -32600 invalid request (also for
batches and oversized bodies), -32601 unknown method, -32602 unknown tool or bad params, -32001
insufficient scope (HTTP 403), -32029 rate limited (HTTP 429).