@abraca/mcp 1.8.0 → 1.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/abracadabra-mcp.cjs +278 -172
- package/dist/abracadabra-mcp.cjs.map +1 -1
- package/dist/abracadabra-mcp.esm.js +278 -172
- package/dist/abracadabra-mcp.esm.js.map +1 -1
- package/dist/index.d.ts +30 -1
- package/package.json +1 -1
- package/src/hook-bridge.ts +18 -8
- package/src/index.ts +29 -2
- package/src/mentions.ts +42 -0
- package/src/server.ts +126 -14
- package/src/tools/awareness.ts +3 -0
- package/src/tools/channel.ts +18 -8
- package/src/tools/content.ts +0 -5
- package/src/tools/files.ts +8 -0
- package/src/tools/meta.ts +1 -7
- package/src/tools/svg.ts +0 -3
- package/src/tools/tree.ts +1 -20
|
@@ -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}"`
|
|
@@ -22768,7 +22845,6 @@ function registerContentTools(mcp, server) {
|
|
|
22768
22845
|
});
|
|
22769
22846
|
children.sort((a, b) => (treeMap.get(a.id)?.order ?? 0) - (treeMap.get(b.id)?.order ?? 0));
|
|
22770
22847
|
}
|
|
22771
|
-
server.setActiveToolCall(null);
|
|
22772
22848
|
const result = {
|
|
22773
22849
|
label,
|
|
22774
22850
|
type,
|
|
@@ -22781,7 +22857,6 @@ function registerContentTools(mcp, server) {
|
|
|
22781
22857
|
text: JSON.stringify(result, null, 2)
|
|
22782
22858
|
}] };
|
|
22783
22859
|
} catch (error) {
|
|
22784
|
-
server.setActiveToolCall(null);
|
|
22785
22860
|
return {
|
|
22786
22861
|
content: [{
|
|
22787
22862
|
type: "text",
|
|
@@ -22831,13 +22906,11 @@ function registerContentTools(mcp, server) {
|
|
|
22831
22906
|
populateYDocFromMarkdown(fragment, body || markdown, title || "Untitled");
|
|
22832
22907
|
server.setFocusedDoc(docId);
|
|
22833
22908
|
server.setDocCursor(docId, fragment.length);
|
|
22834
|
-
server.setActiveToolCall(null);
|
|
22835
22909
|
return { content: [{
|
|
22836
22910
|
type: "text",
|
|
22837
22911
|
text: `Document ${docId} updated (${writeMode} mode)`
|
|
22838
22912
|
}] };
|
|
22839
22913
|
} catch (error) {
|
|
22840
|
-
server.setActiveToolCall(null);
|
|
22841
22914
|
return {
|
|
22842
22915
|
content: [{
|
|
22843
22916
|
type: "text",
|
|
@@ -22859,22 +22932,15 @@ function registerMetaTools(mcp, server) {
|
|
|
22859
22932
|
target: docId
|
|
22860
22933
|
});
|
|
22861
22934
|
const treeMap = server.getTreeMap();
|
|
22862
|
-
if (!treeMap) {
|
|
22863
|
-
|
|
22864
|
-
|
|
22865
|
-
|
|
22866
|
-
text: "Not connected"
|
|
22867
|
-
}] };
|
|
22868
|
-
}
|
|
22935
|
+
if (!treeMap) return { content: [{
|
|
22936
|
+
type: "text",
|
|
22937
|
+
text: "Not connected"
|
|
22938
|
+
}] };
|
|
22869
22939
|
const entry = treeMap.get(docId);
|
|
22870
|
-
if (!entry) {
|
|
22871
|
-
|
|
22872
|
-
|
|
22873
|
-
|
|
22874
|
-
text: `Document ${docId} not found`
|
|
22875
|
-
}] };
|
|
22876
|
-
}
|
|
22877
|
-
server.setActiveToolCall(null);
|
|
22940
|
+
if (!entry) return { content: [{
|
|
22941
|
+
type: "text",
|
|
22942
|
+
text: `Document ${docId} not found`
|
|
22943
|
+
}] };
|
|
22878
22944
|
return { content: [{
|
|
22879
22945
|
type: "text",
|
|
22880
22946
|
text: JSON.stringify({
|
|
@@ -22887,7 +22953,7 @@ function registerMetaTools(mcp, server) {
|
|
|
22887
22953
|
});
|
|
22888
22954
|
mcp.tool("update_metadata", "Update metadata fields on a document. Merges the provided fields into existing metadata.", {
|
|
22889
22955
|
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.")
|
|
22956
|
+
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
22957
|
}, async ({ docId, meta }) => {
|
|
22892
22958
|
server.setAutoStatus("writing", docId);
|
|
22893
22959
|
server.setActiveToolCall({
|
|
@@ -22895,21 +22961,15 @@ function registerMetaTools(mcp, server) {
|
|
|
22895
22961
|
target: docId
|
|
22896
22962
|
});
|
|
22897
22963
|
const treeMap = server.getTreeMap();
|
|
22898
|
-
if (!treeMap) {
|
|
22899
|
-
|
|
22900
|
-
|
|
22901
|
-
|
|
22902
|
-
text: "Not connected"
|
|
22903
|
-
}] };
|
|
22904
|
-
}
|
|
22964
|
+
if (!treeMap) return { content: [{
|
|
22965
|
+
type: "text",
|
|
22966
|
+
text: "Not connected"
|
|
22967
|
+
}] };
|
|
22905
22968
|
const entry = treeMap.get(docId);
|
|
22906
|
-
if (!entry) {
|
|
22907
|
-
|
|
22908
|
-
|
|
22909
|
-
|
|
22910
|
-
text: `Document ${docId} not found`
|
|
22911
|
-
}] };
|
|
22912
|
-
}
|
|
22969
|
+
if (!entry) return { content: [{
|
|
22970
|
+
type: "text",
|
|
22971
|
+
text: `Document ${docId} not found`
|
|
22972
|
+
}] };
|
|
22913
22973
|
treeMap.set(docId, {
|
|
22914
22974
|
...entry,
|
|
22915
22975
|
meta: {
|
|
@@ -22918,7 +22978,6 @@ function registerMetaTools(mcp, server) {
|
|
|
22918
22978
|
},
|
|
22919
22979
|
updatedAt: Date.now()
|
|
22920
22980
|
});
|
|
22921
|
-
server.setActiveToolCall(null);
|
|
22922
22981
|
return { content: [{
|
|
22923
22982
|
type: "text",
|
|
22924
22983
|
text: `Metadata updated for ${docId}`
|
|
@@ -22933,6 +22992,11 @@ function registerMetaTools(mcp, server) {
|
|
|
22933
22992
|
*/
|
|
22934
22993
|
function registerFileTools(mcp, server) {
|
|
22935
22994
|
mcp.tool("list_uploads", "List file attachments for a document.", { docId: z.string().describe("Document ID.") }, async ({ docId }) => {
|
|
22995
|
+
server.setAutoStatus("reading", docId);
|
|
22996
|
+
server.setActiveToolCall({
|
|
22997
|
+
name: "list_uploads",
|
|
22998
|
+
target: docId
|
|
22999
|
+
});
|
|
22936
23000
|
try {
|
|
22937
23001
|
const uploads = await server.client.listUploads(docId);
|
|
22938
23002
|
return { content: [{
|
|
@@ -22954,6 +23018,11 @@ function registerFileTools(mcp, server) {
|
|
|
22954
23018
|
filePath: z.string().describe("Absolute path to the local file to upload."),
|
|
22955
23019
|
filename: z.string().optional().describe("Override filename (defaults to basename of filePath).")
|
|
22956
23020
|
}, async ({ docId, filePath, filename }) => {
|
|
23021
|
+
server.setAutoStatus("uploading", docId);
|
|
23022
|
+
server.setActiveToolCall({
|
|
23023
|
+
name: "upload_file",
|
|
23024
|
+
target: path.basename(filePath)
|
|
23025
|
+
});
|
|
22957
23026
|
try {
|
|
22958
23027
|
const resolvedPath = path.resolve(filePath);
|
|
22959
23028
|
const data = fs.readFileSync(resolvedPath);
|
|
@@ -22979,6 +23048,11 @@ function registerFileTools(mcp, server) {
|
|
|
22979
23048
|
uploadId: z.string().describe("Upload ID to download."),
|
|
22980
23049
|
saveTo: z.string().describe("Absolute local file path to save the download.")
|
|
22981
23050
|
}, async ({ docId, uploadId, saveTo }) => {
|
|
23051
|
+
server.setAutoStatus("reading", docId);
|
|
23052
|
+
server.setActiveToolCall({
|
|
23053
|
+
name: "download_file",
|
|
23054
|
+
target: path.basename(saveTo)
|
|
23055
|
+
});
|
|
22982
23056
|
try {
|
|
22983
23057
|
const blob = await server.client.getUpload(docId, uploadId);
|
|
22984
23058
|
const buffer = Buffer.from(await blob.arrayBuffer());
|
|
@@ -23002,6 +23076,11 @@ function registerFileTools(mcp, server) {
|
|
|
23002
23076
|
docId: z.string().describe("Document ID."),
|
|
23003
23077
|
uploadId: z.string().describe("Upload ID to delete.")
|
|
23004
23078
|
}, async ({ docId, uploadId }) => {
|
|
23079
|
+
server.setAutoStatus("writing", docId);
|
|
23080
|
+
server.setActiveToolCall({
|
|
23081
|
+
name: "delete_file",
|
|
23082
|
+
target: uploadId
|
|
23083
|
+
});
|
|
23005
23084
|
try {
|
|
23006
23085
|
await server.client.deleteUpload(docId, uploadId);
|
|
23007
23086
|
return { content: [{
|
|
@@ -23043,6 +23122,10 @@ function registerAwarenessTools(mcp, server) {
|
|
|
23043
23122
|
docId: z.string().describe("Document ID to set awareness on."),
|
|
23044
23123
|
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
23124
|
}, async ({ docId, fields }) => {
|
|
23125
|
+
server.setActiveToolCall({
|
|
23126
|
+
name: "set_doc_awareness",
|
|
23127
|
+
target: docId
|
|
23128
|
+
});
|
|
23046
23129
|
try {
|
|
23047
23130
|
const provider = await server.getChildProvider(docId);
|
|
23048
23131
|
for (const [key, value] of Object.entries(fields)) provider.awareness.setLocalStateField(key, value ?? null);
|
|
@@ -23061,6 +23144,7 @@ function registerAwarenessTools(mcp, server) {
|
|
|
23061
23144
|
}
|
|
23062
23145
|
});
|
|
23063
23146
|
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 () => {
|
|
23147
|
+
server.setActiveToolCall({ name: "poll_inbox" });
|
|
23064
23148
|
try {
|
|
23065
23149
|
const treeMap = server.getTreeMap();
|
|
23066
23150
|
const rootDocId = server.rootDocId;
|
|
@@ -23107,6 +23191,10 @@ function registerAwarenessTools(mcp, server) {
|
|
|
23107
23191
|
}
|
|
23108
23192
|
});
|
|
23109
23193
|
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 }) => {
|
|
23194
|
+
server.setActiveToolCall({
|
|
23195
|
+
name: "list_connected_users",
|
|
23196
|
+
target: docId
|
|
23197
|
+
});
|
|
23110
23198
|
try {
|
|
23111
23199
|
let awareness;
|
|
23112
23200
|
if (docId) awareness = (await server.getChildProvider(docId)).awareness;
|
|
@@ -23151,16 +23239,13 @@ function registerChannelTools(mcp, server) {
|
|
|
23151
23239
|
});
|
|
23152
23240
|
const treeMap = server.getTreeMap();
|
|
23153
23241
|
const rootDoc = server.rootDocument;
|
|
23154
|
-
if (!treeMap || !rootDoc) {
|
|
23155
|
-
|
|
23156
|
-
|
|
23157
|
-
|
|
23158
|
-
|
|
23159
|
-
|
|
23160
|
-
|
|
23161
|
-
isError: true
|
|
23162
|
-
};
|
|
23163
|
-
}
|
|
23242
|
+
if (!treeMap || !rootDoc) return {
|
|
23243
|
+
content: [{
|
|
23244
|
+
type: "text",
|
|
23245
|
+
text: "Not connected"
|
|
23246
|
+
}],
|
|
23247
|
+
isError: true
|
|
23248
|
+
};
|
|
23164
23249
|
const label = `AI Reply — ${(/* @__PURE__ */ new Date()).toISOString().replace("T", " ").slice(0, 19)}: ${text.slice(0, 40).replace(/\n/g, " ")}`;
|
|
23165
23250
|
const replyId = crypto.randomUUID();
|
|
23166
23251
|
const now = Date.now();
|
|
@@ -23176,7 +23261,6 @@ function registerChannelTools(mcp, server) {
|
|
|
23176
23261
|
});
|
|
23177
23262
|
populateYDocFromMarkdown((await server.getChildProvider(replyId)).document, text);
|
|
23178
23263
|
if (task_id) server.clearAiTask(task_id);
|
|
23179
|
-
server.setActiveToolCall(null);
|
|
23180
23264
|
return { content: [{
|
|
23181
23265
|
type: "text",
|
|
23182
23266
|
text: JSON.stringify({
|
|
@@ -23185,7 +23269,6 @@ function registerChannelTools(mcp, server) {
|
|
|
23185
23269
|
})
|
|
23186
23270
|
}] };
|
|
23187
23271
|
} catch (error) {
|
|
23188
|
-
server.setActiveToolCall(null);
|
|
23189
23272
|
return {
|
|
23190
23273
|
content: [{
|
|
23191
23274
|
type: "text",
|
|
@@ -23208,14 +23291,15 @@ function registerChannelTools(mcp, server) {
|
|
|
23208
23291
|
}],
|
|
23209
23292
|
isError: true
|
|
23210
23293
|
};
|
|
23294
|
+
const normalized = text.replace(/\\r\\n/g, "\n").replace(/\\n/g, "\n").replace(/\\t/g, " ").replace(/\\r/g, "\n");
|
|
23295
|
+
server.setAutoStatus(null);
|
|
23296
|
+
server.sendTypingIndicator(channel);
|
|
23211
23297
|
rootProvider.sendStateless(JSON.stringify({
|
|
23212
23298
|
type: "chat:send",
|
|
23213
23299
|
channel,
|
|
23214
|
-
content:
|
|
23300
|
+
content: normalized,
|
|
23215
23301
|
sender_name: server.agentName
|
|
23216
23302
|
}));
|
|
23217
|
-
server.setAutoStatus(null);
|
|
23218
|
-
server.setActiveToolCall(null);
|
|
23219
23303
|
return { content: [{
|
|
23220
23304
|
type: "text",
|
|
23221
23305
|
text: `Sent to ${channel}`
|
|
@@ -23370,16 +23454,13 @@ function registerSvgTools(mcp, server) {
|
|
|
23370
23454
|
target: docId
|
|
23371
23455
|
});
|
|
23372
23456
|
const cleanSvg = sanitizeSvg(svg);
|
|
23373
|
-
if (!cleanSvg) {
|
|
23374
|
-
|
|
23375
|
-
|
|
23376
|
-
|
|
23377
|
-
|
|
23378
|
-
|
|
23379
|
-
|
|
23380
|
-
isError: true
|
|
23381
|
-
};
|
|
23382
|
-
}
|
|
23457
|
+
if (!cleanSvg) return {
|
|
23458
|
+
content: [{
|
|
23459
|
+
type: "text",
|
|
23460
|
+
text: "Error: SVG markup was empty or entirely stripped by sanitizer."
|
|
23461
|
+
}],
|
|
23462
|
+
isError: true
|
|
23463
|
+
};
|
|
23383
23464
|
const doc = (await server.getChildProvider(docId)).document;
|
|
23384
23465
|
const fragment = doc.getXmlFragment("default");
|
|
23385
23466
|
doc.transact(() => {
|
|
@@ -23390,13 +23471,11 @@ function registerSvgTools(mcp, server) {
|
|
|
23390
23471
|
fragment.insert(insertPos, [el]);
|
|
23391
23472
|
});
|
|
23392
23473
|
server.setFocusedDoc(docId);
|
|
23393
|
-
server.setActiveToolCall(null);
|
|
23394
23474
|
return { content: [{
|
|
23395
23475
|
type: "text",
|
|
23396
23476
|
text: `SVG inserted into document ${docId}${title ? ` ("${title}")` : ""}`
|
|
23397
23477
|
}] };
|
|
23398
23478
|
} catch (error) {
|
|
23399
|
-
server.setActiveToolCall(null);
|
|
23400
23479
|
return {
|
|
23401
23480
|
content: [{
|
|
23402
23481
|
type: "text",
|
|
@@ -24200,6 +24279,9 @@ var HookBridge = class {
|
|
|
24200
24279
|
}
|
|
24201
24280
|
routeEvent(payload) {
|
|
24202
24281
|
switch (payload.hook_event_name) {
|
|
24282
|
+
case "UserPromptSubmit":
|
|
24283
|
+
this.onUserPromptSubmit();
|
|
24284
|
+
break;
|
|
24203
24285
|
case "PreToolUse":
|
|
24204
24286
|
this.onPreToolUse(payload);
|
|
24205
24287
|
break;
|
|
@@ -24210,13 +24292,18 @@ var HookBridge = class {
|
|
|
24210
24292
|
this.onSubagentStart(payload);
|
|
24211
24293
|
break;
|
|
24212
24294
|
case "SubagentStop":
|
|
24213
|
-
this.onSubagentStop(
|
|
24295
|
+
this.onSubagentStop();
|
|
24214
24296
|
break;
|
|
24215
24297
|
case "Stop":
|
|
24216
24298
|
this.onStop();
|
|
24217
24299
|
break;
|
|
24218
24300
|
}
|
|
24219
24301
|
}
|
|
24302
|
+
/** New user turn — reset any lingering status/tool state from the previous turn. */
|
|
24303
|
+
onUserPromptSubmit() {
|
|
24304
|
+
this.server.setAutoStatus(null);
|
|
24305
|
+
this.server.setActiveToolCall(null);
|
|
24306
|
+
}
|
|
24220
24307
|
onPreToolUse(payload) {
|
|
24221
24308
|
const toolName = payload.tool_name ?? "";
|
|
24222
24309
|
if (toolName.startsWith("mcp__abracadabra__")) return;
|
|
@@ -24228,7 +24315,6 @@ var HookBridge = class {
|
|
|
24228
24315
|
}
|
|
24229
24316
|
onPostToolUse(payload) {
|
|
24230
24317
|
if ((payload.tool_name ?? "").startsWith("mcp__abracadabra__")) return;
|
|
24231
|
-
this.server.setAutoStatus("thinking");
|
|
24232
24318
|
}
|
|
24233
24319
|
onSubagentStart(payload) {
|
|
24234
24320
|
const agentType = payload.agent_type ?? "agent";
|
|
@@ -24238,9 +24324,7 @@ var HookBridge = class {
|
|
|
24238
24324
|
});
|
|
24239
24325
|
this.server.setAutoStatus("thinking");
|
|
24240
24326
|
}
|
|
24241
|
-
onSubagentStop(
|
|
24242
|
-
this.server.setAutoStatus("thinking");
|
|
24243
|
-
}
|
|
24327
|
+
onSubagentStop() {}
|
|
24244
24328
|
onStop() {
|
|
24245
24329
|
this.server.setAutoStatus(null);
|
|
24246
24330
|
this.server.setActiveToolCall(null);
|
|
@@ -24253,11 +24337,19 @@ var HookBridge = class {
|
|
|
24253
24337
|
* Abracadabra MCP Server — entry point.
|
|
24254
24338
|
*
|
|
24255
24339
|
* Environment variables:
|
|
24256
|
-
* ABRA_URL
|
|
24340
|
+
* ABRA_URL (required) — Server URL (e.g. http://localhost:1234)
|
|
24257
24341
|
* ABRA_AGENT_NAME — Display name (default: "AI Assistant")
|
|
24258
24342
|
* ABRA_AGENT_COLOR — HSL color for presence (default: "hsl(270, 80%, 60%)")
|
|
24259
24343
|
* ABRA_INVITE_CODE — Invite code for first-run registration (grants role)
|
|
24260
24344
|
* ABRA_KEY_FILE — Path to Ed25519 key file (default: ~/.abracadabra/agent.key)
|
|
24345
|
+
* ABRA_AGENT_TRIGGER_MODE — When to respond in group chats:
|
|
24346
|
+
* all → every message (legacy)
|
|
24347
|
+
* mention → only when @<alias> is used
|
|
24348
|
+
* task → only ai:task awareness events
|
|
24349
|
+
* mention+task → mention OR ai:task (default)
|
|
24350
|
+
* DMs always trigger regardless of mode.
|
|
24351
|
+
* ABRA_AGENT_MENTION_ALIASES — Comma-separated aliases for @mentions
|
|
24352
|
+
* (default: [ABRA_AGENT_NAME])
|
|
24261
24353
|
*/
|
|
24262
24354
|
async function main() {
|
|
24263
24355
|
const url = process.env.ABRA_URL;
|
|
@@ -24265,13 +24357,27 @@ async function main() {
|
|
|
24265
24357
|
console.error("Missing required environment variable: ABRA_URL");
|
|
24266
24358
|
process.exit(1);
|
|
24267
24359
|
}
|
|
24360
|
+
const rawMode = (process.env.ABRA_AGENT_TRIGGER_MODE ?? "mention+task").trim().toLowerCase();
|
|
24361
|
+
const validModes = [
|
|
24362
|
+
"all",
|
|
24363
|
+
"mention",
|
|
24364
|
+
"task",
|
|
24365
|
+
"mention+task"
|
|
24366
|
+
];
|
|
24367
|
+
const triggerMode = validModes.includes(rawMode) ? rawMode : "mention+task";
|
|
24368
|
+
if (rawMode && !validModes.includes(rawMode)) console.error(`[abracadabra-mcp] Invalid ABRA_AGENT_TRIGGER_MODE="${rawMode}", falling back to "mention+task"`);
|
|
24369
|
+
const aliasEnv = process.env.ABRA_AGENT_MENTION_ALIASES;
|
|
24370
|
+
const mentionAliases = aliasEnv ? aliasEnv.split(",").map((a) => a.trim()).filter((a) => a.length > 0) : void 0;
|
|
24268
24371
|
const server = new AbracadabraMCPServer({
|
|
24269
24372
|
url,
|
|
24270
24373
|
agentName: process.env.ABRA_AGENT_NAME,
|
|
24271
24374
|
agentColor: process.env.ABRA_AGENT_COLOR,
|
|
24272
24375
|
inviteCode: process.env.ABRA_INVITE_CODE,
|
|
24273
|
-
keyFile: process.env.ABRA_KEY_FILE
|
|
24376
|
+
keyFile: process.env.ABRA_KEY_FILE,
|
|
24377
|
+
triggerMode,
|
|
24378
|
+
mentionAliases
|
|
24274
24379
|
});
|
|
24380
|
+
console.error(`[abracadabra-mcp] Trigger mode: ${triggerMode}; aliases: ${server.mentionAliases.join(", ")}`);
|
|
24275
24381
|
const mcp = new McpServer({
|
|
24276
24382
|
name: "abracadabra",
|
|
24277
24383
|
version: "1.0.0"
|