Give an AI agent access only to the sources and read operations its task needs, and enforce those limits at the tool server and upstream system. Treat tools without an explicit read-only classification as writes, then require a separate grant for mutations. Read-only access still exposes data, so narrow the readable records and fields as carefully as the writable operations.
Define what the agent needs to inspect
An agent investigating a delayed support ticket needs ticket status, recent timestamps and the relevant runbook. It does not need permission to close tickets, change customer records or read every support attachment. Write that distinction into the access request before connecting the agent: which source, which records, which fields and which operations answer this question?
Separate those dimensions. A key scoped to the ticketing source can still be too powerful if it permits every ticket operation. A read-only key can still disclose too much if it reaches customer messages across every account. For this investigation, a useful starting policy is status fields from the support source and a selected runbook, with no ticket mutations or unrelated sources.
Keep the investigation identity separate from the identity that applies a fix. If the agent concludes that a ticket should be reassigned, its initial deliverable can be the proposed reassignment and supporting evidence. Reassignment authority becomes a deliberate addition to that workflow.
Treat an unmarked MCP tool as a write
MCP's tool-annotation explainer documents readOnlyHint with a default of false. It also distinguishes non-destructive changes from reads: destructiveHint: false can describe an additive update. Creating a ticket is still a write even when it deletes nothing. Idempotence describes repeated effects; it does not establish that an operation is read-only.
Use this conservative admission rule: only an explicitly read-only tool from a reviewed server is eligible for the read grant. Missing annotations keep the tool in the write category until someone verifies its behavior. Do not infer permission from names such as preview or get_report; review whether the implementation creates jobs, sends notifications or changes business state.
That rule is your authorization policy, not an enforcement guarantee supplied by MCP. The tools specification says annotations from untrusted servers must be treated as untrusted and requires servers to implement access controls. Check authorization when executing a call, even if the client previously listed the tool. Removing a button or hiding a tool description cannot substitute for rejecting an unauthorized invocation.
When a server upgrade adds an unmarked operation, keep it outside the read grant. A larger catalog should not silently enlarge the investigation identity's authority.
Make the upstream credential match the tool policy
A read-only tool backed by an application owner's database credential leaves a large enforcement burden in the adapter. Give the adapter a dedicated database identity with only the necessary privileges. PostgreSQL's GRANT reference supports separate database, schema and column privileges, so the access boundary can be narrower than an entire table.
For an existing agent_reader login and an existing reporting table, an administrator can grant the following. Substitute your database, schema and table names. The role must be a non-owner, non-superuser identity whose existing grants and memberships have already been reviewed.
GRANT CONNECT ON DATABASE appdb TO agent_reader;
GRANT USAGE ON SCHEMA agent_reporting TO agent_reader;
GRANT SELECT (ticket_id, status, updated_at)
ON TABLE agent_reporting.ticket_status
TO agent_reader;Using that identity, the adapter can retrieve a bounded result:
SELECT ticket_id, status, updated_at
FROM agent_reporting.ticket_status
WHERE status = 'open'
ORDER BY updated_at DESC
LIMIT 25;These grants add permissions; they do not remove broader access already available through other memberships or PUBLIC. PostgreSQL's privileges documentation also describes default public function execution privileges. Review callable routines and object ownership rather than treating a statement beginning with SELECT as proof that nothing can change.
The example restricts columns, not customer rows. Its WHERE clause is query logic the caller can change. Enforce account boundaries separately before offering arbitrary queries. For implementation details, use the guide to read-only Postgres access for agents.
A GET request can still disclose the wrong data
For a REST adapter, explicitly allow the retrieval operations the investigation requires. RFC 9110 defines GET as a safe method, meaning its requested semantics are essentially read-only. It also explains that safe requests can have incidental side effects and requires resource owners to prevent unsafe actions selected through a GET URL. Audit an endpoint's behavior before admitting it to a read-only catalog.
Restrict the destination as well as the method. A tool that accepts an arbitrary URL is a different capability from a tool that retrieves one ticket from a fixed service. Prefer validated ticket identifiers and an account boundary enforced by the service. Keep authentication and destination selection outside model-supplied arguments.
Read permission also determines what enters the agent's context. For the delayed-ticket investigation, omit payment details, authentication fields and unrelated message bodies. Review the agent's other tools and network access: a narrow ticket reader does not prevent the agent from sending readable content through another available channel.
Grant a specific mutation when the task changes
Suppose the investigation identifies the wrong assignee. Define the new authority as reassignment of a particular ticket within the permitted account. Avoid turning that request into unrestricted ticket administration or a permanent upgrade of the investigation key. Where your infrastructure supports it, use a separate writer identity limited to that operation and working period.
Put the proposed change in front of the operator: ticket identifier, current assignee, proposed assignee and reason. Recheck permissions at execution. Approval of the proposal and authorization to invoke the upstream operation answer different questions; the workflow needs both.
Include less obvious mutations in this review. Posting an investigation summary, storing a reusable memory and editing a shared skill all persist changes. Reading a runbook does not justify rewriting it. Keep those permissions separate so an investigation can finish with a useful answer even when publishing and memory updates remain unavailable.
For deployments with several agents, assign a scoped key to each agent so one approved writer does not expand the whole fleet's authority.
Enforcing separate grants with SPYN3
SPYN3 lets admins scope an agent key to chosen sources. Its call:tools grant reaches only tools the upstream marks read-only; unmarked tools count as writes and require call:write_tools. Memory writes separately require write:memory. Upstream credentials stay in the workspace with envelope encryption; the agent holds a SPYN3 key or OAuth token. list_toolsets reports withheld tools, and revocation takes effect on the next call. Start with the investigation's sources and read grant, then add write authority only for the approved task.
Prove the denied paths before rollout
Use a disposable ticket and test identities to check the intended boundary directly. A successful status lookup proves only that one permitted path works. Exercise operations the investigation identity must never perform, including calls made directly rather than selected through the agent's interface.
- Read an allowed ticket's status and verify that the response contains only approved fields.
- Request a ticket outside the permitted account and verify that the server denies access or returns no unauthorized record.
- Invoke reassignment with the read identity and verify rejection before the ticket changes.
- Introduce an unmarked test tool and verify that the read grant does not admit it.
- Try a write through the adapter's database identity and inspect effective permissions if it succeeds.
- Revoke the agent credential, then verify that a subsequent call fails.
Record which identity attempted each operation, the target and the authorization result. Re-run the relevant checks when tool implementations, upstream grants or account boundaries change. If a read task fails because it genuinely needs another field, add that field deliberately; do not replace the scoped identity with an administrator credential to make the error disappear.
Built for the next step
Put the boundary in one place
One endpoint holds the credentials, scopes and audit log, so agents only ever hold a revocable key.
Read the developer guideSPYN3 is a consolidated MCP layer: one hosted endpoint in front of every system you connect — MCP servers, Postgres, REST APIs, Google Drive — with tools foldered by source and read/write permissions per key. Switching agents means moving one URL. Free plan available; current capabilities are documented on the product facts page.
Sources
- MCP project: tool annotations — risk hints and their enforcement limits. Verified 27 Oct 2026
- MCP specification: tools — tool discovery, invocation and security considerations. Verified 27 Oct 2026
- PostgreSQL documentation: GRANT — object privileges including SELECT and schema-level grants. Verified 27 Oct 2026
- PostgreSQL documentation: privileges — privilege types, revocation and read-only table access. Verified 27 Oct 2026
- RFC 9110: HTTP Semantics — safe methods, GET semantics and automated retrieval constraints. Verified 27 Oct 2026
