@abraca/mcp 1.8.0 → 1.9.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 +316 -190
- package/dist/abracadabra-mcp.cjs.map +1 -1
- package/dist/abracadabra-mcp.esm.js +316 -190
- package/dist/abracadabra-mcp.esm.js.map +1 -1
- package/dist/index.d.ts +30 -1
- package/package.json +1 -1
- package/src/converters/markdownToYjs.ts +30 -13
- package/src/converters/yjsToMarkdown.ts +10 -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}"`
|
|
@@ -21747,14 +21824,12 @@ function parseInline(text) {
|
|
|
21747
21824
|
text: kbdProps["value"] || "",
|
|
21748
21825
|
attrs: { kbd: { value: kbdProps["value"] || "" } }
|
|
21749
21826
|
});
|
|
21750
|
-
} else if (match[5] !== void 0) {
|
|
21751
|
-
|
|
21752
|
-
|
|
21753
|
-
|
|
21754
|
-
|
|
21755
|
-
|
|
21756
|
-
});
|
|
21757
|
-
} else if (match[7] !== void 0) tokens.push({
|
|
21827
|
+
} else if (match[5] !== void 0) tokens.push({
|
|
21828
|
+
text: "",
|
|
21829
|
+
node: "docLink",
|
|
21830
|
+
nodeAttrs: { docId: match[5] }
|
|
21831
|
+
});
|
|
21832
|
+
else if (match[7] !== void 0) tokens.push({
|
|
21758
21833
|
text: match[7],
|
|
21759
21834
|
attrs: { strike: true }
|
|
21760
21835
|
});
|
|
@@ -21781,7 +21856,7 @@ function parseInline(text) {
|
|
|
21781
21856
|
lastIndex = match.index + match[0].length;
|
|
21782
21857
|
}
|
|
21783
21858
|
if (lastIndex < stripped.length) tokens.push({ text: stripped.slice(lastIndex) });
|
|
21784
|
-
return tokens.filter((t) => t.text.length > 0);
|
|
21859
|
+
return tokens.filter((t) => t.node || t.text.length > 0);
|
|
21785
21860
|
}
|
|
21786
21861
|
function parseTableRow(line) {
|
|
21787
21862
|
const parts = line.split("|");
|
|
@@ -21909,11 +21984,14 @@ function parseBlocks(markdown) {
|
|
|
21909
21984
|
i++;
|
|
21910
21985
|
continue;
|
|
21911
21986
|
}
|
|
21912
|
-
const docEmbedMatch = line.match(/^!\[\[([^\]|]+?)(?:\|[^\]]*?)?\]\]\s*$/);
|
|
21987
|
+
const docEmbedMatch = line.match(/^!\[\[([^\]|]+?)(?:\|[^\]]*?)?\]\](\{[^}]*\})?\s*$/);
|
|
21913
21988
|
if (docEmbedMatch) {
|
|
21989
|
+
const props = parseMdcProps(docEmbedMatch[2]);
|
|
21990
|
+
const seamless = "seamless" in props || props["seamless"] === "true" || /\{[^}]*\bseamless\b[^}]*\}/.test(docEmbedMatch[2] ?? "");
|
|
21914
21991
|
blocks.push({
|
|
21915
21992
|
type: "docEmbed",
|
|
21916
|
-
docId: docEmbedMatch[1]
|
|
21993
|
+
docId: docEmbedMatch[1],
|
|
21994
|
+
seamless: seamless || void 0
|
|
21917
21995
|
});
|
|
21918
21996
|
i++;
|
|
21919
21997
|
continue;
|
|
@@ -22191,13 +22269,22 @@ function parseBlocks(markdown) {
|
|
|
22191
22269
|
return blocks;
|
|
22192
22270
|
}
|
|
22193
22271
|
function fillTextInto(el, tokens) {
|
|
22194
|
-
const filtered = tokens.filter((t) => t.text.length > 0);
|
|
22272
|
+
const filtered = tokens.filter((t) => t.node || t.text.length > 0);
|
|
22195
22273
|
if (!filtered.length) return;
|
|
22196
|
-
const
|
|
22197
|
-
|
|
22274
|
+
const children = filtered.map((tok) => {
|
|
22275
|
+
if (tok.node) {
|
|
22276
|
+
const xe = new yjs.XmlElement(tok.node);
|
|
22277
|
+
if (tok.nodeAttrs) for (const [k, v] of Object.entries(tok.nodeAttrs)) xe.setAttribute(k, v);
|
|
22278
|
+
return xe;
|
|
22279
|
+
}
|
|
22280
|
+
return new yjs.XmlText();
|
|
22281
|
+
});
|
|
22282
|
+
el.insert(0, children);
|
|
22198
22283
|
filtered.forEach((tok, i) => {
|
|
22199
|
-
if (tok.
|
|
22200
|
-
|
|
22284
|
+
if (tok.node) return;
|
|
22285
|
+
const xt = children[i];
|
|
22286
|
+
if (tok.attrs) xt.insert(0, tok.text, tok.attrs);
|
|
22287
|
+
else xt.insert(0, tok.text);
|
|
22201
22288
|
});
|
|
22202
22289
|
}
|
|
22203
22290
|
function blockElName(b) {
|
|
@@ -22444,6 +22531,7 @@ function fillBlock(el, block) {
|
|
|
22444
22531
|
break;
|
|
22445
22532
|
case "docEmbed":
|
|
22446
22533
|
el.setAttribute("docId", block.docId);
|
|
22534
|
+
if (block.seamless) el.setAttribute("seamless", "true");
|
|
22447
22535
|
break;
|
|
22448
22536
|
case "svgEmbed":
|
|
22449
22537
|
el.setAttribute("svg", block.svg);
|
|
@@ -22543,7 +22631,14 @@ function elementTextContent(el) {
|
|
|
22543
22631
|
for (let i = 0; i < el.length; i++) {
|
|
22544
22632
|
const child = el.get(i);
|
|
22545
22633
|
if (child instanceof yjs.XmlText) parts.push(xmlTextToMarkdown(child));
|
|
22546
|
-
else if (child instanceof yjs.XmlElement)
|
|
22634
|
+
else if (child instanceof yjs.XmlElement) {
|
|
22635
|
+
if (child.nodeName === "docLink") {
|
|
22636
|
+
const docId = child.getAttribute("docId");
|
|
22637
|
+
if (docId) parts.push(`[[${docId}]]`);
|
|
22638
|
+
continue;
|
|
22639
|
+
}
|
|
22640
|
+
parts.push(elementTextContent(child));
|
|
22641
|
+
}
|
|
22547
22642
|
}
|
|
22548
22643
|
return parts.join("");
|
|
22549
22644
|
}
|
|
@@ -22572,7 +22667,9 @@ function serializeElement(el, indent = "") {
|
|
|
22572
22667
|
case "table": return serializeTable(el);
|
|
22573
22668
|
case "docEmbed": {
|
|
22574
22669
|
const docId = el.getAttribute("docId");
|
|
22575
|
-
|
|
22670
|
+
if (!docId) return "";
|
|
22671
|
+
const seamlessAttr = el.getAttribute("seamless");
|
|
22672
|
+
return seamlessAttr === true || seamlessAttr === "true" ? `![[${docId}]]{seamless}` : `![[${docId}]]`;
|
|
22576
22673
|
}
|
|
22577
22674
|
case "svgEmbed": {
|
|
22578
22675
|
const svg = el.getAttribute("svg") || "";
|
|
@@ -22775,7 +22872,6 @@ function registerContentTools(mcp, server) {
|
|
|
22775
22872
|
});
|
|
22776
22873
|
children.sort((a, b) => (treeMap.get(a.id)?.order ?? 0) - (treeMap.get(b.id)?.order ?? 0));
|
|
22777
22874
|
}
|
|
22778
|
-
server.setActiveToolCall(null);
|
|
22779
22875
|
const result = {
|
|
22780
22876
|
label,
|
|
22781
22877
|
type,
|
|
@@ -22788,7 +22884,6 @@ function registerContentTools(mcp, server) {
|
|
|
22788
22884
|
text: JSON.stringify(result, null, 2)
|
|
22789
22885
|
}] };
|
|
22790
22886
|
} catch (error) {
|
|
22791
|
-
server.setActiveToolCall(null);
|
|
22792
22887
|
return {
|
|
22793
22888
|
content: [{
|
|
22794
22889
|
type: "text",
|
|
@@ -22838,13 +22933,11 @@ function registerContentTools(mcp, server) {
|
|
|
22838
22933
|
populateYDocFromMarkdown(fragment, body || markdown, title || "Untitled");
|
|
22839
22934
|
server.setFocusedDoc(docId);
|
|
22840
22935
|
server.setDocCursor(docId, fragment.length);
|
|
22841
|
-
server.setActiveToolCall(null);
|
|
22842
22936
|
return { content: [{
|
|
22843
22937
|
type: "text",
|
|
22844
22938
|
text: `Document ${docId} updated (${writeMode} mode)`
|
|
22845
22939
|
}] };
|
|
22846
22940
|
} catch (error) {
|
|
22847
|
-
server.setActiveToolCall(null);
|
|
22848
22941
|
return {
|
|
22849
22942
|
content: [{
|
|
22850
22943
|
type: "text",
|
|
@@ -22866,22 +22959,15 @@ function registerMetaTools(mcp, server) {
|
|
|
22866
22959
|
target: docId
|
|
22867
22960
|
});
|
|
22868
22961
|
const treeMap = server.getTreeMap();
|
|
22869
|
-
if (!treeMap) {
|
|
22870
|
-
|
|
22871
|
-
|
|
22872
|
-
|
|
22873
|
-
text: "Not connected"
|
|
22874
|
-
}] };
|
|
22875
|
-
}
|
|
22962
|
+
if (!treeMap) return { content: [{
|
|
22963
|
+
type: "text",
|
|
22964
|
+
text: "Not connected"
|
|
22965
|
+
}] };
|
|
22876
22966
|
const entry = treeMap.get(docId);
|
|
22877
|
-
if (!entry) {
|
|
22878
|
-
|
|
22879
|
-
|
|
22880
|
-
|
|
22881
|
-
text: `Document ${docId} not found`
|
|
22882
|
-
}] };
|
|
22883
|
-
}
|
|
22884
|
-
server.setActiveToolCall(null);
|
|
22967
|
+
if (!entry) return { content: [{
|
|
22968
|
+
type: "text",
|
|
22969
|
+
text: `Document ${docId} not found`
|
|
22970
|
+
}] };
|
|
22885
22971
|
return { content: [{
|
|
22886
22972
|
type: "text",
|
|
22887
22973
|
text: JSON.stringify({
|
|
@@ -22894,7 +22980,7 @@ function registerMetaTools(mcp, server) {
|
|
|
22894
22980
|
});
|
|
22895
22981
|
mcp.tool("update_metadata", "Update metadata fields on a document. Merges the provided fields into existing metadata.", {
|
|
22896
22982
|
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.")
|
|
22983
|
+
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
22984
|
}, async ({ docId, meta }) => {
|
|
22899
22985
|
server.setAutoStatus("writing", docId);
|
|
22900
22986
|
server.setActiveToolCall({
|
|
@@ -22902,21 +22988,15 @@ function registerMetaTools(mcp, server) {
|
|
|
22902
22988
|
target: docId
|
|
22903
22989
|
});
|
|
22904
22990
|
const treeMap = server.getTreeMap();
|
|
22905
|
-
if (!treeMap) {
|
|
22906
|
-
|
|
22907
|
-
|
|
22908
|
-
|
|
22909
|
-
text: "Not connected"
|
|
22910
|
-
}] };
|
|
22911
|
-
}
|
|
22991
|
+
if (!treeMap) return { content: [{
|
|
22992
|
+
type: "text",
|
|
22993
|
+
text: "Not connected"
|
|
22994
|
+
}] };
|
|
22912
22995
|
const entry = treeMap.get(docId);
|
|
22913
|
-
if (!entry) {
|
|
22914
|
-
|
|
22915
|
-
|
|
22916
|
-
|
|
22917
|
-
text: `Document ${docId} not found`
|
|
22918
|
-
}] };
|
|
22919
|
-
}
|
|
22996
|
+
if (!entry) return { content: [{
|
|
22997
|
+
type: "text",
|
|
22998
|
+
text: `Document ${docId} not found`
|
|
22999
|
+
}] };
|
|
22920
23000
|
treeMap.set(docId, {
|
|
22921
23001
|
...entry,
|
|
22922
23002
|
meta: {
|
|
@@ -22925,7 +23005,6 @@ function registerMetaTools(mcp, server) {
|
|
|
22925
23005
|
},
|
|
22926
23006
|
updatedAt: Date.now()
|
|
22927
23007
|
});
|
|
22928
|
-
server.setActiveToolCall(null);
|
|
22929
23008
|
return { content: [{
|
|
22930
23009
|
type: "text",
|
|
22931
23010
|
text: `Metadata updated for ${docId}`
|
|
@@ -22940,6 +23019,11 @@ function registerMetaTools(mcp, server) {
|
|
|
22940
23019
|
*/
|
|
22941
23020
|
function registerFileTools(mcp, server) {
|
|
22942
23021
|
mcp.tool("list_uploads", "List file attachments for a document.", { docId: zod.z.string().describe("Document ID.") }, async ({ docId }) => {
|
|
23022
|
+
server.setAutoStatus("reading", docId);
|
|
23023
|
+
server.setActiveToolCall({
|
|
23024
|
+
name: "list_uploads",
|
|
23025
|
+
target: docId
|
|
23026
|
+
});
|
|
22943
23027
|
try {
|
|
22944
23028
|
const uploads = await server.client.listUploads(docId);
|
|
22945
23029
|
return { content: [{
|
|
@@ -22961,6 +23045,11 @@ function registerFileTools(mcp, server) {
|
|
|
22961
23045
|
filePath: zod.z.string().describe("Absolute path to the local file to upload."),
|
|
22962
23046
|
filename: zod.z.string().optional().describe("Override filename (defaults to basename of filePath).")
|
|
22963
23047
|
}, async ({ docId, filePath, filename }) => {
|
|
23048
|
+
server.setAutoStatus("uploading", docId);
|
|
23049
|
+
server.setActiveToolCall({
|
|
23050
|
+
name: "upload_file",
|
|
23051
|
+
target: node_path.basename(filePath)
|
|
23052
|
+
});
|
|
22964
23053
|
try {
|
|
22965
23054
|
const resolvedPath = node_path.resolve(filePath);
|
|
22966
23055
|
const data = node_fs.readFileSync(resolvedPath);
|
|
@@ -22986,6 +23075,11 @@ function registerFileTools(mcp, server) {
|
|
|
22986
23075
|
uploadId: zod.z.string().describe("Upload ID to download."),
|
|
22987
23076
|
saveTo: zod.z.string().describe("Absolute local file path to save the download.")
|
|
22988
23077
|
}, async ({ docId, uploadId, saveTo }) => {
|
|
23078
|
+
server.setAutoStatus("reading", docId);
|
|
23079
|
+
server.setActiveToolCall({
|
|
23080
|
+
name: "download_file",
|
|
23081
|
+
target: node_path.basename(saveTo)
|
|
23082
|
+
});
|
|
22989
23083
|
try {
|
|
22990
23084
|
const blob = await server.client.getUpload(docId, uploadId);
|
|
22991
23085
|
const buffer = Buffer.from(await blob.arrayBuffer());
|
|
@@ -23009,6 +23103,11 @@ function registerFileTools(mcp, server) {
|
|
|
23009
23103
|
docId: zod.z.string().describe("Document ID."),
|
|
23010
23104
|
uploadId: zod.z.string().describe("Upload ID to delete.")
|
|
23011
23105
|
}, async ({ docId, uploadId }) => {
|
|
23106
|
+
server.setAutoStatus("writing", docId);
|
|
23107
|
+
server.setActiveToolCall({
|
|
23108
|
+
name: "delete_file",
|
|
23109
|
+
target: uploadId
|
|
23110
|
+
});
|
|
23012
23111
|
try {
|
|
23013
23112
|
await server.client.deleteUpload(docId, uploadId);
|
|
23014
23113
|
return { content: [{
|
|
@@ -23050,6 +23149,10 @@ function registerAwarenessTools(mcp, server) {
|
|
|
23050
23149
|
docId: zod.z.string().describe("Document ID to set awareness on."),
|
|
23051
23150
|
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
23151
|
}, async ({ docId, fields }) => {
|
|
23152
|
+
server.setActiveToolCall({
|
|
23153
|
+
name: "set_doc_awareness",
|
|
23154
|
+
target: docId
|
|
23155
|
+
});
|
|
23053
23156
|
try {
|
|
23054
23157
|
const provider = await server.getChildProvider(docId);
|
|
23055
23158
|
for (const [key, value] of Object.entries(fields)) provider.awareness.setLocalStateField(key, value ?? null);
|
|
@@ -23068,6 +23171,7 @@ function registerAwarenessTools(mcp, server) {
|
|
|
23068
23171
|
}
|
|
23069
23172
|
});
|
|
23070
23173
|
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 () => {
|
|
23174
|
+
server.setActiveToolCall({ name: "poll_inbox" });
|
|
23071
23175
|
try {
|
|
23072
23176
|
const treeMap = server.getTreeMap();
|
|
23073
23177
|
const rootDocId = server.rootDocId;
|
|
@@ -23114,6 +23218,10 @@ function registerAwarenessTools(mcp, server) {
|
|
|
23114
23218
|
}
|
|
23115
23219
|
});
|
|
23116
23220
|
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 }) => {
|
|
23221
|
+
server.setActiveToolCall({
|
|
23222
|
+
name: "list_connected_users",
|
|
23223
|
+
target: docId
|
|
23224
|
+
});
|
|
23117
23225
|
try {
|
|
23118
23226
|
let awareness;
|
|
23119
23227
|
if (docId) awareness = (await server.getChildProvider(docId)).awareness;
|
|
@@ -23158,16 +23266,13 @@ function registerChannelTools(mcp, server) {
|
|
|
23158
23266
|
});
|
|
23159
23267
|
const treeMap = server.getTreeMap();
|
|
23160
23268
|
const rootDoc = server.rootDocument;
|
|
23161
|
-
if (!treeMap || !rootDoc) {
|
|
23162
|
-
|
|
23163
|
-
|
|
23164
|
-
|
|
23165
|
-
|
|
23166
|
-
|
|
23167
|
-
|
|
23168
|
-
isError: true
|
|
23169
|
-
};
|
|
23170
|
-
}
|
|
23269
|
+
if (!treeMap || !rootDoc) return {
|
|
23270
|
+
content: [{
|
|
23271
|
+
type: "text",
|
|
23272
|
+
text: "Not connected"
|
|
23273
|
+
}],
|
|
23274
|
+
isError: true
|
|
23275
|
+
};
|
|
23171
23276
|
const label = `AI Reply — ${(/* @__PURE__ */ new Date()).toISOString().replace("T", " ").slice(0, 19)}: ${text.slice(0, 40).replace(/\n/g, " ")}`;
|
|
23172
23277
|
const replyId = crypto.randomUUID();
|
|
23173
23278
|
const now = Date.now();
|
|
@@ -23183,7 +23288,6 @@ function registerChannelTools(mcp, server) {
|
|
|
23183
23288
|
});
|
|
23184
23289
|
populateYDocFromMarkdown((await server.getChildProvider(replyId)).document, text);
|
|
23185
23290
|
if (task_id) server.clearAiTask(task_id);
|
|
23186
|
-
server.setActiveToolCall(null);
|
|
23187
23291
|
return { content: [{
|
|
23188
23292
|
type: "text",
|
|
23189
23293
|
text: JSON.stringify({
|
|
@@ -23192,7 +23296,6 @@ function registerChannelTools(mcp, server) {
|
|
|
23192
23296
|
})
|
|
23193
23297
|
}] };
|
|
23194
23298
|
} catch (error) {
|
|
23195
|
-
server.setActiveToolCall(null);
|
|
23196
23299
|
return {
|
|
23197
23300
|
content: [{
|
|
23198
23301
|
type: "text",
|
|
@@ -23215,14 +23318,15 @@ function registerChannelTools(mcp, server) {
|
|
|
23215
23318
|
}],
|
|
23216
23319
|
isError: true
|
|
23217
23320
|
};
|
|
23321
|
+
const normalized = text.replace(/\\r\\n/g, "\n").replace(/\\n/g, "\n").replace(/\\t/g, " ").replace(/\\r/g, "\n");
|
|
23322
|
+
server.setAutoStatus(null);
|
|
23323
|
+
server.sendTypingIndicator(channel);
|
|
23218
23324
|
rootProvider.sendStateless(JSON.stringify({
|
|
23219
23325
|
type: "chat:send",
|
|
23220
23326
|
channel,
|
|
23221
|
-
content:
|
|
23327
|
+
content: normalized,
|
|
23222
23328
|
sender_name: server.agentName
|
|
23223
23329
|
}));
|
|
23224
|
-
server.setAutoStatus(null);
|
|
23225
|
-
server.setActiveToolCall(null);
|
|
23226
23330
|
return { content: [{
|
|
23227
23331
|
type: "text",
|
|
23228
23332
|
text: `Sent to ${channel}`
|
|
@@ -23377,16 +23481,13 @@ function registerSvgTools(mcp, server) {
|
|
|
23377
23481
|
target: docId
|
|
23378
23482
|
});
|
|
23379
23483
|
const cleanSvg = sanitizeSvg(svg);
|
|
23380
|
-
if (!cleanSvg) {
|
|
23381
|
-
|
|
23382
|
-
|
|
23383
|
-
|
|
23384
|
-
|
|
23385
|
-
|
|
23386
|
-
|
|
23387
|
-
isError: true
|
|
23388
|
-
};
|
|
23389
|
-
}
|
|
23484
|
+
if (!cleanSvg) return {
|
|
23485
|
+
content: [{
|
|
23486
|
+
type: "text",
|
|
23487
|
+
text: "Error: SVG markup was empty or entirely stripped by sanitizer."
|
|
23488
|
+
}],
|
|
23489
|
+
isError: true
|
|
23490
|
+
};
|
|
23390
23491
|
const doc = (await server.getChildProvider(docId)).document;
|
|
23391
23492
|
const fragment = doc.getXmlFragment("default");
|
|
23392
23493
|
doc.transact(() => {
|
|
@@ -23397,13 +23498,11 @@ function registerSvgTools(mcp, server) {
|
|
|
23397
23498
|
fragment.insert(insertPos, [el]);
|
|
23398
23499
|
});
|
|
23399
23500
|
server.setFocusedDoc(docId);
|
|
23400
|
-
server.setActiveToolCall(null);
|
|
23401
23501
|
return { content: [{
|
|
23402
23502
|
type: "text",
|
|
23403
23503
|
text: `SVG inserted into document ${docId}${title ? ` ("${title}")` : ""}`
|
|
23404
23504
|
}] };
|
|
23405
23505
|
} catch (error) {
|
|
23406
|
-
server.setActiveToolCall(null);
|
|
23407
23506
|
return {
|
|
23408
23507
|
content: [{
|
|
23409
23508
|
type: "text",
|
|
@@ -24207,6 +24306,9 @@ var HookBridge = class {
|
|
|
24207
24306
|
}
|
|
24208
24307
|
routeEvent(payload) {
|
|
24209
24308
|
switch (payload.hook_event_name) {
|
|
24309
|
+
case "UserPromptSubmit":
|
|
24310
|
+
this.onUserPromptSubmit();
|
|
24311
|
+
break;
|
|
24210
24312
|
case "PreToolUse":
|
|
24211
24313
|
this.onPreToolUse(payload);
|
|
24212
24314
|
break;
|
|
@@ -24217,13 +24319,18 @@ var HookBridge = class {
|
|
|
24217
24319
|
this.onSubagentStart(payload);
|
|
24218
24320
|
break;
|
|
24219
24321
|
case "SubagentStop":
|
|
24220
|
-
this.onSubagentStop(
|
|
24322
|
+
this.onSubagentStop();
|
|
24221
24323
|
break;
|
|
24222
24324
|
case "Stop":
|
|
24223
24325
|
this.onStop();
|
|
24224
24326
|
break;
|
|
24225
24327
|
}
|
|
24226
24328
|
}
|
|
24329
|
+
/** New user turn — reset any lingering status/tool state from the previous turn. */
|
|
24330
|
+
onUserPromptSubmit() {
|
|
24331
|
+
this.server.setAutoStatus(null);
|
|
24332
|
+
this.server.setActiveToolCall(null);
|
|
24333
|
+
}
|
|
24227
24334
|
onPreToolUse(payload) {
|
|
24228
24335
|
const toolName = payload.tool_name ?? "";
|
|
24229
24336
|
if (toolName.startsWith("mcp__abracadabra__")) return;
|
|
@@ -24235,7 +24342,6 @@ var HookBridge = class {
|
|
|
24235
24342
|
}
|
|
24236
24343
|
onPostToolUse(payload) {
|
|
24237
24344
|
if ((payload.tool_name ?? "").startsWith("mcp__abracadabra__")) return;
|
|
24238
|
-
this.server.setAutoStatus("thinking");
|
|
24239
24345
|
}
|
|
24240
24346
|
onSubagentStart(payload) {
|
|
24241
24347
|
const agentType = payload.agent_type ?? "agent";
|
|
@@ -24245,9 +24351,7 @@ var HookBridge = class {
|
|
|
24245
24351
|
});
|
|
24246
24352
|
this.server.setAutoStatus("thinking");
|
|
24247
24353
|
}
|
|
24248
|
-
onSubagentStop(
|
|
24249
|
-
this.server.setAutoStatus("thinking");
|
|
24250
|
-
}
|
|
24354
|
+
onSubagentStop() {}
|
|
24251
24355
|
onStop() {
|
|
24252
24356
|
this.server.setAutoStatus(null);
|
|
24253
24357
|
this.server.setActiveToolCall(null);
|
|
@@ -24260,11 +24364,19 @@ var HookBridge = class {
|
|
|
24260
24364
|
* Abracadabra MCP Server — entry point.
|
|
24261
24365
|
*
|
|
24262
24366
|
* Environment variables:
|
|
24263
|
-
* ABRA_URL
|
|
24367
|
+
* ABRA_URL (required) — Server URL (e.g. http://localhost:1234)
|
|
24264
24368
|
* ABRA_AGENT_NAME — Display name (default: "AI Assistant")
|
|
24265
24369
|
* ABRA_AGENT_COLOR — HSL color for presence (default: "hsl(270, 80%, 60%)")
|
|
24266
24370
|
* ABRA_INVITE_CODE — Invite code for first-run registration (grants role)
|
|
24267
24371
|
* ABRA_KEY_FILE — Path to Ed25519 key file (default: ~/.abracadabra/agent.key)
|
|
24372
|
+
* ABRA_AGENT_TRIGGER_MODE — When to respond in group chats:
|
|
24373
|
+
* all → every message (legacy)
|
|
24374
|
+
* mention → only when @<alias> is used
|
|
24375
|
+
* task → only ai:task awareness events
|
|
24376
|
+
* mention+task → mention OR ai:task (default)
|
|
24377
|
+
* DMs always trigger regardless of mode.
|
|
24378
|
+
* ABRA_AGENT_MENTION_ALIASES — Comma-separated aliases for @mentions
|
|
24379
|
+
* (default: [ABRA_AGENT_NAME])
|
|
24268
24380
|
*/
|
|
24269
24381
|
async function main() {
|
|
24270
24382
|
const url = process.env.ABRA_URL;
|
|
@@ -24272,13 +24384,27 @@ async function main() {
|
|
|
24272
24384
|
console.error("Missing required environment variable: ABRA_URL");
|
|
24273
24385
|
process.exit(1);
|
|
24274
24386
|
}
|
|
24387
|
+
const rawMode = (process.env.ABRA_AGENT_TRIGGER_MODE ?? "mention+task").trim().toLowerCase();
|
|
24388
|
+
const validModes = [
|
|
24389
|
+
"all",
|
|
24390
|
+
"mention",
|
|
24391
|
+
"task",
|
|
24392
|
+
"mention+task"
|
|
24393
|
+
];
|
|
24394
|
+
const triggerMode = validModes.includes(rawMode) ? rawMode : "mention+task";
|
|
24395
|
+
if (rawMode && !validModes.includes(rawMode)) console.error(`[abracadabra-mcp] Invalid ABRA_AGENT_TRIGGER_MODE="${rawMode}", falling back to "mention+task"`);
|
|
24396
|
+
const aliasEnv = process.env.ABRA_AGENT_MENTION_ALIASES;
|
|
24397
|
+
const mentionAliases = aliasEnv ? aliasEnv.split(",").map((a) => a.trim()).filter((a) => a.length > 0) : void 0;
|
|
24275
24398
|
const server = new AbracadabraMCPServer({
|
|
24276
24399
|
url,
|
|
24277
24400
|
agentName: process.env.ABRA_AGENT_NAME,
|
|
24278
24401
|
agentColor: process.env.ABRA_AGENT_COLOR,
|
|
24279
24402
|
inviteCode: process.env.ABRA_INVITE_CODE,
|
|
24280
|
-
keyFile: process.env.ABRA_KEY_FILE
|
|
24403
|
+
keyFile: process.env.ABRA_KEY_FILE,
|
|
24404
|
+
triggerMode,
|
|
24405
|
+
mentionAliases
|
|
24281
24406
|
});
|
|
24407
|
+
console.error(`[abracadabra-mcp] Trigger mode: ${triggerMode}; aliases: ${server.mentionAliases.join(", ")}`);
|
|
24282
24408
|
const mcp = new McpServer({
|
|
24283
24409
|
name: "abracadabra",
|
|
24284
24410
|
version: "1.0.0"
|