> ## Documentation Index
> Fetch the complete documentation index at: https://docs.kentron.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Tools and permissions

> Control exactly which operations a connection exposes to agents, and understand the fail-closed rules that govern writes.

A connection is not a blanket grant. Every operation a connection publishes is controlled by an allowlist, and the rules around writes fail closed — when Receipt cannot prove an operation is a read, it treats it as a write.

## The dialog

Open a connected card and choose **Manage tools**. The dialog is titled **Manage tools and permissions**, and its description names the scope you are editing:

> `<Integration> · <connection label>. Every published operation is controlled by this <organization|workspace> allowlist.`

It has two sections, each with a count of the operations it holds:

* **Read operations** — "Enable only the data this workspace is allowed to retrieve." When there are none: "No read operations are published for this connection."
* **Write and delete operations** — "Fail closed by default. Enable only the operations this connection should perform." When there are none: "No write or delete operations are published. This is not a grant of unrestricted provider access."

<Frame caption="A Gmail connection in Global Integrations. Four read operations — get-message, get-profile, list-labels and search-messages — are each switched to Allowed, and the Write and delete section shows a count of 0 with the fail-closed notice.">
  <img src="https://mintcdn.com/kentronai-04cb7bb0/9j8LxitK-Dp6LuhI/images/mcp-gateway/manage-tools-gmail.png?fit=max&auto=format&n=9j8LxitK-Dp6LuhI&q=85&s=e9633aa6a48ff9cecc3ddcc0fcfb44d1" alt="The Manage tools and permissions dialog for a Gmail connection, listing four allowed read operations and no write operations." width="1200" height="904" data-path="images/mcp-gateway/manage-tools-gmail.png" />
</Frame>

Each row is a switch reading **Allowed** or **Never allow**. The footer is **Close** and **Save permissions** (**Saving** while it works), and a successful save toasts "Connection permissions saved." That toast is the only one the dialog raises.

A load or save that fails replaces the lists with a **Permissions unavailable** panel holding the message the server returned — or "Connection permissions could not be loaded." and "Connection permissions could not be saved." when the request itself throws. The panel offers **Retry**, or **Reconnect integration** when the connection needs reauthorization. If reauthorization is required and the catalog entry cannot start one, you get neither button and this line instead: "Reauthorization is required, but this catalog entry cannot start a new authorization session from Receipt."

If you are not a workspace owner or admin, the dialog still opens read-only with the banner: "You can review these permissions. Only workspace owners and admins can change access."

## How an operation is classified

Every discovered operation is classified `read` or `write` *before* anything runs, in this order:

<Steps>
  <Step title="Any mutating method wins">
    If any declared endpoint method is `POST`, `PUT`, `PATCH` or `DELETE`, the operation is a **write**.
  </Step>

  <Step title="A declared method, none of them mutating">
    If at least one endpoint method is declared and none of them mutate, the operation is a **read**.
  </Step>

  <Step title="No declared method at all — fail closed">
    Only a conventional read name counts as a read. The accepted prefixes are `get`, `list`, `search`, `find`, `read`, `fetch`, `lookup`, `whoami`, `who-am-i`, `check` and `describe`. Everything else is treated as a **write**.
  </Step>
</Steps>

For tools discovered from a provider's own MCP server, a `readOnlyHint` annotation is **authoritative** — whether it says true or false. The name heuristic applies only when that annotation is absent.

## Two gates on every write

A write executes only when **both** conditions hold. Either one failing stops the call.

**The token gate.** The caller's token must carry `connect:write`. Without it the call is refused with HTTP 400 and the message `integration action '<tool>' requires connect:write`.

**The allowlist gate.** The operation must be enabled in that connection's stored allowlist. A disabled operation is not callable no matter what the token carries.

<Check>
  A tool that does not exist and a tool that exists but is disabled return the **identical** response: HTTP 404 `integration action not found or disabled`. A caller cannot use error messages to probe for hidden tools.
</Check>

Saving a name that is not a published operation of that connection is rejected up front with `integration action '<name>' is not available`.

<Warning>
  **There is no per-action approval prompt in this release.** Write operations are off until a **workspace owner or admin** enables them for that connection — that switch, and the token scope, are the whole control. A caller whose token lacks `connect:write` is refused with HTTP 400 and `integration action '<tool>' requires connect:write`; nothing pauses a run to ask a person before a state-changing call. The **Tool approval** module on the [Policies page](/guard/policies) stores rules that no runtime path reads yet.
</Warning>

The token gate rarely stops anything on its own: a CLI session token carries `connect:write`, and a Factory objective's execution contract adds it whenever the task needs a Receipt Connect capability. The per-connection allowlist is the gate that decides.

## Version 1 and version 2 policies

The stored policy carries a schema version, and the two versions mean genuinely different things.

