viviscape-mcp 2.0.1 → 2.1.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
@@ -1,10 +1,10 @@
1
1
  # viviscape-mcp
2
2
 
3
- MCP server for the [ViviScape](https://viviscape.io) API exposes CRM, projects, companies, tasks, time tracking, notes, and insights as tools for Claude and other MCP clients.
3
+ MCP server for the [ViviScape](https://viviscape.io) API - exposes CRM, projects, companies, tasks, time tracking, notes, and insights as tools for Claude and other MCP clients.
4
4
 
5
5
  ## Installation
6
6
 
7
- No install needed run via `npx`:
7
+ No install needed - run via `npx`:
8
8
 
9
9
  ```bash
10
10
  npx viviscape-mcp
@@ -18,7 +18,7 @@ npm install -g viviscape-mcp
18
18
 
19
19
  ## Sign in
20
20
 
21
- Authentication is per-user, the same browser flow the ViviScape CLI uses no
21
+ Authentication is per-user, the same browser flow the ViviScape CLI uses - no
22
22
  API keys. Sign in once:
23
23
 
24
24
  ```bash
@@ -74,7 +74,7 @@ args = ["-y", "viviscape-mcp"]
74
74
 
75
75
  ## ChatGPT (Developer Mode / Apps SDK)
76
76
 
77
- ChatGPT only accepts **remote HTTP** MCP servers it does not run local stdio
77
+ ChatGPT only accepts **remote HTTP** MCP servers - it does not run local stdio
78
78
  processes. Bridge this server to an HTTP endpoint with
79
79
  [`mcp-remote`](https://www.npmjs.com/package/mcp-remote) or
80
80
  [`supergateway`](https://www.npmjs.com/package/supergateway), then expose it
@@ -88,11 +88,11 @@ npx -y supergateway --stdio "npx -y viviscape-mcp" --port 8000
88
88
  ```
89
89
 
90
90
  The gateway shares the host's stored session, so everyone reaching that endpoint
91
- acts as the signed-in user put your own auth in front of it.
91
+ acts as the signed-in user - put your own auth in front of it.
92
92
 
93
93
  Expose `http://localhost:8000/sse` publicly, then in ChatGPT:
94
94
 
95
- **Settings Connectors Create Custom MCP server**
95
+ **Settings -> Connectors -> Create -> Custom MCP server**
96
96
  - **URL:** `https://your-public-host/sse`
97
97
  - **Auth:** none (or bearer, depending on your gateway)
98
98
 
@@ -100,16 +100,56 @@ Expose `http://localhost:8000/sse` publicly, then in ChatGPT:
100
100
 
101
101
  The server exposes tools across these domains:
102
102
 
103
- - **Prospects** `prospect_add`, `prospect_get`, `prospect_update`, `prospect_query`, follow-ups, notes
104
- - **Clients** `client_add`, `client_get`, `client_get_by_email`, `clients_by_company`
105
- - **Companies** `company_add`, `company_list`, `company_update`
106
- - **Projects** `project_get`, `project_list`, `project_list_active`, `project_staff`, `project_tasks`, `projects_by_company`
107
- - **Tasks** `task_add`, `task_get`, `task_update`, `tasks_open`, `tasks_pending`, `tasks_by_company`, `tasks_by_milestone`
108
- - **Time logs** `timelog_add`, `timelog_update`
109
- - **Notes** `note_add`, `note_get`, `note_update`, `note_remove`, `notes_mine`, `notes_query`
110
- - **Insights** hours by person/service/project, AI summary, person stats, time totals
111
- - **Account** `account_info`, `account_services`, `account_users`
112
- - **Auth** `auth_login`, `auth_status`, `auth_logout`
103
+ - **Prospects** - `prospect_add`, `prospect_get`, `prospect_update`, `prospect_query`, follow-ups, notes
104
+ - **Clients** - `client_add`, `client_get`, `client_get_by_email`, `clients_by_company`
105
+ - **Companies** - `company_add`, `company_list`, `company_update`
106
+ - **Projects** - `project_get`, `project_list`, `project_list_active`, `project_staff`, `project_tasks`, `projects_by_company`
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)
113
+ - **Time logs** - `timelog_add`, `timelog_update`
114
+ - **Notes** - `note_add`, `note_get`, `note_update`, `note_remove`, `notes_mine`, `notes_query`
115
+ - **Insights** - hours by person/service/project, AI summary, person stats, time totals
116
+ - **Account** - `account_info`, `account_services`, `account_users`
117
+ - **Auth** - `auth_login`, `auth_status`, `auth_logout`
118
+
119
+ ### Status and priority values
120
+
121
+ Use the `enums` tool rather than guessing. The platform's vocabularies are:
122
+
123
+ | field | values |
124
+ |---|---|
125
+ | task status | backlog, new, research, inprogress, pendingreview, inreview, testing, waitingcustomer, waitingteammember, onhold, cancelled, completed |
126
+ | priority | low, moderate, important, urgent, critical |
127
+ | project status | new, inprogress, complete |
128
+ | prospect status | new, firstcontact, negotiation, pending, won, lost, spam |
129
+
130
+ Two traps: tasks complete as `completed` while projects complete as
131
+ `complete`, and priority is *not* low/medium/high. Task and project tools
132
+ validate these with `z.enum`, so an invalid value is rejected before it reaches
133
+ the API.
134
+
135
+ ### Paging and field selection
136
+
137
+ List tools (`tasks_open`, `project_list_active`, `project_tasks`, and the rest)
138
+ return a page of trimmed rows rather than every column of every row. A task row
139
+ carries ~94 columns and a project row 87, most of them irrelevant to project
140
+ work, so each list tool accepts:
141
+
142
+ - `fields` - columns to return; omit for a curated default, or pass `["all"]`
143
+ - `limit` / `offset` - page window, default 50 rows
144
+
145
+ Responses are wrapped as `{ total, returned, offset, next_offset, fields, items }`
146
+ so a caller can tell when more rows exist. Task lists also accept
147
+ `company_id`, `project_id`, `assignee_id`, `status`, `priority`,
148
+ `due_before`, `due_after`, and `search`; `tasks_open` pushes `company_id`
149
+ and `only_mine` to the server and filters the rest in-process.
150
+
151
+ In practice this took `tasks_open` from 97 KB to 6.4 KB and
152
+ `project_list_active` from 375 KB to 19 KB.
113
153
 
114
154
  ## Development
115
155
 
@@ -52,15 +52,108 @@ export declare class ViviScapeClient {
52
52
  getProjectStaff(projectId: number): Promise<unknown>;
53
53
  getProjectsByCompany(userId: number, companyId: number): Promise<unknown>;
54
54
  getActiveProjectsByUser(userId: number): Promise<unknown>;
55
+ /**
56
+ * Create a project (group). GEN_Group has ~40 fields; live rows for this
57
+ * tenant show account_id 0, platform_account_id = pid, and group type 5
58
+ * (PROJECT), so those are the defaults here.
59
+ *
60
+ * There is no delete route for projects -- a created project can only be
61
+ * archived or renamed afterward.
62
+ */
63
+ addProject(data: {
64
+ company_id: number;
65
+ name: string;
66
+ short_description?: string;
67
+ description?: string;
68
+ status?: string;
69
+ is_public?: boolean;
70
+ staff?: number[];
71
+ }): Promise<unknown>;
72
+ /** Update a project by merging changes onto the row the API already has. */
73
+ updateProject(projectId: number, changes: Record<string, unknown>): Promise<unknown>;
74
+ /** Status-only update; leaves budget and approved time untouched. */
75
+ setProjectStatus(projectId: number, status: string, billable?: boolean): Promise<unknown>;
76
+ addProjectUser(projectId: number, userId: number, opts?: {
77
+ is_contact?: boolean;
78
+ is_lead?: boolean;
79
+ }): Promise<unknown>;
80
+ removeProjectUser(projectId: number, userId: number): Promise<unknown>;
55
81
  addTask(data: Record<string, unknown>): Promise<unknown>;
56
82
  updateTask(data: Record<string, unknown>): Promise<unknown>;
57
83
  getProjectTasks(projectId: number): Promise<unknown>;
58
- getOpenTasks(): Promise<unknown>;
84
+ /**
85
+ * The account/tasks/open route filters server-side on company, ownership, and
86
+ * a date window; pass them through instead of hardcoding "everything".
87
+ */
88
+ getOpenTasks(opts?: {
89
+ company_id?: number;
90
+ only_mine?: boolean;
91
+ team?: string;
92
+ start?: string;
93
+ end?: string;
94
+ }): Promise<unknown>;
59
95
  getTask(taskId: number): Promise<unknown>;
60
96
  getPendingTasks(userId: number): Promise<unknown>;
61
97
  getTasksByMilestone(milestoneId: number): Promise<unknown>;
62
98
  getTasksByGroupAndUser(groupId: number, userId: number): Promise<unknown>;
63
99
  getCompanyTasks(companyId: number): Promise<unknown>;
100
+ getTaskComments(taskId: number): Promise<unknown>;
101
+ getTaskComment(commentId: string): Promise<unknown>;
102
+ /**
103
+ * NOTE: on platform account 1, the backend fires a client-notification webhook
104
+ * for comments on tasks whose client_id > 0. Adding a comment can therefore
105
+ * reach the customer -- the tool description says so too.
106
+ */
107
+ addTaskComment(data: {
108
+ task_id: number;
109
+ comment: string;
110
+ client_id?: number;
111
+ source?: string;
112
+ }): Promise<unknown>;
113
+ updateTaskComment(data: {
114
+ comment_id: string;
115
+ task_id: number;
116
+ comment: string;
117
+ }): Promise<unknown>;
118
+ /**
119
+ * The backend's remove proxy answers 500 even on success: it returns 400 when
120
+ * the upstream delete fails, so a 500 means it crashed deserializing an empty
121
+ * success body (res.data.ToString() on null). Verify by re-reading the thread
122
+ * rather than reporting a failure that did not happen.
123
+ */
124
+ removeTaskComment(commentId: string, taskId?: number): Promise<unknown>;
125
+ markTaskRead(taskId: number): Promise<unknown>;
126
+ addTaskAssignee(data: {
127
+ task_id: number;
128
+ user_id: number;
129
+ leader?: boolean;
130
+ attention?: boolean;
131
+ notify?: boolean;
132
+ }): Promise<unknown>;
133
+ /** Takes the assignee_id from the task's staff_assignees, not a user_id. */
134
+ removeTaskAssignee(assigneeId: number): Promise<unknown>;
135
+ setTaskLeader(taskId: number, userId: number): Promise<unknown>;
136
+ setTaskAttention(taskId: number, userId: number, flag: boolean): Promise<unknown>;
137
+ deleteTask(taskId: number): Promise<unknown>;
138
+ /** Fold one task into another; the source task is consumed. */
139
+ mergeTasks(fromTaskId: number, toTaskId: number): Promise<unknown>;
140
+ getMilestones(projectId: number): Promise<unknown>;
141
+ getActiveMilestones(): Promise<unknown>;
142
+ getMilestonesByCompany(companyId: number): Promise<unknown>;
143
+ getMilestonesByUser(userId: number): Promise<unknown>;
144
+ getMilestone(milestoneId: number): Promise<unknown>;
145
+ addMilestone(data: Record<string, unknown>): Promise<unknown>;
146
+ updateMilestone(data: Record<string, unknown>): Promise<unknown>;
147
+ /** Copy a milestone (and its task template) into a project. */
148
+ cloneMilestone(milestoneId: number, projectId: number, title?: string): Promise<unknown>;
149
+ /**
150
+ * Known broken upstream: milestone/remove answers 500 and the milestone
151
+ * survives. The proxy returns 400 with detail when the upstream delete fails,
152
+ * so the 500 is a crash in its own success branch (Convert.ToBoolean on a
153
+ * non-boolean data payload) -- but unlike the comment-remove case the record
154
+ * is genuinely still there. Verify and say so plainly instead of pretending.
155
+ */
156
+ removeMilestone(milestoneId: number): Promise<unknown>;
64
157
  addTimeLog(data: Record<string, unknown>): Promise<unknown>;
65
158
  updateTimeLog(data: Record<string, unknown>): Promise<unknown>;
66
159
  getMyNotes(): Promise<unknown>;