@kaneo/mcp 0.1.10 → 0.1.12

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/README.md CHANGED
@@ -8,7 +8,7 @@ It runs over stdio, signs in with Kaneo's device flow, and then calls the Kaneo
8
8
 
9
9
  ## Prerequisites
10
10
 
11
- - Node.js 20+
11
+ - Node.js 24+
12
12
  - A running Kaneo API (for example `http://localhost:1337`) and web app (for device approval UI).
13
13
 
14
14
  Kaneo allows `kaneo-cli` and `kaneo-mcp` by default, so you usually do not need extra server configuration.
@@ -118,7 +118,7 @@ For headless or sandboxed environments where opening a browser is impractical, s
118
118
  - Session: `whoami`, `list_workspaces`, `list_workspace_members`, `list_notifications`
119
119
  - Search: `search`
120
120
  - Projects: `list_projects`, `get_project`, `create_project`, `update_project`, `list_project_columns`
121
- - Tasks: `list_tasks`, `get_task`, `create_task`, `update_task`, `delete_task`, `move_task`, `update_task_status`, `update_task_assignee`, `update_task_due_date`, `list_task_activity`
121
+ - Tasks: `list_tasks`, `get_task`, `get_task_by_ticket_id`, `create_task`, `update_task`, `delete_task`, `move_task`, `update_task_status`, `update_task_assignee`, `update_task_due_date`, `list_task_activity`
122
122
  - Time entries: `list_task_time_entries`, `get_time_entry`, `create_time_entry`, `update_time_entry`
123
123
  - Comments: `list_task_comments`, `create_task_comment`, `update_task_comment`, `delete_task_comment`
124
124
  - Labels: `list_workspace_labels`, `create_label`, `attach_label_to_task`, `detach_label_from_task`, `delete_label`
@@ -126,6 +126,9 @@ For headless or sandboxed environments where opening a browser is impractical, s
126
126
 
127
127
  Call `list_project_columns` before setting a status: the column slugs it
128
128
  returns are the values `create_task` and `update_task_status` accept.
129
+ Call `get_task_by_ticket_id` with a key such as `KAN-12` to retrieve one task
130
+ without listing a project. If the same key identifies multiple accessible tasks,
131
+ pass `workspaceId` or `projectId` to select one.
129
132
  `list_workspace_members` resolves the user IDs the assignee tools expect.
130
133
  Time entries have no delete endpoint on the API, so there is no
131
134
  `delete_time_entry` tool.
@@ -9,6 +9,7 @@ export declare class KaneoClient {
9
9
  baseUrl: string;
10
10
  auth: AuthService;
11
11
  });
12
+ get usingApiKey(): boolean;
12
13
  private authorizedFetch;
13
14
  json<T = Json>(path: string, init?: RequestInit): Promise<T>;
14
15
  }
@@ -5,6 +5,9 @@ export class KaneoClient {
5
5
  this.baseUrl = options.baseUrl;
6
6
  this.auth = options.auth;
7
7
  }
