Skip to content

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: true when 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 from git diff rather 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_REPORTno 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 assumedegraded, 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.