@abraca/mcp 1.6.0 → 1.8.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/dist/abracadabra-mcp.cjs +1165 -190
- package/dist/abracadabra-mcp.cjs.map +1 -1
- package/dist/abracadabra-mcp.esm.js +1165 -190
- package/dist/abracadabra-mcp.esm.js.map +1 -1
- package/dist/index.d.ts +32 -1
- package/package.json +1 -1
- package/src/converters/page-types.ts +408 -0
- package/src/converters/types.ts +15 -11
- package/src/hook-bridge.ts +18 -8
- package/src/index.ts +29 -2
- package/src/mentions.ts +42 -0
- package/src/resources/agent-guide.ts +151 -29
- package/src/server.ts +139 -14
- package/src/tools/awareness.ts +3 -0
- package/src/tools/channel.ts +18 -8
- package/src/tools/content.ts +0 -5
- package/src/tools/files.ts +8 -0
- package/src/tools/meta.ts +1 -7
- package/src/tools/svg.ts +0 -3
- package/src/tools/tree.ts +28 -22
package/src/tools/awareness.ts
CHANGED
|
@@ -38,6 +38,7 @@ export function registerAwarenessTools(mcp: McpServer, server: AbracadabraMCPSer
|
|
|
38
38
|
fields: z.record(z.string(), z.unknown()).describe('Key-value pairs to set on the child document\'s awareness state. Use namespaced keys like "kanban:hovering", "table:editing", "slides:viewing", "outline:editing", "calendar:focused", "gallery:focused", "timeline:focused", "graph:focused", "map:focused", "doc:scroll". Set a key to null to clear it.'),
|
|
39
39
|
},
|
|
40
40
|
async ({ docId, fields }) => {
|
|
41
|
+
server.setActiveToolCall({ name: 'set_doc_awareness', target: docId })
|
|
41
42
|
try {
|
|
42
43
|
const provider = await server.getChildProvider(docId)
|
|
43
44
|
for (const [key, value] of Object.entries(fields)) {
|
|
@@ -58,6 +59,7 @@ export function registerAwarenessTools(mcp: McpServer, server: AbracadabraMCPSer
|
|
|
58
59
|
'Check the "AI Inbox" document for pending instructions from humans. Returns the inbox content and any pending task sub-documents. Create the inbox as a doc called "AI Inbox" under the hub doc if it does not exist yet. Note: channel-based watching via watch_chat is preferred for real-time use.',
|
|
59
60
|
{},
|
|
60
61
|
async () => {
|
|
62
|
+
server.setActiveToolCall({ name: 'poll_inbox' })
|
|
61
63
|
try {
|
|
62
64
|
const treeMap = server.getTreeMap()
|
|
63
65
|
const rootDocId = server.rootDocId
|
|
@@ -124,6 +126,7 @@ export function registerAwarenessTools(mcp: McpServer, server: AbracadabraMCPSer
|
|
|
124
126
|
docId: z.string().optional().describe('If provided, list users connected to this specific document. Otherwise lists users from root awareness.'),
|
|
125
127
|
},
|
|
126
128
|
async ({ docId }) => {
|
|
129
|
+
server.setActiveToolCall({ name: 'list_connected_users', target: docId })
|
|
127
130
|
try {
|
|
128
131
|
let awareness
|
|
129
132
|
if (docId) {
|
package/src/tools/channel.ts
CHANGED
|
@@ -23,7 +23,6 @@ export function registerChannelTools(mcp: McpServer, server: AbracadabraMCPServe
|
|
|
23
23
|
const treeMap = server.getTreeMap()
|
|
24
24
|
const rootDoc = server.rootDocument
|
|
25
25
|
if (!treeMap || !rootDoc) {
|
|
26
|
-
server.setActiveToolCall(null)
|
|
27
26
|
return { content: [{ type: 'text' as const, text: 'Not connected' }], isError: true }
|
|
28
27
|
}
|
|
29
28
|
|
|
@@ -51,13 +50,11 @@ export function registerChannelTools(mcp: McpServer, server: AbracadabraMCPServe
|
|
|
51
50
|
server.clearAiTask(task_id)
|
|
52
51
|
}
|
|
53
52
|
|
|
54
|
-
server.setActiveToolCall(null)
|
|
55
53
|
|
|
56
54
|
return {
|
|
57
55
|
content: [{ type: 'text' as const, text: JSON.stringify({ replyDocId: replyId, parentId: doc_id }) }],
|
|
58
56
|
}
|
|
59
57
|
} catch (error: any) {
|
|
60
|
-
server.setActiveToolCall(null)
|
|
61
58
|
return {
|
|
62
59
|
content: [{ type: 'text' as const, text: `Error: ${error.message}` }],
|
|
63
60
|
isError: true,
|
|
@@ -80,17 +77,30 @@ export function registerChannelTools(mcp: McpServer, server: AbracadabraMCPServe
|
|
|
80
77
|
return { content: [{ type: 'text' as const, text: 'Not connected' }], isError: true }
|
|
81
78
|
}
|
|
82
79
|
|
|
80
|
+
// Normalize literal escape sequences. Some LLM outputs emit the
|
|
81
|
+
// 2-char `\n` / `\t` / `\r` instead of the real control chars, which
|
|
82
|
+
// then render literally in the chat (markdown renderer can't see a
|
|
83
|
+
// newline where there is just "\n"). Convert them back to real chars.
|
|
84
|
+
const normalized = text
|
|
85
|
+
.replace(/\\r\\n/g, '\n')
|
|
86
|
+
.replace(/\\n/g, '\n')
|
|
87
|
+
.replace(/\\t/g, '\t')
|
|
88
|
+
.replace(/\\r/g, '\n')
|
|
89
|
+
|
|
90
|
+
// Order matters: clear status + tool pill FIRST so the dashboard's
|
|
91
|
+
// typing-indicator filter (which hides typing while an activeToolCall
|
|
92
|
+
// exists) doesn't swallow the burst. Then emit the typing frame, then
|
|
93
|
+
// the actual chat:send. The clear also flushes toolHistory + turnId.
|
|
94
|
+
server.setAutoStatus(null)
|
|
95
|
+
server.sendTypingIndicator(channel)
|
|
96
|
+
|
|
83
97
|
rootProvider.sendStateless(JSON.stringify({
|
|
84
98
|
type: 'chat:send',
|
|
85
99
|
channel,
|
|
86
|
-
content:
|
|
100
|
+
content: normalized,
|
|
87
101
|
sender_name: server.agentName,
|
|
88
102
|
}))
|
|
89
103
|
|
|
90
|
-
// Clear thinking/typing status after sending the reply
|
|
91
|
-
server.setAutoStatus(null)
|
|
92
|
-
server.setActiveToolCall(null)
|
|
93
|
-
|
|
94
104
|
return { content: [{ type: 'text' as const, text: `Sent to ${channel}` }] }
|
|
95
105
|
} catch (error: any) {
|
|
96
106
|
return {
|
package/src/tools/content.ts
CHANGED
|
@@ -51,8 +51,6 @@ export function registerContentTools(mcp: McpServer, server: AbracadabraMCPServe
|
|
|
51
51
|
children.sort((a: any, b: any) => ((treeMap.get(a.id)?.order ?? 0) - (treeMap.get(b.id)?.order ?? 0)))
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
-
server.setActiveToolCall(null)
|
|
55
|
-
|
|
56
54
|
const result: Record<string, unknown> = { label, type, meta, markdown, children }
|
|
57
55
|
return {
|
|
58
56
|
content: [{
|
|
@@ -61,7 +59,6 @@ export function registerContentTools(mcp: McpServer, server: AbracadabraMCPServe
|
|
|
61
59
|
}],
|
|
62
60
|
}
|
|
63
61
|
} catch (error: any) {
|
|
64
|
-
server.setActiveToolCall(null)
|
|
65
62
|
return {
|
|
66
63
|
content: [{ type: 'text', text: `Error reading document: ${error.message}` }],
|
|
67
64
|
isError: true,
|
|
@@ -128,13 +125,11 @@ export function registerContentTools(mcp: McpServer, server: AbracadabraMCPServe
|
|
|
128
125
|
server.setFocusedDoc(docId)
|
|
129
126
|
server.setDocCursor(docId, fragment.length)
|
|
130
127
|
|
|
131
|
-
server.setActiveToolCall(null)
|
|
132
128
|
|
|
133
129
|
return {
|
|
134
130
|
content: [{ type: 'text', text: `Document ${docId} updated (${writeMode} mode)` }],
|
|
135
131
|
}
|
|
136
132
|
} catch (error: any) {
|
|
137
|
-
server.setActiveToolCall(null)
|
|
138
133
|
return {
|
|
139
134
|
content: [{ type: 'text', text: `Error writing document: ${error.message}` }],
|
|
140
135
|
isError: true,
|
package/src/tools/files.ts
CHANGED
|
@@ -15,6 +15,8 @@ export function registerFileTools(mcp: McpServer, server: AbracadabraMCPServer)
|
|
|
15
15
|
docId: z.string().describe('Document ID.'),
|
|
16
16
|
},
|
|
17
17
|
async ({ docId }) => {
|
|
18
|
+
server.setAutoStatus('reading', docId)
|
|
19
|
+
server.setActiveToolCall({ name: 'list_uploads', target: docId })
|
|
18
20
|
try {
|
|
19
21
|
const uploads = await server.client.listUploads(docId)
|
|
20
22
|
return {
|
|
@@ -41,6 +43,8 @@ export function registerFileTools(mcp: McpServer, server: AbracadabraMCPServer)
|
|
|
41
43
|
filename: z.string().optional().describe('Override filename (defaults to basename of filePath).'),
|
|
42
44
|
},
|
|
43
45
|
async ({ docId, filePath, filename }) => {
|
|
46
|
+
server.setAutoStatus('uploading', docId)
|
|
47
|
+
server.setActiveToolCall({ name: 'upload_file', target: path.basename(filePath) })
|
|
44
48
|
try {
|
|
45
49
|
const resolvedPath = path.resolve(filePath)
|
|
46
50
|
const data = fs.readFileSync(resolvedPath)
|
|
@@ -71,6 +75,8 @@ export function registerFileTools(mcp: McpServer, server: AbracadabraMCPServer)
|
|
|
71
75
|
saveTo: z.string().describe('Absolute local file path to save the download.'),
|
|
72
76
|
},
|
|
73
77
|
async ({ docId, uploadId, saveTo }) => {
|
|
78
|
+
server.setAutoStatus('reading', docId)
|
|
79
|
+
server.setActiveToolCall({ name: 'download_file', target: path.basename(saveTo) })
|
|
74
80
|
try {
|
|
75
81
|
const blob = await server.client.getUpload(docId, uploadId)
|
|
76
82
|
const buffer = Buffer.from(await blob.arrayBuffer())
|
|
@@ -96,6 +102,8 @@ export function registerFileTools(mcp: McpServer, server: AbracadabraMCPServer)
|
|
|
96
102
|
uploadId: z.string().describe('Upload ID to delete.'),
|
|
97
103
|
},
|
|
98
104
|
async ({ docId, uploadId }) => {
|
|
105
|
+
server.setAutoStatus('writing', docId)
|
|
106
|
+
server.setActiveToolCall({ name: 'delete_file', target: uploadId })
|
|
99
107
|
try {
|
|
100
108
|
await server.client.deleteUpload(docId, uploadId)
|
|
101
109
|
return { content: [{ type: 'text', text: `Deleted upload ${uploadId}` }] }
|
package/src/tools/meta.ts
CHANGED
|
@@ -17,17 +17,14 @@ export function registerMetaTools(mcp: McpServer, server: AbracadabraMCPServer)
|
|
|
17
17
|
server.setActiveToolCall({ name: 'get_metadata', target: docId })
|
|
18
18
|
const treeMap = server.getTreeMap()
|
|
19
19
|
if (!treeMap) {
|
|
20
|
-
server.setActiveToolCall(null)
|
|
21
20
|
return { content: [{ type: 'text', text: 'Not connected' }] }
|
|
22
21
|
}
|
|
23
22
|
|
|
24
23
|
const entry = treeMap.get(docId)
|
|
25
24
|
if (!entry) {
|
|
26
|
-
server.setActiveToolCall(null)
|
|
27
25
|
return { content: [{ type: 'text', text: `Document ${docId} not found` }] }
|
|
28
26
|
}
|
|
29
27
|
|
|
30
|
-
server.setActiveToolCall(null)
|
|
31
28
|
return {
|
|
32
29
|
content: [{
|
|
33
30
|
type: 'text',
|
|
@@ -47,20 +44,18 @@ export function registerMetaTools(mcp: McpServer, server: AbracadabraMCPServer)
|
|
|
47
44
|
'Update metadata fields on a document. Merges the provided fields into existing metadata.',
|
|
48
45
|
{
|
|
49
46
|
docId: z.string().describe('Document ID.'),
|
|
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
|
|
47
|
+
meta: z.record(z.string(), 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, timeStart/timeEnd, 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, coverDocId, dateTaken. Geo/Map (children): geoType ("marker"|"line"|"measure"), geoLat, geoLng, geoDescription. Spatial 3D (children, plugin: spatial): spShape ("box"|"sphere"|"cylinder"|"cone"|"plane"|"torus"|"glb"), spX/spY/spZ, spRX/spRY/spRZ (deg), spSX/spSY/spSZ (scale), spOpacity (0-100), spModelUploadId, spModelDocId — spatial uses the universal `color` key, NOT spColor. Dashboard (children): deskX, deskY, deskZ, deskMode ("icon"|"widget-sm"|"widget-lg"). Mindmap-layout (children): mmX, mmY. Graph-layout (children): graphX, graphY, graphPinned. Slides (children): slidesTransition ("none"|"fade"|"slide"). Coder (children, plugin: coder): fileType ("vue"|"ts"|"js"|"css"|"json"|"folder"), entry (bool). Cell formatting (sheets cells): bold, italic, textColor, bgColor, align ("left"|"center"|"right"), formula. Renderer config (on the PAGE doc itself, not children): kanbanColumnWidth ("narrow"|"default"|"wide"), galleryColumns (1-6), galleryAspect ("square"|"4:3"|"3:2"|"16:9"|"free"), galleryCardStyle ("default"|"compact"|"detailed"), galleryShowLabels, gallerySortBy ("manual"|"date"|"name"|"rating"), calendarView ("month"|"week"|"day"), calendarWeekStart ("sun"|"mon"), calendarShowWeekNumbers, tableMode ("hierarchy"|"flat"), tableSortKey, tableSortDir ("asc"|"desc"), timelineZoom ("week"|"month"|"quarter"), timelinePixelsPerDay, timelineCenterDate (ISO date), checklistFilter ("all"|"active"|"completed"), checklistSort ("manual"|"priority"|"due"), mapShowLabels, graphSpacing ("compact"|"default"|"spacious"), graphShowLabels, graphEdgeThickness ("thin"|"normal"|"thick"), showRefEdges, mmSpacing, spatialGridVisible, slidesTheme ("dark"|"light"), chartType ("bar"|"stacked bar"|"line"|"donut"|"treemap"), chartMetric ("value"|"type"|"tag"|"status"|"priority"|"activity"|"completion"), chartColorScheme ("default"|"warm"|"cool"|"mono"), chartLimit (3-30), chartShowLegend, chartShowValues, sheetsDefaultColWidth (40-500), sheetsDefaultRowHeight (20-100), sheetsShowGridlines, sheetsFreezeRows, sheetsFreezeCols, mediaRepeat ("off"|"all"|"one"), mediaShuffle. Set a key to null to clear it.'),
|
|
51
48
|
},
|
|
52
49
|
async ({ docId, meta }) => {
|
|
53
50
|
server.setAutoStatus('writing', docId)
|
|
54
51
|
server.setActiveToolCall({ name: 'update_metadata', target: docId })
|
|
55
52
|
const treeMap = server.getTreeMap()
|
|
56
53
|
if (!treeMap) {
|
|
57
|
-
server.setActiveToolCall(null)
|
|
58
54
|
return { content: [{ type: 'text', text: 'Not connected' }] }
|
|
59
55
|
}
|
|
60
56
|
|
|
61
57
|
const entry = treeMap.get(docId)
|
|
62
58
|
if (!entry) {
|
|
63
|
-
server.setActiveToolCall(null)
|
|
64
59
|
return { content: [{ type: 'text', text: `Document ${docId} not found` }] }
|
|
65
60
|
}
|
|
66
61
|
|
|
@@ -70,7 +65,6 @@ export function registerMetaTools(mcp: McpServer, server: AbracadabraMCPServer)
|
|
|
70
65
|
updatedAt: Date.now(),
|
|
71
66
|
})
|
|
72
67
|
|
|
73
|
-
server.setActiveToolCall(null)
|
|
74
68
|
return { content: [{ type: 'text', text: `Metadata updated for ${docId}` }] }
|
|
75
69
|
}
|
|
76
70
|
)
|
package/src/tools/svg.ts
CHANGED
|
@@ -43,7 +43,6 @@ export function registerSvgTools(mcp: McpServer, server: AbracadabraMCPServer) {
|
|
|
43
43
|
|
|
44
44
|
const cleanSvg = sanitizeSvg(svg)
|
|
45
45
|
if (!cleanSvg) {
|
|
46
|
-
server.setActiveToolCall(null)
|
|
47
46
|
return {
|
|
48
47
|
content: [{ type: 'text', text: 'Error: SVG markup was empty or entirely stripped by sanitizer.' }],
|
|
49
48
|
isError: true,
|
|
@@ -66,13 +65,11 @@ export function registerSvgTools(mcp: McpServer, server: AbracadabraMCPServer) {
|
|
|
66
65
|
})
|
|
67
66
|
|
|
68
67
|
server.setFocusedDoc(docId)
|
|
69
|
-
server.setActiveToolCall(null)
|
|
70
68
|
|
|
71
69
|
return {
|
|
72
70
|
content: [{ type: 'text', text: `SVG inserted into document ${docId}${title ? ` ("${title}")` : ''}` }],
|
|
73
71
|
}
|
|
74
72
|
} catch (error: any) {
|
|
75
|
-
server.setActiveToolCall(null)
|
|
76
73
|
return {
|
|
77
74
|
content: [{ type: 'text', text: `Error writing SVG: ${error.message}` }],
|
|
78
75
|
isError: true,
|
package/src/tools/tree.ts
CHANGED
|
@@ -6,6 +6,7 @@ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
|
|
|
6
6
|
import { z } from 'zod'
|
|
7
7
|
import type { AbracadabraMCPServer } from '../server.ts'
|
|
8
8
|
import type { TreeEntry, PageMeta } from '../converters/types.ts'
|
|
9
|
+
import { PAGE_TYPES, TYPE_ALIASES, resolvePageType } from '../converters/page-types.ts'
|
|
9
10
|
|
|
10
11
|
/**
|
|
11
12
|
* Normalize a document ID so the hub/root doc ID is treated as the tree root (null).
|
|
@@ -89,7 +90,6 @@ export function registerTreeTools(mcp: McpServer, server: AbracadabraMCPServer)
|
|
|
89
90
|
server.setActiveToolCall({ name: 'list_documents' })
|
|
90
91
|
const treeMap = server.getTreeMap()
|
|
91
92
|
if (!treeMap) {
|
|
92
|
-
server.setActiveToolCall(null)
|
|
93
93
|
return { content: [{ type: 'text', text: 'Not connected' }] }
|
|
94
94
|
}
|
|
95
95
|
|
|
@@ -97,7 +97,6 @@ export function registerTreeTools(mcp: McpServer, server: AbracadabraMCPServer)
|
|
|
97
97
|
const entries = readEntries(treeMap)
|
|
98
98
|
const children = childrenOf(entries, targetId)
|
|
99
99
|
|
|
100
|
-
server.setActiveToolCall(null)
|
|
101
100
|
return {
|
|
102
101
|
content: [{
|
|
103
102
|
type: 'text',
|
|
@@ -119,7 +118,6 @@ export function registerTreeTools(mcp: McpServer, server: AbracadabraMCPServer)
|
|
|
119
118
|
server.setActiveToolCall({ name: 'get_document_tree' })
|
|
120
119
|
const treeMap = server.getTreeMap()
|
|
121
120
|
if (!treeMap) {
|
|
122
|
-
server.setActiveToolCall(null)
|
|
123
121
|
return { content: [{ type: 'text', text: 'Not connected' }] }
|
|
124
122
|
}
|
|
125
123
|
|
|
@@ -128,7 +126,6 @@ export function registerTreeTools(mcp: McpServer, server: AbracadabraMCPServer)
|
|
|
128
126
|
const entries = readEntries(treeMap)
|
|
129
127
|
const tree = buildTree(entries, targetId, maxDepth)
|
|
130
128
|
|
|
131
|
-
server.setActiveToolCall(null)
|
|
132
129
|
return {
|
|
133
130
|
content: [{
|
|
134
131
|
type: 'text',
|
|
@@ -150,7 +147,6 @@ export function registerTreeTools(mcp: McpServer, server: AbracadabraMCPServer)
|
|
|
150
147
|
server.setActiveToolCall({ name: 'find_document', target: query })
|
|
151
148
|
const treeMap = server.getTreeMap()
|
|
152
149
|
if (!treeMap) {
|
|
153
|
-
server.setActiveToolCall(null)
|
|
154
150
|
return { content: [{ type: 'text', text: 'Not connected' }] }
|
|
155
151
|
}
|
|
156
152
|
|
|
@@ -188,7 +184,6 @@ export function registerTreeTools(mcp: McpServer, server: AbracadabraMCPServer)
|
|
|
188
184
|
}
|
|
189
185
|
})
|
|
190
186
|
|
|
191
|
-
server.setActiveToolCall(null)
|
|
192
187
|
if (results.length === 0) {
|
|
193
188
|
return {
|
|
194
189
|
content: [{ type: 'text', text: `No documents found matching "${query}". Try get_document_tree to see the full hierarchy.` }],
|
|
@@ -207,8 +202,8 @@ export function registerTreeTools(mcp: McpServer, server: AbracadabraMCPServer)
|
|
|
207
202
|
{
|
|
208
203
|
parentId: z.string().optional().describe('Parent document ID. Omit for top-level pages. Use a document ID for nested/child pages.'),
|
|
209
204
|
label: z.string().describe('Display name / title for the document.'),
|
|
210
|
-
type: z.string().optional().describe('Page type — sets how this document renders. "doc" (rich text), "kanban" (columns → cards), "table" (columns → rows
|
|
211
|
-
meta: z.record(z.unknown()).optional().describe('Initial metadata (PageMeta fields: color as hex string, icon as Lucide kebab-case name like "star"/"code-2"/"users" — never emoji, dateStart, dateEnd, priority 0-4, tags array, etc). Omit icon entirely to use page type default.'),
|
|
205
|
+
type: z.string().optional().describe('Page type — sets how this document renders. Core types (always available): "doc" (rich text), "kanban" (columns → cards), "table" (columns → rows, positional), "calendar" (events with datetimeStart/End), "timeline" (epics → tasks with dateStart/End + taskProgress), "checklist" (tasks with checked/priority, unlimited nesting), "outline" (nested items, unlimited depth), "gallery" (visual grid with covers/ratings), "map" (markers/lines with geoLat/geoLng), "graph" (force-directed knowledge graph), "dashboard" (positioned widgets with deskX/deskY/deskMode), "slides" (slides → sub-slides with transitions), "chart" (bar/stacked bar/line/donut/treemap from data points or aggregation), "sheets" (spreadsheet with formulas and formatting), "overview" (space home — activity and stats), "call" (video call room, no children). Plugin types (require plugin enabled on the server): "spatial" (3D scene with spShape/spX/spY/spZ + universal color, plugin: spatial), "media" (audio/video player with playlists, plugin: media), "coder" (collaborative multi-file coding env with fileType/entry, plugin: coder). Alias: "desktop" → "dashboard". Omit to inherit parent view. Only set on the parent page, NEVER on child items.'),
|
|
206
|
+
meta: z.record(z.string(), z.unknown()).optional().describe('Initial metadata (PageMeta fields: color as hex string, icon as Lucide kebab-case name like "star"/"code-2"/"users" — never emoji, dateStart, dateEnd, priority 0-4, tags array, etc). Omit icon entirely to use page type default.'),
|
|
212
207
|
},
|
|
213
208
|
async ({ parentId, label, type, meta }) => {
|
|
214
209
|
server.setAutoStatus('creating')
|
|
@@ -217,7 +212,6 @@ export function registerTreeTools(mcp: McpServer, server: AbracadabraMCPServer)
|
|
|
217
212
|
const treeMap = server.getTreeMap()
|
|
218
213
|
const rootDoc = server.rootDocument
|
|
219
214
|
if (!treeMap || !rootDoc) {
|
|
220
|
-
server.setActiveToolCall(null)
|
|
221
215
|
return { content: [{ type: 'text', text: 'Not connected' }] }
|
|
222
216
|
}
|
|
223
217
|
|
|
@@ -237,7 +231,6 @@ export function registerTreeTools(mcp: McpServer, server: AbracadabraMCPServer)
|
|
|
237
231
|
})
|
|
238
232
|
|
|
239
233
|
server.setFocusedDoc(id)
|
|
240
|
-
server.setActiveToolCall(null)
|
|
241
234
|
|
|
242
235
|
return {
|
|
243
236
|
content: [{
|
|
@@ -260,19 +253,16 @@ export function registerTreeTools(mcp: McpServer, server: AbracadabraMCPServer)
|
|
|
260
253
|
server.setActiveToolCall({ name: 'rename_document', target: id })
|
|
261
254
|
const treeMap = server.getTreeMap()
|
|
262
255
|
if (!treeMap) {
|
|
263
|
-
server.setActiveToolCall(null)
|
|
264
256
|
return { content: [{ type: 'text', text: 'Not connected' }] }
|
|
265
257
|
}
|
|
266
258
|
|
|
267
259
|
const raw = treeMap.get(id)
|
|
268
260
|
if (!raw) {
|
|
269
|
-
server.setActiveToolCall(null)
|
|
270
261
|
return { content: [{ type: 'text', text: `Document ${id} not found` }] }
|
|
271
262
|
}
|
|
272
263
|
|
|
273
264
|
const entry = toPlain(raw)
|
|
274
265
|
treeMap.set(id, { ...entry, label, updatedAt: Date.now() })
|
|
275
|
-
server.setActiveToolCall(null)
|
|
276
266
|
return { content: [{ type: 'text', text: `Renamed to "${label}"` }] }
|
|
277
267
|
}
|
|
278
268
|
)
|
|
@@ -290,13 +280,11 @@ export function registerTreeTools(mcp: McpServer, server: AbracadabraMCPServer)
|
|
|
290
280
|
server.setActiveToolCall({ name: 'move_document', target: id })
|
|
291
281
|
const treeMap = server.getTreeMap()
|
|
292
282
|
if (!treeMap) {
|
|
293
|
-
server.setActiveToolCall(null)
|
|
294
283
|
return { content: [{ type: 'text', text: 'Not connected' }] }
|
|
295
284
|
}
|
|
296
285
|
|
|
297
286
|
const raw = treeMap.get(id)
|
|
298
287
|
if (!raw) {
|
|
299
|
-
server.setActiveToolCall(null)
|
|
300
288
|
return { content: [{ type: 'text', text: `Document ${id} not found` }] }
|
|
301
289
|
}
|
|
302
290
|
|
|
@@ -307,7 +295,6 @@ export function registerTreeTools(mcp: McpServer, server: AbracadabraMCPServer)
|
|
|
307
295
|
order: order ?? Date.now(),
|
|
308
296
|
updatedAt: Date.now(),
|
|
309
297
|
})
|
|
310
|
-
server.setActiveToolCall(null)
|
|
311
298
|
return { content: [{ type: 'text', text: `Moved ${id} to parent ${newParentId}` }] }
|
|
312
299
|
}
|
|
313
300
|
)
|
|
@@ -325,7 +312,6 @@ export function registerTreeTools(mcp: McpServer, server: AbracadabraMCPServer)
|
|
|
325
312
|
const trashMap = server.getTrashMap()
|
|
326
313
|
const rootDoc = server.rootDocument
|
|
327
314
|
if (!treeMap || !trashMap || !rootDoc) {
|
|
328
|
-
server.setActiveToolCall(null)
|
|
329
315
|
return { content: [{ type: 'text', text: 'Not connected' }] }
|
|
330
316
|
}
|
|
331
317
|
|
|
@@ -350,7 +336,6 @@ export function registerTreeTools(mcp: McpServer, server: AbracadabraMCPServer)
|
|
|
350
336
|
}
|
|
351
337
|
})
|
|
352
338
|
|
|
353
|
-
server.setActiveToolCall(null)
|
|
354
339
|
return { content: [{ type: 'text', text: `Deleted ${toDelete.length} document(s)` }] }
|
|
355
340
|
}
|
|
356
341
|
)
|
|
@@ -360,26 +345,23 @@ export function registerTreeTools(mcp: McpServer, server: AbracadabraMCPServer)
|
|
|
360
345
|
'Change the page type view of a document (data is preserved).',
|
|
361
346
|
{
|
|
362
347
|
id: z.string().describe('Document ID.'),
|
|
363
|
-
type: z.string().describe('New page type: "doc", "kanban", "table", "calendar", "timeline", "checklist", "outline", "gallery", "map", "graph", "dashboard", "
|
|
348
|
+
type: z.string().describe('New page type. Core: "doc", "kanban", "table", "calendar", "timeline", "checklist", "outline", "gallery", "map", "graph", "dashboard", "slides", "chart", "sheets", "overview", "call". Plugin (require plugin enabled): "spatial", "media", "coder". Switching preserves the tree — children, labels, and meta are all retained; only the view changes.'),
|
|
364
349
|
},
|
|
365
350
|
async ({ id, type }) => {
|
|
366
351
|
server.setAutoStatus('writing')
|
|
367
352
|
server.setActiveToolCall({ name: 'change_document_type', target: id })
|
|
368
353
|
const treeMap = server.getTreeMap()
|
|
369
354
|
if (!treeMap) {
|
|
370
|
-
server.setActiveToolCall(null)
|
|
371
355
|
return { content: [{ type: 'text', text: 'Not connected' }] }
|
|
372
356
|
}
|
|
373
357
|
|
|
374
358
|
const raw = treeMap.get(id)
|
|
375
359
|
if (!raw) {
|
|
376
|
-
server.setActiveToolCall(null)
|
|
377
360
|
return { content: [{ type: 'text', text: `Document ${id} not found` }] }
|
|
378
361
|
}
|
|
379
362
|
|
|
380
363
|
const entry = toPlain(raw)
|
|
381
364
|
treeMap.set(id, { ...entry, type, updatedAt: Date.now() })
|
|
382
|
-
server.setActiveToolCall(null)
|
|
383
365
|
return { content: [{ type: 'text', text: `Changed type to "${type}"` }] }
|
|
384
366
|
}
|
|
385
367
|
)
|
|
@@ -442,4 +424,28 @@ export function registerTreeTools(mcp: McpServer, server: AbracadabraMCPServer)
|
|
|
442
424
|
}
|
|
443
425
|
}
|
|
444
426
|
)
|
|
427
|
+
|
|
428
|
+
mcp.tool(
|
|
429
|
+
'list_page_types',
|
|
430
|
+
'Enumerate all known Abracadabra page types with their metadata schemas. Returns an array of { key, label, icon, description, core, plugin, supportsChildren, childLabel, grandchildLabel, defaultDepth, metaSchema, defaultMetaFields }. `metaSchema` describes fields that apply to DESCENDANTS (children, grandchildren, ...) of a page of this type. `defaultMetaFields` are view-config fields on the page doc itself. Plugin types (core: false) require the named plugin to be enabled on the server. Use this to discover what meta keys a given renderer supports instead of guessing.',
|
|
431
|
+
{
|
|
432
|
+
key: z.string().optional().describe('Filter to a single type by key (e.g. "kanban", "calendar"). Aliases are resolved (e.g. "desktop" → "dashboard"). Omit to list all types.'),
|
|
433
|
+
},
|
|
434
|
+
async ({ key }) => {
|
|
435
|
+
if (key) {
|
|
436
|
+
const resolved = resolvePageType(key)
|
|
437
|
+
if (!resolved) {
|
|
438
|
+
return { content: [{ type: 'text', text: `Unknown page type "${key}". Call list_page_types with no args to see all types.` }] }
|
|
439
|
+
}
|
|
440
|
+
return { content: [{ type: 'text', text: JSON.stringify(resolved, null, 2) }] }
|
|
441
|
+
}
|
|
442
|
+
const all = Object.values(PAGE_TYPES)
|
|
443
|
+
return {
|
|
444
|
+
content: [{
|
|
445
|
+
type: 'text',
|
|
446
|
+
text: JSON.stringify({ types: all, aliases: TYPE_ALIASES }, null, 2),
|
|
447
|
+
}],
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
)
|
|
445
451
|
}
|