viviscape-mcp 2.2.0 → 2.3.0
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 +6 -0
- package/dist/index.js +8 -8
- package/dist/projection.d.ts +21 -1
- package/dist/projection.js +38 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -144,6 +144,12 @@ work, so each list tool accepts:
|
|
|
144
144
|
- `fields` - columns to return; omit for a curated default, or pass `["all"]`
|
|
145
145
|
- `limit` / `offset` - page window, default 50 rows
|
|
146
146
|
|
|
147
|
+
Task rows also carry computed `due_date`, `due_in_days` (negative when
|
|
148
|
+
overdue), and `overdue`, derived from the task's `end` date. Read those rather
|
|
149
|
+
than the API's `deadline` string, which does not track the due date -- it
|
|
150
|
+
reported "In 2 Days" for a task 39 days overdue. `deadline` is excluded from the
|
|
151
|
+
default field set; ask for it explicitly if you need to see what the UI shows.
|
|
152
|
+
|
|
147
153
|
Responses are wrapped as `{ total, returned, offset, next_offset, fields, items }`
|
|
148
154
|
so a caller can tell when more rows exist. Task lists also accept
|
|
149
155
|
`company_id`, `project_id`, `assignee_id`, `status`, `priority`,
|
package/dist/index.js
CHANGED
|
@@ -7,7 +7,7 @@ import { ViviScapeClient } from './api-client.js';
|
|
|
7
7
|
import { AuthService } from './auth/auth-service.js';
|
|
8
8
|
import { describe } from './auth/credentials.js';
|
|
9
9
|
import { PRIORITIES, PROJECT_STATUSES, PROSPECT_STATUSES, TASK_STATUSES, enumReference, } from './enums.js';
|
|
10
|
-
import { FILE_FIELDS, NOTE_FIELDS, PROJECT_FIELDS, TASK_FIELDS, filterTasks, shape, } from './projection.js';
|
|
10
|
+
import { FILE_FIELDS, NOTE_FIELDS, PROJECT_FIELDS, TASK_FIELDS, annotateTasks, filterTasks, shape, } from './projection.js';
|
|
11
11
|
import { baseUrl } from './config.js';
|
|
12
12
|
// quiet: dotenv's banner goes to stdout, which is the MCP protocol channel.
|
|
13
13
|
config({ quiet: true });
|
|
@@ -336,11 +336,11 @@ server.tool('project_tasks', 'Get all tasks for a project. Returns a page of tri
|
|
|
336
336
|
// project_id is the query, not a filter -- re-filtering on it would drop
|
|
337
337
|
// every row if the response names the column differently.
|
|
338
338
|
const rows = Array.isArray(result)
|
|
339
|
-
? filterTasks(result, { ...params, project_id: undefined })
|
|
339
|
+
? annotateTasks(filterTasks(result, { ...params, project_id: undefined }))
|
|
340
340
|
: result;
|
|
341
341
|
return { content: [{ type: 'text', text: json(shape(rows, TASK_FIELDS, paging(params))) }] };
|
|
342
342
|
});
|
|
343
|
-
server.tool('tasks_open', 'Get open tasks across projects. company_id and only_mine filter server-side; the rest filter in-process. Returns a page of trimmed rows.', {
|
|
343
|
+
server.tool('tasks_open', 'Get open tasks across projects. company_id and only_mine filter server-side; the rest filter in-process. Returns a page of trimmed rows with computed due_date / due_in_days / overdue -- read those, not the API deadline string, which is unreliable.', {
|
|
344
344
|
only_mine: z.boolean().optional().describe('Only tasks assigned to the signed-in user (server-side)'),
|
|
345
345
|
team: z.string().optional().describe('Team filter passed through to the API'),
|
|
346
346
|
...taskFilterArgs,
|
|
@@ -351,7 +351,7 @@ server.tool('tasks_open', 'Get open tasks across projects. company_id and only_m
|
|
|
351
351
|
only_mine: params.only_mine,
|
|
352
352
|
team: params.team,
|
|
353
353
|
});
|
|
354
|
-
const rows = Array.isArray(result) ? filterTasks(result, params) : result;
|
|
354
|
+
const rows = Array.isArray(result) ? annotateTasks(filterTasks(result, params)) : result;
|
|
355
355
|
return { content: [{ type: 'text', text: json(shape(rows, TASK_FIELDS, paging(params))) }] };
|
|
356
356
|
});
|
|
357
357
|
server.tool('task_add', 'Add a task to a project. The API requires a full Group_Task payload; this tool builds it from simplified params.', {
|
|
@@ -394,7 +394,7 @@ server.tool('tasks_pending', 'Get pending tasks for a user. Returns a page of tr
|
|
|
394
394
|
...taskFilterArgs,
|
|
395
395
|
}, async (params) => {
|
|
396
396
|
const result = await requireClient().getPendingTasks(params.user_id);
|
|
397
|
-
const rows = Array.isArray(result) ? filterTasks(result, params) : result;
|
|
397
|
+
const rows = Array.isArray(result) ? annotateTasks(filterTasks(result, params)) : result;
|
|
398
398
|
return { content: [{ type: 'text', text: json(shape(rows, TASK_FIELDS, paging(params))) }] };
|
|
399
399
|
});
|
|
400
400
|
server.tool('tasks_by_milestone', 'Get tasks for a milestone. Returns a page of trimmed rows; see fields/limit/offset.', {
|
|
@@ -403,7 +403,7 @@ server.tool('tasks_by_milestone', 'Get tasks for a milestone. Returns a page of
|
|
|
403
403
|
...taskFilterArgs,
|
|
404
404
|
}, async (params) => {
|
|
405
405
|
const result = await requireClient().getTasksByMilestone(params.milestone_id);
|
|
406
|
-
const rows = Array.isArray(result) ? filterTasks(result, params) : result;
|
|
406
|
+
const rows = Array.isArray(result) ? annotateTasks(filterTasks(result, params)) : result;
|
|
407
407
|
return { content: [{ type: 'text', text: json(shape(rows, TASK_FIELDS, paging(params))) }] };
|
|
408
408
|
});
|
|
409
409
|
server.tool('tasks_by_group_user', 'Get tasks by project group and user. Returns a page of trimmed rows; see fields/limit/offset.', {
|
|
@@ -413,7 +413,7 @@ server.tool('tasks_by_group_user', 'Get tasks by project group and user. Returns
|
|
|
413
413
|
...taskFilterArgs,
|
|
414
414
|
}, async (params) => {
|
|
415
415
|
const result = await requireClient().getTasksByGroupAndUser(params.group_id, params.user_id);
|
|
416
|
-
const rows = Array.isArray(result) ? filterTasks(result, params) : result;
|
|
416
|
+
const rows = Array.isArray(result) ? annotateTasks(filterTasks(result, params)) : result;
|
|
417
417
|
return { content: [{ type: 'text', text: json(shape(rows, TASK_FIELDS, paging(params))) }] };
|
|
418
418
|
});
|
|
419
419
|
server.tool('tasks_by_company', 'Get tasks for a company. Returns a page of trimmed rows; see fields/limit/offset.', {
|
|
@@ -423,7 +423,7 @@ server.tool('tasks_by_company', 'Get tasks for a company. Returns a page of trim
|
|
|
423
423
|
}, async (params) => {
|
|
424
424
|
const result = await requireClient().getCompanyTasks(params.company_id);
|
|
425
425
|
const rows = Array.isArray(result)
|
|
426
|
-
? filterTasks(result, { ...params, company_id: undefined })
|
|
426
|
+
? annotateTasks(filterTasks(result, { ...params, company_id: undefined }))
|
|
427
427
|
: result;
|
|
428
428
|
return { content: [{ type: 'text', text: json(shape(rows, TASK_FIELDS, paging(params))) }] };
|
|
429
429
|
});
|
package/dist/projection.d.ts
CHANGED
|
@@ -8,7 +8,16 @@
|
|
|
8
8
|
* read. These helpers trim to a PM-relevant default, page the result, and tell
|
|
9
9
|
* the caller what was left out so it can ask for more deliberately.
|
|
10
10
|
*/
|
|
11
|
-
/**
|
|
11
|
+
/**
|
|
12
|
+
* Task fields worth returning by default, of roughly 90 available.
|
|
13
|
+
*
|
|
14
|
+
* `deadline` is deliberately excluded. It is a server-rendered string that does
|
|
15
|
+
* not track the task's own due date: measured against six live tasks it read
|
|
16
|
+
* "In 2 Days" for one 39 days overdue, and gave "In 4 Days" for tasks at both
|
|
17
|
+
* -9 and +2 days. `end` is the real due date, and `pastdue` agrees with it on
|
|
18
|
+
* every row, so annotateTasks() derives due_date / due_in_days / overdue from
|
|
19
|
+
* `end` and those are what callers should read.
|
|
20
|
+
*/
|
|
12
21
|
export declare const TASK_FIELDS: string[];
|
|
13
22
|
/** Project (group) fields worth returning by default, of 87 available. */
|
|
14
23
|
export declare const PROJECT_FIELDS: string[];
|
|
@@ -38,6 +47,17 @@ export interface TaskFilters {
|
|
|
38
47
|
}
|
|
39
48
|
/** Apply the filters the backend does not support, in-process. */
|
|
40
49
|
export declare function filterTasks(rows: unknown[], f: TaskFilters): unknown[];
|
|
50
|
+
/**
|
|
51
|
+
* Add an unambiguous due-date signal to task rows.
|
|
52
|
+
*
|
|
53
|
+
* The API ships three overlapping fields: `end` (the real due date), `pastdue`
|
|
54
|
+
* (a boolean that matches it), and `deadline` (a human string that does not).
|
|
55
|
+
* Rather than make every caller know which to trust, derive the answer from
|
|
56
|
+
* `end` and pass `deadline` through only when explicitly requested.
|
|
57
|
+
*
|
|
58
|
+
* due_in_days is negative for overdue work, 0 for due today.
|
|
59
|
+
*/
|
|
60
|
+
export declare function annotateTasks(rows: unknown[], now?: number): unknown[];
|
|
41
61
|
/**
|
|
42
62
|
* Trim, page, and annotate a list response. Non-array payloads (a single record,
|
|
43
63
|
* or an error object) pass through untouched.
|
package/dist/projection.js
CHANGED
|
@@ -8,12 +8,22 @@
|
|
|
8
8
|
* read. These helpers trim to a PM-relevant default, page the result, and tell
|
|
9
9
|
* the caller what was left out so it can ask for more deliberately.
|
|
10
10
|
*/
|
|
11
|
-
/**
|
|
11
|
+
/**
|
|
12
|
+
* Task fields worth returning by default, of roughly 90 available.
|
|
13
|
+
*
|
|
14
|
+
* `deadline` is deliberately excluded. It is a server-rendered string that does
|
|
15
|
+
* not track the task's own due date: measured against six live tasks it read
|
|
16
|
+
* "In 2 Days" for one 39 days overdue, and gave "In 4 Days" for tasks at both
|
|
17
|
+
* -9 and +2 days. `end` is the real due date, and `pastdue` agrees with it on
|
|
18
|
+
* every row, so annotateTasks() derives due_date / due_in_days / overdue from
|
|
19
|
+
* `end` and those are what callers should read.
|
|
20
|
+
*/
|
|
12
21
|
export const TASK_FIELDS = [
|
|
13
22
|
'task_id', 'task', 'status', 'percentage', 'priority',
|
|
14
23
|
'company_id', 'company_name', 'group_id', 'group_name',
|
|
15
24
|
'milestone_id', 'milestone', 'estimated_time', 'total_duration_str',
|
|
16
|
-
'
|
|
25
|
+
'start', 'due_date', 'due_in_days', 'overdue', 'pastdue',
|
|
26
|
+
'staff_assignees', 'total_comments', 'unread_for_me',
|
|
17
27
|
];
|
|
18
28
|
/** Project (group) fields worth returning by default, of 87 available. */
|
|
19
29
|
export const PROJECT_FIELDS = [
|
|
@@ -92,6 +102,32 @@ export function filterTasks(rows, f) {
|
|
|
92
102
|
return true;
|
|
93
103
|
});
|
|
94
104
|
}
|
|
105
|
+
/**
|
|
106
|
+
* Add an unambiguous due-date signal to task rows.
|
|
107
|
+
*
|
|
108
|
+
* The API ships three overlapping fields: `end` (the real due date), `pastdue`
|
|
109
|
+
* (a boolean that matches it), and `deadline` (a human string that does not).
|
|
110
|
+
* Rather than make every caller know which to trust, derive the answer from
|
|
111
|
+
* `end` and pass `deadline` through only when explicitly requested.
|
|
112
|
+
*
|
|
113
|
+
* due_in_days is negative for overdue work, 0 for due today.
|
|
114
|
+
*/
|
|
115
|
+
export function annotateTasks(rows, now = Date.now()) {
|
|
116
|
+
return rows.map((row) => {
|
|
117
|
+
if (!isRecord(row))
|
|
118
|
+
return row;
|
|
119
|
+
const due = Date.parse(String(row.end ?? ''));
|
|
120
|
+
if (!Number.isFinite(due)) {
|
|
121
|
+
return { ...row, due_date: null, due_in_days: null, overdue: null };
|
|
122
|
+
}
|
|
123
|
+
return {
|
|
124
|
+
...row,
|
|
125
|
+
due_date: new Date(due).toISOString(),
|
|
126
|
+
due_in_days: Math.round((due - now) / 86_400_000),
|
|
127
|
+
overdue: due < now,
|
|
128
|
+
};
|
|
129
|
+
});
|
|
130
|
+
}
|
|
95
131
|
function pick(row, fields) {
|
|
96
132
|
if (!isRecord(row))
|
|
97
133
|
return row;
|
package/package.json
CHANGED