Problem
Enabling a service currently grants MCP clients access to all tools in that service. A user who enables Contacts to let an AI search their contacts also implicitly allows contacts_create and contacts_update. A user who enables Calendar for reading events also allows events_create.
This is a wider permission surface than necessary for many use cases, and it's inconsistent with how macOS itself handles these APIs — EventKit distinguishes .fullAccess from .writeOnly (macOS 14+), and Contacts separates read from write authorization.
The annotations are already there
iMCP already sets readOnlyHint: true on read tools and readOnlyHint: false (or nil) on write tools. The classification is already done in the codebase:
| Service |
Read tools |
Write tools |
| Calendar |
calendars_list, events_fetch |
events_create |
| Contacts |
contacts_me, contacts_search |
contacts_create, contacts_update |
| Reminders |
reminders_lists, reminders_fetch |
reminders_create |
| Shortcuts |
shortcuts_list |
shortcuts_run |
| Capture, Location, Maps, Messages, Weather, Utilities |
All |
None |
Suggested approach
Replace the current boolean service toggle with a three-state control — Off / Read Only / Read & Write — and filter tools based on the selection:
- In the
ListTools handler, if a service is set to read-only, only include tools where readOnlyHint == true
- In the
CallTool handler, reject write tools with a message pointing the user to settings
- Connected clients already get notified via
notifyToolListChanged() when services change, so they'd pick up the new tool list automatically
For the UI, the existing service icon buttons could cycle through three states (gray → muted color → full color) or use a small segmented picker. A pencil badge or similar indicator could distinguish read from read+write.
Backwards compatibility: existing users who have a service enabled could be migrated to "Read & Write" to preserve their current behavior. New installs could default to "Off" or "Read Only" depending on the service.
This builds directly on top of the annotations already in the codebase and follows Apple's own convention of separating read from write access at the permission level.
Problem
Enabling a service currently grants MCP clients access to all tools in that service. A user who enables Contacts to let an AI search their contacts also implicitly allows
contacts_createandcontacts_update. A user who enables Calendar for reading events also allowsevents_create.This is a wider permission surface than necessary for many use cases, and it's inconsistent with how macOS itself handles these APIs — EventKit distinguishes
.fullAccessfrom.writeOnly(macOS 14+), and Contacts separates read from write authorization.The annotations are already there
iMCP already sets
readOnlyHint: trueon read tools andreadOnlyHint: false(or nil) on write tools. The classification is already done in the codebase:calendars_list,events_fetchevents_createcontacts_me,contacts_searchcontacts_create,contacts_updatereminders_lists,reminders_fetchreminders_createshortcuts_listshortcuts_runSuggested approach
Replace the current boolean service toggle with a three-state control — Off / Read Only / Read & Write — and filter tools based on the selection:
ListToolshandler, if a service is set to read-only, only include tools wherereadOnlyHint == trueCallToolhandler, reject write tools with a message pointing the user to settingsnotifyToolListChanged()when services change, so they'd pick up the new tool list automaticallyFor the UI, the existing service icon buttons could cycle through three states (gray → muted color → full color) or use a small segmented picker. A pencil badge or similar indicator could distinguish read from read+write.
Backwards compatibility: existing users who have a service enabled could be migrated to "Read & Write" to preserve their current behavior. New installs could default to "Off" or "Read Only" depending on the service.
This builds directly on top of the annotations already in the codebase and follows Apple's own convention of separating read from write access at the permission level.