Write tools and approvals
A write tool is any tool that changes data. OrgRelay never lets the AI run one directly: it creates an approval request, a person decides, and only then does your connector get the call, once, with a key that makes retries harmless.
The business side of approvals, who approves and what an owner sees on their phone, is described on orgrelay.com's approvals page. This page is what your connector has to do.
The flow
- A staff member asks for something that needs a write, for example "cancel order SO-1002".
- The AI picks your tool
orders.update_statuswith arguments. Because itsaccessiswrite, OrgRelay does not call you. It creates an approval request: which system, which tool, which arguments, who asked. - The owner or a designated approver approves or rejects it in OrgRelay's web app or mobile app.
- On approval, OrgRelay re-checks the requester's permissions as they are now. If their access was revoked in the meantime, the approval fails and you are never called.
- OrgRelay calls your tool with the approval id and an idempotency key.
- Your result, success or error, is written onto the approval request and shown to the requester.
| What OrgRelay sends | MCP | REST |
|---|---|---|
| Idempotency key | _meta["orgrelay/idempotency_key"] |
Idempotency-Key header |
| Approval id | _meta["orgrelay/approval_id"] |
X-OrgRelay-Approval-Id header |
| Who asked | _meta["orgrelay/actor"] |
X-OrgRelay-Actor header |
The key is always orgrelay-approval-<approval id>. Transports are described on MCP and REST.
What your connector must implement
1. Mark every data-changing tool as write
In the manifest, access: "write". Over MCP, readOnlyHint: false. This includes things that look harmless: adding a note to a customer, sending an email, creating a draft. If in doubt, mark it write.
2. Execute once per idempotency key
Repeated calls with the same key within 24 hours must execute once and return the first result.
const done = new Map(); // `${tenant}|${key}` -> { at, result }
function withIdempotency(tenant, key, args, fn) {
if (!key) return fn();
const k = `${tenant}|${key}`;
const prev = done.get(k);
if (prev) {
if (JSON.stringify(prev.args) !== JSON.stringify(args)) throw new ToolError('idempotency_conflict', 'Key reused with different arguments');
return prev.result;
}
const result = fn();
done.set(k, { at: Date.now(), args, result });
return result;
}In production, store keys in your database alongside the change, in the same transaction, and expire them after 24 hours. The in-memory map in the templates is only for demonstration.
3. Re-check on your side
Never skip a check because OrgRelay already approved. Between approval and execution:
- The record may have changed. An order approved for cancellation may have shipped. Return
conflictwith a message a person understands. - Stock may have moved. Validate quantities again.
- Your own permission model may be stricter than the token's scope. Return
forbidden.
4. Return a useful result
Write tools return at least ok, id and message. The message is shown to the requester and stored on the approval, so write it for a person: "SO-1002 cancelled, 3 items restocked", not "OK".
5. Log who approved
Log orgrelay/actor and the approval id with the change. Your system's audit trail then shows that Lin Wei asked and the owner approved, not just "API user".
Sandbox mode
When an owner adds a connection with Developer mode (sandbox), AI and staff cannot use it. The owner can call any tool by hand, including write tools, without an approval, and watch the call log. Point sandbox connections only at a test system. The quickstart walks through it.
Testing idempotency
The self-test tool with --write calls a write tool twice with one key and checks that the second call returns the first result. Run it only against test data.
node bin/orgrelay-connector-check.mjs https://erp-test.example/mcp \
--token <test-token> --write --sample 'inventory.adjust={"sku":"TEST-1","delta":1,"reason":"selftest"}'Why this design
Approvals in agent systems are often criticised as rubber-stamping. OrgRelay's answer is to make each approval specific (one tool, explicit arguments, the named requester), to re-check permissions at execution time, and to make the execution itself safe to retry. The wider design discussion is on human-in-the-loop for MCP.
Common questions
Can a write ever skip approval? Not for staff or the AI. Only sandbox connections, by the owner, by hand.
Same key, different arguments? Reject with idempotency_conflict.
Trust OrgRelay's permission check? No. Re-check permissions and state.
Next
- Security requirements for write tools that touch money or personal data.
- Events, so OrgRelay hears about the result of long-running writes.
- Standard tools that are defined as write.
- Platform examples: refunds on Stripe, invoices on QuickBooks Online, order status on WooCommerce.
- How an approval looks from the AI client side: MCP overview and Claude Code.
- Writes that start from a vendor's own MCP server: third-party MCP.
Related pages
- Authentication: tokens, OAuth client credentials and rotation.
- Enterprise patterns: permissions, offboarding and data residency.
- OpenAPI import: turn an OpenAPI or Swagger file into tools.