# Read-only ChatGPT access to your company apps

> Read-only ChatGPT access comes from a restriction rule on a role, not from a consent checkbox or a client setting that sounds like one.

**TL;DR** Read-only ChatGPT access in Elaichi comes from a restriction rule on a role that blocks the write and delete tools, not from a consent checkbox. ChatGPT's own permission settings and Elaichi's "Run your connected tools" scope both allow writes once a connected app is reachable. The audit log then proves which calls ran and what each one did.

## Where does read-only ChatGPT access actually get enforced?

Someone in finance asks for ChatGPT against the billing system. Reads are fine. A refund issued by a model that misread a thread is not. The instinct is to tell ChatGPT not to change anything, or to tick the setting that sounds like read-only. Neither holds. Prompt instructions are advice. The settings that look like a read-only switch mostly govern when the client asks before acting, not what it can reach. Elaichi serves one MCP address, and a restriction on a role blocks an app's write tools for every client calling through it.

Real read-only ChatGPT access comes from the tool layer. Elaichi serves MCP at one endpoint, POST https://api.elaichi.ai/mcp, the same address for every organization. MCP (Model Context Protocol) is the standard way an AI assistant calls tools in other apps. Four things decide whether a write can run through it. ChatGPT's own settings, the scopes a person ticks on Elaichi's consent screen, the restriction rules on their role, and the audit log that records what happened. Only one of those four actually stops a write.

| Layer | What it decides | What it does NOT decide | Where it applies |
|---|---|---|---|
| ChatGPT app settings | Whether an app is on for the workspace/role, how often ChatGPT asks before acting | Which operations the connected app will actually accept | ChatGPT only |
| Elaichi consent scopes | What one named client connection may attempt (read/write/destructive/tools) and which toolboxes it can reach | Whether a specific app's writes vs. reads are split out | That one OAuth grant, for that one client |
| Elaichi role restrictions | Which tools a role can execute, enforced server-side per call | Nothing above it; this is the enforcement point | Every client calling through Elaichi: ChatGPT, Claude, Cursor, Copilot, any MCP client |
| Elaichi audit log | What actually ran, classified as read/write/delete, after the fact | Attempts that were refused before execution (no row is written) | All connectors, one log per organization |

Only the restriction layer is a hard stop evaluated at call time. The other three are client-side prompts, scoping at grant time, or a record after the fact. The rest of this post walks each layer in the order a request passes through them.

## What do ChatGPT's own app settings control?

ChatGPT's settings control when it asks a person before acting, and which apps a workspace can use at all. They do not make a connected app read-only.

