rootr-cli 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/client.js +36 -0
- package/lib/mcp-tools/databases.js +163 -0
- package/lib/mcp.js +5 -1
- package/package.json +2 -12
package/lib/client.js
CHANGED
|
@@ -465,4 +465,40 @@ export class RootrClient {
|
|
|
465
465
|
method: 'DELETE',
|
|
466
466
|
});
|
|
467
467
|
}
|
|
468
|
+
|
|
469
|
+
// ---- Databases (rows) -------------------------------------------------
|
|
470
|
+
|
|
471
|
+
/** Database definition: { id, name, icon, path, properties[], views[], rowCount }. */
|
|
472
|
+
async getDatabase(databaseId) {
|
|
473
|
+
return this.request(`/databases/${encodeURIComponent(databaseId)}`);
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
/** All rows of a database, in position order. Returns the raw rows array. */
|
|
477
|
+
async listRows(databaseId) {
|
|
478
|
+
const data = await this.request(`/databases/${encodeURIComponent(databaseId)}/rows`);
|
|
479
|
+
if (Array.isArray(data)) return data;
|
|
480
|
+
if (data && Array.isArray(data.rows)) return data.rows;
|
|
481
|
+
return [];
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
async createRow(databaseId, { values, position } = {}) {
|
|
485
|
+
return this.request(`/databases/${encodeURIComponent(databaseId)}/rows`, {
|
|
486
|
+
method: 'POST',
|
|
487
|
+
body: { values, position },
|
|
488
|
+
});
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
async updateRow(databaseId, rowId, { values, position } = {}) {
|
|
492
|
+
return this.request(
|
|
493
|
+
`/databases/${encodeURIComponent(databaseId)}/rows/${encodeURIComponent(rowId)}`,
|
|
494
|
+
{ method: 'PATCH', body: { values, position } },
|
|
495
|
+
);
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
async deleteRow(databaseId, rowId) {
|
|
499
|
+
return this.request(
|
|
500
|
+
`/databases/${encodeURIComponent(databaseId)}/rows/${encodeURIComponent(rowId)}`,
|
|
501
|
+
{ method: 'DELETE' },
|
|
502
|
+
);
|
|
503
|
+
}
|
|
468
504
|
}
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
|
|
3
|
+
import { jsonResult, errorResult, makeClient } from './shared.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Resolve a `{propertyNameOrId: value}` map to `{propertyId: value}` using the
|
|
7
|
+
* database schema, so agents can address columns by their human name OR their
|
|
8
|
+
* id. `null` is preserved (clear marker for PATCH). Throws on an unknown key so
|
|
9
|
+
* the model is told to read the schema first.
|
|
10
|
+
*/
|
|
11
|
+
function resolveValueKeys(properties, values) {
|
|
12
|
+
const byId = new Map(properties.map((p) => [p.id, p]));
|
|
13
|
+
const byName = new Map(properties.map((p) => [p.name.toLowerCase(), p]));
|
|
14
|
+
const out = {};
|
|
15
|
+
for (const [key, v] of Object.entries(values || {})) {
|
|
16
|
+
const prop = byId.get(key) ?? byName.get(String(key).toLowerCase());
|
|
17
|
+
if (!prop) {
|
|
18
|
+
throw new Error(`Unknown property "${key}". Call rootr_read_database first to see the column ids/names.`);
|
|
19
|
+
}
|
|
20
|
+
out[prop.id] = v;
|
|
21
|
+
}
|
|
22
|
+
return out;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Database (Notion-style table/board/kanban) row tools. The REST layer already
|
|
27
|
+
* exposes GET /databases/:id (schema), GET/POST /databases/:id/rows and
|
|
28
|
+
* PATCH/DELETE /databases/:id/rows/:rowId — these tools wrap it so agents can
|
|
29
|
+
* read the schema, then create/move/edit/delete rows (e.g. change a select
|
|
30
|
+
* "status" column = move a kanban card between columns).
|
|
31
|
+
*/
|
|
32
|
+
export function registerDatabaseTools(server) {
|
|
33
|
+
server.registerTool(
|
|
34
|
+
'rootr_read_database',
|
|
35
|
+
{
|
|
36
|
+
title: 'Read a database schema and rows',
|
|
37
|
+
description:
|
|
38
|
+
'Read a Rootr (루터) DATABASE node — its schema (properties with ids/types and select options) plus all rows ' +
|
|
39
|
+
'(each with its rowId and values). ALWAYS call this before rootr_add_row / rootr_update_row so you know the exact ' +
|
|
40
|
+
'property ids/names and, for board/kanban views, which select column holds the status. Only works on DATABASE nodes.',
|
|
41
|
+
inputSchema: {
|
|
42
|
+
databaseId: z.string().describe('DATABASE node id'),
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
async ({ databaseId }) => {
|
|
46
|
+
try {
|
|
47
|
+
const client = makeClient();
|
|
48
|
+
const [database, rows] = await Promise.all([
|
|
49
|
+
client.getDatabase(databaseId),
|
|
50
|
+
client.listRows(databaseId),
|
|
51
|
+
]);
|
|
52
|
+
return jsonResult({ database, rows });
|
|
53
|
+
} catch (err) {
|
|
54
|
+
return errorResult(err);
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
server.registerTool(
|
|
60
|
+
'rootr_list_rows',
|
|
61
|
+
{
|
|
62
|
+
title: 'List database rows',
|
|
63
|
+
description:
|
|
64
|
+
'List the rows of a Rootr (루터) DATABASE (id + values + position), without the schema. ' +
|
|
65
|
+
'Use rootr_read_database instead when you also need the property/column definitions.',
|
|
66
|
+
inputSchema: {
|
|
67
|
+
databaseId: z.string().describe('DATABASE node id'),
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
async ({ databaseId }) => {
|
|
71
|
+
try {
|
|
72
|
+
const client = makeClient();
|
|
73
|
+
const rows = await client.listRows(databaseId);
|
|
74
|
+
return jsonResult({ rows });
|
|
75
|
+
} catch (err) {
|
|
76
|
+
return errorResult(err);
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
server.registerTool(
|
|
82
|
+
'rootr_add_row',
|
|
83
|
+
{
|
|
84
|
+
title: 'Add a database row',
|
|
85
|
+
description:
|
|
86
|
+
'Append a row to a Rootr (루터) DATABASE. `values` maps property NAME (or id) → value. ' +
|
|
87
|
+
'Value shapes by type: title/text/url/select/person → string; number → number; multi_select → string[]; ' +
|
|
88
|
+
'checkbox → boolean; date → { start: "YYYY-MM-DD", end? }. A select value is the OPTION NAME. ' +
|
|
89
|
+
'Call rootr_read_database first to learn the columns. Optionally set `position` to insert at an index.',
|
|
90
|
+
inputSchema: {
|
|
91
|
+
databaseId: z.string().describe('DATABASE node id'),
|
|
92
|
+
values: z.record(z.unknown()).describe('{ propertyNameOrId: value } for the new row'),
|
|
93
|
+
position: z.number().optional().describe('0-based insert index; appended at the end if omitted'),
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
async ({ databaseId, values, position }) => {
|
|
97
|
+
try {
|
|
98
|
+
const client = makeClient();
|
|
99
|
+
const database = await client.getDatabase(databaseId);
|
|
100
|
+
const resolved = resolveValueKeys(database.properties || [], values);
|
|
101
|
+
const row = await client.createRow(databaseId, { values: resolved, position });
|
|
102
|
+
return jsonResult(row);
|
|
103
|
+
} catch (err) {
|
|
104
|
+
return errorResult(err);
|
|
105
|
+
}
|
|
106
|
+
},
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
server.registerTool(
|
|
110
|
+
'rootr_update_row',
|
|
111
|
+
{
|
|
112
|
+
title: 'Update a database row',
|
|
113
|
+
description:
|
|
114
|
+
'Merge-patch ONE row of a Rootr (루터) DATABASE by rowId (get rowIds from rootr_read_database). ' +
|
|
115
|
+
'`values` maps property NAME (or id) → new value; only the passed keys change, `null` clears a key, omitted keys ' +
|
|
116
|
+
'are left unchanged. To MOVE a kanban card to another column, set its select/status property to the new option name. ' +
|
|
117
|
+
'Optionally set `position` to reorder the row.',
|
|
118
|
+
inputSchema: {
|
|
119
|
+
databaseId: z.string().describe('DATABASE node id'),
|
|
120
|
+
rowId: z.string().describe('Row id (must belong to the database)'),
|
|
121
|
+
values: z.record(z.unknown()).optional().describe('{ propertyNameOrId: newValue | null } partial patch'),
|
|
122
|
+
position: z.number().optional().describe('New 0-based position for the row'),
|
|
123
|
+
},
|
|
124
|
+
},
|
|
125
|
+
async ({ databaseId, rowId, values, position }) => {
|
|
126
|
+
try {
|
|
127
|
+
const client = makeClient();
|
|
128
|
+
let resolved;
|
|
129
|
+
if (values !== undefined) {
|
|
130
|
+
const database = await client.getDatabase(databaseId);
|
|
131
|
+
resolved = resolveValueKeys(database.properties || [], values);
|
|
132
|
+
}
|
|
133
|
+
const row = await client.updateRow(databaseId, rowId, { values: resolved, position });
|
|
134
|
+
return jsonResult(row);
|
|
135
|
+
} catch (err) {
|
|
136
|
+
return errorResult(err);
|
|
137
|
+
}
|
|
138
|
+
},
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
server.registerTool(
|
|
142
|
+
'rootr_delete_row',
|
|
143
|
+
{
|
|
144
|
+
title: 'Delete a database row',
|
|
145
|
+
description:
|
|
146
|
+
'Delete ONE row of a Rootr (루터) DATABASE by rowId (its linked page, if any, is moved to the trash). ' +
|
|
147
|
+
'Verify the row via rootr_read_database first — do not guess rowIds.',
|
|
148
|
+
inputSchema: {
|
|
149
|
+
databaseId: z.string().describe('DATABASE node id'),
|
|
150
|
+
rowId: z.string().describe('Row id to delete (must belong to the database)'),
|
|
151
|
+
},
|
|
152
|
+
},
|
|
153
|
+
async ({ databaseId, rowId }) => {
|
|
154
|
+
try {
|
|
155
|
+
const client = makeClient();
|
|
156
|
+
await client.deleteRow(databaseId, rowId);
|
|
157
|
+
return jsonResult({ deleted: true, rowId });
|
|
158
|
+
} catch (err) {
|
|
159
|
+
return errorResult(err);
|
|
160
|
+
}
|
|
161
|
+
},
|
|
162
|
+
);
|
|
163
|
+
}
|
package/lib/mcp.js
CHANGED
|
@@ -6,6 +6,7 @@ import { registerWorkspaceTools } from './mcp-tools/workspaces.js';
|
|
|
6
6
|
import { registerLogTools } from './mcp-tools/logs.js';
|
|
7
7
|
import { registerAskTools } from './mcp-tools/ask.js';
|
|
8
8
|
import { registerIssueTools } from './mcp-tools/issues.js';
|
|
9
|
+
import { registerDatabaseTools } from './mcp-tools/databases.js';
|
|
9
10
|
import { registerWebhookTools } from './mcp-tools/webhooks.js';
|
|
10
11
|
import { registerMiscTools } from './mcp-tools/misc.js';
|
|
11
12
|
|
|
@@ -20,7 +21,9 @@ export async function startMcpServer({ name, version }) {
|
|
|
20
21
|
'rootr_edit for targeted changes; use rootr_write only for full rewrites. Beyond documents, this ' +
|
|
21
22
|
'server also covers workspace scaffolding (planning/creating/applying document trees), typed LOG ' +
|
|
22
23
|
'datastores (structured entries + stats), GraphRAG root-cause Q&A (rootr_ask), GitHub-style issue ' +
|
|
23
|
-
'trackers,
|
|
24
|
+
'trackers, DATABASE rows (read schema + rows, add/update/delete rows — e.g. move a kanban card by ' +
|
|
25
|
+
'changing its status column, via rootr_read_database / rootr_add_row / rootr_update_row / rootr_delete_row), ' +
|
|
26
|
+
'and webhooks.\n\n' +
|
|
24
27
|
'Node types — pick the block that fits, never fake everything with markdown files: ' +
|
|
25
28
|
'DOCUMENT = free-form markdown narrative (overviews, specs, decision logs); ' +
|
|
26
29
|
'DATABASE = typed columns + views (table/board/calendar/timeline) for anything trackable — tasks, requirements, risks, milestones; ' +
|
|
@@ -42,6 +45,7 @@ export async function startMcpServer({ name, version }) {
|
|
|
42
45
|
registerLogTools(server);
|
|
43
46
|
registerAskTools(server);
|
|
44
47
|
registerIssueTools(server);
|
|
48
|
+
registerDatabaseTools(server);
|
|
45
49
|
registerWebhookTools(server);
|
|
46
50
|
registerMiscTools(server);
|
|
47
51
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rootr-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Official Rootr CLI + MCP server — read, write, edit and search Rootr documents from the shell or from an AI agent.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -14,17 +14,7 @@
|
|
|
14
14
|
"lib",
|
|
15
15
|
"README.md"
|
|
16
16
|
],
|
|
17
|
-
"
|
|
18
|
-
"rootr",
|
|
19
|
-
"mcp",
|
|
20
|
-
"model-context-protocol",
|
|
21
|
-
"ai-agent",
|
|
22
|
-
"knowledge-base",
|
|
23
|
-
"markdown",
|
|
24
|
-
"cli"
|
|
25
|
-
],
|
|
26
|
-
"homepage": "https://rootr.io",
|
|
27
|
-
"license": "MIT",
|
|
17
|
+
"license": "UNLICENSED",
|
|
28
18
|
"dependencies": {
|
|
29
19
|
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
30
20
|
"zod": "^3.23.8"
|