rootr-cli 0.4.0 → 0.4.1
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 +7 -0
- package/lib/mcp-tools/attachments.js +3 -2
- package/lib/mcp-tools/crm.js +29 -15
- package/lib/mcp-tools/databases.js +20 -14
- package/lib/mcp-tools/documents.js +11 -2
- package/lib/mcp-tools/forms.js +19 -10
- package/lib/mcp-tools/issues.js +7 -4
- package/lib/mcp-tools/logs.js +11 -6
- package/lib/mcp-tools/presentations.js +17 -9
- package/lib/mcp-tools/recordings.js +15 -8
- package/lib/mcp-tools/shared.js +26 -0
- package/lib/mcp-tools/spreadsheets.js +15 -8
- package/lib/mcp-tools/webhooks.js +3 -2
- package/lib/mcp-tools/whiteboards.js +7 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -206,6 +206,13 @@ rootr ask "왜 결제 실패가 늘었지?" --workspace ws_123
|
|
|
206
206
|
|
|
207
207
|
`rootr mcp`는 stdio로 MCP(Model Context Protocol) 서버를 구동합니다. 제공 도구 29종:
|
|
208
208
|
|
|
209
|
+
노드를 가리키는 인자(`target`, `databaseId`, `spreadsheetId`, `formId`, `crmId`, `trackerId`,
|
|
210
|
+
`recordingId`, `presentationId`, `whiteboardId`, `logId`, `parentId` 등)는 **노드 id와 경로를 모두**
|
|
211
|
+
받습니다 — CLI와 같은 규칙으로 `/`로 시작하면 경로로 해석합니다.
|
|
212
|
+
즉 `rootr_add_row`에 `databaseId: "/제작 공정/칸반"`을 그대로 넘길 수 있어,
|
|
213
|
+
경로만 아는 에이전트가 uuid를 찾아 헤맬 필요가 없습니다.
|
|
214
|
+
`rootr_list`·`rootr_search` 출력도 각 줄 끝에 `[노드 id]`를 함께 표시합니다.
|
|
215
|
+
|
|
209
216
|
#### 문서 (workspace 키, docs:read/docs:write)
|
|
210
217
|
|
|
211
218
|
| 도구 | 설명 |
|
|
@@ -2,7 +2,7 @@ import { readFileSync } from 'node:fs';
|
|
|
2
2
|
|
|
3
3
|
import { z } from 'zod';
|
|
4
4
|
|
|
5
|
-
import { jsonResult, errorResult, makeClient, requireWorkspace } from './shared.js';
|
|
5
|
+
import { jsonResult, errorResult, makeClient, requireWorkspace, resolveOptionalNodeId, NODE_TARGET_HINT } from './shared.js';
|
|
6
6
|
import { resolveTarget, requireResolved } from '../resolve.js';
|
|
7
7
|
import { RootrApiError } from '../client.js';
|
|
8
8
|
|
|
@@ -24,7 +24,7 @@ const uploadInputSchema = {
|
|
|
24
24
|
.string()
|
|
25
25
|
.optional()
|
|
26
26
|
.describe('Base64-encoded file bytes; use this when the caller has the file in memory instead of on disk'),
|
|
27
|
-
nodeId: z.string().optional().describe(
|
|
27
|
+
nodeId: z.string().optional().describe(`Document/node id${NODE_TARGET_HINT} to associate the attachment with (permission-checked)`),
|
|
28
28
|
workspace: z.string().optional().describe('Workspace id; defaults to ROOTR_WORKSPACE/config'),
|
|
29
29
|
};
|
|
30
30
|
|
|
@@ -48,6 +48,7 @@ export function registerAttachmentTools(server) {
|
|
|
48
48
|
try {
|
|
49
49
|
const client = makeClient();
|
|
50
50
|
const ws = requireWorkspace(workspace);
|
|
51
|
+
nodeId = await resolveOptionalNodeId(client, nodeId);
|
|
51
52
|
const data = readAttachmentBytes({ filePath, base64Data });
|
|
52
53
|
const { id } = await client.uploadAttachment(ws, { filename, mimeType, nodeId, data });
|
|
53
54
|
const url = client.attachmentRawUrl(id);
|
package/lib/mcp-tools/crm.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
|
|
3
|
-
import { jsonResult, errorResult, makeClient, requireWorkspace } from './shared.js';
|
|
3
|
+
import { jsonResult, errorResult, makeClient, requireWorkspace, resolveNodeId, NODE_TARGET_HINT, resolveOptionalNodeId } from './shared.js';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Shared context repeated (shortened) across tool descriptions so an agent
|
|
@@ -61,7 +61,7 @@ export function registerCrmTools(server) {
|
|
|
61
61
|
inputSchema: {
|
|
62
62
|
name: z.string().optional().describe('CRM node name'),
|
|
63
63
|
path: z.string().optional().describe('Full tree path to create at, e.g. "/sales/crm"'),
|
|
64
|
-
parentId: z.string().optional().describe(
|
|
64
|
+
parentId: z.string().optional().describe(`Parent folder/node id${NODE_TARGET_HINT}`),
|
|
65
65
|
icon: z.string().optional().describe('Emoji icon for the CRM node'),
|
|
66
66
|
pipelines: z
|
|
67
67
|
.array(pipelineShape)
|
|
@@ -81,6 +81,7 @@ export function registerCrmTools(server) {
|
|
|
81
81
|
try {
|
|
82
82
|
const ws = requireWorkspace(workspace);
|
|
83
83
|
const client = makeClient(ws);
|
|
84
|
+
parentId = await resolveOptionalNodeId(client, parentId);
|
|
84
85
|
const result = await client.createCrm(ws, {
|
|
85
86
|
name,
|
|
86
87
|
path,
|
|
@@ -111,7 +112,7 @@ export function registerCrmTools(server) {
|
|
|
111
112
|
CRM_CONTEXT,
|
|
112
113
|
annotations: { readOnlyHint: true, destructiveHint: false },
|
|
113
114
|
inputSchema: {
|
|
114
|
-
crmId: z.string().describe(
|
|
115
|
+
crmId: z.string().describe(`CRM node id${NODE_TARGET_HINT}`),
|
|
115
116
|
includeSummary: z
|
|
116
117
|
.boolean()
|
|
117
118
|
.optional()
|
|
@@ -121,6 +122,7 @@ export function registerCrmTools(server) {
|
|
|
121
122
|
async ({ crmId, includeSummary }) => {
|
|
122
123
|
try {
|
|
123
124
|
const client = makeClient();
|
|
125
|
+
crmId = await resolveNodeId(client, crmId);
|
|
124
126
|
const crm = await client.getCrm(crmId);
|
|
125
127
|
if (includeSummary) {
|
|
126
128
|
const summary = await client.getCrmSummary(crmId);
|
|
@@ -144,7 +146,7 @@ export function registerCrmTools(server) {
|
|
|
144
146
|
CRM_CONTEXT,
|
|
145
147
|
annotations: { readOnlyHint: false, destructiveHint: false },
|
|
146
148
|
inputSchema: {
|
|
147
|
-
crmId: z.string().describe(
|
|
149
|
+
crmId: z.string().describe(`CRM node id${NODE_TARGET_HINT}`),
|
|
148
150
|
pipelines: z
|
|
149
151
|
.array(pipelineShape)
|
|
150
152
|
.optional()
|
|
@@ -157,6 +159,7 @@ export function registerCrmTools(server) {
|
|
|
157
159
|
async ({ crmId, pipelines, fields, config, icon }) => {
|
|
158
160
|
try {
|
|
159
161
|
const client = makeClient();
|
|
162
|
+
crmId = await resolveNodeId(client, crmId);
|
|
160
163
|
const result = await client.updateCrm(crmId, {
|
|
161
164
|
pipelines: coerceJson(pipelines),
|
|
162
165
|
fields: coerceJson(fields),
|
|
@@ -182,7 +185,7 @@ export function registerCrmTools(server) {
|
|
|
182
185
|
CRM_CONTEXT,
|
|
183
186
|
annotations: { readOnlyHint: true, destructiveHint: false },
|
|
184
187
|
inputSchema: {
|
|
185
|
-
crmId: z.string().describe(
|
|
188
|
+
crmId: z.string().describe(`CRM node id${NODE_TARGET_HINT}`),
|
|
186
189
|
q: z.string().optional().describe('Free-text search (name/domain/etc.)'),
|
|
187
190
|
ownerId: z.string().optional().describe('Filter by owning user id'),
|
|
188
191
|
tag: z.string().optional().describe('Filter by tag'),
|
|
@@ -194,6 +197,7 @@ export function registerCrmTools(server) {
|
|
|
194
197
|
async ({ crmId, q, ownerId, tag, sort, limit, offset }) => {
|
|
195
198
|
try {
|
|
196
199
|
const client = makeClient();
|
|
200
|
+
crmId = await resolveNodeId(client, crmId);
|
|
197
201
|
const result = await client.listCrmCompanies(crmId, { q, ownerId, tag, sort, limit, offset });
|
|
198
202
|
return jsonResult(result);
|
|
199
203
|
} catch (err) {
|
|
@@ -213,7 +217,7 @@ export function registerCrmTools(server) {
|
|
|
213
217
|
CRM_CONTEXT,
|
|
214
218
|
annotations: { readOnlyHint: false, destructiveHint: false },
|
|
215
219
|
inputSchema: {
|
|
216
|
-
crmId: z.string().describe(
|
|
220
|
+
crmId: z.string().describe(`CRM node id${NODE_TARGET_HINT}`),
|
|
217
221
|
companyId: z.string().optional().describe('Existing company id to update/delete; omit to create a new company'),
|
|
218
222
|
delete: z.boolean().optional().describe('If true (with companyId), delete the company instead of updating'),
|
|
219
223
|
name: z.string().optional().describe('Company name (required when creating)'),
|
|
@@ -231,6 +235,7 @@ export function registerCrmTools(server) {
|
|
|
231
235
|
async ({ crmId, companyId, delete: doDelete, custom, ...rest }) => {
|
|
232
236
|
try {
|
|
233
237
|
const client = makeClient();
|
|
238
|
+
crmId = await resolveNodeId(client, crmId);
|
|
234
239
|
if (companyId && doDelete) {
|
|
235
240
|
await client.deleteCrmCompany(companyId);
|
|
236
241
|
return jsonResult({ deleted: true, companyId });
|
|
@@ -283,7 +288,7 @@ export function registerCrmTools(server) {
|
|
|
283
288
|
CRM_CONTEXT,
|
|
284
289
|
annotations: { readOnlyHint: true, destructiveHint: false },
|
|
285
290
|
inputSchema: {
|
|
286
|
-
crmId: z.string().describe(
|
|
291
|
+
crmId: z.string().describe(`CRM node id${NODE_TARGET_HINT}`),
|
|
287
292
|
q: z.string().optional().describe('Free-text search (name/email/etc.)'),
|
|
288
293
|
companyId: z.string().optional().describe('Filter by company id'),
|
|
289
294
|
lifecycleStage: z.string().optional().describe('lead | mql | sql | opportunity | customer | evangelist'),
|
|
@@ -298,6 +303,7 @@ export function registerCrmTools(server) {
|
|
|
298
303
|
async ({ crmId, q, companyId, lifecycleStage, leadStatus, ownerId, tag, sort, limit, offset }) => {
|
|
299
304
|
try {
|
|
300
305
|
const client = makeClient();
|
|
306
|
+
crmId = await resolveNodeId(client, crmId);
|
|
301
307
|
const result = await client.listCrmContacts(crmId, {
|
|
302
308
|
q,
|
|
303
309
|
companyId,
|
|
@@ -327,7 +333,7 @@ export function registerCrmTools(server) {
|
|
|
327
333
|
CRM_CONTEXT,
|
|
328
334
|
annotations: { readOnlyHint: false, destructiveHint: false },
|
|
329
335
|
inputSchema: {
|
|
330
|
-
crmId: z.string().describe(
|
|
336
|
+
crmId: z.string().describe(`CRM node id${NODE_TARGET_HINT}`),
|
|
331
337
|
contactId: z.string().optional().describe('Existing contact id to update/delete; omit to create a new contact'),
|
|
332
338
|
delete: z.boolean().optional().describe('If true (with contactId), delete the contact instead of updating'),
|
|
333
339
|
name: z.string().optional().describe('Contact name (required when creating)'),
|
|
@@ -353,6 +359,7 @@ export function registerCrmTools(server) {
|
|
|
353
359
|
async ({ crmId, contactId, delete: doDelete, custom, ...rest }) => {
|
|
354
360
|
try {
|
|
355
361
|
const client = makeClient();
|
|
362
|
+
crmId = await resolveNodeId(client, crmId);
|
|
356
363
|
if (contactId && doDelete) {
|
|
357
364
|
await client.deleteCrmContact(contactId);
|
|
358
365
|
return jsonResult({ deleted: true, contactId });
|
|
@@ -401,7 +408,7 @@ export function registerCrmTools(server) {
|
|
|
401
408
|
CRM_CONTEXT,
|
|
402
409
|
annotations: { readOnlyHint: true, destructiveHint: false },
|
|
403
410
|
inputSchema: {
|
|
404
|
-
crmId: z.string().describe(
|
|
411
|
+
crmId: z.string().describe(`CRM node id${NODE_TARGET_HINT}`),
|
|
405
412
|
q: z.string().optional().describe('Free-text search (deal name/etc.)'),
|
|
406
413
|
pipelineId: z.string().optional().describe('Filter by pipeline id'),
|
|
407
414
|
stageId: z.string().optional().describe('Filter by stage id'),
|
|
@@ -418,6 +425,7 @@ export function registerCrmTools(server) {
|
|
|
418
425
|
async ({ crmId, q, pipelineId, stageId, status, ownerId, companyId, contactId, tag, sort, limit, offset }) => {
|
|
419
426
|
try {
|
|
420
427
|
const client = makeClient();
|
|
428
|
+
crmId = await resolveNodeId(client, crmId);
|
|
421
429
|
const result = await client.listCrmDeals(crmId, {
|
|
422
430
|
q,
|
|
423
431
|
pipelineId,
|
|
@@ -450,7 +458,7 @@ export function registerCrmTools(server) {
|
|
|
450
458
|
CRM_CONTEXT,
|
|
451
459
|
annotations: { readOnlyHint: false, destructiveHint: false },
|
|
452
460
|
inputSchema: {
|
|
453
|
-
crmId: z.string().describe(
|
|
461
|
+
crmId: z.string().describe(`CRM node id${NODE_TARGET_HINT}`),
|
|
454
462
|
dealId: z.string().optional().describe('Existing deal id to update/delete; omit to create a new deal'),
|
|
455
463
|
delete: z.boolean().optional().describe('If true (with dealId), delete the deal instead of updating'),
|
|
456
464
|
name: z.string().optional().describe('Deal name (required when creating)'),
|
|
@@ -478,6 +486,7 @@ export function registerCrmTools(server) {
|
|
|
478
486
|
async ({ crmId, dealId, delete: doDelete, contacts, custom, ...rest }) => {
|
|
479
487
|
try {
|
|
480
488
|
const client = makeClient();
|
|
489
|
+
crmId = await resolveNodeId(client, crmId);
|
|
481
490
|
if (dealId && doDelete) {
|
|
482
491
|
await client.deleteCrmDeal(dealId);
|
|
483
492
|
return jsonResult({ deleted: true, dealId });
|
|
@@ -552,7 +561,7 @@ export function registerCrmTools(server) {
|
|
|
552
561
|
CRM_CONTEXT,
|
|
553
562
|
annotations: { readOnlyHint: false, destructiveHint: false },
|
|
554
563
|
inputSchema: {
|
|
555
|
-
crmId: z.string().describe(
|
|
564
|
+
crmId: z.string().describe(`CRM node id${NODE_TARGET_HINT}`),
|
|
556
565
|
type: z.string().describe('note | call | email | meeting'),
|
|
557
566
|
subject: z.string().optional(),
|
|
558
567
|
body: z.string().optional().describe('Activity body/notes (markdown/plain text)'),
|
|
@@ -565,6 +574,7 @@ export function registerCrmTools(server) {
|
|
|
565
574
|
async ({ crmId, ...rest }) => {
|
|
566
575
|
try {
|
|
567
576
|
const client = makeClient();
|
|
577
|
+
crmId = await resolveNodeId(client, crmId);
|
|
568
578
|
const result = await client.createCrmActivity(crmId, rest);
|
|
569
579
|
return jsonResult(result);
|
|
570
580
|
} catch (err) {
|
|
@@ -582,7 +592,7 @@ export function registerCrmTools(server) {
|
|
|
582
592
|
CRM_CONTEXT,
|
|
583
593
|
annotations: { readOnlyHint: true, destructiveHint: false },
|
|
584
594
|
inputSchema: {
|
|
585
|
-
crmId: z.string().describe(
|
|
595
|
+
crmId: z.string().describe(`CRM node id${NODE_TARGET_HINT}`),
|
|
586
596
|
contactId: z.string().optional(),
|
|
587
597
|
companyId: z.string().optional(),
|
|
588
598
|
dealId: z.string().optional(),
|
|
@@ -594,6 +604,7 @@ export function registerCrmTools(server) {
|
|
|
594
604
|
async ({ crmId, contactId, companyId, dealId, type, limit, offset }) => {
|
|
595
605
|
try {
|
|
596
606
|
const client = makeClient();
|
|
607
|
+
crmId = await resolveNodeId(client, crmId);
|
|
597
608
|
const result = await client.listCrmActivities(crmId, { contactId, companyId, dealId, type, limit, offset });
|
|
598
609
|
return jsonResult(result);
|
|
599
610
|
} catch (err) {
|
|
@@ -615,7 +626,7 @@ export function registerCrmTools(server) {
|
|
|
615
626
|
CRM_CONTEXT,
|
|
616
627
|
annotations: { readOnlyHint: false, destructiveHint: false },
|
|
617
628
|
inputSchema: {
|
|
618
|
-
crmId: z.string().describe(
|
|
629
|
+
crmId: z.string().describe(`CRM node id${NODE_TARGET_HINT} (required when creating; ignored when updating)`),
|
|
619
630
|
taskId: z.string().optional().describe('Existing task id to update; omit to create a new task'),
|
|
620
631
|
title: z.string().optional().describe('Task title (required when creating)'),
|
|
621
632
|
type: z.string().optional().describe('todo | call | email | meeting'),
|
|
@@ -630,6 +641,7 @@ export function registerCrmTools(server) {
|
|
|
630
641
|
async ({ crmId, taskId, ...rest }) => {
|
|
631
642
|
try {
|
|
632
643
|
const client = makeClient();
|
|
644
|
+
crmId = await resolveNodeId(client, crmId);
|
|
633
645
|
const result = taskId ? await client.updateCrmTask(taskId, rest) : await client.createCrmTask(crmId, rest);
|
|
634
646
|
return jsonResult(result);
|
|
635
647
|
} catch (err) {
|
|
@@ -648,7 +660,7 @@ export function registerCrmTools(server) {
|
|
|
648
660
|
CRM_CONTEXT,
|
|
649
661
|
annotations: { readOnlyHint: true, destructiveHint: false },
|
|
650
662
|
inputSchema: {
|
|
651
|
-
crmId: z.string().describe(
|
|
663
|
+
crmId: z.string().describe(`CRM node id${NODE_TARGET_HINT}`),
|
|
652
664
|
status: z.string().optional().describe('open | done | all'),
|
|
653
665
|
assigneeId: z.string().optional(),
|
|
654
666
|
due: z.string().optional().describe('overdue | today | upcoming'),
|
|
@@ -662,6 +674,7 @@ export function registerCrmTools(server) {
|
|
|
662
674
|
async ({ crmId, status, assigneeId, due, contactId, companyId, dealId, limit, offset }) => {
|
|
663
675
|
try {
|
|
664
676
|
const client = makeClient();
|
|
677
|
+
crmId = await resolveNodeId(client, crmId);
|
|
665
678
|
const result = await client.listCrmTasks(crmId, {
|
|
666
679
|
status,
|
|
667
680
|
assigneeId,
|
|
@@ -693,7 +706,7 @@ export function registerCrmTools(server) {
|
|
|
693
706
|
CRM_CONTEXT,
|
|
694
707
|
annotations: { readOnlyHint: false, destructiveHint: false },
|
|
695
708
|
inputSchema: {
|
|
696
|
-
crmId: z.string().describe(
|
|
709
|
+
crmId: z.string().describe(`CRM node id${NODE_TARGET_HINT}`),
|
|
697
710
|
entity: z.string().describe('contact | company | deal'),
|
|
698
711
|
rows: z.array(z.record(z.unknown())).describe('Array of row objects to import, matching the entity shape'),
|
|
699
712
|
},
|
|
@@ -701,6 +714,7 @@ export function registerCrmTools(server) {
|
|
|
701
714
|
async ({ crmId, entity, rows }) => {
|
|
702
715
|
try {
|
|
703
716
|
const client = makeClient();
|
|
717
|
+
crmId = await resolveNodeId(client, crmId);
|
|
704
718
|
const result = await client.importCrm(crmId, { entity, rows: coerceJson(rows) });
|
|
705
719
|
return jsonResult(result);
|
|
706
720
|
} catch (err) {
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
|
|
3
|
-
import { jsonResult, errorResult, makeClient } from './shared.js';
|
|
3
|
+
import { jsonResult, errorResult, makeClient, resolveNodeId, NODE_TARGET_HINT } from './shared.js';
|
|
4
|
+
|
|
5
|
+
/** Human text for the `databaseId` param — accepts a path just like the document tools. */
|
|
6
|
+
const DATABASE_TARGET_DESC = `DATABASE node id${NODE_TARGET_HINT} (e.g. "/제작 공정/칸반")`;
|
|
4
7
|
|
|
5
8
|
/**
|
|
6
9
|
* Resolve a `{propertyNameOrId: value}` map to `{propertyId: value}` using the
|
|
@@ -40,15 +43,16 @@ export function registerDatabaseTools(server) {
|
|
|
40
43
|
'property ids/names and, for board/kanban views, which select column holds the status. Only works on DATABASE nodes.',
|
|
41
44
|
annotations: { readOnlyHint: true, destructiveHint: false },
|
|
42
45
|
inputSchema: {
|
|
43
|
-
databaseId: z.string().describe(
|
|
46
|
+
databaseId: z.string().describe(DATABASE_TARGET_DESC),
|
|
44
47
|
},
|
|
45
48
|
},
|
|
46
49
|
async ({ databaseId }) => {
|
|
47
50
|
try {
|
|
48
51
|
const client = makeClient();
|
|
52
|
+
const id = await resolveNodeId(client, databaseId);
|
|
49
53
|
const [database, rows] = await Promise.all([
|
|
50
|
-
client.getDatabase(
|
|
51
|
-
client.listRows(
|
|
54
|
+
client.getDatabase(id),
|
|
55
|
+
client.listRows(id),
|
|
52
56
|
]);
|
|
53
57
|
return jsonResult({ database, rows });
|
|
54
58
|
} catch (err) {
|
|
@@ -66,13 +70,13 @@ export function registerDatabaseTools(server) {
|
|
|
66
70
|
'Use rootr_read_database instead when you also need the property/column definitions.',
|
|
67
71
|
annotations: { readOnlyHint: true, destructiveHint: false },
|
|
68
72
|
inputSchema: {
|
|
69
|
-
databaseId: z.string().describe(
|
|
73
|
+
databaseId: z.string().describe(DATABASE_TARGET_DESC),
|
|
70
74
|
},
|
|
71
75
|
},
|
|
72
76
|
async ({ databaseId }) => {
|
|
73
77
|
try {
|
|
74
78
|
const client = makeClient();
|
|
75
|
-
const rows = await client.listRows(databaseId);
|
|
79
|
+
const rows = await client.listRows(await resolveNodeId(client, databaseId));
|
|
76
80
|
return jsonResult({ rows });
|
|
77
81
|
} catch (err) {
|
|
78
82
|
return errorResult(err);
|
|
@@ -91,7 +95,7 @@ export function registerDatabaseTools(server) {
|
|
|
91
95
|
'Call rootr_read_database first to learn the columns. Optionally set `position` to insert at an index.',
|
|
92
96
|
annotations: { readOnlyHint: false, destructiveHint: false },
|
|
93
97
|
inputSchema: {
|
|
94
|
-
databaseId: z.string().describe(
|
|
98
|
+
databaseId: z.string().describe(DATABASE_TARGET_DESC),
|
|
95
99
|
values: z.record(z.unknown()).describe('{ propertyNameOrId: value } for the new row'),
|
|
96
100
|
position: z.number().optional().describe('0-based insert index; appended at the end if omitted'),
|
|
97
101
|
},
|
|
@@ -99,9 +103,10 @@ export function registerDatabaseTools(server) {
|
|
|
99
103
|
async ({ databaseId, values, position }) => {
|
|
100
104
|
try {
|
|
101
105
|
const client = makeClient();
|
|
102
|
-
const
|
|
106
|
+
const id = await resolveNodeId(client, databaseId);
|
|
107
|
+
const database = await client.getDatabase(id);
|
|
103
108
|
const resolved = resolveValueKeys(database.properties || [], values);
|
|
104
|
-
const row = await client.createRow(
|
|
109
|
+
const row = await client.createRow(id, { values: resolved, position });
|
|
105
110
|
return jsonResult(row);
|
|
106
111
|
} catch (err) {
|
|
107
112
|
return errorResult(err);
|
|
@@ -120,7 +125,7 @@ export function registerDatabaseTools(server) {
|
|
|
120
125
|
'Optionally set `position` to reorder the row.',
|
|
121
126
|
annotations: { readOnlyHint: false, destructiveHint: false },
|
|
122
127
|
inputSchema: {
|
|
123
|
-
databaseId: z.string().describe(
|
|
128
|
+
databaseId: z.string().describe(DATABASE_TARGET_DESC),
|
|
124
129
|
rowId: z.string().describe('Row id (must belong to the database)'),
|
|
125
130
|
values: z.record(z.unknown()).optional().describe('{ propertyNameOrId: newValue | null } partial patch'),
|
|
126
131
|
position: z.number().optional().describe('New 0-based position for the row'),
|
|
@@ -129,12 +134,13 @@ export function registerDatabaseTools(server) {
|
|
|
129
134
|
async ({ databaseId, rowId, values, position }) => {
|
|
130
135
|
try {
|
|
131
136
|
const client = makeClient();
|
|
137
|
+
const id = await resolveNodeId(client, databaseId);
|
|
132
138
|
let resolved;
|
|
133
139
|
if (values !== undefined) {
|
|
134
|
-
const database = await client.getDatabase(
|
|
140
|
+
const database = await client.getDatabase(id);
|
|
135
141
|
resolved = resolveValueKeys(database.properties || [], values);
|
|
136
142
|
}
|
|
137
|
-
const row = await client.updateRow(
|
|
143
|
+
const row = await client.updateRow(id, rowId, { values: resolved, position });
|
|
138
144
|
return jsonResult(row);
|
|
139
145
|
} catch (err) {
|
|
140
146
|
return errorResult(err);
|
|
@@ -151,14 +157,14 @@ export function registerDatabaseTools(server) {
|
|
|
151
157
|
'Verify the row via rootr_read_database first — do not guess rowIds.',
|
|
152
158
|
annotations: { readOnlyHint: false, destructiveHint: true },
|
|
153
159
|
inputSchema: {
|
|
154
|
-
databaseId: z.string().describe(
|
|
160
|
+
databaseId: z.string().describe(DATABASE_TARGET_DESC),
|
|
155
161
|
rowId: z.string().describe('Row id to delete (must belong to the database)'),
|
|
156
162
|
},
|
|
157
163
|
},
|
|
158
164
|
async ({ databaseId, rowId }) => {
|
|
159
165
|
try {
|
|
160
166
|
const client = makeClient();
|
|
161
|
-
await client.deleteRow(databaseId, rowId);
|
|
167
|
+
await client.deleteRow(await resolveNodeId(client, databaseId), rowId);
|
|
162
168
|
return jsonResult({ deleted: true, rowId });
|
|
163
169
|
} catch (err) {
|
|
164
170
|
return errorResult(err);
|
|
@@ -31,7 +31,12 @@ export function registerDocumentTools(server) {
|
|
|
31
31
|
const p = n.path || '';
|
|
32
32
|
return p === path || p.startsWith(path.endsWith('/') ? path : path + '/');
|
|
33
33
|
})
|
|
34
|
-
.map((n) =>
|
|
34
|
+
.map((n) => {
|
|
35
|
+
const label = n.path || n.name || n.id;
|
|
36
|
+
// Keep the id visible: tools that take a node id (databases, sheets,
|
|
37
|
+
// whiteboards, …) are otherwise unreachable from a listing.
|
|
38
|
+
return n.id && label !== n.id ? `${n.type || '?'} ${label} [${n.id}]` : `${n.type || '?'} ${label}`;
|
|
39
|
+
})
|
|
35
40
|
.sort();
|
|
36
41
|
return textResult(rows.length ? rows.join('\n') : '(no matching nodes)');
|
|
37
42
|
} catch (err) {
|
|
@@ -189,7 +194,11 @@ export function registerDocumentTools(server) {
|
|
|
189
194
|
try {
|
|
190
195
|
const client = makeClient();
|
|
191
196
|
const results = await client.search(query);
|
|
192
|
-
const rows = results.map((r) =>
|
|
197
|
+
const rows = results.map((r) => {
|
|
198
|
+
const label = r.path || r.name || r.nodeId;
|
|
199
|
+
const id = r.nodeId && label !== r.nodeId ? ` [${r.nodeId}]` : '';
|
|
200
|
+
return `${label}${id} — ${r.snippet || ''}`;
|
|
201
|
+
});
|
|
193
202
|
return textResult(rows.length ? rows.join('\n') : '(no results)');
|
|
194
203
|
} catch (err) {
|
|
195
204
|
return errorResult(err);
|
package/lib/mcp-tools/forms.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
|
|
3
|
-
import { jsonResult, errorResult, makeClient, requireWorkspace } from './shared.js';
|
|
3
|
+
import { jsonResult, errorResult, makeClient, requireWorkspace, resolveNodeId, NODE_TARGET_HINT, resolveOptionalNodeId } from './shared.js';
|
|
4
4
|
|
|
5
5
|
const fieldShape = z
|
|
6
6
|
.object({
|
|
@@ -37,10 +37,10 @@ export function registerFormTools(server) {
|
|
|
37
37
|
annotations: { readOnlyHint: false, destructiveHint: false },
|
|
38
38
|
inputSchema: {
|
|
39
39
|
name: z.string().optional().describe('Form name/title'),
|
|
40
|
-
parentId: z.string().optional().describe(
|
|
40
|
+
parentId: z.string().optional().describe(`Parent folder/node id${NODE_TARGET_HINT}`),
|
|
41
41
|
icon: z.string().optional().describe('Emoji icon for the node'),
|
|
42
42
|
fields: z.array(fieldShape).optional().describe('Initial field definitions; defaults to an empty form'),
|
|
43
|
-
targetDatabaseId: z.string().optional().describe(
|
|
43
|
+
targetDatabaseId: z.string().optional().describe(`DATABASE node id${NODE_TARGET_HINT} whose rows mirror this form's responses`),
|
|
44
44
|
config: z.record(z.unknown()).optional().describe('Free-form form config object (e.g. publicSubmit mode)'),
|
|
45
45
|
workspace: z.string().optional().describe('Workspace id; defaults to ROOTR_WORKSPACE/config if omitted'),
|
|
46
46
|
},
|
|
@@ -49,6 +49,8 @@ export function registerFormTools(server) {
|
|
|
49
49
|
try {
|
|
50
50
|
const ws = requireWorkspace(workspace);
|
|
51
51
|
const client = makeClient(ws);
|
|
52
|
+
targetDatabaseId = await resolveOptionalNodeId(client, targetDatabaseId);
|
|
53
|
+
parentId = await resolveOptionalNodeId(client, parentId);
|
|
52
54
|
const result = await client.createForm(ws, { name, parentId, icon, fields, targetDatabaseId, config });
|
|
53
55
|
return jsonResult(result);
|
|
54
56
|
} catch (err) {
|
|
@@ -67,12 +69,13 @@ export function registerFormTools(server) {
|
|
|
67
69
|
'which field ids the response `values` are keyed by.',
|
|
68
70
|
annotations: { readOnlyHint: true, destructiveHint: false },
|
|
69
71
|
inputSchema: {
|
|
70
|
-
formId: z.string().describe(
|
|
72
|
+
formId: z.string().describe(`FORM node id${NODE_TARGET_HINT}`),
|
|
71
73
|
},
|
|
72
74
|
},
|
|
73
75
|
async ({ formId }) => {
|
|
74
76
|
try {
|
|
75
77
|
const client = makeClient();
|
|
78
|
+
formId = await resolveNodeId(client, formId);
|
|
76
79
|
const result = await client.getForm(formId);
|
|
77
80
|
return jsonResult(result);
|
|
78
81
|
} catch (err) {
|
|
@@ -92,9 +95,9 @@ export function registerFormTools(server) {
|
|
|
92
95
|
FIELDS_GUIDE,
|
|
93
96
|
annotations: { readOnlyHint: false, destructiveHint: false },
|
|
94
97
|
inputSchema: {
|
|
95
|
-
formId: z.string().describe(
|
|
98
|
+
formId: z.string().describe(`FORM node id${NODE_TARGET_HINT}`),
|
|
96
99
|
fields: z.array(fieldShape).optional().describe('COMPLETE fields array to set — omitted existing fields will be dropped'),
|
|
97
|
-
targetDatabaseId: z.string().nullable().optional().describe(
|
|
100
|
+
targetDatabaseId: z.string().nullable().optional().describe(`DATABASE node id${NODE_TARGET_HINT} to mirror responses into, or null to unlink`),
|
|
98
101
|
config: z.record(z.unknown()).optional().describe('Free-form form config object (e.g. publicSubmit mode)'),
|
|
99
102
|
icon: z.string().optional().describe('Emoji icon for the node'),
|
|
100
103
|
},
|
|
@@ -102,6 +105,8 @@ export function registerFormTools(server) {
|
|
|
102
105
|
async ({ formId, fields, targetDatabaseId, config, icon }) => {
|
|
103
106
|
try {
|
|
104
107
|
const client = makeClient();
|
|
108
|
+
targetDatabaseId = await resolveOptionalNodeId(client, targetDatabaseId);
|
|
109
|
+
formId = await resolveNodeId(client, formId);
|
|
105
110
|
const result = await client.updateForm(formId, { fields, targetDatabaseId, config, icon });
|
|
106
111
|
return jsonResult(result);
|
|
107
112
|
} catch (err) {
|
|
@@ -120,12 +125,13 @@ export function registerFormTools(server) {
|
|
|
120
125
|
'responses collected from a form.',
|
|
121
126
|
annotations: { readOnlyHint: true, destructiveHint: false },
|
|
122
127
|
inputSchema: {
|
|
123
|
-
formId: z.string().describe(
|
|
128
|
+
formId: z.string().describe(`FORM node id${NODE_TARGET_HINT}`),
|
|
124
129
|
},
|
|
125
130
|
},
|
|
126
131
|
async ({ formId }) => {
|
|
127
132
|
try {
|
|
128
133
|
const client = makeClient();
|
|
134
|
+
formId = await resolveNodeId(client, formId);
|
|
129
135
|
const result = await client.listFormResponses(formId);
|
|
130
136
|
return jsonResult({ responses: result });
|
|
131
137
|
} catch (err) {
|
|
@@ -144,13 +150,14 @@ export function registerFormTools(server) {
|
|
|
144
150
|
'exact field ids and types.',
|
|
145
151
|
annotations: { readOnlyHint: false, destructiveHint: false },
|
|
146
152
|
inputSchema: {
|
|
147
|
-
formId: z.string().describe(
|
|
153
|
+
formId: z.string().describe(`FORM node id${NODE_TARGET_HINT}`),
|
|
148
154
|
values: z.record(z.unknown()).describe('Response values keyed by field id'),
|
|
149
155
|
},
|
|
150
156
|
},
|
|
151
157
|
async ({ formId, values }) => {
|
|
152
158
|
try {
|
|
153
159
|
const client = makeClient();
|
|
160
|
+
formId = await resolveNodeId(client, formId);
|
|
154
161
|
const result = await client.createFormResponse(formId, values);
|
|
155
162
|
return jsonResult(result);
|
|
156
163
|
} catch (err) {
|
|
@@ -169,12 +176,13 @@ export function registerFormTools(server) {
|
|
|
169
176
|
'rootr_update_form\'s config.publicSubmit ("off" | "login" | "anon").',
|
|
170
177
|
annotations: { readOnlyHint: false, destructiveHint: false },
|
|
171
178
|
inputSchema: {
|
|
172
|
-
formId: z.string().describe(
|
|
179
|
+
formId: z.string().describe(`FORM node id${NODE_TARGET_HINT}`),
|
|
173
180
|
},
|
|
174
181
|
},
|
|
175
182
|
async ({ formId }) => {
|
|
176
183
|
try {
|
|
177
184
|
const client = makeClient();
|
|
185
|
+
formId = await resolveNodeId(client, formId);
|
|
178
186
|
const result = await client.createFormShareLink(formId);
|
|
179
187
|
return jsonResult(result);
|
|
180
188
|
} catch (err) {
|
|
@@ -190,12 +198,13 @@ export function registerFormTools(server) {
|
|
|
190
198
|
description: 'List existing public share links/tokens issued for a Rootr (루터) FORM.',
|
|
191
199
|
annotations: { readOnlyHint: true, destructiveHint: false },
|
|
192
200
|
inputSchema: {
|
|
193
|
-
formId: z.string().describe(
|
|
201
|
+
formId: z.string().describe(`FORM node id${NODE_TARGET_HINT}`),
|
|
194
202
|
},
|
|
195
203
|
},
|
|
196
204
|
async ({ formId }) => {
|
|
197
205
|
try {
|
|
198
206
|
const client = makeClient();
|
|
207
|
+
formId = await resolveNodeId(client, formId);
|
|
199
208
|
const result = await client.listFormShareLinks(formId);
|
|
200
209
|
return jsonResult({ links: result });
|
|
201
210
|
} catch (err) {
|
package/lib/mcp-tools/issues.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
|
|
3
|
-
import { textResult, jsonResult, errorResult, makeClient, requireWorkspace } from './shared.js';
|
|
3
|
+
import { textResult, jsonResult, errorResult, makeClient, requireWorkspace, resolveNodeId, NODE_TARGET_HINT } from './shared.js';
|
|
4
4
|
|
|
5
5
|
/** Issue tracker tools: create tracker, list/create/get/update issues, comment. */
|
|
6
6
|
export function registerIssueTools(server) {
|
|
@@ -41,7 +41,7 @@ export function registerIssueTools(server) {
|
|
|
41
41
|
'type id, or a text query. Use rootr_get_issue for full details of one issue.',
|
|
42
42
|
annotations: { readOnlyHint: true, destructiveHint: false },
|
|
43
43
|
inputSchema: {
|
|
44
|
-
trackerId: z.string().describe(
|
|
44
|
+
trackerId: z.string().describe(`Issue tracker node id${NODE_TARGET_HINT}`),
|
|
45
45
|
state: z.string().optional().describe('OPEN | CLOSED | ALL'),
|
|
46
46
|
q: z.string().optional().describe('Free-text search within issues'),
|
|
47
47
|
label: z.string().optional().describe('Label id to filter by'),
|
|
@@ -51,6 +51,7 @@ export function registerIssueTools(server) {
|
|
|
51
51
|
async ({ trackerId, state, q, label, type }) => {
|
|
52
52
|
try {
|
|
53
53
|
const client = makeClient();
|
|
54
|
+
trackerId = await resolveNodeId(client, trackerId);
|
|
54
55
|
const result = await client.listIssues(trackerId, { state, q, label, type });
|
|
55
56
|
return jsonResult(result);
|
|
56
57
|
} catch (err) {
|
|
@@ -69,7 +70,7 @@ export function registerIssueTools(server) {
|
|
|
69
70
|
'parentIssueId nests this issue under another issue (sub-issue).',
|
|
70
71
|
annotations: { readOnlyHint: false, destructiveHint: false },
|
|
71
72
|
inputSchema: {
|
|
72
|
-
trackerId: z.string().describe(
|
|
73
|
+
trackerId: z.string().describe(`Issue tracker node id${NODE_TARGET_HINT}`),
|
|
73
74
|
title: z.string().describe('Issue title'),
|
|
74
75
|
body: z.string().optional().describe('Issue body (markdown)'),
|
|
75
76
|
labels: z.array(z.string()).optional().describe('Label ids to attach'),
|
|
@@ -80,6 +81,7 @@ export function registerIssueTools(server) {
|
|
|
80
81
|
async ({ trackerId, title, body, labels, type, parentIssueId }) => {
|
|
81
82
|
try {
|
|
82
83
|
const client = makeClient();
|
|
84
|
+
trackerId = await resolveNodeId(client, trackerId);
|
|
83
85
|
const result = await client.createIssue(trackerId, { title, body, labels, type, parentIssueId });
|
|
84
86
|
return jsonResult(result);
|
|
85
87
|
} catch (err) {
|
|
@@ -150,7 +152,7 @@ export function registerIssueTools(server) {
|
|
|
150
152
|
'so this requires an ACCOUNT key (PAT) as ROOTR_API_KEY — a workspace key gets a 403.',
|
|
151
153
|
annotations: { readOnlyHint: false, destructiveHint: false },
|
|
152
154
|
inputSchema: {
|
|
153
|
-
trackerId: z.string().describe(
|
|
155
|
+
trackerId: z.string().describe(`Issue tracker node id${NODE_TARGET_HINT}`),
|
|
154
156
|
issueId: z.string().describe('Issue id to comment on'),
|
|
155
157
|
body: z.string().describe('Comment body (markdown)'),
|
|
156
158
|
},
|
|
@@ -158,6 +160,7 @@ export function registerIssueTools(server) {
|
|
|
158
160
|
async ({ trackerId, issueId, body }) => {
|
|
159
161
|
try {
|
|
160
162
|
const client = makeClient();
|
|
163
|
+
trackerId = await resolveNodeId(client, trackerId);
|
|
161
164
|
const result = await client.commentIssue(trackerId, issueId, body);
|
|
162
165
|
return textResult(typeof result === 'string' ? result : JSON.stringify(result, null, 2));
|
|
163
166
|
} catch (err) {
|
package/lib/mcp-tools/logs.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
|
|
3
|
-
import { jsonResult, errorResult, makeClient, requireWorkspace } from './shared.js';
|
|
3
|
+
import { jsonResult, errorResult, makeClient, requireWorkspace, resolveNodeId, NODE_TARGET_HINT, resolveOptionalNodeId } from './shared.js';
|
|
4
4
|
|
|
5
5
|
/** LOG datastore tools: create/update-fields/add-entries/query-entries/stats. */
|
|
6
6
|
export function registerLogTools(server) {
|
|
@@ -22,7 +22,7 @@ export function registerLogTools(server) {
|
|
|
22
22
|
'Field definitions: [{name, type, ...}]. type=string|int|float|bool|datetime|json|enum|level|relation. ' +
|
|
23
23
|
'relation fields add target (node id), many?, relation?.',
|
|
24
24
|
),
|
|
25
|
-
parentId: z.string().optional().describe(
|
|
25
|
+
parentId: z.string().optional().describe(`Parent node id${NODE_TARGET_HINT}`),
|
|
26
26
|
workspace: z
|
|
27
27
|
.string()
|
|
28
28
|
.optional()
|
|
@@ -33,6 +33,7 @@ export function registerLogTools(server) {
|
|
|
33
33
|
try {
|
|
34
34
|
const ws = requireWorkspace(workspace);
|
|
35
35
|
const client = makeClient(ws);
|
|
36
|
+
parentId = await resolveOptionalNodeId(client, parentId);
|
|
36
37
|
const result = await client.createLogStore(ws, { name, fields, parentId });
|
|
37
38
|
return jsonResult(result);
|
|
38
39
|
} catch (err) {
|
|
@@ -51,7 +52,7 @@ export function registerLogTools(server) {
|
|
|
51
52
|
'Read the LOG store first (e.g. via rootr_read or a get-log call) to see its current fields before calling this.',
|
|
52
53
|
annotations: { readOnlyHint: false, destructiveHint: true },
|
|
53
54
|
inputSchema: {
|
|
54
|
-
logId: z.string().describe(
|
|
55
|
+
logId: z.string().describe(`LOG store node id${NODE_TARGET_HINT}`),
|
|
55
56
|
fields: z
|
|
56
57
|
.array(z.record(z.any()))
|
|
57
58
|
.describe('COMPLETE field list to set — omitted existing fields will be dropped'),
|
|
@@ -60,6 +61,7 @@ export function registerLogTools(server) {
|
|
|
60
61
|
async ({ logId, fields }) => {
|
|
61
62
|
try {
|
|
62
63
|
const client = makeClient();
|
|
64
|
+
logId = await resolveNodeId(client, logId);
|
|
63
65
|
const result = await client.updateLogFields(logId, fields);
|
|
64
66
|
return jsonResult(result);
|
|
65
67
|
} catch (err) {
|
|
@@ -77,7 +79,7 @@ export function registerLogTools(server) {
|
|
|
77
79
|
'(shaped per the store\'s field schema). This is additive — existing entries are untouched.',
|
|
78
80
|
annotations: { readOnlyHint: false, destructiveHint: false },
|
|
79
81
|
inputSchema: {
|
|
80
|
-
logId: z.string().describe(
|
|
82
|
+
logId: z.string().describe(`LOG store node id${NODE_TARGET_HINT}`),
|
|
81
83
|
entries: z
|
|
82
84
|
.array(z.record(z.any()))
|
|
83
85
|
.describe('Entries to add: [{ts?, source?, level?, message?, data?}, ...]'),
|
|
@@ -86,6 +88,7 @@ export function registerLogTools(server) {
|
|
|
86
88
|
async ({ logId, entries }) => {
|
|
87
89
|
try {
|
|
88
90
|
const client = makeClient();
|
|
91
|
+
logId = await resolveNodeId(client, logId);
|
|
89
92
|
const result = await client.addLogEntries(logId, entries);
|
|
90
93
|
return jsonResult(result);
|
|
91
94
|
} catch (err) {
|
|
@@ -103,7 +106,7 @@ export function registerLogTools(server) {
|
|
|
103
106
|
'Use this to inspect recent activity or find anomalous entries before asking rootr_ask for root-cause analysis.',
|
|
104
107
|
annotations: { readOnlyHint: true, destructiveHint: false },
|
|
105
108
|
inputSchema: {
|
|
106
|
-
logId: z.string().describe(
|
|
109
|
+
logId: z.string().describe(`LOG store node id${NODE_TARGET_HINT}`),
|
|
107
110
|
from: z.string().optional().describe('ISO timestamp lower bound'),
|
|
108
111
|
to: z.string().optional().describe('ISO timestamp upper bound'),
|
|
109
112
|
source: z.string().optional().describe('Filter by source'),
|
|
@@ -116,6 +119,7 @@ export function registerLogTools(server) {
|
|
|
116
119
|
async ({ logId, from, to, source, level, anomalyOnly, limit, order }) => {
|
|
117
120
|
try {
|
|
118
121
|
const client = makeClient();
|
|
122
|
+
logId = await resolveNodeId(client, logId);
|
|
119
123
|
const result = await client.queryLogEntries(logId, {
|
|
120
124
|
from,
|
|
121
125
|
to,
|
|
@@ -142,7 +146,7 @@ export function registerLogTools(server) {
|
|
|
142
146
|
'questions without pulling raw entries.',
|
|
143
147
|
annotations: { readOnlyHint: true, destructiveHint: false },
|
|
144
148
|
inputSchema: {
|
|
145
|
-
logId: z.string().describe(
|
|
149
|
+
logId: z.string().describe(`LOG store node id${NODE_TARGET_HINT}`),
|
|
146
150
|
groupBy: z.string().optional().describe('hour | day | source | level'),
|
|
147
151
|
metric: z.string().optional().describe('count | avg | max | min | sum'),
|
|
148
152
|
field: z.string().optional().describe('Numeric field name, required for avg/max/min/sum'),
|
|
@@ -153,6 +157,7 @@ export function registerLogTools(server) {
|
|
|
153
157
|
async ({ logId, groupBy, metric, field, from, to }) => {
|
|
154
158
|
try {
|
|
155
159
|
const client = makeClient();
|
|
160
|
+
logId = await resolveNodeId(client, logId);
|
|
156
161
|
const result = await client.logStats(logId, { groupBy, metric, field, from, to });
|
|
157
162
|
return jsonResult(result);
|
|
158
163
|
} catch (err) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
|
|
3
|
-
import { jsonResult, errorResult, makeClient, requireWorkspace } from './shared.js';
|
|
3
|
+
import { jsonResult, errorResult, makeClient, requireWorkspace, resolveNodeId, NODE_TARGET_HINT, resolveOptionalNodeId } from './shared.js';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Authoring guide, repeated (shortened) across every tool description so an
|
|
@@ -127,7 +127,7 @@ export function registerPresentationTools(server) {
|
|
|
127
127
|
annotations: { readOnlyHint: false, destructiveHint: false },
|
|
128
128
|
inputSchema: {
|
|
129
129
|
name: z.string().optional().describe('Deck name/title'),
|
|
130
|
-
parentId: z.string().optional().describe(
|
|
130
|
+
parentId: z.string().optional().describe(`Parent folder/node id${NODE_TARGET_HINT}`),
|
|
131
131
|
theme: themeShape,
|
|
132
132
|
slides: z.array(slideShape).optional().describe('Initial slides, in order'),
|
|
133
133
|
config: z.record(z.unknown()).optional().describe('Free-form deck config/settings object'),
|
|
@@ -142,6 +142,7 @@ export function registerPresentationTools(server) {
|
|
|
142
142
|
try {
|
|
143
143
|
const ws = requireWorkspace(workspace);
|
|
144
144
|
const client = makeClient(ws);
|
|
145
|
+
parentId = await resolveOptionalNodeId(client, parentId);
|
|
145
146
|
const result = await client.createPresentation(ws, {
|
|
146
147
|
name,
|
|
147
148
|
parentId,
|
|
@@ -167,12 +168,13 @@ export function registerPresentationTools(server) {
|
|
|
167
168
|
'know the current slide ids and content.',
|
|
168
169
|
annotations: { readOnlyHint: true, destructiveHint: false },
|
|
169
170
|
inputSchema: {
|
|
170
|
-
presentationId: z.string().describe(
|
|
171
|
+
presentationId: z.string().describe(`PRESENTATION node id${NODE_TARGET_HINT}`),
|
|
171
172
|
},
|
|
172
173
|
},
|
|
173
174
|
async ({ presentationId }) => {
|
|
174
175
|
try {
|
|
175
176
|
const client = makeClient();
|
|
177
|
+
presentationId = await resolveNodeId(client, presentationId);
|
|
176
178
|
const result = await client.getPresentation(presentationId);
|
|
177
179
|
return jsonResult(result);
|
|
178
180
|
} catch (err) {
|
|
@@ -193,7 +195,7 @@ export function registerPresentationTools(server) {
|
|
|
193
195
|
AUTHORING_GUIDE,
|
|
194
196
|
annotations: { readOnlyHint: false, destructiveHint: false },
|
|
195
197
|
inputSchema: {
|
|
196
|
-
presentationId: z.string().describe(
|
|
198
|
+
presentationId: z.string().describe(`PRESENTATION node id${NODE_TARGET_HINT}`),
|
|
197
199
|
theme: themeShape,
|
|
198
200
|
slides: z
|
|
199
201
|
.array(slideShape)
|
|
@@ -206,6 +208,7 @@ export function registerPresentationTools(server) {
|
|
|
206
208
|
async ({ presentationId, theme, slides, config, icon }) => {
|
|
207
209
|
try {
|
|
208
210
|
const client = makeClient();
|
|
211
|
+
presentationId = await resolveNodeId(client, presentationId);
|
|
209
212
|
const result = await client.updatePresentation(presentationId, {
|
|
210
213
|
theme,
|
|
211
214
|
slides: ensureSlideIds(slides),
|
|
@@ -229,13 +232,14 @@ export function registerPresentationTools(server) {
|
|
|
229
232
|
AUTHORING_GUIDE,
|
|
230
233
|
annotations: { readOnlyHint: false, destructiveHint: false },
|
|
231
234
|
inputSchema: {
|
|
232
|
-
presentationId: z.string().describe(
|
|
235
|
+
presentationId: z.string().describe(`PRESENTATION node id${NODE_TARGET_HINT}`),
|
|
233
236
|
slides: z.array(slideShape).describe('Slides to append, in order'),
|
|
234
237
|
},
|
|
235
238
|
},
|
|
236
239
|
async ({ presentationId, slides }) => {
|
|
237
240
|
try {
|
|
238
241
|
const client = makeClient();
|
|
242
|
+
presentationId = await resolveNodeId(client, presentationId);
|
|
239
243
|
const result = await client.appendPresentationSlides(presentationId, ensureSlideIds(slides));
|
|
240
244
|
return jsonResult(result);
|
|
241
245
|
} catch (err) {
|
|
@@ -255,7 +259,7 @@ export function registerPresentationTools(server) {
|
|
|
255
259
|
AUTHORING_GUIDE,
|
|
256
260
|
annotations: { readOnlyHint: false, destructiveHint: false },
|
|
257
261
|
inputSchema: {
|
|
258
|
-
presentationId: z.string().describe(
|
|
262
|
+
presentationId: z.string().describe(`PRESENTATION node id${NODE_TARGET_HINT}`),
|
|
259
263
|
slideId: z.string().describe('Slide id to update (must belong to the deck)'),
|
|
260
264
|
slide: slideShape.describe('Partial slide fields to merge into the existing slide'),
|
|
261
265
|
},
|
|
@@ -263,6 +267,7 @@ export function registerPresentationTools(server) {
|
|
|
263
267
|
async ({ presentationId, slideId, slide }) => {
|
|
264
268
|
try {
|
|
265
269
|
const client = makeClient();
|
|
270
|
+
presentationId = await resolveNodeId(client, presentationId);
|
|
266
271
|
const result = await client.updatePresentationSlide(presentationId, slideId, slide);
|
|
267
272
|
return jsonResult(result);
|
|
268
273
|
} catch (err) {
|
|
@@ -280,13 +285,14 @@ export function registerPresentationTools(server) {
|
|
|
280
285
|
'in the new desired order — call rootr_read_presentation first to get the current ids.',
|
|
281
286
|
annotations: { readOnlyHint: false, destructiveHint: false },
|
|
282
287
|
inputSchema: {
|
|
283
|
-
presentationId: z.string().describe(
|
|
288
|
+
presentationId: z.string().describe(`PRESENTATION node id${NODE_TARGET_HINT}`),
|
|
284
289
|
order: z.array(z.string()).describe('Complete ordered array of slide ids'),
|
|
285
290
|
},
|
|
286
291
|
},
|
|
287
292
|
async ({ presentationId, order }) => {
|
|
288
293
|
try {
|
|
289
294
|
const client = makeClient();
|
|
295
|
+
presentationId = await resolveNodeId(client, presentationId);
|
|
290
296
|
const result = await client.reorderPresentationSlides(presentationId, order);
|
|
291
297
|
return jsonResult(result);
|
|
292
298
|
} catch (err) {
|
|
@@ -310,7 +316,7 @@ export function registerPresentationTools(server) {
|
|
|
310
316
|
annotations: { readOnlyHint: false, destructiveHint: false },
|
|
311
317
|
inputSchema: {
|
|
312
318
|
prompt: z.string().describe('English image description; ask for "no text" in the image'),
|
|
313
|
-
nodeId: z.string().optional().describe(
|
|
319
|
+
nodeId: z.string().optional().describe(`Presentation/node id${NODE_TARGET_HINT} to attach the image to (permission-checked)`),
|
|
314
320
|
workspace: z.string().optional().describe('Workspace id; defaults to ROOTR_WORKSPACE/config'),
|
|
315
321
|
},
|
|
316
322
|
},
|
|
@@ -318,6 +324,7 @@ export function registerPresentationTools(server) {
|
|
|
318
324
|
try {
|
|
319
325
|
const client = makeClient();
|
|
320
326
|
const ws = requireWorkspace(workspace);
|
|
327
|
+
nodeId = await resolveOptionalNodeId(client, nodeId);
|
|
321
328
|
const result = await client.generateImage(ws, { prompt, nodeId });
|
|
322
329
|
return jsonResult(result);
|
|
323
330
|
} catch (err) {
|
|
@@ -338,7 +345,7 @@ export function registerPresentationTools(server) {
|
|
|
338
345
|
annotations: { readOnlyHint: false, destructiveHint: false },
|
|
339
346
|
inputSchema: {
|
|
340
347
|
attachmentId: z.string().describe('Source attachment id (from rootr_generate_image or an upload)'),
|
|
341
|
-
nodeId: z.string().optional().describe(
|
|
348
|
+
nodeId: z.string().optional().describe(`Presentation/node id${NODE_TARGET_HINT} to attach the result to (permission-checked)`),
|
|
342
349
|
workspace: z.string().optional().describe('Workspace id; defaults to ROOTR_WORKSPACE/config'),
|
|
343
350
|
},
|
|
344
351
|
},
|
|
@@ -346,6 +353,7 @@ export function registerPresentationTools(server) {
|
|
|
346
353
|
try {
|
|
347
354
|
const client = makeClient();
|
|
348
355
|
const ws = requireWorkspace(workspace);
|
|
356
|
+
nodeId = await resolveOptionalNodeId(client, nodeId);
|
|
349
357
|
const result = await client.removeImageBackground(ws, { attachmentId, nodeId });
|
|
350
358
|
return jsonResult(result);
|
|
351
359
|
} catch (err) {
|
|
@@ -4,7 +4,7 @@ import { basename, extname, join, isAbsolute } from 'node:path';
|
|
|
4
4
|
|
|
5
5
|
import { z } from 'zod';
|
|
6
6
|
|
|
7
|
-
import { jsonResult, errorResult, makeClient, requireWorkspace } from './shared.js';
|
|
7
|
+
import { jsonResult, errorResult, makeClient, requireWorkspace, resolveNodeId, NODE_TARGET_HINT, resolveOptionalNodeId } from './shared.js';
|
|
8
8
|
import { RootrApiError } from '../client.js';
|
|
9
9
|
|
|
10
10
|
/** 100MB — the backend's hard limit on a single audio upload. */
|
|
@@ -72,7 +72,7 @@ export function registerRecordingTools(server) {
|
|
|
72
72
|
annotations: { readOnlyHint: false, destructiveHint: false },
|
|
73
73
|
inputSchema: {
|
|
74
74
|
name: z.string().optional().describe('Recording name/title, e.g. "2026-07-21 주간회의"'),
|
|
75
|
-
parentId: z.string().optional().describe(
|
|
75
|
+
parentId: z.string().optional().describe(`Parent folder/node id${NODE_TARGET_HINT}`),
|
|
76
76
|
path: z
|
|
77
77
|
.string()
|
|
78
78
|
.optional()
|
|
@@ -101,6 +101,7 @@ export function registerRecordingTools(server) {
|
|
|
101
101
|
try {
|
|
102
102
|
const ws = requireWorkspace(workspace);
|
|
103
103
|
const client = makeClient(ws);
|
|
104
|
+
parentId = await resolveOptionalNodeId(client, parentId);
|
|
104
105
|
const result = await client.createRecording(ws, {
|
|
105
106
|
name,
|
|
106
107
|
parentId,
|
|
@@ -130,7 +131,7 @@ export function registerRecordingTools(server) {
|
|
|
130
131
|
'rootr_transcribe_recording.',
|
|
131
132
|
annotations: { readOnlyHint: false, destructiveHint: true },
|
|
132
133
|
inputSchema: {
|
|
133
|
-
recordingId: z.string().describe(
|
|
134
|
+
recordingId: z.string().describe(`RECORDING node id${NODE_TARGET_HINT} (create one with rootr_create_recording)`),
|
|
134
135
|
filePath: z
|
|
135
136
|
.string()
|
|
136
137
|
.optional()
|
|
@@ -156,6 +157,7 @@ export function registerRecordingTools(server) {
|
|
|
156
157
|
async ({ recordingId, filePath, base64Data, filename, mimeType, durationSec }) => {
|
|
157
158
|
try {
|
|
158
159
|
const client = makeClient();
|
|
160
|
+
recordingId = await resolveNodeId(client, recordingId);
|
|
159
161
|
const data = readAudioBytes({ filePath, base64Data });
|
|
160
162
|
if (data.length > MAX_AUDIO_BYTES) {
|
|
161
163
|
throw new RootrApiError(
|
|
@@ -207,12 +209,13 @@ export function registerRecordingTools(server) {
|
|
|
207
209
|
'TRANSCRIBING = speech-to-text still running (poll again later); ' +
|
|
208
210
|
'READY = transcript available; FAILED = last transcription errored (see `error`).',
|
|
209
211
|
inputSchema: {
|
|
210
|
-
recordingId: z.string().describe(
|
|
212
|
+
recordingId: z.string().describe(`RECORDING node id${NODE_TARGET_HINT}`),
|
|
211
213
|
},
|
|
212
214
|
},
|
|
213
215
|
async ({ recordingId }) => {
|
|
214
216
|
try {
|
|
215
217
|
const client = makeClient();
|
|
218
|
+
recordingId = await resolveNodeId(client, recordingId);
|
|
216
219
|
const result = await client.getRecording(recordingId);
|
|
217
220
|
const segments = result?.transcript?.segments ?? [];
|
|
218
221
|
const names = result?.speakerNames ?? {};
|
|
@@ -240,7 +243,7 @@ export function registerRecordingTools(server) {
|
|
|
240
243
|
'{"S1":"김대표","S2":"이팀장"}. Merge-patch: only the keys you send change. The names apply to the ' +
|
|
241
244
|
'transcript everywhere (web UI, rootr_read_recording, knowledge graph).',
|
|
242
245
|
inputSchema: {
|
|
243
|
-
recordingId: z.string().describe(
|
|
246
|
+
recordingId: z.string().describe(`RECORDING node id${NODE_TARGET_HINT}`),
|
|
244
247
|
speakerNames: z
|
|
245
248
|
.record(z.string())
|
|
246
249
|
.optional()
|
|
@@ -260,6 +263,7 @@ export function registerRecordingTools(server) {
|
|
|
260
263
|
throw new RootrApiError('speakerNames 또는 expectedSpeakers 중 하나는 필요합니다.');
|
|
261
264
|
}
|
|
262
265
|
const client = makeClient();
|
|
266
|
+
recordingId = await resolveNodeId(client, recordingId);
|
|
263
267
|
const current = await client.getRecording(recordingId);
|
|
264
268
|
const merged = speakerNames
|
|
265
269
|
? { ...(current?.speakerNames ?? {}), ...speakerNames }
|
|
@@ -292,12 +296,13 @@ export function registerRecordingTools(server) {
|
|
|
292
296
|
'take a few minutes).',
|
|
293
297
|
annotations: { readOnlyHint: false, destructiveHint: true },
|
|
294
298
|
inputSchema: {
|
|
295
|
-
recordingId: z.string().describe(
|
|
299
|
+
recordingId: z.string().describe(`RECORDING node id${NODE_TARGET_HINT}`),
|
|
296
300
|
},
|
|
297
301
|
},
|
|
298
302
|
async ({ recordingId }) => {
|
|
299
303
|
try {
|
|
300
304
|
const client = makeClient();
|
|
305
|
+
recordingId = await resolveNodeId(client, recordingId);
|
|
301
306
|
const result = await client.transcribeRecording(recordingId);
|
|
302
307
|
return jsonResult({
|
|
303
308
|
...result,
|
|
@@ -326,7 +331,7 @@ export function registerRecordingTools(server) {
|
|
|
326
331
|
'for Rootr\'s built-in summary.',
|
|
327
332
|
annotations: { readOnlyHint: false, destructiveHint: false },
|
|
328
333
|
inputSchema: {
|
|
329
|
-
recordingId: z.string().describe(
|
|
334
|
+
recordingId: z.string().describe(`RECORDING node id${NODE_TARGET_HINT} (status must be READY)`),
|
|
330
335
|
format: z
|
|
331
336
|
.enum(['summary', 'onepager'])
|
|
332
337
|
.optional()
|
|
@@ -343,6 +348,7 @@ export function registerRecordingTools(server) {
|
|
|
343
348
|
async ({ recordingId, locale, format }) => {
|
|
344
349
|
try {
|
|
345
350
|
const client = makeClient();
|
|
351
|
+
recordingId = await resolveNodeId(client, recordingId);
|
|
346
352
|
const result = await client.summarizeRecording(recordingId, { locale, format });
|
|
347
353
|
return jsonResult(result);
|
|
348
354
|
} catch (err) {
|
|
@@ -370,7 +376,7 @@ export function registerRecordingTools(server) {
|
|
|
370
376
|
'this way lives wherever you put it (e.g. a document via rootr_write), not in the recording node.',
|
|
371
377
|
annotations: { readOnlyHint: true },
|
|
372
378
|
inputSchema: {
|
|
373
|
-
recordingId: z.string().describe(
|
|
379
|
+
recordingId: z.string().describe(`RECORDING node id${NODE_TARGET_HINT}`),
|
|
374
380
|
filePath: z
|
|
375
381
|
.string()
|
|
376
382
|
.optional()
|
|
@@ -387,6 +393,7 @@ export function registerRecordingTools(server) {
|
|
|
387
393
|
async ({ recordingId, filePath, urlOnly }) => {
|
|
388
394
|
try {
|
|
389
395
|
const client = makeClient();
|
|
396
|
+
recordingId = await resolveNodeId(client, recordingId);
|
|
390
397
|
const rec = await client.getRecording(recordingId);
|
|
391
398
|
if (!rec?.audio?.attachmentId) {
|
|
392
399
|
throw new RootrApiError('이 녹음에는 아직 오디오가 없습니다. rootr_upload_recording_audio로 먼저 올리세요.');
|
package/lib/mcp-tools/shared.js
CHANGED
|
@@ -1,6 +1,32 @@
|
|
|
1
1
|
import { loadConfig, keyForWorkspace } from '../config.js';
|
|
2
2
|
import { RootrClient, RootrApiError } from '../client.js';
|
|
3
3
|
import { currentClientOverride } from '../context.js';
|
|
4
|
+
import { resolveTarget, requireResolved } from '../resolve.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Suffix for the `.describe()` of any node-id parameter: every typed-node tool
|
|
8
|
+
* takes a path too, exactly like rootr_read, so an agent that only knows where
|
|
9
|
+
* a node lives in the tree never has to hunt for its uuid.
|
|
10
|
+
*/
|
|
11
|
+
export const NODE_TARGET_HINT = ', or its path starting with "/"';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Resolve a path-or-id argument to a node id. Paths go through the by-path
|
|
15
|
+
* endpoint; anything else is already an id and is passed through untouched.
|
|
16
|
+
*/
|
|
17
|
+
export async function resolveNodeId(client, target) {
|
|
18
|
+
return requireResolved(await resolveTarget(client, target));
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Same as resolveNodeId, but for optional/nullable arguments (parentId,
|
|
23
|
+
* targetDatabaseId, scopeNodeId …) — undefined/null pass through untouched so
|
|
24
|
+
* "omitted" and "explicitly cleared" keep their meaning.
|
|
25
|
+
*/
|
|
26
|
+
export async function resolveOptionalNodeId(client, target) {
|
|
27
|
+
if (target === undefined || target === null || target === '') return target;
|
|
28
|
+
return resolveNodeId(client, target);
|
|
29
|
+
}
|
|
4
30
|
|
|
5
31
|
export function textResult(text) {
|
|
6
32
|
return { content: [{ type: 'text', text }] };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
|
|
3
|
-
import { jsonResult, errorResult, makeClient, requireWorkspace } from './shared.js';
|
|
3
|
+
import { jsonResult, errorResult, makeClient, requireWorkspace, resolveNodeId, NODE_TARGET_HINT, resolveOptionalNodeId } from './shared.js';
|
|
4
4
|
|
|
5
5
|
const CELL_VALUE = z
|
|
6
6
|
.union([z.string(), z.number(), z.boolean(), z.null()])
|
|
@@ -32,7 +32,7 @@ export function registerSpreadsheetTools(server) {
|
|
|
32
32
|
annotations: { readOnlyHint: false, destructiveHint: false },
|
|
33
33
|
inputSchema: {
|
|
34
34
|
name: z.string().optional().describe('Spreadsheet name/title'),
|
|
35
|
-
parentId: z.string().optional().describe(
|
|
35
|
+
parentId: z.string().optional().describe(`Parent folder/node id${NODE_TARGET_HINT}`),
|
|
36
36
|
icon: z.string().optional().describe('Emoji icon for the node'),
|
|
37
37
|
data: z
|
|
38
38
|
.object({ sheets: z.array(z.record(z.unknown())).optional() })
|
|
@@ -46,6 +46,7 @@ export function registerSpreadsheetTools(server) {
|
|
|
46
46
|
try {
|
|
47
47
|
const ws = requireWorkspace(workspace);
|
|
48
48
|
const client = makeClient(ws);
|
|
49
|
+
parentId = await resolveOptionalNodeId(client, parentId);
|
|
49
50
|
const result = await client.createSpreadsheet(ws, { name, parentId, icon, data, config });
|
|
50
51
|
return jsonResult(result);
|
|
51
52
|
} catch (err) {
|
|
@@ -64,12 +65,13 @@ export function registerSpreadsheetTools(server) {
|
|
|
64
65
|
'and cell contents.',
|
|
65
66
|
annotations: { readOnlyHint: true, destructiveHint: false },
|
|
66
67
|
inputSchema: {
|
|
67
|
-
spreadsheetId: z.string().describe(
|
|
68
|
+
spreadsheetId: z.string().describe(`SPREADSHEET node id${NODE_TARGET_HINT}`),
|
|
68
69
|
},
|
|
69
70
|
},
|
|
70
71
|
async ({ spreadsheetId }) => {
|
|
71
72
|
try {
|
|
72
73
|
const client = makeClient();
|
|
74
|
+
spreadsheetId = await resolveNodeId(client, spreadsheetId);
|
|
73
75
|
const result = await client.getSpreadsheet(spreadsheetId);
|
|
74
76
|
return jsonResult(result);
|
|
75
77
|
} catch (err) {
|
|
@@ -88,7 +90,7 @@ export function registerSpreadsheetTools(server) {
|
|
|
88
90
|
'you want to keep, or prefer rootr_create_sheet / rootr_patch_spreadsheet_cells for incremental changes).',
|
|
89
91
|
annotations: { readOnlyHint: false, destructiveHint: false },
|
|
90
92
|
inputSchema: {
|
|
91
|
-
spreadsheetId: z.string().describe(
|
|
93
|
+
spreadsheetId: z.string().describe(`SPREADSHEET node id${NODE_TARGET_HINT}`),
|
|
92
94
|
sheets: z
|
|
93
95
|
.array(z.record(z.unknown()))
|
|
94
96
|
.optional()
|
|
@@ -100,6 +102,7 @@ export function registerSpreadsheetTools(server) {
|
|
|
100
102
|
async ({ spreadsheetId, sheets, config, icon }) => {
|
|
101
103
|
try {
|
|
102
104
|
const client = makeClient();
|
|
105
|
+
spreadsheetId = await resolveNodeId(client, spreadsheetId);
|
|
103
106
|
const result = await client.updateSpreadsheet(spreadsheetId, { sheets, config, icon });
|
|
104
107
|
return jsonResult(result);
|
|
105
108
|
} catch (err) {
|
|
@@ -115,7 +118,7 @@ export function registerSpreadsheetTools(server) {
|
|
|
115
118
|
description: 'Add a new empty sheet (tab) to an existing Rootr (루터) SPREADSHEET, without touching other sheets.',
|
|
116
119
|
annotations: { readOnlyHint: false, destructiveHint: false },
|
|
117
120
|
inputSchema: {
|
|
118
|
-
spreadsheetId: z.string().describe(
|
|
121
|
+
spreadsheetId: z.string().describe(`SPREADSHEET node id${NODE_TARGET_HINT}`),
|
|
119
122
|
name: z.string().optional().describe('Sheet name, e.g. "Q1 Budget"'),
|
|
120
123
|
rowCount: z.number().int().min(1).max(10_000).optional().describe('Row count (default 1000)'),
|
|
121
124
|
colCount: z.number().int().min(1).max(500).optional().describe('Column count (default 52)'),
|
|
@@ -124,6 +127,7 @@ export function registerSpreadsheetTools(server) {
|
|
|
124
127
|
async ({ spreadsheetId, name, rowCount, colCount }) => {
|
|
125
128
|
try {
|
|
126
129
|
const client = makeClient();
|
|
130
|
+
spreadsheetId = await resolveNodeId(client, spreadsheetId);
|
|
127
131
|
const result = await client.createSheet(spreadsheetId, { name, rowCount, colCount });
|
|
128
132
|
return jsonResult(result);
|
|
129
133
|
} catch (err) {
|
|
@@ -141,7 +145,7 @@ export function registerSpreadsheetTools(server) {
|
|
|
141
145
|
'Call rootr_read_spreadsheet first to get the exact sheetId.',
|
|
142
146
|
annotations: { readOnlyHint: false, destructiveHint: false },
|
|
143
147
|
inputSchema: {
|
|
144
|
-
spreadsheetId: z.string().describe(
|
|
148
|
+
spreadsheetId: z.string().describe(`SPREADSHEET node id${NODE_TARGET_HINT}`),
|
|
145
149
|
sheetId: z.string().describe('Sheet id to update'),
|
|
146
150
|
name: z.string().optional().describe('New sheet name'),
|
|
147
151
|
rowCount: z.number().int().min(1).max(10_000).optional(),
|
|
@@ -157,6 +161,7 @@ export function registerSpreadsheetTools(server) {
|
|
|
157
161
|
async ({ spreadsheetId, sheetId, name, rowCount, colCount, rowSizes, colSizes, merges }) => {
|
|
158
162
|
try {
|
|
159
163
|
const client = makeClient();
|
|
164
|
+
spreadsheetId = await resolveNodeId(client, spreadsheetId);
|
|
160
165
|
const result = await client.patchSheet(spreadsheetId, sheetId, {
|
|
161
166
|
name,
|
|
162
167
|
rowCount,
|
|
@@ -179,13 +184,14 @@ export function registerSpreadsheetTools(server) {
|
|
|
179
184
|
description: 'Delete one sheet (tab) from a Rootr (루터) SPREADSHEET by sheetId. Not recoverable — read first to confirm.',
|
|
180
185
|
annotations: { readOnlyHint: false, destructiveHint: true },
|
|
181
186
|
inputSchema: {
|
|
182
|
-
spreadsheetId: z.string().describe(
|
|
187
|
+
spreadsheetId: z.string().describe(`SPREADSHEET node id${NODE_TARGET_HINT}`),
|
|
183
188
|
sheetId: z.string().describe('Sheet id to delete'),
|
|
184
189
|
},
|
|
185
190
|
},
|
|
186
191
|
async ({ spreadsheetId, sheetId }) => {
|
|
187
192
|
try {
|
|
188
193
|
const client = makeClient();
|
|
194
|
+
spreadsheetId = await resolveNodeId(client, spreadsheetId);
|
|
189
195
|
await client.deleteSheet(spreadsheetId, sheetId);
|
|
190
196
|
return jsonResult({ deleted: sheetId });
|
|
191
197
|
} catch (err) {
|
|
@@ -204,7 +210,7 @@ export function registerSpreadsheetTools(server) {
|
|
|
204
210
|
CELLS_GUIDE,
|
|
205
211
|
annotations: { readOnlyHint: false, destructiveHint: false },
|
|
206
212
|
inputSchema: {
|
|
207
|
-
spreadsheetId: z.string().describe(
|
|
213
|
+
spreadsheetId: z.string().describe(`SPREADSHEET node id${NODE_TARGET_HINT}`),
|
|
208
214
|
sheetId: z.string().describe('Sheet id to patch'),
|
|
209
215
|
cells: z
|
|
210
216
|
.record(cellShape.nullable())
|
|
@@ -214,6 +220,7 @@ export function registerSpreadsheetTools(server) {
|
|
|
214
220
|
async ({ spreadsheetId, sheetId, cells }) => {
|
|
215
221
|
try {
|
|
216
222
|
const client = makeClient();
|
|
223
|
+
spreadsheetId = await resolveNodeId(client, spreadsheetId);
|
|
217
224
|
const result = await client.patchCells(spreadsheetId, sheetId, cells);
|
|
218
225
|
return jsonResult(result);
|
|
219
226
|
} catch (err) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
|
|
3
|
-
import { jsonResult, errorResult, makeClient, requireWorkspace } from './shared.js';
|
|
3
|
+
import { jsonResult, errorResult, makeClient, requireWorkspace, resolveOptionalNodeId, NODE_TARGET_HINT } from './shared.js';
|
|
4
4
|
|
|
5
5
|
const VALID_EVENTS = [
|
|
6
6
|
'document.created',
|
|
@@ -60,7 +60,7 @@ export function registerWebhookTools(server) {
|
|
|
60
60
|
url: z.string().describe('HTTPS endpoint to receive webhook POSTs'),
|
|
61
61
|
events: z.array(z.string()).describe(`Event names to subscribe to, from: ${VALID_EVENTS.join(', ')}`),
|
|
62
62
|
name: z.string().optional().describe('Human-readable name for the webhook'),
|
|
63
|
-
scopeNodeId: z.string().optional().describe(
|
|
63
|
+
scopeNodeId: z.string().optional().describe(`Node id${NODE_TARGET_HINT} — restrict to this node and its descendants`),
|
|
64
64
|
filterTags: z.array(z.string()).optional().describe('Only fire for nodes carrying one of these tags'),
|
|
65
65
|
debounceSeconds: z.number().optional().describe('Trailing debounce window in seconds (server-capped)'),
|
|
66
66
|
workspace: z
|
|
@@ -73,6 +73,7 @@ export function registerWebhookTools(server) {
|
|
|
73
73
|
try {
|
|
74
74
|
const ws = requireWorkspace(workspace);
|
|
75
75
|
const client = makeClient(ws);
|
|
76
|
+
scopeNodeId = await resolveOptionalNodeId(client, scopeNodeId);
|
|
76
77
|
const result = await client.createWebhook(ws, {
|
|
77
78
|
name,
|
|
78
79
|
url,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
|
|
3
|
-
import { jsonResult, errorResult, makeClient, requireWorkspace } from './shared.js';
|
|
3
|
+
import { jsonResult, errorResult, makeClient, requireWorkspace, resolveNodeId, NODE_TARGET_HINT, resolveOptionalNodeId } from './shared.js';
|
|
4
4
|
|
|
5
5
|
const shapeShape = z
|
|
6
6
|
.object({
|
|
@@ -67,7 +67,7 @@ export function registerWhiteboardTools(server) {
|
|
|
67
67
|
annotations: { readOnlyHint: false, destructiveHint: false },
|
|
68
68
|
inputSchema: {
|
|
69
69
|
name: z.string().optional().describe('Whiteboard name/title'),
|
|
70
|
-
parentId: z.string().optional().describe(
|
|
70
|
+
parentId: z.string().optional().describe(`Parent folder/node id${NODE_TARGET_HINT}`),
|
|
71
71
|
icon: z.string().optional().describe('Emoji icon for the node'),
|
|
72
72
|
scene: z
|
|
73
73
|
.union([sceneShape, excalidrawSceneShape])
|
|
@@ -81,6 +81,7 @@ export function registerWhiteboardTools(server) {
|
|
|
81
81
|
try {
|
|
82
82
|
const ws = requireWorkspace(workspace);
|
|
83
83
|
const client = makeClient(ws);
|
|
84
|
+
parentId = await resolveOptionalNodeId(client, parentId);
|
|
84
85
|
const result = await client.createWhiteboard(ws, { name, parentId, icon, scene, config });
|
|
85
86
|
return jsonResult(result);
|
|
86
87
|
} catch (err) {
|
|
@@ -101,13 +102,14 @@ export function registerWhiteboardTools(server) {
|
|
|
101
102
|
'the current ids.',
|
|
102
103
|
annotations: { readOnlyHint: true, destructiveHint: false },
|
|
103
104
|
inputSchema: {
|
|
104
|
-
whiteboardId: z.string().describe(
|
|
105
|
+
whiteboardId: z.string().describe(`WHITEBOARD node id${NODE_TARGET_HINT}`),
|
|
105
106
|
mode: z.enum(['summary', 'full']).optional().describe('"summary" (default) or "full" (raw elements too)'),
|
|
106
107
|
},
|
|
107
108
|
},
|
|
108
109
|
async ({ whiteboardId, mode }) => {
|
|
109
110
|
try {
|
|
110
111
|
const client = makeClient();
|
|
112
|
+
whiteboardId = await resolveNodeId(client, whiteboardId);
|
|
111
113
|
const result = await client.getWhiteboard(whiteboardId);
|
|
112
114
|
const { scene, ...rest } = result ?? {};
|
|
113
115
|
if (mode !== 'full') return jsonResult(rest);
|
|
@@ -139,7 +141,7 @@ export function registerWhiteboardTools(server) {
|
|
|
139
141
|
AUTHORING_GUIDE,
|
|
140
142
|
annotations: { readOnlyHint: false, destructiveHint: false },
|
|
141
143
|
inputSchema: {
|
|
142
|
-
whiteboardId: z.string().describe(
|
|
144
|
+
whiteboardId: z.string().describe(`WHITEBOARD node id${NODE_TARGET_HINT}`),
|
|
143
145
|
scene: z
|
|
144
146
|
.union([sceneShape, excalidrawSceneShape])
|
|
145
147
|
.optional()
|
|
@@ -155,6 +157,7 @@ export function registerWhiteboardTools(server) {
|
|
|
155
157
|
async ({ whiteboardId, scene, merge, config, icon }) => {
|
|
156
158
|
try {
|
|
157
159
|
const client = makeClient();
|
|
160
|
+
whiteboardId = await resolveNodeId(client, whiteboardId);
|
|
158
161
|
const result = await client.updateWhiteboard(whiteboardId, { scene, merge, config, icon });
|
|
159
162
|
const { scene: _scene, ...rest } = result ?? {};
|
|
160
163
|
return jsonResult(rest);
|