mcp-icloud 2.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/DISCLOSURE +35 -0
- package/LICENSE +21 -0
- package/README.md +244 -0
- package/auth/index.js +175 -0
- package/calendar/caldav-client.js +320 -0
- package/calendar/index.js +252 -0
- package/calendar/local-client.js +223 -0
- package/config.js +69 -0
- package/contacts/carddav-client.js +331 -0
- package/contacts/index.js +350 -0
- package/contacts/local-client.js +372 -0
- package/email/imap-client.js +351 -0
- package/email/index.js +278 -0
- package/email/local-client.js +257 -0
- package/email/smtp-client.js +79 -0
- package/index.js +123 -0
- package/messages/index.js +157 -0
- package/messages/local-client.js +74 -0
- package/mode.js +82 -0
- package/notes/index.js +162 -0
- package/notes/local-client.js +203 -0
- package/package.json +76 -0
- package/reminders/index.js +215 -0
- package/reminders/local-client.js +269 -0
- package/safari/index.js +122 -0
- package/safari/local-client.js +156 -0
- package/utils/applescript.js +214 -0
- package/utils/date-utils.js +72 -0
- package/utils/error-handler.js +71 -0
- package/utils/schemas.js +33 -0
package/DISCLOSURE
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
Dual-use disclosure for mcp-icloud
|
|
2
|
+
===================================
|
|
3
|
+
|
|
4
|
+
mcp-icloud is a Model Context Protocol (MCP) server that lets an AI assistant
|
|
5
|
+
(e.g. Claude Desktop) operate the USER'S OWN Apple services on the USER'S OWN
|
|
6
|
+
machine, at the user's request. It is the Apple-services equivalent of the
|
|
7
|
+
official MCP reference servers (filesystem, github, etc.).
|
|
8
|
+
|
|
9
|
+
Why this package can look like malware to automated scanning
|
|
10
|
+
------------------------------------------------------------
|
|
11
|
+
- It reads and sends email, reads contacts, calendars, reminders, notes and
|
|
12
|
+
iMessage conversations, and controls Safari — via AppleScript (local mode)
|
|
13
|
+
or the user's iCloud account over IMAP/CalDAV/CardDAV (cloud mode).
|
|
14
|
+
- It executes osascript as a child process to drive macOS applications.
|
|
15
|
+
- Cloud mode authenticates with an app-specific password supplied by the user.
|
|
16
|
+
|
|
17
|
+
Why this is legitimate dual-use functionality
|
|
18
|
+
---------------------------------------------
|
|
19
|
+
- The server only runs when the user installs and starts it, and only talks
|
|
20
|
+
over stdio to the MCP client the user connected it to. There is no network
|
|
21
|
+
listener, no telemetry, no analytics, and no data leaves the machine except
|
|
22
|
+
toward Apple's own servers (iCloud) on the user's behalf.
|
|
23
|
+
- On macOS, every application the server touches triggers the operating
|
|
24
|
+
system's TCC consent prompts (System Settings > Privacy & Security >
|
|
25
|
+
Automation); nothing is accessible until the user grants it.
|
|
26
|
+
- Credentials are supplied by the user via a local .env file or the MCP
|
|
27
|
+
bundle's OS credential store, are read once at startup, and are never
|
|
28
|
+
logged. Every tool input is schema-validated (zod) before execution, and
|
|
29
|
+
values interpolated into AppleScript are coerced and escaped.
|
|
30
|
+
- Full source: https://github.com/MrGo2/icloud-mcp (MIT license, tests and
|
|
31
|
+
CI public).
|
|
32
|
+
|
|
33
|
+
Intended users: individuals connecting an AI assistant to their own Apple
|
|
34
|
+
account and macOS applications. The package provides no capability against
|
|
35
|
+
third parties that the operating system's own consent model does not gate.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Carlos Lorenzo
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
<picture>
|
|
2
|
+
<source media="(prefers-color-scheme: dark)" srcset="assets/banner-dark.svg">
|
|
3
|
+
<img src="assets/banner-light.svg" alt="icloud-mcp banner: Mail, Calendar, Contacts, Reminders, Notes, Messages and Safari for your AI assistant">
|
|
4
|
+
</picture>
|
|
5
|
+
|
|
6
|
+
<div align="center">
|
|
7
|
+
|
|
8
|
+
[](https://github.com/MrGo2/icloud-mcp/actions/workflows/ci.yml)
|
|
9
|
+
[](https://github.com/MrGo2/icloud-mcp/releases/latest)
|
|
10
|
+
[](LICENSE)
|
|
11
|
+
[](package.json)
|
|
12
|
+
[](https://modelcontextprotocol.io)
|
|
13
|
+
|
|
14
|
+
</div>
|
|
15
|
+
|
|
16
|
+
icloud-mcp covers seven Apple services with two interchangeable backends, and as far as we know it is the only MCP server that does. In local mode it drives the native macOS apps through AppleScript: no credentials, no network, and it reaches the services the iCloud protocols do not expose (Reminders, Notes, Messages, Safari). In cloud mode it speaks IMAP/SMTP, CalDAV and CardDAV instead, so it runs on any machine, not just a Mac. The `set-mode` tool switches between the two at runtime, without a restart.
|
|
17
|
+
|
|
18
|
+
[](https://insiders.vscode.dev/redirect/mcp/install?name=icloud&config=%7B%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22mcp-icloud%22%5D%7D)
|
|
19
|
+
|
|
20
|
+
## Features
|
|
21
|
+
|
|
22
|
+
The server exposes 41 tools across Email, Calendar, Contacts, Reminders, Notes, Messages and Safari. Each tool carries a typed schema, a human title and behavioural annotations, and every `list-*` tool returns machine-readable `structuredContent` alongside its text output. Local mode needs no passwords at all; macOS Automation permissions take their place.
|
|
23
|
+
|
|
24
|
+
## Access and security model
|
|
25
|
+
|
|
26
|
+
The server speaks JSON-RPC over stdin/stdout only. It opens no ports and listens on no socket.
|
|
27
|
+
|
|
28
|
+
Credentials are never logged. `ICLOUD_APP_PASSWORD` is read once at startup and handed only to the IMAP/SMTP/CalDAV/CardDAV clients; diagnostics print a fixed mask, never the value. Where you keep the password is up to you: a `.env` file beside the module for a manual install, or the `.mcpb` bundle's sensitive field, which the host stores in the macOS Keychain. Local mode has no password anywhere, since macOS gates access per app through its own Automation prompts, and you can revoke those at any time.
|
|
29
|
+
|
|
30
|
+
Every tool argument is validated against a zod schema before the handler runs. Arguments are also coerced at each AppleScript interpolation site, so a value that is not a number cannot reach a script template.
|
|
31
|
+
|
|
32
|
+
## Tools
|
|
33
|
+
|
|
34
|
+
Tools marked local only return an error in cloud mode, because the iCloud protocols do not expose those services.
|
|
35
|
+
|
|
36
|
+
### Authentication
|
|
37
|
+
|
|
38
|
+
- **about**
|
|
39
|
+
- Returns information about this server, the active mode and whether credentials are configured
|
|
40
|
+
- No input
|
|
41
|
+
- **check-auth-status**
|
|
42
|
+
- Verifies credentials are usable for the active mode
|
|
43
|
+
- No input
|
|
44
|
+
- **set-mode**
|
|
45
|
+
- Switches between local and cloud without restarting
|
|
46
|
+
- Input: `mode` (string, `local` or `cloud`)
|
|
47
|
+
|
|
48
|
+
### Email
|
|
49
|
+
|
|
50
|
+
- **list-emails**: `folder` (string, optional), `count` (number, optional, max 50)
|
|
51
|
+
- **read-email**: `uid` (string), `folder` (string, optional)
|
|
52
|
+
- **send-email**: `to`, `subject`, `body` (strings); `cc`, `bcc` (strings, optional); `isHtml` (boolean, optional, cloud mode only)
|
|
53
|
+
- **search-emails**: `query`, `from`, `subject`, `folder` (strings, optional), `unreadOnly` (boolean, optional), `count` (number, optional)
|
|
54
|
+
- **mark-as-read**: `uid` (string), `folder` (string, optional), `isRead` (boolean, optional)
|
|
55
|
+
- **list-folders**: no input
|
|
56
|
+
|
|
57
|
+
### Calendar
|
|
58
|
+
|
|
59
|
+
- **list-events**: `count` (number, optional, max 50), `daysAhead` (number, optional)
|
|
60
|
+
- **create-event**: `summary`, `start`, `end` (strings, ISO 8601); `description`, `location` (optional); `calendarUrl` (cloud) or `calendarName` (local)
|
|
61
|
+
- **update-event**: `eventUrl` (string); any of `summary`, `start`, `end`, `description`, `location`. Only the fields you pass change
|
|
62
|
+
- **delete-event**: `eventUrl` (string)
|
|
63
|
+
- **list-calendars**: no input
|
|
64
|
+
|
|
65
|
+
### Contacts
|
|
66
|
+
|
|
67
|
+
- **list-contacts**: `count` (number, optional, max 50)
|
|
68
|
+
- **search-contacts**: `query` (string), `count` (number, optional). Matches name, organisation, email and phone; phone matching ignores formatting
|
|
69
|
+
- **read-contact**: `contactUrl` (string)
|
|
70
|
+
- **create-contact**: `displayName`, `firstName`, `lastName`, `email`, `phone`, `organization`, `title`, `notes` (all optional)
|
|
71
|
+
- **delete-contact**: `contactUrl` (string)
|
|
72
|
+
- **list-contact-accounts**: no input (local only)
|
|
73
|
+
- **list-contact-groups**: `accountId` (string, optional) (local only)
|
|
74
|
+
|
|
75
|
+
### Reminders (local only)
|
|
76
|
+
|
|
77
|
+
- **list-reminder-lists**: no input
|
|
78
|
+
- **list-reminders**: `listName` (string, optional), `includeCompleted` (boolean, optional), `count` (number, optional)
|
|
79
|
+
- **create-reminder**: `name` (string); `body`, `dueDate`, `listName` (optional); `priority` (number 0-9, optional)
|
|
80
|
+
- **update-reminder**: `reminderId` (string); any of `name`, `body`, `dueDate`, `priority`
|
|
81
|
+
- **complete-reminder**: `reminderId` (string), `completed` (boolean, optional)
|
|
82
|
+
- **delete-reminder**: `reminderId` (string)
|
|
83
|
+
- **search-reminders**: `query` (string), `count` (number, optional)
|
|
84
|
+
|
|
85
|
+
### Notes (local only)
|
|
86
|
+
|
|
87
|
+
- **list-note-folders**: no input
|
|
88
|
+
- **list-notes**: `folderName` (string, optional), `count` (number, optional)
|
|
89
|
+
- **read-note**: `noteId` (string)
|
|
90
|
+
- **create-note**: `title` (string), `body` (string, optional), `folderName` (string, optional)
|
|
91
|
+
- **search-notes**: `query` (string), `count` (number, optional)
|
|
92
|
+
|
|
93
|
+
### Messages (local only)
|
|
94
|
+
|
|
95
|
+
Reading requires the [`imsg`](https://github.com/steipete/imsg) CLI and Full Disk Access.
|
|
96
|
+
|
|
97
|
+
- **list-chats**: `limit` (number, optional)
|
|
98
|
+
- **read-messages**: `chatId` (number); `limit` (number, optional); `start`, `end` (ISO 8601, optional); `attachments` (boolean, optional)
|
|
99
|
+
- **send-message**: `to` (string), `body` (string), `file` (string, optional)
|
|
100
|
+
- **react-message**: `chatId` (number), `type` (`love`, `like`, `dislike`, `laugh`, `emphasis`, `question`)
|
|
101
|
+
|
|
102
|
+
### Safari (local only)
|
|
103
|
+
|
|
104
|
+
- **list-safari-tabs**: no input
|
|
105
|
+
- **get-current-safari-url**: no input
|
|
106
|
+
- **open-safari-url**: `url` (string), `inNewWindow` (boolean, optional)
|
|
107
|
+
- **close-safari-tab**: `windowIndex` (number, optional), `tabIndex` (number, optional)
|
|
108
|
+
|
|
109
|
+
### Tool annotations (MCP hints)
|
|
110
|
+
|
|
111
|
+
Every tool declares its behaviour explicitly instead of relying on the spec defaults, which are deliberately pessimistic. All `list-*`, `read-*`, `search-*` and `get-*` tools, plus `about` and `check-auth-status`, are marked read-only. The four destructive tools (`delete-event`, `delete-contact`, `delete-reminder`, `close-safari-tab`) carry `destructiveHint`. Tools that reach the network or another person carry `openWorldHint`: all Email and Calendar tools, `send-message`, `react-message` and `open-safari-url`. The read-only tools plus `mark-as-read`, `complete-reminder`, `update-reminder`, `update-event` and `set-mode` are marked idempotent.
|
|
112
|
+
|
|
113
|
+
## Installation
|
|
114
|
+
|
|
115
|
+
Requires Node.js 20 or newer.
|
|
116
|
+
|
|
117
|
+
### Claude Desktop
|
|
118
|
+
|
|
119
|
+
Add to `claude_desktop_config.json`:
|
|
120
|
+
|
|
121
|
+
```json
|
|
122
|
+
{
|
|
123
|
+
"mcpServers": {
|
|
124
|
+
"icloud": {
|
|
125
|
+
"command": "npx",
|
|
126
|
+
"args": ["-y", "mcp-icloud"]
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
For cloud mode, add credentials:
|
|
133
|
+
|
|
134
|
+
```json
|
|
135
|
+
{
|
|
136
|
+
"mcpServers": {
|
|
137
|
+
"icloud": {
|
|
138
|
+
"command": "npx",
|
|
139
|
+
"args": ["-y", "mcp-icloud"],
|
|
140
|
+
"env": {
|
|
141
|
+
"USE_LOCAL_MODE": "false",
|
|
142
|
+
"ICLOUD_EMAIL": "you@icloud.com",
|
|
143
|
+
"ICLOUD_APP_PASSWORD": "xxxx-xxxx-xxxx-xxxx"
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### Claude Code
|
|
151
|
+
|
|
152
|
+
```bash
|
|
153
|
+
claude mcp add --transport stdio icloud -- npx -y mcp-icloud
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### VS Code
|
|
157
|
+
|
|
158
|
+
Use the badge at the top of this README, or add the same `command`/`args` pair to your MCP settings.
|
|
159
|
+
|
|
160
|
+
### Desktop extension (.mcpb)
|
|
161
|
+
|
|
162
|
+
Download the `.mcpb` from [Releases](https://github.com/MrGo2/icloud-mcp/releases) and open it to sideload. The bundle prompts for the mode and, for cloud mode, stores the app-specific password in the macOS Keychain rather than a file.
|
|
163
|
+
|
|
164
|
+
## Permissions and troubleshooting
|
|
165
|
+
|
|
166
|
+
### macOS Automation prompts (local mode)
|
|
167
|
+
|
|
168
|
+
The first time a tool touches an app, macOS asks whether the calling program may control it. This happens once per app, not once per tool. Approve the prompt, or grant it later under **System Settings → Privacy & Security → Automation**.
|
|
169
|
+
|
|
170
|
+
If you dismissed a prompt, calls to that app fail with an authorisation error (`osascript` error `-1743`, "not authorized to send Apple events"). macOS will not ask again on its own. Re-enable the checkbox under Automation, or reset the decisions:
|
|
171
|
+
|
|
172
|
+
```bash
|
|
173
|
+
tccutil reset AppleEvents
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
That clears Automation permissions for every app, so expect the prompts to return on next use.
|
|
177
|
+
|
|
178
|
+
### Full Disk Access (reading messages)
|
|
179
|
+
|
|
180
|
+
`list-chats` and `read-messages` read the Messages database through the `imsg` CLI, which is gated by Full Disk Access, not Automation. Grant it to the program that launches the server (Claude Desktop, your terminal, or your editor) under **System Settings → Privacy & Security → Full Disk Access**. Without it, those tools report that Full Disk Access is required.
|
|
181
|
+
|
|
182
|
+
If `imsg` is installed somewhere unusual, point at it explicitly:
|
|
183
|
+
|
|
184
|
+
```bash
|
|
185
|
+
export ICLOUD_MCP_IMSG_PATH=/opt/homebrew/bin/imsg
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
The server otherwise looks in `ICLOUD_MCP_IMSG_PATH`, `IMSG_PATH`, both Homebrew prefixes, and finally `PATH`.
|
|
189
|
+
|
|
190
|
+
### Known limitation: large mailboxes
|
|
191
|
+
|
|
192
|
+
Mail.app tools iterate messages through AppleScript, which is slow on very large mailboxes and can exceed the Apple Event timeout before returning. Narrow the request with `folder` and a smaller `count`, or use cloud mode, where IMAP does the filtering server-side. This is a property of the AppleScript bridge, not something the server can work around.
|
|
193
|
+
|
|
194
|
+
### Known limitation: large calendar sets
|
|
195
|
+
|
|
196
|
+
`list-events` in local mode suffers the same AppleScript wall. The date window is already filtered inside Calendar.app with a `whose` query, but on accounts with many calendars, especially subscribed ones carrying years of events (holidays, sports fixtures, travel feeds), the query can still exceed any reasonable timeout. `list-calendars` and the event write tools are not affected. Until the planned EventKit backend lands (see [#10](https://github.com/MrGo2/icloud-mcp/issues/11)), use cloud mode for reading events: CalDAV filters server-side and is fast at any scale.
|
|
197
|
+
|
|
198
|
+
### App-specific password (cloud mode)
|
|
199
|
+
|
|
200
|
+
Cloud mode needs an app-specific password. Your normal Apple ID password will not work, and Apple only issues app-specific passwords on accounts with two-factor authentication turned on.
|
|
201
|
+
|
|
202
|
+
1. Sign in at [appleid.apple.com](https://appleid.apple.com).
|
|
203
|
+
2. Go to **Sign-In and Security → App-Specific Passwords**.
|
|
204
|
+
3. Generate one and name it, for example, "iCloud MCP".
|
|
205
|
+
4. Put it in `ICLOUD_APP_PASSWORD`, together with `ICLOUD_EMAIL`.
|
|
206
|
+
|
|
207
|
+
Revoke it from the same page if it is ever exposed.
|
|
208
|
+
|
|
209
|
+
### Checking what the server thinks
|
|
210
|
+
|
|
211
|
+
Call `about` for the active mode and service list, and `check-auth-status` to confirm credentials are usable in the current mode.
|
|
212
|
+
|
|
213
|
+
## Requirements
|
|
214
|
+
|
|
215
|
+
- Node.js 20 or newer
|
|
216
|
+
- Local mode: macOS with the relevant apps installed, plus `imsg` and Full Disk Access if you want to read messages
|
|
217
|
+
- Cloud mode: any OS, an iCloud account with two-factor authentication, and an app-specific password. Covers Email, Calendar and Contacts only
|
|
218
|
+
|
|
219
|
+
## Configuration
|
|
220
|
+
|
|
221
|
+
| Variable | Default | Purpose |
|
|
222
|
+
|---|---|---|
|
|
223
|
+
| `USE_LOCAL_MODE` | `true` | `false` selects cloud mode |
|
|
224
|
+
| `ICLOUD_EMAIL` | (unset) | iCloud address, cloud mode only |
|
|
225
|
+
| `ICLOUD_APP_PASSWORD` | (unset) | App-specific password, cloud mode only |
|
|
226
|
+
| `ICLOUD_MCP_IMSG_PATH` | (unset) | Explicit path to the `imsg` binary |
|
|
227
|
+
|
|
228
|
+
Read from the environment, or from a `.env` file beside the module. See `.env.example`.
|
|
229
|
+
|
|
230
|
+
## Development
|
|
231
|
+
|
|
232
|
+
`pnpm` is the supported package manager; `pnpm-lock.yaml` is the committed lockfile.
|
|
233
|
+
|
|
234
|
+
```bash
|
|
235
|
+
pnpm install
|
|
236
|
+
pnpm test # unit + contract suites, and a live stdio session
|
|
237
|
+
pnpm run inspect # drive the server with the MCP Inspector
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
The test suites stub `osascript` and the `imsg` CLI, so they touch no real mail, calendar or message data and run on any OS.
|
|
241
|
+
|
|
242
|
+
## License
|
|
243
|
+
|
|
244
|
+
MIT. See [LICENSE](LICENSE).
|
package/auth/index.js
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auth module for iCloud MCP
|
|
3
|
+
* Handles credential management and authentication status
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const { z } = require('zod');
|
|
7
|
+
const config = require('../config');
|
|
8
|
+
const { formatSuccess, formatError } = require('../utils/error-handler');
|
|
9
|
+
const { getMode, setMode, isLocalMode } = require('../mode');
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Check if credentials are configured
|
|
13
|
+
*/
|
|
14
|
+
function hasCredentials() {
|
|
15
|
+
return !!(config.ICLOUD_EMAIL && config.ICLOUD_APP_PASSWORD);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Get credentials for use in clients
|
|
20
|
+
*/
|
|
21
|
+
function getCredentials() {
|
|
22
|
+
if (!hasCredentials()) {
|
|
23
|
+
throw new Error('UNAUTHORIZED');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return {
|
|
27
|
+
email: config.ICLOUD_EMAIL,
|
|
28
|
+
password: config.ICLOUD_APP_PASSWORD
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Handler: About this server
|
|
34
|
+
*/
|
|
35
|
+
async function handleAbout() {
|
|
36
|
+
const mode = getMode();
|
|
37
|
+
const localServices = 'Email, Calendar, Contacts, Reminders, Notes, Messages, Safari';
|
|
38
|
+
const cloudServices = 'Email, Calendar, Contacts';
|
|
39
|
+
|
|
40
|
+
return formatSuccess(
|
|
41
|
+
`iCloud MCP Server v2.1.0
|
|
42
|
+
|
|
43
|
+
Mode: ${mode.toUpperCase()}
|
|
44
|
+
Services: ${mode === 'local' ? localServices : cloudServices}
|
|
45
|
+
|
|
46
|
+
${mode === 'local' ? 'Using AppleScript to access macOS apps directly.' : 'Using iCloud protocols (IMAP, CalDAV, CardDAV).'}
|
|
47
|
+
|
|
48
|
+
Credentials: ${hasCredentials() ? 'Configured' : 'Not configured (needed for cloud mode)'}
|
|
49
|
+
|
|
50
|
+
Use 'set-mode' tool to switch between LOCAL and CLOUD modes.`
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Handler: Check authentication status
|
|
56
|
+
*/
|
|
57
|
+
async function handleCheckAuthStatus() {
|
|
58
|
+
const mode = getMode();
|
|
59
|
+
|
|
60
|
+
if (mode === 'cloud' && !hasCredentials()) {
|
|
61
|
+
return formatError(new Error('UNAUTHORIZED - Cloud mode requires credentials'));
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (mode === 'local') {
|
|
65
|
+
return formatSuccess(
|
|
66
|
+
`Authentication status: OK (Local Mode)
|
|
67
|
+
|
|
68
|
+
Mode: LOCAL (AppleScript)
|
|
69
|
+
Credentials: ${hasCredentials() ? 'Configured (for cloud)' : 'Not needed for local mode'}
|
|
70
|
+
|
|
71
|
+
Services available:
|
|
72
|
+
- Email (Mail.app)
|
|
73
|
+
- Calendar (Calendar.app)
|
|
74
|
+
- Contacts (Contacts.app)
|
|
75
|
+
- Reminders (Reminders.app)
|
|
76
|
+
- Notes (Notes.app)
|
|
77
|
+
- Messages (Messages.app)
|
|
78
|
+
- Safari (Safari.app)`
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return formatSuccess(
|
|
83
|
+
`Authentication status: OK (Cloud Mode)
|
|
84
|
+
|
|
85
|
+
Mode: CLOUD (iCloud protocols)
|
|
86
|
+
Email: ${config.ICLOUD_EMAIL}
|
|
87
|
+
Password: ****-****-****-**** (configured)
|
|
88
|
+
|
|
89
|
+
Services available:
|
|
90
|
+
- Email (IMAP/SMTP)
|
|
91
|
+
- Calendar (CalDAV)
|
|
92
|
+
- Contacts (CardDAV)`
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Handler: Set mode (local or cloud)
|
|
98
|
+
*/
|
|
99
|
+
async function handleSetMode(args) {
|
|
100
|
+
const { mode } = args;
|
|
101
|
+
|
|
102
|
+
if (!mode) {
|
|
103
|
+
return formatError(new Error('Mode is required. Use "local" or "cloud".'));
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (mode !== 'local' && mode !== 'cloud') {
|
|
107
|
+
return formatError(new Error('Invalid mode. Use "local" or "cloud".'));
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (mode === 'local' && !config.IS_MACOS) {
|
|
111
|
+
return formatError(new Error('Local mode only available on macOS.'));
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (mode === 'cloud' && !hasCredentials()) {
|
|
115
|
+
return formatError(new Error('Cloud mode requires iCloud credentials. Set ICLOUD_EMAIL and ICLOUD_APP_PASSWORD.'));
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const previousMode = getMode();
|
|
119
|
+
|
|
120
|
+
if (previousMode === mode) {
|
|
121
|
+
return formatSuccess(`Already in ${mode.toUpperCase()} mode.`);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
setMode(mode);
|
|
125
|
+
|
|
126
|
+
const services = mode === 'local'
|
|
127
|
+
? 'Email, Calendar, Contacts, Reminders, Notes, Messages, Safari'
|
|
128
|
+
: 'Email, Calendar, Contacts';
|
|
129
|
+
|
|
130
|
+
return formatSuccess(
|
|
131
|
+
`Mode changed: ${previousMode.toUpperCase()} → ${mode.toUpperCase()}
|
|
132
|
+
|
|
133
|
+
Services now available: ${services}`
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Tool definitions
|
|
138
|
+
const authTools = [
|
|
139
|
+
{
|
|
140
|
+
name: 'about',
|
|
141
|
+
title: 'About This Server',
|
|
142
|
+
description: 'Returns information about this iCloud MCP server',
|
|
143
|
+
inputSchema: {},
|
|
144
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
|
|
145
|
+
handler: handleAbout
|
|
146
|
+
},
|
|
147
|
+
{
|
|
148
|
+
name: 'check-auth-status',
|
|
149
|
+
title: 'Check Auth Status',
|
|
150
|
+
description: 'Check if iCloud credentials are configured correctly',
|
|
151
|
+
inputSchema: {},
|
|
152
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
|
|
153
|
+
handler: handleCheckAuthStatus
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
name: 'set-mode',
|
|
157
|
+
title: 'Switch Mode',
|
|
158
|
+
description: 'Switch between LOCAL (AppleScript/macOS apps) and CLOUD (iCloud protocols) modes',
|
|
159
|
+
inputSchema: {
|
|
160
|
+
mode: z.enum(['local', 'cloud'])
|
|
161
|
+
.describe('Mode to activate: "local" for AppleScript access to macOS apps, "cloud" for iCloud protocols')
|
|
162
|
+
},
|
|
163
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
164
|
+
handler: handleSetMode
|
|
165
|
+
}
|
|
166
|
+
];
|
|
167
|
+
|
|
168
|
+
module.exports = {
|
|
169
|
+
authTools,
|
|
170
|
+
hasCredentials,
|
|
171
|
+
getCredentials,
|
|
172
|
+
handleAbout,
|
|
173
|
+
handleCheckAuthStatus,
|
|
174
|
+
handleSetMode
|
|
175
|
+
};
|