mcp-macos 2.0.0

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.
Files changed (149) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +360 -0
  3. package/bin/dev.cjs +10 -0
  4. package/bin/run.cjs +15 -0
  5. package/dist/config/index.d.ts +36 -0
  6. package/dist/config/index.js +127 -0
  7. package/dist/config/index.js.map +1 -0
  8. package/dist/config/schema.d.ts +161 -0
  9. package/dist/config/schema.js +45 -0
  10. package/dist/config/schema.js.map +1 -0
  11. package/dist/index.d.ts +11 -0
  12. package/dist/index.js +75 -0
  13. package/dist/index.js.map +1 -0
  14. package/dist/server/handlers.d.ts +10 -0
  15. package/dist/server/handlers.js +43 -0
  16. package/dist/server/handlers.js.map +1 -0
  17. package/dist/server/promptAbstractions.d.ts +74 -0
  18. package/dist/server/promptAbstractions.js +150 -0
  19. package/dist/server/promptAbstractions.js.map +1 -0
  20. package/dist/server/prompts.d.ts +8 -0
  21. package/dist/server/prompts.js +480 -0
  22. package/dist/server/prompts.js.map +1 -0
  23. package/dist/server/server.d.ts +29 -0
  24. package/dist/server/server.js +52 -0
  25. package/dist/server/server.js.map +1 -0
  26. package/dist/server/transports/http/auth.d.ts +34 -0
  27. package/dist/server/transports/http/auth.js +148 -0
  28. package/dist/server/transports/http/auth.js.map +1 -0
  29. package/dist/server/transports/http/health.d.ts +35 -0
  30. package/dist/server/transports/http/health.js +93 -0
  31. package/dist/server/transports/http/health.js.map +1 -0
  32. package/dist/server/transports/http/index.d.ts +43 -0
  33. package/dist/server/transports/http/index.js +141 -0
  34. package/dist/server/transports/http/index.js.map +1 -0
  35. package/dist/server/transports/http/middleware.d.ts +53 -0
  36. package/dist/server/transports/http/middleware.js +133 -0
  37. package/dist/server/transports/http/middleware.js.map +1 -0
  38. package/dist/tools/definitions.d.ts +10 -0
  39. package/dist/tools/definitions.js +633 -0
  40. package/dist/tools/definitions.js.map +1 -0
  41. package/dist/tools/handlers/calendarHandlers.d.ts +11 -0
  42. package/dist/tools/handlers/calendarHandlers.js +123 -0
  43. package/dist/tools/handlers/calendarHandlers.js.map +1 -0
  44. package/dist/tools/handlers/contactsHandlers.d.ts +17 -0
  45. package/dist/tools/handlers/contactsHandlers.js +397 -0
  46. package/dist/tools/handlers/contactsHandlers.js.map +1 -0
  47. package/dist/tools/handlers/index.d.ts +11 -0
  48. package/dist/tools/handlers/index.js +12 -0
  49. package/dist/tools/handlers/index.js.map +1 -0
  50. package/dist/tools/handlers/listHandlers.d.ts +10 -0
  51. package/dist/tools/handlers/listHandlers.js +40 -0
  52. package/dist/tools/handlers/listHandlers.js.map +1 -0
  53. package/dist/tools/handlers/mailHandlers.d.ts +11 -0
  54. package/dist/tools/handlers/mailHandlers.js +301 -0
  55. package/dist/tools/handlers/mailHandlers.js.map +1 -0
  56. package/dist/tools/handlers/messagesHandlers.d.ts +17 -0
  57. package/dist/tools/handlers/messagesHandlers.js +350 -0
  58. package/dist/tools/handlers/messagesHandlers.js.map +1 -0
  59. package/dist/tools/handlers/notesHandlers.d.ts +12 -0
  60. package/dist/tools/handlers/notesHandlers.js +305 -0
  61. package/dist/tools/handlers/notesHandlers.js.map +1 -0
  62. package/dist/tools/handlers/reminderHandlers.d.ts +10 -0
  63. package/dist/tools/handlers/reminderHandlers.js +96 -0
  64. package/dist/tools/handlers/reminderHandlers.js.map +1 -0
  65. package/dist/tools/handlers/shared.d.ts +51 -0
  66. package/dist/tools/handlers/shared.js +107 -0
  67. package/dist/tools/handlers/shared.js.map +1 -0
  68. package/dist/tools/index.d.ts +10 -0
  69. package/dist/tools/index.js +125 -0
  70. package/dist/tools/index.js.map +1 -0
  71. package/dist/types/index.d.ts +265 -0
  72. package/dist/types/index.js +67 -0
  73. package/dist/types/index.js.map +1 -0
  74. package/dist/types/prompts.d.ts +84 -0
  75. package/dist/types/prompts.js +6 -0
  76. package/dist/types/prompts.js.map +1 -0
  77. package/dist/types/repository.d.ts +102 -0
  78. package/dist/types/repository.js +6 -0
  79. package/dist/types/repository.js.map +1 -0
  80. package/dist/utils/binaryValidator.d.ts +52 -0
  81. package/dist/utils/binaryValidator.js +152 -0
  82. package/dist/utils/binaryValidator.js.map +1 -0
  83. package/dist/utils/calendarRepository.d.ts +25 -0
  84. package/dist/utils/calendarRepository.js +100 -0
  85. package/dist/utils/calendarRepository.js.map +1 -0
  86. package/dist/utils/cliExecutor.d.ts +28 -0
  87. package/dist/utils/cliExecutor.js +196 -0
  88. package/dist/utils/cliExecutor.js.map +1 -0
  89. package/dist/utils/constants.d.ts +96 -0
  90. package/dist/utils/constants.js +97 -0
  91. package/dist/utils/constants.js.map +1 -0
  92. package/dist/utils/contactResolver.d.ts +142 -0
  93. package/dist/utils/contactResolver.js +386 -0
  94. package/dist/utils/contactResolver.js.map +1 -0
  95. package/dist/utils/dateFiltering.d.ts +22 -0
  96. package/dist/utils/dateFiltering.js +72 -0
  97. package/dist/utils/dateFiltering.js.map +1 -0
  98. package/dist/utils/dateUtils.d.ts +20 -0
  99. package/dist/utils/dateUtils.js +36 -0
  100. package/dist/utils/dateUtils.js.map +1 -0
  101. package/dist/utils/errorHandling.d.ts +30 -0
  102. package/dist/utils/errorHandling.js +101 -0
  103. package/dist/utils/errorHandling.js.map +1 -0
  104. package/dist/utils/helpers.d.ts +35 -0
  105. package/dist/utils/helpers.js +59 -0
  106. package/dist/utils/helpers.js.map +1 -0
  107. package/dist/utils/jxaExecutor.d.ts +47 -0
  108. package/dist/utils/jxaExecutor.js +194 -0
  109. package/dist/utils/jxaExecutor.js.map +1 -0
  110. package/dist/utils/logging.d.ts +31 -0
  111. package/dist/utils/logging.js +98 -0
  112. package/dist/utils/logging.js.map +1 -0
  113. package/dist/utils/permissionPrompt.d.ts +16 -0
  114. package/dist/utils/permissionPrompt.js +42 -0
  115. package/dist/utils/permissionPrompt.js.map +1 -0
  116. package/dist/utils/preflight.d.ts +30 -0
  117. package/dist/utils/preflight.js +196 -0
  118. package/dist/utils/preflight.js.map +1 -0
  119. package/dist/utils/projectUtils.d.ts +11 -0
  120. package/dist/utils/projectUtils.js +76 -0
  121. package/dist/utils/projectUtils.js.map +1 -0
  122. package/dist/utils/reminderDateParser.d.ts +8 -0
  123. package/dist/utils/reminderDateParser.js +77 -0
  124. package/dist/utils/reminderDateParser.js.map +1 -0
  125. package/dist/utils/reminderRepository.d.ts +23 -0
  126. package/dist/utils/reminderRepository.js +91 -0
  127. package/dist/utils/reminderRepository.js.map +1 -0
  128. package/dist/utils/sqliteContactReader.d.ts +51 -0
  129. package/dist/utils/sqliteContactReader.js +216 -0
  130. package/dist/utils/sqliteContactReader.js.map +1 -0
  131. package/dist/utils/sqliteMailReader.d.ts +97 -0
  132. package/dist/utils/sqliteMailReader.js +310 -0
  133. package/dist/utils/sqliteMailReader.js.map +1 -0
  134. package/dist/utils/sqliteMessageReader.d.ts +71 -0
  135. package/dist/utils/sqliteMessageReader.js +400 -0
  136. package/dist/utils/sqliteMessageReader.js.map +1 -0
  137. package/dist/utils/timeHelpers.d.ts +40 -0
  138. package/dist/utils/timeHelpers.js +136 -0
  139. package/dist/utils/timeHelpers.js.map +1 -0
  140. package/dist/utils/timezone.d.ts +24 -0
  141. package/dist/utils/timezone.js +39 -0
  142. package/dist/utils/timezone.js.map +1 -0
  143. package/dist/validation/schemas.d.ts +610 -0
  144. package/dist/validation/schemas.js +354 -0
  145. package/dist/validation/schemas.js.map +1 -0
  146. package/package.json +97 -0
  147. package/scripts/build-swift.mjs +86 -0
  148. package/src/swift/EventKitCLI.swift +778 -0
  149. package/src/swift/Info.plist +38 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Frad LEE, 2026 Kyle Jensen
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,360 @@
1
+ # macos-mcp ![License: MIT](https://img.shields.io/badge/license-MIT-green)
2
+
3
+ > Based on [FradSer/mcp-server-apple-events](https://github.com/FradSer/mcp-server-apple-events)
4
+
5
+ A Model Context Protocol (MCP) server that gives Claude genuine access to your macOS personal data — **Reminders**, **Calendar**, **Notes**, **Mail**, **Messages**, and **Contacts**. Six apps, working together: ask Claude about tomorrow's meeting and it pulls the calendar event, finds related emails, and creates a prep note — from your phone, your desktop, or the web.
6
+
7
+ Use with Claude Desktop locally, or Claude iOS/web remotely via Cloudflare Tunnel.
8
+
9
+ ## Quick Start
10
+
11
+ ### Install from npm
12
+
13
+ ```bash
14
+ npm install -g mcp-macos
15
+ ```
16
+
17
+ ### Or build from source
18
+
19
+ ```bash
20
+ git clone https://github.com/krmj22/macos-mcp.git
21
+ cd macos-mcp
22
+ pnpm install
23
+ pnpm build
24
+ ```
25
+
26
+ ### Verify setup
27
+
28
+ ```bash
29
+ node dist/index.js --check # or: macos-mcp --check (if installed globally)
30
+ ```
31
+
32
+ The preflight check validates macOS version, Node.js, EventKit binary, Full Disk Access, and JXA automation permissions.
33
+
34
+ **Next step:** [Using Claude Desktop?](#local-setup-stdio) | [Using Claude iOS/web remotely?](#remote-setup-claude-iosweb)
35
+
36
+ ## Available MCP Tools
37
+
38
+ | Tool | App | Bridge | Actions |
39
+ |------|-----|--------|---------|
40
+ | `reminders_tasks` | Reminders | EventKit | read, create, update, delete |
41
+ | `reminders_lists` | Reminders | EventKit | read, create, update, delete |
42
+ | `calendar_events` | Calendar | EventKit | read, create, update, delete |
43
+ | `calendar_calendars` | Calendar | EventKit | read |
44
+ | `notes_items` | Notes | JXA | read, create, update, delete |
45
+ | `notes_folders` | Notes | JXA | read, create |
46
+ | `mail_messages` | Mail | SQLite + JXA | read, create, update, delete |
47
+ | `messages_chat` | Messages | SQLite + JXA | read, create |
48
+ | `contacts_people` | Contacts | JXA | read, search, create, update, delete |
49
+
50
+ All tools support both underscore (`reminders_tasks`) and dot (`reminders.tasks`) notation.
51
+
52
+ ## Local Setup (stdio)
53
+
54
+ ### Prerequisites
55
+
56
+ - **Node.js 18 or later**
57
+ - **macOS** (required for EventKit and JXA)
58
+ - **Xcode Command Line Tools** (required for compiling Swift code)
59
+ - **pnpm** (recommended for package management)
60
+
61
+ ### Configure Your Client
62
+
63
+ #### Claude Desktop
64
+
65
+ Add to your `claude_desktop_config.json`:
66
+
67
+ ```json
68
+ {
69
+ "mcpServers": {
70
+ "macos-mcp": {
71
+ "command": "npx",
72
+ "args": ["mcp-macos"]
73
+ }
74
+ }
75
+ }
76
+ ```
77
+
78
+ #### Cursor
79
+
80
+ 1. Open Cursor Settings > MCP > Add new global MCP server
81
+ 2. Configure:
82
+ ```json
83
+ {
84
+ "mcpServers": {
85
+ "macos-mcp": {
86
+ "command": "npx",
87
+ "args": ["mcp-macos"]
88
+ }
89
+ }
90
+ }
91
+ ```
92
+
93
+ #### Claude Code
94
+
95
+ Add a `.mcp.json` to your project root:
96
+
97
+ ```json
98
+ {
99
+ "mcpServers": {
100
+ "macos-mcp": {
101
+ "command": "npx",
102
+ "args": ["mcp-macos"]
103
+ }
104
+ }
105
+ }
106
+ ```
107
+
108
+ ### Permissions
109
+
110
+ On first use, macOS will prompt you to allow access for each app. Click **Allow** when prompted.
111
+
112
+ - **Reminders & Calendar** — EventKit permission dialogs appear automatically.
113
+ - **Notes, Mail, Contacts** — Automation permission dialogs appear when `osascript` first controls each app. Grant access via **System Settings > Privacy & Security > Automation**.
114
+ - **Messages & Mail** — Require **Full Disk Access** for your terminal app (Terminal, iTerm2, etc.) since both read SQLite databases directly.
115
+
116
+ Run `node dist/index.js --check` to verify all permissions are granted. See [Troubleshooting](#troubleshooting) if anything fails.
117
+
118
+ ## Remote Setup (Claude iOS/web)
119
+
120
+ Use this path to access your Mac's apps from Claude iOS or Claude web via a secure Cloudflare Tunnel.
121
+
122
+ ```
123
+ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
124
+ │ Claude iOS │──────│ Cloudflare │──────│ Your Mac │
125
+ │ or Web │ HTTPS│ Edge + Access │tunnel│ macos-mcp │
126
+ └─────────────────┘ └─────────────────┘ └─────────────────┘
127
+ ```
128
+
129
+ ### What you'll set up
130
+
131
+ 1. **Cloudflare Tunnel** — Secure outbound connection from your Mac to Cloudflare's edge
132
+ 2. **Cloudflare Access** — Email OTP authentication so only you can connect
133
+ 3. **LaunchAgents** — Auto-start on boot for both the server and tunnel
134
+ 4. **Permissions** — Automation + Full Disk Access granted to the node binary
135
+
136
+ ### Prerequisites
137
+
138
+ - **Cloudflare account** (free tier works)
139
+ - **Custom domain** in Cloudflare (or use `.cfargotunnel.com` subdomain)
140
+ - **Always-on Mac** (Mac Mini/Studio recommended)
141
+ - **macos-mcp built and working locally** (`pnpm build && node dist/index.js --check`)
142
+
143
+ ### Full Setup Guide
144
+
145
+ Follow the step-by-step instructions in **[`docs/CLOUDFLARE_SETUP.md`](docs/CLOUDFLARE_SETUP.md)** — covers tunnel creation, Cloudflare Access configuration, LaunchAgent setup, permission granting, and registering in Claude iOS.
146
+
147
+ ## Usage Examples
148
+
149
+ ### Reminders
150
+ ```
151
+ Create a reminder to "Buy groceries" for tomorrow at 5 PM.
152
+ Show all reminders in my "Work" list.
153
+ Organize my reminders by priority.
154
+ ```
155
+
156
+ ### Calendar
157
+ ```
158
+ Create a meeting "Team Standup" tomorrow from 10 AM to 10:30 AM.
159
+ Show my calendar events for this week.
160
+ ```
161
+
162
+ ### Notes
163
+ ```
164
+ Create a note titled "Meeting Notes" in the Work folder.
165
+ Search my notes for "project plan".
166
+ List all note folders.
167
+ ```
168
+
169
+ ### Mail
170
+ ```
171
+ Show my inbox.
172
+ Read the email from John about the project.
173
+ Draft an email to alice@example.com about the meeting.
174
+ Reply to the last email from Bob.
175
+ ```
176
+
177
+ ### Messages
178
+ ```
179
+ Show my recent iMessage chats.
180
+ Read messages from the chat with John.
181
+ Send "On my way!" to the group chat.
182
+ ```
183
+
184
+ ## Troubleshooting
185
+
186
+ ### Permission Quick Reference
187
+
188
+ | App | Permission | System Settings Path |
189
+ |-----|------------|---------------------|
190
+ | Reminders | Full Access | Privacy & Security > Reminders |
191
+ | Calendar | Full Access | Privacy & Security > Calendars |
192
+ | Notes | Automation | Privacy & Security > Automation > Notes |
193
+ | Mail | Automation + Full Disk Access | Both locations |
194
+ | Messages | Automation + Full Disk Access | Both locations |
195
+ | Contacts | Automation | Privacy & Security > Automation > Contacts |
196
+
197
+ ### Quick-Fix Commands
198
+
199
+ ```bash
200
+ # Open specific settings panes
201
+ open "x-apple.systempreferences:com.apple.preference.security?Privacy_Reminders"
202
+ open "x-apple.systempreferences:com.apple.preference.security?Privacy_Calendars"
203
+ open "x-apple.systempreferences:com.apple.preference.security?Privacy_Automation"
204
+ open "x-apple.systempreferences:com.apple.preference.security?Privacy_AllFiles"
205
+
206
+ # Run preflight check
207
+ node dist/index.js --check
208
+ ```
209
+
210
+ ### EventKit (Reminders & Calendar)
211
+
212
+ Apple separates Reminders and Calendar permissions into *write-only* and *full-access* scopes. The Swift bridge declares the following privacy keys:
213
+
214
+ - `NSRemindersUsageDescription` / `NSRemindersFullAccessUsageDescription` / `NSRemindersWriteOnlyAccessUsageDescription`
215
+ - `NSCalendarsUsageDescription` / `NSCalendarsFullAccessUsageDescription` / `NSCalendarsWriteOnlyAccessUsageDescription`
216
+
217
+ If a permission failure occurs, the Node.js layer automatically runs a minimal AppleScript to surface the dialog and retries.
218
+
219
+ ### JXA Automation (Notes, Mail, Contacts)
220
+
221
+ JXA-based tools require macOS Automation permissions. On first use, macOS will prompt you to allow `osascript` to control each app.
222
+
223
+ > **Headless / LaunchAgent:** Automation permission dialogs **cannot appear** through a LaunchAgent, SSH session, or any non-GUI context. You must grant them once from a local graphical Terminal session (physical access or Screen Sharing). See [`docs/CLOUDFLARE_SETUP.md`](docs/CLOUDFLARE_SETUP.md) Step 10 for the full procedure. Once granted, permissions persist across reboots.
224
+
225
+ **Verify all Automation permissions:**
226
+
227
+ ```bash
228
+ /usr/bin/osascript -l JavaScript -e 'Application("Contacts").people().length'
229
+ /usr/bin/osascript -l JavaScript -e 'Application("Calendar").calendars().length'
230
+ /usr/bin/osascript -l JavaScript -e 'Application("Reminders").defaultList().name()'
231
+ /usr/bin/osascript -l JavaScript -e 'Application("Mail").inbox().messages().length'
232
+ /usr/bin/osascript -l JavaScript -e 'Application("Notes").notes().length'
233
+ ```
234
+
235
+ Each command should return a value without errors or hanging. A hang means the permission dialog is trying (and failing) to appear.
236
+
237
+ ### Full Disk Access (Messages & Mail)
238
+
239
+ The Messages and Mail tools read SQLite databases directly (`~/Library/Messages/chat.db` and `~/Library/Mail/V10/MailData/Envelope Index`). These databases are protected by **Full Disk Access (FDA)**.
240
+
241
+ - **stdio transport**: Grant FDA to your terminal app (Terminal, iTerm2, etc.)
242
+ - **HTTP transport / LaunchAgent**: Grant FDA to the **actual node binary**, not a version manager shim
243
+
244
+ To find and grant access to the correct binary:
245
+
246
+ ```bash
247
+ # Find the actual node binary path
248
+ node -e "console.log(process.execPath)"
249
+
250
+ # Reveal it in Finder (drag-and-drop into FDA settings)
251
+ open -R "$(node -e "console.log(process.execPath)")"
252
+
253
+ # Open Full Disk Access settings
254
+ open "x-apple.systempreferences:com.apple.preference.security?Privacy_AllFiles"
255
+ ```
256
+
257
+ > **Note:** Version managers (Volta, nvm, fnm) use shims that point to a launcher, not the real binary. The System Settings file picker may not show binaries in hidden directories — use the drag-and-drop method above instead. See [`docs/CLOUDFLARE_SETUP.md`](docs/CLOUDFLARE_SETUP.md) Step 11 for detailed instructions and troubleshooting.
258
+
259
+ ### Version Manager Shim Resolution
260
+
261
+ If you use **Volta**, **nvm**, or **fnm**, the `node` command is a shim/launcher. System Settings needs the real binary:
262
+
263
+ | Manager | Find real binary |
264
+ |---------|-----------------|
265
+ | Volta | `volta which node` or `node -e "console.log(process.execPath)"` |
266
+ | nvm | `nvm which current` |
267
+ | fnm | `fnm exec -- node -e "console.log(process.execPath)"` |
268
+
269
+ The System Settings file picker may not show binaries in hidden directories — use the `open -R` command above to reveal the binary in Finder, then drag-and-drop it into the FDA list.
270
+
271
+ ### Gmail Labels / Missing Inbox Messages
272
+
273
+ Gmail stores all messages in `[Gmail]/All Mail` and uses **labels** for folder membership. The server checks both the direct mailbox and the labels join table for inbox queries. If you're not seeing Gmail inbox messages, verify the Mail app has fully synced your account.
274
+
275
+ ### Server Restart (LaunchAgent)
276
+
277
+ If running as a LaunchAgent with Cloudflare Tunnel, restart **both** services:
278
+
279
+ ```bash
280
+ launchctl kickstart -k gui/$(id -u)/com.macos-mcp.server
281
+ launchctl kickstart -k gui/$(id -u)/com.cloudflare.macos-mcp-tunnel
282
+ ```
283
+
284
+ ## Development
285
+
286
+ ```bash
287
+ pnpm install # Install dependencies
288
+ pnpm build # Build TypeScript + Swift binary
289
+ pnpm test # Run full test suite
290
+ pnpm lint # Lint and format with Biome + TypeScript check
291
+ pnpm dev # Run from source via tsx (stdio only, no build needed)
292
+ ```
293
+
294
+ > **Note:** The production entry point (`bin/run.cjs`) requires `pnpm build` first. Use `pnpm dev` for quick local development with stdio transport.
295
+
296
+ ### End-to-End Testing
297
+
298
+ For HTTP transport testing, an E2E script is available:
299
+
300
+ ```bash
301
+ ./scripts/test-e2e.sh
302
+ ```
303
+
304
+ This script:
305
+ - Starts the server in HTTP mode
306
+ - Tests health endpoints
307
+ - Verifies CORS headers and OPTIONS preflight
308
+ - Tests MCP endpoint availability
309
+ - Tests rate limit headers
310
+ - Verifies graceful shutdown
311
+
312
+ Requirements: `jq` (install with `brew install jq`)
313
+
314
+ ### Architecture
315
+
316
+ The server uses two bridging strategies to communicate with Apple apps:
317
+
318
+ - **EventKit (Swift binary)** — Reminders and Calendar. A compiled Swift CLI binary performs EventKit operations and returns JSON.
319
+ - **JXA (JavaScript for Automation)** — Notes, Mail, Contacts. Scripts run via `osascript -l JavaScript` with template-based parameter interpolation.
320
+ - **SQLite** — Messages reads (`~/Library/Messages/chat.db`) and Mail reads (`~/Library/Mail/V10/MailData/Envelope Index`). JXA message reading is broken on macOS Sonoma+; JXA mail reading is too slow for real inboxes. Writes still use JXA.
321
+
322
+ ### HTTP Transport Configuration
323
+
324
+ The server supports HTTP transport for remote access. Set via environment variables or `macos-mcp.config.json`:
325
+
326
+ | Variable | Default | Description |
327
+ |----------|---------|-------------|
328
+ | `MCP_TRANSPORT` | `stdio` | Transport mode: `stdio`, `http`, or `both` |
329
+ | `MCP_HTTP_ENABLED` | `false` | Enable HTTP transport |
330
+ | `MCP_HTTP_PORT` | `3847` | HTTP server port |
331
+
332
+ Key design decisions:
333
+ - **Stateless mode** — required for multi-client support (Claude.ai serves multiple users)
334
+ - **Root endpoint** — MCP handler at `/` (Claude expects this, not `/mcp`)
335
+ - **JSON fallback** — `enableJsonResponse: true` for clients without SSE support
336
+
337
+ ### Structured Prompt Library
338
+
339
+ The server ships with prompt templates exposed via MCP `ListPrompts` and `GetPrompt` endpoints:
340
+
341
+ - **daily-task-organizer** — optional `today_focus` input produces a same-day execution blueprint
342
+ - **smart-reminder-creator** — optional `task_idea` generates an optimally scheduled reminder
343
+ - **reminder-review-assistant** — optional `review_focus` to audit and optimize existing reminders
344
+ - **weekly-planning-workflow** — optional `user_ideas` guides a Monday-through-Sunday reset
345
+
346
+ Run `pnpm test -- src/server/prompts.test.ts` to validate prompt metadata and schema compatibility.
347
+
348
+ ### Dependencies
349
+
350
+ **Runtime:** `@modelcontextprotocol/sdk`, `exit-on-epipe`, `tsx`, `zod`
351
+
352
+ **Dev:** `typescript`, `jest`, `ts-jest`, `babel-jest`, `@biomejs/biome`
353
+
354
+ ## License
355
+
356
+ MIT
357
+
358
+ ## Contributing
359
+
360
+ Contributions welcome! Please read the contributing guidelines first.
package/bin/dev.cjs ADDED
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Development entry point — runs src/index.ts directly via tsx.
5
+ * Use for local stdio-only development; HTTP transport requires a compiled build.
6
+ */
7
+
8
+ const { register } = require('tsx/cjs/api');
9
+ register();
10
+ require('../src/index.ts');
package/bin/run.cjs ADDED
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env node
2
+
3
+ const path = require('node:path');
4
+ const fs = require('node:fs');
5
+
6
+ const entryPoint = path.resolve(__dirname, '..', 'dist', 'index.js');
7
+
8
+ if (!fs.existsSync(entryPoint)) {
9
+ process.stderr.write(
10
+ 'Error: dist/index.js not found. Run `pnpm build` before starting the server.\n',
11
+ );
12
+ process.exit(1);
13
+ }
14
+
15
+ import(entryPoint);
@@ -0,0 +1,36 @@
1
+ /**
2
+ * @fileoverview Configuration loading and management for macos-mcp server
3
+ * @module config
4
+ * @description Loads configuration from file and environment variables with auto-injection of package.json metadata
5
+ */
6
+ import { type CloudflareAccessConfig, type FullServerConfig, type HttpConfig } from './schema.js';
7
+ /**
8
+ * Loads server configuration from file and environment variables
9
+ *
10
+ * Configuration is loaded in the following priority (highest to lowest):
11
+ * 1. Environment variables (MCP_TRANSPORT, MCP_HTTP_*, CF_ACCESS_*)
12
+ * 2. Configuration file (macos-mcp.config.json in project root)
13
+ * 3. Default values from schema
14
+ *
15
+ * Name and version are always auto-injected from package.json
16
+ *
17
+ * @returns Validated server configuration
18
+ * @throws Error if configuration is invalid
19
+ *
20
+ * @example
21
+ * // Basic usage
22
+ * const config = loadConfig();
23
+ * console.log(config.transport); // 'stdio' (default)
24
+ *
25
+ * @example
26
+ * // With environment variables
27
+ * // MCP_TRANSPORT=http
28
+ * // MCP_HTTP_ENABLED=true
29
+ * // MCP_HTTP_PORT=8080
30
+ * const config = loadConfig();
31
+ * console.log(config.transport); // 'http'
32
+ * console.log(config.http?.port); // 8080
33
+ */
34
+ export declare function loadConfig(): FullServerConfig;
35
+ export type { CloudflareAccessConfig, FullServerConfig, HttpConfig };
36
+ export { CloudflareAccessConfigSchema, HttpConfigSchema, ServerConfigSchema, } from './schema.js';
@@ -0,0 +1,127 @@
1
+ /**
2
+ * @fileoverview Configuration loading and management for macos-mcp server
3
+ * @module config
4
+ * @description Loads configuration from file and environment variables with auto-injection of package.json metadata
5
+ */
6
+ import { existsSync, readFileSync } from 'node:fs';
7
+ import { join } from 'node:path';
8
+ import { findProjectRoot } from '../utils/projectUtils.js';
9
+ import { ServerConfigSchema, } from './schema.js';
10
+ /** Configuration file name */
11
+ const CONFIG_FILENAME = 'macos-mcp.config.json';
12
+ /**
13
+ * Loads server configuration from file and environment variables
14
+ *
15
+ * Configuration is loaded in the following priority (highest to lowest):
16
+ * 1. Environment variables (MCP_TRANSPORT, MCP_HTTP_*, CF_ACCESS_*)
17
+ * 2. Configuration file (macos-mcp.config.json in project root)
18
+ * 3. Default values from schema
19
+ *
20
+ * Name and version are always auto-injected from package.json
21
+ *
22
+ * @returns Validated server configuration
23
+ * @throws Error if configuration is invalid
24
+ *
25
+ * @example
26
+ * // Basic usage
27
+ * const config = loadConfig();
28
+ * console.log(config.transport); // 'stdio' (default)
29
+ *
30
+ * @example
31
+ * // With environment variables
32
+ * // MCP_TRANSPORT=http
33
+ * // MCP_HTTP_ENABLED=true
34
+ * // MCP_HTTP_PORT=8080
35
+ * const config = loadConfig();
36
+ * console.log(config.transport); // 'http'
37
+ * console.log(config.http?.port); // 8080
38
+ */
39
+ export function loadConfig() {
40
+ const projectRoot = findProjectRoot();
41
+ const configPath = join(projectRoot, CONFIG_FILENAME);
42
+ // Load file configuration if it exists
43
+ let fileConfig = {};
44
+ if (existsSync(configPath)) {
45
+ const fileContent = readFileSync(configPath, 'utf-8');
46
+ fileConfig = JSON.parse(fileContent);
47
+ }
48
+ // Build environment variable configuration
49
+ const envConfig = buildEnvConfig();
50
+ // Deep merge file config with env config (env takes precedence)
51
+ const merged = deepMerge(fileConfig, envConfig);
52
+ // Load package.json for name and version
53
+ const packageJsonPath = join(projectRoot, 'package.json');
54
+ const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
55
+ // Parse and validate configuration
56
+ return ServerConfigSchema.parse({
57
+ name: packageJson.name,
58
+ version: packageJson.version,
59
+ ...merged,
60
+ });
61
+ }
62
+ /**
63
+ * Builds configuration object from environment variables
64
+ * @returns Partial configuration from environment variables
65
+ */
66
+ function buildEnvConfig() {
67
+ const config = {};
68
+ // Transport mode
69
+ if (process.env.MCP_TRANSPORT) {
70
+ config.transport = process.env.MCP_TRANSPORT;
71
+ }
72
+ // HTTP configuration (only if explicitly enabled)
73
+ if (process.env.MCP_HTTP_ENABLED === 'true') {
74
+ const httpConfig = {
75
+ enabled: true,
76
+ };
77
+ if (process.env.MCP_HTTP_HOST) {
78
+ httpConfig.host = process.env.MCP_HTTP_HOST;
79
+ }
80
+ if (process.env.MCP_HTTP_PORT) {
81
+ const port = Number.parseInt(process.env.MCP_HTTP_PORT, 10);
82
+ if (!Number.isNaN(port)) {
83
+ httpConfig.port = port;
84
+ }
85
+ }
86
+ // Cloudflare Access configuration
87
+ if (process.env.CF_ACCESS_TEAM_DOMAIN && process.env.CF_ACCESS_POLICY_AUD) {
88
+ const cfConfig = {
89
+ teamDomain: process.env.CF_ACCESS_TEAM_DOMAIN,
90
+ policyAUD: process.env.CF_ACCESS_POLICY_AUD,
91
+ };
92
+ if (process.env.CF_ACCESS_ALLOWED_EMAILS) {
93
+ cfConfig.allowedEmails = process.env.CF_ACCESS_ALLOWED_EMAILS.split(',').map((e) => e.trim());
94
+ }
95
+ httpConfig.cloudflareAccess = cfConfig;
96
+ }
97
+ config.http = httpConfig;
98
+ }
99
+ return config;
100
+ }
101
+ /**
102
+ * Deep merges two objects, with source taking precedence
103
+ * @param target - Base object
104
+ * @param source - Object to merge in (takes precedence)
105
+ * @returns Merged object
106
+ */
107
+ function deepMerge(target, source) {
108
+ const result = { ...target };
109
+ for (const key of Object.keys(source)) {
110
+ const sourceValue = source[key];
111
+ const targetValue = result[key];
112
+ if (sourceValue !== null &&
113
+ typeof sourceValue === 'object' &&
114
+ !Array.isArray(sourceValue) &&
115
+ targetValue !== null &&
116
+ typeof targetValue === 'object' &&
117
+ !Array.isArray(targetValue)) {
118
+ result[key] = deepMerge(targetValue, sourceValue);
119
+ }
120
+ else if (sourceValue !== undefined) {
121
+ result[key] = sourceValue;
122
+ }
123
+ }
124
+ return result;
125
+ }
126
+ export { CloudflareAccessConfigSchema, HttpConfigSchema, ServerConfigSchema, } from './schema.js';
127
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/config/index.ts"],"names":[],"mappings":"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"}