@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
package/dist/abracadabra-mcp.cjs
CHANGED
|
@@ -38,8 +38,11 @@ let node_fs_promises = require("node:fs/promises");
|
|
|
38
38
|
let node_fs = require("node:fs");
|
|
39
39
|
node_fs = __toESM(node_fs);
|
|
40
40
|
let node_os = require("node:os");
|
|
41
|
+
node_os = __toESM(node_os);
|
|
41
42
|
let node_path = require("node:path");
|
|
42
43
|
node_path = __toESM(node_path);
|
|
44
|
+
let node_http = require("node:http");
|
|
45
|
+
node_http = __toESM(node_http);
|
|
43
46
|
|
|
44
47
|
//#region node_modules/zod/v3/helpers/util.js
|
|
45
48
|
var util;
|
|
@@ -4064,7 +4067,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-
|
|
|
4064
4067
|
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])$/;
|
|
4065
4068
|
const base64 = /^$|^(?:[0-9a-zA-Z+/]{4})*(?:(?:[0-9a-zA-Z+/]{2}==)|(?:[0-9a-zA-Z+/]{3}=))?$/;
|
|
4066
4069
|
const base64url = /^[A-Za-z0-9_-]*$/;
|
|
4067
|
-
const hostname = /^([a-zA-Z0-9-]+\.)*[a-zA-Z0-9-]+$/;
|
|
4070
|
+
const hostname$1 = /^([a-zA-Z0-9-]+\.)*[a-zA-Z0-9-]+$/;
|
|
4068
4071
|
const e164 = /^\+(?:[0-9]){6,14}[0-9]$/;
|
|
4069
4072
|
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])))`;
|
|
4070
4073
|
const date$1 = /* @__PURE__ */ new RegExp(`^${dateSource}$`);
|
|
@@ -4611,7 +4614,7 @@ const $ZodURL = /* @__PURE__ */ $constructor("$ZodURL", (inst, def) => {
|
|
|
4611
4614
|
code: "invalid_format",
|
|
4612
4615
|
format: "url",
|
|
4613
4616
|
note: "Invalid hostname",
|
|
4614
|
-
pattern: hostname.source,
|
|
4617
|
+
pattern: hostname$1.source,
|
|
4615
4618
|
input: payload.value,
|
|
4616
4619
|
inst,
|
|
4617
4620
|
continue: !def.abort
|
|
@@ -19446,6 +19449,9 @@ var AbracadabraMCPServer = class {
|
|
|
19446
19449
|
this._serverRef = null;
|
|
19447
19450
|
this._handledTaskIds = /* @__PURE__ */ new Set();
|
|
19448
19451
|
this._userId = null;
|
|
19452
|
+
this._statusClearTimer = null;
|
|
19453
|
+
this._typingInterval = null;
|
|
19454
|
+
this._lastChatChannel = null;
|
|
19449
19455
|
this.config = config;
|
|
19450
19456
|
this.client = new _abraca_dabra.AbracadabraClient({
|
|
19451
19457
|
url: config.url,
|
|
@@ -19540,8 +19546,12 @@ var AbracadabraMCPServer = class {
|
|
|
19540
19546
|
provider.awareness.setLocalStateField("user", {
|
|
19541
19547
|
name: this.agentName,
|
|
19542
19548
|
color: this.agentColor,
|
|
19543
|
-
publicKey: this._userId
|
|
19549
|
+
publicKey: this._userId,
|
|
19550
|
+
isAgent: true
|
|
19544
19551
|
});
|
|
19552
|
+
provider.awareness.setLocalStateField("status", null);
|
|
19553
|
+
provider.awareness.setLocalStateField("activeToolCall", null);
|
|
19554
|
+
provider.awareness.setLocalStateField("statusContext", null);
|
|
19545
19555
|
const conn = {
|
|
19546
19556
|
doc,
|
|
19547
19557
|
provider,
|
|
@@ -19593,7 +19603,8 @@ var AbracadabraMCPServer = class {
|
|
|
19593
19603
|
childProvider.awareness.setLocalStateField("user", {
|
|
19594
19604
|
name: this.agentName,
|
|
19595
19605
|
color: this.agentColor,
|
|
19596
|
-
publicKey: this._userId
|
|
19606
|
+
publicKey: this._userId,
|
|
19607
|
+
isAgent: true
|
|
19597
19608
|
});
|
|
19598
19609
|
this.childCache.set(docId, {
|
|
19599
19610
|
provider: childProvider,
|
|
@@ -19660,6 +19671,7 @@ var AbracadabraMCPServer = class {
|
|
|
19660
19671
|
const user = state["user"];
|
|
19661
19672
|
const senderName = user && typeof user === "object" && typeof user.name === "string" ? user.name : "Unknown";
|
|
19662
19673
|
console.error(`[abracadabra-mcp] Handling ai:task id=${id} from ${senderName}: ${text.slice(0, 80)}`);
|
|
19674
|
+
this.setAutoStatus("thinking");
|
|
19663
19675
|
this._dispatchAiTask({
|
|
19664
19676
|
id,
|
|
19665
19677
|
text,
|
|
@@ -19724,6 +19736,18 @@ var AbracadabraMCPServer = class {
|
|
|
19724
19736
|
const parts = channel.split(":");
|
|
19725
19737
|
if (parts.length === 3 && parts[1] !== this._userId && parts[2] !== this._userId) return;
|
|
19726
19738
|
}
|
|
19739
|
+
if (channel) {
|
|
19740
|
+
const rootProvider = this._activeConnection?.provider;
|
|
19741
|
+
if (rootProvider) rootProvider.sendStateless(JSON.stringify({
|
|
19742
|
+
type: "chat:mark_read",
|
|
19743
|
+
channel,
|
|
19744
|
+
timestamp: Math.floor(Date.now() / 1e3)
|
|
19745
|
+
}));
|
|
19746
|
+
this._lastChatChannel = channel;
|
|
19747
|
+
this.sendTypingIndicator(channel);
|
|
19748
|
+
this._startTypingInterval(channel);
|
|
19749
|
+
}
|
|
19750
|
+
this.setAutoStatus("thinking");
|
|
19727
19751
|
await this._serverRef.notification({
|
|
19728
19752
|
method: "notifications/claude/channel",
|
|
19729
19753
|
params: {
|
|
@@ -19741,15 +19765,82 @@ var AbracadabraMCPServer = class {
|
|
|
19741
19765
|
console.error(`[abracadabra-mcp] Chat message from ${data.sender_name ?? "unknown"} on ${channel}`);
|
|
19742
19766
|
} catch {}
|
|
19743
19767
|
}
|
|
19768
|
+
/**
|
|
19769
|
+
* Set the agent's status in root awareness with auto-clear after idle.
|
|
19770
|
+
* @param statusContext — scopes the status to a specific channel/context so the
|
|
19771
|
+
* dashboard only shows it in the relevant chat. Defaults to `_lastChatChannel`.
|
|
19772
|
+
*/
|
|
19773
|
+
setAutoStatus(status, docId, statusContext) {
|
|
19774
|
+
const provider = this._activeConnection?.provider;
|
|
19775
|
+
if (!provider) return;
|
|
19776
|
+
if (this._statusClearTimer) {
|
|
19777
|
+
clearTimeout(this._statusClearTimer);
|
|
19778
|
+
this._statusClearTimer = null;
|
|
19779
|
+
}
|
|
19780
|
+
provider.awareness.setLocalStateField("status", status);
|
|
19781
|
+
if (docId !== void 0) provider.awareness.setLocalStateField("docId", docId);
|
|
19782
|
+
const context = status ? statusContext !== void 0 ? statusContext : this._lastChatChannel : null;
|
|
19783
|
+
provider.awareness.setLocalStateField("statusContext", context ?? null);
|
|
19784
|
+
if (!status) this._stopTypingInterval();
|
|
19785
|
+
if (status) this._statusClearTimer = setTimeout(() => {
|
|
19786
|
+
provider.awareness.setLocalStateField("status", null);
|
|
19787
|
+
provider.awareness.setLocalStateField("activeToolCall", null);
|
|
19788
|
+
provider.awareness.setLocalStateField("statusContext", null);
|
|
19789
|
+
this._stopTypingInterval();
|
|
19790
|
+
}, 1e4);
|
|
19791
|
+
}
|
|
19792
|
+
/** Re-send typing indicator every 2s so dashboard keeps showing it (expires at 3s). */
|
|
19793
|
+
_startTypingInterval(channel) {
|
|
19794
|
+
this._stopTypingInterval();
|
|
19795
|
+
this._typingInterval = setInterval(() => {
|
|
19796
|
+
this.sendTypingIndicator(channel);
|
|
19797
|
+
}, 2e3);
|
|
19798
|
+
}
|
|
19799
|
+
_stopTypingInterval() {
|
|
19800
|
+
if (this._typingInterval) {
|
|
19801
|
+
clearInterval(this._typingInterval);
|
|
19802
|
+
this._typingInterval = null;
|
|
19803
|
+
}
|
|
19804
|
+
this._lastChatChannel = null;
|
|
19805
|
+
}
|
|
19806
|
+
/**
|
|
19807
|
+
* Broadcast which tool the agent is currently executing.
|
|
19808
|
+
* Dashboard renders this as a ChatTool indicator.
|
|
19809
|
+
*/
|
|
19810
|
+
setActiveToolCall(toolCall) {
|
|
19811
|
+
this._activeConnection?.provider?.awareness.setLocalStateField("activeToolCall", toolCall);
|
|
19812
|
+
}
|
|
19813
|
+
/**
|
|
19814
|
+
* Send a typing indicator to a chat channel.
|
|
19815
|
+
*/
|
|
19816
|
+
sendTypingIndicator(channel) {
|
|
19817
|
+
const rootProvider = this._activeConnection?.provider;
|
|
19818
|
+
if (!rootProvider) return;
|
|
19819
|
+
rootProvider.sendStateless(JSON.stringify({
|
|
19820
|
+
type: "chat:typing",
|
|
19821
|
+
channel,
|
|
19822
|
+
sender_name: this.agentName
|
|
19823
|
+
}));
|
|
19824
|
+
}
|
|
19744
19825
|
/** Graceful shutdown. */
|
|
19745
19826
|
async destroy() {
|
|
19827
|
+
this._stopTypingInterval();
|
|
19828
|
+
if (this._statusClearTimer) {
|
|
19829
|
+
clearTimeout(this._statusClearTimer);
|
|
19830
|
+
this._statusClearTimer = null;
|
|
19831
|
+
}
|
|
19746
19832
|
if (this.evictionTimer) {
|
|
19747
19833
|
clearInterval(this.evictionTimer);
|
|
19748
19834
|
this.evictionTimer = null;
|
|
19749
19835
|
}
|
|
19750
19836
|
for (const [, cached] of this.childCache) cached.provider.destroy();
|
|
19751
19837
|
this.childCache.clear();
|
|
19752
|
-
for (const [, conn] of this._spaceConnections)
|
|
19838
|
+
for (const [, conn] of this._spaceConnections) {
|
|
19839
|
+
conn.provider.awareness.setLocalStateField("status", null);
|
|
19840
|
+
conn.provider.awareness.setLocalStateField("activeToolCall", null);
|
|
19841
|
+
conn.provider.awareness.setLocalStateField("statusContext", null);
|
|
19842
|
+
conn.provider.destroy();
|
|
19843
|
+
}
|
|
19753
19844
|
this._spaceConnections.clear();
|
|
19754
19845
|
this._activeConnection = null;
|
|
19755
19846
|
console.error("[abracadabra-mcp] Shutdown complete");
|
|
@@ -19891,15 +19982,23 @@ function registerTreeTools(mcp, server) {
|
|
|
19891
19982
|
mcp.tool("create_document", "Create a new document in the tree. Returns the new document ID.", {
|
|
19892
19983
|
parentId: zod.z.string().optional().describe("Parent document ID. Omit for top-level pages. Use a document ID for nested/child pages."),
|
|
19893
19984
|
label: zod.z.string().describe("Display name / title for the document."),
|
|
19894
|
-
type: zod.z.string().optional().describe("Page type
|
|
19985
|
+
type: zod.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."),
|
|
19895
19986
|
meta: zod.z.record(zod.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.")
|
|
19896
19987
|
}, async ({ parentId, label, type, meta }) => {
|
|
19988
|
+
server.setAutoStatus("creating");
|
|
19989
|
+
server.setActiveToolCall({
|
|
19990
|
+
name: "create_document",
|
|
19991
|
+
target: label
|
|
19992
|
+
});
|
|
19897
19993
|
const treeMap = server.getTreeMap();
|
|
19898
19994
|
const rootDoc = server.rootDocument;
|
|
19899
|
-
if (!treeMap || !rootDoc)
|
|
19900
|
-
|
|
19901
|
-
|
|
19902
|
-
|
|
19995
|
+
if (!treeMap || !rootDoc) {
|
|
19996
|
+
server.setActiveToolCall(null);
|
|
19997
|
+
return { content: [{
|
|
19998
|
+
type: "text",
|
|
19999
|
+
text: "Not connected"
|
|
20000
|
+
}] };
|
|
20001
|
+
}
|
|
19903
20002
|
const id = crypto.randomUUID();
|
|
19904
20003
|
const normalizedParent = normalizeRootId(parentId, server);
|
|
19905
20004
|
const now = Date.now();
|
|
@@ -19914,6 +20013,8 @@ function registerTreeTools(mcp, server) {
|
|
|
19914
20013
|
updatedAt: now
|
|
19915
20014
|
});
|
|
19916
20015
|
});
|
|
20016
|
+
server.setFocusedDoc(id);
|
|
20017
|
+
server.setActiveToolCall(null);
|
|
19917
20018
|
return { content: [{
|
|
19918
20019
|
type: "text",
|
|
19919
20020
|
text: JSON.stringify({
|
|
@@ -20068,6 +20169,7 @@ function registerTreeTools(mcp, server) {
|
|
|
20068
20169
|
label: (entry.label || "Untitled") + " (copy)",
|
|
20069
20170
|
order: Date.now()
|
|
20070
20171
|
});
|
|
20172
|
+
server.setFocusedDoc(newId);
|
|
20071
20173
|
return { content: [{
|
|
20072
20174
|
type: "text",
|
|
20073
20175
|
text: JSON.stringify({
|
|
@@ -20153,11 +20255,44 @@ function parseFrontmatter(markdown) {
|
|
|
20153
20255
|
if (subtitle) meta.subtitle = subtitle;
|
|
20154
20256
|
const url = getStr(["url"]);
|
|
20155
20257
|
if (url) meta.url = url;
|
|
20258
|
+
const email = getStr(["email"]);
|
|
20259
|
+
if (email) meta.email = email;
|
|
20260
|
+
const phone = getStr(["phone"]);
|
|
20261
|
+
if (phone) meta.phone = phone;
|
|
20156
20262
|
const ratingRaw = getStr(["rating"]);
|
|
20157
20263
|
if (ratingRaw !== void 0) {
|
|
20158
20264
|
const n = Number(ratingRaw);
|
|
20159
20265
|
if (!Number.isNaN(n)) meta.rating = Math.min(5, Math.max(0, n));
|
|
20160
20266
|
}
|
|
20267
|
+
const datetimeStart = getStr(["datetimeStart"]);
|
|
20268
|
+
if (datetimeStart) meta.datetimeStart = datetimeStart;
|
|
20269
|
+
const datetimeEnd = getStr(["datetimeEnd"]);
|
|
20270
|
+
if (datetimeEnd) meta.datetimeEnd = datetimeEnd;
|
|
20271
|
+
const allDayRaw = raw["allDay"];
|
|
20272
|
+
if (allDayRaw !== void 0) meta.allDay = allDayRaw === "true" || allDayRaw === true;
|
|
20273
|
+
const geoLatRaw = getStr(["geoLat"]);
|
|
20274
|
+
if (geoLatRaw !== void 0) {
|
|
20275
|
+
const n = Number(geoLatRaw);
|
|
20276
|
+
if (!Number.isNaN(n)) meta.geoLat = n;
|
|
20277
|
+
}
|
|
20278
|
+
const geoLngRaw = getStr(["geoLng"]);
|
|
20279
|
+
if (geoLngRaw !== void 0) {
|
|
20280
|
+
const n = Number(geoLngRaw);
|
|
20281
|
+
if (!Number.isNaN(n)) meta.geoLng = n;
|
|
20282
|
+
}
|
|
20283
|
+
const geoType = getStr(["geoType"]);
|
|
20284
|
+
if (geoType && (geoType === "marker" || geoType === "line" || geoType === "measure")) meta.geoType = geoType;
|
|
20285
|
+
const geoDescription = getStr(["geoDescription"]);
|
|
20286
|
+
if (geoDescription) meta.geoDescription = geoDescription;
|
|
20287
|
+
const numberRaw = getStr(["number"]);
|
|
20288
|
+
if (numberRaw !== void 0) {
|
|
20289
|
+
const n = Number(numberRaw);
|
|
20290
|
+
if (!Number.isNaN(n)) meta.number = n;
|
|
20291
|
+
}
|
|
20292
|
+
const unit = getStr(["unit"]);
|
|
20293
|
+
if (unit) meta.unit = unit;
|
|
20294
|
+
const note = getStr(["note"]);
|
|
20295
|
+
if (note) meta.note = note;
|
|
20161
20296
|
return {
|
|
20162
20297
|
title: typeof raw["title"] === "string" ? raw["title"] : void 0,
|
|
20163
20298
|
meta,
|
|
@@ -20195,8 +20330,12 @@ function parseInline(text) {
|
|
|
20195
20330
|
attrs: { kbd: { value: kbdProps["value"] || "" } }
|
|
20196
20331
|
});
|
|
20197
20332
|
} else if (match[5] !== void 0) {
|
|
20198
|
-
const
|
|
20199
|
-
|
|
20333
|
+
const docId = match[5];
|
|
20334
|
+
const displayText = match[6] ?? docId;
|
|
20335
|
+
tokens.push({
|
|
20336
|
+
text: displayText,
|
|
20337
|
+
attrs: { link: { href: `/doc/${docId}` } }
|
|
20338
|
+
});
|
|
20200
20339
|
} else if (match[7] !== void 0) tokens.push({
|
|
20201
20340
|
text: match[7],
|
|
20202
20341
|
attrs: { strike: true }
|
|
@@ -20345,6 +20484,15 @@ function parseBlocks(markdown) {
|
|
|
20345
20484
|
i++;
|
|
20346
20485
|
continue;
|
|
20347
20486
|
}
|
|
20487
|
+
const docEmbedMatch = line.match(/^!\[\[([^\]|]+?)(?:\|[^\]]*?)?\]\]\s*$/);
|
|
20488
|
+
if (docEmbedMatch) {
|
|
20489
|
+
blocks.push({
|
|
20490
|
+
type: "docEmbed",
|
|
20491
|
+
docId: docEmbedMatch[1]
|
|
20492
|
+
});
|
|
20493
|
+
i++;
|
|
20494
|
+
continue;
|
|
20495
|
+
}
|
|
20348
20496
|
const imgMatch = line.match(/^!\[([^\]]*)\]\(([^)]+)\)(\{[^}]*\})?\s*$/);
|
|
20349
20497
|
if (imgMatch) {
|
|
20350
20498
|
const alt = imgMatch[1] ?? "";
|
|
@@ -20652,6 +20800,7 @@ function blockElName(b) {
|
|
|
20652
20800
|
case "field": return "field";
|
|
20653
20801
|
case "fieldGroup": return "fieldGroup";
|
|
20654
20802
|
case "image": return "image";
|
|
20803
|
+
case "docEmbed": return "docEmbed";
|
|
20655
20804
|
}
|
|
20656
20805
|
}
|
|
20657
20806
|
function fillBlock(el, block) {
|
|
@@ -20867,6 +21016,9 @@ function fillBlock(el, block) {
|
|
|
20867
21016
|
if (block.width) el.setAttribute("width", block.width);
|
|
20868
21017
|
if (block.height) el.setAttribute("height", block.height);
|
|
20869
21018
|
break;
|
|
21019
|
+
case "docEmbed":
|
|
21020
|
+
el.setAttribute("docId", block.docId);
|
|
21021
|
+
break;
|
|
20870
21022
|
}
|
|
20871
21023
|
}
|
|
20872
21024
|
function populateYDocFromMarkdown(fragment, markdown, fallbackTitle = "Untitled") {
|
|
@@ -20915,6 +21067,7 @@ function populateYDocFromMarkdown(fragment, markdown, fallbackTitle = "Untitled"
|
|
|
20915
21067
|
case "field": return new yjs.XmlElement("field");
|
|
20916
21068
|
case "fieldGroup": return new yjs.XmlElement("fieldGroup");
|
|
20917
21069
|
case "image": return new yjs.XmlElement("image");
|
|
21070
|
+
case "docEmbed": return new yjs.XmlElement("docEmbed");
|
|
20918
21071
|
}
|
|
20919
21072
|
});
|
|
20920
21073
|
fragment.insert(0, [
|
|
@@ -20986,6 +21139,10 @@ function serializeElement(el, indent = "") {
|
|
|
20986
21139
|
}
|
|
20987
21140
|
case "horizontalRule": return "---";
|
|
20988
21141
|
case "table": return serializeTable(el);
|
|
21142
|
+
case "docEmbed": {
|
|
21143
|
+
const docId = el.getAttribute("docId");
|
|
21144
|
+
return docId ? `![[${docId}]]` : "";
|
|
21145
|
+
}
|
|
20989
21146
|
case "image": {
|
|
20990
21147
|
const src = el.getAttribute("src") || "";
|
|
20991
21148
|
const alt = el.getAttribute("alt") || "";
|
|
@@ -21151,6 +21308,11 @@ function yjsToMarkdown(fragment) {
|
|
|
21151
21308
|
function registerContentTools(mcp, server) {
|
|
21152
21309
|
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: zod.z.string().describe("Document ID to read.") }, async ({ docId }) => {
|
|
21153
21310
|
try {
|
|
21311
|
+
server.setAutoStatus("reading", docId);
|
|
21312
|
+
server.setActiveToolCall({
|
|
21313
|
+
name: "read_document",
|
|
21314
|
+
target: docId
|
|
21315
|
+
});
|
|
21154
21316
|
const { title, markdown } = yjsToMarkdown((await server.getChildProvider(docId)).document.getXmlFragment("default"));
|
|
21155
21317
|
server.setFocusedDoc(docId);
|
|
21156
21318
|
server.setDocCursor(docId, 0);
|
|
@@ -21176,6 +21338,7 @@ function registerContentTools(mcp, server) {
|
|
|
21176
21338
|
});
|
|
21177
21339
|
children.sort((a, b) => (treeMap.get(a.id)?.order ?? 0) - (treeMap.get(b.id)?.order ?? 0));
|
|
21178
21340
|
}
|
|
21341
|
+
server.setActiveToolCall(null);
|
|
21179
21342
|
const result = {
|
|
21180
21343
|
label,
|
|
21181
21344
|
type,
|
|
@@ -21188,6 +21351,7 @@ function registerContentTools(mcp, server) {
|
|
|
21188
21351
|
text: JSON.stringify(result, null, 2)
|
|
21189
21352
|
}] };
|
|
21190
21353
|
} catch (error) {
|
|
21354
|
+
server.setActiveToolCall(null);
|
|
21191
21355
|
return {
|
|
21192
21356
|
content: [{
|
|
21193
21357
|
type: "text",
|
|
@@ -21197,12 +21361,17 @@ function registerContentTools(mcp, server) {
|
|
|
21197
21361
|
};
|
|
21198
21362
|
}
|
|
21199
21363
|
});
|
|
21200
|
-
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.", {
|
|
21364
|
+
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.", {
|
|
21201
21365
|
docId: zod.z.string().describe("Document ID to write to."),
|
|
21202
21366
|
markdown: zod.z.string().describe("Markdown content to write. Can include YAML frontmatter with title and metadata fields."),
|
|
21203
21367
|
mode: zod.z.enum(["replace", "append"]).optional().describe("Write mode. \"replace\" clears existing content first (default). \"append\" adds to the end.")
|
|
21204
21368
|
}, async ({ docId, markdown, mode }) => {
|
|
21205
21369
|
try {
|
|
21370
|
+
server.setAutoStatus("writing", docId);
|
|
21371
|
+
server.setActiveToolCall({
|
|
21372
|
+
name: "write_document",
|
|
21373
|
+
target: docId
|
|
21374
|
+
});
|
|
21206
21375
|
const writeMode = mode ?? "replace";
|
|
21207
21376
|
const doc = (await server.getChildProvider(docId)).document;
|
|
21208
21377
|
const fragment = doc.getXmlFragment("default");
|
|
@@ -21232,11 +21401,13 @@ function registerContentTools(mcp, server) {
|
|
|
21232
21401
|
populateYDocFromMarkdown(fragment, body || markdown, title || "Untitled");
|
|
21233
21402
|
server.setFocusedDoc(docId);
|
|
21234
21403
|
server.setDocCursor(docId, fragment.length);
|
|
21404
|
+
server.setActiveToolCall(null);
|
|
21235
21405
|
return { content: [{
|
|
21236
21406
|
type: "text",
|
|
21237
21407
|
text: `Document ${docId} updated (${writeMode} mode)`
|
|
21238
21408
|
}] };
|
|
21239
21409
|
} catch (error) {
|
|
21410
|
+
server.setActiveToolCall(null);
|
|
21240
21411
|
return {
|
|
21241
21412
|
content: [{
|
|
21242
21413
|
type: "text",
|
|
@@ -21274,7 +21445,7 @@ function registerMetaTools(mcp, server) {
|
|
|
21274
21445
|
});
|
|
21275
21446
|
mcp.tool("update_metadata", "Update metadata fields on a document. Merges the provided fields into existing metadata.", {
|
|
21276
21447
|
docId: zod.z.string().describe("Document ID."),
|
|
21277
|
-
meta: zod.z.record(zod.z.unknown()).describe("Metadata fields to update (merged with existing).
|
|
21448
|
+
meta: zod.z.record(zod.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.")
|
|
21278
21449
|
}, async ({ docId, meta }) => {
|
|
21279
21450
|
const treeMap = server.getTreeMap();
|
|
21280
21451
|
if (!treeMap) return { content: [{
|
|
@@ -21519,15 +21690,23 @@ function registerChannelTools(mcp, server) {
|
|
|
21519
21690
|
task_id: zod.z.string().optional().describe("If replying to an ai:task, the task ID to clear from awareness.")
|
|
21520
21691
|
}, async ({ doc_id, text, task_id }) => {
|
|
21521
21692
|
try {
|
|
21693
|
+
server.setAutoStatus("writing", doc_id);
|
|
21694
|
+
server.setActiveToolCall({
|
|
21695
|
+
name: "reply",
|
|
21696
|
+
target: doc_id
|
|
21697
|
+
});
|
|
21522
21698
|
const treeMap = server.getTreeMap();
|
|
21523
21699
|
const rootDoc = server.rootDocument;
|
|
21524
|
-
if (!treeMap || !rootDoc)
|
|
21525
|
-
|
|
21526
|
-
|
|
21527
|
-
|
|
21528
|
-
|
|
21529
|
-
|
|
21530
|
-
|
|
21700
|
+
if (!treeMap || !rootDoc) {
|
|
21701
|
+
server.setActiveToolCall(null);
|
|
21702
|
+
return {
|
|
21703
|
+
content: [{
|
|
21704
|
+
type: "text",
|
|
21705
|
+
text: "Not connected"
|
|
21706
|
+
}],
|
|
21707
|
+
isError: true
|
|
21708
|
+
};
|
|
21709
|
+
}
|
|
21531
21710
|
const label = `AI Reply — ${(/* @__PURE__ */ new Date()).toISOString().replace("T", " ").slice(0, 19)}: ${text.slice(0, 40).replace(/\n/g, " ")}`;
|
|
21532
21711
|
const replyId = crypto.randomUUID();
|
|
21533
21712
|
const now = Date.now();
|
|
@@ -21543,6 +21722,7 @@ function registerChannelTools(mcp, server) {
|
|
|
21543
21722
|
});
|
|
21544
21723
|
populateYDocFromMarkdown((await server.getChildProvider(replyId)).document, text);
|
|
21545
21724
|
if (task_id) server.clearAiTask(task_id);
|
|
21725
|
+
server.setActiveToolCall(null);
|
|
21546
21726
|
return { content: [{
|
|
21547
21727
|
type: "text",
|
|
21548
21728
|
text: JSON.stringify({
|
|
@@ -21551,6 +21731,7 @@ function registerChannelTools(mcp, server) {
|
|
|
21551
21731
|
})
|
|
21552
21732
|
}] };
|
|
21553
21733
|
} catch (error) {
|
|
21734
|
+
server.setActiveToolCall(null);
|
|
21554
21735
|
return {
|
|
21555
21736
|
content: [{
|
|
21556
21737
|
type: "text",
|
|
@@ -21579,6 +21760,8 @@ function registerChannelTools(mcp, server) {
|
|
|
21579
21760
|
content: text,
|
|
21580
21761
|
sender_name: server.agentName
|
|
21581
21762
|
}));
|
|
21763
|
+
server.setAutoStatus(null);
|
|
21764
|
+
server.setActiveToolCall(null);
|
|
21582
21765
|
return { content: [{
|
|
21583
21766
|
type: "text",
|
|
21584
21767
|
text: `Sent to ${channel}`
|
|
@@ -21610,6 +21793,18 @@ Every piece of visible data in Abracadabra — a calendar event, a kanban card,
|
|
|
21610
21793
|
|
|
21611
21794
|
**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.
|
|
21612
21795
|
|
|
21796
|
+
The same tree viewed as different page types:
|
|
21797
|
+
\`\`\`
|
|
21798
|
+
My Doc
|
|
21799
|
+
├── "To Do" → Kanban: column / Table: column / Outline: item
|
|
21800
|
+
│ ├── "Fix bug" → Kanban: card / Table: cell / Outline: sub-item
|
|
21801
|
+
│ └── "Add tests"
|
|
21802
|
+
├── "In Progress"
|
|
21803
|
+
│ └── "Refactor"
|
|
21804
|
+
└── "Done"
|
|
21805
|
+
└── "Deploy"
|
|
21806
|
+
\`\`\`
|
|
21807
|
+
|
|
21613
21808
|
---
|
|
21614
21809
|
|
|
21615
21810
|
## Getting Oriented in a Space
|
|
@@ -21629,23 +21824,24 @@ If you are adding content to an existing space, call \`get_document_tree(rootId:
|
|
|
21629
21824
|
|
|
21630
21825
|
## Page Types Reference
|
|
21631
21826
|
|
|
21632
|
-
| Type | Children Are | Grandchildren Are | Depth
|
|
21633
|
-
|
|
21634
|
-
| **doc** | Sub-documents | Sub-sub-documents |
|
|
21635
|
-
| **kanban** | Columns | Cards | 2 |
|
|
21636
|
-
| **table** | Columns | Cells (positional rows) | 2 |
|
|
21637
|
-
| **calendar** | Events | — | 1 |
|
|
21638
|
-
| **timeline** | Epics | Tasks | 2 |
|
|
21639
|
-
| **
|
|
21640
|
-
| **
|
|
21641
|
-
| **
|
|
21642
|
-
| **
|
|
21643
|
-
| **
|
|
21644
|
-
| **
|
|
21645
|
-
| **
|
|
21646
|
-
| **
|
|
21647
|
-
| **
|
|
21648
|
-
| **
|
|
21827
|
+
| Type | Children Are | Grandchildren Are | Depth | Key Meta on Children |
|
|
21828
|
+
|------|-------------|-------------------|-------|---------------------|
|
|
21829
|
+
| **doc** | Sub-documents | Sub-sub-documents | ∞ | — |
|
|
21830
|
+
| **kanban** | Columns | Cards | 2 | color, icon on cards |
|
|
21831
|
+
| **table** | Columns | Cells (positional rows) | 2 | — |
|
|
21832
|
+
| **calendar** | Events | — | 1 | datetimeStart, datetimeEnd, allDay, color |
|
|
21833
|
+
| **timeline** | Epics | Tasks | 2 | dateStart, dateEnd, taskProgress, color |
|
|
21834
|
+
| **checklist** | Tasks | Sub-tasks | ∞ | checked, priority, dateEnd |
|
|
21835
|
+
| **outline** | Items | Sub-items | ∞ | — |
|
|
21836
|
+
| **mindmap** | Central nodes | Branches | ∞ | mmX, mmY |
|
|
21837
|
+
| **graph** | Nodes | — | 1 | graphX, graphY, graphPinned, color |
|
|
21838
|
+
| **gallery** | Items | — | 1 | geoLat, geoLng, datetimeStart, tags |
|
|
21839
|
+
| **map** | Markers/Lines | Points (for lines) | 2 | geoType, geoLat, geoLng, icon, color |
|
|
21840
|
+
| **dashboard** | Items | — | 1 | deskX, deskY, deskMode |
|
|
21841
|
+
| **spatial** | Objects | Sub-parts | ∞ | spShape, spX/Y/Z, spColor, spOpacity |
|
|
21842
|
+
| **media** | Tracks | — | 1 | tags |
|
|
21843
|
+
| **slides** | Slides | — | 1 | — |
|
|
21844
|
+
| **whiteboard** | Objects | — | 1 | wbX, wbY, wbW, wbH |
|
|
21649
21845
|
|
|
21650
21846
|
---
|
|
21651
21847
|
|
|
@@ -21654,26 +21850,110 @@ If you are adding content to an existing space, call \`get_document_tree(rootId:
|
|
|
21654
21850
|
### Creating Well-Structured Hierarchies
|
|
21655
21851
|
|
|
21656
21852
|
**Kanban board:**
|
|
21657
|
-
1.
|
|
21853
|
+
1. \`create_document(parentId, "Project Board", "kanban")\`
|
|
21658
21854
|
2. Create columns: \`create_document(boardId, "To Do")\`, \`create_document(boardId, "In Progress")\`, \`create_document(boardId, "Done")\`
|
|
21659
21855
|
3. Create cards under columns: \`create_document(toDoColumnId, "Fix bug #123")\`
|
|
21856
|
+
4. Optionally set card metadata: \`update_metadata(cardId, { color: "#ef4444", priority: 3, tags: ["urgent"] })\`
|
|
21857
|
+
5. Optionally configure column width: \`update_metadata(boardId, { kanbanColumnWidth: "wide" })\`
|
|
21858
|
+
|
|
21859
|
+
**Table with data:**
|
|
21860
|
+
1. \`create_document(parentId, "Contacts", "table")\`
|
|
21861
|
+
2. Create columns: \`create_document(tableId, "Name")\`, \`create_document(tableId, "Email")\`, \`create_document(tableId, "Phone")\`
|
|
21862
|
+
3. Create cells (children of columns): \`create_document(nameColId, "Alice")\`, \`create_document(emailColId, "alice@example.com")\`
|
|
21863
|
+
4. **Rows are positional** — the Nth child of each column forms row N
|
|
21864
|
+
5. Row count = max children across all columns. Missing cells render as empty.
|
|
21865
|
+
6. To add a row: create a child under each column at the same index position
|
|
21866
|
+
7. To delete row N: delete the Nth child from every column
|
|
21660
21867
|
|
|
21661
21868
|
**Calendar with events:**
|
|
21662
|
-
1.
|
|
21869
|
+
1. \`create_document(parentId, "Team Calendar", "calendar")\`
|
|
21663
21870
|
2. Create events: \`create_document(calendarId, "Sprint Planning")\`
|
|
21664
21871
|
3. Set event times: \`update_metadata(eventId, { datetimeStart: "2026-03-20T10:00:00Z", datetimeEnd: "2026-03-20T11:00:00Z" })\`
|
|
21872
|
+
4. All-day events: \`update_metadata(eventId, { datetimeStart: "2026-03-20", allDay: true })\`
|
|
21873
|
+
5. Multi-day events: \`update_metadata(eventId, { datetimeStart: "2026-03-20", datetimeEnd: "2026-03-22", allDay: true })\`
|
|
21874
|
+
6. Configure view: \`update_metadata(calendarId, { calendarView: "week" })\`
|
|
21665
21875
|
|
|
21666
|
-
**Timeline
|
|
21667
|
-
1.
|
|
21876
|
+
**Timeline (Gantt chart):**
|
|
21877
|
+
1. \`create_document(parentId, "Q1 Roadmap", "timeline")\`
|
|
21668
21878
|
2. Create epics: \`create_document(timelineId, "Auth Rewrite")\`
|
|
21669
|
-
3. Create tasks: \`create_document(epicId, "Implement JWT refresh")\`
|
|
21670
|
-
4. Set dates
|
|
21879
|
+
3. Create tasks under epics: \`create_document(epicId, "Implement JWT refresh")\`
|
|
21880
|
+
4. Set dates and progress: \`update_metadata(taskId, { dateStart: "2026-03-01", dateEnd: "2026-03-15", taskProgress: 50, color: "#6366f1" })\`
|
|
21671
21881
|
|
|
21672
|
-
**
|
|
21673
|
-
1.
|
|
21674
|
-
2. Create
|
|
21675
|
-
3.
|
|
21676
|
-
4.
|
|
21882
|
+
**Checklist (nested task list):**
|
|
21883
|
+
1. \`create_document(parentId, "Sprint Tasks", "checklist")\`
|
|
21884
|
+
2. Create tasks: \`create_document(checklistId, "Write tests")\`
|
|
21885
|
+
3. Set task properties: \`update_metadata(taskId, { checked: false, priority: 3, dateEnd: "2026-04-01" })\`
|
|
21886
|
+
4. Create sub-tasks (unlimited nesting): \`create_document(taskId, "Unit tests")\`
|
|
21887
|
+
|
|
21888
|
+
**Map with markers and lines:**
|
|
21889
|
+
1. \`create_document(parentId, "Travel Map", "map")\`
|
|
21890
|
+
2. Create markers: \`create_document(mapId, "Coffee Shop")\`
|
|
21891
|
+
3. Set marker position: \`update_metadata(markerId, { geoType: "marker", geoLat: 37.7749, geoLng: -122.4194, icon: "coffee", color: "#f97316" })\`
|
|
21892
|
+
4. Create a line/route: \`create_document(mapId, "Walking Route")\`
|
|
21893
|
+
5. Set line properties: \`update_metadata(routeId, { geoType: "line", color: "#3b82f6" })\`
|
|
21894
|
+
6. Create line points as children: \`create_document(routeId, "Start")\` then \`update_metadata(pointId, { geoLat: 37.7749, geoLng: -122.4194 })\`
|
|
21895
|
+
7. Create measuring tool: set \`geoType: "measure"\` instead of "line"
|
|
21896
|
+
|
|
21897
|
+
**Gallery:**
|
|
21898
|
+
1. \`create_document(parentId, "Photo Album", "gallery")\`
|
|
21899
|
+
2. Create items: \`create_document(galleryId, "Sunset at Beach")\`
|
|
21900
|
+
3. Set metadata: \`update_metadata(itemId, { geoLat: 37.7749, geoLng: -122.4194, tags: ["travel", "sunset"] })\`
|
|
21901
|
+
4. Configure layout: \`update_metadata(galleryId, { galleryColumns: 4, galleryAspect: "16:9" })\`
|
|
21902
|
+
|
|
21903
|
+
**Graph (knowledge graph):**
|
|
21904
|
+
1. \`create_document(parentId, "Tech Stack", "graph")\`
|
|
21905
|
+
2. Create nodes: \`create_document(graphId, "React")\`, \`create_document(graphId, "Vue")\`
|
|
21906
|
+
3. Set node properties: \`update_metadata(nodeId, { color: "#6366f1", icon: "file-code" })\`
|
|
21907
|
+
4. Edges are created by parent-child relationships AND document references (doc embeds/links in content)
|
|
21908
|
+
5. Enable reference edges: \`update_metadata(graphId, { showRefEdges: true })\`
|
|
21909
|
+
|
|
21910
|
+
**Dashboard:**
|
|
21911
|
+
1. \`create_document(parentId, "Overview", "dashboard")\`
|
|
21912
|
+
2. Create items: \`create_document(dashboardId, "Revenue Chart")\`
|
|
21913
|
+
3. Position items on grid: \`update_metadata(itemId, { deskX: 0, deskY: 0, deskMode: "widget-lg" })\`
|
|
21914
|
+
4. Modes: \`"icon"\` (small), \`"widget-sm"\` (240×180), \`"widget-lg"\` (400×320)
|
|
21915
|
+
5. Grid uses 80px cells
|
|
21916
|
+
|
|
21917
|
+
**Spatial (3D scene):**
|
|
21918
|
+
1. \`create_document(parentId, "3D Scene", "spatial")\`
|
|
21919
|
+
2. Create objects: \`create_document(sceneId, "Red Cube")\`
|
|
21920
|
+
3. Set 3D properties: \`update_metadata(objId, { spShape: "box", spColor: "#ef4444", spX: 0, spY: 1, spZ: 0, spSX: 2, spSY: 2, spSZ: 2 })\`
|
|
21921
|
+
4. Shapes: \`"box"\`, \`"sphere"\`, \`"cylinder"\`, \`"cone"\`, \`"plane"\`, \`"torus"\`, \`"glb"\` (uploaded 3D model)
|
|
21922
|
+
5. Rotation (degrees): \`spRX\`, \`spRY\`, \`spRZ\`. Scale: \`spSX\`, \`spSY\`, \`spSZ\` (default 1). Opacity: \`spOpacity\` (0–100)
|
|
21923
|
+
|
|
21924
|
+
**Outline (nested items):**
|
|
21925
|
+
1. \`create_document(parentId, "Meeting Notes", "outline")\`
|
|
21926
|
+
2. Create items: \`create_document(outlineId, "Agenda Item 1")\`
|
|
21927
|
+
3. Create sub-items (unlimited depth): \`create_document(itemId, "Sub-point")\`
|
|
21928
|
+
|
|
21929
|
+
---
|
|
21930
|
+
|
|
21931
|
+
## Document References
|
|
21932
|
+
|
|
21933
|
+
You can link and embed documents within rich-text content:
|
|
21934
|
+
|
|
21935
|
+
**Block-level doc embed** — renders an embedded document preview:
|
|
21936
|
+
\`\`\`
|
|
21937
|
+
![[docId]]
|
|
21938
|
+
\`\`\`
|
|
21939
|
+
|
|
21940
|
+
**Inline link to another document:**
|
|
21941
|
+
\`\`\`
|
|
21942
|
+
[Link text](/doc/docId)
|
|
21943
|
+
\`\`\`
|
|
21944
|
+
|
|
21945
|
+
**Inline wikilink** (shorthand — converted to doc link):
|
|
21946
|
+
\`\`\`
|
|
21947
|
+
[[docId]]
|
|
21948
|
+
[[docId|Display Text]]
|
|
21949
|
+
\`\`\`
|
|
21950
|
+
|
|
21951
|
+
Example — write content with references:
|
|
21952
|
+
\`\`\`
|
|
21953
|
+
write_document(docId, "# Project Overview\\n\\nSee the detailed spec:\\n\\n![[specDocId]]\\n\\nCheck the [timeline](/doc/timelineDocId) for dates.")
|
|
21954
|
+
\`\`\`
|
|
21955
|
+
|
|
21956
|
+
In the **graph** page type, document references (embeds and links) create visible edges between nodes when \`showRefEdges\` is enabled.
|
|
21677
21957
|
|
|
21678
21958
|
---
|
|
21679
21959
|
|
|
@@ -21684,7 +21964,7 @@ If you are adding content to an existing space, call \`get_document_tree(rootId:
|
|
|
21684
21964
|
| Key | Type | Meaning |
|
|
21685
21965
|
|-----|------|---------|
|
|
21686
21966
|
| \`color\` | string | Hex/CSS color (e.g. "#6366f1") |
|
|
21687
|
-
| \`icon\` | string | Lucide icon name in kebab-case (e.g. "star", "code-2", "users"
|
|
21967
|
+
| \`icon\` | string | Lucide icon name in kebab-case (e.g. "star", "code-2", "users"). **Never emoji.** Omit for default. |
|
|
21688
21968
|
| \`datetimeStart\` / \`datetimeEnd\` | string | ISO datetime with time |
|
|
21689
21969
|
| \`allDay\` | boolean | Whether datetime is all-day |
|
|
21690
21970
|
| \`dateStart\` / \`dateEnd\` | string | ISO date-only (e.g. "2026-03-20") |
|
|
@@ -21702,45 +21982,86 @@ If you are adding content to an existing space, call \`get_document_tree(rootId:
|
|
|
21702
21982
|
| \`subtitle\` | string | Secondary text |
|
|
21703
21983
|
| \`note\` | string | Free-text note |
|
|
21704
21984
|
| \`taskProgress\` | number | 0–100 progress (timeline tasks) |
|
|
21985
|
+
| \`members\` | { id, label }[] | Assigned users |
|
|
21986
|
+
| \`coverUploadId\` | string | Cover image (upload ID from upload_file) |
|
|
21705
21987
|
|
|
21706
|
-
###
|
|
21988
|
+
### Geo/Map Keys (for map children)
|
|
21989
|
+
|
|
21990
|
+
| Key | Type | Meaning |
|
|
21991
|
+
|-----|------|---------|
|
|
21992
|
+
| \`geoType\` | string | "marker", "line", or "measure" |
|
|
21993
|
+
| \`geoLat\` | number | Latitude |
|
|
21994
|
+
| \`geoLng\` | number | Longitude |
|
|
21995
|
+
| \`geoDescription\` | string | Location description text |
|
|
21707
21996
|
|
|
21708
|
-
|
|
21997
|
+
### Renderer Config Keys (set on the page doc itself, not its children)
|
|
21709
21998
|
|
|
21710
|
-
|
|
21999
|
+
| Key | Type | Applies to | Values |
|
|
22000
|
+
|-----|------|-----------|--------|
|
|
22001
|
+
| \`kanbanColumnWidth\` | string | kanban | "narrow", "default", "wide" |
|
|
22002
|
+
| \`galleryColumns\` | number | gallery | 1–6 |
|
|
22003
|
+
| \`galleryAspect\` | string | gallery | "square", "4:3", "16:9" |
|
|
22004
|
+
| \`calendarView\` | string | calendar | "month", "week", "day" |
|
|
22005
|
+
| \`calendarWeekStart\` | string | calendar | "sun", "mon" |
|
|
22006
|
+
| \`tableMode\` | string | table | "hierarchy", "flat" |
|
|
22007
|
+
| \`showRefEdges\` | boolean | graph | show doc-reference edges |
|
|
21711
22008
|
|
|
21712
|
-
|
|
22009
|
+
### Spatial 3D Keys (for spatial children)
|
|
22010
|
+
|
|
22011
|
+
| Key | Type | Default | Meaning |
|
|
22012
|
+
|-----|------|---------|---------|
|
|
22013
|
+
| \`spShape\` | string | — | "box", "sphere", "cylinder", "cone", "plane", "torus", "glb" |
|
|
22014
|
+
| \`spColor\` | string | — | CSS color |
|
|
22015
|
+
| \`spOpacity\` | number | 100 | 0–100 |
|
|
22016
|
+
| \`spX\`, \`spY\`, \`spZ\` | number | 0 | Position |
|
|
22017
|
+
| \`spRX\`, \`spRY\`, \`spRZ\` | number | 0 | Rotation (degrees) |
|
|
22018
|
+
| \`spSX\`, \`spSY\`, \`spSZ\` | number | 1 | Scale |
|
|
22019
|
+
|
|
22020
|
+
### Dashboard Keys (for dashboard children)
|
|
22021
|
+
|
|
22022
|
+
| Key | Type | Meaning |
|
|
22023
|
+
|-----|------|---------|
|
|
22024
|
+
| \`deskX\`, \`deskY\` | number | Grid position (80px cells) |
|
|
22025
|
+
| \`deskZ\` | number | Z-index (layering) |
|
|
22026
|
+
| \`deskMode\` | string | "icon", "widget-sm" (240×180), "widget-lg" (400×320) |
|
|
21713
22027
|
|
|
21714
22028
|
---
|
|
21715
22029
|
|
|
21716
22030
|
## Content Structure
|
|
21717
22031
|
|
|
21718
|
-
Documents use a TipTap editor schema
|
|
21719
|
-
- **documentHeader** — The document title (H1)
|
|
21720
|
-
- **documentMeta** — Metadata display block (skip when reading/writing)
|
|
21721
|
-
- **Body blocks** — paragraphs, headings (H2-H6), lists, code blocks, tables, images, and custom components
|
|
21722
|
-
|
|
21723
|
-
### Supported Markdown Elements
|
|
22032
|
+
Documents use a TipTap editor schema. The body supports:
|
|
21724
22033
|
- Headings (# through ######)
|
|
21725
|
-
- Paragraphs with **bold**, *italic*, ~~strikethrough~~,
|
|
21726
|
-
- Bullet lists, ordered lists, task lists
|
|
22034
|
+
- Paragraphs with **bold**, *italic*, ~~strikethrough~~, \\\`code\\\`, [links](url)
|
|
22035
|
+
- Bullet lists, ordered lists, task lists (- [x] / - [ ])
|
|
21727
22036
|
- Code blocks with language
|
|
21728
22037
|
- Blockquotes
|
|
21729
22038
|
- Tables (pipe syntax)
|
|
21730
22039
|
- Horizontal rules
|
|
21731
|
-
- Images
|
|
21732
|
-
-
|
|
22040
|
+
- Images: \`{width="..." height="..."}\`
|
|
22041
|
+
- Document embeds: \`![[docId]]\`
|
|
22042
|
+
- Inline doc links: \`[[docId|label]]\` or \`[label](/doc/docId)\`
|
|
22043
|
+
- MDC components: \`::tip\`, \`::note\`, \`::warning\`, \`::collapsible\`, \`::steps\`, \`::card\`, \`::accordion\`, \`::tabs\`, \`::code-group\`, etc.
|
|
22044
|
+
- Badges: \`:badge[Label]{color="..."}\`
|
|
22045
|
+
- Icons: \`:icon{name="star"}\`
|
|
21733
22046
|
|
|
21734
22047
|
### Writing Content with Frontmatter
|
|
21735
22048
|
|
|
21736
|
-
|
|
22049
|
+
Set document title and metadata via YAML frontmatter:
|
|
21737
22050
|
|
|
21738
22051
|
\`\`\`markdown
|
|
21739
22052
|
---
|
|
21740
22053
|
title: My Document
|
|
21741
22054
|
tags: [important, review]
|
|
21742
22055
|
color: "#6366f1"
|
|
22056
|
+
icon: star
|
|
21743
22057
|
priority: high
|
|
22058
|
+
checked: false
|
|
22059
|
+
datetimeStart: "2026-03-20T10:00:00Z"
|
|
22060
|
+
datetimeEnd: "2026-03-20T11:00:00Z"
|
|
22061
|
+
allDay: false
|
|
22062
|
+
geoLat: 37.7749
|
|
22063
|
+
geoLng: -122.4194
|
|
22064
|
+
geoType: marker
|
|
21744
22065
|
---
|
|
21745
22066
|
|
|
21746
22067
|
Content goes here...
|
|
@@ -21762,8 +22083,7 @@ You do **not** need to call \`set_presence\` after reading or writing content
|
|
|
21762
22083
|
|---|---|
|
|
21763
22084
|
| \`read_document\` | Root \`docId\` updated; TipTap cursor placed at start of document |
|
|
21764
22085
|
| \`write_document\` | Root \`docId\` updated; TipTap cursor placed at end of written content |
|
|
21765
|
-
|
|
21766
|
-
The TipTap cursor (\`anchor\`/\`head\` as \`Y.RelativePosition\`) makes your colored caret with name label appear inline in the document editor for human collaborators.
|
|
22086
|
+
| \`create_document\` | Root \`docId\` updated to the new document |
|
|
21767
22087
|
|
|
21768
22088
|
### set_presence — manual overrides only
|
|
21769
22089
|
|
|
@@ -21785,8 +22105,6 @@ Always clear fields when done by setting them to \`null\`.
|
|
|
21785
22105
|
| **Calendar** | \`calendar:focused\` | eventId | Interacting with an event |
|
|
21786
22106
|
| **Calendar** | \`calendar:viewing\` | "YYYY-MM" | The month/week you're looking at |
|
|
21787
22107
|
| **Outline** | \`outline:editing\` | nodeId | Editing an outline node |
|
|
21788
|
-
| **Outline** | \`outline:selected\` | nodeId | Node selected/hovered |
|
|
21789
|
-
| **Slides** | \`slides:viewing\` | slideId | The slide you're currently on |
|
|
21790
22108
|
| **Gallery** | \`gallery:focused\` | itemId | Item hovered/selected |
|
|
21791
22109
|
| **Timeline** | \`timeline:focused\` | taskId | Task selected |
|
|
21792
22110
|
| **Mindmap** | \`mindmap:focused\` | nodeId | Node selected/edited |
|
|
@@ -21794,7 +22112,7 @@ Always clear fields when done by setting them to \`null\`.
|
|
|
21794
22112
|
| **Map** | \`map:focused\` | markerId | Marker hovered/selected |
|
|
21795
22113
|
| **Doc** | \`doc:scroll\` | 0–1 number | Scroll position in document |
|
|
21796
22114
|
|
|
21797
|
-
Example — mark a kanban card as being inspected, then clear
|
|
22115
|
+
Example — mark a kanban card as being inspected, then clear:
|
|
21798
22116
|
\`\`\`
|
|
21799
22117
|
set_doc_awareness(boardDocId, { "kanban:hovering": cardId })
|
|
21800
22118
|
// ... do work ...
|
|
@@ -21805,73 +22123,48 @@ set_doc_awareness(boardDocId, { "kanban:hovering": null })
|
|
|
21805
22123
|
|
|
21806
22124
|
## Receiving Instructions from Humans
|
|
21807
22125
|
|
|
21808
|
-
Three ways humans can send you tasks:
|
|
21809
|
-
|
|
21810
22126
|
**Channel events (preferred):** When running in channel mode, \`ai:task\` events from
|
|
21811
22127
|
human users arrive as \`<channel source="abracadabra" ...>\` notifications directly in
|
|
21812
|
-
your session.
|
|
21813
|
-
|
|
21814
|
-
task completion.
|
|
22128
|
+
your session. Use the \`reply\` tool to send your response — it creates a reply document
|
|
22129
|
+
and signals task completion.
|
|
21815
22130
|
|
|
21816
|
-
**Chat
|
|
21817
|
-
|
|
21818
|
-
with the chat document's ID.
|
|
22131
|
+
**Chat messages:** Chat messages from the platform arrive as channel notifications. Use
|
|
22132
|
+
\`send_chat_message\` to respond in the same channel.
|
|
21819
22133
|
|
|
21820
22134
|
**Polling (fallback):** Call \`poll_inbox\` to read the "AI Inbox" document.
|
|
21821
|
-
Act on any pending tasks you find there. Write your response back with \`write_document\`
|
|
21822
|
-
or \`create_document\` under the inbox.
|
|
21823
|
-
|
|
21824
|
-
### Channel Tools
|
|
21825
|
-
|
|
21826
|
-
| Tool | Purpose |
|
|
21827
|
-
|------|---------|
|
|
21828
|
-
| \`reply\` | Send a reply to Abracadabra — creates a child doc with your response. Pass \`task_id\` to signal ai:task completion. |
|
|
21829
|
-
| \`watch_chat\` | Start/stop watching a document for chat messages. New content arrives as channel notifications. |
|
|
21830
|
-
|
|
21831
|
-
When you complete a task, always use \`reply\` (in channel mode) or write a response
|
|
21832
|
-
document (in polling mode) so the human knows the work is done.
|
|
21833
22135
|
|
|
21834
22136
|
---
|
|
21835
22137
|
|
|
21836
22138
|
## Common Patterns
|
|
21837
22139
|
|
|
21838
|
-
### Populate a kanban board from a list
|
|
21839
|
-
\`\`\`
|
|
21840
|
-
1. create_document → kanban parent
|
|
21841
|
-
2. For each column: create_document under kanban
|
|
21842
|
-
3. For each card: create_document under the appropriate column
|
|
21843
|
-
4. Optionally set metadata (color, tags, priority) on cards
|
|
21844
|
-
\`\`\`
|
|
21845
|
-
|
|
21846
22140
|
### Explore a document fully (the correct way)
|
|
21847
22141
|
\`\`\`
|
|
21848
|
-
|
|
21849
|
-
|
|
21850
|
-
1. read_document(marketingId)
|
|
22142
|
+
1. read_document(docId)
|
|
21851
22143
|
→ returns { markdown, children: [...] }
|
|
21852
22144
|
2. The body may be empty — that does NOT mean the doc is empty.
|
|
21853
22145
|
Children ARE the content. Read every child:
|
|
21854
22146
|
read_document(childId1), read_document(childId2), ...
|
|
21855
|
-
3.
|
|
21856
|
-
Continue recursively until no children remain.
|
|
22147
|
+
3. Continue recursively until no children remain.
|
|
21857
22148
|
\`\`\`
|
|
21858
22149
|
|
|
21859
22150
|
**Never conclude a document is "empty" just because its markdown body is empty.
|
|
21860
22151
|
Always check and traverse the \`children\` array returned by \`read_document\`.**
|
|
21861
22152
|
|
|
21862
|
-
###
|
|
22153
|
+
### Populate a kanban board from a list
|
|
21863
22154
|
\`\`\`
|
|
21864
|
-
1.
|
|
21865
|
-
2.
|
|
21866
|
-
3.
|
|
22155
|
+
1. create_document → kanban parent
|
|
22156
|
+
2. For each column: create_document under kanban
|
|
22157
|
+
3. For each card: create_document under the appropriate column
|
|
22158
|
+
4. Optionally set metadata (color, tags, priority) on cards
|
|
21867
22159
|
\`\`\`
|
|
21868
22160
|
|
|
21869
|
-
###
|
|
22161
|
+
### Write rich content with references
|
|
21870
22162
|
\`\`\`
|
|
21871
|
-
1.
|
|
21872
|
-
2.
|
|
21873
|
-
3.
|
|
21874
|
-
4.
|
|
22163
|
+
1. read_document to see current content (and children)
|
|
22164
|
+
2. write_document with markdown (mode: "replace" or "append")
|
|
22165
|
+
3. Use ![[docId]] to embed other documents
|
|
22166
|
+
4. Use [[docId|label]] for inline doc links
|
|
22167
|
+
5. Include frontmatter for title/metadata if needed
|
|
21875
22168
|
\`\`\`
|
|
21876
22169
|
|
|
21877
22170
|
---
|
|
@@ -21897,6 +22190,7 @@ Always check and traverse the \`children\` array returned by \`read_document\`.*
|
|
|
21897
22190
|
- Use emoji as \`icon\` values — only lowercase kebab-case Lucide icon names are valid
|
|
21898
22191
|
- Create top-level documents without first confirming the correct hub doc ID
|
|
21899
22192
|
- Rename or write content to the space hub document itself
|
|
22193
|
+
- Set \`type\` on child items (cards, cells, events) — only set type on the parent page
|
|
21900
22194
|
`;
|
|
21901
22195
|
function registerAgentGuide(mcp) {
|
|
21902
22196
|
mcp.resource("agent-guide", "abracadabra://agent-guide", {
|
|
@@ -21989,6 +22283,221 @@ function registerServerInfoResource(mcp, server) {
|
|
|
21989
22283
|
});
|
|
21990
22284
|
}
|
|
21991
22285
|
|
|
22286
|
+
//#endregion
|
|
22287
|
+
//#region packages/mcp/src/tools/hooks.ts
|
|
22288
|
+
function registerHookTools(mcp, hookBridge) {
|
|
22289
|
+
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 () => {
|
|
22290
|
+
const port = hookBridge.port;
|
|
22291
|
+
if (!port) return {
|
|
22292
|
+
content: [{
|
|
22293
|
+
type: "text",
|
|
22294
|
+
text: "Hook bridge is not running."
|
|
22295
|
+
}],
|
|
22296
|
+
isError: true
|
|
22297
|
+
};
|
|
22298
|
+
const hookEntry = {
|
|
22299
|
+
type: "http",
|
|
22300
|
+
url: `http://127.0.0.1:${port}/hook`,
|
|
22301
|
+
timeout: 3
|
|
22302
|
+
};
|
|
22303
|
+
const config = { hooks: {
|
|
22304
|
+
PreToolUse: [{ hooks: [hookEntry] }],
|
|
22305
|
+
PostToolUse: [{ hooks: [hookEntry] }],
|
|
22306
|
+
SubagentStart: [{ hooks: [hookEntry] }],
|
|
22307
|
+
SubagentStop: [{ hooks: [hookEntry] }],
|
|
22308
|
+
Stop: [{ hooks: [hookEntry] }]
|
|
22309
|
+
} };
|
|
22310
|
+
return { content: [{
|
|
22311
|
+
type: "text",
|
|
22312
|
+
text: JSON.stringify(config, null, 2)
|
|
22313
|
+
}] };
|
|
22314
|
+
});
|
|
22315
|
+
}
|
|
22316
|
+
|
|
22317
|
+
//#endregion
|
|
22318
|
+
//#region packages/mcp/src/hook-bridge.ts
|
|
22319
|
+
/**
|
|
22320
|
+
* HookBridge — lightweight HTTP server that receives Claude Code hook events
|
|
22321
|
+
* and translates them into Yjs awareness updates via AbracadabraMCPServer.
|
|
22322
|
+
*
|
|
22323
|
+
* Claude Code hooks (PreToolUse, PostToolUse, SubagentStart, SubagentStop, Stop)
|
|
22324
|
+
* POST JSON to http://127.0.0.1:{port}/hook. The bridge maps these to awareness
|
|
22325
|
+
* fields (status, activeToolCall) so the cou-sh dashboard shows real-time activity.
|
|
22326
|
+
*/
|
|
22327
|
+
/** Map Claude Code tool names to awareness-friendly names + extract a target string. */
|
|
22328
|
+
function mapToolCall(toolName, toolInput) {
|
|
22329
|
+
switch (toolName) {
|
|
22330
|
+
case "Bash": return {
|
|
22331
|
+
name: "bash",
|
|
22332
|
+
target: truncate(toolInput.command ?? toolInput.description, 60)
|
|
22333
|
+
};
|
|
22334
|
+
case "Read": return {
|
|
22335
|
+
name: "read_file",
|
|
22336
|
+
target: basename(toolInput.file_path)
|
|
22337
|
+
};
|
|
22338
|
+
case "Edit": return {
|
|
22339
|
+
name: "edit_file",
|
|
22340
|
+
target: basename(toolInput.file_path)
|
|
22341
|
+
};
|
|
22342
|
+
case "Write": return {
|
|
22343
|
+
name: "write_file",
|
|
22344
|
+
target: basename(toolInput.file_path)
|
|
22345
|
+
};
|
|
22346
|
+
case "Grep": return {
|
|
22347
|
+
name: "grep",
|
|
22348
|
+
target: truncate(toolInput.pattern, 40)
|
|
22349
|
+
};
|
|
22350
|
+
case "Glob": return {
|
|
22351
|
+
name: "glob",
|
|
22352
|
+
target: truncate(toolInput.pattern, 40)
|
|
22353
|
+
};
|
|
22354
|
+
case "Agent": return {
|
|
22355
|
+
name: "subagent",
|
|
22356
|
+
target: toolInput.description || toolInput.subagent_type || "agent"
|
|
22357
|
+
};
|
|
22358
|
+
case "WebFetch": return {
|
|
22359
|
+
name: "web_fetch",
|
|
22360
|
+
target: hostname(toolInput.url)
|
|
22361
|
+
};
|
|
22362
|
+
case "WebSearch": return {
|
|
22363
|
+
name: "web_search",
|
|
22364
|
+
target: truncate(toolInput.query, 40)
|
|
22365
|
+
};
|
|
22366
|
+
default: return { name: toolName.replace(/([a-z])([A-Z])/g, "$1_$2").toLowerCase() };
|
|
22367
|
+
}
|
|
22368
|
+
}
|
|
22369
|
+
function truncate(str, max) {
|
|
22370
|
+
if (!str) return void 0;
|
|
22371
|
+
return str.length > max ? str.slice(0, max) + "..." : str;
|
|
22372
|
+
}
|
|
22373
|
+
function basename(filePath) {
|
|
22374
|
+
if (!filePath) return void 0;
|
|
22375
|
+
return node_path.basename(filePath);
|
|
22376
|
+
}
|
|
22377
|
+
function hostname(url) {
|
|
22378
|
+
if (!url) return void 0;
|
|
22379
|
+
try {
|
|
22380
|
+
return new URL(url).hostname;
|
|
22381
|
+
} catch {
|
|
22382
|
+
return url.slice(0, 30);
|
|
22383
|
+
}
|
|
22384
|
+
}
|
|
22385
|
+
var HookBridge = class {
|
|
22386
|
+
constructor(server) {
|
|
22387
|
+
this.server = server;
|
|
22388
|
+
this.httpServer = null;
|
|
22389
|
+
this._port = null;
|
|
22390
|
+
this.portFilePath = process.env.ABRA_HOOK_PORT_FILE || node_path.join(node_os.tmpdir(), "abracadabra-mcp-hook.port");
|
|
22391
|
+
}
|
|
22392
|
+
get port() {
|
|
22393
|
+
return this._port;
|
|
22394
|
+
}
|
|
22395
|
+
/** Start the HTTP server on a random port and write the port file. */
|
|
22396
|
+
async start() {
|
|
22397
|
+
return new Promise((resolve, reject) => {
|
|
22398
|
+
const srv = node_http.createServer((req, res) => this.handleRequest(req, res));
|
|
22399
|
+
srv.on("error", reject);
|
|
22400
|
+
srv.listen(0, "127.0.0.1", () => {
|
|
22401
|
+
const addr = srv.address();
|
|
22402
|
+
if (!addr || typeof addr === "string") {
|
|
22403
|
+
reject(/* @__PURE__ */ new Error("Failed to get server address"));
|
|
22404
|
+
return;
|
|
22405
|
+
}
|
|
22406
|
+
this._port = addr.port;
|
|
22407
|
+
this.httpServer = srv;
|
|
22408
|
+
try {
|
|
22409
|
+
node_fs.writeFileSync(this.portFilePath, String(this._port), "utf-8");
|
|
22410
|
+
} catch (err) {
|
|
22411
|
+
console.error(`[hook-bridge] Warning: could not write port file: ${err.message}`);
|
|
22412
|
+
}
|
|
22413
|
+
console.error(`[hook-bridge] Listening on 127.0.0.1:${this._port}`);
|
|
22414
|
+
console.error(`[hook-bridge] Port file: ${this.portFilePath}`);
|
|
22415
|
+
resolve(this._port);
|
|
22416
|
+
});
|
|
22417
|
+
});
|
|
22418
|
+
}
|
|
22419
|
+
/** Shut down the HTTP server and remove the port file. */
|
|
22420
|
+
async destroy() {
|
|
22421
|
+
if (this.httpServer) {
|
|
22422
|
+
await new Promise((resolve) => {
|
|
22423
|
+
this.httpServer.close(() => resolve());
|
|
22424
|
+
});
|
|
22425
|
+
this.httpServer = null;
|
|
22426
|
+
}
|
|
22427
|
+
try {
|
|
22428
|
+
node_fs.unlinkSync(this.portFilePath);
|
|
22429
|
+
} catch {}
|
|
22430
|
+
this._port = null;
|
|
22431
|
+
console.error("[hook-bridge] Shut down");
|
|
22432
|
+
}
|
|
22433
|
+
handleRequest(req, res) {
|
|
22434
|
+
if (req.method !== "POST" || req.url !== "/hook") {
|
|
22435
|
+
res.writeHead(404);
|
|
22436
|
+
res.end();
|
|
22437
|
+
return;
|
|
22438
|
+
}
|
|
22439
|
+
let body = "";
|
|
22440
|
+
req.on("data", (chunk) => {
|
|
22441
|
+
body += chunk.toString();
|
|
22442
|
+
});
|
|
22443
|
+
req.on("end", () => {
|
|
22444
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
22445
|
+
res.end("{}");
|
|
22446
|
+
try {
|
|
22447
|
+
const payload = JSON.parse(body);
|
|
22448
|
+
this.routeEvent(payload);
|
|
22449
|
+
} catch {}
|
|
22450
|
+
});
|
|
22451
|
+
}
|
|
22452
|
+
routeEvent(payload) {
|
|
22453
|
+
switch (payload.hook_event_name) {
|
|
22454
|
+
case "PreToolUse":
|
|
22455
|
+
this.onPreToolUse(payload);
|
|
22456
|
+
break;
|
|
22457
|
+
case "PostToolUse":
|
|
22458
|
+
this.onPostToolUse(payload);
|
|
22459
|
+
break;
|
|
22460
|
+
case "SubagentStart":
|
|
22461
|
+
this.onSubagentStart(payload);
|
|
22462
|
+
break;
|
|
22463
|
+
case "SubagentStop":
|
|
22464
|
+
this.onSubagentStop(payload);
|
|
22465
|
+
break;
|
|
22466
|
+
case "Stop":
|
|
22467
|
+
this.onStop();
|
|
22468
|
+
break;
|
|
22469
|
+
}
|
|
22470
|
+
}
|
|
22471
|
+
onPreToolUse(payload) {
|
|
22472
|
+
const toolName = payload.tool_name ?? "";
|
|
22473
|
+
if (toolName.startsWith("mcp__abracadabra__")) return;
|
|
22474
|
+
const mapped = mapToolCall(toolName, payload.tool_input ?? {});
|
|
22475
|
+
if (mapped) {
|
|
22476
|
+
this.server.setActiveToolCall(mapped);
|
|
22477
|
+
this.server.setAutoStatus("working");
|
|
22478
|
+
}
|
|
22479
|
+
}
|
|
22480
|
+
onPostToolUse(payload) {
|
|
22481
|
+
if ((payload.tool_name ?? "").startsWith("mcp__abracadabra__")) return;
|
|
22482
|
+
this.server.setActiveToolCall(null);
|
|
22483
|
+
}
|
|
22484
|
+
onSubagentStart(payload) {
|
|
22485
|
+
const agentType = payload.agent_type ?? "agent";
|
|
22486
|
+
this.server.setActiveToolCall({
|
|
22487
|
+
name: "subagent",
|
|
22488
|
+
target: agentType
|
|
22489
|
+
});
|
|
22490
|
+
this.server.setAutoStatus("thinking");
|
|
22491
|
+
}
|
|
22492
|
+
onSubagentStop(_payload) {
|
|
22493
|
+
this.server.setActiveToolCall(null);
|
|
22494
|
+
}
|
|
22495
|
+
onStop() {
|
|
22496
|
+
this.server.setAutoStatus(null);
|
|
22497
|
+
this.server.setActiveToolCall(null);
|
|
22498
|
+
}
|
|
22499
|
+
};
|
|
22500
|
+
|
|
21992
22501
|
//#endregion
|
|
21993
22502
|
//#region packages/mcp/src/index.ts
|
|
21994
22503
|
/**
|
|
@@ -22030,8 +22539,9 @@ async function main() {
|
|
|
22030
22539
|
## Key Concepts
|
|
22031
22540
|
- Documents form a tree. A kanban board's columns are child documents; cards are grandchildren.
|
|
22032
22541
|
- A document's label IS its display name everywhere. Children ARE the content (not just the body text).
|
|
22033
|
-
- Page types (doc, kanban, table, calendar, timeline, outline, etc.) are views over the SAME tree — switching types preserves data.
|
|
22542
|
+
- 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.
|
|
22034
22543
|
- An empty markdown body does NOT mean empty content — always check the children array.
|
|
22544
|
+
- Use ![[docId]] in content to embed another document, or [[docId|label]] for inline links.
|
|
22035
22545
|
|
|
22036
22546
|
## Finding Documents
|
|
22037
22547
|
- 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.
|
|
@@ -22068,12 +22578,21 @@ Read the resource at abracadabra://agent-guide for the complete guide covering p
|
|
|
22068
22578
|
console.error(`[abracadabra-mcp] Failed to connect: ${error.message}`);
|
|
22069
22579
|
process.exit(1);
|
|
22070
22580
|
}
|
|
22581
|
+
const hookBridge = new HookBridge(server);
|
|
22582
|
+
try {
|
|
22583
|
+
const hookPort = await hookBridge.start();
|
|
22584
|
+
console.error(`[abracadabra-mcp] Hook bridge listening on port ${hookPort}`);
|
|
22585
|
+
} catch (error) {
|
|
22586
|
+
console.error(`[abracadabra-mcp] Hook bridge failed to start: ${error.message}`);
|
|
22587
|
+
}
|
|
22588
|
+
registerHookTools(mcp, hookBridge);
|
|
22071
22589
|
const transport = new StdioServerTransport();
|
|
22072
22590
|
await mcp.connect(transport);
|
|
22073
22591
|
server.startChannelNotifications(mcp);
|
|
22074
22592
|
console.error("[abracadabra-mcp] MCP server running on stdio");
|
|
22075
22593
|
const shutdown = async () => {
|
|
22076
22594
|
console.error("[abracadabra-mcp] Shutting down...");
|
|
22595
|
+
await hookBridge.destroy();
|
|
22077
22596
|
await server.destroy();
|
|
22078
22597
|
process.exit(0);
|
|
22079
22598
|
};
|