morgen-mcp 0.1.1 → 0.1.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "morgen-mcp",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "MCP server for Morgen — events, tasks, and calendar management for Claude Code via natural language",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
@@ -20,7 +20,7 @@
20
20
  "test": "vitest run"
21
21
  },
22
22
  "engines": {
23
- "node": ">=18"
23
+ "node": ">=20"
24
24
  },
25
25
  "publishConfig": {
26
26
  "access": "public",
@@ -9,6 +9,7 @@ const TTL_MS = 10 * 60 * 1000;
9
9
 
10
10
  let cache = null;
11
11
  let expiresAt = 0;
12
+ let loadingPromise = null;
12
13
 
13
14
  async function loadCache() {
14
15
  const raw = await morgenFetch("/v3/calendars/list", { points: 10 });
@@ -45,7 +46,11 @@ async function loadCache() {
45
46
 
46
47
  export async function getCalendarCache() {
47
48
  if (cache && expiresAt > Date.now()) return cache;
48
- return loadCache();
49
+ if (loadingPromise) return loadingPromise;
50
+ loadingPromise = loadCache().finally(() => {
51
+ loadingPromise = null;
52
+ });
53
+ return loadingPromise;
49
54
  }
50
55
 
51
56
  export async function resolveCalendarMeta(calendarId) {
@@ -93,12 +98,14 @@ export async function getAllAccountsWithCalendars() {
93
98
  export function _resetCalendarCache() {
94
99
  cache = null;
95
100
  expiresAt = 0;
101
+ loadingPromise = null;
96
102
  }
97
103
 
98
104
  // Test helper: preload the cache with fake entries so handlers can look up
99
105
  // calendar metadata without hitting a real API. Entries should be
100
106
  // { id, accountId, name?, readOnly?, integrationId?, color? } objects.
101
107
  export function _seedCalendarCache(entries) {
108
+ loadingPromise = null;
102
109
  const byId = new Map();
103
110
  const byAccount = new Map();
104
111
  for (const e of entries) {
package/src/client.js CHANGED
@@ -34,6 +34,11 @@ function msUntilFits(now, incomingPoints) {
34
34
  }
35
35
 
36
36
  function enforceRateLimit(points) {
37
+ if (points > RATE_LIMIT_POINTS) {
38
+ throw new Error(
39
+ `request requires ${points} points but the Morgen rate limit budget is only ${RATE_LIMIT_POINTS} points per 15 minutes`
40
+ );
41
+ }
37
42
  const now = Date.now();
38
43
  pruneLedger(now);
39
44
 
package/src/index.js CHANGED
@@ -32,7 +32,7 @@ const HANDLERS = { ...eventHandlers, ...taskHandlers };
32
32
 
33
33
  // Server setup
34
34
  const server = new Server(
35
- { name: "morgen", version: "0.1.0" },
35
+ { name: "morgen", version: "0.1.2" },
36
36
  { capabilities: { tools: {} } }
37
37
  );
38
38
 
@@ -22,7 +22,6 @@ import {
22
22
  groupCalendarIdsByAccount,
23
23
  resolveCalendarMeta,
24
24
  resolveDefaultCalendarMeta,
25
- _resetCalendarCache,
26
25
  } from "./calendar-cache.js";
27
26
  import {
28
27
  EVENT_TOOLS,
@@ -69,8 +68,16 @@ function validateRequiredString(value, field, maxLen) {
69
68
  validateString(value, field, maxLen);
70
69
  }
71
70
 
72
- export function _resetDefaultCalendarCache() {
73
- _resetCalendarCache();
71
+ // Morgen represents event locations as a keyed map of Location objects, not a
72
+ // scalar string. Wrap the caller-provided string into the minimum viable shape.
73
+ function toLocationsMap(locationString) {
74
+ if (!locationString) return undefined;
75
+ return {
76
+ [locationString]: {
77
+ "@type": "Location",
78
+ name: locationString,
79
+ },
80
+ };
74
81
  }
75
82
 
76
83
  // ---------- handlers ----------
@@ -186,7 +193,7 @@ async function handleCreateEvent(args = {}) {
186
193
  };
187
194
 
188
195
  if (args.description !== undefined) body.description = args.description;
189
- if (args.location !== undefined) body.location = args.location;
196
+ if (args.location !== undefined) body.locations = toLocationsMap(args.location);
190
197
  if (args.participants !== undefined) {
191
198
  body.participants = toParticipantMap(args.participants);
192
199
  }
@@ -242,15 +249,20 @@ async function handleUpdateEvent(args = {}) {
242
249
  calendarId: calendarMeta.id,
243
250
  };
244
251
  if (args.title !== undefined) body.title = args.title;
245
- if (args.start !== undefined) {
246
- body.start = isoUtcToLocal(args.start, timeZone);
252
+ // Morgen requires the full timing quartet (start, duration, timeZone,
253
+ // showWithoutTime) whenever any timing field is updated.
254
+ if (args.start !== undefined || args.end !== undefined) {
255
+ if (args.start !== undefined) {
256
+ body.start = isoUtcToLocal(args.start, timeZone);
257
+ }
247
258
  body.timeZone = timeZone;
248
- }
249
- if (args.start !== undefined && args.end !== undefined) {
250
- body.duration = isoDurationFromRange(args.start, args.end);
259
+ if (args.start !== undefined && args.end !== undefined) {
260
+ body.duration = isoDurationFromRange(args.start, args.end);
261
+ }
262
+ body.showWithoutTime = false;
251
263
  }
252
264
  if (args.description !== undefined) body.description = args.description;
253
- if (args.location !== undefined) body.location = args.location;
265
+ if (args.location !== undefined) body.locations = toLocationsMap(args.location);
254
266
  if (args.participants !== undefined) {
255
267
  body.participants = toParticipantMap(args.participants);
256
268
  }
@@ -293,7 +293,7 @@ export const taskHandlers = {
293
293
  move_task: async (args = {}) => {
294
294
  const id = validateId(args.task_id, "task_id");
295
295
  const taskListId = validateId(args.task_list_id, "task_list_id");
296
- const response = await morgenFetch("/v3/tasks/move", {
296
+ const response = await morgenFetch("/v3/tasks/update", {
297
297
  method: "POST",
298
298
  body: { id, taskListId },
299
299
  points: 1,