On ChatGPT Business, Enterprise and Edu, admins turn an app on or off for the workspace. Enterprise and Edu admins can also pick which apps a custom role may use (checked October 2026, [help.openai.com](https://help.openai.com/en/articles/11509118-admin-controls-security-and-compliance-for-plugins-and-apps)). Plugin permissions offer Always ask, Allow read actions, Allow low-risk actions and Allow all actions (checked October 2026, [help.openai.com](https://help.openai.com/en/articles/20001495-managing-app-permissions-in-chatgpt)). For write or modify actions, ChatGPT may ask for confirmation depending on the app permission and the action's context (checked October 2026, [help.openai.com](https://help.openai.com/en/articles/12584461-developer-mode-and-mcp-apps-in-chatgpt)). OpenAI can change these labels and defaults between releases. Treat the dated checks above as a snapshot, not a permanent spec, and re-verify against the linked pages before relying on them for a compliance write-up. The grain those controls stop at is covered in [how far ChatGPT Enterprise connector restrictions go](/blog/restrict-chatgpt-enterprise-connectors/).

The trap is the read-actions setting. To any MCP client, Elaichi is one server. Its tool list holds Elaichi's own `elaichi__` operations plus `search_tools`, `read_tool` and `execute_tool`, and `run_code` where Code Mode is on. No tool in that list names a connected app. The model finds a connected tool with `search_tools` and runs it with `read_tool` for a read or `execute_tool` for anything, or with `run_code` under Code Mode. Each search result says which of the two to use.

The annotations differ by tool. Elaichi marks `execute_tool` and `run_code` destructive and open-world. It marks `read_tool` read-only, idempotent and open-world, titled "Read from a connected app". So a client that runs tools marked read-only without asking can run a connected read through `read_tool` with no approval prompt. That same client still prompts for every connected-app call made through `execute_tool` and `run_code`, reads included. Whether a given client skips the prompt is that client's own setting, so check the vendor's page rather than assume it.

Elaichi does not rely on the hint. Before anything is sent, `read_tool` refuses any connected tool whose tier at call time is not a read. It also refuses a synthetic tool, an `elaichi__` operation, and a read whose arguments could send the connection's credentials to another address. `read_tool` is not a lower permission level: it meets the same gates and the same restrictions as `execute_tool`. None of this makes connected apps read-only. The role restrictions below do that.

A tool's own read-only label is not an enforcement point. MCP's ToolAnnotations include readOnlyHint and destructiveHint, and the specification says every annotation is a hint that is not guaranteed to describe the tool's behavior faithfully (checked October 2026, [modelcontextprotocol.io](https://modelcontextprotocol.io/specification/2025-06-18/schema)). The specification also tells clients to treat tool annotations as untrusted unless they come from trusted servers (checked October 2026, [modelcontextprotocol.io](https://modelcontextprotocol.io/specification/2025-06-18/server/tools)). That is why read-only has to be enforced where the call is checked, not where it is labeled.

There is a second limit. These settings apply to ChatGPT specifically. Claude, Cursor and Microsoft Copilot each ship their own equivalent of "ask before acting". Anthropic and Cursor document their own tool-permission prompts separately, and those prompts are configured in their own admin surfaces, not OpenAI's. Nothing set in the ChatGPT admin console reaches those clients, and nothing set in Claude's or Cursor's settings reaches ChatGPT. If a team uses more than one AI client against the same backend, per-client prompts give you as many inconsistent policies as there are clients. Use ChatGPT's settings for what they are: workspace availability, role availability, and how often a person is asked. Set the read-only boundary in Elaichi, once, and it holds across every client.

## How do Elaichi consent scopes change what a connection reaches?

The scopes a person ticks on Elaichi's consent screen decide what that one client connection may do. They do not split a connected app into reads and writes.

The screen draws a checkbox for each scope the client requested: "Read your organization's data" (mcp:read), "Create and change data" (mcp:write), "Delete data and remove access" (mcp:destructive), and "Run your connected tools" (mcp:tools). Every requested scope starts ticked except Delete, which is never pre-ticked. A person can untick any of them, and approving needs at least one.

| Scope label | Internal name | Actually governs |
|---|---|---|
| Read your organization's data | mcp:read | Elaichi's own read operations (listing toolboxes, members, etc.) |
| Create and change data | mcp:write | Elaichi's own write operations, such as creating a toolbox or sharing a connection |
| Delete data and remove access | mcp:destructive | Elaichi's own deletes; never pre-ticked |
| Run your connected tools | mcp:tools | Every tier of every connected app's tools: reads, writes, and deletes together |

The scope names are easy to misread. Three of the four sound like they describe the connected apps, and only one of them actually does. "Run your connected tools" covers every tier of connected tool: reads, writes and deletes in the apps themselves, as a single bundle. "Create and change data" governs Elaichi's own write operations, not a third-party app's update tools. Unticking it does not make a connected app read-only. "Delete data and remove access" covers Elaichi's own deletes, not a delete in Stripe or Notion.

There is one consent-time way to narrow the surface. When "Run your connected tools" is ticked, consent has a second step asking which toolboxes the client may run from: "All my tools", or "Only the ones I pick", up to 50 stored toolboxes. If someone picks a toolbox built only from read tools, that grant reaches only those tools, re-checked on every call. That works, but it depends on each person choosing correctly at consent time, and on somebody maintaining those toolboxes. A grant's scopes never change after consent either, so the only fix for a wrong scope is reconnecting. The administrator's control is the next layer, and it is the only one that does not depend on an individual getting consent right.

## Blocking write and delete tools with a role restriction

A restriction rule on a role is the mechanism that makes an app read-only for everyone who holds that role, whichever client they use.

In Elaichi, restriction targets are role or user only; the organization default is the absence of a rule, which means allow everything. A read-only posture for a group is written on the role those people hold. Within each layer, allow rules merge into one allowlist, block rules merge into one blocklist, and a block always beats an allow.

For read-only, blocks are usually the right shape. In Governance, Restrictions, New restriction, pick Applies to Role, choose the role, choose Block list, add the connector, then choose Specific tools. The picker offers three presets:

| Preset | Blocks | Reading stays on? |
|---|---|---|
| Block deleting | Every tool that deletes | Yes |
| Block all changes | Every tool that makes changes | Yes |
| Block everything | Every tool, reading too | No |

Be precise about what the presets do not do. Block deleting and Block all changes both leave reading on. Neither is a label for "safe tools". The post claims nothing further about how either preset sorts an app's tools. [Watch a single block rule on Stripe go in](/blog/block-a-risky-tool-for-a-whole-role/) if you want to see the picker end to end.

Two mechanics decide whether the rule does what you expect.

Start with the block-only shape. In Elaichi, a layer with only block rules has no allowlist, so everything it does not block passes it. If you hand-pick a few write tools instead of using the preset, every write tool you did not name stays reachable. On Microsoft Intune, for example, `msintune_managed_devices_execute_action` stays reachable unless a rule names it.

The opposite shape has its own trap. In Elaichi, once a role carries any allow rule, every connector and tool none of its allow rules names is blocked for that role. So a read-only allowlist on one app silently cuts the role off from every other app unless you also write whole-connector allow rules for each of them. [MCP tool allowlist vs blocklist](/blog/allowlist-vs-blocklist-mcp-tools/) walks through when each shape earns its keep.

A block binds the operation, not the label. When a rule is saved, Elaichi looks each named tool up in the organization's catalog and stores its operation beside the name. A block rule then matches by name or by operation. Renaming a tool in a connector cannot slip it past the block. A block on a connector also blocks every fork that declares descent from it.

The rule binds the tool, not the client. The same block applies whether the call arrives from ChatGPT, Claude, Cursor, Microsoft Copilot or any MCP client. Every connected-tool call Elaichi runs passes one restriction clamp, evaluated for the member the call acts as. That clamp covers a direct `tools/call`, `execute_tool`, `read_tool`, `run_code`, a toolbox execute and each step of a synthetic tool. It covers Elaichi's own connectors and native MCP connectors alike, where the vendor builds and runs the MCP server and Elaichi handles sign-in, access and audit.

One timing fact matters for your rollout plan. A restriction change takes effect about two minutes, not on the next call. Write the rule, wait, then test, and do not treat the first post-save call as proof either way.

A blocked tool is not invisible. A restricted tool is left out of the tools Elaichi offers a client and cannot be called. Search still names it, flagged restricted, with no schema. That is deliberate: the model can tell the user what to request instead of inventing a workaround.

## How do you prove from the audit log that no write ran?

Filter the audit log to the connector and the window, then check that every entry's "What it did" line reads Read data. The audit log is where read-only stops being a configuration claim and becomes evidence, because every connected-tool call that reaches execution writes a row, whatever client sent it.

Elaichi writes one entry for each connected-tool call that reaches execution (a call refused earlier writes none). For a call from an MCP client, the row also carries the client's id. In the console, an entry's detail panel shows a "What it did" line reading Read data, Changed data or Deleted data, taken from that recorded classification. A call from an MCP client reads "Allowed by the access ChatGPT was granted". A read that needed no approval reads "Didn't need approval because it only read data".

So the review is short. Filter the window, open the connector's rows, and check that every "What it did" line says Read data. One organization has one audit log across every connector, so the same pass covers Claude and Cursor traffic without a second query.

Be honest about the edges when you hand this to an auditor. A call refused before execution, by a restriction or a missing scope, writes no row. The log shows the absence of writes rather than a list of blocked attempts. If you need proof of attempted-and-blocked writes specifically, that evidence has to come from the restriction configuration itself. The console's "All time" range sends no start time and covers at most the last 400 days, and Elaichi keeps 90 days of audit history. For a longer history, forward it to your own log service: export to your own Datadog comes with the Black plan. Connected-tool calls sit in the audit log's `assistant` category, not the `mcp` one. [What an AI agent audit log must capture](/blog/what-an-ai-audit-log-must-capture/) covers the fields in more depth.

## When is read-only too blunt, and approvals a better fit?

Read-only is too blunt the moment the job involves producing something. Think of a support agent who can read Zendesk but never draft a reply, or a recruiter who can read Greenhouse but never leave a note. For those people, a blanket write block pushes the work back into copy and paste. That is the behavior you were trying to replace.

Three narrower shapes usually fit better, in roughly the order to reach for them:

1. **Block only the deletes.** Use the Block deleting preset, which blocks every tool that deletes and leaves reading on. Most of the real risk in a connected app is irreversible, and creates are generally not. Default to this unless you have a specific reason not to.
2. **Block the few tools that cross a line, and leave the rest.** On Stripe, block the one tool that creates a coupon rather than every tool that changes data. [Restrict one AI tool or the whole app?](/blog/per-tool-vs-per-app-restrictions/) works through the six cases.
3. **Lock the dangerous argument instead of the tool.** A toolbox entry can carry frozen parameters: values written into the entry that a caller cannot override. The AI can see that a value is set, and often what it is, but cannot change it on a call through that entry. Note the scope carefully. A freeze holds only on calls that run through that entry, so anyone who can use the underlying connection also has the unfrozen tool through their All tools toolbox. The way to close that is to keep the connection unshared and share the toolbox instead.

What Elaichi does not give you over MCP is a per-call approval. The OAuth grant is the approval. The person chose those scopes on Elaichi's own consent screen for that named client, and Elaichi runs every call that passes the scope check as already approved. Any per-call confirmation a person sees in ChatGPT is ChatGPT's own prompt. OpenAI says it may ask for confirmation before a write or modify action depending on the app permission and context (checked October 2026, [help.openai.com](https://help.openai.com/en/articles/12584461-developer-mode-and-mcp-apps-in-chatgpt)). If your control has to be a human saying yes to each action, that control lives in the client, and you should read what the vendor documents rather than assume Elaichi supplies it. [Human approval for AI agent actions](/blog/human-approval-for-ai-agent-actions/) lays out which layer owns which part.

Finally, the case where you need none of this. If one person wants ChatGPT against one app they already administer, connect the vendor's own MCP server and skip the control plane. Read-only rules across a catalog earn their keep when several teams, several apps and more than one AI client are involved, and when somebody has to prove next quarter that nothing was written. That is the argument in [when you don't need an MCP gateway yet](/blog/when-you-dont-need-an-mcp-gateway/).

For the setup that comes before any of this, see [Connect Elaichi to ChatGPT](/blog/connect-elaichi-to-chatgpt/), and browse what the role rules apply to in the [connector catalog](/connectors/). If the same question comes up for Claude, [approving apps for Claude across a company](/blog/approve-apps-employees-connect-to-claude/) covers the two places that decision lives.

## FAQ

### Does unticking "Create and change data" on Elaichi's consent screen make a connected app read-only?

No. On Elaichi's consent screen, "Create and change data" (mcp:write) governs Elaichi's own write operations, such as creating a toolbox or sharing a connection. A connected app's create and update tools run under "Run your connected tools" (mcp:tools), which covers every tier including deletes in the connected app. To hold a client to reads, either pick toolboxes of read-only tools at consent under "Only the ones I pick", or block the write and delete tools with a restriction rule on the person's role.

### Can ChatGPT's "Allow read actions" setting make Elaichi's connected apps read-only?

No. To any MCP client, Elaichi is one server whose tool list holds its own elaichi__ operations plus search_tools, read_tool and execute_tool, and run_code where Code Mode is on. Elaichi marks execute_tool and run_code destructive and open-world, and read_tool read-only, idempotent and open-world. So a client that runs read-only tools unprompted can run a connected read through read_tool without an approval prompt, and still prompts for every call made through execute_tool and run_code, reads included. Read-only access to the app itself comes from a restriction rule on the role, set in Elaichi.

### How long does a restriction change take to apply in Elaichi?

A role, team, restriction or access-grant change takes effect about two minutes across every surface, including the MCP endpoint, the console and the REST API. It is not immediate. In Elaichi, removing or suspending a member revokes every live grant in the same transaction as the membership change.

### Can an admin make every role read-only at once?

No. In Elaichi, restriction targets are role or user only; the organization default is the absence of a rule, which means allow everything. The same block rules therefore go on each role separately, and a person on a role without the rule still reaches the write tools. Within each layer, allow rules merge into one allowlist, block rules merge into one blocklist, and a block always beats an allow.

### Can an admin see every write a connected AI client made across ChatGPT, Claude and Cursor in one place?

Yes. An organization has one audit log across all its connectors. Elaichi writes one entry for each connected-tool call that reaches execution (a call refused earlier writes none). In the console, each entry's detail panel shows a "What it did" line reading Read data, Changed data or Deleted data. Calls refused before execution, such as a tool a restriction withholds, write no row.

### What is the difference between a block rule and an allow rule for read-only access?

A block rule naming an app's write and delete tools leaves everything it does not block reachable, so a hand-picked list can miss a write tool. In Elaichi, once a role carries any allow rule, every connector and tool none of its allow rules names is blocked for that role. A read-only allowlist on one app therefore also needs whole-connector allow rules for every other app that role uses.

## Read next

- [How to restrict ChatGPT Enterprise connectors](/blog/restrict-chatgpt-enterprise-connectors/) — You can restrict ChatGPT Enterprise connectors per app, and per group on Enterprise and Edu. Here is exactly where that grain stops.
- [Video: Block a Risky Tool for a Whole Role, Once](/blog/block-a-risky-tool-for-a-whole-role/) — Block a risky tool for a whole role: one Elaichi restriction on Stripe's Create a coupon, set once for the Member role, holds in every AI app your team uses.
- [How to approve apps for Claude across a company](/blog/approve-apps-employees-connect-to-claude/) — There are two places you approve apps for Claude: Anthropic's connector controls, and the restriction rules behind your one MCP endpoint.
