viviscape-mcp 2.0.1 → 2.0.2
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 +16 -16
- package/dist/api-client.js +11 -11
- package/dist/auth/auth-service.d.ts +1 -1
- package/dist/auth/auth-service.js +4 -4
- package/dist/auth/permissions.d.ts +1 -1
- package/dist/auth/permissions.js +3 -3
- package/dist/auth/token-store.d.ts +1 -1
- package/dist/auth/token-store.js +2 -2
- package/dist/config.d.ts +2 -2
- package/dist/config.js +2 -2
- package/dist/index.js +23 -23
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
# viviscape-mcp
|
|
2
2
|
|
|
3
|
-
MCP server for the [ViviScape](https://viviscape.io) API
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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,16 @@ Expose `http://localhost:8000/sse` publicly, then in ChatGPT:
|
|
|
100
100
|
|
|
101
101
|
The server exposes tools across these domains:
|
|
102
102
|
|
|
103
|
-
- **Prospects**
|
|
104
|
-
- **Clients**
|
|
105
|
-
- **Companies**
|
|
106
|
-
- **Projects**
|
|
107
|
-
- **Tasks**
|
|
108
|
-
- **Time logs**
|
|
109
|
-
- **Notes**
|
|
110
|
-
- **Insights**
|
|
111
|
-
- **Account**
|
|
112
|
-
- **Auth**
|
|
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`
|
|
113
113
|
|
|
114
114
|
## Development
|
|
115
115
|
|
package/dist/api-client.js
CHANGED
|
@@ -242,7 +242,7 @@ export class ViviScapeClient {
|
|
|
242
242
|
if (res.status >= 300 && res.status < 400)
|
|
243
243
|
throw new SessionExpiredError();
|
|
244
244
|
if (!res.ok) {
|
|
245
|
-
const snippet = text.length > 400 ? `${text.slice(0, 400)}
|
|
245
|
+
const snippet = text.length > 400 ? `${text.slice(0, 400)}...` : text;
|
|
246
246
|
throw new ApiError(res.status, `API ${method} ${path} returned ${res.status}: ${snippet}`);
|
|
247
247
|
}
|
|
248
248
|
// Cookie-auth middleware bounces unauthenticated API calls to /login, which
|
|
@@ -267,7 +267,7 @@ export class ViviScapeClient {
|
|
|
267
267
|
post(path, body) {
|
|
268
268
|
return this.request('POST', path, body);
|
|
269
269
|
}
|
|
270
|
-
//
|
|
270
|
+
// -- Prospects ----------------------------------------------
|
|
271
271
|
async addProspect(data) {
|
|
272
272
|
return this.post('prospect/add', {
|
|
273
273
|
...data,
|
|
@@ -304,7 +304,7 @@ export class ViviScapeClient {
|
|
|
304
304
|
async removeProspect(prospectId) {
|
|
305
305
|
return this.get(`prospect/remove/${prospectId}`);
|
|
306
306
|
}
|
|
307
|
-
//
|
|
307
|
+
// -- Prospect notes -----------------------------------------
|
|
308
308
|
async addProspectNote(data) {
|
|
309
309
|
return this.post('prospect/note/add', { user_id: this.userId, ...data });
|
|
310
310
|
}
|
|
@@ -317,7 +317,7 @@ export class ViviScapeClient {
|
|
|
317
317
|
async removeProspectNote(noteId) {
|
|
318
318
|
return this.get(`prospect/note/remove/${noteId}`);
|
|
319
319
|
}
|
|
320
|
-
//
|
|
320
|
+
// -- Prospect follow-ups ------------------------------------
|
|
321
321
|
async addFollowup(data) {
|
|
322
322
|
return this.post('prospect/followup/add', { user_id: this.userId, ...data });
|
|
323
323
|
}
|
|
@@ -327,7 +327,7 @@ export class ViviScapeClient {
|
|
|
327
327
|
async getFollowups(prospectId) {
|
|
328
328
|
return this.get(`prospect/followups/list/${prospectId}`);
|
|
329
329
|
}
|
|
330
|
-
//
|
|
330
|
+
// -- Companies & clients ------------------------------------
|
|
331
331
|
async listCompanies() {
|
|
332
332
|
return this.get(`user/accounts?user_id=${this.userId}&account_id=${this.accountId}`);
|
|
333
333
|
}
|
|
@@ -349,7 +349,7 @@ export class ViviScapeClient {
|
|
|
349
349
|
async getClientByEmail(data) {
|
|
350
350
|
return this.post('client/email', data);
|
|
351
351
|
}
|
|
352
|
-
//
|
|
352
|
+
// -- Projects (groups) --------------------------------------
|
|
353
353
|
async getMyProjects() {
|
|
354
354
|
return this.get(`user/groups/company?user_id=${this.userId}&account_id=${this.accountId}&companyid=0`);
|
|
355
355
|
}
|
|
@@ -371,7 +371,7 @@ export class ViviScapeClient {
|
|
|
371
371
|
async getActiveProjectsByUser(userId) {
|
|
372
372
|
return this.get(`user/groups/active?user_id=${userId || this.userId}&account_id=${this.accountId}`);
|
|
373
373
|
}
|
|
374
|
-
//
|
|
374
|
+
// -- Tasks --------------------------------------------------
|
|
375
375
|
async addTask(data) {
|
|
376
376
|
return this.post('tasks/add', buildTaskPayload(data, this.ctx));
|
|
377
377
|
}
|
|
@@ -411,14 +411,14 @@ export class ViviScapeClient {
|
|
|
411
411
|
async getCompanyTasks(companyId) {
|
|
412
412
|
return this.get(`company/tasks?company_id=${companyId}&user_id=${this.userId}`);
|
|
413
413
|
}
|
|
414
|
-
//
|
|
414
|
+
// -- Time logs ----------------------------------------------
|
|
415
415
|
async addTimeLog(data) {
|
|
416
416
|
return this.post('logs/add', buildTimeLogPayload(data, this.ctx));
|
|
417
417
|
}
|
|
418
418
|
async updateTimeLog(data) {
|
|
419
419
|
return this.post('logs/update', buildTimeLogPayload(data, this.ctx));
|
|
420
420
|
}
|
|
421
|
-
//
|
|
421
|
+
// -- Notes --------------------------------------------------
|
|
422
422
|
async getMyNotes() {
|
|
423
423
|
return this.get('notes/me');
|
|
424
424
|
}
|
|
@@ -451,7 +451,7 @@ export class ViviScapeClient {
|
|
|
451
451
|
module: 'notes',
|
|
452
452
|
});
|
|
453
453
|
}
|
|
454
|
-
//
|
|
454
|
+
// -- Account ------------------------------------------------
|
|
455
455
|
async getAccountInfo() {
|
|
456
456
|
return this.get(`account/id/${this.accountId}`);
|
|
457
457
|
}
|
|
@@ -464,7 +464,7 @@ export class ViviScapeClient {
|
|
|
464
464
|
async getUsers() {
|
|
465
465
|
return this.get(`account/users/${this.accountId}`);
|
|
466
466
|
}
|
|
467
|
-
//
|
|
467
|
+
// -- Insights -----------------------------------------------
|
|
468
468
|
async getHoursByPerson(data) {
|
|
469
469
|
const userId = Number(data.user_id) || this.userId;
|
|
470
470
|
return this.get(`insights/logs/hours/person/${userId}?account_id=${this.accountId}` +
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Credentials } from './credentials.js';
|
|
2
2
|
import { TokenStore } from './token-store.js';
|
|
3
3
|
/**
|
|
4
|
-
* Browser login over a loopback bridge
|
|
4
|
+
* Browser login over a loopback bridge - the same flow as `vs auth login`
|
|
5
5
|
* (ViviScape.CLI/Auth/AuthService.cs): open /auth/cli/connect on ViviScape Work,
|
|
6
6
|
* the page posts the signed-in session to a one-shot listener on 127.0.0.1.
|
|
7
7
|
*/
|
|
@@ -5,7 +5,7 @@ import { APP_NAME, CLI_CONNECT_PATH, baseUrl } from '../config.js';
|
|
|
5
5
|
import { parseCredentials } from './credentials.js';
|
|
6
6
|
import { createTokenStore } from './token-store.js';
|
|
7
7
|
/**
|
|
8
|
-
* Browser login over a loopback bridge
|
|
8
|
+
* Browser login over a loopback bridge - the same flow as `vs auth login`
|
|
9
9
|
* (ViviScape.CLI/Auth/AuthService.cs): open /auth/cli/connect on ViviScape Work,
|
|
10
10
|
* the page posts the signed-in session to a one-shot listener on 127.0.0.1.
|
|
11
11
|
*/
|
|
@@ -39,14 +39,14 @@ export class AuthService {
|
|
|
39
39
|
if (!creds)
|
|
40
40
|
throw new Error('The login bridge posted an unreadable payload.');
|
|
41
41
|
if (posted !== state)
|
|
42
|
-
throw new Error('State mismatch
|
|
42
|
+
throw new Error('State mismatch - possible CSRF.');
|
|
43
43
|
creds.base_url = creds.base_url || baseUrl();
|
|
44
44
|
this.store.write(creds);
|
|
45
45
|
respond(res, 200, 'application/json', '{"ok":true}');
|
|
46
46
|
settle({ creds });
|
|
47
47
|
})
|
|
48
48
|
.catch((err) => {
|
|
49
|
-
// Reject the callback but keep listening
|
|
49
|
+
// Reject the callback but keep listening - a stray or hostile post
|
|
50
50
|
// must not cancel a login the user is still completing.
|
|
51
51
|
respond(res, 400, 'application/json', JSON.stringify({ ok: false, message: err.message }));
|
|
52
52
|
log(`Rejected a login callback: ${err.message}`);
|
|
@@ -127,7 +127,7 @@ function respond(res, status, contentType, body) {
|
|
|
127
127
|
'Access-Control-Allow-Origin': '*',
|
|
128
128
|
'Access-Control-Allow-Headers': 'Content-Type',
|
|
129
129
|
'Access-Control-Allow-Methods': 'POST,GET,OPTIONS',
|
|
130
|
-
// Chrome Private Network Access
|
|
130
|
+
// Chrome Private Network Access - let a public origin reach loopback.
|
|
131
131
|
'Access-Control-Allow-Private-Network': 'true',
|
|
132
132
|
'Access-Control-Max-Age': '600',
|
|
133
133
|
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Credentials } from './credentials.js';
|
|
2
2
|
/**
|
|
3
|
-
* Plan gating
|
|
3
|
+
* Plan gating - mirrors ViviScape.CLI/Auth/Permissions.cs. The job here is a
|
|
4
4
|
* clean error when the account's tier can't reach a feature; role-level RBAC
|
|
5
5
|
* stays on the server.
|
|
6
6
|
*/
|
package/dist/auth/permissions.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { baseUrl } from '../config.js';
|
|
2
2
|
/**
|
|
3
|
-
* Plan gating
|
|
3
|
+
* Plan gating - mirrors ViviScape.CLI/Auth/Permissions.cs. The job here is a
|
|
4
4
|
* clean error when the account's tier can't reach a feature; role-level RBAC
|
|
5
5
|
* stays on the server.
|
|
6
6
|
*/
|
|
@@ -38,9 +38,9 @@ export class PermissionDeniedError extends Error {
|
|
|
38
38
|
export function requireFeature(feature, creds) {
|
|
39
39
|
const min = MIN_PLAN[feature];
|
|
40
40
|
if (min === undefined)
|
|
41
|
-
return; // unknown
|
|
41
|
+
return; // unknown -> allow
|
|
42
42
|
const forced = process.env.VS_FORCE_PLAN;
|
|
43
|
-
// An unknown tier is not a denial
|
|
43
|
+
// An unknown tier is not a denial - only gate when the login told us the plan.
|
|
44
44
|
if (!forced && !creds.plan_type)
|
|
45
45
|
return;
|
|
46
46
|
const have = forced ? parsePlan(forced) : parsePlan(creds.plan_type);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Credentials } from './credentials.js';
|
|
2
2
|
/**
|
|
3
|
-
* Credential persistence
|
|
3
|
+
* Credential persistence - Windows Credential Manager on win32 (the same store
|
|
4
4
|
* and the same blob format the ViviScape CLI uses), a 0600 file elsewhere.
|
|
5
5
|
* Mirrors ViviScape.CLI/Auth/TokenStore.cs.
|
|
6
6
|
*/
|
package/dist/auth/token-store.js
CHANGED
|
@@ -4,7 +4,7 @@ import { homedir, tmpdir } from 'node:os';
|
|
|
4
4
|
import { join } from 'node:path';
|
|
5
5
|
import { CLI_CREDENTIAL_KEY, CREDENTIAL_KEY } from '../config.js';
|
|
6
6
|
import { parseCredentials } from './credentials.js';
|
|
7
|
-
//
|
|
7
|
+
// -- Windows Credential Manager --------------------------------
|
|
8
8
|
const CRED_PS1 = `param(
|
|
9
9
|
[Parameter(Mandatory=$true)][ValidateSet('read','write','delete')][string]$Action,
|
|
10
10
|
[Parameter(Mandatory=$true)][string]$Target,
|
|
@@ -123,7 +123,7 @@ class WindowsCredentialStore {
|
|
|
123
123
|
});
|
|
124
124
|
}
|
|
125
125
|
}
|
|
126
|
-
//
|
|
126
|
+
// -- Plain file store (non-Windows, or Credential Manager unavailable) --
|
|
127
127
|
class FileStore {
|
|
128
128
|
path;
|
|
129
129
|
cliPath;
|
package/dist/config.d.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Shared configuration
|
|
2
|
+
* Shared configuration - mirrors ViviScape.CLI/Config.cs so the MCP server and
|
|
3
3
|
* the CLI talk to the same host, the same API prefix, and the same login bridge.
|
|
4
4
|
*/
|
|
5
5
|
export declare const APP_NAME = "viviscape-mcp";
|
|
6
6
|
export declare const DISPLAY_NAME = "ViviScape MCP";
|
|
7
7
|
/** Credential Manager / credential-file target for this client. */
|
|
8
8
|
export declare const CREDENTIAL_KEY = "ViviScape.MCP:default";
|
|
9
|
-
/** The CLI's target
|
|
9
|
+
/** The CLI's target - read as a fallback so `vs auth login` also signs in the MCP. */
|
|
10
10
|
export declare const CLI_CREDENTIAL_KEY = "ViviScape.CLI:default";
|
|
11
11
|
export declare const API_PREFIX = "/api/v1/core";
|
|
12
12
|
export declare const CLI_CONNECT_PATH = "/auth/cli/connect";
|
package/dist/config.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Shared configuration
|
|
2
|
+
* Shared configuration - mirrors ViviScape.CLI/Config.cs so the MCP server and
|
|
3
3
|
* the CLI talk to the same host, the same API prefix, and the same login bridge.
|
|
4
4
|
*/
|
|
5
5
|
export const APP_NAME = 'viviscape-mcp';
|
|
6
6
|
export const DISPLAY_NAME = 'ViviScape MCP';
|
|
7
7
|
/** Credential Manager / credential-file target for this client. */
|
|
8
8
|
export const CREDENTIAL_KEY = 'ViviScape.MCP:default';
|
|
9
|
-
/** The CLI's target
|
|
9
|
+
/** The CLI's target - read as a fallback so `vs auth login` also signs in the MCP. */
|
|
10
10
|
export const CLI_CREDENTIAL_KEY = 'ViviScape.CLI:default';
|
|
11
11
|
export const API_PREFIX = '/api/v1/core';
|
|
12
12
|
export const CLI_CONNECT_PATH = '/auth/cli/connect';
|
package/dist/index.js
CHANGED
|
@@ -37,9 +37,9 @@ const server = new McpServer({
|
|
|
37
37
|
function json(data) {
|
|
38
38
|
return JSON.stringify(data, null, 2);
|
|
39
39
|
}
|
|
40
|
-
//
|
|
40
|
+
// ============================================================
|
|
41
41
|
// AUTH
|
|
42
|
-
//
|
|
42
|
+
// ============================================================
|
|
43
43
|
server.tool('auth_status', 'Show the ViviScape session this server is using (user, account, role, plan)', {}, async () => {
|
|
44
44
|
const creds = auth.current();
|
|
45
45
|
if (!creds) {
|
|
@@ -98,9 +98,9 @@ server.tool('auth_logout', 'Clear the stored ViviScape session', {}, async () =>
|
|
|
98
98
|
invalidateClient();
|
|
99
99
|
return { content: [{ type: 'text', text: json({ signed_in: false }) }] };
|
|
100
100
|
});
|
|
101
|
-
//
|
|
101
|
+
// ============================================================
|
|
102
102
|
// PROSPECTS
|
|
103
|
-
//
|
|
103
|
+
// ============================================================
|
|
104
104
|
server.tool('prospect_add', 'Add a new prospect/lead to the ViviScape CRM', {
|
|
105
105
|
first_name: z.string().describe('First name'),
|
|
106
106
|
last_name: z.string().describe('Last name'),
|
|
@@ -158,9 +158,9 @@ server.tool('prospect_remove', 'Remove a prospect from the CRM', { prospect_id:
|
|
|
158
158
|
const result = await requireClient().removeProspect(prospect_id);
|
|
159
159
|
return { content: [{ type: 'text', text: json(result) }] };
|
|
160
160
|
});
|
|
161
|
-
//
|
|
161
|
+
// ============================================================
|
|
162
162
|
// PROSPECT NOTES
|
|
163
|
-
//
|
|
163
|
+
// ============================================================
|
|
164
164
|
server.tool('prospect_note_add', 'Add a note to a prospect', {
|
|
165
165
|
prospect_id: z.number().describe('Prospect ID'),
|
|
166
166
|
note: z.string().describe('Note content'),
|
|
@@ -184,9 +184,9 @@ server.tool('prospect_note_remove', 'Remove a prospect note', { note_id: z.numbe
|
|
|
184
184
|
const result = await requireClient().removeProspectNote(note_id);
|
|
185
185
|
return { content: [{ type: 'text', text: json(result) }] };
|
|
186
186
|
});
|
|
187
|
-
//
|
|
187
|
+
// ============================================================
|
|
188
188
|
// PROSPECT FOLLOWUPS
|
|
189
|
-
//
|
|
189
|
+
// ============================================================
|
|
190
190
|
server.tool('prospect_followup_add', 'Add a followup record for a prospect', {
|
|
191
191
|
prospect_id: z.number().describe('Prospect ID'),
|
|
192
192
|
contact_method: z.string().optional().describe('Contact method (e.g., email, phone, meeting)'),
|
|
@@ -213,9 +213,9 @@ server.tool('prospect_followups_list', 'Get all followup records for a prospect'
|
|
|
213
213
|
const result = await requireClient().getFollowups(prospect_id);
|
|
214
214
|
return { content: [{ type: 'text', text: json(result) }] };
|
|
215
215
|
});
|
|
216
|
-
//
|
|
216
|
+
// ============================================================
|
|
217
217
|
// COMPANIES & CLIENTS
|
|
218
|
-
//
|
|
218
|
+
// ============================================================
|
|
219
219
|
server.tool('company_list', 'List all companies', {}, async () => {
|
|
220
220
|
const result = await requireClient().listCompanies();
|
|
221
221
|
return { content: [{ type: 'text', text: json(result) }] };
|
|
@@ -269,9 +269,9 @@ server.tool('clients_by_company', 'Get all clients for a company', { company_id:
|
|
|
269
269
|
const result = await requireClient().getClientsByCompany(company_id);
|
|
270
270
|
return { content: [{ type: 'text', text: json(result) }] };
|
|
271
271
|
});
|
|
272
|
-
//
|
|
272
|
+
// ============================================================
|
|
273
273
|
// PROJECTS & TASKS
|
|
274
|
-
//
|
|
274
|
+
// ============================================================
|
|
275
275
|
server.tool('project_list', 'Get all projects for current user', {}, async () => {
|
|
276
276
|
const result = await requireClient().getMyProjects();
|
|
277
277
|
return { content: [{ type: 'text', text: json(result) }] };
|
|
@@ -359,9 +359,9 @@ server.tool('projects_active_by_user', 'Get active projects for a specific user'
|
|
|
359
359
|
const result = await requireClient().getActiveProjectsByUser(user_id);
|
|
360
360
|
return { content: [{ type: 'text', text: json(result) }] };
|
|
361
361
|
});
|
|
362
|
-
//
|
|
362
|
+
// ============================================================
|
|
363
363
|
// TIME TRACKING
|
|
364
|
-
//
|
|
364
|
+
// ============================================================
|
|
365
365
|
server.tool('timelog_add', 'Log time against a task. Duration uses shorthand format (e.g., 1h30m, 30m, 2h). Builds full LOG_Time payload.', {
|
|
366
366
|
task_id: z.number().describe('Task ID to log time against'),
|
|
367
367
|
user_id: z.number().optional().describe('User ID who performed the work. Defaults to Art Hicks (210)'),
|
|
@@ -391,9 +391,9 @@ server.tool('timelog_update', 'Update an existing time log entry. Builds full LO
|
|
|
391
391
|
const result = await requireClient().updateTimeLog(params);
|
|
392
392
|
return { content: [{ type: 'text', text: json(result) }] };
|
|
393
393
|
});
|
|
394
|
-
//
|
|
394
|
+
// ============================================================
|
|
395
395
|
// NOTES
|
|
396
|
-
//
|
|
396
|
+
// ============================================================
|
|
397
397
|
server.tool('notes_mine', 'Get all notes for the current user', {}, async () => {
|
|
398
398
|
const result = await requireClient().getMyNotes();
|
|
399
399
|
return { content: [{ type: 'text', text: json(result) }] };
|
|
@@ -427,9 +427,9 @@ server.tool('notes_query', 'Search notes', {
|
|
|
427
427
|
const result = await requireClient().queryNotes(params);
|
|
428
428
|
return { content: [{ type: 'text', text: json(result) }] };
|
|
429
429
|
});
|
|
430
|
-
//
|
|
430
|
+
// ============================================================
|
|
431
431
|
// ACCOUNT
|
|
432
|
-
//
|
|
432
|
+
// ============================================================
|
|
433
433
|
server.tool('account_info', 'Get account information', {}, async () => {
|
|
434
434
|
const result = await requireClient().getAccountInfo();
|
|
435
435
|
return { content: [{ type: 'text', text: json(result) }] };
|
|
@@ -442,9 +442,9 @@ server.tool('account_services', 'Get active services for the account', {}, async
|
|
|
442
442
|
const result = await requireClient().getActiveServices();
|
|
443
443
|
return { content: [{ type: 'text', text: json(result) }] };
|
|
444
444
|
});
|
|
445
|
-
//
|
|
445
|
+
// ============================================================
|
|
446
446
|
// INSIGHTS
|
|
447
|
-
//
|
|
447
|
+
// ============================================================
|
|
448
448
|
server.tool('insights_hours_by_person', 'Get logged hours by person', {
|
|
449
449
|
user_id: z.number().describe('User ID'),
|
|
450
450
|
start_date: z.string().describe('Start date (ISO 8601)'),
|
|
@@ -527,10 +527,10 @@ server.tool('insights_all_users_time', 'Get time totals for all users', {}, asyn
|
|
|
527
527
|
const result = await requireClient().getAllUsersTime();
|
|
528
528
|
return { content: [{ type: 'text', text: json(result) }] };
|
|
529
529
|
});
|
|
530
|
-
//
|
|
530
|
+
// ============================================================
|
|
531
531
|
// START
|
|
532
|
-
//
|
|
533
|
-
/** `viviscape-mcp login | logout | status`
|
|
532
|
+
// ============================================================
|
|
533
|
+
/** `viviscape-mcp login | logout | status` - the interactive auth surface. */
|
|
534
534
|
async function runCommand(command) {
|
|
535
535
|
switch (command) {
|
|
536
536
|
case 'login': {
|
package/package.json
CHANGED