@abraca/mcp 1.0.12 → 1.0.18
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 +632 -113
- package/dist/abracadabra-mcp.cjs.map +1 -1
- package/dist/abracadabra-mcp.esm.js +631 -113
- package/dist/abracadabra-mcp.esm.js.map +1 -1
- package/dist/index.d.ts +24 -0
- package/package.json +1 -1
- package/src/converters/markdownToYjs.ts +52 -2
- package/src/converters/types.ts +54 -4
- package/src/converters/yjsToMarkdown.ts +5 -0
- package/src/hook-bridge.ts +204 -0
- package/src/index.ts +17 -1
- package/src/resources/agent-guide.ts +203 -92
- package/src/server.ts +114 -0
- package/src/tools/channel.ts +11 -0
- package/src/tools/content.ts +13 -1
- package/src/tools/hooks.ts +42 -0
- package/src/tools/meta.ts +1 -1
- package/src/tools/tree.ts +13 -2
|
@@ -6,9 +6,11 @@ import { AbracadabraClient, AbracadabraProvider, awarenessStatesToArray } from "
|
|
|
6
6
|
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
7
7
|
import * as fs from "node:fs";
|
|
8
8
|
import { existsSync } from "node:fs";
|
|
9
|
+
import * as os from "node:os";
|
|
9
10
|
import { homedir } from "node:os";
|
|
10
11
|
import * as path from "node:path";
|
|
11
12
|
import { dirname, join } from "node:path";
|
|
13
|
+
import * as http from "node:http";
|
|
12
14
|
|
|
13
15
|
//#region \0rolldown/runtime.js
|
|
14
16
|
var __create = Object.create;
|
|
@@ -4061,7 +4063,7 @@ const cidrv4 = /^((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\.){3}(25[0-
|
|
|
4061
4063
|
const cidrv6 = /^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|::|([0-9a-fA-F]{1,4})?::([0-9a-fA-F]{1,4}:?){0,6})\/(12[0-8]|1[01][0-9]|[1-9]?[0-9])$/;
|
|
4062
4064
|
const base64 = /^$|^(?:[0-9a-zA-Z+/]{4})*(?:(?:[0-9a-zA-Z+/]{2}==)|(?:[0-9a-zA-Z+/]{3}=))?$/;
|
|
4063
4065
|
const base64url = /^[A-Za-z0-9_-]*$/;
|
|
4064
|
-
const hostname = /^([a-zA-Z0-9-]+\.)*[a-zA-Z0-9-]+$/;
|
|
4066
|
+
const hostname$1 = /^([a-zA-Z0-9-]+\.)*[a-zA-Z0-9-]+$/;
|
|
4065
4067
|
const e164 = /^\+(?:[0-9]){6,14}[0-9]$/;
|
|
4066
4068
|
const dateSource = `(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))`;
|
|
4067
4069
|
const date$1 = /* @__PURE__ */ new RegExp(`^${dateSource}$`);
|
|
@@ -4608,7 +4610,7 @@ const $ZodURL = /* @__PURE__ */ $constructor("$ZodURL", (inst, def) => {
|
|
|
4608
4610
|
code: "invalid_format",
|
|
4609
4611
|
format: "url",
|
|
4610
4612
|
note: "Invalid hostname",
|
|
4611
|
-
pattern: hostname.source,
|
|
4613
|
+
pattern: hostname$1.source,
|
|
4612
4614
|
input: payload.value,
|
|
4613
4615
|
inst,
|
|
4614
4616
|
continue: !def.abort
|
|
@@ -19443,6 +19445,9 @@ var AbracadabraMCPServer = class {
|
|
|
19443
19445
|
this._serverRef = null;
|
|
19444
19446
|
this._handledTaskIds = /* @__PURE__ */ new Set();
|
|
19445
19447
|
this._userId = null;
|
|
19448
|
+
this._statusClearTimer = null;
|
|
19449
|
+
this._typingInterval = null;
|
|
19450
|
+
this._lastChatChannel = null;
|
|
19446
19451
|
this.config = config;
|
|
19447
19452
|
this.client = new AbracadabraClient({
|
|
19448
19453
|
url: config.url,
|
|
@@ -19537,8 +19542,12 @@ var AbracadabraMCPServer = class {
|
|
|
19537
19542
|
provider.awareness.setLocalStateField("user", {
|
|
19538
19543
|
name: this.agentName,
|
|
19539
19544
|
color: this.agentColor,
|
|
19540
|
-
publicKey: this._userId
|
|
19545
|
+
publicKey: this._userId,
|
|
19546
|
+
isAgent: true
|
|
19541
19547
|
});
|
|
19548
|
+
provider.awareness.setLocalStateField("status", null);
|
|
19549
|
+
provider.awareness.setLocalStateField("activeToolCall", null);
|
|
19550
|
+
provider.awareness.setLocalStateField("statusContext", null);
|
|
19542
19551
|
const conn = {
|
|
19543
19552
|
doc,
|
|
19544
19553
|
provider,
|
|
@@ -19590,7 +19599,8 @@ var AbracadabraMCPServer = class {
|
|
|
19590
19599
|
childProvider.awareness.setLocalStateField("user", {
|
|
19591
19600
|
name: this.agentName,
|
|
19592
19601
|
color: this.agentColor,
|
|
19593
|
-
publicKey: this._userId
|
|
19602
|
+
publicKey: this._userId,
|
|
19603
|
+
isAgent: true
|
|
19594
19604
|
});
|
|
19595
19605
|
this.childCache.set(docId, {
|
|
19596
19606
|
provider: childProvider,
|
|
@@ -19657,6 +19667,7 @@ var AbracadabraMCPServer = class {
|
|
|
19657
19667
|
const user = state["user"];
|
|
19658
19668
|
const senderName = user && typeof user === "object" && typeof user.name === "string" ? user.name : "Unknown";
|
|
19659
19669
|
console.error(`[abracadabra-mcp] Handling ai:task id=${id} from ${senderName}: ${text.slice(0, 80)}`);
|
|
19670
|
+
this.setAutoStatus("thinking");
|
|
19660
19671
|
this._dispatchAiTask({
|
|
19661
19672
|
id,
|
|
19662
19673
|
text,
|
|
@@ -19721,6 +19732,18 @@ var AbracadabraMCPServer = class {
|
|
|
19721
19732
|
const parts = channel.split(":");
|
|
19722
19733
|
if (parts.length === 3 && parts[1] !== this._userId && parts[2] !== this._userId) return;
|
|
19723
19734
|
}
|
|
19735
|
+
if (channel) {
|
|
19736
|
+
const rootProvider = this._activeConnection?.provider;
|
|
19737
|
+
if (rootProvider) rootProvider.sendStateless(JSON.stringify({
|
|
19738
|
+
type: "chat:mark_read",
|
|
19739
|
+
channel,
|
|
19740
|
+
timestamp: Math.floor(Date.now() / 1e3)
|
|
19741
|
+
}));
|
|
19742
|
+
this._lastChatChannel = channel;
|
|
19743
|
+
this.sendTypingIndicator(channel);
|
|
19744
|
+
this._startTypingInterval(channel);
|
|
19745
|
+
}
|
|
19746
|
+
this.setAutoStatus("thinking");
|
|
19724
19747
|
await this._serverRef.notification({
|
|
19725
19748
|
method: "notifications/claude/channel",
|
|
19726
19749
|
params: {
|
|
@@ -19738,15 +19761,82 @@ var AbracadabraMCPServer = class {
|
|
|
19738
19761
|
console.error(`[abracadabra-mcp] Chat message from ${data.sender_name ?? "unknown"} on ${channel}`);
|
|
19739
19762
|
} catch {}
|
|
19740
19763
|
}
|
|
19764
|
+
/**
|
|
19765
|
+
* Set the agent's status in root awareness with auto-clear after idle.
|
|
19766
|
+
* @param statusContext — scopes the status to a specific channel/context so the
|
|
19767
|
+
* dashboard only shows it in the relevant chat. Defaults to `_lastChatChannel`.
|
|
19768
|
+
*/
|
|
19769
|
+
setAutoStatus(status, docId, statusContext) {
|
|
19770
|
+
const provider = this._activeConnection?.provider;
|
|
19771
|
+
if (!provider) return;
|
|
19772
|
+
if (this._statusClearTimer) {
|
|
19773
|
+
clearTimeout(this._statusClearTimer);
|
|
19774
|
+
this._statusClearTimer = null;
|
|
19775
|
+
}
|
|
19776
|
+
provider.awareness.setLocalStateField("status", status);
|
|
19777
|
+
if (docId !== void 0) provider.awareness.setLocalStateField("docId", docId);
|
|
19778
|
+
const context = status ? statusContext !== void 0 ? statusContext : this._lastChatChannel : null;
|
|
19779
|
+
provider.awareness.setLocalStateField("statusContext", context ?? null);
|
|
19780
|
+
if (!status) this._stopTypingInterval();
|
|
19781
|
+
if (status) this._statusClearTimer = setTimeout(() => {
|
|
19782
|
+
provider.awareness.setLocalStateField("status", null);
|
|
19783
|
+
provider.awareness.setLocalStateField("activeToolCall", null);
|
|
19784
|
+
provider.awareness.setLocalStateField("statusContext", null);
|
|
19785
|
+
this._stopTypingInterval();
|
|
19786
|
+
}, 1e4);
|
|
19787
|
+
}
|
|
19788
|
+
/** Re-send typing indicator every 2s so dashboard keeps showing it (expires at 3s). */
|
|
19789
|
+
_startTypingInterval(channel) {
|
|
19790
|
+
this._stopTypingInterval();
|
|
19791
|
+
this._typingInterval = setInterval(() => {
|
|
19792
|
+
this.sendTypingIndicator(channel);
|
|
19793
|
+
}, 2e3);
|
|
19794
|
+
}
|
|
19795
|
+
_stopTypingInterval() {
|
|
19796
|
+
if (this._typingInterval) {
|
|
19797
|
+
clearInterval(this._typingInterval);
|
|
19798
|
+
this._typingInterval = null;
|
|
19799
|
+
}
|
|
19800
|
+
this._lastChatChannel = null;
|
|
19801
|
+
}
|
|
19802
|
+
/**
|
|
19803
|
+
* Broadcast which tool the agent is currently executing.
|
|
19804
|
+
* Dashboard renders this as a ChatTool indicator.
|
|
19805
|
+
*/
|
|
19806
|
+
setActiveToolCall(toolCall) {
|
|
19807
|
+
this._activeConnection?.provider?.awareness.setLocalStateField("activeToolCall", toolCall);
|
|
19808
|
+
}
|
|
19809
|
+
/**
|
|
19810
|
+
* Send a typing indicator to a chat channel.
|
|
19811
|
+
*/
|
|
19812
|
+
sendTypingIndicator(channel) {
|
|
19813
|
+
const rootProvider = this._activeConnection?.provider;
|
|
19814
|
+
if (!rootProvider) return;
|
|
19815
|
+
rootProvider.sendStateless(JSON.stringify({
|
|
19816
|
+
type: "chat:typing",
|
|
19817
|
+
channel,
|
|
19818
|
+
sender_name: this.agentName
|
|
19819
|
+
}));
|
|
19820
|
+
}
|
|
19741
19821
|
/** Graceful shutdown. */
|
|
19742
19822
|
async destroy() {
|
|
19823
|
+
this._stopTypingInterval();
|
|
19824
|
+
if (this._statusClearTimer) {
|
|
19825
|
+
clearTimeout(this._statusClearTimer);
|
|
19826
|
+
this._statusClearTimer = null;
|
|
19827
|
+
}
|
|
19743
19828
|
if (this.evictionTimer) {
|
|
19744
19829
|
clearInterval(this.evictionTimer);
|
|
19745
19830
|
this.evictionTimer = null;
|
|
19746
19831
|
}
|
|
19747
19832
|
for (const [, cached] of this.childCache) cached.provider.destroy();
|
|
19748
19833
|
this.childCache.clear();
|
|
19749
|
-
for (const [, conn] of this._spaceConnections)
|
|
19834
|
+
for (const [, conn] of this._spaceConnections) {
|
|
19835
|
+
conn.provider.awareness.setLocalStateField("status", null);
|
|
19836
|
+
conn.provider.awareness.setLocalStateField("activeToolCall", null);
|
|
19837
|
+
conn.provider.awareness.setLocalStateField("statusContext", null);
|
|
19838
|
+
conn.provider.destroy();
|
|
19839
|
+
}
|
|
19750
19840
|
this._spaceConnections.clear();
|
|
19751
19841
|
this._activeConnection = null;
|
|
19752
19842
|
console.error("[abracadabra-mcp] Shutdown complete");
|
|
@@ -19888,15 +19978,23 @@ function registerTreeTools(mcp, server) {
|
|
|
19888
19978
|
mcp.tool("create_document", "Create a new document in the tree. Returns the new document ID.", {
|
|
19889
19979
|
parentId: z.string().optional().describe("Parent document ID. Omit for top-level pages. Use a document ID for nested/child pages."),
|
|
19890
19980
|
label: z.string().describe("Display name / title for the document."),
|
|
19891
|
-
type: z.string().optional().describe("Page type
|
|
19981
|
+
type: z.string().optional().describe("Page type — sets how this document renders. \"doc\" (rich text), \"kanban\" (columns → cards), \"table\" (columns → cells, positional rows), \"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\" (image/media items), \"map\" (markers/lines with geoLat/geoLng), \"graph\" (knowledge graph nodes), \"dashboard\" (positioned widgets with deskX/deskY/deskMode), \"mindmap\" (connected nodes), \"spatial\" (3D objects with spShape/spX/spY/spZ), \"media\" (audio/video tracks), \"slides\" (slide deck), \"whiteboard\" (freeform canvas). Omit to inherit parent view. Only set on the parent page, NEVER on child items."),
|
|
19892
19982
|
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.")
|
|
19893
19983
|
}, async ({ parentId, label, type, meta }) => {
|
|
19984
|
+
server.setAutoStatus("creating");
|
|
19985
|
+
server.setActiveToolCall({
|
|
19986
|
+
name: "create_document",
|
|
19987
|
+
target: label
|
|
19988
|
+
});
|
|
19894
19989
|
const treeMap = server.getTreeMap();
|
|
19895
19990
|
const rootDoc = server.rootDocument;
|
|
19896
|
-
if (!treeMap || !rootDoc)
|
|
19897
|
-
|
|
19898
|
-
|
|
19899
|
-
|
|
19991
|
+
if (!treeMap || !rootDoc) {
|
|
19992
|
+
server.setActiveToolCall(null);
|
|
19993
|
+
return { content: [{
|
|
19994
|
+
type: "text",
|
|
19995
|
+
text: "Not connected"
|
|
19996
|
+
}] };
|
|
19997
|
+
}
|
|
19900
19998
|
const id = crypto.randomUUID();
|
|
19901
19999
|
const normalizedParent = normalizeRootId(parentId, server);
|
|
19902
20000
|
const now = Date.now();
|
|
@@ -19911,6 +20009,8 @@ function registerTreeTools(mcp, server) {
|
|
|
19911
20009
|
updatedAt: now
|
|
19912
20010
|
});
|
|
19913
20011
|
});
|
|
20012
|
+
server.setFocusedDoc(id);
|
|
20013
|
+
server.setActiveToolCall(null);
|
|
19914
20014
|
return { content: [{
|
|
19915
20015
|
type: "text",
|
|
19916
20016
|
text: JSON.stringify({
|
|
@@ -20065,6 +20165,7 @@ function registerTreeTools(mcp, server) {
|
|
|
20065
20165
|
label: (entry.label || "Untitled") + " (copy)",
|
|
20066
20166
|
order: Date.now()
|
|
20067
20167
|
});
|
|
20168
|
+
server.setFocusedDoc(newId);
|
|
20068
20169
|
return { content: [{
|
|
20069
20170
|
type: "text",
|
|
20070
20171
|
text: JSON.stringify({
|
|
@@ -20150,11 +20251,44 @@ function parseFrontmatter(markdown) {
|
|
|
20150
20251
|
if (subtitle) meta.subtitle = subtitle;
|
|
20151
20252
|
const url = getStr(["url"]);
|
|
20152
20253
|
if (url) meta.url = url;
|
|
20254
|
+
const email = getStr(["email"]);
|
|
20255
|
+
if (email) meta.email = email;
|
|
20256
|
+
const phone = getStr(["phone"]);
|
|
20257
|
+
if (phone) meta.phone = phone;
|
|
20153
20258
|
const ratingRaw = getStr(["rating"]);
|
|
20154
20259
|
if (ratingRaw !== void 0) {
|
|
20155
20260
|
const n = Number(ratingRaw);
|
|
20156
20261
|
if (!Number.isNaN(n)) meta.rating = Math.min(5, Math.max(0, n));
|
|
20157
20262
|
}
|
|
20263
|
+
const datetimeStart = getStr(["datetimeStart"]);
|
|
20264
|
+
if (datetimeStart) meta.datetimeStart = datetimeStart;
|
|
20265
|
+
const datetimeEnd = getStr(["datetimeEnd"]);
|
|
20266
|
+
if (datetimeEnd) meta.datetimeEnd = datetimeEnd;
|
|
20267
|
+
const allDayRaw = raw["allDay"];
|
|
20268
|
+
if (allDayRaw !== void 0) meta.allDay = allDayRaw === "true" || allDayRaw === true;
|
|
20269
|
+
const geoLatRaw = getStr(["geoLat"]);
|
|
20270
|
+
if (geoLatRaw !== void 0) {
|
|
20271
|
+
const n = Number(geoLatRaw);
|
|
20272
|
+
if (!Number.isNaN(n)) meta.geoLat = n;
|
|
20273
|
+
}
|
|
20274
|
+
const geoLngRaw = getStr(["geoLng"]);
|
|
20275
|
+
if (geoLngRaw !== void 0) {
|
|
20276
|
+
const n = Number(geoLngRaw);
|
|
20277
|
+
if (!Number.isNaN(n)) meta.geoLng = n;
|
|
20278
|
+
}
|
|
20279
|
+
const geoType = getStr(["geoType"]);
|
|
20280
|
+
if (geoType && (geoType === "marker" || geoType === "line" || geoType === "measure")) meta.geoType = geoType;
|
|
20281
|
+
const geoDescription = getStr(["geoDescription"]);
|
|
20282
|
+
if (geoDescription) meta.geoDescription = geoDescription;
|
|
20283
|
+
const numberRaw = getStr(["number"]);
|
|
20284
|
+
if (numberRaw !== void 0) {
|
|
20285
|
+
const n = Number(numberRaw);
|
|
20286
|
+
if (!Number.isNaN(n)) meta.number = n;
|
|
20287
|
+
}
|
|
20288
|
+
const unit = getStr(["unit"]);
|
|
20289
|
+
if (unit) meta.unit = unit;
|
|
20290
|
+
const note = getStr(["note"]);
|
|
20291
|
+
if (note) meta.note = note;
|
|
20158
20292
|
return {
|
|
20159
20293
|
title: typeof raw["title"] === "string" ? raw["title"] : void 0,
|
|
20160
20294
|
meta,
|
|
@@ -20192,8 +20326,12 @@ function parseInline(text) {
|
|
|
20192
20326
|
attrs: { kbd: { value: kbdProps["value"] || "" } }
|
|
20193
20327
|
});
|
|
20194
20328
|
} else if (match[5] !== void 0) {
|
|
20195
|
-
const
|
|
20196
|
-
|
|
20329
|
+
const docId = match[5];
|
|
20330
|
+
const displayText = match[6] ?? docId;
|
|
20331
|
+
tokens.push({
|
|
20332
|
+
text: displayText,
|
|
20333
|
+
attrs: { link: { href: `/doc/${docId}` } }
|
|
20334
|
+
});
|
|
20197
20335
|
} else if (match[7] !== void 0) tokens.push({
|
|
20198
20336
|
text: match[7],
|
|
20199
20337
|
attrs: { strike: true }
|
|
@@ -20342,6 +20480,15 @@ function parseBlocks(markdown) {
|
|
|
20342
20480
|
i++;
|
|
20343
20481
|
continue;
|
|
20344
20482
|
}
|
|
20483
|
+
const docEmbedMatch = line.match(/^!\[\[([^\]|]+?)(?:\|[^\]]*?)?\]\]\s*$/);
|
|
20484
|
+
if (docEmbedMatch) {
|
|
20485
|
+
blocks.push({
|
|
20486
|
+
type: "docEmbed",
|
|
20487
|
+
docId: docEmbedMatch[1]
|
|
20488
|
+
});
|
|
20489
|
+
i++;
|
|
20490
|
+
continue;
|
|
20491
|
+
}
|
|
20345
20492
|
const imgMatch = line.match(/^!\[([^\]]*)\]\(([^)]+)\)(\{[^}]*\})?\s*$/);
|
|
20346
20493
|
if (imgMatch) {
|
|
20347
20494
|
const alt = imgMatch[1] ?? "";
|
|
@@ -20649,6 +20796,7 @@ function blockElName(b) {
|
|
|
20649
20796
|
case "field": return "field";
|
|
20650
20797
|
case "fieldGroup": return "fieldGroup";
|
|
20651
20798
|
case "image": return "image";
|
|
20799
|
+
case "docEmbed": return "docEmbed";
|
|
20652
20800
|
}
|
|
20653
20801
|
}
|
|
20654
20802
|
function fillBlock(el, block) {
|
|
@@ -20864,6 +21012,9 @@ function fillBlock(el, block) {
|
|
|
20864
21012
|
if (block.width) el.setAttribute("width", block.width);
|
|
20865
21013
|
if (block.height) el.setAttribute("height", block.height);
|
|
20866
21014
|
break;
|
|
21015
|
+
case "docEmbed":
|
|
21016
|
+
el.setAttribute("docId", block.docId);
|
|
21017
|
+
break;
|
|
20867
21018
|
}
|
|
20868
21019
|
}
|
|
20869
21020
|
function populateYDocFromMarkdown(fragment, markdown, fallbackTitle = "Untitled") {
|
|
@@ -20912,6 +21063,7 @@ function populateYDocFromMarkdown(fragment, markdown, fallbackTitle = "Untitled"
|
|
|
20912
21063
|
case "field": return new Y.XmlElement("field");
|
|
20913
21064
|
case "fieldGroup": return new Y.XmlElement("fieldGroup");
|
|
20914
21065
|
case "image": return new Y.XmlElement("image");
|
|
21066
|
+
case "docEmbed": return new Y.XmlElement("docEmbed");
|
|
20915
21067
|
}
|
|
20916
21068
|
});
|
|
20917
21069
|
fragment.insert(0, [
|
|
@@ -20983,6 +21135,10 @@ function serializeElement(el, indent = "") {
|
|
|
20983
21135
|
}
|
|
20984
21136
|
case "horizontalRule": return "---";
|
|
20985
21137
|
case "table": return serializeTable(el);
|
|
21138
|
+
case "docEmbed": {
|
|
21139
|
+
const docId = el.getAttribute("docId");
|
|
21140
|
+
return docId ? `![[${docId}]]` : "";
|
|
21141
|
+
}
|
|
20986
21142
|
case "image": {
|
|
20987
21143
|
const src = el.getAttribute("src") || "";
|
|
20988
21144
|
const alt = el.getAttribute("alt") || "";
|
|
@@ -21145,6 +21301,11 @@ function yjsToMarkdown(fragment) {
|
|
|
21145
21301
|
function registerContentTools(mcp, server) {
|
|
21146
21302
|
mcp.tool("read_document", "Read a document's content as markdown, along with its immediate children. IMPORTANT: A document's full content is its body text PLUS all its children. An empty body does not mean empty content — the children ARE the content (sub-docs, kanban columns, table columns, calendar events, etc.). Always check the returned \"children\" array and read child documents too.", { docId: z.string().describe("Document ID to read.") }, async ({ docId }) => {
|
|
21147
21303
|
try {
|
|
21304
|
+
server.setAutoStatus("reading", docId);
|
|
21305
|
+
server.setActiveToolCall({
|
|
21306
|
+
name: "read_document",
|
|
21307
|
+
target: docId
|
|
21308
|
+
});
|
|
21148
21309
|
const { title, markdown } = yjsToMarkdown((await server.getChildProvider(docId)).document.getXmlFragment("default"));
|
|
21149
21310
|
server.setFocusedDoc(docId);
|
|
21150
21311
|
server.setDocCursor(docId, 0);
|
|
@@ -21170,6 +21331,7 @@ function registerContentTools(mcp, server) {
|
|
|
21170
21331
|
});
|
|
21171
21332
|
children.sort((a, b) => (treeMap.get(a.id)?.order ?? 0) - (treeMap.get(b.id)?.order ?? 0));
|
|
21172
21333
|
}
|
|
21334
|
+
server.setActiveToolCall(null);
|
|
21173
21335
|
const result = {
|
|
21174
21336
|
label,
|
|
21175
21337
|
type,
|
|
@@ -21182,6 +21344,7 @@ function registerContentTools(mcp, server) {
|
|
|
21182
21344
|
text: JSON.stringify(result, null, 2)
|
|
21183
21345
|
}] };
|
|
21184
21346
|
} catch (error) {
|
|
21347
|
+
server.setActiveToolCall(null);
|
|
21185
21348
|
return {
|
|
21186
21349
|
content: [{
|
|
21187
21350
|
type: "text",
|
|
@@ -21191,12 +21354,17 @@ function registerContentTools(mcp, server) {
|
|
|
21191
21354
|
};
|
|
21192
21355
|
}
|
|
21193
21356
|
});
|
|
21194
|
-
mcp.tool("write_document", "Write markdown content to a document. Parses the markdown and writes it to the Y.js CRDT document, which syncs in real-time to all connected clients. Supports optional YAML frontmatter for title and metadata.", {
|
|
21357
|
+
mcp.tool("write_document", "Write markdown content to a document. Parses the markdown and writes it to the Y.js CRDT document, which syncs in real-time to all connected clients. Supports optional YAML frontmatter for title and metadata. Use ![[docId]] to embed another document as a block, or [[docId|label]] for inline doc links.", {
|
|
21195
21358
|
docId: z.string().describe("Document ID to write to."),
|
|
21196
21359
|
markdown: z.string().describe("Markdown content to write. Can include YAML frontmatter with title and metadata fields."),
|
|
21197
21360
|
mode: z.enum(["replace", "append"]).optional().describe("Write mode. \"replace\" clears existing content first (default). \"append\" adds to the end.")
|
|
21198
21361
|
}, async ({ docId, markdown, mode }) => {
|
|
21199
21362
|
try {
|
|
21363
|
+
server.setAutoStatus("writing", docId);
|
|
21364
|
+
server.setActiveToolCall({
|
|
21365
|
+
name: "write_document",
|
|
21366
|
+
target: docId
|
|
21367
|
+
});
|
|
21200
21368
|
const writeMode = mode ?? "replace";
|
|
21201
21369
|
const doc = (await server.getChildProvider(docId)).document;
|
|
21202
21370
|
const fragment = doc.getXmlFragment("default");
|
|
@@ -21226,11 +21394,13 @@ function registerContentTools(mcp, server) {
|
|
|
21226
21394
|
populateYDocFromMarkdown(fragment, body || markdown, title || "Untitled");
|
|
21227
21395
|
server.setFocusedDoc(docId);
|
|
21228
21396
|
server.setDocCursor(docId, fragment.length);
|
|
21397
|
+
server.setActiveToolCall(null);
|
|
21229
21398
|
return { content: [{
|
|
21230
21399
|
type: "text",
|
|
21231
21400
|
text: `Document ${docId} updated (${writeMode} mode)`
|
|
21232
21401
|
}] };
|
|
21233
21402
|
} catch (error) {
|
|
21403
|
+
server.setActiveToolCall(null);
|
|
21234
21404
|
return {
|
|
21235
21405
|
content: [{
|
|
21236
21406
|
type: "text",
|
|
@@ -21268,7 +21438,7 @@ function registerMetaTools(mcp, server) {
|
|
|
21268
21438
|
});
|
|
21269
21439
|
mcp.tool("update_metadata", "Update metadata fields on a document. Merges the provided fields into existing metadata.", {
|
|
21270
21440
|
docId: z.string().describe("Document ID."),
|
|
21271
|
-
meta: z.record(z.unknown()).describe("Metadata fields to update (merged with existing).
|
|
21441
|
+
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.")
|
|
21272
21442
|
}, async ({ docId, meta }) => {
|
|
21273
21443
|
const treeMap = server.getTreeMap();
|
|
21274
21444
|
if (!treeMap) return { content: [{
|
|
@@ -21513,15 +21683,23 @@ function registerChannelTools(mcp, server) {
|
|
|
21513
21683
|
task_id: z.string().optional().describe("If replying to an ai:task, the task ID to clear from awareness.")
|
|
21514
21684
|
}, async ({ doc_id, text, task_id }) => {
|
|
21515
21685
|
try {
|
|
21686
|
+
server.setAutoStatus("writing", doc_id);
|
|
21687
|
+
server.setActiveToolCall({
|
|
21688
|
+
name: "reply",
|
|
21689
|
+
target: doc_id
|
|
21690
|
+
});
|
|
21516
21691
|
const treeMap = server.getTreeMap();
|
|
21517
21692
|
const rootDoc = server.rootDocument;
|
|
21518
|
-
if (!treeMap || !rootDoc)
|
|
21519
|
-
|
|
21520
|
-
|
|
21521
|
-
|
|
21522
|
-
|
|
21523
|
-
|
|
21524
|
-
|
|
21693
|
+
if (!treeMap || !rootDoc) {
|
|
21694
|
+
server.setActiveToolCall(null);
|
|
21695
|
+
return {
|
|
21696
|
+
content: [{
|
|
21697
|
+
type: "text",
|
|
21698
|
+
text: "Not connected"
|
|
21699
|
+
}],
|
|
21700
|
+
isError: true
|
|
21701
|
+
};
|
|
21702
|
+
}
|
|
21525
21703
|
const label = `AI Reply — ${(/* @__PURE__ */ new Date()).toISOString().replace("T", " ").slice(0, 19)}: ${text.slice(0, 40).replace(/\n/g, " ")}`;
|
|
21526
21704
|
const replyId = crypto.randomUUID();
|
|
21527
21705
|
const now = Date.now();
|
|
@@ -21537,6 +21715,7 @@ function registerChannelTools(mcp, server) {
|
|
|
21537
21715
|
});
|
|
21538
21716
|
populateYDocFromMarkdown((await server.getChildProvider(replyId)).document, text);
|
|
21539
21717
|
if (task_id) server.clearAiTask(task_id);
|
|
21718
|
+
server.setActiveToolCall(null);
|
|
21540
21719
|
return { content: [{
|
|
21541
21720
|
type: "text",
|
|
21542
21721
|
text: JSON.stringify({
|
|
@@ -21545,6 +21724,7 @@ function registerChannelTools(mcp, server) {
|
|
|
21545
21724
|
})
|
|
21546
21725
|
}] };
|
|
21547
21726
|
} catch (error) {
|
|
21727
|
+
server.setActiveToolCall(null);
|
|
21548
21728
|
return {
|
|
21549
21729
|
content: [{
|
|
21550
21730
|
type: "text",
|
|
@@ -21573,6 +21753,8 @@ function registerChannelTools(mcp, server) {
|
|
|
21573
21753
|
content: text,
|
|
21574
21754
|
sender_name: server.agentName
|
|
21575
21755
|
}));
|
|
21756
|
+
server.setAutoStatus(null);
|
|
21757
|
+
server.setActiveToolCall(null);
|
|
21576
21758
|
return { content: [{
|
|
21577
21759
|
type: "text",
|
|
21578
21760
|
text: `Sent to ${channel}`
|
|
@@ -21604,6 +21786,18 @@ Every piece of visible data in Abracadabra — a calendar event, a kanban card,
|
|
|
21604
21786
|
|
|
21605
21787
|
**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.
|
|
21606
21788
|
|
|
21789
|
+
The same tree viewed as different page types:
|
|
21790
|
+
\`\`\`
|
|
21791
|
+
My Doc
|
|
21792
|
+
├── "To Do" → Kanban: column / Table: column / Outline: item
|
|
21793
|
+
│ ├── "Fix bug" → Kanban: card / Table: cell / Outline: sub-item
|
|
21794
|
+
│ └── "Add tests"
|
|
21795
|
+
├── "In Progress"
|
|
21796
|
+
│ └── "Refactor"
|
|
21797
|
+
└── "Done"
|
|
21798
|
+
└── "Deploy"
|
|
21799
|
+
\`\`\`
|
|
21800
|
+
|
|
21607
21801
|
---
|
|
21608
21802
|
|
|
21609
21803
|
## Getting Oriented in a Space
|
|
@@ -21623,23 +21817,24 @@ If you are adding content to an existing space, call \`get_document_tree(rootId:
|
|
|
21623
21817
|
|
|
21624
21818
|
## Page Types Reference
|
|
21625
21819
|
|
|
21626
|
-
| Type | Children Are | Grandchildren Are | Depth
|
|
21627
|
-
|
|
21628
|
-
| **doc** | Sub-documents | Sub-sub-documents |
|
|
21629
|
-
| **kanban** | Columns | Cards | 2 |
|
|
21630
|
-
| **table** | Columns | Cells (positional rows) | 2 |
|
|
21631
|
-
| **calendar** | Events | — | 1 |
|
|
21632
|
-
| **timeline** | Epics | Tasks | 2 |
|
|
21633
|
-
| **
|
|
21634
|
-
| **
|
|
21635
|
-
| **
|
|
21636
|
-
| **
|
|
21637
|
-
| **
|
|
21638
|
-
| **
|
|
21639
|
-
| **
|
|
21640
|
-
| **
|
|
21641
|
-
| **
|
|
21642
|
-
| **
|
|
21820
|
+
| Type | Children Are | Grandchildren Are | Depth | Key Meta on Children |
|
|
21821
|
+
|------|-------------|-------------------|-------|---------------------|
|
|
21822
|
+
| **doc** | Sub-documents | Sub-sub-documents | ∞ | — |
|
|
21823
|
+
| **kanban** | Columns | Cards | 2 | color, icon on cards |
|
|
21824
|
+
| **table** | Columns | Cells (positional rows) | 2 | — |
|
|
21825
|
+
| **calendar** | Events | — | 1 | datetimeStart, datetimeEnd, allDay, color |
|
|
21826
|
+
| **timeline** | Epics | Tasks | 2 | dateStart, dateEnd, taskProgress, color |
|
|
21827
|
+
| **checklist** | Tasks | Sub-tasks | ∞ | checked, priority, dateEnd |
|
|
21828
|
+
| **outline** | Items | Sub-items | ∞ | — |
|
|
21829
|
+
| **mindmap** | Central nodes | Branches | ∞ | mmX, mmY |
|
|
21830
|
+
| **graph** | Nodes | — | 1 | graphX, graphY, graphPinned, color |
|
|
21831
|
+
| **gallery** | Items | — | 1 | geoLat, geoLng, datetimeStart, tags |
|
|
21832
|
+
| **map** | Markers/Lines | Points (for lines) | 2 | geoType, geoLat, geoLng, icon, color |
|
|
21833
|
+
| **dashboard** | Items | — | 1 | deskX, deskY, deskMode |
|
|
21834
|
+
| **spatial** | Objects | Sub-parts | ∞ | spShape, spX/Y/Z, spColor, spOpacity |
|
|
21835
|
+
| **media** | Tracks | — | 1 | tags |
|
|
21836
|
+
| **slides** | Slides | — | 1 | — |
|
|
21837
|
+
| **whiteboard** | Objects | — | 1 | wbX, wbY, wbW, wbH |
|
|
21643
21838
|
|
|
21644
21839
|
---
|
|
21645
21840
|
|
|
@@ -21648,26 +21843,110 @@ If you are adding content to an existing space, call \`get_document_tree(rootId:
|
|
|
21648
21843
|
### Creating Well-Structured Hierarchies
|
|
21649
21844
|
|
|
21650
21845
|
**Kanban board:**
|
|
21651
|
-
1.
|
|
21846
|
+
1. \`create_document(parentId, "Project Board", "kanban")\`
|
|
21652
21847
|
2. Create columns: \`create_document(boardId, "To Do")\`, \`create_document(boardId, "In Progress")\`, \`create_document(boardId, "Done")\`
|
|
21653
21848
|
3. Create cards under columns: \`create_document(toDoColumnId, "Fix bug #123")\`
|
|
21849
|
+
4. Optionally set card metadata: \`update_metadata(cardId, { color: "#ef4444", priority: 3, tags: ["urgent"] })\`
|
|
21850
|
+
5. Optionally configure column width: \`update_metadata(boardId, { kanbanColumnWidth: "wide" })\`
|
|
21851
|
+
|
|
21852
|
+
**Table with data:**
|
|
21853
|
+
1. \`create_document(parentId, "Contacts", "table")\`
|
|
21854
|
+
2. Create columns: \`create_document(tableId, "Name")\`, \`create_document(tableId, "Email")\`, \`create_document(tableId, "Phone")\`
|
|
21855
|
+
3. Create cells (children of columns): \`create_document(nameColId, "Alice")\`, \`create_document(emailColId, "alice@example.com")\`
|
|
21856
|
+
4. **Rows are positional** — the Nth child of each column forms row N
|
|
21857
|
+
5. Row count = max children across all columns. Missing cells render as empty.
|
|
21858
|
+
6. To add a row: create a child under each column at the same index position
|
|
21859
|
+
7. To delete row N: delete the Nth child from every column
|
|
21654
21860
|
|
|
21655
21861
|
**Calendar with events:**
|
|
21656
|
-
1.
|
|
21862
|
+
1. \`create_document(parentId, "Team Calendar", "calendar")\`
|
|
21657
21863
|
2. Create events: \`create_document(calendarId, "Sprint Planning")\`
|
|
21658
21864
|
3. Set event times: \`update_metadata(eventId, { datetimeStart: "2026-03-20T10:00:00Z", datetimeEnd: "2026-03-20T11:00:00Z" })\`
|
|
21865
|
+
4. All-day events: \`update_metadata(eventId, { datetimeStart: "2026-03-20", allDay: true })\`
|
|
21866
|
+
5. Multi-day events: \`update_metadata(eventId, { datetimeStart: "2026-03-20", datetimeEnd: "2026-03-22", allDay: true })\`
|
|
21867
|
+
6. Configure view: \`update_metadata(calendarId, { calendarView: "week" })\`
|
|
21659
21868
|
|
|
21660
|
-
**Timeline
|
|
21661
|
-
1.
|
|
21869
|
+
**Timeline (Gantt chart):**
|
|
21870
|
+
1. \`create_document(parentId, "Q1 Roadmap", "timeline")\`
|
|
21662
21871
|
2. Create epics: \`create_document(timelineId, "Auth Rewrite")\`
|
|
21663
|
-
3. Create tasks: \`create_document(epicId, "Implement JWT refresh")\`
|
|
21664
|
-
4. Set dates
|
|
21872
|
+
3. Create tasks under epics: \`create_document(epicId, "Implement JWT refresh")\`
|
|
21873
|
+
4. Set dates and progress: \`update_metadata(taskId, { dateStart: "2026-03-01", dateEnd: "2026-03-15", taskProgress: 50, color: "#6366f1" })\`
|
|
21665
21874
|
|
|
21666
|
-
**
|
|
21667
|
-
1.
|
|
21668
|
-
2. Create
|
|
21669
|
-
3.
|
|
21670
|
-
4.
|
|
21875
|
+
**Checklist (nested task list):**
|
|
21876
|
+
1. \`create_document(parentId, "Sprint Tasks", "checklist")\`
|
|
21877
|
+
2. Create tasks: \`create_document(checklistId, "Write tests")\`
|
|
21878
|
+
3. Set task properties: \`update_metadata(taskId, { checked: false, priority: 3, dateEnd: "2026-04-01" })\`
|
|
21879
|
+
4. Create sub-tasks (unlimited nesting): \`create_document(taskId, "Unit tests")\`
|
|
21880
|
+
|
|
21881
|
+
**Map with markers and lines:**
|
|
21882
|
+
1. \`create_document(parentId, "Travel Map", "map")\`
|
|
21883
|
+
2. Create markers: \`create_document(mapId, "Coffee Shop")\`
|
|
21884
|
+
3. Set marker position: \`update_metadata(markerId, { geoType: "marker", geoLat: 37.7749, geoLng: -122.4194, icon: "coffee", color: "#f97316" })\`
|
|
21885
|
+
4. Create a line/route: \`create_document(mapId, "Walking Route")\`
|
|
21886
|
+
5. Set line properties: \`update_metadata(routeId, { geoType: "line", color: "#3b82f6" })\`
|
|
21887
|
+
6. Create line points as children: \`create_document(routeId, "Start")\` then \`update_metadata(pointId, { geoLat: 37.7749, geoLng: -122.4194 })\`
|
|
21888
|
+
7. Create measuring tool: set \`geoType: "measure"\` instead of "line"
|
|
21889
|
+
|
|
21890
|
+
**Gallery:**
|
|
21891
|
+
1. \`create_document(parentId, "Photo Album", "gallery")\`
|
|
21892
|
+
2. Create items: \`create_document(galleryId, "Sunset at Beach")\`
|
|
21893
|
+
3. Set metadata: \`update_metadata(itemId, { geoLat: 37.7749, geoLng: -122.4194, tags: ["travel", "sunset"] })\`
|
|
21894
|
+
4. Configure layout: \`update_metadata(galleryId, { galleryColumns: 4, galleryAspect: "16:9" })\`
|
|
21895
|
+
|
|
21896
|
+
**Graph (knowledge graph):**
|
|
21897
|
+
1. \`create_document(parentId, "Tech Stack", "graph")\`
|
|
21898
|
+
2. Create nodes: \`create_document(graphId, "React")\`, \`create_document(graphId, "Vue")\`
|
|
21899
|
+
3. Set node properties: \`update_metadata(nodeId, { color: "#6366f1", icon: "file-code" })\`
|
|
21900
|
+
4. Edges are created by parent-child relationships AND document references (doc embeds/links in content)
|
|
21901
|
+
5. Enable reference edges: \`update_metadata(graphId, { showRefEdges: true })\`
|
|
21902
|
+
|
|
21903
|
+
**Dashboard:**
|
|
21904
|
+
1. \`create_document(parentId, "Overview", "dashboard")\`
|
|
21905
|
+
2. Create items: \`create_document(dashboardId, "Revenue Chart")\`
|
|
21906
|
+
3. Position items on grid: \`update_metadata(itemId, { deskX: 0, deskY: 0, deskMode: "widget-lg" })\`
|
|
21907
|
+
4. Modes: \`"icon"\` (small), \`"widget-sm"\` (240×180), \`"widget-lg"\` (400×320)
|
|
21908
|
+
5. Grid uses 80px cells
|
|
21909
|
+
|
|
21910
|
+
**Spatial (3D scene):**
|
|
21911
|
+
1. \`create_document(parentId, "3D Scene", "spatial")\`
|
|
21912
|
+
2. Create objects: \`create_document(sceneId, "Red Cube")\`
|
|
21913
|
+
3. Set 3D properties: \`update_metadata(objId, { spShape: "box", spColor: "#ef4444", spX: 0, spY: 1, spZ: 0, spSX: 2, spSY: 2, spSZ: 2 })\`
|
|
21914
|
+
4. Shapes: \`"box"\`, \`"sphere"\`, \`"cylinder"\`, \`"cone"\`, \`"plane"\`, \`"torus"\`, \`"glb"\` (uploaded 3D model)
|
|
21915
|
+
5. Rotation (degrees): \`spRX\`, \`spRY\`, \`spRZ\`. Scale: \`spSX\`, \`spSY\`, \`spSZ\` (default 1). Opacity: \`spOpacity\` (0–100)
|
|
21916
|
+
|
|
21917
|
+
**Outline (nested items):**
|
|
21918
|
+
1. \`create_document(parentId, "Meeting Notes", "outline")\`
|
|
21919
|
+
2. Create items: \`create_document(outlineId, "Agenda Item 1")\`
|
|
21920
|
+
3. Create sub-items (unlimited depth): \`create_document(itemId, "Sub-point")\`
|
|
21921
|
+
|
|
21922
|
+
---
|
|
21923
|
+
|
|
21924
|
+
## Document References
|
|
21925
|
+
|
|
21926
|
+
You can link and embed documents within rich-text content:
|
|
21927
|
+
|
|
21928
|
+
**Block-level doc embed** — renders an embedded document preview:
|
|
21929
|
+
\`\`\`
|
|
21930
|
+
![[docId]]
|
|
21931
|
+
\`\`\`
|
|
21932
|
+
|
|
21933
|
+
**Inline link to another document:**
|
|
21934
|
+
\`\`\`
|
|
21935
|
+
[Link text](/doc/docId)
|
|
21936
|
+
\`\`\`
|
|
21937
|
+
|
|
21938
|
+
**Inline wikilink** (shorthand — converted to doc link):
|
|
21939
|
+
\`\`\`
|
|
21940
|
+
[[docId]]
|
|
21941
|
+
[[docId|Display Text]]
|
|
21942
|
+
\`\`\`
|
|
21943
|
+
|
|
21944
|
+
Example — write content with references:
|
|
21945
|
+
\`\`\`
|
|
21946
|
+
write_document(docId, "# Project Overview\\n\\nSee the detailed spec:\\n\\n![[specDocId]]\\n\\nCheck the [timeline](/doc/timelineDocId) for dates.")
|
|
21947
|
+
\`\`\`
|
|
21948
|
+
|
|
21949
|
+
In the **graph** page type, document references (embeds and links) create visible edges between nodes when \`showRefEdges\` is enabled.
|
|
21671
21950
|
|
|
21672
21951
|
---
|
|
21673
21952
|
|
|
@@ -21678,7 +21957,7 @@ If you are adding content to an existing space, call \`get_document_tree(rootId:
|
|
|
21678
21957
|
| Key | Type | Meaning |
|
|
21679
21958
|
|-----|------|---------|
|
|
21680
21959
|
| \`color\` | string | Hex/CSS color (e.g. "#6366f1") |
|
|
21681
|
-
| \`icon\` | string | Lucide icon name in kebab-case (e.g. "star", "code-2", "users"
|
|
21960
|
+
| \`icon\` | string | Lucide icon name in kebab-case (e.g. "star", "code-2", "users"). **Never emoji.** Omit for default. |
|
|
21682
21961
|
| \`datetimeStart\` / \`datetimeEnd\` | string | ISO datetime with time |
|
|
21683
21962
|
| \`allDay\` | boolean | Whether datetime is all-day |
|
|
21684
21963
|
| \`dateStart\` / \`dateEnd\` | string | ISO date-only (e.g. "2026-03-20") |
|
|
@@ -21696,45 +21975,86 @@ If you are adding content to an existing space, call \`get_document_tree(rootId:
|
|
|
21696
21975
|
| \`subtitle\` | string | Secondary text |
|
|
21697
21976
|
| \`note\` | string | Free-text note |
|
|
21698
21977
|
| \`taskProgress\` | number | 0–100 progress (timeline tasks) |
|
|
21978
|
+
| \`members\` | { id, label }[] | Assigned users |
|
|
21979
|
+
| \`coverUploadId\` | string | Cover image (upload ID from upload_file) |
|
|
21699
21980
|
|
|
21700
|
-
###
|
|
21981
|
+
### Geo/Map Keys (for map children)
|
|
21982
|
+
|
|
21983
|
+
| Key | Type | Meaning |
|
|
21984
|
+
|-----|------|---------|
|
|
21985
|
+
| \`geoType\` | string | "marker", "line", or "measure" |
|
|
21986
|
+
| \`geoLat\` | number | Latitude |
|
|
21987
|
+
| \`geoLng\` | number | Longitude |
|
|
21988
|
+
| \`geoDescription\` | string | Location description text |
|
|
21701
21989
|
|
|
21702
|
-
|
|
21990
|
+
### Renderer Config Keys (set on the page doc itself, not its children)
|
|
21703
21991
|
|
|
21704
|
-
|
|
21992
|
+
| Key | Type | Applies to | Values |
|
|
21993
|
+
|-----|------|-----------|--------|
|
|
21994
|
+
| \`kanbanColumnWidth\` | string | kanban | "narrow", "default", "wide" |
|
|
21995
|
+
| \`galleryColumns\` | number | gallery | 1–6 |
|
|
21996
|
+
| \`galleryAspect\` | string | gallery | "square", "4:3", "16:9" |
|
|
21997
|
+
| \`calendarView\` | string | calendar | "month", "week", "day" |
|
|
21998
|
+
| \`calendarWeekStart\` | string | calendar | "sun", "mon" |
|
|
21999
|
+
| \`tableMode\` | string | table | "hierarchy", "flat" |
|
|
22000
|
+
| \`showRefEdges\` | boolean | graph | show doc-reference edges |
|
|
21705
22001
|
|
|
21706
|
-
|
|
22002
|
+
### Spatial 3D Keys (for spatial children)
|
|
22003
|
+
|
|
22004
|
+
| Key | Type | Default | Meaning |
|
|
22005
|
+
|-----|------|---------|---------|
|
|
22006
|
+
| \`spShape\` | string | — | "box", "sphere", "cylinder", "cone", "plane", "torus", "glb" |
|
|
22007
|
+
| \`spColor\` | string | — | CSS color |
|
|
22008
|
+
| \`spOpacity\` | number | 100 | 0–100 |
|
|
22009
|
+
| \`spX\`, \`spY\`, \`spZ\` | number | 0 | Position |
|
|
22010
|
+
| \`spRX\`, \`spRY\`, \`spRZ\` | number | 0 | Rotation (degrees) |
|
|
22011
|
+
| \`spSX\`, \`spSY\`, \`spSZ\` | number | 1 | Scale |
|
|
22012
|
+
|
|
22013
|
+
### Dashboard Keys (for dashboard children)
|
|
22014
|
+
|
|
22015
|
+
| Key | Type | Meaning |
|
|
22016
|
+
|-----|------|---------|
|
|
22017
|
+
| \`deskX\`, \`deskY\` | number | Grid position (80px cells) |
|
|
22018
|
+
| \`deskZ\` | number | Z-index (layering) |
|
|
22019
|
+
| \`deskMode\` | string | "icon", "widget-sm" (240×180), "widget-lg" (400×320) |
|
|
21707
22020
|
|
|
21708
22021
|
---
|
|
21709
22022
|
|
|
21710
22023
|
## Content Structure
|
|
21711
22024
|
|
|
21712
|
-
Documents use a TipTap editor schema
|
|
21713
|
-
- **documentHeader** — The document title (H1)
|
|
21714
|
-
- **documentMeta** — Metadata display block (skip when reading/writing)
|
|
21715
|
-
- **Body blocks** — paragraphs, headings (H2-H6), lists, code blocks, tables, images, and custom components
|
|
21716
|
-
|
|
21717
|
-
### Supported Markdown Elements
|
|
22025
|
+
Documents use a TipTap editor schema. The body supports:
|
|
21718
22026
|
- Headings (# through ######)
|
|
21719
|
-
- Paragraphs with **bold**, *italic*, ~~strikethrough~~,
|
|
21720
|
-
- Bullet lists, ordered lists, task lists
|
|
22027
|
+
- Paragraphs with **bold**, *italic*, ~~strikethrough~~, \\\`code\\\`, [links](url)
|
|
22028
|
+
- Bullet lists, ordered lists, task lists (- [x] / - [ ])
|
|
21721
22029
|
- Code blocks with language
|
|
21722
22030
|
- Blockquotes
|
|
21723
22031
|
- Tables (pipe syntax)
|
|
21724
22032
|
- Horizontal rules
|
|
21725
|
-
- Images
|
|
21726
|
-
-
|
|
22033
|
+
- Images: \`{width="..." height="..."}\`
|
|
22034
|
+
- Document embeds: \`![[docId]]\`
|
|
22035
|
+
- Inline doc links: \`[[docId|label]]\` or \`[label](/doc/docId)\`
|
|
22036
|
+
- MDC components: \`::tip\`, \`::note\`, \`::warning\`, \`::collapsible\`, \`::steps\`, \`::card\`, \`::accordion\`, \`::tabs\`, \`::code-group\`, etc.
|
|
22037
|
+
- Badges: \`:badge[Label]{color="..."}\`
|
|
22038
|
+
- Icons: \`:icon{name="star"}\`
|
|
21727
22039
|
|
|
21728
22040
|
### Writing Content with Frontmatter
|
|
21729
22041
|
|
|
21730
|
-
|
|
22042
|
+
Set document title and metadata via YAML frontmatter:
|
|
21731
22043
|
|
|
21732
22044
|
\`\`\`markdown
|
|
21733
22045
|
---
|
|
21734
22046
|
title: My Document
|
|
21735
22047
|
tags: [important, review]
|
|
21736
22048
|
color: "#6366f1"
|
|
22049
|
+
icon: star
|
|
21737
22050
|
priority: high
|
|
22051
|
+
checked: false
|
|
22052
|
+
datetimeStart: "2026-03-20T10:00:00Z"
|
|
22053
|
+
datetimeEnd: "2026-03-20T11:00:00Z"
|
|
22054
|
+
allDay: false
|
|
22055
|
+
geoLat: 37.7749
|
|
22056
|
+
geoLng: -122.4194
|
|
22057
|
+
geoType: marker
|
|
21738
22058
|
---
|
|
21739
22059
|
|
|
21740
22060
|
Content goes here...
|
|
@@ -21756,8 +22076,7 @@ You do **not** need to call \`set_presence\` after reading or writing content
|
|
|
21756
22076
|
|---|---|
|
|
21757
22077
|
| \`read_document\` | Root \`docId\` updated; TipTap cursor placed at start of document |
|
|
21758
22078
|
| \`write_document\` | Root \`docId\` updated; TipTap cursor placed at end of written content |
|
|
21759
|
-
|
|
21760
|
-
The TipTap cursor (\`anchor\`/\`head\` as \`Y.RelativePosition\`) makes your colored caret with name label appear inline in the document editor for human collaborators.
|
|
22079
|
+
| \`create_document\` | Root \`docId\` updated to the new document |
|
|
21761
22080
|
|
|
21762
22081
|
### set_presence — manual overrides only
|
|
21763
22082
|
|
|
@@ -21779,8 +22098,6 @@ Always clear fields when done by setting them to \`null\`.
|
|
|
21779
22098
|
| **Calendar** | \`calendar:focused\` | eventId | Interacting with an event |
|
|
21780
22099
|
| **Calendar** | \`calendar:viewing\` | "YYYY-MM" | The month/week you're looking at |
|
|
21781
22100
|
| **Outline** | \`outline:editing\` | nodeId | Editing an outline node |
|
|
21782
|
-
| **Outline** | \`outline:selected\` | nodeId | Node selected/hovered |
|
|
21783
|
-
| **Slides** | \`slides:viewing\` | slideId | The slide you're currently on |
|
|
21784
22101
|
| **Gallery** | \`gallery:focused\` | itemId | Item hovered/selected |
|
|
21785
22102
|
| **Timeline** | \`timeline:focused\` | taskId | Task selected |
|
|
21786
22103
|
| **Mindmap** | \`mindmap:focused\` | nodeId | Node selected/edited |
|
|
@@ -21788,7 +22105,7 @@ Always clear fields when done by setting them to \`null\`.
|
|
|
21788
22105
|
| **Map** | \`map:focused\` | markerId | Marker hovered/selected |
|
|
21789
22106
|
| **Doc** | \`doc:scroll\` | 0–1 number | Scroll position in document |
|
|
21790
22107
|
|
|
21791
|
-
Example — mark a kanban card as being inspected, then clear
|
|
22108
|
+
Example — mark a kanban card as being inspected, then clear:
|
|
21792
22109
|
\`\`\`
|
|
21793
22110
|
set_doc_awareness(boardDocId, { "kanban:hovering": cardId })
|
|
21794
22111
|
// ... do work ...
|
|
@@ -21799,73 +22116,48 @@ set_doc_awareness(boardDocId, { "kanban:hovering": null })
|
|
|
21799
22116
|
|
|
21800
22117
|
## Receiving Instructions from Humans
|
|
21801
22118
|
|
|
21802
|
-
Three ways humans can send you tasks:
|
|
21803
|
-
|
|
21804
22119
|
**Channel events (preferred):** When running in channel mode, \`ai:task\` events from
|
|
21805
22120
|
human users arrive as \`<channel source="abracadabra" ...>\` notifications directly in
|
|
21806
|
-
your session.
|
|
21807
|
-
|
|
21808
|
-
task completion.
|
|
22121
|
+
your session. Use the \`reply\` tool to send your response — it creates a reply document
|
|
22122
|
+
and signals task completion.
|
|
21809
22123
|
|
|
21810
|
-
**Chat
|
|
21811
|
-
|
|
21812
|
-
with the chat document's ID.
|
|
22124
|
+
**Chat messages:** Chat messages from the platform arrive as channel notifications. Use
|
|
22125
|
+
\`send_chat_message\` to respond in the same channel.
|
|
21813
22126
|
|
|
21814
22127
|
**Polling (fallback):** Call \`poll_inbox\` to read the "AI Inbox" document.
|
|
21815
|
-
Act on any pending tasks you find there. Write your response back with \`write_document\`
|
|
21816
|
-
or \`create_document\` under the inbox.
|
|
21817
|
-
|
|
21818
|
-
### Channel Tools
|
|
21819
|
-
|
|
21820
|
-
| Tool | Purpose |
|
|
21821
|
-
|------|---------|
|
|
21822
|
-
| \`reply\` | Send a reply to Abracadabra — creates a child doc with your response. Pass \`task_id\` to signal ai:task completion. |
|
|
21823
|
-
| \`watch_chat\` | Start/stop watching a document for chat messages. New content arrives as channel notifications. |
|
|
21824
|
-
|
|
21825
|
-
When you complete a task, always use \`reply\` (in channel mode) or write a response
|
|
21826
|
-
document (in polling mode) so the human knows the work is done.
|
|
21827
22128
|
|
|
21828
22129
|
---
|
|
21829
22130
|
|
|
21830
22131
|
## Common Patterns
|
|
21831
22132
|
|
|
21832
|
-
### Populate a kanban board from a list
|
|
21833
|
-
\`\`\`
|
|
21834
|
-
1. create_document → kanban parent
|
|
21835
|
-
2. For each column: create_document under kanban
|
|
21836
|
-
3. For each card: create_document under the appropriate column
|
|
21837
|
-
4. Optionally set metadata (color, tags, priority) on cards
|
|
21838
|
-
\`\`\`
|
|
21839
|
-
|
|
21840
22133
|
### Explore a document fully (the correct way)
|
|
21841
22134
|
\`\`\`
|
|
21842
|
-
|
|
21843
|
-
|
|
21844
|
-
1. read_document(marketingId)
|
|
22135
|
+
1. read_document(docId)
|
|
21845
22136
|
→ returns { markdown, children: [...] }
|
|
21846
22137
|
2. The body may be empty — that does NOT mean the doc is empty.
|
|
21847
22138
|
Children ARE the content. Read every child:
|
|
21848
22139
|
read_document(childId1), read_document(childId2), ...
|
|
21849
|
-
3.
|
|
21850
|
-
Continue recursively until no children remain.
|
|
22140
|
+
3. Continue recursively until no children remain.
|
|
21851
22141
|
\`\`\`
|
|
21852
22142
|
|
|
21853
22143
|
**Never conclude a document is "empty" just because its markdown body is empty.
|
|
21854
22144
|
Always check and traverse the \`children\` array returned by \`read_document\`.**
|
|
21855
22145
|
|
|
21856
|
-
###
|
|
22146
|
+
### Populate a kanban board from a list
|
|
21857
22147
|
\`\`\`
|
|
21858
|
-
1.
|
|
21859
|
-
2.
|
|
21860
|
-
3.
|
|
22148
|
+
1. create_document → kanban parent
|
|
22149
|
+
2. For each column: create_document under kanban
|
|
22150
|
+
3. For each card: create_document under the appropriate column
|
|
22151
|
+
4. Optionally set metadata (color, tags, priority) on cards
|
|
21861
22152
|
\`\`\`
|
|
21862
22153
|
|
|
21863
|
-
###
|
|
22154
|
+
### Write rich content with references
|
|
21864
22155
|
\`\`\`
|
|
21865
|
-
1.
|
|
21866
|
-
2.
|
|
21867
|
-
3.
|
|
21868
|
-
4.
|
|
22156
|
+
1. read_document to see current content (and children)
|
|
22157
|
+
2. write_document with markdown (mode: "replace" or "append")
|
|
22158
|
+
3. Use ![[docId]] to embed other documents
|
|
22159
|
+
4. Use [[docId|label]] for inline doc links
|
|
22160
|
+
5. Include frontmatter for title/metadata if needed
|
|
21869
22161
|
\`\`\`
|
|
21870
22162
|
|
|
21871
22163
|
---
|
|
@@ -21891,6 +22183,7 @@ Always check and traverse the \`children\` array returned by \`read_document\`.*
|
|
|
21891
22183
|
- Use emoji as \`icon\` values — only lowercase kebab-case Lucide icon names are valid
|
|
21892
22184
|
- Create top-level documents without first confirming the correct hub doc ID
|
|
21893
22185
|
- Rename or write content to the space hub document itself
|
|
22186
|
+
- Set \`type\` on child items (cards, cells, events) — only set type on the parent page
|
|
21894
22187
|
`;
|
|
21895
22188
|
function registerAgentGuide(mcp) {
|
|
21896
22189
|
mcp.resource("agent-guide", "abracadabra://agent-guide", {
|
|
@@ -21983,6 +22276,221 @@ function registerServerInfoResource(mcp, server) {
|
|
|
21983
22276
|
});
|
|
21984
22277
|
}
|
|
21985
22278
|
|
|
22279
|
+
//#endregion
|
|
22280
|
+
//#region packages/mcp/src/tools/hooks.ts
|
|
22281
|
+
function registerHookTools(mcp, hookBridge) {
|
|
22282
|
+
mcp.tool("get_hook_config", "Returns Claude Code hook configuration JSON for bridging activity to the Abracadabra dashboard. Copy the \"hooks\" object into your .claude/settings.local.json to enable real-time activity indicators (tool calls, subagents, etc.) visible to all connected users.", {}, async () => {
|
|
22283
|
+
const port = hookBridge.port;
|
|
22284
|
+
if (!port) return {
|
|
22285
|
+
content: [{
|
|
22286
|
+
type: "text",
|
|
22287
|
+
text: "Hook bridge is not running."
|
|
22288
|
+
}],
|
|
22289
|
+
isError: true
|
|
22290
|
+
};
|
|
22291
|
+
const hookEntry = {
|
|
22292
|
+
type: "http",
|
|
22293
|
+
url: `http://127.0.0.1:${port}/hook`,
|
|
22294
|
+
timeout: 3
|
|
22295
|
+
};
|
|
22296
|
+
const config = { hooks: {
|
|
22297
|
+
PreToolUse: [{ hooks: [hookEntry] }],
|
|
22298
|
+
PostToolUse: [{ hooks: [hookEntry] }],
|
|
22299
|
+
SubagentStart: [{ hooks: [hookEntry] }],
|
|
22300
|
+
SubagentStop: [{ hooks: [hookEntry] }],
|
|
22301
|
+
Stop: [{ hooks: [hookEntry] }]
|
|
22302
|
+
} };
|
|
22303
|
+
return { content: [{
|
|
22304
|
+
type: "text",
|
|
22305
|
+
text: JSON.stringify(config, null, 2)
|
|
22306
|
+
}] };
|
|
22307
|
+
});
|
|
22308
|
+
}
|
|
22309
|
+
|
|
22310
|
+
//#endregion
|
|
22311
|
+
//#region packages/mcp/src/hook-bridge.ts
|
|
22312
|
+
/**
|
|
22313
|
+
* HookBridge — lightweight HTTP server that receives Claude Code hook events
|
|
22314
|
+
* and translates them into Yjs awareness updates via AbracadabraMCPServer.
|
|
22315
|
+
*
|
|
22316
|
+
* Claude Code hooks (PreToolUse, PostToolUse, SubagentStart, SubagentStop, Stop)
|
|
22317
|
+
* POST JSON to http://127.0.0.1:{port}/hook. The bridge maps these to awareness
|
|
22318
|
+
* fields (status, activeToolCall) so the cou-sh dashboard shows real-time activity.
|
|
22319
|
+
*/
|
|
22320
|
+
/** Map Claude Code tool names to awareness-friendly names + extract a target string. */
|
|
22321
|
+
function mapToolCall(toolName, toolInput) {
|
|
22322
|
+
switch (toolName) {
|
|
22323
|
+
case "Bash": return {
|
|
22324
|
+
name: "bash",
|
|
22325
|
+
target: truncate(toolInput.command ?? toolInput.description, 60)
|
|
22326
|
+
};
|
|
22327
|
+
case "Read": return {
|
|
22328
|
+
name: "read_file",
|
|
22329
|
+
target: basename(toolInput.file_path)
|
|
22330
|
+
};
|
|
22331
|
+
case "Edit": return {
|
|
22332
|
+
name: "edit_file",
|
|
22333
|
+
target: basename(toolInput.file_path)
|
|
22334
|
+
};
|
|
22335
|
+
case "Write": return {
|
|
22336
|
+
name: "write_file",
|
|
22337
|
+
target: basename(toolInput.file_path)
|
|
22338
|
+
};
|
|
22339
|
+
case "Grep": return {
|
|
22340
|
+
name: "grep",
|
|
22341
|
+
target: truncate(toolInput.pattern, 40)
|
|
22342
|
+
};
|
|
22343
|
+
case "Glob": return {
|
|
22344
|
+
name: "glob",
|
|
22345
|
+
target: truncate(toolInput.pattern, 40)
|
|
22346
|
+
};
|
|
22347
|
+
case "Agent": return {
|
|
22348
|
+
name: "subagent",
|
|
22349
|
+
target: toolInput.description || toolInput.subagent_type || "agent"
|
|
22350
|
+
};
|
|
22351
|
+
case "WebFetch": return {
|
|
22352
|
+
name: "web_fetch",
|
|
22353
|
+
target: hostname(toolInput.url)
|
|
22354
|
+
};
|
|
22355
|
+
case "WebSearch": return {
|
|
22356
|
+
name: "web_search",
|
|
22357
|
+
target: truncate(toolInput.query, 40)
|
|
22358
|
+
};
|
|
22359
|
+
default: return { name: toolName.replace(/([a-z])([A-Z])/g, "$1_$2").toLowerCase() };
|
|
22360
|
+
}
|
|
22361
|
+
}
|
|
22362
|
+
function truncate(str, max) {
|
|
22363
|
+
if (!str) return void 0;
|
|
22364
|
+
return str.length > max ? str.slice(0, max) + "..." : str;
|
|
22365
|
+
}
|
|
22366
|
+
function basename(filePath) {
|
|
22367
|
+
if (!filePath) return void 0;
|
|
22368
|
+
return path.basename(filePath);
|
|
22369
|
+
}
|
|
22370
|
+
function hostname(url) {
|
|
22371
|
+
if (!url) return void 0;
|
|
22372
|
+
try {
|
|
22373
|
+
return new URL(url).hostname;
|
|
22374
|
+
} catch {
|
|
22375
|
+
return url.slice(0, 30);
|
|
22376
|
+
}
|
|
22377
|
+
}
|
|
22378
|
+
var HookBridge = class {
|
|
22379
|
+
constructor(server) {
|
|
22380
|
+
this.server = server;
|
|
22381
|
+
this.httpServer = null;
|
|
22382
|
+
this._port = null;
|
|
22383
|
+
this.portFilePath = process.env.ABRA_HOOK_PORT_FILE || path.join(os.tmpdir(), "abracadabra-mcp-hook.port");
|
|
22384
|
+
}
|
|
22385
|
+
get port() {
|
|
22386
|
+
return this._port;
|
|
22387
|
+
}
|
|
22388
|
+
/** Start the HTTP server on a random port and write the port file. */
|
|
22389
|
+
async start() {
|
|
22390
|
+
return new Promise((resolve, reject) => {
|
|
22391
|
+
const srv = http.createServer((req, res) => this.handleRequest(req, res));
|
|
22392
|
+
srv.on("error", reject);
|
|
22393
|
+
srv.listen(0, "127.0.0.1", () => {
|
|
22394
|
+
const addr = srv.address();
|
|
22395
|
+
if (!addr || typeof addr === "string") {
|
|
22396
|
+
reject(/* @__PURE__ */ new Error("Failed to get server address"));
|
|
22397
|
+
return;
|
|
22398
|
+
}
|
|
22399
|
+
this._port = addr.port;
|
|
22400
|
+
this.httpServer = srv;
|
|
22401
|
+
try {
|
|
22402
|
+
fs.writeFileSync(this.portFilePath, String(this._port), "utf-8");
|
|
22403
|
+
} catch (err) {
|
|
22404
|
+
console.error(`[hook-bridge] Warning: could not write port file: ${err.message}`);
|
|
22405
|
+
}
|
|
22406
|
+
console.error(`[hook-bridge] Listening on 127.0.0.1:${this._port}`);
|
|
22407
|
+
console.error(`[hook-bridge] Port file: ${this.portFilePath}`);
|
|
22408
|
+
resolve(this._port);
|
|
22409
|
+
});
|
|
22410
|
+
});
|
|
22411
|
+
}
|
|
22412
|
+
/** Shut down the HTTP server and remove the port file. */
|
|
22413
|
+
async destroy() {
|
|
22414
|
+
if (this.httpServer) {
|
|
22415
|
+
await new Promise((resolve) => {
|
|
22416
|
+
this.httpServer.close(() => resolve());
|
|
22417
|
+
});
|
|
22418
|
+
this.httpServer = null;
|
|
22419
|
+
}
|
|
22420
|
+
try {
|
|
22421
|
+
fs.unlinkSync(this.portFilePath);
|
|
22422
|
+
} catch {}
|
|
22423
|
+
this._port = null;
|
|
22424
|
+
console.error("[hook-bridge] Shut down");
|
|
22425
|
+
}
|
|
22426
|
+
handleRequest(req, res) {
|
|
22427
|
+
if (req.method !== "POST" || req.url !== "/hook") {
|
|
22428
|
+
res.writeHead(404);
|
|
22429
|
+
res.end();
|
|
22430
|
+
return;
|
|
22431
|
+
}
|
|
22432
|
+
let body = "";
|
|
22433
|
+
req.on("data", (chunk) => {
|
|
22434
|
+
body += chunk.toString();
|
|
22435
|
+
});
|
|
22436
|
+
req.on("end", () => {
|
|
22437
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
22438
|
+
res.end("{}");
|
|
22439
|
+
try {
|
|
22440
|
+
const payload = JSON.parse(body);
|
|
22441
|
+
this.routeEvent(payload);
|
|
22442
|
+
} catch {}
|
|
22443
|
+
});
|
|
22444
|
+
}
|
|
22445
|
+
routeEvent(payload) {
|
|
22446
|
+
switch (payload.hook_event_name) {
|
|
22447
|
+
case "PreToolUse":
|
|
22448
|
+
this.onPreToolUse(payload);
|
|
22449
|
+
break;
|
|
22450
|
+
case "PostToolUse":
|
|
22451
|
+
this.onPostToolUse(payload);
|
|
22452
|
+
break;
|
|
22453
|
+
case "SubagentStart":
|
|
22454
|
+
this.onSubagentStart(payload);
|
|
22455
|
+
break;
|
|
22456
|
+
case "SubagentStop":
|
|
22457
|
+
this.onSubagentStop(payload);
|
|
22458
|
+
break;
|
|
22459
|
+
case "Stop":
|
|
22460
|
+
this.onStop();
|
|
22461
|
+
break;
|
|
22462
|
+
}
|
|
22463
|
+
}
|
|
22464
|
+
onPreToolUse(payload) {
|
|
22465
|
+
const toolName = payload.tool_name ?? "";
|
|
22466
|
+
if (toolName.startsWith("mcp__abracadabra__")) return;
|
|
22467
|
+
const mapped = mapToolCall(toolName, payload.tool_input ?? {});
|
|
22468
|
+
if (mapped) {
|
|
22469
|
+
this.server.setActiveToolCall(mapped);
|
|
22470
|
+
this.server.setAutoStatus("working");
|
|
22471
|
+
}
|
|
22472
|
+
}
|
|
22473
|
+
onPostToolUse(payload) {
|
|
22474
|
+
if ((payload.tool_name ?? "").startsWith("mcp__abracadabra__")) return;
|
|
22475
|
+
this.server.setActiveToolCall(null);
|
|
22476
|
+
}
|
|
22477
|
+
onSubagentStart(payload) {
|
|
22478
|
+
const agentType = payload.agent_type ?? "agent";
|
|
22479
|
+
this.server.setActiveToolCall({
|
|
22480
|
+
name: "subagent",
|
|
22481
|
+
target: agentType
|
|
22482
|
+
});
|
|
22483
|
+
this.server.setAutoStatus("thinking");
|
|
22484
|
+
}
|
|
22485
|
+
onSubagentStop(_payload) {
|
|
22486
|
+
this.server.setActiveToolCall(null);
|
|
22487
|
+
}
|
|
22488
|
+
onStop() {
|
|
22489
|
+
this.server.setAutoStatus(null);
|
|
22490
|
+
this.server.setActiveToolCall(null);
|
|
22491
|
+
}
|
|
22492
|
+
};
|
|
22493
|
+
|
|
21986
22494
|
//#endregion
|
|
21987
22495
|
//#region packages/mcp/src/index.ts
|
|
21988
22496
|
/**
|
|
@@ -22024,8 +22532,9 @@ async function main() {
|
|
|
22024
22532
|
## Key Concepts
|
|
22025
22533
|
- Documents form a tree. A kanban board's columns are child documents; cards are grandchildren.
|
|
22026
22534
|
- A document's label IS its display name everywhere. Children ARE the content (not just the body text).
|
|
22027
|
-
- Page types (doc, kanban, table, calendar, timeline, outline, etc.) are views over the SAME tree — switching types preserves data.
|
|
22535
|
+
- Page types (doc, kanban, table, calendar, timeline, checklist, outline, gallery, map, graph, dashboard, spatial, media, mindmap, etc.) are views over the SAME tree — switching types preserves data.
|
|
22028
22536
|
- An empty markdown body does NOT mean empty content — always check the children array.
|
|
22537
|
+
- Use ![[docId]] in content to embed another document, or [[docId|label]] for inline links.
|
|
22029
22538
|
|
|
22030
22539
|
## Finding Documents
|
|
22031
22540
|
- list_documents only shows ONE level of children. If you don't find what you need, use find_document to search the entire tree by name, or get_document_tree to see the full hierarchy.
|
|
@@ -22062,12 +22571,21 @@ Read the resource at abracadabra://agent-guide for the complete guide covering p
|
|
|
22062
22571
|
console.error(`[abracadabra-mcp] Failed to connect: ${error.message}`);
|
|
22063
22572
|
process.exit(1);
|
|
22064
22573
|
}
|
|
22574
|
+
const hookBridge = new HookBridge(server);
|
|
22575
|
+
try {
|
|
22576
|
+
const hookPort = await hookBridge.start();
|
|
22577
|
+
console.error(`[abracadabra-mcp] Hook bridge listening on port ${hookPort}`);
|
|
22578
|
+
} catch (error) {
|
|
22579
|
+
console.error(`[abracadabra-mcp] Hook bridge failed to start: ${error.message}`);
|
|
22580
|
+
}
|
|
22581
|
+
registerHookTools(mcp, hookBridge);
|
|
22065
22582
|
const transport = new StdioServerTransport();
|
|
22066
22583
|
await mcp.connect(transport);
|
|
22067
22584
|
server.startChannelNotifications(mcp);
|
|
22068
22585
|
console.error("[abracadabra-mcp] MCP server running on stdio");
|
|
22069
22586
|
const shutdown = async () => {
|
|
22070
22587
|
console.error("[abracadabra-mcp] Shutting down...");
|
|
22588
|
+
await hookBridge.destroy();
|
|
22071
22589
|
await server.destroy();
|
|
22072
22590
|
process.exit(0);
|
|
22073
22591
|
};
|