@shin1ohno/sage 1.0.2 → 1.0.4

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 (159) hide show
  1. package/README.md +110 -0
  2. package/dist/cli/mcp-handler.d.ts +5 -0
  3. package/dist/cli/mcp-handler.d.ts.map +1 -1
  4. package/dist/cli/mcp-handler.js +219 -38
  5. package/dist/cli/mcp-handler.js.map +1 -1
  6. package/dist/config/storage/file-storage.d.ts +1 -1
  7. package/dist/config/storage/file-storage.d.ts.map +1 -1
  8. package/dist/config/storage/session-storage.d.ts +1 -1
  9. package/dist/config/storage/session-storage.d.ts.map +1 -1
  10. package/dist/config/storage/storage-factory.d.ts +1 -1
  11. package/dist/config/storage/storage-factory.d.ts.map +1 -1
  12. package/dist/config/validation.d.ts +337 -6
  13. package/dist/config/validation.d.ts.map +1 -1
  14. package/dist/config/validation.js +276 -0
  15. package/dist/config/validation.js.map +1 -1
  16. package/dist/core/sage-core.d.ts +4 -1
  17. package/dist/core/sage-core.d.ts.map +1 -1
  18. package/dist/core/sage-core.js.map +1 -1
  19. package/dist/index.js +217 -27
  20. package/dist/index.js.map +1 -1
  21. package/dist/integrations/calendar-service.d.ts +23 -0
  22. package/dist/integrations/calendar-service.d.ts.map +1 -1
  23. package/dist/integrations/calendar-service.js +96 -0
  24. package/dist/integrations/calendar-service.js.map +1 -1
  25. package/dist/integrations/calendar-source-manager.d.ts +51 -13
  26. package/dist/integrations/calendar-source-manager.d.ts.map +1 -1
  27. package/dist/integrations/calendar-source-manager.js +224 -27
  28. package/dist/integrations/calendar-source-manager.js.map +1 -1
  29. package/dist/integrations/google-calendar-room-service.d.ts +140 -0
  30. package/dist/integrations/google-calendar-room-service.d.ts.map +1 -0
  31. package/dist/integrations/google-calendar-room-service.js +383 -0
  32. package/dist/integrations/google-calendar-room-service.js.map +1 -0
  33. package/dist/integrations/google-calendar-service.d.ts +167 -6
  34. package/dist/integrations/google-calendar-service.d.ts.map +1 -1
  35. package/dist/integrations/google-calendar-service.js +1036 -84
  36. package/dist/integrations/google-calendar-service.js.map +1 -1
  37. package/dist/integrations/google-people-service.d.ts +57 -0
  38. package/dist/integrations/google-people-service.d.ts.map +1 -0
  39. package/dist/integrations/google-people-service.js +207 -0
  40. package/dist/integrations/google-people-service.js.map +1 -0
  41. package/dist/oauth/google-oauth-handler.d.ts +2 -1
  42. package/dist/oauth/google-oauth-handler.d.ts.map +1 -1
  43. package/dist/oauth/google-oauth-handler.js +3 -1
  44. package/dist/oauth/google-oauth-handler.js.map +1 -1
  45. package/dist/services/integration-strategy-manager.d.ts +275 -0
  46. package/dist/services/integration-strategy-manager.d.ts.map +1 -0
  47. package/dist/services/integration-strategy-manager.js +362 -0
  48. package/dist/services/integration-strategy-manager.js.map +1 -0
  49. package/dist/services/sampling-service.d.ts +191 -0
  50. package/dist/services/sampling-service.d.ts.map +1 -0
  51. package/dist/services/sampling-service.js +352 -0
  52. package/dist/services/sampling-service.js.map +1 -0
  53. package/dist/tools/calendar/handlers.d.ts +223 -0
  54. package/dist/tools/calendar/handlers.d.ts.map +1 -1
  55. package/dist/tools/calendar/handlers.js +725 -8
  56. package/dist/tools/calendar/handlers.js.map +1 -1
  57. package/dist/tools/calendar/index.d.ts +3 -2
  58. package/dist/tools/calendar/index.d.ts.map +1 -1
  59. package/dist/tools/calendar/index.js +4 -1
  60. package/dist/tools/calendar/index.js.map +1 -1
  61. package/dist/tools/directory/handlers.d.ts +43 -0
  62. package/dist/tools/directory/handlers.d.ts.map +1 -0
  63. package/dist/tools/directory/handlers.js +74 -0
  64. package/dist/tools/directory/handlers.js.map +1 -0
  65. package/dist/tools/directory/index.d.ts +11 -0
  66. package/dist/tools/directory/index.d.ts.map +1 -0
  67. package/dist/tools/directory/index.js +10 -0
  68. package/dist/tools/directory/index.js.map +1 -0
  69. package/dist/tools/reminders/handlers.d.ts +32 -1
  70. package/dist/tools/reminders/handlers.d.ts.map +1 -1
  71. package/dist/tools/reminders/handlers.js +135 -1
  72. package/dist/tools/reminders/handlers.js.map +1 -1
  73. package/dist/tools/reminders/index.d.ts +3 -3
  74. package/dist/tools/reminders/index.d.ts.map +1 -1
  75. package/dist/tools/reminders/index.js +2 -2
  76. package/dist/tools/reminders/index.js.map +1 -1
  77. package/dist/tools/shared/availability-tools.d.ts +87 -0
  78. package/dist/tools/shared/availability-tools.d.ts.map +1 -0
  79. package/dist/tools/shared/availability-tools.js +64 -0
  80. package/dist/tools/shared/availability-tools.js.map +1 -0
  81. package/dist/tools/shared/calendar-tools.d.ts +142 -0
  82. package/dist/tools/shared/calendar-tools.d.ts.map +1 -0
  83. package/dist/tools/shared/calendar-tools.js +98 -0
  84. package/dist/tools/shared/calendar-tools.js.map +1 -0
  85. package/dist/tools/shared/directory-tools.d.ts +35 -0
  86. package/dist/tools/shared/directory-tools.d.ts.map +1 -0
  87. package/dist/tools/shared/directory-tools.js +31 -0
  88. package/dist/tools/shared/directory-tools.js.map +1 -0
  89. package/dist/tools/shared/index.d.ts +32 -0
  90. package/dist/tools/shared/index.d.ts.map +1 -0
  91. package/dist/tools/shared/index.js +37 -0
  92. package/dist/tools/shared/index.js.map +1 -0
  93. package/dist/tools/shared/room-tools.d.ts +94 -0
  94. package/dist/tools/shared/room-tools.d.ts.map +1 -0
  95. package/dist/tools/shared/room-tools.js +67 -0
  96. package/dist/tools/shared/room-tools.js.map +1 -0
  97. package/dist/tools/shared/types.d.ts +35 -0
  98. package/dist/tools/shared/types.d.ts.map +1 -0
  99. package/dist/tools/shared/types.js +26 -0
  100. package/dist/tools/shared/types.js.map +1 -0
  101. package/dist/types/calendar.d.ts +54 -0
  102. package/dist/types/calendar.d.ts.map +1 -1
  103. package/dist/types/config.d.ts +4 -0
  104. package/dist/types/config.d.ts.map +1 -1
  105. package/dist/types/config.js.map +1 -1
  106. package/dist/types/google-calendar-types.d.ts +210 -2
  107. package/dist/types/google-calendar-types.d.ts.map +1 -1
  108. package/dist/types/google-calendar-types.js +33 -1
  109. package/dist/types/google-calendar-types.js.map +1 -1
  110. package/dist/types/google-people-types.d.ts +47 -0
  111. package/dist/types/google-people-types.d.ts.map +1 -0
  112. package/dist/types/google-people-types.js +8 -0
  113. package/dist/types/google-people-types.js.map +1 -0
  114. package/dist/types/index.d.ts +1 -0
  115. package/dist/types/index.d.ts.map +1 -1
  116. package/dist/types/index.js +1 -0
  117. package/dist/types/index.js.map +1 -1
  118. package/dist/types/platform.d.ts +128 -0
  119. package/dist/types/platform.d.ts.map +1 -0
  120. package/dist/types/platform.js +10 -0
  121. package/dist/types/platform.js.map +1 -0
  122. package/dist/types/sampling.d.ts +134 -0
  123. package/dist/types/sampling.d.ts.map +1 -0
  124. package/dist/types/sampling.js +25 -0
  125. package/dist/types/sampling.js.map +1 -0
  126. package/dist/types/storage.d.ts +39 -0
  127. package/dist/types/storage.d.ts.map +1 -0
  128. package/dist/types/storage.js +7 -0
  129. package/dist/types/storage.js.map +1 -0
  130. package/dist/utils/recurrence-validator.d.ts +133 -0
  131. package/dist/utils/recurrence-validator.d.ts.map +1 -0
  132. package/dist/utils/recurrence-validator.js +595 -0
  133. package/dist/utils/recurrence-validator.js.map +1 -0
  134. package/dist/version.js +1 -1
  135. package/package.json +3 -2
  136. package/dist/platform/adapter-factory.d.ts +0 -22
  137. package/dist/platform/adapter-factory.d.ts.map +0 -1
  138. package/dist/platform/adapter-factory.js +0 -37
  139. package/dist/platform/adapter-factory.js.map +0 -1
  140. package/dist/platform/adapters/mcp-adapter.d.ts +0 -32
  141. package/dist/platform/adapters/mcp-adapter.d.ts.map +0 -1
  142. package/dist/platform/adapters/mcp-adapter.js +0 -52
  143. package/dist/platform/adapters/mcp-adapter.js.map +0 -1
  144. package/dist/platform/adapters/remote-mcp-adapter.d.ts +0 -34
  145. package/dist/platform/adapters/remote-mcp-adapter.d.ts.map +0 -1
  146. package/dist/platform/adapters/remote-mcp-adapter.js +0 -54
  147. package/dist/platform/adapters/remote-mcp-adapter.js.map +0 -1
  148. package/dist/platform/detector.d.ts +0 -49
  149. package/dist/platform/detector.d.ts.map +0 -1
  150. package/dist/platform/detector.js +0 -161
  151. package/dist/platform/detector.js.map +0 -1
  152. package/dist/platform/index.d.ts +0 -9
  153. package/dist/platform/index.d.ts.map +0 -1
  154. package/dist/platform/index.js +0 -9
  155. package/dist/platform/index.js.map +0 -1
  156. package/dist/platform/types.d.ts +0 -163
  157. package/dist/platform/types.d.ts.map +0 -1
  158. package/dist/platform/types.js +0 -28
  159. package/dist/platform/types.js.map +0 -1
