@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
|
@@ -3757,7 +3757,7 @@ const propertyKeyTypes = new Set([
|
|
|
3757
3757
|
"number",
|
|
3758
3758
|
"symbol"
|
|
3759
3759
|
]);
|
|
3760
|
-
function escapeRegex(str) {
|
|
3760
|
+
function escapeRegex$1(str) {
|
|
3761
3761
|
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
3762
3762
|
}
|
|
3763
3763
|
function clone(inst, def, params) {
|
|
@@ -4468,7 +4468,7 @@ const $ZodCheckUpperCase = /* @__PURE__ */ $constructor("$ZodCheckUpperCase", (i
|
|
|
4468
4468
|
});
|
|
4469
4469
|
const $ZodCheckIncludes = /* @__PURE__ */ $constructor("$ZodCheckIncludes", (inst, def) => {
|
|
4470
4470
|
$ZodCheck.init(inst, def);
|
|
4471
|
-
const escapedRegex = escapeRegex(def.includes);
|
|
4471
|
+
const escapedRegex = escapeRegex$1(def.includes);
|
|
4472
4472
|
const pattern = new RegExp(typeof def.position === "number" ? `^.{${def.position}}${escapedRegex}` : escapedRegex);
|
|
4473
4473
|
def.pattern = pattern;
|
|
4474
4474
|
inst._zod.onattach.push((inst) => {
|
|
@@ -4491,7 +4491,7 @@ const $ZodCheckIncludes = /* @__PURE__ */ $constructor("$ZodCheckIncludes", (ins
|
|
|
4491
4491
|
});
|
|
4492
4492
|
const $ZodCheckStartsWith = /* @__PURE__ */ $constructor("$ZodCheckStartsWith", (inst, def) => {
|
|
4493
4493
|
$ZodCheck.init(inst, def);
|
|
4494
|
-
const pattern = new RegExp(`^${escapeRegex(def.prefix)}.*`);
|
|
4494
|
+
const pattern = new RegExp(`^${escapeRegex$1(def.prefix)}.*`);
|
|
4495
4495
|
def.pattern ?? (def.pattern = pattern);
|
|
4496
4496
|
inst._zod.onattach.push((inst) => {
|
|
4497
4497
|
const bag = inst._zod.bag;
|
|
@@ -4513,7 +4513,7 @@ const $ZodCheckStartsWith = /* @__PURE__ */ $constructor("$ZodCheckStartsWith",
|
|
|
4513
4513
|
});
|
|
4514
4514
|
const $ZodCheckEndsWith = /* @__PURE__ */ $constructor("$ZodCheckEndsWith", (inst, def) => {
|
|
4515
4515
|
$ZodCheck.init(inst, def);
|
|
4516
|
-
const pattern = new RegExp(`.*${escapeRegex(def.suffix)}$`);
|
|
4516
|
+
const pattern = new RegExp(`.*${escapeRegex$1(def.suffix)}$`);
|
|
4517
4517
|
def.pattern ?? (def.pattern = pattern);
|
|
4518
4518
|
inst._zod.onattach.push((inst) => {
|
|
4519
4519
|
const bag = inst._zod.bag;
|
|
@@ -5545,7 +5545,7 @@ const $ZodEnum = /* @__PURE__ */ $constructor("$ZodEnum", (inst, def) => {
|
|
|
5545
5545
|
const values = getEnumValues(def.entries);
|
|
5546
5546
|
const valuesSet = new Set(values);
|
|
5547
5547
|
inst._zod.values = valuesSet;
|
|
5548
|
-
inst._zod.pattern = new RegExp(`^(${values.filter((k) => propertyKeyTypes.has(typeof k)).map((o) => typeof o === "string" ? escapeRegex(o) : o.toString()).join("|")})$`);
|
|
5548
|
+
inst._zod.pattern = new RegExp(`^(${values.filter((k) => propertyKeyTypes.has(typeof k)).map((o) => typeof o === "string" ? escapeRegex$1(o) : o.toString()).join("|")})$`);
|
|
5549
5549
|
inst._zod.parse = (payload, _ctx) => {
|
|
5550
5550
|
const input = payload.value;
|
|
5551
5551
|
if (valuesSet.has(input)) return payload;
|
|
@@ -5563,7 +5563,7 @@ const $ZodLiteral = /* @__PURE__ */ $constructor("$ZodLiteral", (inst, def) => {
|
|
|
5563
5563
|
if (def.values.length === 0) throw new Error("Cannot create literal schema with no valid values");
|
|
5564
5564
|
const values = new Set(def.values);
|
|
5565
5565
|
inst._zod.values = values;
|
|
5566
|
-
inst._zod.pattern = new RegExp(`^(${def.values.map((o) => typeof o === "string" ? escapeRegex(o) : o ? escapeRegex(o.toString()) : String(o)).join("|")})$`);
|
|
5566
|
+
inst._zod.pattern = new RegExp(`^(${def.values.map((o) => typeof o === "string" ? escapeRegex$1(o) : o ? escapeRegex$1(o.toString()) : String(o)).join("|")})$`);
|
|
5567
5567
|
inst._zod.parse = (payload, _ctx) => {
|
|
5568
5568
|
const input = payload.value;
|
|
5569
5569
|
if (values.has(input)) return payload;
|
|
@@ -19965,6 +19965,47 @@ function signChallenge(challengeB64, privateKey) {
|
|
|
19965
19965
|
return toBase64url(sign(challenge, privateKey));
|
|
19966
19966
|
}
|
|
19967
19967
|
|
|
19968
|
+
//#endregion
|
|
19969
|
+
//#region packages/mcp/src/mentions.ts
|
|
19970
|
+
/**
|
|
19971
|
+
* Mention parsing for chat messages.
|
|
19972
|
+
*
|
|
19973
|
+
* Recognizes `@alias` tokens (case-insensitive, word-boundary) so the agent
|
|
19974
|
+
* can decide whether a group-chat message is directed at it.
|
|
19975
|
+
*/
|
|
19976
|
+
/** Escape regex metacharacters in an alias string. */
|
|
19977
|
+
function escapeRegex(s) {
|
|
19978
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
19979
|
+
}
|
|
19980
|
+
/**
|
|
19981
|
+
* Build a regex that matches `@<alias>` for any of the given aliases.
|
|
19982
|
+
* Requires a non-word char (or start) before `@` and a word boundary after the alias
|
|
19983
|
+
* so `@Claude` matches but `email@claudesomething` does not.
|
|
19984
|
+
*/
|
|
19985
|
+
function buildMentionRegex(aliases) {
|
|
19986
|
+
const cleaned = aliases.map((a) => a.trim()).filter((a) => a.length > 0);
|
|
19987
|
+
if (cleaned.length === 0) return null;
|
|
19988
|
+
const alt = cleaned.map(escapeRegex).join("|");
|
|
19989
|
+
return new RegExp(`(?:^|[^\\w@])@(?:${alt})\\b`, "i");
|
|
19990
|
+
}
|
|
19991
|
+
/** Returns true if `text` contains `@alias` for any alias (case-insensitive). */
|
|
19992
|
+
function containsMention(text, aliases) {
|
|
19993
|
+
const re = buildMentionRegex(aliases);
|
|
19994
|
+
if (!re) return false;
|
|
19995
|
+
return re.test(text);
|
|
19996
|
+
}
|
|
19997
|
+
/**
|
|
19998
|
+
* Remove `@alias` tokens from the text. Leaves surrounding whitespace tidy so
|
|
19999
|
+
* the cleaned prompt reads naturally (e.g. `"@Claude help"` → `"help"`).
|
|
20000
|
+
*/
|
|
20001
|
+
function stripMention(text, aliases) {
|
|
20002
|
+
const cleaned = aliases.map((a) => a.trim()).filter((a) => a.length > 0);
|
|
20003
|
+
if (cleaned.length === 0) return text;
|
|
20004
|
+
const alt = cleaned.map(escapeRegex).join("|");
|
|
20005
|
+
const re = new RegExp(`(^|\\s)@(?:${alt})\\b[,:]?\\s*`, "gi");
|
|
20006
|
+
return text.replace(re, (_m, lead) => lead ? " " : "").replace(/\s{2,}/g, " ").trim();
|
|
20007
|
+
}
|
|
20008
|
+
|
|
19968
20009
|
//#endregion
|
|
19969
20010
|
//#region packages/mcp/src/server.ts
|
|
19970
20011
|
/**
|
|
@@ -19975,7 +20016,10 @@ function signChallenge(challengeB64, privateKey) {
|
|
|
19975
20016
|
* Use switchSpace(docId) to change the active space.
|
|
19976
20017
|
*/
|
|
19977
20018
|
const IDLE_TIMEOUT_MS = 300 * 1e3;
|
|
19978
|
-
var AbracadabraMCPServer = class {
|
|
20019
|
+
var AbracadabraMCPServer = class AbracadabraMCPServer {
|
|
20020
|
+
static {
|
|
20021
|
+
this.TOOL_HISTORY_MAX = 20;
|
|
20022
|
+
}
|
|
19979
20023
|
constructor(config) {
|
|
19980
20024
|
this._serverInfo = null;
|
|
19981
20025
|
this._rootDocId = null;
|
|
@@ -19992,6 +20036,7 @@ var AbracadabraMCPServer = class {
|
|
|
19992
20036
|
this._typingInterval = null;
|
|
19993
20037
|
this._lastChatChannel = null;
|
|
19994
20038
|
this._signFn = null;
|
|
20039
|
+
this._toolHistory = [];
|
|
19995
20040
|
this.config = config;
|
|
19996
20041
|
this.client = new AbracadabraClient({
|
|
19997
20042
|
url: config.url,
|
|
@@ -20004,6 +20049,14 @@ var AbracadabraMCPServer = class {
|
|
|
20004
20049
|
get agentColor() {
|
|
20005
20050
|
return this.config.agentColor || "hsl(270, 80%, 60%)";
|
|
20006
20051
|
}
|
|
20052
|
+
get triggerMode() {
|
|
20053
|
+
return this.config.triggerMode ?? "mention+task";
|
|
20054
|
+
}
|
|
20055
|
+
get mentionAliases() {
|
|
20056
|
+
const explicit = this.config.mentionAliases?.filter((a) => a.trim().length > 0);
|
|
20057
|
+
if (explicit && explicit.length > 0) return explicit;
|
|
20058
|
+
return [this.agentName];
|
|
20059
|
+
}
|
|
20007
20060
|
get serverInfo() {
|
|
20008
20061
|
return this._serverInfo;
|
|
20009
20062
|
}
|
|
@@ -20044,7 +20097,7 @@ var AbracadabraMCPServer = class {
|
|
|
20044
20097
|
await this.client.loginWithKey(keypair.publicKeyB64, signFn);
|
|
20045
20098
|
} else throw err;
|
|
20046
20099
|
}
|
|
20047
|
-
console.error(`[abracadabra-mcp] Authenticated as ${this.agentName} (
|
|
20100
|
+
console.error(`[abracadabra-mcp] Authenticated as ${this.agentName} (pubkey=${keypair.publicKeyB64})`);
|
|
20048
20101
|
this._serverInfo = await this.client.serverInfo();
|
|
20049
20102
|
let initialDocId = this._serverInfo.index_doc_id ?? null;
|
|
20050
20103
|
try {
|
|
@@ -20111,6 +20164,8 @@ var AbracadabraMCPServer = class {
|
|
|
20111
20164
|
provider.awareness.setLocalStateField("status", null);
|
|
20112
20165
|
provider.awareness.setLocalStateField("activeToolCall", null);
|
|
20113
20166
|
provider.awareness.setLocalStateField("statusContext", null);
|
|
20167
|
+
provider.awareness.setLocalStateField("turnId", null);
|
|
20168
|
+
provider.awareness.setLocalStateField("toolHistory", []);
|
|
20114
20169
|
const conn = {
|
|
20115
20170
|
doc,
|
|
20116
20171
|
provider,
|
|
@@ -20234,6 +20289,7 @@ var AbracadabraMCPServer = class {
|
|
|
20234
20289
|
_observeRootAwareness(provider) {
|
|
20235
20290
|
const selfId = provider.awareness.clientID;
|
|
20236
20291
|
provider.awareness.on("change", () => {
|
|
20292
|
+
if (this.triggerMode === "mention") return;
|
|
20237
20293
|
const states = provider.awareness.getStates();
|
|
20238
20294
|
for (const [clientId, state] of states) {
|
|
20239
20295
|
if (clientId === selfId) continue;
|
|
@@ -20246,6 +20302,7 @@ var AbracadabraMCPServer = class {
|
|
|
20246
20302
|
const user = state["user"];
|
|
20247
20303
|
const senderName = user && typeof user === "object" && typeof user.name === "string" ? user.name : "Unknown";
|
|
20248
20304
|
console.error(`[abracadabra-mcp] Handling ai:task id=${id} from ${senderName}: ${text.slice(0, 80)}`);
|
|
20305
|
+
this._beginTurn();
|
|
20249
20306
|
this.setAutoStatus("thinking");
|
|
20250
20307
|
this._dispatchAiTask({
|
|
20251
20308
|
id,
|
|
@@ -20308,9 +20365,28 @@ var AbracadabraMCPServer = class {
|
|
|
20308
20365
|
if (data.sender_id && data.sender_id === this._userId) return;
|
|
20309
20366
|
const channel = data.channel;
|
|
20310
20367
|
const docId = channel?.startsWith("group:") ? channel.slice(6) : "";
|
|
20311
|
-
|
|
20368
|
+
const isDM = channel?.startsWith("dm:") ?? false;
|
|
20369
|
+
const isGroup = channel?.startsWith("group:") ?? false;
|
|
20370
|
+
if (isDM) {
|
|
20312
20371
|
const parts = channel.split(":");
|
|
20313
|
-
if (parts.length === 3 && parts[1] !== this._userId && parts[2] !== this._userId)
|
|
20372
|
+
if (parts.length === 3 && parts[1] !== this._userId && parts[2] !== this._userId) {
|
|
20373
|
+
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.`);
|
|
20374
|
+
return;
|
|
20375
|
+
}
|
|
20376
|
+
}
|
|
20377
|
+
const mode = this.triggerMode;
|
|
20378
|
+
const content = typeof data.content === "string" ? data.content : "";
|
|
20379
|
+
let dispatchContent = content;
|
|
20380
|
+
if (isGroup) {
|
|
20381
|
+
if (mode === "task") return;
|
|
20382
|
+
if (mode === "mention" || mode === "mention+task") {
|
|
20383
|
+
const aliases = this.mentionAliases;
|
|
20384
|
+
if (!containsMention(content, aliases)) {
|
|
20385
|
+
console.error(`[abracadabra-mcp] skipped message on ${channel} — no @mention for ${aliases.join("|")}`);
|
|
20386
|
+
return;
|
|
20387
|
+
}
|
|
20388
|
+
dispatchContent = stripMention(content, aliases) || content;
|
|
20389
|
+
}
|
|
20314
20390
|
}
|
|
20315
20391
|
if (channel) {
|
|
20316
20392
|
const rootProvider = this._activeConnection?.provider;
|
|
@@ -20320,14 +20396,13 @@ var AbracadabraMCPServer = class {
|
|
|
20320
20396
|
timestamp: Math.floor(Date.now() / 1e3)
|
|
20321
20397
|
}));
|
|
20322
20398
|
this._lastChatChannel = channel;
|
|
20323
|
-
this.sendTypingIndicator(channel);
|
|
20324
|
-
this._startTypingInterval(channel);
|
|
20325
20399
|
}
|
|
20400
|
+
this._beginTurn();
|
|
20326
20401
|
this.setAutoStatus("thinking");
|
|
20327
20402
|
await this._serverRef.notification({
|
|
20328
20403
|
method: "notifications/claude/channel",
|
|
20329
20404
|
params: {
|
|
20330
|
-
content:
|
|
20405
|
+
content: dispatchContent,
|
|
20331
20406
|
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.`,
|
|
20332
20407
|
meta: {
|
|
20333
20408
|
source: "abracadabra",
|
|
@@ -20358,13 +20433,35 @@ var AbracadabraMCPServer = class {
|
|
|
20358
20433
|
if (docId !== void 0) provider.awareness.setLocalStateField("docId", docId);
|
|
20359
20434
|
const context = status ? statusContext !== void 0 ? statusContext : this._lastChatChannel : null;
|
|
20360
20435
|
provider.awareness.setLocalStateField("statusContext", context ?? null);
|
|
20361
|
-
if (!status)
|
|
20436
|
+
if (!status) {
|
|
20437
|
+
this._stopTypingInterval();
|
|
20438
|
+
provider.awareness.setLocalStateField("activeToolCall", null);
|
|
20439
|
+
provider.awareness.setLocalStateField("turnId", null);
|
|
20440
|
+
this._toolHistory = [];
|
|
20441
|
+
provider.awareness.setLocalStateField("toolHistory", []);
|
|
20442
|
+
}
|
|
20362
20443
|
if (status) this._statusClearTimer = setTimeout(() => {
|
|
20363
20444
|
provider.awareness.setLocalStateField("status", null);
|
|
20364
20445
|
provider.awareness.setLocalStateField("activeToolCall", null);
|
|
20365
20446
|
provider.awareness.setLocalStateField("statusContext", null);
|
|
20447
|
+
provider.awareness.setLocalStateField("turnId", null);
|
|
20448
|
+
this._toolHistory = [];
|
|
20449
|
+
provider.awareness.setLocalStateField("toolHistory", []);
|
|
20366
20450
|
this._stopTypingInterval();
|
|
20367
|
-
},
|
|
20451
|
+
}, 1e4);
|
|
20452
|
+
}
|
|
20453
|
+
/**
|
|
20454
|
+
* Start a new agent turn. Mints a fresh UUID and writes it to awareness so
|
|
20455
|
+
* the dashboard can gate the incantation on "there is an active turn",
|
|
20456
|
+
* decoupled from the (racier) status field. Called from chat arrival and
|
|
20457
|
+
* ai:task dispatch right before `setAutoStatus('thinking')`.
|
|
20458
|
+
*/
|
|
20459
|
+
_beginTurn() {
|
|
20460
|
+
const provider = this._activeConnection?.provider;
|
|
20461
|
+
if (!provider) return;
|
|
20462
|
+
this._toolHistory = [];
|
|
20463
|
+
provider.awareness.setLocalStateField("toolHistory", []);
|
|
20464
|
+
provider.awareness.setLocalStateField("turnId", crypto.randomUUID());
|
|
20368
20465
|
}
|
|
20369
20466
|
/** Re-send typing indicator every 2s so dashboard keeps showing it (expires at 3s). */
|
|
20370
20467
|
_startTypingInterval(channel) {
|
|
@@ -20382,10 +20479,28 @@ var AbracadabraMCPServer = class {
|
|
|
20382
20479
|
}
|
|
20383
20480
|
/**
|
|
20384
20481
|
* Broadcast which tool the agent is currently executing.
|
|
20385
|
-
*
|
|
20482
|
+
*
|
|
20483
|
+
* Renders as a ChatTool pill on the dashboard. On non-null calls, the tool
|
|
20484
|
+
* is also appended to `toolHistory` (capped at TOOL_HISTORY_MAX) and written
|
|
20485
|
+
* to awareness so the dashboard's inline trace can show the turn's recent
|
|
20486
|
+
* activity. Tools do NOT clear (`setActiveToolCall(null)`) on completion —
|
|
20487
|
+
* the pill stays until the next tool replaces it or `setAutoStatus(null)`
|
|
20488
|
+
* flushes the turn. This keeps pills visible long enough to see.
|
|
20386
20489
|
*/
|
|
20387
20490
|
setActiveToolCall(toolCall) {
|
|
20388
|
-
this._activeConnection?.provider
|
|
20491
|
+
const provider = this._activeConnection?.provider;
|
|
20492
|
+
if (!provider) return;
|
|
20493
|
+
provider.awareness.setLocalStateField("activeToolCall", toolCall);
|
|
20494
|
+
if (toolCall) {
|
|
20495
|
+
this._toolHistory.push({
|
|
20496
|
+
tool: toolCall.name,
|
|
20497
|
+
target: toolCall.target,
|
|
20498
|
+
ts: Date.now(),
|
|
20499
|
+
channel: this._lastChatChannel
|
|
20500
|
+
});
|
|
20501
|
+
if (this._toolHistory.length > AbracadabraMCPServer.TOOL_HISTORY_MAX) this._toolHistory.splice(0, this._toolHistory.length - AbracadabraMCPServer.TOOL_HISTORY_MAX);
|
|
20502
|
+
provider.awareness.setLocalStateField("toolHistory", [...this._toolHistory]);
|
|
20503
|
+
}
|
|
20389
20504
|
}
|
|
20390
20505
|
/**
|
|
20391
20506
|
* Send a typing indicator to a chat channel.
|
|
@@ -20416,8 +20531,11 @@ var AbracadabraMCPServer = class {
|
|
|
20416
20531
|
conn.provider.awareness.setLocalStateField("status", null);
|
|
20417
20532
|
conn.provider.awareness.setLocalStateField("activeToolCall", null);
|
|
20418
20533
|
conn.provider.awareness.setLocalStateField("statusContext", null);
|
|
20534
|
+
conn.provider.awareness.setLocalStateField("turnId", null);
|
|
20535
|
+
conn.provider.awareness.setLocalStateField("toolHistory", []);
|
|
20419
20536
|
conn.provider.destroy();
|
|
20420
20537
|
}
|
|
20538
|
+
this._toolHistory = [];
|
|
20421
20539
|
this._spaceConnections.clear();
|
|
20422
20540
|
this._activeConnection = null;
|
|
20423
20541
|
console.error("[abracadabra-mcp] Shutdown complete");
|
|
@@ -21233,16 +21351,12 @@ function registerTreeTools(mcp, server) {
|
|
|
21233
21351
|
server.setAutoStatus("reading");
|
|
21234
21352
|
server.setActiveToolCall({ name: "list_documents" });
|
|
21235
21353
|
const treeMap = server.getTreeMap();
|
|
21236
|
-
if (!treeMap) {
|
|
21237
|
-
|
|
21238
|
-
|
|
21239
|
-
|
|
21240
|
-
text: "Not connected"
|
|
21241
|
-
}] };
|
|
21242
|
-
}
|
|
21354
|
+
if (!treeMap) return { content: [{
|
|
21355
|
+
type: "text",
|
|
21356
|
+
text: "Not connected"
|
|
21357
|
+
}] };
|
|
21243
21358
|
const targetId = normalizeRootId(parentId, server);
|
|
21244
21359
|
const children = childrenOf$1(readEntries$1(treeMap), targetId);
|
|
21245
|
-
server.setActiveToolCall(null);
|
|
21246
21360
|
return { content: [{
|
|
21247
21361
|
type: "text",
|
|
21248
21362
|
text: JSON.stringify(children, null, 2)
|
|
@@ -21255,17 +21369,13 @@ function registerTreeTools(mcp, server) {
|
|
|
21255
21369
|
server.setAutoStatus("reading");
|
|
21256
21370
|
server.setActiveToolCall({ name: "get_document_tree" });
|
|
21257
21371
|
const treeMap = server.getTreeMap();
|
|
21258
|
-
if (!treeMap) {
|
|
21259
|
-
|
|
21260
|
-
|
|
21261
|
-
|
|
21262
|
-
text: "Not connected"
|
|
21263
|
-
}] };
|
|
21264
|
-
}
|
|
21372
|
+
if (!treeMap) return { content: [{
|
|
21373
|
+
type: "text",
|
|
21374
|
+
text: "Not connected"
|
|
21375
|
+
}] };
|
|
21265
21376
|
const targetId = normalizeRootId(rootId, server);
|
|
21266
21377
|
const maxDepth = depth ?? 3;
|
|
21267
21378
|
const tree = buildTree$1(readEntries$1(treeMap), targetId, maxDepth);
|
|
21268
|
-
server.setActiveToolCall(null);
|
|
21269
21379
|
return { content: [{
|
|
21270
21380
|
type: "text",
|
|
21271
21381
|
text: JSON.stringify(tree, null, 2)
|
|
@@ -21281,13 +21391,10 @@ function registerTreeTools(mcp, server) {
|
|
|
21281
21391
|
target: query
|
|
21282
21392
|
});
|
|
21283
21393
|
const treeMap = server.getTreeMap();
|
|
21284
|
-
if (!treeMap) {
|
|
21285
|
-
|
|
21286
|
-
|
|
21287
|
-
|
|
21288
|
-
text: "Not connected"
|
|
21289
|
-
}] };
|
|
21290
|
-
}
|
|
21394
|
+
if (!treeMap) return { content: [{
|
|
21395
|
+
type: "text",
|
|
21396
|
+
text: "Not connected"
|
|
21397
|
+
}] };
|
|
21291
21398
|
const entries = readEntries$1(treeMap);
|
|
21292
21399
|
const lowerQuery = query.toLowerCase();
|
|
21293
21400
|
const normalizedRoot = normalizeRootId(rootId, server);
|
|
@@ -21312,7 +21419,6 @@ function registerTreeTools(mcp, server) {
|
|
|
21312
21419
|
path
|
|
21313
21420
|
};
|
|
21314
21421
|
});
|
|
21315
|
-
server.setActiveToolCall(null);
|
|
21316
21422
|
if (results.length === 0) return { content: [{
|
|
21317
21423
|
type: "text",
|
|
21318
21424
|
text: `No documents found matching "${query}". Try get_document_tree to see the full hierarchy.`
|
|
@@ -21326,7 +21432,7 @@ function registerTreeTools(mcp, server) {
|
|
|
21326
21432
|
parentId: z.string().optional().describe("Parent document ID. Omit for top-level pages. Use a document ID for nested/child pages."),
|
|
21327
21433
|
label: z.string().describe("Display name / title for the document."),
|
|
21328
21434
|
type: 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."),
|
|
21329
|
-
meta: z.record(z.unknown()).optional().describe("Initial metadata (PageMeta fields: color as hex string, icon as Lucide kebab-case name like \"star\"/\"code-2\"/\"users\" — never emoji, dateStart, dateEnd, priority 0-4, tags array, etc). Omit icon entirely to use page type default.")
|
|
21435
|
+
meta: z.record(z.string(), 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.")
|
|
21330
21436
|
}, async ({ parentId, label, type, meta }) => {
|
|
21331
21437
|
server.setAutoStatus("creating");
|
|
21332
21438
|
server.setActiveToolCall({
|
|
@@ -21335,13 +21441,10 @@ function registerTreeTools(mcp, server) {
|
|
|
21335
21441
|
});
|
|
21336
21442
|
const treeMap = server.getTreeMap();
|
|
21337
21443
|
const rootDoc = server.rootDocument;
|
|
21338
|
-
if (!treeMap || !rootDoc) {
|
|
21339
|
-
|
|
21340
|
-
|
|
21341
|
-
|
|
21342
|
-
text: "Not connected"
|
|
21343
|
-
}] };
|
|
21344
|
-
}
|
|
21444
|
+
if (!treeMap || !rootDoc) return { content: [{
|
|
21445
|
+
type: "text",
|
|
21446
|
+
text: "Not connected"
|
|
21447
|
+
}] };
|
|
21345
21448
|
const id = crypto.randomUUID();
|
|
21346
21449
|
const normalizedParent = normalizeRootId(parentId, server);
|
|
21347
21450
|
const now = Date.now();
|
|
@@ -21357,7 +21460,6 @@ function registerTreeTools(mcp, server) {
|
|
|
21357
21460
|
});
|
|
21358
21461
|
});
|
|
21359
21462
|
server.setFocusedDoc(id);
|
|
21360
|
-
server.setActiveToolCall(null);
|
|
21361
21463
|
return { content: [{
|
|
21362
21464
|
type: "text",
|
|
21363
21465
|
text: JSON.stringify({
|
|
@@ -21378,28 +21480,21 @@ function registerTreeTools(mcp, server) {
|
|
|
21378
21480
|
target: id
|
|
21379
21481
|
});
|
|
21380
21482
|
const treeMap = server.getTreeMap();
|
|
21381
|
-
if (!treeMap) {
|
|
21382
|
-
|
|
21383
|
-
|
|
21384
|
-
|
|
21385
|
-
text: "Not connected"
|
|
21386
|
-
}] };
|
|
21387
|
-
}
|
|
21483
|
+
if (!treeMap) return { content: [{
|
|
21484
|
+
type: "text",
|
|
21485
|
+
text: "Not connected"
|
|
21486
|
+
}] };
|
|
21388
21487
|
const raw = treeMap.get(id);
|
|
21389
|
-
if (!raw) {
|
|
21390
|
-
|
|
21391
|
-
|
|
21392
|
-
|
|
21393
|
-
text: `Document ${id} not found`
|
|
21394
|
-
}] };
|
|
21395
|
-
}
|
|
21488
|
+
if (!raw) return { content: [{
|
|
21489
|
+
type: "text",
|
|
21490
|
+
text: `Document ${id} not found`
|
|
21491
|
+
}] };
|
|
21396
21492
|
const entry = toPlain(raw);
|
|
21397
21493
|
treeMap.set(id, {
|
|
21398
21494
|
...entry,
|
|
21399
21495
|
label,
|
|
21400
21496
|
updatedAt: Date.now()
|
|
21401
21497
|
});
|
|
21402
|
-
server.setActiveToolCall(null);
|
|
21403
21498
|
return { content: [{
|
|
21404
21499
|
type: "text",
|
|
21405
21500
|
text: `Renamed to "${label}"`
|
|
@@ -21416,21 +21511,15 @@ function registerTreeTools(mcp, server) {
|
|
|
21416
21511
|
target: id
|
|
21417
21512
|
});
|
|
21418
21513
|
const treeMap = server.getTreeMap();
|
|
21419
|
-
if (!treeMap) {
|
|
21420
|
-
|
|
21421
|
-
|
|
21422
|
-
|
|
21423
|
-
text: "Not connected"
|
|
21424
|
-
}] };
|
|
21425
|
-
}
|
|
21514
|
+
if (!treeMap) return { content: [{
|
|
21515
|
+
type: "text",
|
|
21516
|
+
text: "Not connected"
|
|
21517
|
+
}] };
|
|
21426
21518
|
const raw = treeMap.get(id);
|
|
21427
|
-
if (!raw) {
|
|
21428
|
-
|
|
21429
|
-
|
|
21430
|
-
|
|
21431
|
-
text: `Document ${id} not found`
|
|
21432
|
-
}] };
|
|
21433
|
-
}
|
|
21519
|
+
if (!raw) return { content: [{
|
|
21520
|
+
type: "text",
|
|
21521
|
+
text: `Document ${id} not found`
|
|
21522
|
+
}] };
|
|
21434
21523
|
const entry = toPlain(raw);
|
|
21435
21524
|
treeMap.set(id, {
|
|
21436
21525
|
...entry,
|
|
@@ -21438,7 +21527,6 @@ function registerTreeTools(mcp, server) {
|
|
|
21438
21527
|
order: order ?? Date.now(),
|
|
21439
21528
|
updatedAt: Date.now()
|
|
21440
21529
|
});
|
|
21441
|
-
server.setActiveToolCall(null);
|
|
21442
21530
|
return { content: [{
|
|
21443
21531
|
type: "text",
|
|
21444
21532
|
text: `Moved ${id} to parent ${newParentId}`
|
|
@@ -21453,13 +21541,10 @@ function registerTreeTools(mcp, server) {
|
|
|
21453
21541
|
const treeMap = server.getTreeMap();
|
|
21454
21542
|
const trashMap = server.getTrashMap();
|
|
21455
21543
|
const rootDoc = server.rootDocument;
|
|
21456
|
-
if (!treeMap || !trashMap || !rootDoc) {
|
|
21457
|
-
|
|
21458
|
-
|
|
21459
|
-
|
|
21460
|
-
text: "Not connected"
|
|
21461
|
-
}] };
|
|
21462
|
-
}
|
|
21544
|
+
if (!treeMap || !trashMap || !rootDoc) return { content: [{
|
|
21545
|
+
type: "text",
|
|
21546
|
+
text: "Not connected"
|
|
21547
|
+
}] };
|
|
21463
21548
|
const toDelete = [id, ...descendantsOf(readEntries$1(treeMap), id).map((e) => e.id)];
|
|
21464
21549
|
const now = Date.now();
|
|
21465
21550
|
rootDoc.transact(() => {
|
|
@@ -21478,7 +21563,6 @@ function registerTreeTools(mcp, server) {
|
|
|
21478
21563
|
treeMap.delete(nid);
|
|
21479
21564
|
}
|
|
21480
21565
|
});
|
|
21481
|
-
server.setActiveToolCall(null);
|
|
21482
21566
|
return { content: [{
|
|
21483
21567
|
type: "text",
|
|
21484
21568
|
text: `Deleted ${toDelete.length} document(s)`
|
|
@@ -21494,28 +21578,21 @@ function registerTreeTools(mcp, server) {
|
|
|
21494
21578
|
target: id
|
|
21495
21579
|
});
|
|
21496
21580
|
const treeMap = server.getTreeMap();
|
|
21497
|
-
if (!treeMap) {
|
|
21498
|
-
|
|
21499
|
-
|
|
21500
|
-
|
|
21501
|
-
text: "Not connected"
|
|
21502
|
-
}] };
|
|
21503
|
-
}
|
|
21581
|
+
if (!treeMap) return { content: [{
|
|
21582
|
+
type: "text",
|
|
21583
|
+
text: "Not connected"
|
|
21584
|
+
}] };
|
|
21504
21585
|
const raw = treeMap.get(id);
|
|
21505
|
-
if (!raw) {
|
|
21506
|
-
|
|
21507
|
-
|
|
21508
|
-
|
|
21509
|
-
text: `Document ${id} not found`
|
|
21510
|
-
}] };
|
|
21511
|
-
}
|
|
21586
|
+
if (!raw) return { content: [{
|
|
21587
|
+
type: "text",
|
|
21588
|
+
text: `Document ${id} not found`
|
|
21589
|
+
}] };
|
|
21512
21590
|
const entry = toPlain(raw);
|
|
21513
21591
|
treeMap.set(id, {
|
|
21514
21592
|
...entry,
|
|
21515
21593
|
type,
|
|
21516
21594
|
updatedAt: Date.now()
|
|
21517
21595
|
});
|
|
21518
|
-
server.setActiveToolCall(null);
|
|
21519
21596
|
return { content: [{
|
|
21520
21597
|
type: "text",
|
|
21521
21598
|
text: `Changed type to "${type}"`
|
|
@@ -21743,14 +21820,12 @@ function parseInline(text) {
|
|
|
21743
21820
|
text: kbdProps["value"] || "",
|
|
21744
21821
|
attrs: { kbd: { value: kbdProps["value"] || "" } }
|
|
21745
21822
|
});
|
|
21746
|
-
} else if (match[5] !== void 0) {
|
|
21747
|
-
|
|
21748
|
-
|
|
21749
|
-
|
|
21750
|
-
|
|
21751
|
-
|
|
21752
|
-
});
|
|
21753
|
-
} else if (match[7] !== void 0) tokens.push({
|
|
21823
|
+
} else if (match[5] !== void 0) tokens.push({
|
|
21824
|
+
text: "",
|
|
21825
|
+
node: "docLink",
|
|
21826
|
+
nodeAttrs: { docId: match[5] }
|
|
21827
|
+
});
|
|
21828
|
+
else if (match[7] !== void 0) tokens.push({
|
|
21754
21829
|
text: match[7],
|
|
21755
21830
|
attrs: { strike: true }
|
|
21756
21831
|
});
|
|
@@ -21777,7 +21852,7 @@ function parseInline(text) {
|
|
|
21777
21852
|
lastIndex = match.index + match[0].length;
|
|
21778
21853
|
}
|
|
21779
21854
|
if (lastIndex < stripped.length) tokens.push({ text: stripped.slice(lastIndex) });
|
|
21780
|
-
return tokens.filter((t) => t.text.length > 0);
|
|
21855
|
+
return tokens.filter((t) => t.node || t.text.length > 0);
|
|
21781
21856
|
}
|
|
21782
21857
|
function parseTableRow(line) {
|
|
21783
21858
|
const parts = line.split("|");
|
|
@@ -21905,11 +21980,14 @@ function parseBlocks(markdown) {
|
|
|
21905
21980
|
i++;
|
|
21906
21981
|
continue;
|
|
21907
21982
|
}
|
|
21908
|
-
const docEmbedMatch = line.match(/^!\[\[([^\]|]+?)(?:\|[^\]]*?)?\]\]\s*$/);
|
|
21983
|
+
const docEmbedMatch = line.match(/^!\[\[([^\]|]+?)(?:\|[^\]]*?)?\]\](\{[^}]*\})?\s*$/);
|
|
21909
21984
|
if (docEmbedMatch) {
|
|
21985
|
+
const props = parseMdcProps(docEmbedMatch[2]);
|
|
21986
|
+
const seamless = "seamless" in props || props["seamless"] === "true" || /\{[^}]*\bseamless\b[^}]*\}/.test(docEmbedMatch[2] ?? "");
|
|
21910
21987
|
blocks.push({
|
|
21911
21988
|
type: "docEmbed",
|
|
21912
|
-
docId: docEmbedMatch[1]
|
|
21989
|
+
docId: docEmbedMatch[1],
|
|
21990
|
+
seamless: seamless || void 0
|
|
21913
21991
|
});
|
|
21914
21992
|
i++;
|
|
21915
21993
|
continue;
|
|
@@ -22187,13 +22265,22 @@ function parseBlocks(markdown) {
|
|
|
22187
22265
|
return blocks;
|
|
22188
22266
|
}
|
|
22189
22267
|
function fillTextInto(el, tokens) {
|
|
22190
|
-
const filtered = tokens.filter((t) => t.text.length > 0);
|
|
22268
|
+
const filtered = tokens.filter((t) => t.node || t.text.length > 0);
|
|
22191
22269
|
if (!filtered.length) return;
|
|
22192
|
-
const
|
|
22193
|
-
|
|
22270
|
+
const children = filtered.map((tok) => {
|
|
22271
|
+
if (tok.node) {
|
|
22272
|
+
const xe = new Y.XmlElement(tok.node);
|
|
22273
|
+
if (tok.nodeAttrs) for (const [k, v] of Object.entries(tok.nodeAttrs)) xe.setAttribute(k, v);
|
|
22274
|
+
return xe;
|
|
22275
|
+
}
|
|
22276
|
+
return new Y.XmlText();
|
|
22277
|
+
});
|
|
22278
|
+
el.insert(0, children);
|
|
22194
22279
|
filtered.forEach((tok, i) => {
|
|
22195
|
-
if (tok.
|
|
22196
|
-
|
|
22280
|
+
if (tok.node) return;
|
|
22281
|
+
const xt = children[i];
|
|
22282
|
+
if (tok.attrs) xt.insert(0, tok.text, tok.attrs);
|
|
22283
|
+
else xt.insert(0, tok.text);
|
|
22197
22284
|
});
|
|
22198
22285
|
}
|
|
22199
22286
|
function blockElName(b) {
|
|
@@ -22440,6 +22527,7 @@ function fillBlock(el, block) {
|
|
|
22440
22527
|
break;
|
|
22441
22528
|
case "docEmbed":
|
|
22442
22529
|
el.setAttribute("docId", block.docId);
|
|
22530
|
+
if (block.seamless) el.setAttribute("seamless", "true");
|
|
22443
22531
|
break;
|
|
22444
22532
|
case "svgEmbed":
|
|
22445
22533
|
el.setAttribute("svg", block.svg);
|
|
@@ -22539,7 +22627,14 @@ function elementTextContent(el) {
|
|
|
22539
22627
|
for (let i = 0; i < el.length; i++) {
|
|
22540
22628
|
const child = el.get(i);
|
|
22541
22629
|
if (child instanceof Y.XmlText) parts.push(xmlTextToMarkdown(child));
|
|
22542
|
-
else if (child instanceof Y.XmlElement)
|
|
22630
|
+
else if (child instanceof Y.XmlElement) {
|
|
22631
|
+
if (child.nodeName === "docLink") {
|
|
22632
|
+
const docId = child.getAttribute("docId");
|
|
22633
|
+
if (docId) parts.push(`[[${docId}]]`);
|
|
22634
|
+
continue;
|
|
22635
|
+
}
|
|
22636
|
+
parts.push(elementTextContent(child));
|
|
22637
|
+
}
|
|
22543
22638
|
}
|
|
22544
22639
|
return parts.join("");
|
|
22545
22640
|
}
|
|
@@ -22568,7 +22663,9 @@ function serializeElement(el, indent = "") {
|
|
|
22568
22663
|
case "table": return serializeTable(el);
|
|
22569
22664
|
case "docEmbed": {
|
|
22570
22665
|
const docId = el.getAttribute("docId");
|
|
22571
|
-
|
|
22666
|
+
if (!docId) return "";
|
|
22667
|
+
const seamlessAttr = el.getAttribute("seamless");
|
|
22668
|
+
return seamlessAttr === true || seamlessAttr === "true" ? `![[${docId}]]{seamless}` : `![[${docId}]]`;
|
|
22572
22669
|
}
|
|
22573
22670
|
case "svgEmbed": {
|
|
22574
22671
|
const svg = el.getAttribute("svg") || "";
|
|
@@ -22768,7 +22865,6 @@ function registerContentTools(mcp, server) {
|
|
|
22768
22865
|
});
|
|
22769
22866
|
children.sort((a, b) => (treeMap.get(a.id)?.order ?? 0) - (treeMap.get(b.id)?.order ?? 0));
|
|
22770
22867
|
}
|
|
22771
|
-
server.setActiveToolCall(null);
|
|
22772
22868
|
const result = {
|
|
22773
22869
|
label,
|
|
22774
22870
|
type,
|
|
@@ -22781,7 +22877,6 @@ function registerContentTools(mcp, server) {
|
|
|
22781
22877
|
text: JSON.stringify(result, null, 2)
|
|
22782
22878
|
}] };
|
|
22783
22879
|
} catch (error) {
|
|
22784
|
-
server.setActiveToolCall(null);
|
|
22785
22880
|
return {
|
|
22786
22881
|
content: [{
|
|
22787
22882
|
type: "text",
|
|
@@ -22831,13 +22926,11 @@ function registerContentTools(mcp, server) {
|
|
|
22831
22926
|
populateYDocFromMarkdown(fragment, body || markdown, title || "Untitled");
|
|
22832
22927
|
server.setFocusedDoc(docId);
|
|
22833
22928
|
server.setDocCursor(docId, fragment.length);
|
|
22834
|
-
server.setActiveToolCall(null);
|
|
22835
22929
|
return { content: [{
|
|
22836
22930
|
type: "text",
|
|
22837
22931
|
text: `Document ${docId} updated (${writeMode} mode)`
|
|
22838
22932
|
}] };
|
|
22839
22933
|
} catch (error) {
|
|
22840
|
-
server.setActiveToolCall(null);
|
|
22841
22934
|
return {
|
|
22842
22935
|
content: [{
|
|
22843
22936
|
type: "text",
|
|
@@ -22859,22 +22952,15 @@ function registerMetaTools(mcp, server) {
|
|
|
22859
22952
|
target: docId
|
|
22860
22953
|
});
|
|
22861
22954
|
const treeMap = server.getTreeMap();
|
|
22862
|
-
if (!treeMap) {
|
|
22863
|
-
|
|
22864
|
-
|
|
22865
|
-
|
|
22866
|
-
text: "Not connected"
|
|
22867
|
-
}] };
|
|
22868
|
-
}
|
|
22955
|
+
if (!treeMap) return { content: [{
|
|
22956
|
+
type: "text",
|
|
22957
|
+
text: "Not connected"
|
|
22958
|
+
}] };
|
|
22869
22959
|
const entry = treeMap.get(docId);
|
|
22870
|
-
if (!entry) {
|
|
22871
|
-
|
|
22872
|
-
|
|
22873
|
-
|
|
22874
|
-
text: `Document ${docId} not found`
|
|
22875
|
-
}] };
|
|
22876
|
-
}
|
|
22877
|
-
server.setActiveToolCall(null);
|
|
22960
|
+
if (!entry) return { content: [{
|
|
22961
|
+
type: "text",
|
|
22962
|
+
text: `Document ${docId} not found`
|
|
22963
|
+
}] };
|
|
22878
22964
|
return { content: [{
|
|
22879
22965
|
type: "text",
|
|
22880
22966
|
text: JSON.stringify({
|
|
@@ -22887,7 +22973,7 @@ function registerMetaTools(mcp, server) {
|
|
|
22887
22973
|
});
|
|
22888
22974
|
mcp.tool("update_metadata", "Update metadata fields on a document. Merges the provided fields into existing metadata.", {
|
|
22889
22975
|
docId: z.string().describe("Document ID."),
|
|
22890
|
-
meta: z.record(z.unknown()).describe("Metadata fields to update (merged with existing). Universal keys: color (hex), icon (Lucide kebab-case — NEVER emoji), dateStart/dateEnd, datetimeStart/datetimeEnd, allDay, 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.")
|
|
22976
|
+
meta: z.record(z.string(), 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.")
|
|
22891
22977
|
}, async ({ docId, meta }) => {
|
|
22892
22978
|
server.setAutoStatus("writing", docId);
|
|
22893
22979
|
server.setActiveToolCall({
|
|
@@ -22895,21 +22981,15 @@ function registerMetaTools(mcp, server) {
|
|
|
22895
22981
|
target: docId
|
|
22896
22982
|
});
|
|
22897
22983
|
const treeMap = server.getTreeMap();
|
|
22898
|
-
if (!treeMap) {
|
|
22899
|
-
|
|
22900
|
-
|
|
22901
|
-
|
|
22902
|
-
text: "Not connected"
|
|
22903
|
-
}] };
|
|
22904
|
-
}
|
|
22984
|
+
if (!treeMap) return { content: [{
|
|
22985
|
+
type: "text",
|
|
22986
|
+
text: "Not connected"
|
|
22987
|
+
}] };
|
|
22905
22988
|
const entry = treeMap.get(docId);
|
|
22906
|
-
if (!entry) {
|
|
22907
|
-
|
|
22908
|
-
|
|
22909
|
-
|
|
22910
|
-
text: `Document ${docId} not found`
|
|
22911
|
-
}] };
|
|
22912
|
-
}
|
|
22989
|
+
if (!entry) return { content: [{
|
|
22990
|
+
type: "text",
|
|
22991
|
+
text: `Document ${docId} not found`
|
|
22992
|
+
}] };
|
|
22913
22993
|
treeMap.set(docId, {
|
|
22914
22994
|
...entry,
|
|
22915
22995
|
meta: {
|
|
@@ -22918,7 +22998,6 @@ function registerMetaTools(mcp, server) {
|
|
|
22918
22998
|
},
|
|
22919
22999
|
updatedAt: Date.now()
|
|
22920
23000
|
});
|
|
22921
|
-
server.setActiveToolCall(null);
|
|
22922
23001
|
return { content: [{
|
|
22923
23002
|
type: "text",
|
|
22924
23003
|
text: `Metadata updated for ${docId}`
|
|
@@ -22933,6 +23012,11 @@ function registerMetaTools(mcp, server) {
|
|
|
22933
23012
|
*/
|
|
22934
23013
|
function registerFileTools(mcp, server) {
|
|
22935
23014
|
mcp.tool("list_uploads", "List file attachments for a document.", { docId: z.string().describe("Document ID.") }, async ({ docId }) => {
|
|
23015
|
+
server.setAutoStatus("reading", docId);
|
|
23016
|
+
server.setActiveToolCall({
|
|
23017
|
+
name: "list_uploads",
|
|
23018
|
+
target: docId
|
|
23019
|
+
});
|
|
22936
23020
|
try {
|
|
22937
23021
|
const uploads = await server.client.listUploads(docId);
|
|
22938
23022
|
return { content: [{
|
|
@@ -22954,6 +23038,11 @@ function registerFileTools(mcp, server) {
|
|
|
22954
23038
|
filePath: z.string().describe("Absolute path to the local file to upload."),
|
|
22955
23039
|
filename: z.string().optional().describe("Override filename (defaults to basename of filePath).")
|
|
22956
23040
|
}, async ({ docId, filePath, filename }) => {
|
|
23041
|
+
server.setAutoStatus("uploading", docId);
|
|
23042
|
+
server.setActiveToolCall({
|
|
23043
|
+
name: "upload_file",
|
|
23044
|
+
target: path.basename(filePath)
|
|
23045
|
+
});
|
|
22957
23046
|
try {
|
|
22958
23047
|
const resolvedPath = path.resolve(filePath);
|
|
22959
23048
|
const data = fs.readFileSync(resolvedPath);
|
|
@@ -22979,6 +23068,11 @@ function registerFileTools(mcp, server) {
|
|
|
22979
23068
|
uploadId: z.string().describe("Upload ID to download."),
|
|
22980
23069
|
saveTo: z.string().describe("Absolute local file path to save the download.")
|
|
22981
23070
|
}, async ({ docId, uploadId, saveTo }) => {
|
|
23071
|
+
server.setAutoStatus("reading", docId);
|
|
23072
|
+
server.setActiveToolCall({
|
|
23073
|
+
name: "download_file",
|
|
23074
|
+
target: path.basename(saveTo)
|
|
23075
|
+
});
|
|
22982
23076
|
try {
|
|
22983
23077
|
const blob = await server.client.getUpload(docId, uploadId);
|
|
22984
23078
|
const buffer = Buffer.from(await blob.arrayBuffer());
|
|
@@ -23002,6 +23096,11 @@ function registerFileTools(mcp, server) {
|
|
|
23002
23096
|
docId: z.string().describe("Document ID."),
|
|
23003
23097
|
uploadId: z.string().describe("Upload ID to delete.")
|
|
23004
23098
|
}, async ({ docId, uploadId }) => {
|
|
23099
|
+
server.setAutoStatus("writing", docId);
|
|
23100
|
+
server.setActiveToolCall({
|
|
23101
|
+
name: "delete_file",
|
|
23102
|
+
target: uploadId
|
|
23103
|
+
});
|
|
23005
23104
|
try {
|
|
23006
23105
|
await server.client.deleteUpload(docId, uploadId);
|
|
23007
23106
|
return { content: [{
|
|
@@ -23043,6 +23142,10 @@ function registerAwarenessTools(mcp, server) {
|
|
|
23043
23142
|
docId: z.string().describe("Document ID to set awareness on."),
|
|
23044
23143
|
fields: z.record(z.string(), 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.")
|
|
23045
23144
|
}, async ({ docId, fields }) => {
|
|
23145
|
+
server.setActiveToolCall({
|
|
23146
|
+
name: "set_doc_awareness",
|
|
23147
|
+
target: docId
|
|
23148
|
+
});
|
|
23046
23149
|
try {
|
|
23047
23150
|
const provider = await server.getChildProvider(docId);
|
|
23048
23151
|
for (const [key, value] of Object.entries(fields)) provider.awareness.setLocalStateField(key, value ?? null);
|
|
@@ -23061,6 +23164,7 @@ function registerAwarenessTools(mcp, server) {
|
|
|
23061
23164
|
}
|
|
23062
23165
|
});
|
|
23063
23166
|
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 () => {
|
|
23167
|
+
server.setActiveToolCall({ name: "poll_inbox" });
|
|
23064
23168
|
try {
|
|
23065
23169
|
const treeMap = server.getTreeMap();
|
|
23066
23170
|
const rootDocId = server.rootDocId;
|
|
@@ -23107,6 +23211,10 @@ function registerAwarenessTools(mcp, server) {
|
|
|
23107
23211
|
}
|
|
23108
23212
|
});
|
|
23109
23213
|
mcp.tool("list_connected_users", "List all connected users and their awareness state. Shows who is online and what they are doing.", { docId: z.string().optional().describe("If provided, list users connected to this specific document. Otherwise lists users from root awareness.") }, async ({ docId }) => {
|
|
23214
|
+
server.setActiveToolCall({
|
|
23215
|
+
name: "list_connected_users",
|
|
23216
|
+
target: docId
|
|
23217
|
+
});
|
|
23110
23218
|
try {
|
|
23111
23219
|
let awareness;
|
|
23112
23220
|
if (docId) awareness = (await server.getChildProvider(docId)).awareness;
|
|
@@ -23151,16 +23259,13 @@ function registerChannelTools(mcp, server) {
|
|
|
23151
23259
|
});
|
|
23152
23260
|
const treeMap = server.getTreeMap();
|
|
23153
23261
|
const rootDoc = server.rootDocument;
|
|
23154
|
-
if (!treeMap || !rootDoc) {
|
|
23155
|
-
|
|
23156
|
-
|
|
23157
|
-
|
|
23158
|
-
|
|
23159
|
-
|
|
23160
|
-
|
|
23161
|
-
isError: true
|
|
23162
|
-
};
|
|
23163
|
-
}
|
|
23262
|
+
if (!treeMap || !rootDoc) return {
|
|
23263
|
+
content: [{
|
|
23264
|
+
type: "text",
|
|
23265
|
+
text: "Not connected"
|
|
23266
|
+
}],
|
|
23267
|
+
isError: true
|
|
23268
|
+
};
|
|
23164
23269
|
const label = `AI Reply — ${(/* @__PURE__ */ new Date()).toISOString().replace("T", " ").slice(0, 19)}: ${text.slice(0, 40).replace(/\n/g, " ")}`;
|
|
23165
23270
|
const replyId = crypto.randomUUID();
|
|
23166
23271
|
const now = Date.now();
|
|
@@ -23176,7 +23281,6 @@ function registerChannelTools(mcp, server) {
|
|
|
23176
23281
|
});
|
|
23177
23282
|
populateYDocFromMarkdown((await server.getChildProvider(replyId)).document, text);
|
|
23178
23283
|
if (task_id) server.clearAiTask(task_id);
|
|
23179
|
-
server.setActiveToolCall(null);
|
|
23180
23284
|
return { content: [{
|
|
23181
23285
|
type: "text",
|
|
23182
23286
|
text: JSON.stringify({
|
|
@@ -23185,7 +23289,6 @@ function registerChannelTools(mcp, server) {
|
|
|
23185
23289
|
})
|
|
23186
23290
|
}] };
|
|
23187
23291
|
} catch (error) {
|
|
23188
|
-
server.setActiveToolCall(null);
|
|
23189
23292
|
return {
|
|
23190
23293
|
content: [{
|
|
23191
23294
|
type: "text",
|
|
@@ -23208,14 +23311,15 @@ function registerChannelTools(mcp, server) {
|
|
|
23208
23311
|
}],
|
|
23209
23312
|
isError: true
|
|
23210
23313
|
};
|
|
23314
|
+
const normalized = text.replace(/\\r\\n/g, "\n").replace(/\\n/g, "\n").replace(/\\t/g, " ").replace(/\\r/g, "\n");
|
|
23315
|
+
server.setAutoStatus(null);
|
|
23316
|
+
server.sendTypingIndicator(channel);
|
|
23211
23317
|
rootProvider.sendStateless(JSON.stringify({
|
|
23212
23318
|
type: "chat:send",
|
|
23213
23319
|
channel,
|
|
23214
|
-
content:
|
|
23320
|
+
content: normalized,
|
|
23215
23321
|
sender_name: server.agentName
|
|
23216
23322
|
}));
|
|
23217
|
-
server.setAutoStatus(null);
|
|
23218
|
-
server.setActiveToolCall(null);
|
|
23219
23323
|
return { content: [{
|
|
23220
23324
|
type: "text",
|
|
23221
23325
|
text: `Sent to ${channel}`
|
|
@@ -23370,16 +23474,13 @@ function registerSvgTools(mcp, server) {
|
|
|
23370
23474
|
target: docId
|
|
23371
23475
|
});
|
|
23372
23476
|
const cleanSvg = sanitizeSvg(svg);
|
|
23373
|
-
if (!cleanSvg) {
|
|
23374
|
-
|
|
23375
|
-
|
|
23376
|
-
|
|
23377
|
-
|
|
23378
|
-
|
|
23379
|
-
|
|
23380
|
-
isError: true
|
|
23381
|
-
};
|
|
23382
|
-
}
|
|
23477
|
+
if (!cleanSvg) return {
|
|
23478
|
+
content: [{
|
|
23479
|
+
type: "text",
|
|
23480
|
+
text: "Error: SVG markup was empty or entirely stripped by sanitizer."
|
|
23481
|
+
}],
|
|
23482
|
+
isError: true
|
|
23483
|
+
};
|
|
23383
23484
|
const doc = (await server.getChildProvider(docId)).document;
|
|
23384
23485
|
const fragment = doc.getXmlFragment("default");
|
|
23385
23486
|
doc.transact(() => {
|
|
@@ -23390,13 +23491,11 @@ function registerSvgTools(mcp, server) {
|
|
|
23390
23491
|
fragment.insert(insertPos, [el]);
|
|
23391
23492
|
});
|
|
23392
23493
|
server.setFocusedDoc(docId);
|
|
23393
|
-
server.setActiveToolCall(null);
|
|
23394
23494
|
return { content: [{
|
|
23395
23495
|
type: "text",
|
|
23396
23496
|
text: `SVG inserted into document ${docId}${title ? ` ("${title}")` : ""}`
|
|
23397
23497
|
}] };
|
|
23398
23498
|
} catch (error) {
|
|
23399
|
-
server.setActiveToolCall(null);
|
|
23400
23499
|
return {
|
|
23401
23500
|
content: [{
|
|
23402
23501
|
type: "text",
|
|
@@ -24200,6 +24299,9 @@ var HookBridge = class {
|
|
|
24200
24299
|
}
|
|
24201
24300
|
routeEvent(payload) {
|
|
24202
24301
|
switch (payload.hook_event_name) {
|
|
24302
|
+
case "UserPromptSubmit":
|
|
24303
|
+
this.onUserPromptSubmit();
|
|
24304
|
+
break;
|
|
24203
24305
|
case "PreToolUse":
|
|
24204
24306
|
this.onPreToolUse(payload);
|
|
24205
24307
|
break;
|
|
@@ -24210,13 +24312,18 @@ var HookBridge = class {
|
|
|
24210
24312
|
this.onSubagentStart(payload);
|
|
24211
24313
|
break;
|
|
24212
24314
|
case "SubagentStop":
|
|
24213
|
-
this.onSubagentStop(
|
|
24315
|
+
this.onSubagentStop();
|
|
24214
24316
|
break;
|
|
24215
24317
|
case "Stop":
|
|
24216
24318
|
this.onStop();
|
|
24217
24319
|
break;
|
|
24218
24320
|
}
|
|
24219
24321
|
}
|
|
24322
|
+
/** New user turn — reset any lingering status/tool state from the previous turn. */
|
|
24323
|
+
onUserPromptSubmit() {
|
|
24324
|
+
this.server.setAutoStatus(null);
|
|
24325
|
+
this.server.setActiveToolCall(null);
|
|
24326
|
+
}
|
|
24220
24327
|
onPreToolUse(payload) {
|
|
24221
24328
|
const toolName = payload.tool_name ?? "";
|
|
24222
24329
|
if (toolName.startsWith("mcp__abracadabra__")) return;
|
|
@@ -24228,7 +24335,6 @@ var HookBridge = class {
|
|
|
24228
24335
|
}
|
|
24229
24336
|
onPostToolUse(payload) {
|
|
24230
24337
|
if ((payload.tool_name ?? "").startsWith("mcp__abracadabra__")) return;
|
|
24231
|
-
this.server.setAutoStatus("thinking");
|
|
24232
24338
|
}
|
|
24233
24339
|
onSubagentStart(payload) {
|
|
24234
24340
|
const agentType = payload.agent_type ?? "agent";
|
|
@@ -24238,9 +24344,7 @@ var HookBridge = class {
|
|
|
24238
24344
|
});
|
|
24239
24345
|
this.server.setAutoStatus("thinking");
|
|
24240
24346
|
}
|
|
24241
|
-
onSubagentStop(
|
|
24242
|
-
this.server.setAutoStatus("thinking");
|
|
24243
|
-
}
|
|
24347
|
+
onSubagentStop() {}
|
|
24244
24348
|
onStop() {
|
|
24245
24349
|
this.server.setAutoStatus(null);
|
|
24246
24350
|
this.server.setActiveToolCall(null);
|
|
@@ -24253,11 +24357,19 @@ var HookBridge = class {
|
|
|
24253
24357
|
* Abracadabra MCP Server — entry point.
|
|
24254
24358
|
*
|
|
24255
24359
|
* Environment variables:
|
|
24256
|
-
* ABRA_URL
|
|
24360
|
+
* ABRA_URL (required) — Server URL (e.g. http://localhost:1234)
|
|
24257
24361
|
* ABRA_AGENT_NAME — Display name (default: "AI Assistant")
|
|
24258
24362
|
* ABRA_AGENT_COLOR — HSL color for presence (default: "hsl(270, 80%, 60%)")
|
|
24259
24363
|
* ABRA_INVITE_CODE — Invite code for first-run registration (grants role)
|
|
24260
24364
|
* ABRA_KEY_FILE — Path to Ed25519 key file (default: ~/.abracadabra/agent.key)
|
|
24365
|
+
* ABRA_AGENT_TRIGGER_MODE — When to respond in group chats:
|
|
24366
|
+
* all → every message (legacy)
|
|
24367
|
+
* mention → only when @<alias> is used
|
|
24368
|
+
* task → only ai:task awareness events
|
|
24369
|
+
* mention+task → mention OR ai:task (default)
|
|
24370
|
+
* DMs always trigger regardless of mode.
|
|
24371
|
+
* ABRA_AGENT_MENTION_ALIASES — Comma-separated aliases for @mentions
|
|
24372
|
+
* (default: [ABRA_AGENT_NAME])
|
|
24261
24373
|
*/
|
|
24262
24374
|
async function main() {
|
|
24263
24375
|
const url = process.env.ABRA_URL;
|
|
@@ -24265,13 +24377,27 @@ async function main() {
|
|
|
24265
24377
|
console.error("Missing required environment variable: ABRA_URL");
|
|
24266
24378
|
process.exit(1);
|
|
24267
24379
|
}
|
|
24380
|
+
const rawMode = (process.env.ABRA_AGENT_TRIGGER_MODE ?? "mention+task").trim().toLowerCase();
|
|
24381
|
+
const validModes = [
|
|
24382
|
+
"all",
|
|
24383
|
+
"mention",
|
|
24384
|
+
"task",
|
|
24385
|
+
"mention+task"
|
|
24386
|
+
];
|
|
24387
|
+
const triggerMode = validModes.includes(rawMode) ? rawMode : "mention+task";
|
|
24388
|
+
if (rawMode && !validModes.includes(rawMode)) console.error(`[abracadabra-mcp] Invalid ABRA_AGENT_TRIGGER_MODE="${rawMode}", falling back to "mention+task"`);
|
|
24389
|
+
const aliasEnv = process.env.ABRA_AGENT_MENTION_ALIASES;
|
|
24390
|
+
const mentionAliases = aliasEnv ? aliasEnv.split(",").map((a) => a.trim()).filter((a) => a.length > 0) : void 0;
|
|
24268
24391
|
const server = new AbracadabraMCPServer({
|
|
24269
24392
|
url,
|
|
24270
24393
|
agentName: process.env.ABRA_AGENT_NAME,
|
|
24271
24394
|
agentColor: process.env.ABRA_AGENT_COLOR,
|
|
24272
24395
|
inviteCode: process.env.ABRA_INVITE_CODE,
|
|
24273
|
-
keyFile: process.env.ABRA_KEY_FILE
|
|
24396
|
+
keyFile: process.env.ABRA_KEY_FILE,
|
|
24397
|
+
triggerMode,
|
|
24398
|
+
mentionAliases
|
|
24274
24399
|
});
|
|
24400
|
+
console.error(`[abracadabra-mcp] Trigger mode: ${triggerMode}; aliases: ${server.mentionAliases.join(", ")}`);
|
|
24275
24401
|
const mcp = new McpServer({
|
|
24276
24402
|
name: "abracadabra",
|
|
24277
24403
|
version: "1.0.0"
|