@fswap/mcp-vikunja 0.1.2 → 0.1.3
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 +1 -1
- package/dist/index.js +42 -15
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -97,7 +97,7 @@ VIKUNJA_API_TOKEN = "tk_..."
|
|
|
97
97
|
| `create_label` | `PUT /labels` | |
|
|
98
98
|
| `delete_task` | `DELETE /tasks/{id}` | only when delete is allowed (setup answer or `VIKUNJA_ALLOW_DELETE=true`) |
|
|
99
99
|
|
|
100
|
-
Vikunja's zero date (`0001-01-01T00:00:00Z`) is normalised to `null` in every response. API errors are returned to the model as `isError` results rather than crashing the server.
|
|
100
|
+
Every task and project includes a `url` to its page in the Vikunja web UI (taken from `/info` `frontend_url`). Vikunja's zero date (`0001-01-01T00:00:00Z`) is normalised to `null` in every response. API errors are returned to the model as `isError` results rather than crashing the server.
|
|
101
101
|
|
|
102
102
|
## Development
|
|
103
103
|
|
package/dist/index.js
CHANGED
|
@@ -180,10 +180,11 @@ function stripUndefined(obj) {
|
|
|
180
180
|
}
|
|
181
181
|
//#endregion
|
|
182
182
|
//#region src/tools/projects.ts
|
|
183
|
-
function summarizeProject(p) {
|
|
183
|
+
function summarizeProject(p, base) {
|
|
184
184
|
return {
|
|
185
185
|
id: p.id,
|
|
186
186
|
title: p.title,
|
|
187
|
+
url: `${base}/projects/${p.id}`,
|
|
187
188
|
description: p.description || null,
|
|
188
189
|
identifier: p.identifier || null,
|
|
189
190
|
parentProjectId: p.parent_project_id || null,
|
|
@@ -191,6 +192,16 @@ function summarizeProject(p) {
|
|
|
191
192
|
favorite: Boolean(p.is_favorite)
|
|
192
193
|
};
|
|
193
194
|
}
|
|
195
|
+
let frontendBase$1 = null;
|
|
196
|
+
async function frontendUrl$1(vikunja) {
|
|
197
|
+
if (frontendBase$1) return frontendBase$1;
|
|
198
|
+
try {
|
|
199
|
+
frontendBase$1 = ((await vikunja.get("/info")).frontend_url || vikunja.baseUrl).replace(/\/+$/, "");
|
|
200
|
+
} catch {
|
|
201
|
+
frontendBase$1 = vikunja.baseUrl;
|
|
202
|
+
}
|
|
203
|
+
return frontendBase$1;
|
|
204
|
+
}
|
|
194
205
|
function registerProjectTools(server, vikunja) {
|
|
195
206
|
server.registerTool("list_projects", {
|
|
196
207
|
title: "List projects",
|
|
@@ -202,19 +213,21 @@ function registerProjectTools(server, vikunja) {
|
|
|
202
213
|
perPage: z.number().int().min(1).max(100).default(50)
|
|
203
214
|
}
|
|
204
215
|
}, guard(async ({ search, includeArchived, page, perPage }) => {
|
|
205
|
-
|
|
216
|
+
const data = await vikunja.get("/projects", {
|
|
206
217
|
s: search,
|
|
207
218
|
is_archived: includeArchived ? true : void 0,
|
|
208
219
|
page,
|
|
209
220
|
per_page: perPage
|
|
210
|
-
})
|
|
221
|
+
});
|
|
222
|
+
const base = await frontendUrl$1(vikunja);
|
|
223
|
+
return ok(data.map((p) => summarizeProject(p, base)));
|
|
211
224
|
}));
|
|
212
225
|
server.registerTool("get_project", {
|
|
213
226
|
title: "Get project",
|
|
214
227
|
description: "Get one Vikunja project by id.",
|
|
215
228
|
inputSchema: { id: z.number().int().describe("Project id") }
|
|
216
229
|
}, guard(async ({ id }) => {
|
|
217
|
-
return ok(summarizeProject(await vikunja.get(`/projects/${id}`)));
|
|
230
|
+
return ok(summarizeProject(await vikunja.get(`/projects/${id}`), await frontendUrl$1(vikunja)));
|
|
218
231
|
}));
|
|
219
232
|
server.registerTool("create_project", {
|
|
220
233
|
title: "Create project",
|
|
@@ -228,15 +241,27 @@ function registerProjectTools(server, vikunja) {
|
|
|
228
241
|
const body = { title };
|
|
229
242
|
if (description !== void 0) body.description = description;
|
|
230
243
|
if (parentProjectId !== void 0) body.parent_project_id = parentProjectId;
|
|
231
|
-
return ok(summarizeProject(await vikunja.put("/projects", body)));
|
|
244
|
+
return ok(summarizeProject(await vikunja.put("/projects", body), await frontendUrl$1(vikunja)));
|
|
232
245
|
}));
|
|
233
246
|
}
|
|
234
247
|
//#endregion
|
|
235
248
|
//#region src/tools/tasks.ts
|
|
236
|
-
|
|
249
|
+
let frontendBase = null;
|
|
250
|
+
/** Resolve the web UI base URL once (Vikunja's API host may differ from its frontend host). */
|
|
251
|
+
async function frontendUrl(vikunja) {
|
|
252
|
+
if (frontendBase) return frontendBase;
|
|
253
|
+
try {
|
|
254
|
+
frontendBase = ((await vikunja.get("/info")).frontend_url || vikunja.baseUrl).replace(/\/+$/, "");
|
|
255
|
+
} catch {
|
|
256
|
+
frontendBase = vikunja.baseUrl;
|
|
257
|
+
}
|
|
258
|
+
return frontendBase;
|
|
259
|
+
}
|
|
260
|
+
function summarizeTask(t, base) {
|
|
237
261
|
return {
|
|
238
262
|
id: t.id,
|
|
239
263
|
title: t.title,
|
|
264
|
+
url: `${base}/tasks/${t.id}`,
|
|
240
265
|
done: t.done,
|
|
241
266
|
dueDate: normalizeDate(t.due_date),
|
|
242
267
|
priority: t.priority ?? 0,
|
|
@@ -247,9 +272,9 @@ function summarizeTask(t) {
|
|
|
247
272
|
updated: t.updated
|
|
248
273
|
};
|
|
249
274
|
}
|
|
250
|
-
function fullTask(t) {
|
|
275
|
+
function fullTask(t, base) {
|
|
251
276
|
return {
|
|
252
|
-
...summarizeTask(t),
|
|
277
|
+
...summarizeTask(t, base),
|
|
253
278
|
description: t.description || "",
|
|
254
279
|
doneAt: normalizeDate(t.done_at),
|
|
255
280
|
startDate: normalizeDate(t.start_date),
|
|
@@ -278,7 +303,7 @@ async function setLabels(vikunja, taskId, labelIds) {
|
|
|
278
303
|
function registerTaskTools(server, vikunja, { allowDelete = false, defaultProjectId = null } = {}) {
|
|
279
304
|
server.registerTool("list_tasks", {
|
|
280
305
|
title: "List tasks",
|
|
281
|
-
description: "List tasks, across all projects or within one project. Returns id, title, done, due date, priority, label names
|
|
306
|
+
description: "List tasks, across all projects or within one project. Returns id, title, done, due date, priority, label names, assignee usernames and a web url (no descriptions — use get_task). By default only open tasks are returned. Set assignedToMe=true for the current user's tasks. For advanced queries pass a raw Vikunja `filter` string such as `done = false && due_date < now+7d`, `labels in 3, 5` or `assignees in alice`.",
|
|
282
307
|
inputSchema: {
|
|
283
308
|
projectId: z.number().int().optional().describe("Limit to this project (omit for all projects)"),
|
|
284
309
|
includeDone: z.boolean().default(false).describe("Include completed tasks"),
|
|
@@ -308,21 +333,23 @@ function registerTaskTools(server, vikunja, { allowDelete = false, defaultProjec
|
|
|
308
333
|
}
|
|
309
334
|
const effectiveFilter = clauses.length > 0 ? clauses.join(" && ") : void 0;
|
|
310
335
|
const path = projectId != null ? `/projects/${projectId}/tasks` : "/tasks";
|
|
311
|
-
|
|
336
|
+
const data = await vikunja.get(path, {
|
|
312
337
|
s: search,
|
|
313
338
|
filter: effectiveFilter,
|
|
314
339
|
sort_by: sortBy,
|
|
315
340
|
order_by: orderBy,
|
|
316
341
|
page,
|
|
317
342
|
per_page: perPage
|
|
318
|
-
})
|
|
343
|
+
});
|
|
344
|
+
const base = await frontendUrl(vikunja);
|
|
345
|
+
return ok(data.map((t) => summarizeTask(t, base)));
|
|
319
346
|
}));
|
|
320
347
|
server.registerTool("get_task", {
|
|
321
348
|
title: "Get task",
|
|
322
349
|
description: "Get a single task with full details: description, dates, priority, labels and assignees.",
|
|
323
350
|
inputSchema: { id: z.number().int().describe("Task id") }
|
|
324
351
|
}, guard(async ({ id }) => {
|
|
325
|
-
return ok(fullTask(await vikunja.get(`/tasks/${id}`)));
|
|
352
|
+
return ok(fullTask(await vikunja.get(`/tasks/${id}`), await frontendUrl(vikunja)));
|
|
326
353
|
}));
|
|
327
354
|
server.registerTool("create_task", {
|
|
328
355
|
title: "Create task",
|
|
@@ -351,7 +378,7 @@ function registerTaskTools(server, vikunja, { allowDelete = false, defaultProjec
|
|
|
351
378
|
await setLabels(vikunja, task.id, labelIds);
|
|
352
379
|
task = await vikunja.get(`/tasks/${task.id}`);
|
|
353
380
|
}
|
|
354
|
-
return ok(fullTask(task));
|
|
381
|
+
return ok(fullTask(task, await frontendUrl(vikunja)));
|
|
355
382
|
}));
|
|
356
383
|
server.registerTool("update_task", {
|
|
357
384
|
title: "Update task",
|
|
@@ -392,7 +419,7 @@ function registerTaskTools(server, vikunja, { allowDelete = false, defaultProjec
|
|
|
392
419
|
await setLabels(vikunja, id, labelIds);
|
|
393
420
|
task = await vikunja.get(`/tasks/${id}`);
|
|
394
421
|
}
|
|
395
|
-
return ok(fullTask(task));
|
|
422
|
+
return ok(fullTask(task, await frontendUrl(vikunja)));
|
|
396
423
|
}));
|
|
397
424
|
server.registerTool("complete_task", {
|
|
398
425
|
title: "Complete task",
|
|
@@ -406,7 +433,7 @@ function registerTaskTools(server, vikunja, { allowDelete = false, defaultProjec
|
|
|
406
433
|
return ok(summarizeTask(await vikunja.post(`/tasks/${id}`, {
|
|
407
434
|
...current,
|
|
408
435
|
done
|
|
409
|
-
})));
|
|
436
|
+
}), await frontendUrl(vikunja)));
|
|
410
437
|
}));
|
|
411
438
|
server.registerTool("list_labels", {
|
|
412
439
|
title: "List labels",
|