package/README.md CHANGED
@@ -142,6 +142,116 @@ From Claude, run the `authenticate_google` tool:
142
142
  | Session expired | Complete authentication within 10 minutes |
143
143
  | 503 from callback | Verify `GOOGLE_CLIENT_ID` and `GOOGLE_CLIENT_SECRET` are set |
144
144
 
145
+ ### Platform-Adaptive Integration
146
+
147
+ sage automatically detects your platform and uses the best available integration method for calendar and reminder access. This ensures you get the most seamless experience regardless of where you're using Claude.
148
+
149
+ **How it Works**
150
+
151
+ 1. **Platform Detection**: When sage starts, it detects your platform from the MCP client information (iOS, iPadOS, macOS, desktop, or web)
152
+ 2. **Strategy Selection**: Based on the detected platform, sage selects the optimal integration strategy for calendar and reminder operations
153
+ 3. **MCP Sampling Protocol**: On iOS/iPadOS, sage uses the MCP Sampling protocol to instruct Claude to use native Calendar and Reminders APIs
154
+ 4. **Native APIs**: On macOS, sage uses EventKit (calendar) and AppleScript (reminders) for direct system integration
155
+ 5. **Fallback**: On web/Linux/Windows, sage uses Google Calendar API for calendar features
156
+
157
+ **Platform Capabilities**
158
+
159
+ | Platform | Calendar | Reminders | Integration Method |
160
+ |----------|----------|-----------|-------------------|
161
+ | iOS/iPadOS | Native Calendar + Google Calendar | Native Reminders | MCP Sampling |
162
+ | macOS | EventKit + Google Calendar | AppleScript | Native APIs |
163
+ | Web/Linux/Windows | Google Calendar only | Not available | Google API |
164
+
165
+ **Key Benefits**
166
+
167
+ - **True multi-platform support**: No need to run a Mac server for iOS/iPadOS access
168
+ - **Multi-source calendar integration**: Access both Google Calendar and Apple Calendar simultaneously on iOS/iPadOS
169
+ - **Transparent UX**: sage automatically selects the best method - you don't need to configure anything
170
+ - **Graceful degradation**: If native access is unavailable, sage falls back to API-based methods
171
+
172
+ **Using `get_platform_info`**
173
+
174
+ Query your current platform and available capabilities:
175
+
176
+ ```
177
+ User: get_platform_info
178
+
179
+ sage:
180
+ {
181
+ "platform": "ipados",
182
+ "clientName": "Claude iOS",
183
+ "clientVersion": "1.0.0",
184
+ "supportsSampling": true,
185
+ "availableIntegrations": {
186
+ "calendar": {
187
+ "google": true,
188
+ "eventkit": false,
189
+ "native": true
190
+ },
191
+ "reminders": {
192
+ "applescript": false,
193
+ "native": true
194
+ }
195
+ }
196
+ }
197
+ ```
198
+
199
+ **Example: Listing Calendar Events on iOS**
200
+
201
+ When you call `list_calendar_events` on iOS/iPadOS, sage uses Sampling to instruct Claude to:
202
+ 1. Fetch Google Calendar events via the MCP tool
203
+ 2. Access native iOS Calendar events via the native Calendar API
204
+ 3. Merge both sets of events, removing duplicates by iCalUID
205
+ 4. Return the combined results
206
+
207
+ This happens automatically - you just call the tool normally:
208
+
209
+ ```
210
+ User: list_calendar_events with startDate: "2026-01-09", endDate: "2026-01-16"
211
+
212
+ sage: (automatically uses Sampling to access both calendars)
213
+ [Shows merged events from both Google Calendar and Apple Calendar]
214
+ ```
215
+
216
+ **Example: Creating Reminders on iOS**
217
+
218
+ When you create a reminder on iOS/iPadOS, sage uses Sampling to access the native Reminders app:
219
+
220
+ ```
221
+ User: set_reminder with title: "Review pull requests", dueDate: "2026-01-10T15:00:00"
222
+
223
+ sage: (uses Sampling to create reminder in native iOS Reminders app)
224
+ ✓ Reminder created successfully
225
+ ID: reminder-123
226
+ ```
227
+
228
+ **Troubleshooting**
229
+
230
+ **Issue**: "Sampling approval required" message appears
231
+
232
+ **Solution**:
233
+ - This is expected behavior on iOS/iPadOS when sage needs to access native Calendar or Reminders
234
+ - Claude will show a prompt asking for your approval to access these native features
235
+ - Approve the Sampling request to allow sage to access your calendar/reminders
236
+ - Once approved, the operation will complete automatically
237
+
238
+ **Issue**: Platform-adaptive integration not working
239
+
240
+ **Checklist**:
241
+ 1. Verify your platform supports Sampling (run `get_platform_info`)
242
+ 2. Check that you're using a recent version of Claude iOS/iPadOS app
243
+ 3. Ensure you've granted Calendar/Reminders permissions in iOS Settings > Privacy
244
+ 4. For Google Calendar: Make sure you've authenticated with `authenticate_google`
245
+
246
+ **Issue**: Calendar events not showing up
247
+
248
+ **Possible causes**:
249
+ 1. Google Calendar not authenticated: Run `authenticate_google` tool
250
+ 2. iOS Calendar permissions denied: Check Settings > Privacy > Calendars
251
+ 3. Network connectivity issues: Verify you have internet access
252
+
253
+ **Note**: Platform-adaptive integration requires Claude clients that support MCP Sampling. This includes Claude iOS, Claude iPadOS, and recent versions of Claude Desktop. If your client doesn't support Sampling, sage will automatically fall back to API-based methods.
254
+
145
255
  ### Smart Reminder Routing
