viviscape-mcp 2.0.2 → 2.2.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 CHANGED
@@ -105,12 +105,54 @@ The server exposes tools across these domains:
105
105
  - **Companies** - `company_add`, `company_list`, `company_update`
106
106
  - **Projects** - `project_get`, `project_list`, `project_list_active`, `project_staff`, `project_tasks`, `projects_by_company`
107
107
  - **Tasks** - `task_add`, `task_get`, `task_update`, `tasks_open`, `tasks_pending`, `tasks_by_company`, `tasks_by_milestone`
108
+ - **Task comments** - `task_comments`, `task_comment_get`, `task_comment_add`, `task_comment_update`, `task_comment_remove`, `task_mark_read`
109
+ - **Task assignment** - `task_assignee_add`, `task_assignee_remove`, `task_set_leader`, `task_set_attention`, `task_delete`, `task_merge`
110
+ - **Milestones** - `milestone_list`, `milestones_active`, `milestones_by_company`, `milestones_by_user`, `milestone_get`, `milestone_add`, `milestone_update`, `milestone_clone`, `milestone_remove`
111
+ - **Project writes** - `project_add`, `project_update`, `project_set_status`, `project_user_add`, `project_user_remove`
112
+ - **Reference** - `enums` (the status, priority, and group-type values the platform accepts)
108
113
  - **Time logs** - `timelog_add`, `timelog_update`
109
- - **Notes** - `note_add`, `note_get`, `note_update`, `note_remove`, `notes_mine`, `notes_query`
114
+ - **Notes** - `note_add`, `note_get`, `note_update`, `note_remove`, `notes_mine`, `notes_query`, `notes_account`, `note_revisions`
115
+ - **Note filing** - `note_companies`, `note_users`, `note_tags`, `note_attachments`, `notebook`, `notebook_users` (each takes an `action`)
116
+ - **Files** - `project_files` (list/upload/update/delete/download), `task_files`
110
117
  - **Insights** - hours by person/service/project, AI summary, person stats, time totals
111
118
  - **Account** - `account_info`, `account_services`, `account_users`
112
119
  - **Auth** - `auth_login`, `auth_status`, `auth_logout`
113
120
 
121
+ ### Status and priority values
122
+
123
+ Use the `enums` tool rather than guessing. The platform's vocabularies are:
124
+
125
+ | field | values |
126
+ |---|---|
127
+ | task status | backlog, new, research, inprogress, pendingreview, inreview, testing, waitingcustomer, waitingteammember, onhold, cancelled, completed |
128
+ | priority | low, moderate, important, urgent, critical |
129
+ | project status | new, inprogress, complete |
130
+ | prospect status | new, firstcontact, negotiation, pending, won, lost, spam |
131
+
132
+ Two traps: tasks complete as `completed` while projects complete as
133
+ `complete`, and priority is *not* low/medium/high. Task and project tools
134
+ validate these with `z.enum`, so an invalid value is rejected before it reaches
135
+ the API.
136
+
137
+ ### Paging and field selection
138
+
139
+ List tools (`tasks_open`, `project_list_active`, `project_tasks`, and the rest)
140
+ return a page of trimmed rows rather than every column of every row. A task row
141
+ carries ~94 columns and a project row 87, most of them irrelevant to project
142
+ work, so each list tool accepts:
143
+
144
+ - `fields` - columns to return; omit for a curated default, or pass `["all"]`
145
+ - `limit` / `offset` - page window, default 50 rows
146
+
147
+ Responses are wrapped as `{ total, returned, offset, next_offset, fields, items }`
148
+ so a caller can tell when more rows exist. Task lists also accept
149
+ `company_id`, `project_id`, `assignee_id`, `status`, `priority`,
150
+ `due_before`, `due_after`, and `search`; `tasks_open` pushes `company_id`
151
+ and `only_mine` to the server and filters the rest in-process.
152
+
153
+ In practice this took `tasks_open` from 97 KB to 6.4 KB and
154
+ `project_list_active` from 375 KB to 19 KB.
155
+
114
156
  ## Development
115
157
 
