@abraca/mcp 1.8.0 → 1.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/abracadabra-mcp.cjs +278 -172
- package/dist/abracadabra-mcp.cjs.map +1 -1
- package/dist/abracadabra-mcp.esm.js +278 -172
- package/dist/abracadabra-mcp.esm.js.map +1 -1
- package/dist/index.d.ts +30 -1
- package/package.json +1 -1
- package/src/hook-bridge.ts +18 -8
- package/src/index.ts +29 -2
- package/src/mentions.ts +42 -0
- package/src/server.ts +126 -14
- package/src/tools/awareness.ts +3 -0
- package/src/tools/channel.ts +18 -8
- package/src/tools/content.ts +0 -5
- package/src/tools/files.ts +8 -0
- package/src/tools/meta.ts +1 -7
- package/src/tools/svg.ts +0 -3
- package/src/tools/tree.ts +1 -20
package/dist/abracadabra-mcp.cjs
CHANGED
|
@@ -3761,7 +3761,7 @@ const propertyKeyTypes = new Set([
|
|
|
3761
3761
|
"number",
|
|
3762
3762
|
"symbol"
|
|
3763
3763
|
]);
|
|
3764
|
-
function escapeRegex(str) {
|
|
3764
|
+
function escapeRegex$1(str) {
|
|
3765
3765
|
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
3766
3766
|
}
|
|
3767
3767
|
function clone(inst, def, params) {
|
|
@@ -4472,7 +4472,7 @@ const $ZodCheckUpperCase = /* @__PURE__ */ $constructor("$ZodCheckUpperCase", (i
|
|
|
4472
4472
|
});
|
|
4473
4473
|
const $ZodCheckIncludes = /* @__PURE__ */ $constructor("$ZodCheckIncludes", (inst, def) => {
|
|
4474
4474
|
$ZodCheck.init(inst, def);
|
|
4475
|
-
const escapedRegex = escapeRegex(def.includes);
|
|
4475
|
+
const escapedRegex = escapeRegex$1(def.includes);
|
|
4476
4476
|
const pattern = new RegExp(typeof def.position === "number" ? `^.{${def.position}}${escapedRegex}` : escapedRegex);
|
|
4477
4477
|
def.pattern = pattern;
|
|
4478
4478
|
inst._zod.onattach.push((inst) => {
|
|
@@ -4495,7 +4495,7 @@ const $ZodCheckIncludes = /* @__PURE__ */ $constructor("$ZodCheckIncludes", (ins
|
|
|
4495
4495
|
});
|
|
4496
4496
|
const $ZodCheckStartsWith = /* @__PURE__ */ $constructor("$ZodCheckStartsWith", (inst, def) => {
|
|
4497
4497
|
$ZodCheck.init(inst, def);
|
|
4498
|
-
const pattern = new RegExp(`^${escapeRegex(def.prefix)}.*`);
|
|
4498
|
+
const pattern = new RegExp(`^${escapeRegex$1(def.prefix)}.*`);
|
|
4499
4499
|
def.pattern ?? (def.pattern = pattern);
|
|
4500
4500
|
inst._zod.onattach.push((inst) => {
|
|
4501
4501
|
const bag = inst._zod.bag;
|
|
@@ -4517,7 +4517,7 @@ const $ZodCheckStartsWith = /* @__PURE__ */ $constructor("$ZodCheckStartsWith",
|
|
|
4517
4517
|
});
|
|
4518
4518
|
const $ZodCheckEndsWith = /* @__PURE__ */ $constructor("$ZodCheckEndsWith", (inst, def) => {
|
|
4519
4519
|
$ZodCheck.init(inst, def);
|
|
4520
|
-
const pattern = new RegExp(`.*${escapeRegex(def.suffix)}$`);
|
|
4520
|
+
const pattern = new RegExp(`.*${escapeRegex$1(def.suffix)}$`);
|
|
4521
4521
|
def.pattern ?? (def.pattern = pattern);
|
|
4522
4522
|
inst._zod.onattach.push((inst) => {
|
|
4523
4523
|
const bag = inst._zod.bag;
|
|
@@ -5549,7 +5549,7 @@ const $ZodEnum = /* @__PURE__ */ $constructor("$ZodEnum", (inst, def) => {
|
|
|
5549
5549
|
const values = getEnumValues(def.entries);
|
|
5550
5550
|
const valuesSet = new Set(values);
|
|
5551
5551
|
inst._zod.values = valuesSet;
|
|
5552
|
-
inst._zod.pattern = new RegExp(`^(${values.filter((k) => propertyKeyTypes.has(typeof k)).map((o) => typeof o === "string" ? escapeRegex(o) : o.toString()).join("|")})$`);
|
|
5552
|
+
inst._zod.pattern = new RegExp(`^(${values.filter((k) => propertyKeyTypes.has(typeof k)).map((o) => typeof o === "string" ? escapeRegex$1(o) : o.toString()).join("|")})$`);
|
|
5553
5553
|
inst._zod.parse = (payload, _ctx) => {
|
|
5554
5554
|
const input = payload.value;
|
|
5555
5555
|
if (valuesSet.has(input)) return payload;
|
|
@@ -5567,7 +5567,7 @@ const $ZodLiteral = /* @__PURE__ */ $constructor("$ZodLiteral", (inst, def) => {
|
|
|
5567
5567
|
if (def.values.length === 0) throw new Error("Cannot create literal schema with no valid values");
|
|
5568
5568
|
const values = new Set(def.values);
|
|
5569
5569
|
inst._zod.values = values;
|
|
5570
|
-
inst._zod.pattern = new RegExp(`^(${def.values.map((o) => typeof o === "string" ? escapeRegex(o) : o ? escapeRegex(o.toString()) : String(o)).join("|")})$`);
|
|
5570
|
+
inst._zod.pattern = new RegExp(`^(${def.values.map((o) => typeof o === "string" ? escapeRegex$1(o) : o ? escapeRegex$1(o.toString()) : String(o)).join("|")})$`);
|
|
5571
5571
|
inst._zod.parse = (payload, _ctx) => {
|
|
5572
5572
|
const input = payload.value;
|
|
5573
5573
|
if (values.has(input)) return payload;
|
|
@@ -19969,6 +19969,47 @@ function signChallenge(challengeB64, privateKey) {
|
|
|
19969
19969
|
return toBase64url(sign(challenge, privateKey));
|
|
19970
19970
|
}
|
|
19971
19971
|
|
|
19972
|
+
//#endregion
|
|
19973
|
+
//#region packages/mcp/src/mentions.ts
|
|
19974
|
+
/**
|
|
19975
|
+
* Mention parsing for chat messages.
|
|
19976
|
+
*
|
|
19977
|
+
* Recognizes `@alias` tokens (case-insensitive, word-boundary) so the agent
|
|
19978
|
+
* can decide whether a group-chat message is directed at it.
|
|
19979
|
+
*/
|
|
19980
|
+
/** Escape regex metacharacters in an alias string. */
|
|
19981
|
+
function escapeRegex(s) {
|
|
19982
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
19983
|
+
}
|
|
19984
|
+
/**
|
|
19985
|
+
* Build a regex that matches `@<alias>` for any of the given aliases.
|
|
19986
|
+
* Requires a non-word char (or start) before `@` and a word boundary after the alias
|
|
19987
|
+
* so `@Claude` matches but `email@claudesomething` does not.
|
|
19988
|
+
*/
|
|
19989
|
+
function buildMentionRegex(aliases) {
|
|
19990
|
+
const cleaned = aliases.map((a) => a.trim()).filter((a) => a.length > 0);
|
|
19991
|
+
if (cleaned.length === 0) return null;
|
|
19992
|
+
const alt = cleaned.map(escapeRegex).join("|");
|
|
19993
|
+
return new RegExp(`(?:^|[^\\w@])@(?:${alt})\\b`, "i");
|
|
19994
|
+
}
|
|
19995
|
+
/** Returns true if `text` contains `@alias` for any alias (case-insensitive). */
|
|
19996
|
+
function containsMention(text, aliases) {
|
|
19997
|
+
const re = buildMentionRegex(aliases);
|
|
19998
|
+
if (!re) return false;
|
|
19999
|
+
return re.test(text);
|
|
20000
|
+
}
|
|
20001
|
+
/**
|
|
20002
|
+
* Remove `@alias` tokens from the text. Leaves surrounding whitespace tidy so
|
|
20003
|
+
* the cleaned prompt reads naturally (e.g. `"@Claude help"` → `"help"`).
|
|
20004
|
+
*/
|
|
20005
|
+
function stripMention(text, aliases) {
|
|
20006
|
+
const cleaned = aliases.map((a) => a.trim()).filter((a) => a.length > 0);
|
|
20007
|
+
if (cleaned.length === 0) return text;
|
|
20008
|
+
const alt = cleaned.map(escapeRegex).join("|");
|
|
20009
|
+
const re = new RegExp(`(^|\\s)@(?:${alt})\\b[,:]?\\s*`, "gi");
|
|
20010
|
+
return text.replace(re, (_m, lead) => lead ? " " : "").replace(/\s{2,}/g, " ").trim();
|
|
20011
|
+
}
|
|
20012
|
+
|
|
19972
20013
|
//#endregion
|
|
19973
20014
|
//#region packages/mcp/src/server.ts
|
|
19974
20015
|
/**
|
|
@@ -19979,7 +20020,10 @@ function signChallenge(challengeB64, privateKey) {
|
|
|
19979
20020
|
* Use switchSpace(docId) to change the active space.
|
|
19980
20021
|
*/
|
|
19981
20022
|
const IDLE_TIMEOUT_MS = 300 * 1e3;
|
|
19982
|
-
var AbracadabraMCPServer = class {
|
|
20023
|
+
var AbracadabraMCPServer = class AbracadabraMCPServer {
|
|
20024
|
+
static {
|
|
20025
|
+
this.TOOL_HISTORY_MAX = 20;
|
|
20026
|
+
}
|
|
19983
20027
|
constructor(config) {
|
|
19984
20028
|
this._serverInfo = null;
|
|
19985
20029
|
this._rootDocId = null;
|
|
@@ -19996,6 +20040,7 @@ var AbracadabraMCPServer = class {
|
|
|
19996
20040
|
this._typingInterval = null;
|
|
19997
20041
|
this._lastChatChannel = null;
|
|
19998
20042
|
this._signFn = null;
|
|
20043
|
+
this._toolHistory = [];
|
|
19999
20044
|
this.config = config;
|
|
20000
20045
|
this.client = new _abraca_dabra.AbracadabraClient({
|
|
20001
20046
|
url: config.url,
|
|
@@ -20008,6 +20053,14 @@ var AbracadabraMCPServer = class {
|
|
|
20008
20053
|
get agentColor() {
|
|
20009
20054
|
return this.config.agentColor || "hsl(270, 80%, 60%)";
|
|
20010
20055
|
}
|
|
20056
|
+
get triggerMode() {
|
|
20057
|
+
return this.config.triggerMode ?? "mention+task";
|
|
20058
|
+
}
|
|
20059
|
+
get mentionAliases() {
|
|
20060
|
+
const explicit = this.config.mentionAliases?.filter((a) => a.trim().length > 0);
|
|
20061
|
+
if (explicit && explicit.length > 0) return explicit;
|
|
20062
|
+
return [this.agentName];
|
|
20063
|
+
}
|
|
20011
20064
|
get serverInfo() {
|
|
20012
20065
|
return this._serverInfo;
|
|
20013
20066
|
}
|
|
@@ -20048,7 +20101,7 @@ var AbracadabraMCPServer = class {
|
|
|
20048
20101
|
await this.client.loginWithKey(keypair.publicKeyB64, signFn);
|
|
20049
20102
|
} else throw err;
|
|
20050
20103
|
}
|
|
20051
|
-
console.error(`[abracadabra-mcp] Authenticated as ${this.agentName} (
|
|
20104
|
+
console.error(`[abracadabra-mcp] Authenticated as ${this.agentName} (pubkey=${keypair.publicKeyB64})`);
|
|
20052
20105
|
this._serverInfo = await this.client.serverInfo();
|
|
20053
20106
|
let initialDocId = this._serverInfo.index_doc_id ?? null;
|
|
20054
20107
|
try {
|
|
@@ -20115,6 +20168,8 @@ var AbracadabraMCPServer = class {
|
|
|
20115
20168
|
provider.awareness.setLocalStateField("status", null);
|
|
20116
20169
|
provider.awareness.setLocalStateField("activeToolCall", null);
|
|
20117
20170
|
provider.awareness.setLocalStateField("statusContext", null);
|
|
20171
|
+
provider.awareness.setLocalStateField("turnId", null);
|
|
20172
|
+
provider.awareness.setLocalStateField("toolHistory", []);
|
|
20118
20173
|
const conn = {
|
|
20119
20174
|
doc,
|
|
20120
20175
|
provider,
|
|
@@ -20238,6 +20293,7 @@ var AbracadabraMCPServer = class {
|
|
|
20238
20293
|
_observeRootAwareness(provider) {
|
|
20239
20294
|
const selfId = provider.awareness.clientID;
|
|
20240
20295
|
provider.awareness.on("change", () => {
|
|
20296
|
+
if (this.triggerMode === "mention") return;
|
|
20241
20297
|
const states = provider.awareness.getStates();
|
|
20242
20298
|
for (const [clientId, state] of states) {
|
|
20243
20299
|
if (clientId === selfId) continue;
|
|
@@ -20250,6 +20306,7 @@ var AbracadabraMCPServer = class {
|
|
|
20250
20306
|
const user = state["user"];
|
|
20251
20307
|
const senderName = user && typeof user === "object" && typeof user.name === "string" ? user.name : "Unknown";
|
|
20252
20308
|
console.error(`[abracadabra-mcp] Handling ai:task id=${id} from ${senderName}: ${text.slice(0, 80)}`);
|
|
20309
|
+
this._beginTurn();
|
|
20253
20310
|
this.setAutoStatus("thinking");
|
|
20254
20311
|
this._dispatchAiTask({
|
|
20255
20312
|
id,
|
|
@@ -20312,9 +20369,28 @@ var AbracadabraMCPServer = class {
|
|
|
20312
20369
|
if (data.sender_id && data.sender_id === this._userId) return;
|
|
20313
20370
|
const channel = data.channel;
|
|
20314
20371
|
const docId = channel?.startsWith("group:") ? channel.slice(6) : "";
|
|
20315
|
-
|
|
20372
|
+
const isDM = channel?.startsWith("dm:") ?? false;
|
|
20373
|
+
const isGroup = channel?.startsWith("group:") ?? false;
|
|
20374
|
+
if (isDM) {
|
|
20316
20375
|
const parts = channel.split(":");
|
|
20317
|
-
if (parts.length === 3 && parts[1] !== this._userId && parts[2] !== this._userId)
|
|
20376
|
+
if (parts.length === 3 && parts[1] !== this._userId && parts[2] !== this._userId) {
|
|
20377
|
+
console.error(`[abracadabra-mcp] Dropping DM: agent _userId=${this._userId} not in channel parts=[${parts[1]}, ${parts[2]}] — channel='${channel}'. The dashboard's awareness likely points at a stale Claude identity.`);
|
|
20378
|
+
return;
|
|
20379
|
+
}
|
|
20380
|
+
}
|
|
20381
|
+
const mode = this.triggerMode;
|
|
20382
|
+
const content = typeof data.content === "string" ? data.content : "";
|
|
20383
|
+
let dispatchContent = content;
|
|
20384
|
+
if (isGroup) {
|
|
20385
|
+
if (mode === "task") return;
|
|
20386
|
+
if (mode === "mention" || mode === "mention+task") {
|
|
20387
|
+
const aliases = this.mentionAliases;
|
|
20388
|
+
if (!containsMention(content, aliases)) {
|
|
20389
|
+
console.error(`[abracadabra-mcp] skipped message on ${channel} — no @mention for ${aliases.join("|")}`);
|
|
20390
|
+
return;
|
|
20391
|
+
}
|
|
20392
|
+
dispatchContent = stripMention(content, aliases) || content;
|
|
20393
|
+
}
|
|
20318
20394
|
}
|
|
20319
20395
|
if (channel) {
|
|
20320
20396
|
const rootProvider = this._activeConnection?.provider;
|
|
@@ -20324,14 +20400,13 @@ var AbracadabraMCPServer = class {
|
|
|
20324
20400
|
timestamp: Math.floor(Date.now() / 1e3)
|
|
20325
20401
|
}));
|
|
20326
20402
|
this._lastChatChannel = channel;
|
|
20327
|
-
this.sendTypingIndicator(channel);
|
|
20328
|
-
this._startTypingInterval(channel);
|
|
20329
20403
|
}
|
|
20404
|
+
this._beginTurn();
|
|
20330
20405
|
this.setAutoStatus("thinking");
|
|
20331
20406
|
await this._serverRef.notification({
|
|
20332
20407
|
method: "notifications/claude/channel",
|
|
20333
20408
|
params: {
|
|
20334
|
-
content:
|
|
20409
|
+
content: dispatchContent,
|
|
20335
20410
|
instructions: `You MUST use send_chat_message with channel="${channel ?? ""}" for ALL responses — both progress updates and final answers. The user CANNOT see plain text output; they only see messages sent via send_chat_message. When doing multi-step work, send brief status updates via send_chat_message (e.g. "Looking into that..." or "Found it, writing up results...") so the user knows you're working. Never output plain text as a substitute for send_chat_message.`,
|
|
20336
20411
|
meta: {
|
|
20337
20412
|
source: "abracadabra",
|
|
@@ -20362,13 +20437,35 @@ var AbracadabraMCPServer = class {
|
|
|
20362
20437
|
if (docId !== void 0) provider.awareness.setLocalStateField("docId", docId);
|
|
20363
20438
|
const context = status ? statusContext !== void 0 ? statusContext : this._lastChatChannel : null;
|
|
20364
20439
|
provider.awareness.setLocalStateField("statusContext", context ?? null);
|
|
20365
|
-
if (!status)
|
|
20440
|
+
if (!status) {
|
|
20441
|
+
this._stopTypingInterval();
|
|
20442
|
+
provider.awareness.setLocalStateField("activeToolCall", null);
|
|
20443
|
+
provider.awareness.setLocalStateField("turnId", null);
|
|
20444
|
+
this._toolHistory = [];
|
|
20445
|
+
provider.awareness.setLocalStateField("toolHistory", []);
|
|
20446
|
+
}
|
|
20366
20447
|
if (status) this._statusClearTimer = setTimeout(() => {
|
|
20367
20448
|
provider.awareness.setLocalStateField("status", null);
|
|
20368
20449
|
provider.awareness.setLocalStateField("activeToolCall", null);
|
|
20369
20450
|
provider.awareness.setLocalStateField("statusContext", null);
|
|
20451
|
+
provider.awareness.setLocalStateField("turnId", null);
|
|
20452
|
+
this._toolHistory = [];
|
|
20453
|
+
provider.awareness.setLocalStateField("toolHistory", []);
|
|
20370
20454
|
this._stopTypingInterval();
|
|
20371
|
-
},
|
|
20455
|
+
}, 1e4);
|
|
20456
|
+
}
|
|
20457
|
+
/**
|
|
20458
|
+
* Start a new agent turn. Mints a fresh UUID and writes it to awareness so
|
|
20459
|
+
* the dashboard can gate the incantation on "there is an active turn",
|
|
20460
|
+
* decoupled from the (racier) status field. Called from chat arrival and
|
|
20461
|
+
* ai:task dispatch right before `setAutoStatus('thinking')`.
|
|
20462
|
+
*/
|
|
20463
|
+
_beginTurn() {
|
|
20464
|
+
const provider = this._activeConnection?.provider;
|
|
20465
|
+
if (!provider) return;
|
|
20466
|
+
this._toolHistory = [];
|
|
20467
|
+
provider.awareness.setLocalStateField("toolHistory", []);
|
|
20468
|
+
provider.awareness.setLocalStateField("turnId", crypto.randomUUID());
|
|
20372
20469
|
}
|
|
20373
20470
|
/** Re-send typing indicator every 2s so dashboard keeps showing it (expires at 3s). */
|
|
20374
20471
|
_startTypingInterval(channel) {
|
|
@@ -20386,10 +20483,28 @@ var AbracadabraMCPServer = class {
|
|
|
20386
20483
|
}
|
|
20387
20484
|
/**
|
|
20388
20485
|
* Broadcast which tool the agent is currently executing.
|
|
20389
|
-
*
|
|
20486
|
+
*
|
|
20487
|
+
* Renders as a ChatTool pill on the dashboard. On non-null calls, the tool
|
|
20488
|
+
* is also appended to `toolHistory` (capped at TOOL_HISTORY_MAX) and written
|
|
20489
|
+
* to awareness so the dashboard's inline trace can show the turn's recent
|
|
20490
|
+
* activity. Tools do NOT clear (`setActiveToolCall(null)`) on completion —
|
|
20491
|
+
* the pill stays until the next tool replaces it or `setAutoStatus(null)`
|
|
20492
|
+
* flushes the turn. This keeps pills visible long enough to see.
|
|
20390
20493
|
*/
|
|
20391
20494
|
setActiveToolCall(toolCall) {
|
|
20392
|
-
this._activeConnection?.provider
|
|
20495
|
+
const provider = this._activeConnection?.provider;
|
|
20496
|
+
if (!provider) return;
|
|
20497
|
+
provider.awareness.setLocalStateField("activeToolCall", toolCall);
|
|
20498
|
+
if (toolCall) {
|
|
20499
|
+
this._toolHistory.push({
|
|
20500
|
+
tool: toolCall.name,
|
|
20501
|
+
target: toolCall.target,
|
|
20502
|
+
ts: Date.now(),
|
|
20503
|
+
channel: this._lastChatChannel
|
|
20504
|
+
});
|
|
20505
|
+
if (this._toolHistory.length > AbracadabraMCPServer.TOOL_HISTORY_MAX) this._toolHistory.splice(0, this._toolHistory.length - AbracadabraMCPServer.TOOL_HISTORY_MAX);
|
|
20506
|
+
provider.awareness.setLocalStateField("toolHistory", [...this._toolHistory]);
|
|
20507
|
+
}
|
|
20393
20508
|
}
|
|
20394
20509
|
/**
|
|
20395
20510
|
* Send a typing indicator to a chat channel.
|
|
@@ -20420,8 +20535,11 @@ var AbracadabraMCPServer = class {
|
|
|
20420
20535
|
conn.provider.awareness.setLocalStateField("status", null);
|
|
20421
20536
|
conn.provider.awareness.setLocalStateField("activeToolCall", null);
|
|
20422
20537
|
conn.provider.awareness.setLocalStateField("statusContext", null);
|
|
20538
|
+
conn.provider.awareness.setLocalStateField("turnId", null);
|
|
20539
|
+
conn.provider.awareness.setLocalStateField("toolHistory", []);
|
|
20423
20540
|
conn.provider.destroy();
|
|
20424
20541
|
}
|
|
20542
|
+
this._toolHistory = [];
|
|
20425
20543
|
this._spaceConnections.clear();
|
|
20426
20544
|
this._activeConnection = null;
|
|
20427
20545
|
console.error("[abracadabra-mcp] Shutdown complete");
|
|
@@ -21237,16 +21355,12 @@ function registerTreeTools(mcp, server) {
|
|
|
21237
21355
|
server.setAutoStatus("reading");
|
|
21238
21356
|
server.setActiveToolCall({ name: "list_documents" });
|
|
21239
21357
|
const treeMap = server.getTreeMap();
|
|
21240
|
-
if (!treeMap) {
|
|
21241
|
-
|
|
21242
|
-
|
|
21243
|
-
|
|
21244
|
-
text: "Not connected"
|
|
21245
|
-
}] };
|
|
21246
|
-
}
|
|
21358
|
+
if (!treeMap) return { content: [{
|
|
21359
|
+
type: "text",
|
|
21360
|
+
text: "Not connected"
|
|
21361
|
+
}] };
|
|
21247
21362
|
const targetId = normalizeRootId(parentId, server);
|
|
21248
21363
|
const children = childrenOf$1(readEntries$1(treeMap), targetId);
|
|
21249
|
-
server.setActiveToolCall(null);
|
|
21250
21364
|
return { content: [{
|
|
21251
21365
|
type: "text",
|
|
21252
21366
|
text: JSON.stringify(children, null, 2)
|
|
@@ -21259,17 +21373,13 @@ function registerTreeTools(mcp, server) {
|
|
|
21259
21373
|
server.setAutoStatus("reading");
|
|
21260
21374
|
server.setActiveToolCall({ name: "get_document_tree" });
|
|
21261
21375
|
const treeMap = server.getTreeMap();
|
|
21262
|
-
if (!treeMap) {
|
|
21263
|
-
|
|
21264
|
-
|
|
21265
|
-
|
|
21266
|
-
text: "Not connected"
|
|
21267
|
-
}] };
|
|
21268
|
-
}
|
|
21376
|
+
if (!treeMap) return { content: [{
|
|
21377
|
+
type: "text",
|
|
21378
|
+
text: "Not connected"
|
|
21379
|
+
}] };
|
|
21269
21380
|
const targetId = normalizeRootId(rootId, server);
|
|
21270
21381
|
const maxDepth = depth ?? 3;
|
|
21271
21382
|
const tree = buildTree$1(readEntries$1(treeMap), targetId, maxDepth);
|
|
21272
|
-
server.setActiveToolCall(null);
|
|
21273
21383
|
return { content: [{
|
|
21274
21384
|
type: "text",
|
|
21275
21385
|
text: JSON.stringify(tree, null, 2)
|
|
@@ -21285,13 +21395,10 @@ function registerTreeTools(mcp, server) {
|
|
|
21285
21395
|
target: query
|
|
21286
21396
|
});
|
|
21287
21397
|
const treeMap = server.getTreeMap();
|
|
21288
|
-
if (!treeMap) {
|
|
21289
|
-
|
|
21290
|
-
|
|
21291
|
-
|
|
21292
|
-
text: "Not connected"
|
|
21293
|
-
}] };
|
|
21294
|
-
}
|
|
21398
|
+
if (!treeMap) return { content: [{
|
|
21399
|
+
type: "text",
|
|
21400
|
+
text: "Not connected"
|
|
21401
|
+
}] };
|
|
21295
21402
|
const entries = readEntries$1(treeMap);
|
|
21296
21403
|
const lowerQuery = query.toLowerCase();
|
|
21297
21404
|
const normalizedRoot = normalizeRootId(rootId, server);
|
|
@@ -21316,7 +21423,6 @@ function registerTreeTools(mcp, server) {
|
|
|
21316
21423
|
path
|
|
21317
21424
|
};
|
|
21318
21425
|
});
|
|
21319
|
-
server.setActiveToolCall(null);
|
|
21320
21426
|
if (results.length === 0) return { content: [{
|
|
21321
21427
|
type: "text",
|
|
21322
21428
|
text: `No documents found matching "${query}". Try get_document_tree to see the full hierarchy.`
|
|
@@ -21330,7 +21436,7 @@ function registerTreeTools(mcp, server) {
|
|
|
21330
21436
|
parentId: zod.z.string().optional().describe("Parent document ID. Omit for top-level pages. Use a document ID for nested/child pages."),
|
|
21331
21437
|
label: zod.z.string().describe("Display name / title for the document."),
|
|
21332
21438
|
type: zod.z.string().optional().describe("Page type — sets how this document renders. Core types (always available): \"doc\" (rich text), \"kanban\" (columns → cards), \"table\" (columns → rows, positional), \"calendar\" (events with datetimeStart/End), \"timeline\" (epics → tasks with dateStart/End + taskProgress), \"checklist\" (tasks with checked/priority, unlimited nesting), \"outline\" (nested items, unlimited depth), \"gallery\" (visual grid with covers/ratings), \"map\" (markers/lines with geoLat/geoLng), \"graph\" (force-directed knowledge graph), \"dashboard\" (positioned widgets with deskX/deskY/deskMode), \"slides\" (slides → sub-slides with transitions), \"chart\" (bar/stacked bar/line/donut/treemap from data points or aggregation), \"sheets\" (spreadsheet with formulas and formatting), \"overview\" (space home — activity and stats), \"call\" (video call room, no children). Plugin types (require plugin enabled on the server): \"spatial\" (3D scene with spShape/spX/spY/spZ + universal color, plugin: spatial), \"media\" (audio/video player with playlists, plugin: media), \"coder\" (collaborative multi-file coding env with fileType/entry, plugin: coder). Alias: \"desktop\" → \"dashboard\". Omit to inherit parent view. Only set on the parent page, NEVER on child items."),
|
|
21333
|
-
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.")
|
|
21439
|
+
meta: zod.z.record(zod.z.string(), 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.")
|
|
21334
21440
|
}, async ({ parentId, label, type, meta }) => {
|
|
21335
21441
|
server.setAutoStatus("creating");
|
|
21336
21442
|
server.setActiveToolCall({
|
|
@@ -21339,13 +21445,10 @@ function registerTreeTools(mcp, server) {
|
|
|
21339
21445
|
});
|
|
21340
21446
|
const treeMap = server.getTreeMap();
|
|
21341
21447
|
const rootDoc = server.rootDocument;
|
|
21342
|
-
if (!treeMap || !rootDoc) {
|
|
21343
|
-
|
|
21344
|
-
|
|
21345
|
-
|
|
21346
|
-
text: "Not connected"
|
|
21347
|
-
}] };
|
|
21348
|
-
}
|
|
21448
|
+
if (!treeMap || !rootDoc) return { content: [{
|
|
21449
|
+
type: "text",
|
|
21450
|
+
text: "Not connected"
|
|
21451
|
+
}] };
|
|
21349
21452
|
const id = crypto.randomUUID();
|
|
21350
21453
|
const normalizedParent = normalizeRootId(parentId, server);
|
|
21351
21454
|
const now = Date.now();
|
|
@@ -21361,7 +21464,6 @@ function registerTreeTools(mcp, server) {
|
|
|
21361
21464
|
});
|
|
21362
21465
|
});
|
|
21363
21466
|
server.setFocusedDoc(id);
|
|
21364
|
-
server.setActiveToolCall(null);
|
|
21365
21467
|
return { content: [{
|
|
21366
21468
|
type: "text",
|
|
21367
21469
|
text: JSON.stringify({
|
|
@@ -21382,28 +21484,21 @@ function registerTreeTools(mcp, server) {
|
|
|
21382
21484
|
target: id
|
|
21383
21485
|
});
|
|
21384
21486
|
const treeMap = server.getTreeMap();
|
|
21385
|
-
if (!treeMap) {
|
|
21386
|
-
|
|
21387
|
-
|
|
21388
|
-
|
|
21389
|
-
text: "Not connected"
|
|
21390
|
-
}] };
|
|
21391
|
-
}
|
|
21487
|
+
if (!treeMap) return { content: [{
|
|
21488
|
+
type: "text",
|
|
21489
|
+
text: "Not connected"
|
|
21490
|
+
}] };
|
|
21392
21491
|
const raw = treeMap.get(id);
|
|
21393
|
-
if (!raw) {
|
|
21394
|
-
|
|
21395
|
-
|
|
21396
|
-
|
|
21397
|
-
text: `Document ${id} not found`
|
|
21398
|
-
}] };
|
|
21399
|
-
}
|
|
21492
|
+
if (!raw) return { content: [{
|
|
21493
|
+
type: "text",
|
|
21494
|
+
text: `Document ${id} not found`
|
|
21495
|
+
}] };
|
|
21400
21496
|
const entry = toPlain(raw);
|
|
21401
21497
|
treeMap.set(id, {
|
|
21402
21498
|
...entry,
|
|
21403
21499
|
label,
|
|
21404
21500
|
updatedAt: Date.now()
|
|
21405
21501
|
});
|
|
21406
|
-
server.setActiveToolCall(null);
|
|
21407
21502
|
return { content: [{
|
|
21408
21503
|
type: "text",
|
|
21409
21504
|
text: `Renamed to "${label}"`
|
|
@@ -21420,21 +21515,15 @@ function registerTreeTools(mcp, server) {
|
|
|
21420
21515
|
target: id
|
|
21421
21516
|
});
|
|
21422
21517
|
const treeMap = server.getTreeMap();
|
|
21423
|
-
if (!treeMap) {
|
|
21424
|
-
|
|
21425
|
-
|
|
21426
|
-
|
|
21427
|
-
text: "Not connected"
|
|
21428
|
-
}] };
|
|
21429
|
-
}
|
|
21518
|
+
if (!treeMap) return { content: [{
|
|
21519
|
+
type: "text",
|
|
21520
|
+
text: "Not connected"
|
|
21521
|
+
}] };
|
|
21430
21522
|
const raw = treeMap.get(id);
|
|
21431
|
-
if (!raw) {
|
|
21432
|
-
|
|
21433
|
-
|
|
21434
|
-
|
|
21435
|
-
text: `Document ${id} not found`
|
|
21436
|
-
}] };
|
|
21437
|
-
}
|
|
21523
|
+
if (!raw) return { content: [{
|
|
21524
|
+
type: "text",
|
|
21525
|
+
text: `Document ${id} not found`
|
|
21526
|
+
}] };
|
|
21438
21527
|
const entry = toPlain(raw);
|
|
21439
21528
|
treeMap.set(id, {
|
|
21440
21529
|
...entry,
|
|
@@ -21442,7 +21531,6 @@ function registerTreeTools(mcp, server) {
|
|
|
21442
21531
|
order: order ?? Date.now(),
|
|
21443
21532
|
updatedAt: Date.now()
|
|
21444
21533
|
});
|
|
21445
|
-
server.setActiveToolCall(null);
|
|
21446
21534
|
return { content: [{
|
|
21447
21535
|
type: "text",
|
|
21448
21536
|
text: `Moved ${id} to parent ${newParentId}`
|
|
@@ -21457,13 +21545,10 @@ function registerTreeTools(mcp, server) {
|
|
|
21457
21545
|
const treeMap = server.getTreeMap();
|
|
21458
21546
|
const trashMap = server.getTrashMap();
|
|
21459
21547
|
const rootDoc = server.rootDocument;
|
|
21460
|
-
if (!treeMap || !trashMap || !rootDoc) {
|
|
21461
|
-
|
|
21462
|
-
|
|
21463
|
-
|
|
21464
|
-
text: "Not connected"
|
|
21465
|
-
}] };
|
|
21466
|
-
}
|
|
21548
|
+
if (!treeMap || !trashMap || !rootDoc) return { content: [{
|
|
21549
|
+
type: "text",
|
|
21550
|
+
text: "Not connected"
|
|
21551
|
+
}] };
|
|
21467
21552
|
const toDelete = [id, ...descendantsOf(readEntries$1(treeMap), id).map((e) => e.id)];
|
|
21468
21553
|
const now = Date.now();
|
|
21469
21554
|
rootDoc.transact(() => {
|
|
@@ -21482,7 +21567,6 @@ function registerTreeTools(mcp, server) {
|
|
|
21482
21567
|
treeMap.delete(nid);
|
|
21483
21568
|
}
|
|
21484
21569
|
});
|
|
21485
|
-
server.setActiveToolCall(null);
|
|
21486
21570
|
return { content: [{
|
|
21487
21571
|
type: "text",
|
|
21488
21572
|
text: `Deleted ${toDelete.length} document(s)`
|
|
@@ -21498,28 +21582,21 @@ function registerTreeTools(mcp, server) {
|
|
|
21498
21582
|
target: id
|
|
21499
21583
|
});
|
|
21500
21584
|
const treeMap = server.getTreeMap();
|
|
21501
|
-
if (!treeMap) {
|
|
21502
|
-
|
|
21503
|
-
|
|
21504
|
-
|
|
21505
|
-
text: "Not connected"
|
|
21506
|
-
}] };
|
|
21507
|
-
}
|
|
21585
|
+
if (!treeMap) return { content: [{
|
|
21586
|
+
type: "text",
|
|
21587
|
+
text: "Not connected"
|
|
21588
|
+
}] };
|
|
21508
21589
|
const raw = treeMap.get(id);
|
|
21509
|
-
if (!raw) {
|
|
21510
|
-
|
|
21511
|
-
|
|
21512
|
-
|
|
21513
|
-
text: `Document ${id} not found`
|
|
21514
|
-
}] };
|
|
21515
|
-
}
|
|
21590
|
+
if (!raw) return { content: [{
|
|
21591
|
+
type: "text",
|
|
21592
|
+
text: `Document ${id} not found`
|
|
21593
|
+
}] };
|
|
21516
21594
|
const entry = toPlain(raw);
|
|
21517
21595
|
treeMap.set(id, {
|
|
21518
21596
|
...entry,
|
|
21519
21597
|
type,
|
|
21520
21598
|
updatedAt: Date.now()
|
|
21521
21599
|
});
|
|
21522
|
-
server.setActiveToolCall(null);
|
|
21523
21600
|
return { content: [{
|
|
21524
21601
|
type: "text",
|
|
21525
21602
|
text: `Changed type to "${type}"`
|
|
@@ -22775,7 +22852,6 @@ function registerContentTools(mcp, server) {
|
|
|
22775
22852
|
});
|
|
22776
22853
|
children.sort((a, b) => (treeMap.get(a.id)?.order ?? 0) - (treeMap.get(b.id)?.order ?? 0));
|
|
22777
22854
|
}
|
|
22778
|
-
server.setActiveToolCall(null);
|
|
22779
22855
|
const result = {
|
|
22780
22856
|
label,
|
|
22781
22857
|
type,
|
|
@@ -22788,7 +22864,6 @@ function registerContentTools(mcp, server) {
|
|
|
22788
22864
|
text: JSON.stringify(result, null, 2)
|
|
22789
22865
|
}] };
|
|
22790
22866
|
} catch (error) {
|
|
22791
|
-
server.setActiveToolCall(null);
|
|
22792
22867
|
return {
|
|
22793
22868
|
content: [{
|
|
22794
22869
|
type: "text",
|
|
@@ -22838,13 +22913,11 @@ function registerContentTools(mcp, server) {
|
|
|
22838
22913
|
populateYDocFromMarkdown(fragment, body || markdown, title || "Untitled");
|
|
22839
22914
|
server.setFocusedDoc(docId);
|
|
22840
22915
|
server.setDocCursor(docId, fragment.length);
|
|
22841
|
-
server.setActiveToolCall(null);
|
|
22842
22916
|
return { content: [{
|
|
22843
22917
|
type: "text",
|
|
22844
22918
|
text: `Document ${docId} updated (${writeMode} mode)`
|
|
22845
22919
|
}] };
|
|
22846
22920
|
} catch (error) {
|
|
22847
|
-
server.setActiveToolCall(null);
|
|
22848
22921
|
return {
|
|
22849
22922
|
content: [{
|
|
22850
22923
|
type: "text",
|
|
@@ -22866,22 +22939,15 @@ function registerMetaTools(mcp, server) {
|
|
|
22866
22939
|
target: docId
|
|
22867
22940
|
});
|
|
22868
22941
|
const treeMap = server.getTreeMap();
|
|
22869
|
-
if (!treeMap) {
|
|
22870
|
-
|
|
22871
|
-
|
|
22872
|
-
|
|
22873
|
-
text: "Not connected"
|
|
22874
|
-
}] };
|
|
22875
|
-
}
|
|
22942
|
+
if (!treeMap) return { content: [{
|
|
22943
|
+
type: "text",
|
|
22944
|
+
text: "Not connected"
|
|
22945
|
+
}] };
|
|
22876
22946
|
const entry = treeMap.get(docId);
|
|
22877
|
-
if (!entry) {
|
|
22878
|
-
|
|
22879
|
-
|
|
22880
|
-
|
|
22881
|
-
text: `Document ${docId} not found`
|
|
22882
|
-
}] };
|
|
22883
|
-
}
|
|
22884
|
-
server.setActiveToolCall(null);
|
|
22947
|
+
if (!entry) return { content: [{
|
|
22948
|
+
type: "text",
|
|
22949
|
+
text: `Document ${docId} not found`
|
|
22950
|
+
}] };
|
|
22885
22951
|
return { content: [{
|
|
22886
22952
|
type: "text",
|
|
22887
22953
|
text: JSON.stringify({
|
|
@@ -22894,7 +22960,7 @@ function registerMetaTools(mcp, server) {
|
|
|
22894
22960
|
});
|
|
22895
22961
|
mcp.tool("update_metadata", "Update metadata fields on a document. Merges the provided fields into existing metadata.", {
|
|
22896
22962
|
docId: zod.z.string().describe("Document ID."),
|
|
22897
|
-
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, timeStart/timeEnd, tags (string[]), checked (bool), priority (0=none,1=low,2=med,3=high,4=urgent), status, rating (0-5), url, email, phone, number, unit, subtitle, note, taskProgress (0-100), members ({id,label}[]), coverUploadId, coverDocId, dateTaken. Geo/Map (children): geoType (\"marker\"|\"line\"|\"measure\"), geoLat, geoLng, geoDescription. Spatial 3D (children, plugin: spatial): spShape (\"box\"|\"sphere\"|\"cylinder\"|\"cone\"|\"plane\"|\"torus\"|\"glb\"), spX/spY/spZ, spRX/spRY/spRZ (deg), spSX/spSY/spSZ (scale), spOpacity (0-100), spModelUploadId, spModelDocId — spatial uses the universal `color` key, NOT spColor. Dashboard (children): deskX, deskY, deskZ, deskMode (\"icon\"|\"widget-sm\"|\"widget-lg\"). Mindmap-layout (children): mmX, mmY. Graph-layout (children): graphX, graphY, graphPinned. Slides (children): slidesTransition (\"none\"|\"fade\"|\"slide\"). Coder (children, plugin: coder): fileType (\"vue\"|\"ts\"|\"js\"|\"css\"|\"json\"|\"folder\"), entry (bool). Cell formatting (sheets cells): bold, italic, textColor, bgColor, align (\"left\"|\"center\"|\"right\"), formula. Renderer config (on the PAGE doc itself, not children): kanbanColumnWidth (\"narrow\"|\"default\"|\"wide\"), galleryColumns (1-6), galleryAspect (\"square\"|\"4:3\"|\"3:2\"|\"16:9\"|\"free\"), galleryCardStyle (\"default\"|\"compact\"|\"detailed\"), galleryShowLabels, gallerySortBy (\"manual\"|\"date\"|\"name\"|\"rating\"), calendarView (\"month\"|\"week\"|\"day\"), calendarWeekStart (\"sun\"|\"mon\"), calendarShowWeekNumbers, tableMode (\"hierarchy\"|\"flat\"), tableSortKey, tableSortDir (\"asc\"|\"desc\"), timelineZoom (\"week\"|\"month\"|\"quarter\"), timelinePixelsPerDay, timelineCenterDate (ISO date), checklistFilter (\"all\"|\"active\"|\"completed\"), checklistSort (\"manual\"|\"priority\"|\"due\"), mapShowLabels, graphSpacing (\"compact\"|\"default\"|\"spacious\"), graphShowLabels, graphEdgeThickness (\"thin\"|\"normal\"|\"thick\"), showRefEdges, mmSpacing, spatialGridVisible, slidesTheme (\"dark\"|\"light\"), chartType (\"bar\"|\"stacked bar\"|\"line\"|\"donut\"|\"treemap\"), chartMetric (\"value\"|\"type\"|\"tag\"|\"status\"|\"priority\"|\"activity\"|\"completion\"), chartColorScheme (\"default\"|\"warm\"|\"cool\"|\"mono\"), chartLimit (3-30), chartShowLegend, chartShowValues, sheetsDefaultColWidth (40-500), sheetsDefaultRowHeight (20-100), sheetsShowGridlines, sheetsFreezeRows, sheetsFreezeCols, mediaRepeat (\"off\"|\"all\"|\"one\"), mediaShuffle. Set a key to null to clear it.")
|
|
22963
|
+
meta: zod.z.record(zod.z.string(), 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, timeStart/timeEnd, tags (string[]), checked (bool), priority (0=none,1=low,2=med,3=high,4=urgent), status, rating (0-5), url, email, phone, number, unit, subtitle, note, taskProgress (0-100), members ({id,label}[]), coverUploadId, coverDocId, dateTaken. Geo/Map (children): geoType (\"marker\"|\"line\"|\"measure\"), geoLat, geoLng, geoDescription. Spatial 3D (children, plugin: spatial): spShape (\"box\"|\"sphere\"|\"cylinder\"|\"cone\"|\"plane\"|\"torus\"|\"glb\"), spX/spY/spZ, spRX/spRY/spRZ (deg), spSX/spSY/spSZ (scale), spOpacity (0-100), spModelUploadId, spModelDocId — spatial uses the universal `color` key, NOT spColor. Dashboard (children): deskX, deskY, deskZ, deskMode (\"icon\"|\"widget-sm\"|\"widget-lg\"). Mindmap-layout (children): mmX, mmY. Graph-layout (children): graphX, graphY, graphPinned. Slides (children): slidesTransition (\"none\"|\"fade\"|\"slide\"). Coder (children, plugin: coder): fileType (\"vue\"|\"ts\"|\"js\"|\"css\"|\"json\"|\"folder\"), entry (bool). Cell formatting (sheets cells): bold, italic, textColor, bgColor, align (\"left\"|\"center\"|\"right\"), formula. Renderer config (on the PAGE doc itself, not children): kanbanColumnWidth (\"narrow\"|\"default\"|\"wide\"), galleryColumns (1-6), galleryAspect (\"square\"|\"4:3\"|\"3:2\"|\"16:9\"|\"free\"), galleryCardStyle (\"default\"|\"compact\"|\"detailed\"), galleryShowLabels, gallerySortBy (\"manual\"|\"date\"|\"name\"|\"rating\"), calendarView (\"month\"|\"week\"|\"day\"), calendarWeekStart (\"sun\"|\"mon\"), calendarShowWeekNumbers, tableMode (\"hierarchy\"|\"flat\"), tableSortKey, tableSortDir (\"asc\"|\"desc\"), timelineZoom (\"week\"|\"month\"|\"quarter\"), timelinePixelsPerDay, timelineCenterDate (ISO date), checklistFilter (\"all\"|\"active\"|\"completed\"), checklistSort (\"manual\"|\"priority\"|\"due\"), mapShowLabels, graphSpacing (\"compact\"|\"default\"|\"spacious\"), graphShowLabels, graphEdgeThickness (\"thin\"|\"normal\"|\"thick\"), showRefEdges, mmSpacing, spatialGridVisible, slidesTheme (\"dark\"|\"light\"), chartType (\"bar\"|\"stacked bar\"|\"line\"|\"donut\"|\"treemap\"), chartMetric (\"value\"|\"type\"|\"tag\"|\"status\"|\"priority\"|\"activity\"|\"completion\"), chartColorScheme (\"default\"|\"warm\"|\"cool\"|\"mono\"), chartLimit (3-30), chartShowLegend, chartShowValues, sheetsDefaultColWidth (40-500), sheetsDefaultRowHeight (20-100), sheetsShowGridlines, sheetsFreezeRows, sheetsFreezeCols, mediaRepeat (\"off\"|\"all\"|\"one\"), mediaShuffle. Set a key to null to clear it.")
|
|
22898
22964
|
}, async ({ docId, meta }) => {
|
|
22899
22965
|
server.setAutoStatus("writing", docId);
|
|
22900
22966
|
server.setActiveToolCall({
|
|
@@ -22902,21 +22968,15 @@ function registerMetaTools(mcp, server) {
|
|
|
22902
22968
|
target: docId
|
|
22903
22969
|
});
|
|
22904
22970
|
const treeMap = server.getTreeMap();
|
|
22905
|
-
if (!treeMap) {
|
|
22906
|
-
|
|
22907
|
-
|
|
22908
|
-
|
|
22909
|
-
text: "Not connected"
|
|
22910
|
-
}] };
|
|
22911
|
-
}
|
|
22971
|
+
if (!treeMap) return { content: [{
|
|
22972
|
+
type: "text",
|
|
22973
|
+
text: "Not connected"
|
|
22974
|
+
}] };
|
|
22912
22975
|
const entry = treeMap.get(docId);
|
|
22913
|
-
if (!entry) {
|
|
22914
|
-
|
|
22915
|
-
|
|
22916
|
-
|
|
22917
|
-
text: `Document ${docId} not found`
|
|
22918
|
-
}] };
|
|
22919
|
-
}
|
|
22976
|
+
if (!entry) return { content: [{
|
|
22977
|
+
type: "text",
|
|
22978
|
+
text: `Document ${docId} not found`
|
|
22979
|
+
}] };
|
|
22920
22980
|
treeMap.set(docId, {
|
|
22921
22981
|
...entry,
|
|
22922
22982
|
meta: {
|
|
@@ -22925,7 +22985,6 @@ function registerMetaTools(mcp, server) {
|
|
|
22925
22985
|
},
|
|
22926
22986
|
updatedAt: Date.now()
|
|
22927
22987
|
});
|
|
22928
|
-
server.setActiveToolCall(null);
|
|
22929
22988
|
return { content: [{
|
|
22930
22989
|
type: "text",
|
|
22931
22990
|
text: `Metadata updated for ${docId}`
|
|
@@ -22940,6 +22999,11 @@ function registerMetaTools(mcp, server) {
|
|
|
22940
22999
|
*/
|
|
22941
23000
|
function registerFileTools(mcp, server) {
|
|
22942
23001
|
mcp.tool("list_uploads", "List file attachments for a document.", { docId: zod.z.string().describe("Document ID.") }, async ({ docId }) => {
|
|
23002
|
+
server.setAutoStatus("reading", docId);
|
|
23003
|
+
server.setActiveToolCall({
|
|
23004
|
+
name: "list_uploads",
|
|
23005
|
+
target: docId
|
|
23006
|
+
});
|
|
22943
23007
|
try {
|
|
22944
23008
|
const uploads = await server.client.listUploads(docId);
|
|
22945
23009
|
return { content: [{
|
|
@@ -22961,6 +23025,11 @@ function registerFileTools(mcp, server) {
|
|
|
22961
23025
|
filePath: zod.z.string().describe("Absolute path to the local file to upload."),
|
|
22962
23026
|
filename: zod.z.string().optional().describe("Override filename (defaults to basename of filePath).")
|
|
22963
23027
|
}, async ({ docId, filePath, filename }) => {
|
|
23028
|
+
server.setAutoStatus("uploading", docId);
|
|
23029
|
+
server.setActiveToolCall({
|
|
23030
|
+
name: "upload_file",
|
|
23031
|
+
target: node_path.basename(filePath)
|
|
23032
|
+
});
|
|
22964
23033
|
try {
|
|
22965
23034
|
const resolvedPath = node_path.resolve(filePath);
|
|
22966
23035
|
const data = node_fs.readFileSync(resolvedPath);
|
|
@@ -22986,6 +23055,11 @@ function registerFileTools(mcp, server) {
|
|
|
22986
23055
|
uploadId: zod.z.string().describe("Upload ID to download."),
|
|
22987
23056
|
saveTo: zod.z.string().describe("Absolute local file path to save the download.")
|
|
22988
23057
|
}, async ({ docId, uploadId, saveTo }) => {
|
|
23058
|
+
server.setAutoStatus("reading", docId);
|
|
23059
|
+
server.setActiveToolCall({
|
|
23060
|
+
name: "download_file",
|
|
23061
|
+
target: node_path.basename(saveTo)
|
|
23062
|
+
});
|
|
22989
23063
|
try {
|
|
22990
23064
|
const blob = await server.client.getUpload(docId, uploadId);
|
|
22991
23065
|
const buffer = Buffer.from(await blob.arrayBuffer());
|
|
@@ -23009,6 +23083,11 @@ function registerFileTools(mcp, server) {
|
|
|
23009
23083
|
docId: zod.z.string().describe("Document ID."),
|
|
23010
23084
|
uploadId: zod.z.string().describe("Upload ID to delete.")
|
|
23011
23085
|
}, async ({ docId, uploadId }) => {
|
|
23086
|
+
server.setAutoStatus("writing", docId);
|
|
23087
|
+
server.setActiveToolCall({
|
|
23088
|
+
name: "delete_file",
|
|
23089
|
+
target: uploadId
|
|
23090
|
+
});
|
|
23012
23091
|
try {
|
|
23013
23092
|
await server.client.deleteUpload(docId, uploadId);
|
|
23014
23093
|
return { content: [{
|
|
@@ -23050,6 +23129,10 @@ function registerAwarenessTools(mcp, server) {
|
|
|
23050
23129
|
docId: zod.z.string().describe("Document ID to set awareness on."),
|
|
23051
23130
|
fields: zod.z.record(zod.z.string(), zod.z.unknown()).describe("Key-value pairs to set on the child document's awareness state. Use namespaced keys like \"kanban:hovering\", \"table:editing\", \"slides:viewing\", \"outline:editing\", \"calendar:focused\", \"gallery:focused\", \"timeline:focused\", \"graph:focused\", \"map:focused\", \"doc:scroll\". Set a key to null to clear it.")
|
|
23052
23131
|
}, async ({ docId, fields }) => {
|
|
23132
|
+
server.setActiveToolCall({
|
|
23133
|
+
name: "set_doc_awareness",
|
|
23134
|
+
target: docId
|
|
23135
|
+
});
|
|
23053
23136
|
try {
|
|
23054
23137
|
const provider = await server.getChildProvider(docId);
|
|
23055
23138
|
for (const [key, value] of Object.entries(fields)) provider.awareness.setLocalStateField(key, value ?? null);
|
|
@@ -23068,6 +23151,7 @@ function registerAwarenessTools(mcp, server) {
|
|
|
23068
23151
|
}
|
|
23069
23152
|
});
|
|
23070
23153
|
mcp.tool("poll_inbox", "Check the \"AI Inbox\" document for pending instructions from humans. Returns the inbox content and any pending task sub-documents. Create the inbox as a doc called \"AI Inbox\" under the hub doc if it does not exist yet. Note: channel-based watching via watch_chat is preferred for real-time use.", {}, async () => {
|
|
23154
|
+
server.setActiveToolCall({ name: "poll_inbox" });
|
|
23071
23155
|
try {
|
|
23072
23156
|
const treeMap = server.getTreeMap();
|
|
23073
23157
|
const rootDocId = server.rootDocId;
|
|
@@ -23114,6 +23198,10 @@ function registerAwarenessTools(mcp, server) {
|
|
|
23114
23198
|
}
|
|
23115
23199
|
});
|
|
23116
23200
|
mcp.tool("list_connected_users", "List all connected users and their awareness state. Shows who is online and what they are doing.", { docId: zod.z.string().optional().describe("If provided, list users connected to this specific document. Otherwise lists users from root awareness.") }, async ({ docId }) => {
|
|
23201
|
+
server.setActiveToolCall({
|
|
23202
|
+
name: "list_connected_users",
|
|
23203
|
+
target: docId
|
|
23204
|
+
});
|
|
23117
23205
|
try {
|
|
23118
23206
|
let awareness;
|
|
23119
23207
|
if (docId) awareness = (await server.getChildProvider(docId)).awareness;
|
|
@@ -23158,16 +23246,13 @@ function registerChannelTools(mcp, server) {
|
|
|
23158
23246
|
});
|
|
23159
23247
|
const treeMap = server.getTreeMap();
|
|
23160
23248
|
const rootDoc = server.rootDocument;
|
|
23161
|
-
if (!treeMap || !rootDoc) {
|
|
23162
|
-
|
|
23163
|
-
|
|
23164
|
-
|
|
23165
|
-
|
|
23166
|
-
|
|
23167
|
-
|
|
23168
|
-
isError: true
|
|
23169
|
-
};
|
|
23170
|
-
}
|
|
23249
|
+
if (!treeMap || !rootDoc) return {
|
|
23250
|
+
content: [{
|
|
23251
|
+
type: "text",
|
|
23252
|
+
text: "Not connected"
|
|
23253
|
+
}],
|
|
23254
|
+
isError: true
|
|
23255
|
+
};
|
|
23171
23256
|
const label = `AI Reply — ${(/* @__PURE__ */ new Date()).toISOString().replace("T", " ").slice(0, 19)}: ${text.slice(0, 40).replace(/\n/g, " ")}`;
|
|
23172
23257
|
const replyId = crypto.randomUUID();
|
|
23173
23258
|
const now = Date.now();
|
|
@@ -23183,7 +23268,6 @@ function registerChannelTools(mcp, server) {
|
|
|
23183
23268
|
});
|
|
23184
23269
|
populateYDocFromMarkdown((await server.getChildProvider(replyId)).document, text);
|
|
23185
23270
|
if (task_id) server.clearAiTask(task_id);
|
|
23186
|
-
server.setActiveToolCall(null);
|
|
23187
23271
|
return { content: [{
|
|
23188
23272
|
type: "text",
|
|
23189
23273
|
text: JSON.stringify({
|
|
@@ -23192,7 +23276,6 @@ function registerChannelTools(mcp, server) {
|
|
|
23192
23276
|
})
|
|
23193
23277
|
}] };
|
|
23194
23278
|
} catch (error) {
|
|
23195
|
-
server.setActiveToolCall(null);
|
|
23196
23279
|
return {
|
|
23197
23280
|
content: [{
|
|
23198
23281
|
type: "text",
|
|
@@ -23215,14 +23298,15 @@ function registerChannelTools(mcp, server) {
|
|
|
23215
23298
|
}],
|
|
23216
23299
|
isError: true
|
|
23217
23300
|
};
|
|
23301
|
+
const normalized = text.replace(/\\r\\n/g, "\n").replace(/\\n/g, "\n").replace(/\\t/g, " ").replace(/\\r/g, "\n");
|
|
23302
|
+
server.setAutoStatus(null);
|
|
23303
|
+
server.sendTypingIndicator(channel);
|
|
23218
23304
|
rootProvider.sendStateless(JSON.stringify({
|
|
23219
23305
|
type: "chat:send",
|
|
23220
23306
|
channel,
|
|
23221
|
-
content:
|
|
23307
|
+
content: normalized,
|
|
23222
23308
|
sender_name: server.agentName
|
|
23223
23309
|
}));
|
|
23224
|
-
server.setAutoStatus(null);
|
|
23225
|
-
server.setActiveToolCall(null);
|
|
23226
23310
|
return { content: [{
|
|
23227
23311
|
type: "text",
|
|
23228
23312
|
text: `Sent to ${channel}`
|
|
@@ -23377,16 +23461,13 @@ function registerSvgTools(mcp, server) {
|
|
|
23377
23461
|
target: docId
|
|
23378
23462
|
});
|
|
23379
23463
|
const cleanSvg = sanitizeSvg(svg);
|
|
23380
|
-
if (!cleanSvg) {
|
|
23381
|
-
|
|
23382
|
-
|
|
23383
|
-
|
|
23384
|
-
|
|
23385
|
-
|
|
23386
|
-
|
|
23387
|
-
isError: true
|
|
23388
|
-
};
|
|
23389
|
-
}
|
|
23464
|
+
if (!cleanSvg) return {
|
|
23465
|
+
content: [{
|
|
23466
|
+
type: "text",
|
|
23467
|
+
text: "Error: SVG markup was empty or entirely stripped by sanitizer."
|
|
23468
|
+
}],
|
|
23469
|
+
isError: true
|
|
23470
|
+
};
|
|
23390
23471
|
const doc = (await server.getChildProvider(docId)).document;
|
|
23391
23472
|
const fragment = doc.getXmlFragment("default");
|
|
23392
23473
|
doc.transact(() => {
|
|
@@ -23397,13 +23478,11 @@ function registerSvgTools(mcp, server) {
|
|
|
23397
23478
|
fragment.insert(insertPos, [el]);
|
|
23398
23479
|
});
|
|
23399
23480
|
server.setFocusedDoc(docId);
|
|
23400
|
-
server.setActiveToolCall(null);
|
|
23401
23481
|
return { content: [{
|
|
23402
23482
|
type: "text",
|
|
23403
23483
|
text: `SVG inserted into document ${docId}${title ? ` ("${title}")` : ""}`
|
|
23404
23484
|
}] };
|
|
23405
23485
|
} catch (error) {
|
|
23406
|
-
server.setActiveToolCall(null);
|
|
23407
23486
|
return {
|
|
23408
23487
|
content: [{
|
|
23409
23488
|
type: "text",
|
|
@@ -24207,6 +24286,9 @@ var HookBridge = class {
|
|
|
24207
24286
|
}
|
|
24208
24287
|
routeEvent(payload) {
|
|
24209
24288
|
switch (payload.hook_event_name) {
|
|
24289
|
+
case "UserPromptSubmit":
|
|
24290
|
+
this.onUserPromptSubmit();
|
|
24291
|
+
break;
|
|
24210
24292
|
case "PreToolUse":
|
|
24211
24293
|
this.onPreToolUse(payload);
|
|
24212
24294
|
break;
|
|
@@ -24217,13 +24299,18 @@ var HookBridge = class {
|
|
|
24217
24299
|
this.onSubagentStart(payload);
|
|
24218
24300
|
break;
|
|
24219
24301
|
case "SubagentStop":
|
|
24220
|
-
this.onSubagentStop(
|
|
24302
|
+
this.onSubagentStop();
|
|
24221
24303
|
break;
|
|
24222
24304
|
case "Stop":
|
|
24223
24305
|
this.onStop();
|
|
24224
24306
|
break;
|
|
24225
24307
|
}
|
|
24226
24308
|
}
|
|
24309
|
+
/** New user turn — reset any lingering status/tool state from the previous turn. */
|
|
24310
|
+
onUserPromptSubmit() {
|
|
24311
|
+
this.server.setAutoStatus(null);
|
|
24312
|
+
this.server.setActiveToolCall(null);
|
|
24313
|
+
}
|
|
24227
24314
|
onPreToolUse(payload) {
|
|
24228
24315
|
const toolName = payload.tool_name ?? "";
|
|
24229
24316
|
if (toolName.startsWith("mcp__abracadabra__")) return;
|
|
@@ -24235,7 +24322,6 @@ var HookBridge = class {
|
|
|
24235
24322
|
}
|
|
24236
24323
|
onPostToolUse(payload) {
|
|
24237
24324
|
if ((payload.tool_name ?? "").startsWith("mcp__abracadabra__")) return;
|
|
24238
|
-
this.server.setAutoStatus("thinking");
|
|
24239
24325
|
}
|
|
24240
24326
|
onSubagentStart(payload) {
|
|
24241
24327
|
const agentType = payload.agent_type ?? "agent";
|
|
@@ -24245,9 +24331,7 @@ var HookBridge = class {
|
|
|
24245
24331
|
});
|
|
24246
24332
|
this.server.setAutoStatus("thinking");
|
|
24247
24333
|
}
|
|
24248
|
-
onSubagentStop(
|
|
24249
|
-
this.server.setAutoStatus("thinking");
|
|
24250
|
-
}
|
|
24334
|
+
onSubagentStop() {}
|
|
24251
24335
|
onStop() {
|
|
24252
24336
|
this.server.setAutoStatus(null);
|
|
24253
24337
|
this.server.setActiveToolCall(null);
|
|
@@ -24260,11 +24344,19 @@ var HookBridge = class {
|
|
|
24260
24344
|
* Abracadabra MCP Server — entry point.
|
|
24261
24345
|
*
|
|
24262
24346
|
* Environment variables:
|
|
24263
|
-
* ABRA_URL
|
|
24347
|
+
* ABRA_URL (required) — Server URL (e.g. http://localhost:1234)
|
|
24264
24348
|
* ABRA_AGENT_NAME — Display name (default: "AI Assistant")
|
|
24265
24349
|
* ABRA_AGENT_COLOR — HSL color for presence (default: "hsl(270, 80%, 60%)")
|
|
24266
24350
|
* ABRA_INVITE_CODE — Invite code for first-run registration (grants role)
|
|
24267
24351
|
* ABRA_KEY_FILE — Path to Ed25519 key file (default: ~/.abracadabra/agent.key)
|
|
24352
|
+
* ABRA_AGENT_TRIGGER_MODE — When to respond in group chats:
|
|
24353
|
+
* all → every message (legacy)
|
|
24354
|
+
* mention → only when @<alias> is used
|
|
24355
|
+
* task → only ai:task awareness events
|
|
24356
|
+
* mention+task → mention OR ai:task (default)
|
|
24357
|
+
* DMs always trigger regardless of mode.
|
|
24358
|
+
* ABRA_AGENT_MENTION_ALIASES — Comma-separated aliases for @mentions
|
|
24359
|
+
* (default: [ABRA_AGENT_NAME])
|
|
24268
24360
|
*/
|
|
24269
24361
|
async function main() {
|
|
24270
24362
|
const url = process.env.ABRA_URL;
|
|
@@ -24272,13 +24364,27 @@ async function main() {
|
|
|
24272
24364
|
console.error("Missing required environment variable: ABRA_URL");
|
|
24273
24365
|
process.exit(1);
|
|
24274
24366
|
}
|
|
24367
|
+
const rawMode = (process.env.ABRA_AGENT_TRIGGER_MODE ?? "mention+task").trim().toLowerCase();
|
|
24368
|
+
const validModes = [
|
|
24369
|
+
"all",
|
|
24370
|
+
"mention",
|
|
24371
|
+
"task",
|
|
24372
|
+
"mention+task"
|
|
24373
|
+
];
|
|
24374
|
+
const triggerMode = validModes.includes(rawMode) ? rawMode : "mention+task";
|
|
24375
|
+
if (rawMode && !validModes.includes(rawMode)) console.error(`[abracadabra-mcp] Invalid ABRA_AGENT_TRIGGER_MODE="${rawMode}", falling back to "mention+task"`);
|
|
24376
|
+
const aliasEnv = process.env.ABRA_AGENT_MENTION_ALIASES;
|
|
24377
|
+
const mentionAliases = aliasEnv ? aliasEnv.split(",").map((a) => a.trim()).filter((a) => a.length > 0) : void 0;
|
|
24275
24378
|
const server = new AbracadabraMCPServer({
|
|
24276
24379
|
url,
|
|
24277
24380
|
agentName: process.env.ABRA_AGENT_NAME,
|
|
24278
24381
|
agentColor: process.env.ABRA_AGENT_COLOR,
|
|
24279
24382
|
inviteCode: process.env.ABRA_INVITE_CODE,
|
|
24280
|
-
keyFile: process.env.ABRA_KEY_FILE
|
|
24383
|
+
keyFile: process.env.ABRA_KEY_FILE,
|
|
24384
|
+
triggerMode,
|
|
24385
|
+
mentionAliases
|
|
24281
24386
|
});
|
|
24387
|
+
console.error(`[abracadabra-mcp] Trigger mode: ${triggerMode}; aliases: ${server.mentionAliases.join(", ")}`);
|
|
24282
24388
|
const mcp = new McpServer({
|
|
24283
24389
|
name: "abracadabra",
|
|
24284
24390
|
version: "1.0.0"
|