This is the second part of the MCP server guide. The first part — issuing an agent token, scopes, delegation, and connecting each client — is here: <https://help.auditbadger.com/hc/auditbadger-help-center/articles/1775898378-mcp-server-connect-ai-assistants-to-humadroid> 

## **The tools**

The server mounts 134 tools. You never need to memorise them — the assistant discovers them, and the server's own instructions tell it how they fit together — but here is the shape.

### **Reads (65)**

Every read returns current state plus an **observed version** for each resource. Two different reads legitimately return two different observed versions for the same record — each one fingerprints its own projection, so `list_risks`, `get_risk`, and an operation companion each answer "has what *this read* shows changed?", not "has the row changed?". Only the versions returned by the **operation companion** (the `operation_companion` tool, or the operation's paired read) are valid in a proposal — every read envelope says so in `capability.operation_companion_required_for_mutation`. Hand those back when proposing, and if the record moved in between, the proposal is refused as stale instead of overwriting someone's work.

* **Orientation**: `whoami`, `list_available_operations`, `describe_operations`, `operation_companion`, `get_project_posture`

* **Projects and controls**: `list_projects` / `get_project`, `list_sections` / `get_section`, `list_controls` / `get_control` (controls report their notes and whether notes and status are directly editable, so a register audit is one list call)

* **Evidence**: `list_evidence` / `get_evidence`, `list_collection_sources` / `get_collection_source`

* **Documents**: `list_documents` / `get_document`, `list_document_assignments`

* **Risks**: `list_risks` / `get_risk`, `get_risk_assessment_context`, `list_treatment_actions` / `get_treatment_action`

* **Checks**: `list_checks` / `get_check`, `list_check_acceptances` / `get_check_acceptance`

* **Vendors**: `list_vendors` / `get_vendor`, `list_assurance_reviews` / `get_assurance_review`

* **Business continuity**: `list_business_continuity_processes` / `get_business_continuity_process`, and the same pair for `plans` and `exercises`

* **Assets**: `list_physical_assets` / `get_physical_asset`, the same for `digital` and `infrastructure`, plus `list_asset_relationships` and `list_asset_reference_data`

* **ISMS workbook**: `get_isms_workbook`, `list_workbook_requirements` / `get_workbook_requirement`, `list_security_objectives` / `get_security_objective`, `list_management_reviews` / `get_management_review`, and `list_management_review_input_candidates`

* **People**: `list_people` / `get_person`, `list_employment_types` / `get_employment_type`

* **Reference vocabularies**: `list_data_classification_levels` (documents access), `list_risk_scoring_methods` / `get_risk_scoring_method` (risks access)

* **Pipeline**: `list_users`, `list_change_sets` / `get_change_set`, `list_staged_uploads` / `get_staged_upload`

Control reads report whether in-app AI implementation notes still await a person's approval, without returning the private notes. ISMS requirement reads report the same pending/approved state for AI-prefilled narratives; the requirement's ordinary reviewed state identifies the approving person and time. After approval, a manual edit makes the current text human-authored and reports `not_ai_generated`; only an explicit AI generation or regeneration creates a new pending approval.

### **Proposal tools (57)**

One focused tool per registered operation, named after it: `risk_assess`, `control_create`, `document_publish`, `evidence_create`, and so on. Each creates a one-item change set for review.

Every one takes the same envelope: an `idempotency_key` (reuse it to replay a submission safely rather than to make a second one), a `title` naming the *proposal* rather than the record, an optional `rationale`, the operation's own fields under `params`, the `observed_versions` the assistant read, and `source_refs` — where the information came from, `[]` for internal-only work. Project-scoped operations also require `compliance_project_id`.

42 of the 57 are **bulk-safe**: reversible bookkeeping a reviewer can approve together. The other 15 need **explicit judgment** and must be decided one at a time, because they are the moments where a person is asserting something to an auditor, or handing somebody authority over somebody else:

* `risk_assess`, `risk_update_treatment`, `risk_create_treatment_action`

* `control_update_implementation`

* `document_publish`

* `evidence_submit`

* `vendor_record_assurance_review`

* `check_acceptance_create`

* `asset_unlink_infrastructure`

* `isms_requirement_mark_reviewed`

* `isms_security_objective_create`, `isms_security_objective_update`, `isms_security_objective_record_review`

* `isms_management_review_complete`

* `people_person_set_manager`

Two of those go further. `document_publish` and `isms_management_review_complete` are the only tools that reach an action the server otherwise refuses outright, so neither is ever eligible for the per-token delegation described in [part 1](guide_mcp-server.md#letting-an-agent-approve-its-own-safe-changes): a signed-in person confirms the item, whatever the token is allowed to approve.

### **Lifecycle tools (3)**

* `submit_change_set` — propose several related changes as one set, with dependencies between items where one needs another to land first

* `cancel_change_set` — withdraw a proposal the assistant no longer stands behind

* `approve_change_set` — only usable when you have enabled the per-token delegation described in [part 1](guide_mcp-server.md#letting-an-agent-approve-its-own-safe-changes)

### **Upload tools (2)**

`request_upload_url` and `complete_staged_upload`. See [Attaching files](#attaching-files).

### **Feedback (1)**

`send_feedback` — the one tool that acts immediately. It emails product feedback — a bug, a missing capability, a place the assistant got stuck — to the AuditBadger team. It needs no scope, goes through no review, and changes nothing in your account.

### **Deprecated aliases (6)**

The six write tools from the old endpoint keep their names here so a migrating prompt does not simply break: `create_vendor`, `update_vendor`, `update_control`, `update_control_implementation`, `update_document_content`, and `record_vendor_assurance_review`.

They keep the name but not the behaviour — each one proposes for review like everything else, and each one's description names its replacement. `update_control_implementation` is the awkward case: it used to take implementation status and notes together, and those are now two operations, one of them a judgment call. Send one contract's fields or the other's; a request mixing them is refused rather than silently split.

Move off the aliases. They exist to make the migration legible, not to be a permanent second name for the same thing.

## **A worked example**

Suppose you ask an assistant: *"Our access-control policy has been in draft since the review. Check whether the control it covers is still marked unimplemented, and fix the notes."*

**1. It reads.**

`list_documents` finds the policy. `get_control` returns the control and its implementation notes. Then, because it intends to propose, the assistant calls `operation_companion` for `control.update_implementation_notes` on that control — the companion's `observed_versions` are the ones a proposal must carry (a foundation read's own `observed_version` fingerprints that read's projection and is refused as stale if submitted).

**2. It proposes.**

`control_update_implementation_notes` with:

* `idempotency_key`: a fresh UUID

* `title`: "Refresh access-control notes after policy review"

* `rationale`: what it concluded and why

* `params`: the new notes

* `observed_versions`: the versions the operation companion returned

* `source_refs`: a reference to the policy document it read

* `compliance_project_id`: the project

The receipt comes back with a `change_set_id`, state `proposed`, and a `review_path`. **Nothing has changed yet.**

Note what it did *not* do: implementation *status* is `control_update_implementation`, an explicit-judgment operation. An assistant that decides a control is implemented is asserting a compliance fact, so that comes to you as its own decision even inside a bigger set.

**3. You review.**

The change set appears in your review queue, and you get a notification. The item shows the before and after, the assistant's rationale, and the source it cites. You approve it, reject it with a note, or leave it — proposals expire on their own within 14 days by default.

**4. It polls.**

`get_change_set` returns the state and, per item, an outcome code and a `next_action` telling the assistant what to do: `await_review` (nothing to do yet), `await_apply` (decided, queued), `none` (done, or permanently refused), `repropose` (read again and try once more — this is what a stale version gets), or `retry`.

A set ends in `applied`, `partially_applied`, `rejected`, `failed`, `cancelled`, or `expired`. Per item, the application state is `applied` or one of `failed_precondition`, `failed_validation`, `failed_infrastructure`, `skipped_dependency_rejected`, `skipped_dependency_failed`.

**5. The audit log records it.**

Account Settings, Security, Audit Logs. The entry carries the `agent` channel, the token, the change set, and you as the approver.

### **A second example: onboarding a new employment type**

*"We are taking on contractors. Create a contractor employment type, require the handbook of it, and move Ada onto it."*

`list_employment_types` shows the type does not exist and `list_documents` finds the published handbook. Then three proposals, in order: `people_employment_type_create` with the name, `people_employment_type_link_documents` naming the handbook, and `people_person_set_employment_type` naming Ada and the new type. There is no `compliance_project_id` — these are account-scoped.

Approving the third one does what the settings screen does: Ada is asked to acknowledge every document her new employment type requires, and to sign every contract it carries, inside the same operation. The receipt tells you how many assignments that created. Withdrawing a requirement later, with `people_employment_type_unlink_documents`, does *not* take those obligations back — the tool says so in its output, because cancelling something somebody already owes is a separate decision a person makes on the web.

The assistant cannot invite Ada, change her role or email, set her joining or leaving date, deactivate her, or acknowledge a document on her behalf. Those have no tool at all.

## **Attaching files**

An assistant can attach a screenshot or a PDF to evidence, or a SOC 2 report to a vendor assurance review. The flow is four steps:

1. **`request_upload_url`** — the assistant declares the filename, content type, byte size, and SHA-256 checksum. It gets back a `staged_upload_id`, a `source_ref`, and a presigned form **POST** target: a `url`, a set of `fields`, and any `headers`. The capability is valid for **5 minutes**.

2. **POST the bytes** to that URL as a multipart form, including the returned fields. The presign is pinned to the exact byte size and content type declared, so the file that arrives is the file that was described.

3. **`complete_staged_upload`** — AuditBadger verifies the checksum, sniffs the real content type from the bytes rather than trusting the name, neutralizes the content, and seals it.

4. **Bind it** by passing `staged_upload_ids` on the proposal. Which operation an upload may be attached to is decided then, against that operation's own scopes.

**Allowed types**: PNG, JPEG, GIF, WebP, and PDF. Nothing else. This is stricter than ordinary evidence upload on purpose — every extra format is another parser. SVG is excluded despite being an image, because it is a document format that executes script.

**Size**: 10 MB per file, up to 10 uploads and 50 MB per change set.

**Content neutralization** happens on every file:

* A **PDF** is refused if its bytes carry a name that brings active content — script, open or page actions, launch actions, embedded files, rich media, XFA forms — or encryption, which could hide any of those. You get `upload_active_content`. Send a flattened PDF.

* An **image** is decoded and written back in the same format with every metadata block dropped: EXIF, ICC, XMP, comments, and anything appended after the image data. A rotated photo stays upright because orientation is applied before it is stripped; an animated image keeps its first frame. If re-encoding pushes it past 10 MB you get `upload_neutralized_too_large`.

Uploads are not scanned for malware today. Neutralization is what stands in for it, and it cannot see inside a compressed PDF object stream.

**Scopes**: an upload capability needs at least one `<module>:propose` scope for a module with an operation that accepts attachments — evidence and vendor assurance. A vendors-only token can upload the report it attaches; a controls-only token cannot upload at all, and gets `token_scope_missing`.

## **Limits and errors**

### **Rate limits**

* **100 requests per minute per token.** The bucket is keyed by a digest of the credential, never the credential itself.

* **150 bearer-carrying requests per minute per IP address**, across all tokens. This is what stops someone rotating bearer values to earn a fresh budget per guess. A team behind one shared egress address can reach it without any single token exceeding its own limit.

* **20 requests per minute per IP for requests with no bearer header at all.**

* **An IP that presents 30 rejected bearers within 10 minutes is banned for 30 minutes.** A client still polling with a token you revoked can walk into this; fix the configuration and wait it out.

Exceeding a throttle returns `429` with a `Retry-After` header.

### **Authentication**

Every rejection is the same `401` with `agent_token_required`. It does not tell you whether the subdomain exists, whether the token exists, or whether it was merely the wrong kind of credential — that is deliberate, so the endpoint cannot be used to enumerate accounts. A valid REST API token lands here too, and gets the same `401`: it authenticates elsewhere but is not valid for this surface.

Check, in order: the subdomain in the URL, the token's expiry, whether it was revoked, whether your account still has API access enabled, and whether you are still an active member.

### **Maintenance pauses**

If we pause the ingress for incident response, every request answers `503 headless_compliance_ingress_paused` with `retryable: true`, a `Retry-After: 60` header, and `retry_after_seconds: 60` in the body. That tells a client to come back in a minute rather than re-authenticate or give up. Work already approved keeps applying.

### **Error codes**

Errors are stable codes with a `retryable` flag and a `next_action`, so an assistant can act on them rather than parse prose. A refusal about the request itself also says where to look: `item_position` names the item (1-based), `field` is a JSON pointer into it (`/params/owner_id`, `/observed_versions`, `/source_refs/2`), and `detail` is one sentence about the rule it broke — so a 25-item set is fixed in one round rather than by bisection. Those three describe only what the assistant sent or what a read already returned to it; a domain refusal whose remedy is the assistant's — a subcontrol identifier that does not extend its parent, say — carries them too, and the rest stay a bare code. The ones you are most likely to see:

* `version_stale` / `version_required` — the record moved, or the assistant did not send what it observed. `next_action` is `repropose`: read again and try once more.

* `token_scope_missing` — the token lacks the module scope for that operation. Rotate with wider scopes, or narrow the task.

* `unauthorized` — the sponsor cannot perform that action on that record. The agent inherits your permissions; it does not exceed them.

* `idempotency_key_reused` — the same key with different content. A replay is fine; a second, different proposal needs a new key.

* `explicit_judgment_confirmation_required` — delegated approval was asked to approve a judgment call. Open the review queue.

* `proposal_limit_reached` — 20 open sets per token, 100 per account. Decide some.

* `control_cannot_hold_evidence` — evidence was proposed on a criterion rather than a subcontrol beneath it.

* `source_refs_invalid` — provenance was missing or malformed; `field` names the reference and `detail` the rule (`https` or `urn` without a query string, an explicitly zoned `observed_at`).

* `invalid_request` — the request broke a structural rule; `detail` says which: an unknown field, a wrong type, `compliance_project_id` present on an account-scoped set or missing on a project-scoped one, an item mixing scopes, a subcontrol identifier that is not the parent identifier plus a numeric suffix (`CC1.1.4` under `CC1.1`).

* `headless_compliance_ingress_unavailable` — the pause above, seen from inside a tool result.

### **Workload limits**

A single change set holds at most 25 items, with dependency chains at most 5 deep and 10 wide. Each item may carry 10 source references and 256 KB of serialized content; a set may carry 50 references and 1 MB. Titles are capped at 200 characters, rationales and decision notes at 5,000. Lists page 50 at a time, 100 at most. Proposals expire 14 days after submission by default, and never later than 30 days.

The assistant can read all of these from `whoami` rather than discovering them by failing. `whoami` also reports whether self-approval is enabled on the token, and both `whoami` and `describe_operations` give each operation its `scope_kind` (whether a set needs `compliance_project_id`) and `approval_policy` (`explicit_judgment` items are decided one by one by a person, so a set containing one is never approved through `approve_change_set`).

## **Notifications and webhooks**

Seven lifecycle events are published, and both channels use the same list, so a subscription and a notification can never disagree about what exists:

* `agent.change_set_proposed`

* `agent.change_set_decisions_finalized`

* `agent.change_set_applied`

* `agent.change_set_partially_applied`

* `agent.change_set_failed`

* `agent.change_set_cancelled`

* `agent.change_set_expired`

**Notifications** reach reviewers by **email** by default, linking straight to the review page. Each person tunes their own channels in notification preferences; in-app is selectable for the record it keeps, but nothing renders these in-app yet, so email is what a reviewer should rely on.

**Webhooks** are configured in Account Settings and can be account-wide or filtered to a single compliance project. Payloads carry the change set's identity, event, revision, state, item count, and review path — not the proposal's content, so a webhook can wake an external orchestrator without leaking what was proposed. Delivery is durable: it retries, and a redelivered event produces neither a second notification nor a second agent turn.

A webhook is the right way to build "propose overnight, review in the morning". Polling `get_change_set` works too, and is simpler if the assistant is already running.