116
158
  ```bash
@@ -21,6 +21,16 @@ export declare class ViviScapeClient {
21
21
  get session(): Credentials;
22
22
  private get ctx();
23
23
  private request;
24
+ /**
25
+ * Shared response handling for the binary upload paths, which cannot go
26
+ * through request() because that always sends JSON.
27
+ */
28
+ private send;
29
+ private authHeaders;
30
+ /** multipart/form-data upload; fetch sets the boundary, so no Content-Type here. */
31
+ private postMultipart;
32
+ /** Raw-body upload with metadata in headers (the groups/file/add contract). */
33
+ private postBytes;
24
34
  private get;
25
35
  private post;
26
36
  addProspect(data: Record<string, unknown>): Promise<unknown>;
@@ -52,15 +62,108 @@ export declare class ViviScapeClient {
52
62
  getProjectStaff(projectId: number): Promise<unknown>;
53
63
  getProjectsByCompany(userId: number, companyId: number): Promise<unknown>;
54
64
  getActiveProjectsByUser(userId: number): Promise<unknown>;
65
+ /**
66
+ * Create a project (group). GEN_Group has ~40 fields; live rows for this
67
+ * tenant show account_id 0, platform_account_id = pid, and group type 5
68
+ * (PROJECT), so those are the defaults here.
69
+ *
70
+ * There is no delete route for projects -- a created project can only be
71
+ * archived or renamed afterward.
72
+ */
73
+ addProject(data: {
74
+ company_id: number;
75
+ name: string;
76
+ short_description?: string;
77
+ description?: string;
78
+ status?: string;
79
+ is_public?: boolean;
80
+ staff?: number[];
81
+ }): Promise<unknown>;
82
+ /** Update a project by merging changes onto the row the API already has. */
83
+ updateProject(projectId: number, changes: Record<string, unknown>): Promise<unknown>;
84
+ /** Status-only update; leaves budget and approved time untouched. */
85
+ setProjectStatus(projectId: number, status: string, billable?: boolean): Promise<unknown>;
86
+ addProjectUser(projectId: number, userId: number, opts?: {
87
+ is_contact?: boolean;
88
+ is_lead?: boolean;
89
+ }): Promise<unknown>;
90
+ removeProjectUser(projectId: number, userId: number): Promise<unknown>;
55
91
  addTask(data: Record<string, unknown>): Promise<unknown>;
56
92
  updateTask(data: Record<string, unknown>): Promise<unknown>;
57
93
  getProjectTasks(projectId: number): Promise<unknown>;
58
- getOpenTasks(): Promise<unknown>;
94
+ /**
95
+ * The account/tasks/open route filters server-side on company, ownership, and
96
+ * a date window; pass them through instead of hardcoding "everything".
97
+ */
98
+ getOpenTasks(opts?: {
99
+ company_id?: number;
100
+ only_mine?: boolean;
101
+ team?: string;
102
+ start?: string;
103
+ end?: string;
104
+ }): Promise<unknown>;
59
105
  getTask(taskId: number): Promise<unknown>;
60
106
  getPendingTasks(userId: number): Promise<unknown>;
61
107
  getTasksByMilestone(milestoneId: number): Promise<unknown>;
62
108
  getTasksByGroupAndUser(groupId: number, userId: number): Promise<unknown>;
63
109
  getCompanyTasks(companyId: number): Promise<unknown>;
110
+ getTaskComments(taskId: number): Promise<unknown>;
111
+ getTaskComment(commentId: string): Promise<unknown>;
112
+ /**
113
+ * NOTE: on platform account 1, the backend fires a client-notification webhook
114
+ * for comments on tasks whose client_id > 0. Adding a comment can therefore
115
+ * reach the customer -- the tool description says so too.
116
+ */
117
+ addTaskComment(data: {
118
+ task_id: number;
119
+ comment: string;
120
+ client_id?: number;
121
+ source?: string;
122
+ }): Promise<unknown>;
123
+ updateTaskComment(data: {
124
+ comment_id: string;
125
+ task_id: number;
126
+ comment: string;
127
+ }): Promise<unknown>;
128
+ /**
129
+ * The backend's remove proxy answers 500 even on success: it returns 400 when
130
+ * the upstream delete fails, so a 500 means it crashed deserializing an empty
131
+ * success body (res.data.ToString() on null). Verify by re-reading the thread
132
+ * rather than reporting a failure that did not happen.
133
+ */
134
+ removeTaskComment(commentId: string, taskId?: number): Promise<unknown>;
135
+ markTaskRead(taskId: number): Promise<unknown>;
136
+ addTaskAssignee(data: {
137
+ task_id: number;
138
+ user_id: number;
139
+ leader?: boolean;
140
+ attention?: boolean;
141
+ notify?: boolean;
142
+ }): Promise<unknown>;
143
+ /** Takes the assignee_id from the task's staff_assignees, not a user_id. */
144
+ removeTaskAssignee(assigneeId: number): Promise<unknown>;
145
+ setTaskLeader(taskId: number, userId: number): Promise<unknown>;
146
+ setTaskAttention(taskId: number, userId: number, flag: boolean): Promise<unknown>;
147
+ deleteTask(taskId: number): Promise<unknown>;
148
+ /** Fold one task into another; the source task is consumed. */
149
+ mergeTasks(fromTaskId: number, toTaskId: number): Promise<unknown>;
150
+ getMilestones(projectId: number): Promise<unknown>;
151
+ getActiveMilestones(): Promise<unknown>;
152
+ getMilestonesByCompany(companyId: number): Promise<unknown>;
153
+ getMilestonesByUser(userId: number): Promise<unknown>;
154
+ getMilestone(milestoneId: number): Promise<unknown>;
155
+ addMilestone(data: Record<string, unknown>): Promise<unknown>;
156
+ updateMilestone(data: Record<string, unknown>): Promise<unknown>;
157
+ /** Copy a milestone (and its task template) into a project. */
158
+ cloneMilestone(milestoneId: number, projectId: number, title?: string): Promise<unknown>;
159
+ /**
160
+ * Known broken upstream: milestone/remove answers 500 and the milestone
161
+ * survives. The proxy returns 400 with detail when the upstream delete fails,
162
+ * so the 500 is a crash in its own success branch (Convert.ToBoolean on a
163
+ * non-boolean data payload) -- but unlike the comment-remove case the record
164
+ * is genuinely still there. Verify and say so plainly instead of pretending.
165
+ */
166
+ removeMilestone(milestoneId: number): Promise<unknown>;
64
167
  addTimeLog(data: Record<string, unknown>): Promise<unknown>;
65
168
  updateTimeLog(data: Record<string, unknown>): Promise<unknown>;
66
169
  getMyNotes(): Promise<unknown>;
@@ -69,6 +172,53 @@ export declare class ViviScapeClient {
69
172
  updateNote(data: Record<string, unknown>): Promise<unknown>;
70
173
  removeNote(noteId: number | string): Promise<unknown>;
71
174
  queryNotes(data: Record<string, unknown>): Promise<unknown>;
175
+ getAccountNotes(companyId?: number, userId?: number): Promise<unknown>;
176
+ getNoteRevisions(noteId: string): Promise<unknown>;
177
+ getNoteCompanies(noteId: string): Promise<unknown>;
178
+ addNoteCompany(noteId: string, companyId: number): Promise<unknown>;
179
+ removeNoteCompany(assignmentId: string): Promise<unknown>;
180
+ getNoteUsers(noteId: string): Promise<unknown>;
181
+ addNoteUser(noteId: string, userId: number, permission?: string): Promise<unknown>;
182
+ updateNoteUser(assignmentId: string, noteId: string, userId: number, permission: string): Promise<unknown>;
183
+ removeNoteUser(assignmentId: string): Promise<unknown>;
184
+ getNoteTags(noteId: string): Promise<unknown>;
185
+ addNoteTag(noteId: string, tag: string): Promise<unknown>;
186
+ removeNoteTag(tagId: string): Promise<unknown>;
187
+ getNoteAttachments(noteId: string): Promise<unknown>;
188
+ /** Uploads a local file as a note attachment. knowledge=true marks it for extraction. */
189
+ uploadNoteAttachment(noteId: string, filePath: string, knowledge?: boolean): Promise<unknown>;
190
+ removeNoteAttachment(attachmentId: string): Promise<unknown>;
191
+ /** Flag an attachment as knowledge-base material (drives text extraction). */
192
+ setNoteAttachmentKnowledge(attachment: Record<string, unknown>): Promise<unknown>;
193
+ getNotebooks(userId?: number): Promise<unknown>;
194
+ getNotebookNotes(bookId: string): Promise<unknown>;
195
+ addNotebook(name: string, description?: string): Promise<unknown>;
196
+ updateNotebook(bookId: string, changes: Record<string, unknown>): Promise<unknown>;
197
+ removeNotebook(bookId: string): Promise<unknown>;
198
+ /** Add or remove notes from a notebook in one call. */
199
+ assignNotesToBook(bookId: string, noteIds: string[], assign?: boolean): Promise<unknown>;
200
+ getNotebookUsers(bookId: string): Promise<unknown>;
201
+ addNotebookUser(bookId: string, userId: number, permission?: string): Promise<unknown>;
202
+ updateNotebookUser(assignmentId: string, bookId: string, userId: number, permission: string): Promise<unknown>;
203
+ removeNotebookUser(assignmentId: string): Promise<unknown>;
204
+ getProjectFiles(projectId: number, userId?: number): Promise<unknown>;
205
+ getTaskFiles(taskId: number): Promise<unknown>;
206
+ /**
207
+ * Upload a file to a project. This route takes the raw bytes as the body and
208
+ * every piece of metadata as a header, and the handler reads each header
209
+ * without a null check -- so all of them must be sent, even when empty.
210
+ */
211
+ uploadProjectFile(opts: {
212
+ project_id: number;
213
+ file_path: string;
214
+ title?: string;
215
+ task_id?: number;
216
+ notify?: boolean;
217
+ }): Promise<unknown>;
218
+ updateProjectFile(file: Record<string, unknown>): Promise<unknown>;
219
+ deleteProjectFile(fileId: number): Promise<unknown>;
220
+ /** Returns whatever the download route serves; usually a redirect target or URL. */
221
+ downloadProjectFile(fileId: number): Promise<unknown>;
72
222
  getAccountInfo(): Promise<unknown>;
73
223
  getActiveServices(): Promise<unknown>;
74
224
  getAllServices(): Promise<unknown>;