@gethmy/mcp 2.13.0 → 2.13.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/dist/cli.js CHANGED
@@ -3529,6 +3529,45 @@ function parseLabelList(raw) {
3529
3529
  }
3530
3530
  return;
3531
3531
  }
3532
+ var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
3533
+ function requireStringArg(raw, field, opts = {}) {
3534
+ let value;
3535
+ if (typeof raw === "string")
3536
+ value = raw.trim();
3537
+ else if (typeof raw === "number" || typeof raw === "boolean")
3538
+ value = String(raw);
3539
+ else
3540
+ throw new Error(`${field} is required and must be a string (received ${raw === undefined || raw === null ? "nothing" : typeof raw}).`);
3541
+ if (value.length === 0)
3542
+ throw new Error(`${field} is required and must not be empty.`);
3543
+ if (opts.max && value.length > opts.max)
3544
+ throw new Error(`${field} must be at most ${opts.max} characters (received ${value.length}).`);
3545
+ return value;
3546
+ }
3547
+ function requireUuidArg(raw, field) {
3548
+ const value = requireStringArg(raw, field);
3549
+ if (!UUID_RE.test(value))
3550
+ throw new Error(`${field} must be a valid UUID (received "${value}").`);
3551
+ return value;
3552
+ }
3553
+ function optionalPercentArg(raw, field) {
3554
+ if (raw === undefined || raw === null)
3555
+ return;
3556
+ const n = typeof raw === "number" ? raw : Number(raw);
3557
+ if (!Number.isFinite(n))
3558
+ throw new Error(`${field} must be a number between 0 and 100.`);
3559
+ if (n < 0 || n > 100)
3560
+ throw new Error(`${field} must be between 0 and 100 (received ${n}).`);
3561
+ return n;
3562
+ }
3563
+ function optionalNonNegativeNumberArg(raw, field) {
3564
+ if (raw === undefined || raw === null)
3565
+ return;
3566
+ const n = typeof raw === "number" ? raw : Number(raw);
3567
+ if (!Number.isFinite(n) || n < 0)
3568
+ throw new Error(`${field} must be a non-negative number.`);
3569
+ return n;
3570
+ }
3532
3571
  function initMemorySession(cardId, agentIdentifier, agentName, agentSessionId) {
3533
3572
  memorySessions.set(cardId, {
3534
3573
  cardId,
@@ -5376,7 +5415,6 @@ function registerHandlers(server, deps) {
5376
5415
  const { name, arguments: args } = request.params;
5377
5416
  const toolArgs = args || {};
5378
5417
  const cardIdArg = toolArgs.cardId;
5379
- const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
5380
5418
  if (cardIdArg && UUID_RE.test(cardIdArg) && deps.isConfigured()) {
5381
5419
  const isAutoStartTrigger = AUTO_START_TRIGGERS.has(name);
5382
5420
  const cv = server.getClientVersion?.();
@@ -6042,9 +6080,11 @@ async function handleToolCall(name, args, deps) {
6042
6080
  return { success: true, ...result };
6043
6081
  }
6044
6082
  case "harmony_start_agent_session": {
6045
- const cardId = z.string().uuid().parse(args.cardId);
6046
- const agentIdentifier = z.string().min(1).max(100).parse(args.agentIdentifier);
6047
- const agentName = z.string().min(1).max(100).parse(args.agentName);
6083
+ const cardId = requireUuidArg(args.cardId, "cardId");
6084
+ const agentIdentifier = requireStringArg(args.agentIdentifier, "agentIdentifier", { max: 100 });
6085
+ const agentName = requireStringArg(args.agentName, "agentName", {
6086
+ max: 100
6087
+ });
6048
6088
  const moveToColumn = args.moveToColumn;
6049
6089
  const addLabels = parseLabelList(args.addLabels);
6050
6090
  let movedTo = null;
@@ -6105,7 +6145,7 @@ async function handleToolCall(name, args, deps) {
6105
6145
  agentName,
6106
6146
  status: "working",
6107
6147
  currentTask: args.currentTask,
6108
- estimatedMinutesRemaining: args.estimatedMinutesRemaining,
6148
+ estimatedMinutesRemaining: optionalNonNegativeNumberArg(args.estimatedMinutesRemaining, "estimatedMinutesRemaining"),
6109
6149
  steerable: args.steerable === true || args.steerable === "true" ? true : undefined
6110
6150
  });
6111
6151
  markExplicit(cardId, {
@@ -6125,10 +6165,12 @@ async function handleToolCall(name, args, deps) {
6125
6165
  };
6126
6166
  }
6127
6167
  case "harmony_update_agent_progress": {
6128
- const cardId = z.string().uuid().parse(args.cardId);
6129
- const agentIdentifier = z.string().min(1).max(100).parse(args.agentIdentifier);
6130
- const agentName = z.string().min(1).max(100).parse(args.agentName);
6131
- const progressPercent = args.progressPercent !== undefined ? z.number().min(0).max(100).parse(args.progressPercent) : undefined;
6168
+ const cardId = requireUuidArg(args.cardId, "cardId");
6169
+ const agentIdentifier = requireStringArg(args.agentIdentifier, "agentIdentifier", { max: 100 });
6170
+ const agentName = requireStringArg(args.agentName, "agentName", {
6171
+ max: 100
6172
+ });
6173
+ const progressPercent = optionalPercentArg(args.progressPercent, "progressPercent");
6132
6174
  const callerActions = args.actions;
6133
6175
  const now = new Date().toISOString();
6134
6176
  const callerRecentActions = [
@@ -6153,17 +6195,17 @@ async function handleToolCall(name, args, deps) {
6153
6195
  progressPercent,
6154
6196
  currentTask: args.currentTask,
6155
6197
  blockers: args.blockers,
6156
- estimatedMinutesRemaining: args.estimatedMinutesRemaining,
6198
+ estimatedMinutesRemaining: optionalNonNegativeNumberArg(args.estimatedMinutesRemaining, "estimatedMinutesRemaining"),
6157
6199
  ...mergedRecentActions && { recentActions: mergedRecentActions },
6158
6200
  ...runActivity.length > 0 && { runActivity }
6159
6201
  });
6160
6202
  return { success: true, midSessionLearnings: 0, ...result };
6161
6203
  }
6162
6204
  case "harmony_end_agent_session": {
6163
- const cardId = z.string().uuid().parse(args.cardId);
6205
+ const cardId = requireUuidArg(args.cardId, "cardId");
6164
6206
  const moveToColumn = args.moveToColumn;
6165
6207
  const sessionStatus = args.status || "completed";
6166
- const endProgressPercent = args.progressPercent !== undefined ? z.number().min(0).max(100).parse(args.progressPercent) : undefined;
6208
+ const endProgressPercent = optionalPercentArg(args.progressPercent, "progressPercent");
6167
6209
  await flushMemoryActions(client3, cardId);
6168
6210
  cleanupMemorySession(cardId);
6169
6211
  let result = { session: null };
package/dist/index.js CHANGED
@@ -3524,6 +3524,45 @@ function parseLabelList(raw) {
3524
3524
  }
3525
3525
  return;
3526
3526
  }
3527
+ var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
3528
+ function requireStringArg(raw, field, opts = {}) {
3529
+ let value;
3530
+ if (typeof raw === "string")
3531
+ value = raw.trim();
3532
+ else if (typeof raw === "number" || typeof raw === "boolean")
3533
+ value = String(raw);
3534
+ else
3535
+ throw new Error(`${field} is required and must be a string (received ${raw === undefined || raw === null ? "nothing" : typeof raw}).`);
3536
+ if (value.length === 0)
3537
+ throw new Error(`${field} is required and must not be empty.`);
3538
+ if (opts.max && value.length > opts.max)
3539
+ throw new Error(`${field} must be at most ${opts.max} characters (received ${value.length}).`);
3540
+ return value;
3541
+ }
3542
+ function requireUuidArg(raw, field) {
3543
+ const value = requireStringArg(raw, field);
3544
+ if (!UUID_RE.test(value))
3545
+ throw new Error(`${field} must be a valid UUID (received "${value}").`);
3546
+ return value;
3547
+ }
3548
+ function optionalPercentArg(raw, field) {
3549
+ if (raw === undefined || raw === null)
3550
+ return;
3551
+ const n = typeof raw === "number" ? raw : Number(raw);
3552
+ if (!Number.isFinite(n))
3553
+ throw new Error(`${field} must be a number between 0 and 100.`);
3554
+ if (n < 0 || n > 100)
3555
+ throw new Error(`${field} must be between 0 and 100 (received ${n}).`);
3556
+ return n;
3557
+ }
3558
+ function optionalNonNegativeNumberArg(raw, field) {
3559
+ if (raw === undefined || raw === null)
3560
+ return;
3561
+ const n = typeof raw === "number" ? raw : Number(raw);
3562
+ if (!Number.isFinite(n) || n < 0)
3563
+ throw new Error(`${field} must be a non-negative number.`);
3564
+ return n;
3565
+ }
3527
3566
  function initMemorySession(cardId, agentIdentifier, agentName, agentSessionId) {
3528
3567
  memorySessions.set(cardId, {
3529
3568
  cardId,
@@ -5371,7 +5410,6 @@ function registerHandlers(server, deps) {
5371
5410
  const { name, arguments: args } = request.params;
5372
5411
  const toolArgs = args || {};
5373
5412
  const cardIdArg = toolArgs.cardId;
5374
- const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
5375
5413
  if (cardIdArg && UUID_RE.test(cardIdArg) && deps.isConfigured()) {
5376
5414
  const isAutoStartTrigger = AUTO_START_TRIGGERS.has(name);
5377
5415
  const cv = server.getClientVersion?.();
@@ -6037,9 +6075,11 @@ async function handleToolCall(name, args, deps) {
6037
6075
  return { success: true, ...result };
6038
6076
  }
6039
6077
  case "harmony_start_agent_session": {
6040
- const cardId = z.string().uuid().parse(args.cardId);
6041
- const agentIdentifier = z.string().min(1).max(100).parse(args.agentIdentifier);
6042
- const agentName = z.string().min(1).max(100).parse(args.agentName);
6078
+ const cardId = requireUuidArg(args.cardId, "cardId");
6079
+ const agentIdentifier = requireStringArg(args.agentIdentifier, "agentIdentifier", { max: 100 });
6080
+ const agentName = requireStringArg(args.agentName, "agentName", {
6081
+ max: 100
6082
+ });
6043
6083
  const moveToColumn = args.moveToColumn;
6044
6084
  const addLabels = parseLabelList(args.addLabels);
6045
6085
  let movedTo = null;
@@ -6100,7 +6140,7 @@ async function handleToolCall(name, args, deps) {
6100
6140
  agentName,
6101
6141
  status: "working",
6102
6142
  currentTask: args.currentTask,
6103
- estimatedMinutesRemaining: args.estimatedMinutesRemaining,
6143
+ estimatedMinutesRemaining: optionalNonNegativeNumberArg(args.estimatedMinutesRemaining, "estimatedMinutesRemaining"),
6104
6144
  steerable: args.steerable === true || args.steerable === "true" ? true : undefined
6105
6145
  });
6106
6146
  markExplicit(cardId, {
@@ -6120,10 +6160,12 @@ async function handleToolCall(name, args, deps) {
6120
6160
  };
6121
6161
  }
6122
6162
  case "harmony_update_agent_progress": {
6123
- const cardId = z.string().uuid().parse(args.cardId);
6124
- const agentIdentifier = z.string().min(1).max(100).parse(args.agentIdentifier);
6125
- const agentName = z.string().min(1).max(100).parse(args.agentName);
6126
- const progressPercent = args.progressPercent !== undefined ? z.number().min(0).max(100).parse(args.progressPercent) : undefined;
6163
+ const cardId = requireUuidArg(args.cardId, "cardId");
6164
+ const agentIdentifier = requireStringArg(args.agentIdentifier, "agentIdentifier", { max: 100 });
6165
+ const agentName = requireStringArg(args.agentName, "agentName", {
6166
+ max: 100
6167
+ });
6168
+ const progressPercent = optionalPercentArg(args.progressPercent, "progressPercent");
6127
6169
  const callerActions = args.actions;
6128
6170
  const now = new Date().toISOString();
6129
6171
  const callerRecentActions = [
@@ -6148,17 +6190,17 @@ async function handleToolCall(name, args, deps) {
6148
6190
  progressPercent,
6149
6191
  currentTask: args.currentTask,
6150
6192
  blockers: args.blockers,
6151
- estimatedMinutesRemaining: args.estimatedMinutesRemaining,
6193
+ estimatedMinutesRemaining: optionalNonNegativeNumberArg(args.estimatedMinutesRemaining, "estimatedMinutesRemaining"),
6152
6194
  ...mergedRecentActions && { recentActions: mergedRecentActions },
6153
6195
  ...runActivity.length > 0 && { runActivity }
6154
6196
  });
6155
6197
  return { success: true, midSessionLearnings: 0, ...result };
6156
6198
  }
6157
6199
  case "harmony_end_agent_session": {
6158
- const cardId = z.string().uuid().parse(args.cardId);
6200
+ const cardId = requireUuidArg(args.cardId, "cardId");
6159
6201
  const moveToColumn = args.moveToColumn;
6160
6202
  const sessionStatus = args.status || "completed";
6161
- const endProgressPercent = args.progressPercent !== undefined ? z.number().min(0).max(100).parse(args.progressPercent) : undefined;
6203
+ const endProgressPercent = optionalPercentArg(args.progressPercent, "progressPercent");
6162
6204
  await flushMemoryActions(client3, cardId);
6163
6205
  cleanupMemorySession(cardId);
6164
6206
  let result = { session: null };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gethmy/mcp",
3
- "version": "2.13.0",
3
+ "version": "2.13.1",
4
4
  "description": "MCP server for Harmony Kanban board - enables AI coding agents to manage your boards",
5
5
  "publishConfig": {
6
6
  "access": "public"
package/src/server.ts CHANGED
@@ -244,6 +244,86 @@ function parseLabelList(raw: unknown): string[] | undefined {
244
244
  return undefined;
245
245
  }
246
246
 
247
+ const UUID_RE =
248
+ /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
249
+
250
+ /**
251
+ * Coerce + validate a required string argument from a (possibly lenient) MCP
252
+ * caller, throwing a clear, field-attributed error on failure.
253
+ *
254
+ * The agent-session tools previously parsed these with a bare
255
+ * `z.string().parse(args.foo)`. When the field was missing or arrived as a
256
+ * non-string, Zod surfaced an opaque root-level blob —
257
+ * `[{"expected":"string","received":"undefined","path":[],...}]` — that names
258
+ * neither the offending field nor the received value. To an agent that reads
259
+ * as "the tool rejects every argument shape with an `expected string` error"
260
+ * (card #530). Instead we name the field, report what we got, and (matching
261
+ * the `parseLabelList` philosophy for quirky clients) accept the
262
+ * number/boolean values some MCP clients emit by stringifying scalars.
263
+ */
264
+ function requireStringArg(
265
+ raw: unknown,
266
+ field: string,
267
+ opts: { max?: number } = {},
268
+ ): string {
269
+ let value: string;
270
+ if (typeof raw === "string") value = raw.trim();
271
+ else if (typeof raw === "number" || typeof raw === "boolean")
272
+ value = String(raw);
273
+ else
274
+ throw new Error(
275
+ `${field} is required and must be a string (received ${
276
+ raw === undefined || raw === null ? "nothing" : typeof raw
277
+ }).`,
278
+ );
279
+ if (value.length === 0)
280
+ throw new Error(`${field} is required and must not be empty.`);
281
+ if (opts.max && value.length > opts.max)
282
+ throw new Error(
283
+ `${field} must be at most ${opts.max} characters (received ${value.length}).`,
284
+ );
285
+ return value;
286
+ }
287
+
288
+ /** Coerce + validate a required UUID argument with a field-named error. */
289
+ function requireUuidArg(raw: unknown, field: string): string {
290
+ const value = requireStringArg(raw, field);
291
+ if (!UUID_RE.test(value))
292
+ throw new Error(`${field} must be a valid UUID (received "${value}").`);
293
+ return value;
294
+ }
295
+
296
+ /**
297
+ * Coerce an optional 0–100 percentage. Accepts numeric strings (e.g. "50")
298
+ * because lenient MCP clients stringify scalar arguments; returns `undefined`
299
+ * when absent and throws a clear, field-named error when out of range.
300
+ */
301
+ function optionalPercentArg(raw: unknown, field: string): number | undefined {
302
+ if (raw === undefined || raw === null) return undefined;
303
+ const n = typeof raw === "number" ? raw : Number(raw);
304
+ if (!Number.isFinite(n))
305
+ throw new Error(`${field} must be a number between 0 and 100.`);
306
+ if (n < 0 || n > 100)
307
+ throw new Error(`${field} must be between 0 and 100 (received ${n}).`);
308
+ return n;
309
+ }
310
+
311
+ /**
312
+ * Coerce an optional non-negative number argument (e.g.
313
+ * `estimatedMinutesRemaining`). Accepts numeric strings; returns `undefined`
314
+ * when absent so the field is omitted from the API payload entirely.
315
+ */
316
+ function optionalNonNegativeNumberArg(
317
+ raw: unknown,
318
+ field: string,
319
+ ): number | undefined {
320
+ if (raw === undefined || raw === null) return undefined;
321
+ const n = typeof raw === "number" ? raw : Number(raw);
322
+ if (!Number.isFinite(n) || n < 0)
323
+ throw new Error(`${field} must be a non-negative number.`);
324
+ return n;
325
+ }
326
+
247
327
  function initMemorySession(
248
328
  cardId: string,
249
329
  agentIdentifier: string,
@@ -2369,8 +2449,6 @@ export function registerHandlers(server: Server, deps: ToolDeps): void {
2369
2449
  // Auto-session pre-hook: track activity on card-related tools
2370
2450
  const toolArgs = args || {};
2371
2451
  const cardIdArg = toolArgs.cardId as string | undefined;
2372
- const UUID_RE =
2373
- /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
2374
2452
  if (cardIdArg && UUID_RE.test(cardIdArg) && deps.isConfigured()) {
2375
2453
  const isAutoStartTrigger = AUTO_START_TRIGGERS.has(name);
2376
2454
  // Resolve MCP client identity per request from the in-scope Server. The
@@ -3356,13 +3434,15 @@ async function handleToolCall(
3356
3434
 
3357
3435
  // Agent context operations
3358
3436
  case "harmony_start_agent_session": {
3359
- const cardId = z.string().uuid().parse(args.cardId);
3360
- const agentIdentifier = z
3361
- .string()
3362
- .min(1)
3363
- .max(100)
3364
- .parse(args.agentIdentifier);
3365
- const agentName = z.string().min(1).max(100).parse(args.agentName);
3437
+ const cardId = requireUuidArg(args.cardId, "cardId");
3438
+ const agentIdentifier = requireStringArg(
3439
+ args.agentIdentifier,
3440
+ "agentIdentifier",
3441
+ { max: 100 },
3442
+ );
3443
+ const agentName = requireStringArg(args.agentName, "agentName", {
3444
+ max: 100,
3445
+ });
3366
3446
  const moveToColumn = args.moveToColumn as string | undefined;
3367
3447
  const addLabels = parseLabelList(args.addLabels);
3368
3448
 
@@ -3449,9 +3529,10 @@ async function handleToolCall(
3449
3529
  agentName,
3450
3530
  status: "working",
3451
3531
  currentTask: args.currentTask as string | undefined,
3452
- estimatedMinutesRemaining: args.estimatedMinutesRemaining as
3453
- | number
3454
- | undefined,
3532
+ estimatedMinutesRemaining: optionalNonNegativeNumberArg(
3533
+ args.estimatedMinutesRemaining,
3534
+ "estimatedMinutesRemaining",
3535
+ ),
3455
3536
  steerable:
3456
3537
  args.steerable === true || args.steerable === "true"
3457
3538
  ? true
@@ -3482,17 +3563,19 @@ async function handleToolCall(
3482
3563
  }
3483
3564
 
3484
3565
  case "harmony_update_agent_progress": {
3485
- const cardId = z.string().uuid().parse(args.cardId);
3486
- const agentIdentifier = z
3487
- .string()
3488
- .min(1)
3489
- .max(100)
3490
- .parse(args.agentIdentifier);
3491
- const agentName = z.string().min(1).max(100).parse(args.agentName);
3492
- const progressPercent =
3493
- args.progressPercent !== undefined
3494
- ? z.number().min(0).max(100).parse(args.progressPercent)
3495
- : undefined;
3566
+ const cardId = requireUuidArg(args.cardId, "cardId");
3567
+ const agentIdentifier = requireStringArg(
3568
+ args.agentIdentifier,
3569
+ "agentIdentifier",
3570
+ { max: 100 },
3571
+ );
3572
+ const agentName = requireStringArg(args.agentName, "agentName", {
3573
+ max: 100,
3574
+ });
3575
+ const progressPercent = optionalPercentArg(
3576
+ args.progressPercent,
3577
+ "progressPercent",
3578
+ );
3496
3579
  // Convert actions parameter to recentActions format and merge with memory actions
3497
3580
  const callerActions = args.actions as
3498
3581
  | { description: string }[]
@@ -3535,9 +3618,10 @@ async function handleToolCall(
3535
3618
  progressPercent,
3536
3619
  currentTask: args.currentTask as string | undefined,
3537
3620
  blockers: args.blockers as string[] | undefined,
3538
- estimatedMinutesRemaining: args.estimatedMinutesRemaining as
3539
- | number
3540
- | undefined,
3621
+ estimatedMinutesRemaining: optionalNonNegativeNumberArg(
3622
+ args.estimatedMinutesRemaining,
3623
+ "estimatedMinutesRemaining",
3624
+ ),
3541
3625
  ...(mergedRecentActions && { recentActions: mergedRecentActions }),
3542
3626
  ...(runActivity.length > 0 && { runActivity }),
3543
3627
  });
@@ -3547,14 +3631,14 @@ async function handleToolCall(
3547
3631
  }
3548
3632
 
3549
3633
  case "harmony_end_agent_session": {
3550
- const cardId = z.string().uuid().parse(args.cardId);
3634
+ const cardId = requireUuidArg(args.cardId, "cardId");
3551
3635
  const moveToColumn = args.moveToColumn as string | undefined;
3552
3636
  const sessionStatus =
3553
3637
  (args.status as "completed" | "paused") || "completed";
3554
- const endProgressPercent =
3555
- args.progressPercent !== undefined
3556
- ? z.number().min(0).max(100).parse(args.progressPercent)
3557
- : undefined;
3638
+ const endProgressPercent = optionalPercentArg(
3639
+ args.progressPercent,
3640
+ "progressPercent",
3641
+ );
3558
3642
 
3559
3643
  // Final flush of any pending memory actions before ending the session
3560
3644
  await flushMemoryActions(client, cardId);