Tool Reference¶
The server has two surfaces: HTTP endpoints for session lifecycle and diff upload, and MCP tools for the reviews themselves. Your client calls both for you — this page is the reference for building against the server directly, or for adding a client CloudAEye does not yet ship a guide for.
The base URL and the MCP transport URL are the same string, e.g. https://api.cloudaeye.com/mcp.
HTTP endpoints¶
Both endpoints require a CloudAEye product API key in an X-Product-API-Key header, and the tenant_key the key belongs to. The key must carry that tenant on its record and grant the Code Review product; a mismatch answers 403.
POST /session¶
Mint or resume a session. A session is identified by (tenant, repository, developer) — not by branch or head. One developer working on one repository has exactly one session, and it survives branch switches and commits. The client sends no session id and holds no state; the server resolves the same session every time.
Request body (JSON):
| Field | Required | Description |
|---|---|---|
repo |
yes | Repository name |
branch |
yes | Current branch. Refreshed on every resume — state, not identity |
head |
yes | Current git rev-parse HEAD. Also refreshed on resume; it is what the context refresh fetches base file contents against |
tenant_key |
yes | Your CloudAEye tenant. Selects the database holding the repo integration, the code-context graph and the Jira installation |
user_name |
no | Scopes the session, so two developers on one repository don't share one |
language |
no | Legacy override. Leave it out — the server derives it from the diff. |
Response:
{
"session_id": "…",
"resumed": false,
"target_branch": "main",
"repo_full": "owner/name"
}
target_branch and repo_full come from your tenant's integration record — the bare repo name you sent is resolved into owner/name, and target_branch is the review baseline.
When the repository is not integrated, the response carries target_branch: "" plus target_branch_error and an integration_url naming where to connect it. This is not an error: the session still works, and the client falls back to diffing against local HEAD.
On a resume where a task was previously checked, the response also includes a prior_task block (task_description, task_source) so a client can offer to reuse it instead of re-prompting.
Errors: 400 if the body is not JSON or a required field is missing; 401 for a missing, unknown or expired key; 403 if the key does not belong to the named tenant or lacks the Code Review product; 503 if the server cannot reach its database.
POST /upload/{session_id}¶
Replace the diff for an existing session. Multipart, two fields: file (the diff) and base_sha (the commit it was taken against — the server stages file contents against it).
curl -s -F "file=@session.diff" -F "base_sha=$BASE" \
-H "X-Product-API-Key: $CLOUDAEYE_API_KEY" \
https://api.cloudaeye.com/upload/$SESSION_ID
Response:
{ "session_id": "…", "diff_unchanged": false }
diff_unchanged: truewhen the uploaded bytes are identical to the last upload and the cached context is still on disk — the server skips the cache wipe.- Otherwise the cached code-context graph and staged post-edit file tree are invalidated, so the next tool call re-derives them against the new diff.
Errors: 404 if the session is unknown — call POST /session first; 400 if the file field is missing.
The diff is never passed inline to an MCP tool. It lives on the server keyed by
session_id. Always derive it fromgit diffrather than constructing it by hand — see Deriving the diff.
MCP tools¶
There are four tools. All take a session_id and reference the uploaded diff by it; all return a JSON string. They are not key-authenticated — a session_id is only obtainable by a caller who authenticated to POST /session.
inspect_diff¶
One tool covers all three review passes. profile decides which prompts run.
inspect_diff(session_id: str, intent: str = "", profile: str = "inspect",
context: dict | None = None) -> str
| Argument | Description |
|---|---|
session_id |
Id from POST /session |
intent |
Optional. A one-line note on what the agent just changed — steers what the planner prioritises, and on a recheck says which findings were already addressed |
profile |
inspect (default), security, or review. An unknown value falls back to inspect, never to a wider set |
context |
Optional per-call extras: pr_title, pr_description, review_config, report_types |
profile |
Report types |
|---|---|
inspect |
BUG_REPORT — no security prompts |
security |
SECURITY_REPORT, LLM_SECURITY_REPORT, AIAGENT_SECURITY_REPORT, MCP_REPORT, SECRET_REPORT |
review |
all of the above |
Returns: verdict, findings[], profile, session_id, and counts when there are findings.
Everything else appears only when something didn't do what you'd assume — degraded, context_refresh, secret_scan, compiler_check, cached. No field means it went fine.
describe_change¶
A plain-markdown description of the session's diff.
describe_change(session_id: str, intent: str = "", context: dict | None = None) -> str
| Argument | Description |
|---|---|
session_id |
Id from POST /session |
intent |
Optional. The user's original task request, verbatim |
context |
Optional extras (pr_title, pr_description) |
Returns: a description field — a # Change Description paragraph plus an ## Important Changes bullet list. Trivial diffs short-circuit to a one-line "Trivial change —" summary. Also returns files_visited, an eval_summary, and a context_refresh summary when the graph refresh was skipped or failed.
ask¶
Answers a free-form question about the session's diff, against the refreshed code-context graph.
ask(session_id: str, question: str, intent: str = "",
context: dict | None = None) -> str
| Argument | Description |
|---|---|
session_id |
Id from POST /session |
question |
The user's question, passed verbatim. Do not summarise, expand or rewrite it — the phrasing carries what they want to know, and a rewritten question gets a different answer |
intent |
Optional summary of what the agent just changed. Context for the question, not its subject |
context |
Optional extras (pr_title, pr_description) |
Returns: a plain-markdown answer plus files_visited. This is not a review — no verdict, no findings. For findings use inspect_diff.
check_task¶
Verifies whether the session's diff fulfils a stated task.
check_task(session_id: str, task_description: str,
task_source: str = "user-supplied",
intent: str = "", context: dict | None = None) -> str
| Argument | Description |
|---|---|
session_id |
Id from POST /session |
task_description |
The task to verify against — a ticket reference list ([BETA-5225, #42], a bare Jira key, owner/repo#42, or a GitHub issue URL), or freeform text passed verbatim |
task_source |
Provenance label: github-issue, jira, spec, or user-text |
intent |
Optional. A one-line note on this round of edits; on a recheck, which prior gaps were addressed |
context |
Optional extras (pr_title, pr_description) |
Returns: a report field — a # Task Completion Check heading, an overall verdict (DONE / PARTIAL / NOT DONE), a per-requirement checklist, and any gaps. Tickets are re-fetched on every call.
Errors: returns a structured { "error": … } rather than a verdict when task_source=github-issue but the URL is unrecognised, when a ticket cannot be resolved, or when a task made only of unresolvable ticket references has nothing to judge. See Custom Context.
Deriving the diff¶
Always derive the diff from git diff rather than constructing it manually, so the server sees exactly what git sees.
git add --intent-to-add . && git diff > session.diff
git add --intent-to-add is non-destructive — it registers new files in the index without staging their content, so git diff emits them as new file hunks. Without it, completely untracked files are invisible to the diff and to the review. Undo with git reset HEAD <file> if needed.
End-to-end example¶
CE=https://api.cloudaeye.com/mcp
REPO=$(basename -s .git "$(git config --get remote.origin.url)")
BRANCH=$(git rev-parse --abbrev-ref HEAD)
HEAD_SHA=$(git rev-parse HEAD)
# 1. Create or resume the session. The response names the baseline branch.
curl -s -X POST "$CE/session" \
-H "Content-Type: application/json" \
-H "X-Product-API-Key: $CLOUDAEYE_API_KEY" \
-d "{\"repo\":\"$REPO\",\"branch\":\"$BRANCH\",\"head\":\"$HEAD_SHA\",\"tenant_key\":\"$CLOUDAEYE_TENANT_KEY\"}" \
> session.json
SESSION_ID=$(python -c "import json;print(json.load(open('session.json'))['session_id'])")
TARGET=$(python -c "import json;print(json.load(open('session.json')).get('target_branch') or '')")
# 2. Resolve the baseline: the fork point off the integrated branch, not its tip.
# Validate $TARGET first — it comes off the wire, and a leading dash reaching
# git fetch is command execution. No integration, no branch: fall back to HEAD.
BASE=""
case "$TARGET" in -*|*[!A-Za-z0-9._/-]*) TARGET="";; esac
if [ -n "$TARGET" ]; then
git rev-parse --verify -q "origin/$TARGET" >/dev/null || git fetch -q origin "$TARGET"
BASE=$(git merge-base "origin/$TARGET" HEAD)
fi
[ -n "$BASE" ] || BASE=$HEAD_SHA
# 3. Capture and upload the diff, with the commit it was taken against.
git add --intent-to-add . && git diff "$BASE" > session.diff
curl -s -F "file=@session.diff" -F "base_sha=$BASE" \
-H "X-Product-API-Key: $CLOUDAEYE_API_KEY" \
"$CE/upload/$SESSION_ID"
# 4. Call inspect_diff via your MCP client, passing session_id + intent + profile
Steps 1–3 are plain HTTP; step 4 is an MCP tool call your client makes. The Claude Code plugin bundles all four into one slash command; a client without that mechanism runs the first three itself.