146
256
 
147
257
  Tasks are automatically routed to the appropriate system:
@@ -48,6 +48,11 @@ export interface ToolDefinition {
48
48
  export interface MCPHandler {
49
49
  handleRequest(request: MCPRequest): Promise<MCPResponse>;
50
50
  listTools(): ToolDefinition[];
51
+ /**
52
+ * Shutdown the handler and release all resources
53
+ * Should be called when the handler is no longer needed (e.g., in tests)
54
+ */
55
+ shutdown(): Promise<void>;
51
56
  }
52
57
  /**
53
58
  * Create an MCP handler
@@ -1 +1 @@
1
- {"version":3,"file":"mcp-handler.d.ts","sourceRoot":"","sources":["../../src/cli/mcp-handler.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAoFH;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,KAAK,CAAC;IACf,EAAE,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAClC;AAED;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,KAAK,CAAC;IACf,EAAE,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;IAC3B,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,QAAQ,CAAC;CAClB;AAED;;GAEG;AACH,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE;QACX,IAAI,EAAE,QAAQ,CAAC;QACf,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QACpC,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;KACrB,CAAC;CACH;AASD;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,aAAa,CAAC,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC;IACzD,SAAS,IAAI,cAAc,EAAE,CAAC;CAC/B;AAqyCD;;GAEG;AACH,wBAAsB,gBAAgB,IAAI,OAAO,CAAC,UAAU,CAAC,CAI5D"}
1
+ {"version":3,"file":"mcp-handler.d.ts","sourceRoot":"","sources":["../../src/cli/mcp-handler.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AA+GH;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,KAAK,CAAC;IACf,EAAE,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAClC;AAED;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,KAAK,CAAC;IACf,EAAE,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;IAC3B,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,QAAQ,CAAC;CAClB;AAED;;GAEG;AACH,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE;QACX,IAAI,EAAE,QAAQ,CAAC;QACf,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QACpC,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;KACrB,CAAC;CACH;AASD;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,aAAa,CAAC,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC;IACzD,SAAS,IAAI,cAAc,EAAE,CAAC;IAC9B;;;OAGG;IACH,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CAC3B;AAwgDD;;GAEG;AACH,wBAAsB,gBAAgB,IAAI,OAAO,CAAC,UAAU,CAAC,CAI5D"}
@@ -15,6 +15,7 @@ import { CalendarEventResponseService } from '../integrations/calendar-event-res
15
15
  import { WorkingCadenceService } from '../services/working-cadence.js';
16
16
  import { CalendarSourceManager } from '../integrations/calendar-source-manager.js';
17
17
  import { GoogleCalendarService } from '../integrations/google-calendar-service.js';
18
+ import { GooglePeopleService } from '../integrations/google-people-service.js';
18
19
  import { GoogleOAuthHandler } from '../oauth/google-oauth-handler.js';
19
20
  // Hot-reload imports
20
21
  import { ServiceRegistry } from '../services/service-registry.js';
@@ -25,9 +26,12 @@ import { getHotReloadConfig } from '../config/hot-reload-config.js';
25
26
  // Extracted tool handlers
26
27
  import { handleCheckSetupStatus, handleStartSetupWizard, handleAnswerWizardQuestion, handleSaveConfig, } from '../tools/setup/index.js';
27
28
  import { handleAnalyzeTasks, handleUpdateTaskStatus, handleSyncTasks, handleDetectDuplicates, } from '../tools/tasks/index.js';
28
- import { handleSetReminder, handleListTodos, } from '../tools/reminders/index.js';
29
+ import { handleSetReminder, handleListTodos, handleSetReminderWithSampling, } from '../tools/reminders/index.js';
29
30
  import { handleSyncToNotion, handleUpdateConfig, } from '../tools/integrations/index.js';
30
- import { handleListCalendarSources, handleListCalendarEvents, handleFindAvailableSlots, handleCreateCalendarEvent, handleRespondToCalendarEvent, handleRespondToCalendarEventsBatch, handleDeleteCalendarEvent, handleDeleteCalendarEventsBatch, } from '../tools/calendar/handlers.js';
31
+ import { handleListCalendarSources, handleListCalendarResources, handleListCalendarEvents, handleFindAvailableSlots, handleCreateCalendarEvent, handleRespondToCalendarEvent, handleRespondToCalendarEventsBatch, handleDeleteCalendarEvent, handleDeleteCalendarEventsBatch, handleUpdateCalendarEvent, handleSearchRoomAvailability, handleCheckRoomAvailability, handleCheckPeopleAvailability, handleFindCommonAvailability, handleListCalendarEventsWithSampling, } from '../tools/calendar/handlers.js';
32
+ // Shared tool definitions
33
+ import { searchRoomAvailabilityTool, checkRoomAvailabilityTool, updateCalendarEventTool, deleteCalendarEventTool, searchDirectoryPeopleTool, checkPeopleAvailabilityTool, findCommonAvailabilityTool, toJsonSchema, } from '../tools/shared/index.js';
34
+ import { handleSearchDirectoryPeople, } from '../tools/directory/index.js';
31
35
  import { handleAuthenticateGoogle, } from '../tools/oauth/index.js';
32
36
  // Protocol version
33
37
  const PROTOCOL_VERSION = '2024-11-05';
@@ -37,6 +41,7 @@ const PROTOCOL_VERSION = '2024-11-05';
37
41
  class MCPHandlerImpl {
38
42
  config = null;
39
43
  wizardSession = null;
44
+ supportsSampling = false;
40
45
  reminderManager = null;
41
46
  calendarService = null;
42
47
  notionService = null;
@@ -46,6 +51,7 @@ class MCPHandlerImpl {
46
51
  workingCadenceService = null;
47
52
  calendarSourceManager = null;
48
53
  googleCalendarService = null;
54
+ googlePeopleService = null;
49
55
  initialized = false;
50
56
  // Hot-reload infrastructure
51
57
  serviceRegistry = null;
@@ -194,6 +200,8 @@ class MCPHandlerImpl {
194
200
  };
195
201
  const oauthHandler = new GoogleOAuthHandler(oauthConfig);
196
202
  this.googleCalendarService = new GoogleCalendarService(oauthHandler);
203
+ // Initialize Google People service with the same OAuth handler
204
+ this.googlePeopleService = new GooglePeopleService(oauthHandler);
197
205
  this.calendarSourceManager = new CalendarSourceManager({
198
206
  calendarService: this.calendarService,
199
207
  googleCalendarService: this.googleCalendarService,
@@ -295,6 +303,7 @@ class MCPHandlerImpl {
295
303
  },
296
304
  getCalendarEventResponseService: () => this.calendarEventResponseService,
297
305
  getGoogleCalendarService: () => this.googleCalendarService,
306
+ getGooglePeopleService: () => this.googlePeopleService,
298
307
  getWorkingCadenceService: () => {
299
308
  // Prefer reloadable adapter instance if available
300
309
  if (this.workingCadenceAdapter) {
@@ -335,6 +344,25 @@ class MCPHandlerImpl {
335
344
  createGoogleOAuthHandler: createHandler,
336
345
  };
337
346
  }
347
+ /**
348
+ * Create DirectoryToolsContext for directory tool handlers
349
+ */
350
+ createDirectoryToolsContext() {
351
+ return {
352
+ getConfig: () => this.config,
353
+ getGooglePeopleService: () => this.googlePeopleService,
354
+ };
355
+ }
356
+ /**
357
+ * Create SamplingContext for Sampling-based tool handlers
358
+ */
359
+ createSamplingContext() {
360
+ return {
361
+ // TODO: Implement MCP Server instance passing for Sampling requests
362
+ // Currently returns null - Sampling handlers will fall back to non-Sampling behavior
363
+ getMcpServer: () => null,
364
+ };
365
+ }
338
366
  /**
339
367
  * Handle an MCP request
340
368
  */
@@ -374,8 +402,24 @@ class MCPHandlerImpl {
374
402
  }
375
403
  /**
376
404
  * Handle initialize request
405
+ * Requirements: 1.1, 1.6 (platform-adaptive-integration)
406
+ *
407
+ * Extracts clientInfo and capabilities from the initialize request to detect
408
+ * the platform type and available features.
377
409
  */
378
- handleInitialize(id, _params) {
410
+ handleInitialize(id, params) {
411
+ // Extract clientInfo and capabilities from params
412
+ const clientInfo = params?.clientInfo;
413
+ const capabilities = params?.capabilities;
414
+ // Capability-based: check for Sampling support
415
+ if (clientInfo && capabilities) {
416
+ this.supportsSampling = capabilities.sampling !== undefined;
417
+ console.log(`[sage] MCP client initialized: ${clientInfo.name} v${clientInfo.version}, ` +
418
+ `sampling: ${this.supportsSampling}`);
419
+ }
420
+ else {
421
+ console.warn('[sage] No clientInfo available in initialize request');
422
+ }
379
423
  return {
380
424
  jsonrpc: '2.0',
381
425
  id,
@@ -464,6 +508,17 @@ class MCPHandlerImpl {
464
508
  listTools() {
465
509
  return Array.from(this.tools.values()).map((t) => t.definition);
466
510
  }
511
+ /**
512
+ * Shutdown the handler and release all resources
513
+ * Stops the ConfigReloadService and ConfigWatcher to prevent resource leaks
514
+ */
515
+ async shutdown() {
516
+ if (this.configReloadService) {
517
+ this.configReloadService.stop();
518
+ this.configReloadService = null;
519
+ }
520
+ this.initialized = false;
521
+ }
467
522
  /**
468
523
  * Register all tools
469
524
  */
@@ -562,10 +617,10 @@ class MCPHandlerImpl {
562
617
  }, async (args) => handleAnalyzeTasks(this.createTaskToolsContext(), {
563
618
  tasks: args.tasks,
564
619
  }));
565
- // set_reminder - uses extracted handler
620
+ // set_reminder - with platform-based runtime dispatch
566
621
  this.registerTool({
567
622
  name: 'set_reminder',
568
- description: 'Set a reminder for a task in Apple Reminders or Notion.',
623
+ description: 'Set a reminder for a task. Uses native iOS Reminders on iOS/iPad, or AppleScript/Notion on other platforms.',
569
624
  inputSchema: {
570
625
  type: 'object',
571
626
  properties: {
@@ -601,14 +656,23 @@ class MCPHandlerImpl {
601
656
  },
602
657
  required: ['taskTitle'],
603
658
  },
604
- }, async (args) => handleSetReminder(this.createReminderTodoContext(), {
605
- taskTitle: args.taskTitle,
606
- dueDate: args.dueDate,
607
- reminderType: args.reminderType,
608
- list: args.list,
609
- priority: args.priority,
610
- notes: args.notes,
611
- }));
659
+ }, async (args) => {
660
+ const input = {
661
+ taskTitle: args.taskTitle,
662
+ dueDate: args.dueDate,
663
+ reminderType: args.reminderType,
664
+ list: args.list,
665
+ priority: args.priority,
666
+ notes: args.notes,
667
+ };
668
+ // Capability-based routing: Use Sampling when available
669
+ if (this.supportsSampling) {
670
+ return handleSetReminderWithSampling(input, this.createReminderTodoContext(), this.createSamplingContext());
671
+ }
672
+ else {
673
+ return handleSetReminder(this.createReminderTodoContext(), input);
674
+ }
675
+ });
612
676
  // find_available_slots - uses extracted handler
613
677
  this.registerTool({
614
678
  name: 'find_available_slots',
@@ -641,10 +705,10 @@ class MCPHandlerImpl {
641
705
  endDate: args.endDate,
642
706
  preferDeepWork: args.preferDeepWork,
643
707
  }));
644
- // list_calendar_events - uses extracted handler
708
+ // list_calendar_events - with platform-based runtime dispatch
645
709
  this.registerTool({
646
710
  name: 'list_calendar_events',
647
- description: 'List calendar events for a specified period. Returns events with details including calendar name and location.',
711
+ description: 'List calendar events for a specified period. Uses native iOS Calendar on iOS/iPad, or EventKit/Google Calendar on other platforms. Returns events with details including calendar name and location.',
648
712
  inputSchema: {
649
713
  type: 'object',
650
714
  properties: {
@@ -663,11 +727,20 @@ class MCPHandlerImpl {
663
727
  },
664
728
  required: ['startDate', 'endDate'],
665
729
  },
666
- }, async (args) => handleListCalendarEvents(this.createCalendarToolsContext(), {
667
- startDate: args.startDate,
668
- endDate: args.endDate,
669
- calendarId: args.calendarId,
670
- }));
730
+ }, async (args) => {
731
+ const input = {
732
+ startDate: args.startDate,
733
+ endDate: args.endDate,
734
+ calendarId: args.calendarId,
735
+ };
736
+ // Capability-based routing: Use Sampling when available
737
+ if (this.supportsSampling) {
738
+ return handleListCalendarEventsWithSampling(input, this.createCalendarToolsContext(), this.createSamplingContext());
739
+ }
740
+ else {
741
+ return handleListCalendarEvents(this.createCalendarToolsContext(), input);
742
+ }
743
+ });
671
744
  // sync_to_notion - uses extracted handler
672
745
  this.registerTool({
673
746
  name: 'sync_to_notion',
@@ -965,7 +1038,7 @@ class MCPHandlerImpl {
965
1038
  // create_calendar_event
966
1039
  this.registerTool({
967
1040
  name: 'create_calendar_event',
968
- description: 'Create a new calendar event with support for Google Calendar event types (OOO, Focus Time, Working Location, etc.).',
1041
+ description: 'Create a new calendar event with support for Google Calendar event types (OOO, Focus Time, Working Location, etc.) and recurring events.',
969
1042
  inputSchema: {
970
1043
  type: 'object',
971
1044
  properties: {
@@ -989,6 +1062,11 @@ class MCPHandlerImpl {
989
1062
  items: { type: 'string' },
990
1063
  description: "Optional: Override default alarms with custom settings (e.g., ['-15m', '-1h']). If omitted, calendar's default alarm settings apply.",
991
1064
  },
1065
+ recurrence: {
1066
+ type: 'array',
1067
+ items: { type: 'string' },
1068
+ description: 'Optional: Array of RRULE strings for recurring events (e.g., ["FREQ=WEEKLY;BYDAY=MO,WE,FR"]). Google Calendar only. Supports DAILY, WEEKLY, MONTHLY, YEARLY frequencies with optional INTERVAL, COUNT, UNTIL, and BYDAY parameters.',
1069
+ },
992
1070
  eventType: {
993
1071
  type: 'string',
994
1072
  enum: ['default', 'outOfOffice', 'focusTime', 'workingLocation', 'birthday'],
@@ -1022,6 +1100,10 @@ class MCPHandlerImpl {
1022
1100
  enum: ['birthday', 'anniversary', 'other'],
1023
1101
  description: 'For birthday: type of birthday event',
1024
1102
  },
1103
+ roomId: {
1104
+ type: 'string',
1105
+ description: 'Room calendar ID to book for this event. Use search_room_availability to find available rooms. Requires Google Calendar.',
1106
+ },
1025
1107
  },
1026
1108
  required: ['title', 'startDate', 'endDate'],
1027
1109
  },
@@ -1032,6 +1114,7 @@ class MCPHandlerImpl {
1032
1114
  location: args.location,
1033
1115
  notes: args.notes,
1034
1116
  calendarName: args.calendarName,
1117
+ recurrence: args.recurrence,
1035
1118
  eventType: args.eventType,
1036
1119
  autoDeclineMode: args.autoDeclineMode,
1037
1120
  declineMessage: args.declineMessage,
@@ -1039,27 +1122,19 @@ class MCPHandlerImpl {
1039
1122
  workingLocationType: args.workingLocationType,
1040
1123
  workingLocationLabel: args.workingLocationLabel,
1041
1124
  birthdayType: args.birthdayType,
1125
+ roomId: args.roomId,
1042
1126
  }));
1043
- // delete_calendar_event
1127
+ // delete_calendar_event - Delete a calendar event
1128
+ // Requirement: recurring-calendar-events 5.1
1129
+ // Uses shared definition from tools/shared/calendar-tools.ts
1044
1130
  this.registerTool({
1045
- name: 'delete_calendar_event',
1046
- description: 'Delete a calendar event by its ID.',
1047
- inputSchema: {
1048
- type: 'object',
1049
- properties: {
1050
- eventId: {
1051
- type: 'string',
1052
- description: 'Event ID (UUID or full ID from list_calendar_events)',
1053
- },
1054
- calendarName: {
1055
- type: 'string',
1056
- description: 'Calendar name (searches all calendars if not specified)',
1057
- },
1058
- },
1059
- required: ['eventId'],
1060
- },
1131
+ name: deleteCalendarEventTool.name,
1132
+ description: deleteCalendarEventTool.description,
1133
+ inputSchema: toJsonSchema(deleteCalendarEventTool.schema),
1061
1134
  }, async (args) => handleDeleteCalendarEvent(this.createCalendarToolsContext(), {
1062
1135
  eventId: args.eventId,
1136
+ deleteScope: args.deleteScope,
1137
+ calendarName: args.calendarName,
1063
1138
  }));
1064
1139
  // delete_calendar_events_batch
1065
1140
  this.registerTool({
@@ -1083,6 +1158,29 @@ class MCPHandlerImpl {
1083
1158
  }, async (args) => handleDeleteCalendarEventsBatch(this.createCalendarToolsContext(), {
1084
1159
  eventIds: args.eventIds,
1085
1160
  }));
1161
+ // update_calendar_event - Update an existing calendar event
1162
+ // Requirement: update-calendar-event 1-8
1163
+ // Uses shared definition from tools/shared/calendar-tools.ts
1164
+ this.registerTool({
1165
+ name: updateCalendarEventTool.name,
1166
+ description: updateCalendarEventTool.description,
1167
+ inputSchema: toJsonSchema(updateCalendarEventTool.schema),
1168
+ }, async (args) => handleUpdateCalendarEvent(this.createCalendarToolsContext(), {
1169
+ eventId: args.eventId,
1170
+ title: args.title,
1171
+ startDate: args.startDate,
1172
+ endDate: args.endDate,
1173
+ location: args.location,
1174
+ notes: args.notes,
1175
+ attendees: args.attendees,
1176
+ alarms: args.alarms,
1177
+ roomId: args.roomId,
1178
+ removeRoom: args.removeRoom,
1179
+ autoDeclineMode: args.autoDeclineMode,
1180
+ declineMessage: args.declineMessage,
1181
+ chatStatus: args.chatStatus,
1182
+ calendarName: args.calendarName,
1183
+ }));
1086
1184
  // list_calendar_sources - uses extracted handler
1087
1185
  this.registerTool({
1088
1186
  name: 'list_calendar_sources',
@@ -1092,6 +1190,89 @@ class MCPHandlerImpl {
1092
1190
  properties: {},
1093
1191
  },
1094
1192
  }, async () => handleListCalendarSources(this.createCalendarToolsContext()));
1193
+ // list_calendar_resources - list individual calendars from all sources
1194
+ // Requirement: multi-calendar-resources 1.1
1195
+ this.registerTool({
1196
+ name: 'list_calendar_resources',
1197
+ description: 'List all available calendar resources (individual calendars) from enabled sources. Returns calendar names, IDs, colors, and write permissions.',
1198
+ inputSchema: {
1199
+ type: 'object',
1200
+ properties: {
1201
+ source: {
1202
+ type: 'string',
1203
+ enum: ['eventkit', 'google', 'all'],
1204
+ description: "Filter by source type: 'eventkit', 'google', or 'all' (default: 'all')",
1205
+ },
1206
+ },
1207
+ },
1208
+ }, async (args) => handleListCalendarResources(this.createCalendarToolsContext(), {
1209
+ source: args.source,
1210
+ }));
1211
+ // search_room_availability - Search for available meeting rooms
1212
+ // Requirement: room-availability-search 1
1213
+ // Uses shared definition from tools/shared/room-tools.ts
1214
+ this.registerTool({
1215
+ name: searchRoomAvailabilityTool.name,
1216
+ description: searchRoomAvailabilityTool.description,
1217
+ inputSchema: toJsonSchema(searchRoomAvailabilityTool.schema),
1218
+ }, async (args) => handleSearchRoomAvailability(this.createCalendarToolsContext(), {
1219
+ startTime: args.startTime,
1220
+ endTime: args.endTime,
1221
+ durationMinutes: args.durationMinutes,
1222
+ minCapacity: args.minCapacity,
1223
+ building: args.building,
1224
+ floor: args.floor,
1225
+ features: args.features,
1226
+ }));
1227
+ // check_room_availability - Check availability of a specific room
1228
+ // Requirement: room-availability-search 2
1229
+ // Uses shared definition from tools/shared/room-tools.ts
1230
+ this.registerTool({
1231
+ name: checkRoomAvailabilityTool.name,
1232
+ description: checkRoomAvailabilityTool.description,
1233
+ inputSchema: toJsonSchema(checkRoomAvailabilityTool.schema),
1234
+ }, async (args) => handleCheckRoomAvailability(this.createCalendarToolsContext(), {
1235
+ roomId: args.roomId,
1236
+ startTime: args.startTime,
1237
+ endTime: args.endTime,
1238
+ }));
1239
+ // search_directory_people - Search organization directory for people
1240
+ // Requirement: directory-people-search 1
1241
+ // Uses shared definition from tools/shared/directory-tools.ts
1242
+ this.registerTool({
1243
+ name: searchDirectoryPeopleTool.name,
1244
+ description: searchDirectoryPeopleTool.description,
1245
+ inputSchema: toJsonSchema(searchDirectoryPeopleTool.schema),
1246
+ }, async (args) => handleSearchDirectoryPeople(this.createDirectoryToolsContext(), {
1247
+ query: args.query,
1248
+ pageSize: args.pageSize,
1249
+ }));
1250
+ // check_people_availability - Check availability of people by email
1251
+ // Requirement: check-others-availability 1
1252
+ // Uses shared definition from tools/shared/availability-tools.ts
1253
+ this.registerTool({
1254
+ name: checkPeopleAvailabilityTool.name,
1255
+ description: checkPeopleAvailabilityTool.description,
1256
+ inputSchema: toJsonSchema(checkPeopleAvailabilityTool.schema),
1257
+ }, async (args) => handleCheckPeopleAvailability(this.createCalendarToolsContext(), {
1258
+ emails: args.emails,
1259
+ startTime: args.startTime,
1260
+ endTime: args.endTime,
1261
+ }));
1262
+ // find_common_availability - Find common free time among people
1263
+ // Requirement: check-others-availability 2, 4
1264
+ // Uses shared definition from tools/shared/availability-tools.ts
1265
+ this.registerTool({
1266
+ name: findCommonAvailabilityTool.name,
1267
+ description: findCommonAvailabilityTool.description,
1268
+ inputSchema: toJsonSchema(findCommonAvailabilityTool.schema),
1269
+ }, async (args) => handleFindCommonAvailability(this.createCalendarToolsContext(), {
1270
+ participants: args.participants,
1271
+ startTime: args.startTime,
1272
+ endTime: args.endTime,
1273
+ minDurationMinutes: args.minDurationMinutes,
1274
+ includeMyCalendar: args.includeMyCalendar,
1275
+ }));
1095
1276
  // authenticate_google - Complete Google OAuth flow automatically
1096
1277
  this.registerTool({
1097
1278
  name: 'authenticate_google',