Give an AI agent access to someone's calendar safely: scopes, confirmation and prompt injection
Updated
TL;DR
An agent with calendar access can send real invitations, cancel meetings for every attendee, and read text that anyone can put on a calendar by sending an invitation, which is a known prompt-injection route. Give it the narrowest scope that does the job, keep a human approving every change, mark tools as read-only or destructive so the host can ask first, let it practice on test data, and make every write check that the event hasn't changed since the agent read it.
What can go wrong
A calendar looks harmless next to email or files, but an agent with write access acts on other people's time:
- Invitations go out. On Outlook, "When you create an event that includes attendees, the server sends invitations to all attendees ... and can't be configured", and deleting an organizer's event "sends a cancellation message to the meeting attendees." On Google,
sendUpdatesdecides whether guests are emailed. An agent that "tidies up" a calendar can cancel meetings for dozens of people. - Anyone can write to the calendar. Inviting someone puts your text on their calendar. Security researchers showed in 2025 that they could "hijack a remote victim's Gemini agents simply by sending them a Google calendar invite" with "a prompt injection in the subject or title" (SafeBreach). OWASP lists this as indirect prompt injection: an LLM that "accepts input from external sources" (LLM01).
- Edits collide. An agent that read an event a minute ago can overwrite a change a person just made.
Narrow the scope
Google's advice: "Generally, you should choose the most narrowly focused scope possible." For an agent that only answers questions, calendar.events.readonly or calendar.freebusy is enough; calendar.events.owned limits writes to calendars the user owns. On Microsoft Graph, Calendars.ReadBasic reads events "except for properties such as body, attachments, and extensions", which also keeps the longest free-text field, where injected instructions usually sit, away from the model. Any create, update or delete needs Calendars.ReadWrite.
OWASP's Excessive Agency entry gives the general rule: "Limit the permissions that LLM extensions are granted to other systems to the minimum necessary."
Keep a human approving changes
The Model Context Protocol says "there SHOULD always be a human in the loop with the ability to deny tool invocations", and that clients should "Prompt for user confirmation on sensitive operations" and "Show tool inputs to the user before calling the server" (MCP tools). Hosts decide when to ask based on each tool's annotations: readOnlyHint for tools that change nothing, destructiveHint for ones that delete. A server that marks its tools accurately lets Claude, Cursor or VS Code ask before every change and skip the prompt for reads. The MCP guide covers the annotations in detail.
Treat calendar text as data
Instructions in a title, location or description are someone else's words, not your user's. OWASP's mitigations apply directly: "Separate and clearly denote untrusted content", and require "a human to approve high-impact actions." Give the agent summaries of events (time, title, attendees) when that is enough, and fetch full descriptions only when the task needs them.
Practice on fake calendars
Let the agent, and the developer building it, run against test data before anything touches a real person's calendar. A sandbox catches the agent that deletes the wrong series, or invites the whole team, while it can do no harm.
Check versions on every write
Have the agent send the version of the event it read with every update or delete. If someone changed the event since, the write is refused and the agent reads it again, instead of overwriting a person's change.
How Pyas does it
The Pyas MCP server and @pyas/ai-tools are built around these controls. Pyas runs no AI models itself; your agent does the reasoning.
- Every tool is marked. Reads carry
readOnlyHint, writes are marked as changes, anddelete_eventas destructive, so the host can ask before anything changes. - A read-only mode. Start the server with
--read-onlyand it offers only tools that change nothing. - Test keys reach only the sandbox. An agent on a test key works on simulated Google, Outlook and Zoom accounts and can never reach a live account.
- Separate addresses for test and live. On the hosted server, the sandbox has its own address,
mcp.pyas.io/sandbox, and live calendars are atmcp.pyas.io. A tool connected to the sandbox can't be switched to real calendars by anything it reads; that takes adding the other address and signing in again. - A warning with live keys. On a live key, the server tells the agent that changes affect real people's calendars and send real invitations, and to confirm with the user before creating, updating or deleting anything.
- Each change confirmed, on the hosted server. When a tool works on real calendars through the hosted server, every change waits until the person who connected it confirms it on a Pyas page. They can let it go ahead without asking for the next hour, and no longer.
- Summaries by default.
list_eventsreturns events without their descriptions; the full event is a separateget_eventcall. - Version checks.
update_eventtakes the event's version, and a stale one is refused instead of overwriting someone's change. - One person's calendars only. With
@pyas/ai-tools, bind the tools to one of your users withsubjectId, so an agent acting for them can't reach anyone else's accounts.
Try it with a test key in the sandbox.