| Version                  | Meaning                                                                                                         |
| ------------------------ | --------------------------------------------------------------------------------------------------------------- |
| 1 — never edited         | Every operation classified as a **read** is enabled by default. Writes are enabled only when explicitly listed. |
| 2 — after any admin save | **Only** the listed operations are enabled, reads included.                                                     |

Any save from the dialog writes a version-2 policy. That is the transition to be careful about: the first save turns an implicit "reads are on" into an explicit list, so anything you did not tick is now off.

<Warning>
  An **empty version-2 list exposes nothing** — not even reads. It is a valid state, and it is what you get by saving with every switch off.
</Warning>

Web chat detects exactly that state and answers rather than failing silently:

> `<Provider>` is connected in Global Integrations, but you don't have access to any enabled `<Provider>` tools right now.

followed by a line telling you to open Integrations, choose that provider > **Manage tools**, enable the required tool, then retry this chat request.

## GitHub repository selection

A GitHub connection whose status is `valid` gets a second policy alongside the tool allowlist, shown as a **GitHub repositories** section: "Choose which repositories this connection can use." The selector offers **All repositories** ("Keep the current connection behavior.") and **Selected repositories** ("Limit Receipt to the repositories checked below."), with a `Search repositories…` box. The trigger reads `All repos`, `<n> repos`, or `Repositories`.

<Frame caption="The same dialog for a GitHub connection. The GitHub repositories section sits above the operation lists, and the single published read operation is read-provider-resource, the GET-only compatibility read tool this connection publishes.">
  <img src="https://mintcdn.com/kentronai-04cb7bb0/9j8LxitK-Dp6LuhI/images/mcp-gateway/manage-tools-github.png?fit=max&auto=format&n=9j8LxitK-Dp6LuhI&q=85&s=ca6e146645ba1f29f193df3aaf074e04" alt="The Manage tools and permissions dialog for a GitHub connection, showing a GitHub repositories section above one allowed read operation." width="1200" height="904" data-path="images/mcp-gateway/manage-tools-github.png" />
</Frame>

Enforcement under `selected` mode is real, not cosmetic:

| Request                                                | Result                                                                             |
| ------------------------------------------------------ | ---------------------------------------------------------------------------------- |
| `/user`                                                | Allowed — identity                                                                 |
| `/user/repos`, `/repositories`, `/orgs/{org}/repos`    | Allowed, but the response array is **filtered** to the selected repositories       |
| `/repos/{owner}/{repo}/…` for a selected repository    | Allowed                                                                            |
| `/repos/{owner}/{repo}/…` for an unselected repository | 403 `GitHub repository '<owner/repo>' is not selected for this connection`         |
| Any other GitHub path                                  | 403 `this GitHub endpoint is unavailable while selected repositories are enforced` |

Repository discovery pages at 100 repositories per page for up to 10 pages. If the tenth page comes back full, the list is marked truncated and the selector footer reads `showing the first 1,000 repositories`.

## Atlassian site resolution

For Atlassian connections, Receipt resolves the site itself and rewrites the requested path, unless you already asked for `/ex/<product>/…`. If the connection can reach more than one Atlassian site, the call fails explicitly rather than guessing: HTTP 409 `<Label> connection exposes multiple Atlassian sites; call the explicit /ex/<product>/{cloudId}/... path`. If it can reach none, the call fails with HTTP 502 `<Label> connection did not expose an accessible Atlassian site`. `<Label>` is the connector's catalog name and `<product>` its Atlassian product: the two connectors that resolve this way are `Jira OAuth` (`jira`) and `Confluence OAuth` (`confluence`).

## Who can save, and the size limits

Saving either policy requires workspace mutation authority: you are an owner or admin of that workspace, or an owner or admin of the organization. Everyone else gets the read-only dialog described above, and the server refuses the write independently with HTTP 403 `workspace_membership_required`.

| Save                        | Limit                                             |
| --------------------------- | ------------------------------------------------- |
| Tool allowlist              | 200 operation names, each 256 characters or fewer |
| GitHub repository selection | 1,000 repository ids, each 64 characters or fewer |

Anything larger is rejected with `enabledActions must be an array of action names` or `mode and repositoryIds must describe a valid GitHub repository selection`.

## What each call records

A tool call made through the REST route `POST /connect/call` writes a `tool.called` receipt and, on success, a `tool.observed` receipt, onto the per-workspace stream `receipt-connect/gateway/<orgId>/<workspaceId>`. Output is truncated at 2,000 characters with a truncation flag. A failed call records only `tool.called`, with its error — which is how outcomes are scored later. A call with no workspace in scope is not recorded at all.

<Note>
  Calls that arrive over the MCP protocol at `POST /connect/mcp` — the route [MCP clients and the `receipt mcp` bridge](/mcp-gateway/aggregate-server) use — are **not yet recorded as receipts**. The allowlist and scope gates above apply to them identically; only the receipt is missing.
</Note>

Next step: [publish these tools to an MCP client](/mcp-gateway/aggregate-server).
