multi-app-mcp 1.0.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.
@@ -0,0 +1,476 @@
1
+ // ─────────────────────────────────────────────────────────────────────────────
2
+ // DOCUMENTS — Document management tools
3
+ // ─────────────────────────────────────────────────────────────────────────────
4
+
5
+ import { getAuthHeaders, requireLogin, session } from "./session.js";
6
+
7
+ const QR_API_URL = process.env.QR_API_URL || "https://app.quickreviewer.com/api";
8
+
9
+ // ─── Tool Definitions ─────────────────────────────────────────────────────────
10
+
11
+ export const tools = [
12
+ {
13
+ name: "qr_get_documents",
14
+ description:
15
+ "Get list of documents in a folder. " +
16
+ "Use folderId=0 for root folder. " +
17
+ "User must be logged in.",
18
+ inputSchema: {
19
+ type: "object",
20
+ properties: {
21
+ folderId: { type: "integer", description: "Folder ID (0 for root)" },
22
+ },
23
+ required: ["folderId"],
24
+ },
25
+ },
26
+ {
27
+ name: "qr_get_recent_documents",
28
+ description: "Get recently accessed documents. User must be logged in.",
29
+ inputSchema: {
30
+ type: "object",
31
+ properties: {
32
+ folderId: { type: "integer", description: "Folder ID (use 0 for all)" },
33
+ },
34
+ required: ["folderId"],
35
+ },
36
+ },
37
+ {
38
+ name: "qr_get_document_detail",
39
+ description:
40
+ "Get full details of a specific document by documentId and versionId. " +
41
+ "User must be logged in.",
42
+ inputSchema: {
43
+ type: "object",
44
+ properties: {
45
+ documentId: { type: "integer", description: "Document ID" },
46
+ versionId: { type: "integer", description: "Version ID of the document" },
47
+ },
48
+ required: ["documentId", "versionId"],
49
+ },
50
+ },
51
+ {
52
+ name: "qr_get_document_versions",
53
+ description:
54
+ "Get all versions of a document. User must be logged in.",
55
+ inputSchema: {
56
+ type: "object",
57
+ properties: {
58
+ documentId: { type: "integer", description: "Document ID" },
59
+ },
60
+ required: ["documentId"],
61
+ },
62
+ },
63
+ {
64
+ name: "qr_get_document_log",
65
+ description:
66
+ "Get activity log of a document — who viewed, commented, approved, etc. " +
67
+ "User must be logged in.",
68
+ inputSchema: {
69
+ type: "object",
70
+ properties: {
71
+ versionId: { type: "integer", description: "Version ID of the document" },
72
+ },
73
+ required: ["versionId"],
74
+ },
75
+ },
76
+ {
77
+ name: "qr_rename_document",
78
+ description:
79
+ "Rename a document. Provide documentId, versionId, and the new title. " +
80
+ "User must be logged in.",
81
+ inputSchema: {
82
+ type: "object",
83
+ properties: {
84
+ documentId: { type: "integer", description: "Document ID" },
85
+ versionId: { type: "integer", description: "Version ID of the document" },
86
+ documentTitle: { type: "string", description: "New name/title for the document" },
87
+ },
88
+ required: ["documentId", "versionId", "documentTitle"],
89
+ },
90
+ },
91
+ {
92
+ name: "qr_trash_document",
93
+ description:
94
+ "Move a document to trash. Provide documentId and versionId as arrays. " +
95
+ "User must be logged in.",
96
+ inputSchema: {
97
+ type: "object",
98
+ properties: {
99
+ documentId: {
100
+ type: "array",
101
+ items: { type: "integer" },
102
+ description: "Array of document IDs to trash",
103
+ },
104
+ versionId: {
105
+ type: "array",
106
+ items: { type: "integer" },
107
+ description: "Array of version IDs to trash",
108
+ },
109
+ },
110
+ required: ["documentId", "versionId"],
111
+ },
112
+ },
113
+ {
114
+ name: "qr_move_document",
115
+ description:
116
+ "Move documents to a different folder. " +
117
+ "Provide documentId and versionId as arrays, and the target folderId. " +
118
+ "User must be logged in.",
119
+ inputSchema: {
120
+ type: "object",
121
+ properties: {
122
+ documentId: {
123
+ type: "array",
124
+ items: { type: "integer" },
125
+ description: "Array of document IDs to move",
126
+ },
127
+ versionId: {
128
+ type: "array",
129
+ items: { type: "integer" },
130
+ description: "Array of version IDs to move",
131
+ },
132
+ folderId: { type: "integer", description: "Target folder ID to move documents into" },
133
+ },
134
+ required: ["documentId", "versionId", "folderId"],
135
+ },
136
+ },
137
+ {
138
+ name: "qr_share_document",
139
+ description:
140
+ "Share a document with someone via email. " +
141
+ "shareAction can be: 'view', 'comment', or 'approve'. " +
142
+ "download: 1 to allow download, 0 to disallow. " +
143
+ "User must be logged in.",
144
+ inputSchema: {
145
+ type: "object",
146
+ properties: {
147
+ documentId: {
148
+ type: "array",
149
+ items: { type: "integer" },
150
+ description: "Array of document IDs to share",
151
+ },
152
+ versionId: {
153
+ type: "array",
154
+ items: { type: "integer" },
155
+ description: "Array of version IDs to share",
156
+ },
157
+ shareEmail: { type: "string", description: "Email address to share with" },
158
+ shareAction: { type: "string", description: "Permission: 'view', 'comment', or 'approve'" },
159
+ download: { type: "integer", description: "Allow download: 1 = yes, 0 = no" },
160
+ anybody: { type: "integer", description: "Share with anyone who has the link: 1 = yes, 0 = no" },
161
+ note: { type: "string", description: "Optional message to include with the share" },
162
+ },
163
+ required: ["documentId", "versionId", "shareEmail", "shareAction", "download"],
164
+ },
165
+ },
166
+ {
167
+ name: "qr_get_trashed_documents",
168
+ description:
169
+ "Get all trashed (deleted) documents of the logged-in user. " +
170
+ "Use folderId=0 to get all trashed documents across all folders. " +
171
+ "User must be logged in.",
172
+ inputSchema: {
173
+ type: "object",
174
+ properties: {
175
+ folderId: { type: "integer", description: "Folder ID to filter trashed docs (0 = all)" },
176
+ },
177
+ required: ["folderId"],
178
+ },
179
+ },
180
+ ];
181
+
182
+ // ─── Handlers ─────────────────────────────────────────────────────────────────
183
+
184
+ export const handlers = {
185
+
186
+ qr_get_documents: async ({ folderId }) => {
187
+ const authError = requireLogin();
188
+ if (authError) return authError;
189
+
190
+ const res = await fetch(`${QR_API_URL}/documents`, {
191
+ method: "POST",
192
+ headers: getAuthHeaders(),
193
+ body: JSON.stringify({ folderId, userId: session.userId }),
194
+ });
195
+ const data = await res.json();
196
+
197
+ if (res.ok) {
198
+ const docs = data.data || data.documents || data || [];
199
+ const count = Array.isArray(docs) ? docs.length : "unknown";
200
+ return {
201
+ content: [{
202
+ type: "text",
203
+ text:
204
+ `✅ Documents fetched (${count} found)\n\n` +
205
+ `Folder ID: ${folderId}\n\n` +
206
+ `${JSON.stringify(data, null, 2)}`,
207
+ }],
208
+ };
209
+ }
210
+ return {
211
+ content: [{
212
+ type: "text",
213
+ text: `❌ Failed (HTTP ${res.status})\n\n${JSON.stringify(data, null, 2)}`,
214
+ }],
215
+ isError: true,
216
+ };
217
+ },
218
+
219
+ qr_get_recent_documents: async ({ folderId }) => {
220
+ const authError = requireLogin();
221
+ if (authError) return authError;
222
+
223
+ const res = await fetch(`${QR_API_URL}/recent`, {
224
+ method: "POST",
225
+ headers: getAuthHeaders(),
226
+ body: JSON.stringify({ folderId, userId: session.userId }),
227
+ });
228
+ const data = await res.json();
229
+
230
+ if (res.ok) {
231
+ return {
232
+ content: [{
233
+ type: "text",
234
+ text: `✅ Recent documents fetched\n\n${JSON.stringify(data, null, 2)}`,
235
+ }],
236
+ };
237
+ }
238
+ return {
239
+ content: [{
240
+ type: "text",
241
+ text: `❌ Failed (HTTP ${res.status})\n\n${JSON.stringify(data, null, 2)}`,
242
+ }],
243
+ isError: true,
244
+ };
245
+ },
246
+
247
+ qr_get_document_detail: async ({ documentId, versionId }) => {
248
+ const authError = requireLogin();
249
+ if (authError) return authError;
250
+
251
+ const res = await fetch(`${QR_API_URL}/document/document-detail`, {
252
+ method: "POST",
253
+ headers: getAuthHeaders(),
254
+ body: JSON.stringify({ documentId, versionId }),
255
+ });
256
+ const data = await res.json();
257
+
258
+ if (res.ok) {
259
+ return {
260
+ content: [{
261
+ type: "text",
262
+ text: `✅ Document detail fetched\n\n${JSON.stringify(data, null, 2)}`,
263
+ }],
264
+ };
265
+ }
266
+ return {
267
+ content: [{
268
+ type: "text",
269
+ text: `❌ Failed (HTTP ${res.status})\n\n${JSON.stringify(data, null, 2)}`,
270
+ }],
271
+ isError: true,
272
+ };
273
+ },
274
+
275
+ qr_get_document_versions: async ({ documentId }) => {
276
+ const authError = requireLogin();
277
+ if (authError) return authError;
278
+
279
+ const res = await fetch(`${QR_API_URL}/document/document-versions`, {
280
+ method: "POST",
281
+ headers: getAuthHeaders(),
282
+ body: JSON.stringify({ documentId }),
283
+ });
284
+ const data = await res.json();
285
+
286
+ if (res.ok) {
287
+ return {
288
+ content: [{
289
+ type: "text",
290
+ text: `✅ Document versions fetched\n\n${JSON.stringify(data, null, 2)}`,
291
+ }],
292
+ };
293
+ }
294
+ return {
295
+ content: [{
296
+ type: "text",
297
+ text: `❌ Failed (HTTP ${res.status})\n\n${JSON.stringify(data, null, 2)}`,
298
+ }],
299
+ isError: true,
300
+ };
301
+ },
302
+
303
+ qr_get_document_log: async ({ versionId }) => {
304
+ const authError = requireLogin();
305
+ if (authError) return authError;
306
+
307
+ const res = await fetch(`${QR_API_URL}/document/log`, {
308
+ method: "POST",
309
+ headers: getAuthHeaders(),
310
+ body: JSON.stringify({ versionId }),
311
+ });
312
+ const data = await res.json();
313
+
314
+ if (res.ok) {
315
+ return {
316
+ content: [{
317
+ type: "text",
318
+ text: `✅ Activity log fetched\n\n${JSON.stringify(data, null, 2)}`,
319
+ }],
320
+ };
321
+ }
322
+ return {
323
+ content: [{
324
+ type: "text",
325
+ text: `❌ Failed (HTTP ${res.status})\n\n${JSON.stringify(data, null, 2)}`,
326
+ }],
327
+ isError: true,
328
+ };
329
+ },
330
+
331
+ qr_rename_document: async ({ documentId, versionId, documentTitle }) => {
332
+ const authError = requireLogin();
333
+ if (authError) return authError;
334
+
335
+ const res = await fetch(`${QR_API_URL}/document/rename`, {
336
+ method: "POST",
337
+ headers: getAuthHeaders(),
338
+ body: JSON.stringify({ documentId, versionId, documentTitle }),
339
+ });
340
+ const data = await res.json();
341
+
342
+ if (res.ok) {
343
+ return {
344
+ content: [{
345
+ type: "text",
346
+ text: `✅ Document renamed successfully!\n\nNew title: ${documentTitle}`,
347
+ }],
348
+ };
349
+ }
350
+ return {
351
+ content: [{
352
+ type: "text",
353
+ text: `❌ Failed (HTTP ${res.status})\n\n${JSON.stringify(data, null, 2)}`,
354
+ }],
355
+ isError: true,
356
+ };
357
+ },
358
+
359
+ qr_trash_document: async ({ documentId, versionId }) => {
360
+ const authError = requireLogin();
361
+ if (authError) return authError;
362
+
363
+ const res = await fetch(`${QR_API_URL}/document/trash`, {
364
+ method: "PATCH",
365
+ headers: getAuthHeaders(),
366
+ body: JSON.stringify({ documentId, versionId }),
367
+ });
368
+ const data = await res.json();
369
+
370
+ if (res.ok) {
371
+ return {
372
+ content: [{
373
+ type: "text",
374
+ text: `✅ Document(s) moved to trash!\n\nDocument IDs: ${documentId.join(", ")}`,
375
+ }],
376
+ };
377
+ }
378
+ return {
379
+ content: [{
380
+ type: "text",
381
+ text: `❌ Failed (HTTP ${res.status})\n\n${JSON.stringify(data, null, 2)}`,
382
+ }],
383
+ isError: true,
384
+ };
385
+ },
386
+
387
+ qr_move_document: async ({ documentId, versionId, folderId }) => {
388
+ const authError = requireLogin();
389
+ if (authError) return authError;
390
+
391
+ const res = await fetch(`${QR_API_URL}/document/move`, {
392
+ method: "PATCH",
393
+ headers: getAuthHeaders(),
394
+ body: JSON.stringify({ documentId, versionId, folderId }),
395
+ });
396
+ const data = await res.json();
397
+
398
+ if (res.ok) {
399
+ return {
400
+ content: [{
401
+ type: "text",
402
+ text: `✅ Document(s) moved successfully!\n\nMoved to folder ID: ${folderId}`,
403
+ }],
404
+ };
405
+ }
406
+ return {
407
+ content: [{
408
+ type: "text",
409
+ text: `❌ Failed (HTTP ${res.status})\n\n${JSON.stringify(data, null, 2)}`,
410
+ }],
411
+ isError: true,
412
+ };
413
+ },
414
+
415
+ qr_share_document: async ({ documentId, versionId, shareEmail, shareAction, download, anybody = 0, note = "" }) => {
416
+ const authError = requireLogin();
417
+ if (authError) return authError;
418
+
419
+ const res = await fetch(`${QR_API_URL}/document/share`, {
420
+ method: "POST",
421
+ headers: getAuthHeaders(),
422
+ body: JSON.stringify({ documentId, versionId, shareEmail, shareAction, download, anybody, note }),
423
+ });
424
+ const data = await res.json();
425
+
426
+ if (res.ok) {
427
+ return {
428
+ content: [{
429
+ type: "text",
430
+ text:
431
+ `✅ Document shared successfully!\n\n` +
432
+ `Shared with: ${shareEmail}\n` +
433
+ `Permission: ${shareAction}\n` +
434
+ `Download allowed: ${download === 1 ? "Yes" : "No"}`,
435
+ }],
436
+ };
437
+ }
438
+ return {
439
+ content: [{
440
+ type: "text",
441
+ text: `❌ Failed (HTTP ${res.status})\n\n${JSON.stringify(data, null, 2)}`,
442
+ }],
443
+ isError: true,
444
+ };
445
+ },
446
+
447
+ qr_get_trashed_documents: async ({ folderId = 0 }) => {
448
+ const authError = requireLogin();
449
+ if (authError) return authError;
450
+
451
+ const res = await fetch(`${QR_API_URL}/trashed`, {
452
+ method: "POST",
453
+ headers: getAuthHeaders(),
454
+ body: JSON.stringify({ folderId }),
455
+ });
456
+ const data = await res.json();
457
+
458
+ if (res.ok) {
459
+ const items = Array.isArray(data) ? data : (data.rows || []);
460
+ return {
461
+ content: [{
462
+ type: "text",
463
+ text: `✅ Trashed documents fetched (${items.length} items)\n\n${JSON.stringify(data, null, 2)}`,
464
+ }],
465
+ };
466
+ }
467
+ return {
468
+ content: [{
469
+ type: "text",
470
+ text: `❌ Failed (HTTP ${res.status})\n\n${JSON.stringify(data, null, 2)}`,
471
+ }],
472
+ isError: true,
473
+ };
474
+ },
475
+
476
+ };