8
+ get usingApiKey() {
9
+ return this.auth.usingApiKey;
10
+ }
8
11
  async authorizedFetch(path, init, didRetry = false) {
9
12
  const token = await this.auth.getAccessToken();
10
13
  const headers = new Headers(init?.headers);
@@ -1,5 +1,15 @@
1
- import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
- import type { KaneoClient } from "../kaneo/client.js";
3
- export declare function registerTools(server: McpServer, ctx: {
4
- client: KaneoClient;
1
+ import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
2
+ import { z } from "zod";
3
+ export type ToolClient = {
4
+ readonly usingApiKey?: boolean;
5
+ json<T = unknown>(path: string, init?: RequestInit): Promise<T>;
6
+ };
7
+ export type ToolRegistrar = {
8
+ registerTool(name: string, config: {
9
+ description: string;
10
+ inputSchema: z.ZodObject;
11
+ }, callback: (args: unknown) => Promise<CallToolResult>): unknown;
12
+ };
13
+ export declare function registerTools(server: ToolRegistrar, ctx: {
14
+ client: ToolClient;
5
15
  }): void;
@@ -26,15 +26,21 @@ function run(fn) {
26
26
  }
27
27
  export function registerTools(server, ctx) {
28
28
  const { client } = ctx;
29
- server.registerTool("whoami", {
30
- description: "Return the current Kaneo session and user for the cached device token.",
29
+ const registerTool = (name, config, callback) => server.registerTool(name, config, async (args) => {
30
+ const parsed = config.inputSchema.safeParse(args);
31
+ if (!parsed.success)
32
+ return errorResult(z.prettifyError(parsed.error));
33
+ return callback(parsed.data);
34
+ });
35
+ registerTool("whoami", {
36
+ description: "Return the current Kaneo user for the configured authentication method.",
31
37
  inputSchema: z.object({}),
32
- }, async () => run(() => client.json("/api/auth/get-session", { method: "GET" })));
33
- server.registerTool("list_workspaces", {
38
+ }, async () => run(() => client.json(client.usingApiKey ? "/api/user/me" : "/api/auth/get-session", { method: "GET" })));
39
+ registerTool("list_workspaces", {
34
40
  description: "List workspaces (Better Auth organizations) the signed-in user can access.",
35
41
  inputSchema: z.object({}),
36
42
  }, async () => run(() => client.json("/api/auth/organization/list", { method: "GET" })));
37
- server.registerTool("list_projects", {
43
+ registerTool("list_projects", {
38
44
  description: "List projects in a workspace.",
39
45
  inputSchema: z.object({
40
46
  workspaceId: nonEmptyString.describe("Workspace ID"),
@@ -51,11 +57,11 @@ export function registerTools(server, ctx) {
51
57
  }
52
58
  return run(() => client.json(`/api/project?${qs.toString()}`, { method: "GET" }));
53
59
  });
54
- server.registerTool("get_project", {
60
+ registerTool("get_project", {
55
61
  description: "Get a single project by ID.",
56
62
  inputSchema: z.object({ id: nonEmptyString }),
57
63
  }, async (args) => run(() => client.json(`/api/project/${encodeURIComponent(args.id)}`)));
58
- server.registerTool("create_project", {
64
+ registerTool("create_project", {
59
65
  description: "Create a project in a workspace.",
60
66
  inputSchema: z.object({
61
67
  name: nonEmptyString,
@@ -72,7 +78,7 @@ export function registerTools(server, ctx) {
72
78
  slug: args.slug,
73
79
  }),
74
80
  })));
75
- server.registerTool("update_project", {
81
+ registerTool("update_project", {
76
82
  description: "Update project metadata (PATCH-style: only provided fields are changed).",
77
83
  inputSchema: z.object({
78
84
  id: nonEmptyString,
@@ -123,8 +129,9 @@ export function registerTools(server, ctx) {
123
129
  status: optionalNonEmptyString,
124
130
  priority: prioritySchema.optional(),
125
131
  assigneeId: optionalNonEmptyString,
126
- page: z.number().int().positive().optional(),
127
- limit: z.number().int().positive().optional(),
132
+ page: z.number().int().min(1).max(1_000_000).optional(),
133
+ relatedPage: z.number().int().min(1).max(1_000_000).optional(),
134
+ limit: z.number().int().min(1).max(100).optional(),
128
135
  sortBy: z
129
136
  .enum(["createdAt", "priority", "dueDate", "position", "title", "number"])
130
137
  .optional(),
@@ -132,8 +139,8 @@ export function registerTools(server, ctx) {
132
139
  dueBefore: optionalIsoDateTimeSchema,
133
140
  dueAfter: optionalIsoDateTimeSchema,
134
141
  });
135
- server.registerTool("list_tasks", {
136
- description: "List tasks for a project (optionally filtered/sorted).",
142
+ registerTool("list_tasks", {
143
+ description: "List a bounded page of tasks for a project (50 by default, maximum 100). Use pagination.totalPages and page to retrieve the rest; filters and sorting apply before pagination. For every task page, also follow relatedPage through pagination.relatedTotalPages for complete labels, links and column metadata.",
137
144
  inputSchema: listTasksSchema,
138
145
  }, async (args) => {
139
146
  const { projectId, ...rest } = args;
@@ -148,13 +155,29 @@ export function registerTools(server, ctx) {
148
155
  const path = `/api/task/tasks/${encodeURIComponent(projectId)}${q ? `?${q}` : ""}`;
149
156
  return run(() => client.json(path, { method: "GET" }));
150
157
  });
151
- server.registerTool("get_task", {
158
+ registerTool("get_task", {
152
159
  description: "Get a task by ID.",
153
160
  inputSchema: z.object({ taskId: nonEmptyString }),
154
161
  }, async (args) => run(() => client.json(`/api/task/${encodeURIComponent(args.taskId)}`, {
155
162
  method: "GET",
156
163
  })));
157
- server.registerTool("create_task", {
164
+ registerTool("get_task_by_ticket_id", {
165
+ description: "Get one task by its ticket ID (project key and number, e.g. KAN-12). If multiple accessible tasks share the ID, provide workspaceId or projectId.",
166
+ inputSchema: z.object({
167
+ ticketId: nonEmptyString,
168
+ workspaceId: optionalNonEmptyString,
169
+ projectId: optionalNonEmptyString,
170
+ }),
171
+ }, async ({ ticketId, workspaceId, projectId }) => {
172
+ const query = new URLSearchParams();
173
+ if (workspaceId)
174
+ query.set("workspaceId", workspaceId);
175
+ if (projectId)
176
+ query.set("projectId", projectId);
177
+ const suffix = query.toString();
178
+ return run(() => client.json(`/api/task/by-ticket-id/${encodeURIComponent(ticketId)}${suffix ? `?${suffix}` : ""}`, { method: "GET" }));
179
+ });
180
+ registerTool("create_task", {
158
181
  description: "Create a task in a project.",
159
182
  inputSchema: z.object({
160
183
  projectId: nonEmptyString,
@@ -187,6 +210,22 @@ export function registerTools(server, ctx) {
187
210
  body: JSON.stringify(body),
188
211
  }));
189
212
  });
213
+ registerTool("duplicate_task", {
214
+ description: "Duplicate a task in the same project, copying its fields and labels. Pass title to rename the copy.",
215
+ inputSchema: z.object({
216
+ taskId: nonEmptyString,
217
+ title: optionalNonEmptyString,
218
+ }),
219
+ }, async (args) => {
220
+ const body = {};
221
+ if (args.title !== undefined) {
222
+ body.title = args.title;
223
+ }
224
+ return run(() => client.json(`/api/task/duplicate/${encodeURIComponent(args.taskId)}`, {
225
+ method: "POST",
226
+ body: JSON.stringify(body),
227
+ }));
228
+ });
190
229
  const updateTaskSchema = z.object({
191
230
  taskId: nonEmptyString,
192
231
  title: optionalNonEmptyString,
@@ -199,7 +238,7 @@ export function registerTools(server, ctx) {
199
238
  dueDate: nullableOptionalIsoDateTimeSchema,
200
239
  userId: nullableOptionalNonEmptyString,
201
240
  });
202
- server.registerTool("update_task", {
241
+ registerTool("update_task", {
203
242
  description: "Update a task (fetches current task, merges fields, then full update).",
204
243
  inputSchema: updateTaskSchema,
205
244
  }, async (args) => {
@@ -213,7 +252,7 @@ export function registerTools(server, ctx) {
213
252
  });
214
253
  });
215
254
  });
216
- server.registerTool("move_task", {
255
+ registerTool("move_task", {
217
256
  description: "Move a task to another project (and optional column status).",
218
257
  inputSchema: z.object({
219
258
  taskId: nonEmptyString,
@@ -229,7 +268,7 @@ export function registerTools(server, ctx) {
229
268
  : {}),
230
269
  }),
231
270
  })));
232
- server.registerTool("update_task_status", {
271
+ registerTool("update_task_status", {
233
272
  description: "Update only the status (column) of a task.",
234
273
  inputSchema: z.object({
235
274
  taskId: nonEmptyString,
@@ -239,13 +278,13 @@ export function registerTools(server, ctx) {
239
278
  method: "PUT",
240
279
  body: JSON.stringify({ status: args.status }),
241
280
  })));
242
- server.registerTool("list_task_comments", {
281
+ registerTool("list_task_comments", {
243
282
  description: "List comments on a task.",
244
283
  inputSchema: z.object({ taskId: nonEmptyString }),
245
284
  }, async (args) => run(() => client.json(`/api/comment/${encodeURIComponent(args.taskId)}`, {
246
285
  method: "GET",
247
286
  })));
248
- server.registerTool("create_task_comment", {
287
+ registerTool("create_task_comment", {
249
288
  description: "Add a comment to a task.",
250
289
  inputSchema: z.object({
251
290
  taskId: nonEmptyString,
@@ -255,7 +294,7 @@ export function registerTools(server, ctx) {
255
294
  method: "POST",
256
295
  body: JSON.stringify({ content: args.content }),
257
296
  })));
258
- server.registerTool("update_task_comment", {
297
+ registerTool("update_task_comment", {
259
298
  description: "Update one of your comments on a task.",
260
299
  inputSchema: z.object({
261
300
  commentId: nonEmptyString,
@@ -265,17 +304,17 @@ export function registerTools(server, ctx) {
265
304
  method: "PUT",
266
305
  body: JSON.stringify({ content: args.content }),
267
306
  })));
268
- server.registerTool("delete_task_comment", {
307
+ registerTool("delete_task_comment", {
269
308
  description: "Delete one of your comments from a task.",
270
309
  inputSchema: z.object({ commentId: nonEmptyString }),
271
310
  }, async (args) => run(() => client.json(`/api/comment/${encodeURIComponent(args.commentId)}`, {
272
311
  method: "DELETE",
273
312
  })));
274
- server.registerTool("list_workspace_labels", {
313
+ registerTool("list_workspace_labels", {
275
314
  description: "List labels defined in a workspace.",
276
315
  inputSchema: z.object({ workspaceId: nonEmptyString }),
277
316
  }, async (args) => run(() => client.json(`/api/label/workspace/${encodeURIComponent(args.workspaceId)}`, { method: "GET" })));
278
- server.registerTool("create_label", {
317
+ registerTool("create_label", {
279
318
  description: "Create a label in a workspace (optionally attach to a task).",
280
319
  inputSchema: z.object({
281
320
  name: nonEmptyString,
@@ -292,7 +331,7 @@ export function registerTools(server, ctx) {
292
331
  ...(args.taskId !== undefined ? { taskId: args.taskId } : {}),
293
332
  }),
294
333
  })));
295
- server.registerTool("attach_label_to_task", {
334
+ registerTool("attach_label_to_task", {
296
335
  description: "Attach an existing label to a task.",
297
336
  inputSchema: z.object({
298
337
  labelId: nonEmptyString,
@@ -302,13 +341,13 @@ export function registerTools(server, ctx) {
302
341
  method: "PUT",
303
342
  body: JSON.stringify({ taskId: args.taskId }),
304
343
  })));
305
- server.registerTool("detach_label_from_task", {
344
+ registerTool("detach_label_from_task", {
306
345
  description: "Detach a label from its current task.",
307
346
  inputSchema: z.object({ labelId: nonEmptyString }),
308
347
  }, async (args) => run(() => client.json(`/api/label/${encodeURIComponent(args.labelId)}/task`, {
309
348
  method: "DELETE",
310
349
  })));
311
- server.registerTool("create_task_relation", {
350
+ registerTool("create_task_relation", {
312
351
  description: "Create a relation between two tasks. relationType: 'subtask' (sourceTaskId is the parent, targetTaskId the child), 'blocks' (sourceTaskId blocks targetTaskId), or 'related' (bidirectional).",
313
352
  inputSchema: z.object({
314
353
  sourceTaskId: nonEmptyString,
@@ -323,35 +362,49 @@ export function registerTools(server, ctx) {
323
362
  relationType: args.relationType,
324
363
  }),
325
364
  })));
326
- server.registerTool("get_task_relations", {
365
+ registerTool("get_task_relations", {
327
366
  description: "List all relations (subtask/blocks/related) involving a task.",
328
367
  inputSchema: z.object({ taskId: nonEmptyString }),
329
368
  }, async (args) => run(() => client.json(`/api/task-relation/${encodeURIComponent(args.taskId)}`, {
330
369
  method: "GET",
331
370
  })));
332
- server.registerTool("delete_task_relation", {
371
+ registerTool("delete_task_relation", {
333
372
  description: "Delete a task relation by its relation ID.",
334
373
  inputSchema: z.object({ id: nonEmptyString }),
335
374
  }, async (args) => run(() => client.json(`/api/task-relation/${encodeURIComponent(args.id)}`, {
336
375
  method: "DELETE",
337
376
  })));
338
- server.registerTool("delete_label", {
339
- description: "Delete a label by ID. Only task-associated labels can be deleted; workspace-level labels (taskId null) are rejected by the API.",
377
+ registerTool("delete_label", {
378
+ description: "Delete a task or workspace label by ID. Workspace-label cascades resume automatically; repeat the tool if pendingDeletion remains true.",
340
379
  inputSchema: z.object({ id: nonEmptyString }),
341
380
  }, async (args) => run(async () => {
342
- const label = (await client.json(`/api/label/${encodeURIComponent(args.id)}`, { method: "GET" }));
343
- if (!label?.taskId) {
344
- throw new Error("Label is not associated with a task and cannot be deleted (workspace-level labels are not deletable via this endpoint).");
381
+ let result;
382
+ const signal = AbortSignal.timeout(10_000);
383
+ // Bound work per tool call. The API persists the continuation boundary.
384
+ for (let page = 0; page < 100; page++) {
385
+ if (page > 0 && signal.aborted)
386
+ break;
387
+ try {
388
+ result = await client.json(`/api/label/${encodeURIComponent(args.id)}`, { method: "DELETE", signal });
389
+ }
390
+ catch (error) {
391
+ if (signal.aborted && result)
392
+ break;
393
+ throw error;
394
+ }
395
+ if (!result ||
396
+ typeof result !== "object" ||
397
+ !("pendingDeletion" in result) ||
398
+ !result.pendingDeletion)
399
+ break;
345
400
  }
346
- return client.json(`/api/label/${encodeURIComponent(args.id)}`, {
347
- method: "DELETE",
348
- });
401
+ return result;
349
402
  }));
350
- server.registerTool("list_workspace_members", {
403
+ registerTool("list_workspace_members", {
351
404
  description: "List the members of a workspace. Use this to resolve the user ID an assignee tool expects.",
352
405
  inputSchema: z.object({ workspaceId: nonEmptyString }),
353
406
  }, async (args) => run(() => client.json(`/api/workspace/${encodeURIComponent(args.workspaceId)}/members`)));
354
- server.registerTool("search", {
407
+ registerTool("search", {
355
408
  description: "Search across tasks, projects, workspaces, comments, and activities.",
356
409
  inputSchema: z.object({
357
410
  q: nonEmptyString.describe("Search query"),
@@ -388,17 +441,17 @@ export function registerTools(server, ctx) {
388
441
  qs.set("limit", String(args.limit));
389
442
  return run(() => client.json(`/api/search?${qs.toString()}`));
390
443
  });
391
- server.registerTool("list_project_columns", {
444
+ registerTool("list_project_columns", {
392
445
  description: "List a project's columns. Their slugs are the values update_task_status and create_task accept as a status.",
393
446
  inputSchema: z.object({ projectId: nonEmptyString }),
394
447
  }, async (args) => run(() => client.json(`/api/column/${encodeURIComponent(args.projectId)}`)));
395
- server.registerTool("delete_task", {
448
+ registerTool("delete_task", {
396
449
  description: "Delete a task by ID.",
397
450
  inputSchema: z.object({ taskId: nonEmptyString }),
398
451
  }, async (args) => run(() => client.json(`/api/task/${encodeURIComponent(args.taskId)}`, {
399
452
  method: "DELETE",
400
453
  })));
401
- server.registerTool("update_task_assignee", {
454
+ registerTool("update_task_assignee", {
402
455
  description: "Assign a task to a workspace member, or pass a null userId to unassign it.",
403
456
  inputSchema: z.object({
404
457
  taskId: nonEmptyString,
@@ -410,7 +463,7 @@ export function registerTools(server, ctx) {
410
463
  method: "PUT",
411
464
  body: JSON.stringify({ userId: args.userId }),
412
465
  })));
413
- server.registerTool("update_task_due_date", {
466
+ registerTool("update_task_due_date", {
414
467
  description: "Set a task's due date. Omit dueDate to clear it.",
415
468
  inputSchema: z.object({
416
469
  taskId: nonEmptyString,
@@ -420,15 +473,15 @@ export function registerTools(server, ctx) {
420
473
  method: "PUT",
421
474
  body: JSON.stringify(args.dueDate === undefined ? {} : { dueDate: args.dueDate }),
422
475
  })));
423
- server.registerTool("list_task_time_entries", {
476
+ registerTool("list_task_time_entries", {
424
477
  description: "List the time entries logged against a task.",
425
478
  inputSchema: z.object({ taskId: nonEmptyString }),
426
479
  }, async (args) => run(() => client.json(`/api/time-entry/task/${encodeURIComponent(args.taskId)}`)));
427
- server.registerTool("get_time_entry", {
480
+ registerTool("get_time_entry", {
428
481
  description: "Get a single time entry by ID.",
429
482
  inputSchema: z.object({ id: nonEmptyString }),
430
483
  }, async (args) => run(() => client.json(`/api/time-entry/${encodeURIComponent(args.id)}`)));
431
- server.registerTool("create_time_entry", {
484
+ registerTool("create_time_entry", {
432
485
  description: "Log time against a task. Omit endTime to leave the entry running.",
433
486
  inputSchema: z.object({
434
487
  taskId: nonEmptyString,
@@ -445,8 +498,8 @@ export function registerTools(server, ctx) {
445
498
  ...(args.description ? { description: args.description } : {}),
446
499
  }),
447
500
  })));
448
- server.registerTool("update_time_entry", {
449
- description: "Update a time entry. startTime is required; omit endTime to reopen the entry.",
501
+ registerTool("update_time_entry", {
502
+ description: "Update a time entry. startTime is required; omitting endTime keeps the stored one. startTime cannot be later than the end time.",
450
503
  inputSchema: z.object({
451
504
  id: nonEmptyString,
452
505
  startTime: isoDateTimeSchema,
@@ -461,11 +514,11 @@ export function registerTools(server, ctx) {
461
514
  ...(args.description ? { description: args.description } : {}),
462
515
  }),
463
516
  })));
464
- server.registerTool("list_task_activity", {
517
+ registerTool("list_task_activity", {
465
518
  description: "List a task's activity history.",
466
519
  inputSchema: z.object({ taskId: nonEmptyString }),
467
520
  }, async (args) => run(() => client.json(`/api/activity/${encodeURIComponent(args.taskId)}`)));
468
- server.registerTool("list_notifications", {
521
+ registerTool("list_notifications", {
469
522
  description: "List the signed-in user's notifications.",
470
523
  inputSchema: z.object({}),
471
524
  }, async () => run(() => client.json("/api/notification")));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kaneo/mcp",
3
- "version": "0.1.10",
3
+ "version": "0.1.12",
4
4
  "mcpName": "io.github.usekaneo/kaneo",
5
5
  "description": "Official MCP (Model Context Protocol) server for Kaneo, the open source project management platform: manage tasks, projects, and labels from Claude, Cursor, and other MCP clients",
6
6
  "license": "MIT",
@@ -19,6 +19,10 @@
19
19
  ".": {
20
20
  "import": "./dist/server.js",
21
21
  "types": "./dist/server.d.ts"
22
+ },
23
+ "./tools": {
24
+ "types": "./dist/tools/register.d.ts",
25
+ "import": "./dist/tools/register.js"
22
26
  }
23
27
  },
24
28
  "publishConfig": {
@@ -30,39 +34,39 @@
30
34
  "LICENSE"
31
35
  ],
32
36
  "keywords": [
37
+ "ai-agents",
38
+ "claude",
39
+ "kanban",
40
+ "kaneo",
41
+ "kaneo-mcp",
33
42
  "mcp",
34
43
  "mcp-server",
35
44
  "model-context-protocol",
36
- "kaneo",
37
- "kaneo-mcp",
38
45
  "project-management",
39
- "tasks",
40
- "kanban",
41
- "ai-agents",
42
- "claude",
43
- "stdio"
46
+ "stdio",
47
+ "tasks"
44
48
  ],
45
49
  "scripts": {
46
- "build": "tsc -p tsconfig.json",
50
+ "build": "vp run compile",
47
51
  "prepublishOnly": "pnpm run build",
48
52
  "dev": "tsx watch src/index.ts",
49
- "lint": "biome check --write .",
53
+ "lint": "vp check .",
50
54
  "start": "node dist/index.js",
51
- "test": "vitest run --config vitest.config.ts",
52
- "test:watch": "vitest --config vitest.config.ts"
55
+ "test": "vp run test:run",
56
+ "test:watch": "vp test --config vitest.config.ts"
53
57
  },
54
58
  "dependencies": {
55
- "@modelcontextprotocol/sdk": "^1.30.0",
56
- "open": "^11.0.0",
59
+ "@modelcontextprotocol/sdk": "^1.30.1",
60
+ "open": "^11.0.4",
57
61
  "prompts": "^2.4.2",
58
- "zod": "^4.4.3"
62
+ "zod": "^4.6.5"
59
63
  },
60
64
  "devDependencies": {
61
65
  "@kaneo/typescript-config": "workspace:*",
62
- "@types/node": "^26.1.2",
66
+ "@types/node": "^26.6.2",
63
67
  "@types/prompts": "^2.4.9",
64
- "tsx": "^4.23.9",
68
+ "tsx": "^4.23.15",
65
69
  "typescript": "^7.0.2",
66
- "vitest": "^4.1.10"
70
+ "vite-plus": "catalog:"
67
71
  }
68
72
  }