@abraca/mcp 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/abracadabra-mcp.cjs +21007 -0
- package/dist/abracadabra-mcp.cjs.map +1 -0
- package/dist/abracadabra-mcp.esm.js +20999 -0
- package/dist/abracadabra-mcp.esm.js.map +1 -0
- package/dist/index.d.ts +8785 -0
- package/package.json +39 -0
- package/src/converters/markdownToYjs.ts +852 -0
- package/src/converters/types.ts +75 -0
- package/src/converters/yjsToMarkdown.ts +334 -0
- package/src/index.ts +103 -0
- package/src/resources/agent-guide.ts +324 -0
- package/src/resources/server-info.ts +47 -0
- package/src/resources/tree-resource.ts +71 -0
- package/src/server.ts +431 -0
- package/src/tools/awareness.ts +156 -0
- package/src/tools/channel.ts +92 -0
- package/src/tools/content.ts +133 -0
- package/src/tools/files.ts +110 -0
- package/src/tools/meta.ts +59 -0
- package/src/tools/tree.ts +296 -0
- package/src/utils.ts +37 -0
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Static resource: AI agent guide for working with Abracadabra documents.
|
|
3
|
+
*/
|
|
4
|
+
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
|
|
5
|
+
|
|
6
|
+
const AGENT_GUIDE = `# Abracadabra AI Agent Guide
|
|
7
|
+
|
|
8
|
+
## Core Principle: "Everything is a Document"
|
|
9
|
+
|
|
10
|
+
Every piece of visible data in Abracadabra — a calendar event, a kanban card, a table cell, a map marker — is a **document** in the tree. The document's **label** (name) IS the displayed value. Always.
|
|
11
|
+
|
|
12
|
+
- A column's name IS the column header
|
|
13
|
+
- A card's name IS the card title
|
|
14
|
+
- A cell's name IS the cell content
|
|
15
|
+
- An event's name IS the event title
|
|
16
|
+
|
|
17
|
+
**Page types are views over the same document hierarchy**, not isolated data stores. Switching a page from Kanban to Table to Outline works without migration — the tree doesn't change, only the rendering.
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## Getting Oriented in a Space
|
|
22
|
+
|
|
23
|
+
Before creating any documents, always call \`list_spaces\` to understand the current workspace. Each space has a \`doc_id\` — this is the **hub document** (the space root). All user-created documents must be direct or indirect children of this hub doc.
|
|
24
|
+
|
|
25
|
+
\`\`\`
|
|
26
|
+
list_spaces → pick the active space → note its doc_id (= hub doc ID)
|
|
27
|
+
create_document(parentId: hubDocId, ...) ← top-level pages go here
|
|
28
|
+
\`\`\`
|
|
29
|
+
|
|
30
|
+
The hub doc itself is a system document — do not rename, move, or write content to it. It is just a container.
|
|
31
|
+
|
|
32
|
+
If you are adding content to an existing space, call \`get_document_tree(rootId: hubDocId)\` first to understand what already exists before creating anything.
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## Page Types Reference
|
|
37
|
+
|
|
38
|
+
| Type | Children Are | Grandchildren Are | Depth Limit |
|
|
39
|
+
|------|-------------|-------------------|-------------|
|
|
40
|
+
| **doc** | Sub-documents | Sub-sub-documents | Unlimited |
|
|
41
|
+
| **kanban** | Columns | Cards | 2 |
|
|
42
|
+
| **table** | Columns | Cells (positional rows) | 2 |
|
|
43
|
+
| **calendar** | Events | — | 1 |
|
|
44
|
+
| **timeline** | Epics | Tasks | 2 |
|
|
45
|
+
| **outline** | Items | Sub-items | Unlimited |
|
|
46
|
+
| **mindmap** | Central nodes | Branches | Unlimited |
|
|
47
|
+
| **graph** | Nodes | — | 1 |
|
|
48
|
+
| **gallery** | Items | — | 1 |
|
|
49
|
+
| **slides** | Slides | — | 1 |
|
|
50
|
+
| **whiteboard** | Objects | — | 1 |
|
|
51
|
+
| **map** | Markers/Lines | Points (for lines) | 2 |
|
|
52
|
+
| **desktop** | Items | — | 1 |
|
|
53
|
+
| **call** | — | — | 0 |
|
|
54
|
+
| **game** | — | — | 0 |
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## Document Tree Operations
|
|
59
|
+
|
|
60
|
+
### Creating Well-Structured Hierarchies
|
|
61
|
+
|
|
62
|
+
**Kanban board:**
|
|
63
|
+
1. Create the kanban document: \`create_document(parentId, "Project Board", "kanban")\`
|
|
64
|
+
2. Create columns: \`create_document(boardId, "To Do")\`, \`create_document(boardId, "In Progress")\`, \`create_document(boardId, "Done")\`
|
|
65
|
+
3. Create cards under columns: \`create_document(toDoColumnId, "Fix bug #123")\`
|
|
66
|
+
|
|
67
|
+
**Calendar with events:**
|
|
68
|
+
1. Create calendar: \`create_document(parentId, "Team Calendar", "calendar")\`
|
|
69
|
+
2. Create events: \`create_document(calendarId, "Sprint Planning")\`
|
|
70
|
+
3. Set event times: \`update_metadata(eventId, { datetimeStart: "2026-03-20T10:00:00Z", datetimeEnd: "2026-03-20T11:00:00Z" })\`
|
|
71
|
+
|
|
72
|
+
**Timeline with tasks:**
|
|
73
|
+
1. Create timeline: \`create_document(parentId, "Q1 Roadmap", "timeline")\`
|
|
74
|
+
2. Create epics: \`create_document(timelineId, "Auth Rewrite")\`
|
|
75
|
+
3. Create tasks: \`create_document(epicId, "Implement JWT refresh")\`
|
|
76
|
+
4. Set dates/progress: \`update_metadata(taskId, { dateStart: "2026-03-01", dateEnd: "2026-03-15", taskProgress: 50 })\`
|
|
77
|
+
|
|
78
|
+
**Table with data:**
|
|
79
|
+
1. Create table: \`create_document(parentId, "Contacts", "table")\`
|
|
80
|
+
2. Create columns: \`create_document(tableId, "Name")\`, \`create_document(tableId, "Email")\`, \`create_document(tableId, "Phone")\`
|
|
81
|
+
3. Create cells (children of columns): \`create_document(nameColId, "Alice")\`, \`create_document(emailColId, "alice@example.com")\`
|
|
82
|
+
4. Rows are positional — the Nth child of each column forms row N
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
## Metadata Reference
|
|
87
|
+
|
|
88
|
+
### Universal Keys (use these — never invent prefixed variants)
|
|
89
|
+
|
|
90
|
+
| Key | Type | Meaning |
|
|
91
|
+
|-----|------|---------|
|
|
92
|
+
| \`color\` | string | Hex/CSS color (e.g. "#6366f1") |
|
|
93
|
+
| \`icon\` | string | Lucide icon name in kebab-case (e.g. "star", "code-2", "users", "calendar-days", "book-open"). **Never emoji.** Omit to use the page type's default icon. |
|
|
94
|
+
| \`datetimeStart\` / \`datetimeEnd\` | string | ISO datetime with time |
|
|
95
|
+
| \`allDay\` | boolean | Whether datetime is all-day |
|
|
96
|
+
| \`dateStart\` / \`dateEnd\` | string | ISO date-only (e.g. "2026-03-20") |
|
|
97
|
+
| \`timeStart\` / \`timeEnd\` | string | HH:MM time |
|
|
98
|
+
| \`tags\` | string[] | Tag labels |
|
|
99
|
+
| \`checked\` | boolean | Done/checked state |
|
|
100
|
+
| \`priority\` | number | 0=none, 1=low, 2=medium, 3=high, 4=urgent |
|
|
101
|
+
| \`status\` | string | Status string |
|
|
102
|
+
| \`rating\` | number | 0–5 stars |
|
|
103
|
+
| \`url\` | string | URL |
|
|
104
|
+
| \`email\` | string | Email address |
|
|
105
|
+
| \`phone\` | string | Phone number |
|
|
106
|
+
| \`number\` | number | Generic numeric value |
|
|
107
|
+
| \`unit\` | string | Unit label for \`number\` |
|
|
108
|
+
| \`subtitle\` | string | Secondary text |
|
|
109
|
+
| \`note\` | string | Free-text note |
|
|
110
|
+
| \`taskProgress\` | number | 0–100 progress (timeline tasks) |
|
|
111
|
+
|
|
112
|
+
### Type-Specific Meta Schemas
|
|
113
|
+
|
|
114
|
+
**Calendar events** require: \`datetimeStart\`, \`datetimeEnd\`, optionally \`allDay\`, \`color\`
|
|
115
|
+
|
|
116
|
+
**Timeline tasks** require: \`dateStart\`, \`dateEnd\`, optionally \`taskProgress\` (0–100), \`color\`
|
|
117
|
+
|
|
118
|
+
**Map markers** require: \`geoLat\`, \`geoLng\`, \`geoType\` ("marker"|"line"|"measure"), optionally \`icon\`, \`color\`
|
|
119
|
+
|
|
120
|
+
---
|
|
121
|
+
|
|
122
|
+
## Content Structure
|
|
123
|
+
|
|
124
|
+
Documents use a TipTap editor schema with these top-level elements:
|
|
125
|
+
- **documentHeader** — The document title (H1)
|
|
126
|
+
- **documentMeta** — Metadata display block (skip when reading/writing)
|
|
127
|
+
- **Body blocks** — paragraphs, headings (H2-H6), lists, code blocks, tables, images, and custom components
|
|
128
|
+
|
|
129
|
+
### Supported Markdown Elements
|
|
130
|
+
- Headings (# through ######)
|
|
131
|
+
- Paragraphs with **bold**, *italic*, ~~strikethrough~~, \`code\`, [links](url)
|
|
132
|
+
- Bullet lists, ordered lists, task lists
|
|
133
|
+
- Code blocks with language
|
|
134
|
+
- Blockquotes
|
|
135
|
+
- Tables (pipe syntax)
|
|
136
|
+
- Horizontal rules
|
|
137
|
+
- Images
|
|
138
|
+
- MDC components: callout, collapsible, steps, card, accordion, tabs, code-group, etc.
|
|
139
|
+
|
|
140
|
+
### Writing Content with Frontmatter
|
|
141
|
+
|
|
142
|
+
You can set document title and metadata via YAML frontmatter:
|
|
143
|
+
|
|
144
|
+
\`\`\`markdown
|
|
145
|
+
---
|
|
146
|
+
title: My Document
|
|
147
|
+
tags: [important, review]
|
|
148
|
+
color: "#6366f1"
|
|
149
|
+
priority: high
|
|
150
|
+
---
|
|
151
|
+
|
|
152
|
+
Content goes here...
|
|
153
|
+
\`\`\`
|
|
154
|
+
|
|
155
|
+
---
|
|
156
|
+
|
|
157
|
+
## Awareness / Presence
|
|
158
|
+
|
|
159
|
+
When you connect, you appear in the awareness system alongside human users. Presence runs at two levels:
|
|
160
|
+
- **Root awareness**: your name, color, and which document you're currently viewing (\`docId\`)
|
|
161
|
+
- **Child doc awareness**: per-document presence including TipTap cursor position and renderer-specific state
|
|
162
|
+
|
|
163
|
+
### What happens automatically
|
|
164
|
+
|
|
165
|
+
You do **not** need to call \`set_presence\` after reading or writing content — it's done for you:
|
|
166
|
+
|
|
167
|
+
| Action | Automatic effect |
|
|
168
|
+
|---|---|
|
|
169
|
+
| \`read_document\` | Root \`docId\` updated; TipTap cursor placed at start of document |
|
|
170
|
+
| \`write_document\` | Root \`docId\` updated; TipTap cursor placed at end of written content |
|
|
171
|
+
|
|
172
|
+
The TipTap cursor (\`anchor\`/\`head\` as \`Y.RelativePosition\`) makes your colored caret with name label appear inline in the document editor for human collaborators.
|
|
173
|
+
|
|
174
|
+
### set_presence — manual overrides only
|
|
175
|
+
|
|
176
|
+
Use \`set_presence\` when you want to:
|
|
177
|
+
- Override \`status\` to communicate intent ("thinking", "reviewing", "writing")
|
|
178
|
+
- Explicitly update \`docId\` when browsing the document tree without reading content
|
|
179
|
+
|
|
180
|
+
### set_doc_awareness — renderer-specific item presence
|
|
181
|
+
|
|
182
|
+
For structured renderers (kanban, table, calendar, etc.), use \`set_doc_awareness(docId, fields)\` to broadcast which item you are interacting with. This is visible as highlighted cards, cells, events, etc. in the dashboard.
|
|
183
|
+
|
|
184
|
+
Always clear fields when done by setting them to \`null\`.
|
|
185
|
+
|
|
186
|
+
| Renderer | Key | Value | When to set |
|
|
187
|
+
|---|---|---|---|
|
|
188
|
+
| **Kanban** | \`kanban:hovering\` | cardId | Inspecting or about to edit a card |
|
|
189
|
+
| **Kanban** | \`kanban:dragging\` | \`{ cardId, toColumnId }\` | Moving a card between columns |
|
|
190
|
+
| **Table** | \`table:editing\` | \`{ rowIdx, fieldId }\` | Editing a cell |
|
|
191
|
+
| **Calendar** | \`calendar:focused\` | eventId | Interacting with an event |
|
|
192
|
+
| **Calendar** | \`calendar:viewing\` | "YYYY-MM" | The month/week you're looking at |
|
|
193
|
+
| **Outline** | \`outline:editing\` | nodeId | Editing an outline node |
|
|
194
|
+
| **Outline** | \`outline:selected\` | nodeId | Node selected/hovered |
|
|
195
|
+
| **Slides** | \`slides:viewing\` | slideId | The slide you're currently on |
|
|
196
|
+
| **Gallery** | \`gallery:focused\` | itemId | Item hovered/selected |
|
|
197
|
+
| **Timeline** | \`timeline:focused\` | taskId | Task selected |
|
|
198
|
+
| **Mindmap** | \`mindmap:focused\` | nodeId | Node selected/edited |
|
|
199
|
+
| **Graph** | \`graph:focused\` | nodeId | Node hovered/selected |
|
|
200
|
+
| **Map** | \`map:focused\` | markerId | Marker hovered/selected |
|
|
201
|
+
| **Doc** | \`doc:scroll\` | 0–1 number | Scroll position in document |
|
|
202
|
+
|
|
203
|
+
Example — mark a kanban card as being inspected, then clear on completion:
|
|
204
|
+
\`\`\`
|
|
205
|
+
set_doc_awareness(boardDocId, { "kanban:hovering": cardId })
|
|
206
|
+
// ... do work ...
|
|
207
|
+
set_doc_awareness(boardDocId, { "kanban:hovering": null })
|
|
208
|
+
\`\`\`
|
|
209
|
+
|
|
210
|
+
---
|
|
211
|
+
|
|
212
|
+
## Receiving Instructions from Humans
|
|
213
|
+
|
|
214
|
+
Three ways humans can send you tasks:
|
|
215
|
+
|
|
216
|
+
**Channel events (preferred):** When running in channel mode, \`ai:task\` events from
|
|
217
|
+
human users arrive as \`<channel source="abracadabra" ...>\` notifications directly in
|
|
218
|
+
your session. These include the sender name, task ID, and document context. Use the
|
|
219
|
+
\`reply\` tool to send your response back — it creates a reply document and signals
|
|
220
|
+
task completion.
|
|
221
|
+
|
|
222
|
+
**Chat document watching:** Use \`watch_chat\` to observe a document for new messages.
|
|
223
|
+
Changes are pushed as channel notifications in real time. Reply using the \`reply\` tool
|
|
224
|
+
with the chat document's ID.
|
|
225
|
+
|
|
226
|
+
**Polling (fallback):** Call \`poll_inbox\` to read the "AI Inbox" document.
|
|
227
|
+
Act on any pending tasks you find there. Write your response back with \`write_document\`
|
|
228
|
+
or \`create_document\` under the inbox.
|
|
229
|
+
|
|
230
|
+
### Channel Tools
|
|
231
|
+
|
|
232
|
+
| Tool | Purpose |
|
|
233
|
+
|------|---------|
|
|
234
|
+
| \`reply\` | Send a reply to Abracadabra — creates a child doc with your response. Pass \`task_id\` to signal ai:task completion. |
|
|
235
|
+
| \`watch_chat\` | Start/stop watching a document for chat messages. New content arrives as channel notifications. |
|
|
236
|
+
|
|
237
|
+
When you complete a task, always use \`reply\` (in channel mode) or write a response
|
|
238
|
+
document (in polling mode) so the human knows the work is done.
|
|
239
|
+
|
|
240
|
+
---
|
|
241
|
+
|
|
242
|
+
## Common Patterns
|
|
243
|
+
|
|
244
|
+
### Populate a kanban board from a list
|
|
245
|
+
\`\`\`
|
|
246
|
+
1. create_document → kanban parent
|
|
247
|
+
2. For each column: create_document under kanban
|
|
248
|
+
3. For each card: create_document under the appropriate column
|
|
249
|
+
4. Optionally set metadata (color, tags, priority) on cards
|
|
250
|
+
\`\`\`
|
|
251
|
+
|
|
252
|
+
### Explore a document fully (the correct way)
|
|
253
|
+
\`\`\`
|
|
254
|
+
User: "Check out the Marketing doc"
|
|
255
|
+
|
|
256
|
+
1. read_document(marketingId)
|
|
257
|
+
→ returns { markdown, children: [...] }
|
|
258
|
+
2. The body may be empty — that does NOT mean the doc is empty.
|
|
259
|
+
Children ARE the content. Read every child:
|
|
260
|
+
read_document(childId1), read_document(childId2), ...
|
|
261
|
+
3. Each child's response also includes ITS children.
|
|
262
|
+
Continue recursively until no children remain.
|
|
263
|
+
\`\`\`
|
|
264
|
+
|
|
265
|
+
**Never conclude a document is "empty" just because its markdown body is empty.
|
|
266
|
+
Always check and traverse the \`children\` array returned by \`read_document\`.**
|
|
267
|
+
|
|
268
|
+
### Write rich content to a document
|
|
269
|
+
\`\`\`
|
|
270
|
+
1. read_document to see current content (and children)
|
|
271
|
+
2. write_document with markdown (mode: "replace" to overwrite, "append" to add)
|
|
272
|
+
3. Include frontmatter for title/metadata if needed
|
|
273
|
+
\`\`\`
|
|
274
|
+
|
|
275
|
+
### Organize documents into a hierarchy
|
|
276
|
+
\`\`\`
|
|
277
|
+
1. get_document_tree to understand current structure
|
|
278
|
+
2. create_document to add new nodes
|
|
279
|
+
3. move_document to reorganize
|
|
280
|
+
4. change_document_type to switch views
|
|
281
|
+
\`\`\`
|
|
282
|
+
|
|
283
|
+
---
|
|
284
|
+
|
|
285
|
+
## Rules
|
|
286
|
+
|
|
287
|
+
### MUST:
|
|
288
|
+
- Call \`list_spaces\` first to find the hub doc ID before creating any documents
|
|
289
|
+
- Use document \`label\` as the display value — renaming a document renames it everywhere
|
|
290
|
+
- Create child documents for every visible item (columns, cards, events, etc.)
|
|
291
|
+
- Use universal meta keys (color, icon, dateStart) — never prefix with page type
|
|
292
|
+
- Set meaningful labels on every document you create
|
|
293
|
+
- Use \`get_document_tree\` to understand existing structure before adding to a space
|
|
294
|
+
- Traverse the full \`children\` tree when asked to "check out", "explore", or "summarize" a document
|
|
295
|
+
|
|
296
|
+
### MUST NOT:
|
|
297
|
+
- Conclude a document is empty because its markdown body is empty — check children first
|
|
298
|
+
- Stop at the top-level doc when exploring — children and grandchildren are part of the content
|
|
299
|
+
- Store display values in metadata — the label IS the display value
|
|
300
|
+
- Use special metadata to distinguish document roles — the hierarchy does that
|
|
301
|
+
- Create documents just for metadata — metadata lives ON documents
|
|
302
|
+
- Break interchangeability — switching page types must not lose data
|
|
303
|
+
- Use emoji as \`icon\` values — only lowercase kebab-case Lucide icon names are valid
|
|
304
|
+
- Create top-level documents without first confirming the correct hub doc ID
|
|
305
|
+
- Rename or write content to the space hub document itself
|
|
306
|
+
`
|
|
307
|
+
|
|
308
|
+
export function registerAgentGuide(mcp: McpServer) {
|
|
309
|
+
mcp.resource(
|
|
310
|
+
'agent-guide',
|
|
311
|
+
'abracadabra://agent-guide',
|
|
312
|
+
{
|
|
313
|
+
description: 'Comprehensive guide for AI agents working with Abracadabra documents. Covers page types, tree operations, metadata, content structure, and best practices.',
|
|
314
|
+
mimeType: 'text/markdown',
|
|
315
|
+
},
|
|
316
|
+
async () => ({
|
|
317
|
+
contents: [{
|
|
318
|
+
uri: 'abracadabra://agent-guide',
|
|
319
|
+
text: AGENT_GUIDE,
|
|
320
|
+
mimeType: 'text/markdown',
|
|
321
|
+
}],
|
|
322
|
+
})
|
|
323
|
+
)
|
|
324
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dynamic resource: server health + info.
|
|
3
|
+
*/
|
|
4
|
+
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
|
|
5
|
+
import type { AbracadabraMCPServer } from '../server.ts'
|
|
6
|
+
|
|
7
|
+
export function registerServerInfoResource(mcp: McpServer, server: AbracadabraMCPServer) {
|
|
8
|
+
mcp.resource(
|
|
9
|
+
'server-info',
|
|
10
|
+
'abracadabra://server-info',
|
|
11
|
+
{
|
|
12
|
+
description: 'Abracadabra server info: name, version, root document ID, and health status.',
|
|
13
|
+
mimeType: 'application/json',
|
|
14
|
+
},
|
|
15
|
+
async () => {
|
|
16
|
+
let health = null
|
|
17
|
+
try {
|
|
18
|
+
health = await server.client.health()
|
|
19
|
+
} catch {
|
|
20
|
+
// health endpoint unavailable
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const spaces = server.spaces
|
|
24
|
+
const activeSpace = spaces.find(s => s.doc_id === server.rootDocId) ?? null
|
|
25
|
+
|
|
26
|
+
const info = {
|
|
27
|
+
serverInfo: server.serverInfo,
|
|
28
|
+
activeSpaceDocId: server.rootDocId,
|
|
29
|
+
activeSpace,
|
|
30
|
+
spaces: spaces.length > 0 ? spaces : undefined,
|
|
31
|
+
health,
|
|
32
|
+
agent: {
|
|
33
|
+
name: server.agentName,
|
|
34
|
+
color: server.agentColor,
|
|
35
|
+
},
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return {
|
|
39
|
+
contents: [{
|
|
40
|
+
uri: 'abracadabra://server-info',
|
|
41
|
+
text: JSON.stringify(info, null, 2),
|
|
42
|
+
mimeType: 'application/json',
|
|
43
|
+
}],
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
)
|
|
47
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dynamic resource: document tree as navigable JSON.
|
|
3
|
+
*/
|
|
4
|
+
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
|
|
5
|
+
import type { AbracadabraMCPServer } from '../server.ts'
|
|
6
|
+
import type { TreeEntry } from '../converters/types.ts'
|
|
7
|
+
|
|
8
|
+
function readEntries(treeMap: any): TreeEntry[] {
|
|
9
|
+
const entries: TreeEntry[] = []
|
|
10
|
+
treeMap.forEach((value: any, id: string) => {
|
|
11
|
+
entries.push({
|
|
12
|
+
id,
|
|
13
|
+
label: value.label || 'Untitled',
|
|
14
|
+
parentId: value.parentId ?? null,
|
|
15
|
+
order: value.order ?? 0,
|
|
16
|
+
type: value.type,
|
|
17
|
+
meta: value.meta,
|
|
18
|
+
})
|
|
19
|
+
})
|
|
20
|
+
return entries
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function childrenOf(entries: TreeEntry[], parentId: string | null): TreeEntry[] {
|
|
24
|
+
return entries
|
|
25
|
+
.filter(e => e.parentId === parentId)
|
|
26
|
+
.sort((a, b) => (a.order ?? 0) - (b.order ?? 0))
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function buildTree(entries: TreeEntry[], rootId: string | null, maxDepth: number, depth = 0): any[] {
|
|
30
|
+
if (maxDepth >= 0 && depth >= maxDepth) return []
|
|
31
|
+
return childrenOf(entries, rootId).map(e => ({
|
|
32
|
+
id: e.id,
|
|
33
|
+
label: e.label,
|
|
34
|
+
type: e.type,
|
|
35
|
+
children: buildTree(entries, e.id, maxDepth, depth + 1),
|
|
36
|
+
}))
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function registerTreeResource(mcp: McpServer, server: AbracadabraMCPServer) {
|
|
40
|
+
mcp.resource(
|
|
41
|
+
'tree',
|
|
42
|
+
'abracadabra://tree',
|
|
43
|
+
{
|
|
44
|
+
description: 'Full document tree as nested JSON. Shows the complete hierarchy of all documents.',
|
|
45
|
+
mimeType: 'application/json',
|
|
46
|
+
},
|
|
47
|
+
async () => {
|
|
48
|
+
const treeMap = server.getTreeMap()
|
|
49
|
+
if (!treeMap) {
|
|
50
|
+
return {
|
|
51
|
+
contents: [{
|
|
52
|
+
uri: 'abracadabra://tree',
|
|
53
|
+
text: '{"error": "Not connected"}',
|
|
54
|
+
mimeType: 'application/json',
|
|
55
|
+
}],
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const entries = readEntries(treeMap)
|
|
60
|
+
const tree = buildTree(entries, null, 10)
|
|
61
|
+
|
|
62
|
+
return {
|
|
63
|
+
contents: [{
|
|
64
|
+
uri: 'abracadabra://tree',
|
|
65
|
+
text: JSON.stringify(tree, null, 2),
|
|
66
|
+
mimeType: 'application/json',
|
|
67
|
+
}],
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
)
|
|
71
|
+
}
|