@abraca/mcp 1.0.18 → 1.0.21
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/dist/abracadabra-mcp.cjs +157 -64
- package/dist/abracadabra-mcp.cjs.map +1 -1
- package/dist/abracadabra-mcp.esm.js +157 -64
- package/dist/abracadabra-mcp.esm.js.map +1 -1
- package/package.json +1 -1
- package/src/index.ts +9 -8
- package/src/server.ts +2 -0
- package/src/tools/meta.ts +22 -4
- package/src/tools/tree.ts +61 -10
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -48,6 +48,13 @@ async function main() {
|
|
|
48
48
|
capabilities: { experimental: { 'claude/channel': {} } },
|
|
49
49
|
instructions: `Abracadabra is a CRDT collaboration platform where everything is a document in a tree.
|
|
50
50
|
|
|
51
|
+
## CRITICAL: Responding to Channel Events
|
|
52
|
+
The user CANNOT see your plain text output — they only see messages sent via MCP tools. When you receive a channel event:
|
|
53
|
+
- **Chat messages**: Use send_chat_message with the SAME channel for ALL replies AND progress updates.
|
|
54
|
+
- **AI tasks**: Use reply tool for the final response; use send_chat_message for progress updates.
|
|
55
|
+
- For multi-step work, send brief status updates via send_chat_message so the user sees progress.
|
|
56
|
+
- NEVER output plain text as a response or status update — it is invisible to the user.
|
|
57
|
+
|
|
51
58
|
## Quick Start Workflow
|
|
52
59
|
1. list_spaces → note the active space's doc_id (this is the hub/root document)
|
|
53
60
|
2. get_document_tree(rootId: hubDocId) → see the FULL hierarchy before doing anything
|
|
@@ -57,12 +64,12 @@ async function main() {
|
|
|
57
64
|
## Key Concepts
|
|
58
65
|
- Documents form a tree. A kanban board's columns are child documents; cards are grandchildren.
|
|
59
66
|
- A document's label IS its display name everywhere. Children ARE the content (not just the body text).
|
|
60
|
-
- Page types
|
|
67
|
+
- Page types are views over the SAME tree — switching types preserves data.
|
|
61
68
|
- An empty markdown body does NOT mean empty content — always check the children array.
|
|
62
69
|
- Use ![[docId]] in content to embed another document, or [[docId|label]] for inline links.
|
|
63
70
|
|
|
64
71
|
## Finding Documents
|
|
65
|
-
- list_documents only shows ONE level of children.
|
|
72
|
+
- list_documents only shows ONE level of children. Use find_document to search by name, or get_document_tree to see the full hierarchy.
|
|
66
73
|
- NEVER conclude a document doesn't exist after only checking root-level documents.
|
|
67
74
|
|
|
68
75
|
## Rules
|
|
@@ -72,12 +79,6 @@ async function main() {
|
|
|
72
79
|
- Never rename or write content to the hub document itself.
|
|
73
80
|
- Use universal meta keys (color, icon, dateStart) — never prefix with page type names.
|
|
74
81
|
|
|
75
|
-
## Channel Events
|
|
76
|
-
Events from the abracadabra channel arrive as <channel source="abracadabra" ...>. They may be:
|
|
77
|
-
- ai:task events from human users (includes sender, doc_id context)
|
|
78
|
-
- chat messages from the platform chat system (includes channel, sender, sender_id)
|
|
79
|
-
When you receive a channel event, read it, do the work using your tools, and reply using the reply tool (for document responses) or send_chat_message (for chat responses).
|
|
80
|
-
|
|
81
82
|
## Full Reference
|
|
82
83
|
Read the resource at abracadabra://agent-guide for the complete guide covering page type schemas, metadata reference, awareness/presence, content structure, and detailed examples.`,
|
|
83
84
|
},
|
package/src/server.ts
CHANGED
|
@@ -352,6 +352,7 @@ export class AbracadabraMCPServer {
|
|
|
352
352
|
method: 'notifications/claude/channel',
|
|
353
353
|
params: {
|
|
354
354
|
content: task.text,
|
|
355
|
+
instructions: `You MUST use the reply tool with doc_id="${task.docId ?? ''}" and task_id="${task.id}" for your final response. The user CANNOT see plain text output; they only see replies sent via MCP tools. For progress updates during multi-step work, use send_chat_message with channel="group:${task.docId ?? ''}" (e.g. "Looking into that..." or "Found it, writing up results..."). Never output plain text as a substitute for MCP tools.`,
|
|
355
356
|
meta: {
|
|
356
357
|
source: 'abracadabra',
|
|
357
358
|
type: 'ai_task',
|
|
@@ -433,6 +434,7 @@ export class AbracadabraMCPServer {
|
|
|
433
434
|
method: 'notifications/claude/channel',
|
|
434
435
|
params: {
|
|
435
436
|
content: data.content ?? '',
|
|
437
|
+
instructions: `You MUST use send_chat_message with channel="${channel ?? ''}" for ALL responses — both progress updates and final answers. The user CANNOT see plain text output; they only see messages sent via send_chat_message. When doing multi-step work, send brief status updates via send_chat_message (e.g. "Looking into that..." or "Found it, writing up results...") so the user knows you're working. Never output plain text as a substitute for send_chat_message.`,
|
|
436
438
|
meta: {
|
|
437
439
|
source: 'abracadabra',
|
|
438
440
|
type: 'chat_message',
|
package/src/tools/meta.ts
CHANGED
|
@@ -13,12 +13,21 @@ export function registerMetaTools(mcp: McpServer, server: AbracadabraMCPServer)
|
|
|
13
13
|
docId: z.string().describe('Document ID.'),
|
|
14
14
|
},
|
|
15
15
|
async ({ docId }) => {
|
|
16
|
+
server.setAutoStatus('reading', docId)
|
|
17
|
+
server.setActiveToolCall({ name: 'get_metadata', target: docId })
|
|
16
18
|
const treeMap = server.getTreeMap()
|
|
17
|
-
if (!treeMap)
|
|
19
|
+
if (!treeMap) {
|
|
20
|
+
server.setActiveToolCall(null)
|
|
21
|
+
return { content: [{ type: 'text', text: 'Not connected' }] }
|
|
22
|
+
}
|
|
18
23
|
|
|
19
24
|
const entry = treeMap.get(docId)
|
|
20
|
-
if (!entry)
|
|
25
|
+
if (!entry) {
|
|
26
|
+
server.setActiveToolCall(null)
|
|
27
|
+
return { content: [{ type: 'text', text: `Document ${docId} not found` }] }
|
|
28
|
+
}
|
|
21
29
|
|
|
30
|
+
server.setActiveToolCall(null)
|
|
22
31
|
return {
|
|
23
32
|
content: [{
|
|
24
33
|
type: 'text',
|
|
@@ -41,11 +50,19 @@ export function registerMetaTools(mcp: McpServer, server: AbracadabraMCPServer)
|
|
|
41
50
|
meta: z.record(z.unknown()).describe('Metadata fields to update (merged with existing). Universal keys: color (hex), icon (Lucide kebab-case — NEVER emoji), dateStart/dateEnd, datetimeStart/datetimeEnd, allDay, tags (string[]), checked (bool), priority (0=none,1=low,2=med,3=high,4=urgent), status, rating (0-5), url, email, phone, number, unit, subtitle, note, taskProgress (0-100), members ({id,label}[]), coverUploadId. Geo/Map: geoType ("marker"|"line"|"measure"), geoLat, geoLng, geoDescription. Spatial 3D: spShape ("box"|"sphere"|"cylinder"|"cone"|"plane"|"torus"|"glb"), spX/spY/spZ, spRX/spRY/spRZ, spSX/spSY/spSZ, spColor, spOpacity (0-100). Dashboard: deskX, deskY, deskZ, deskMode ("icon"|"widget-sm"|"widget-lg"). Renderer config (on the page doc itself): kanbanColumnWidth, galleryColumns, galleryAspect, calendarView, calendarWeekStart, tableMode, showRefEdges. Set a key to null to clear it.'),
|
|
42
51
|
},
|
|
43
52
|
async ({ docId, meta }) => {
|
|
53
|
+
server.setAutoStatus('writing', docId)
|
|
54
|
+
server.setActiveToolCall({ name: 'update_metadata', target: docId })
|
|
44
55
|
const treeMap = server.getTreeMap()
|
|
45
|
-
if (!treeMap)
|
|
56
|
+
if (!treeMap) {
|
|
57
|
+
server.setActiveToolCall(null)
|
|
58
|
+
return { content: [{ type: 'text', text: 'Not connected' }] }
|
|
59
|
+
}
|
|
46
60
|
|
|
47
61
|
const entry = treeMap.get(docId)
|
|
48
|
-
if (!entry)
|
|
62
|
+
if (!entry) {
|
|
63
|
+
server.setActiveToolCall(null)
|
|
64
|
+
return { content: [{ type: 'text', text: `Document ${docId} not found` }] }
|
|
65
|
+
}
|
|
49
66
|
|
|
50
67
|
treeMap.set(docId, {
|
|
51
68
|
...entry,
|
|
@@ -53,6 +70,7 @@ export function registerMetaTools(mcp: McpServer, server: AbracadabraMCPServer)
|
|
|
53
70
|
updatedAt: Date.now(),
|
|
54
71
|
})
|
|
55
72
|
|
|
73
|
+
server.setActiveToolCall(null)
|
|
56
74
|
return { content: [{ type: 'text', text: `Metadata updated for ${docId}` }] }
|
|
57
75
|
}
|
|
58
76
|
)
|
package/src/tools/tree.ts
CHANGED
|
@@ -78,13 +78,19 @@ export function registerTreeTools(mcp: McpServer, server: AbracadabraMCPServer)
|
|
|
78
78
|
'List direct children of a document (defaults to root). Returns id, label, type, meta, order. NOTE: Only returns ONE level. Use find_document to search by name across the full tree, or get_document_tree to see the complete hierarchy.',
|
|
79
79
|
{ parentId: z.string().optional().describe('Parent document ID. Omit for root-level documents.') },
|
|
80
80
|
async ({ parentId }) => {
|
|
81
|
+
server.setAutoStatus('reading')
|
|
82
|
+
server.setActiveToolCall({ name: 'list_documents' })
|
|
81
83
|
const treeMap = server.getTreeMap()
|
|
82
|
-
if (!treeMap)
|
|
84
|
+
if (!treeMap) {
|
|
85
|
+
server.setActiveToolCall(null)
|
|
86
|
+
return { content: [{ type: 'text', text: 'Not connected' }] }
|
|
87
|
+
}
|
|
83
88
|
|
|
84
89
|
const targetId = normalizeRootId(parentId, server)
|
|
85
90
|
const entries = readEntries(treeMap)
|
|
86
91
|
const children = childrenOf(entries, targetId)
|
|
87
92
|
|
|
93
|
+
server.setActiveToolCall(null)
|
|
88
94
|
return {
|
|
89
95
|
content: [{
|
|
90
96
|
type: 'text',
|
|
@@ -102,14 +108,20 @@ export function registerTreeTools(mcp: McpServer, server: AbracadabraMCPServer)
|
|
|
102
108
|
depth: z.number().optional().describe('Maximum depth to traverse. Default 3. Use -1 for unlimited.'),
|
|
103
109
|
},
|
|
104
110
|
async ({ rootId, depth }) => {
|
|
111
|
+
server.setAutoStatus('reading')
|
|
112
|
+
server.setActiveToolCall({ name: 'get_document_tree' })
|
|
105
113
|
const treeMap = server.getTreeMap()
|
|
106
|
-
if (!treeMap)
|
|
114
|
+
if (!treeMap) {
|
|
115
|
+
server.setActiveToolCall(null)
|
|
116
|
+
return { content: [{ type: 'text', text: 'Not connected' }] }
|
|
117
|
+
}
|
|
107
118
|
|
|
108
119
|
const targetId = normalizeRootId(rootId, server)
|
|
109
120
|
const maxDepth = depth ?? 3
|
|
110
121
|
const entries = readEntries(treeMap)
|
|
111
122
|
const tree = buildTree(entries, targetId, maxDepth)
|
|
112
123
|
|
|
124
|
+
server.setActiveToolCall(null)
|
|
113
125
|
return {
|
|
114
126
|
content: [{
|
|
115
127
|
type: 'text',
|
|
@@ -127,8 +139,13 @@ export function registerTreeTools(mcp: McpServer, server: AbracadabraMCPServer)
|
|
|
127
139
|
rootId: z.string().optional().describe('Restrict search to descendants of this document. Omit to search the entire tree.'),
|
|
128
140
|
},
|
|
129
141
|
async ({ query, rootId }) => {
|
|
142
|
+
server.setAutoStatus('searching')
|
|
143
|
+
server.setActiveToolCall({ name: 'find_document', target: query })
|
|
130
144
|
const treeMap = server.getTreeMap()
|
|
131
|
-
if (!treeMap)
|
|
145
|
+
if (!treeMap) {
|
|
146
|
+
server.setActiveToolCall(null)
|
|
147
|
+
return { content: [{ type: 'text', text: 'Not connected' }] }
|
|
148
|
+
}
|
|
132
149
|
|
|
133
150
|
const entries = readEntries(treeMap)
|
|
134
151
|
const lowerQuery = query.toLowerCase()
|
|
@@ -164,6 +181,7 @@ export function registerTreeTools(mcp: McpServer, server: AbracadabraMCPServer)
|
|
|
164
181
|
}
|
|
165
182
|
})
|
|
166
183
|
|
|
184
|
+
server.setActiveToolCall(null)
|
|
167
185
|
if (results.length === 0) {
|
|
168
186
|
return {
|
|
169
187
|
content: [{ type: 'text', text: `No documents found matching "${query}". Try get_document_tree to see the full hierarchy.` }],
|
|
@@ -231,13 +249,22 @@ export function registerTreeTools(mcp: McpServer, server: AbracadabraMCPServer)
|
|
|
231
249
|
label: z.string().describe('New display name.'),
|
|
232
250
|
},
|
|
233
251
|
async ({ id, label }) => {
|
|
252
|
+
server.setAutoStatus('writing')
|
|
253
|
+
server.setActiveToolCall({ name: 'rename_document', target: id })
|
|
234
254
|
const treeMap = server.getTreeMap()
|
|
235
|
-
if (!treeMap)
|
|
255
|
+
if (!treeMap) {
|
|
256
|
+
server.setActiveToolCall(null)
|
|
257
|
+
return { content: [{ type: 'text', text: 'Not connected' }] }
|
|
258
|
+
}
|
|
236
259
|
|
|
237
260
|
const entry = treeMap.get(id)
|
|
238
|
-
if (!entry)
|
|
261
|
+
if (!entry) {
|
|
262
|
+
server.setActiveToolCall(null)
|
|
263
|
+
return { content: [{ type: 'text', text: `Document ${id} not found` }] }
|
|
264
|
+
}
|
|
239
265
|
|
|
240
266
|
treeMap.set(id, { ...entry, label, updatedAt: Date.now() })
|
|
267
|
+
server.setActiveToolCall(null)
|
|
241
268
|
return { content: [{ type: 'text', text: `Renamed to "${label}"` }] }
|
|
242
269
|
}
|
|
243
270
|
)
|
|
@@ -251,11 +278,19 @@ export function registerTreeTools(mcp: McpServer, server: AbracadabraMCPServer)
|
|
|
251
278
|
order: z.number().optional().describe('New sort order. Defaults to Date.now() (append to end).'),
|
|
252
279
|
},
|
|
253
280
|
async ({ id, newParentId, order }) => {
|
|
281
|
+
server.setAutoStatus('writing')
|
|
282
|
+
server.setActiveToolCall({ name: 'move_document', target: id })
|
|
254
283
|
const treeMap = server.getTreeMap()
|
|
255
|
-
if (!treeMap)
|
|
284
|
+
if (!treeMap) {
|
|
285
|
+
server.setActiveToolCall(null)
|
|
286
|
+
return { content: [{ type: 'text', text: 'Not connected' }] }
|
|
287
|
+
}
|
|
256
288
|
|
|
257
289
|
const entry = treeMap.get(id)
|
|
258
|
-
if (!entry)
|
|
290
|
+
if (!entry) {
|
|
291
|
+
server.setActiveToolCall(null)
|
|
292
|
+
return { content: [{ type: 'text', text: `Document ${id} not found` }] }
|
|
293
|
+
}
|
|
259
294
|
|
|
260
295
|
treeMap.set(id, {
|
|
261
296
|
...entry,
|
|
@@ -263,6 +298,7 @@ export function registerTreeTools(mcp: McpServer, server: AbracadabraMCPServer)
|
|
|
263
298
|
order: order ?? Date.now(),
|
|
264
299
|
updatedAt: Date.now(),
|
|
265
300
|
})
|
|
301
|
+
server.setActiveToolCall(null)
|
|
266
302
|
return { content: [{ type: 'text', text: `Moved ${id} to parent ${newParentId}` }] }
|
|
267
303
|
}
|
|
268
304
|
)
|
|
@@ -274,10 +310,15 @@ export function registerTreeTools(mcp: McpServer, server: AbracadabraMCPServer)
|
|
|
274
310
|
id: z.string().describe('Document ID to delete.'),
|
|
275
311
|
},
|
|
276
312
|
async ({ id }) => {
|
|
313
|
+
server.setAutoStatus('writing')
|
|
314
|
+
server.setActiveToolCall({ name: 'delete_document', target: id })
|
|
277
315
|
const treeMap = server.getTreeMap()
|
|
278
316
|
const trashMap = server.getTrashMap()
|
|
279
317
|
const rootDoc = server.rootDocument
|
|
280
|
-
if (!treeMap || !trashMap || !rootDoc)
|
|
318
|
+
if (!treeMap || !trashMap || !rootDoc) {
|
|
319
|
+
server.setActiveToolCall(null)
|
|
320
|
+
return { content: [{ type: 'text', text: 'Not connected' }] }
|
|
321
|
+
}
|
|
281
322
|
|
|
282
323
|
const entries = readEntries(treeMap)
|
|
283
324
|
const toDelete = [id, ...descendantsOf(entries, id).map(e => e.id)]
|
|
@@ -299,6 +340,7 @@ export function registerTreeTools(mcp: McpServer, server: AbracadabraMCPServer)
|
|
|
299
340
|
}
|
|
300
341
|
})
|
|
301
342
|
|
|
343
|
+
server.setActiveToolCall(null)
|
|
302
344
|
return { content: [{ type: 'text', text: `Deleted ${toDelete.length} document(s)` }] }
|
|
303
345
|
}
|
|
304
346
|
)
|
|
@@ -311,13 +353,22 @@ export function registerTreeTools(mcp: McpServer, server: AbracadabraMCPServer)
|
|
|
311
353
|
type: z.string().describe('New page type (e.g. "doc", "kanban", "table", "calendar", "outline", "gallery", "slides", "timeline", "whiteboard", "map", "dashboard", "mindmap", "graph").'),
|
|
312
354
|
},
|
|
313
355
|
async ({ id, type }) => {
|
|
356
|
+
server.setAutoStatus('writing')
|
|
357
|
+
server.setActiveToolCall({ name: 'change_document_type', target: id })
|
|
314
358
|
const treeMap = server.getTreeMap()
|
|
315
|
-
if (!treeMap)
|
|
359
|
+
if (!treeMap) {
|
|
360
|
+
server.setActiveToolCall(null)
|
|
361
|
+
return { content: [{ type: 'text', text: 'Not connected' }] }
|
|
362
|
+
}
|
|
316
363
|
|
|
317
364
|
const entry = treeMap.get(id)
|
|
318
|
-
if (!entry)
|
|
365
|
+
if (!entry) {
|
|
366
|
+
server.setActiveToolCall(null)
|
|
367
|
+
return { content: [{ type: 'text', text: `Document ${id} not found` }] }
|
|
368
|
+
}
|
|
319
369
|
|
|
320
370
|
treeMap.set(id, { ...entry, type, updatedAt: Date.now() })
|
|
371
|
+
server.setActiveToolCall(null)
|
|
321
372
|
return { content: [{ type: 'text', text: `Changed type to "${type}"` }] }
|
|
322
373
|
}
|
|
323
374
|
)
|