@abraca/mcp 1.0.11 → 1.0.15
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 +123 -13
- package/dist/abracadabra-mcp.cjs.map +1 -1
- package/dist/abracadabra-mcp.esm.js +123 -13
- package/dist/abracadabra-mcp.esm.js.map +1 -1
- package/dist/index.d.ts +22 -0
- package/package.json +1 -1
- package/src/server.ts +99 -0
- package/src/tools/channel.ts +11 -0
- package/src/tools/content.ts +12 -0
- package/src/tools/tree.ts +9 -1
|
@@ -19443,6 +19443,9 @@ var AbracadabraMCPServer = class {
|
|
|
19443
19443
|
this._serverRef = null;
|
|
19444
19444
|
this._handledTaskIds = /* @__PURE__ */ new Set();
|
|
19445
19445
|
this._userId = null;
|
|
19446
|
+
this._statusClearTimer = null;
|
|
19447
|
+
this._typingInterval = null;
|
|
19448
|
+
this._lastChatChannel = null;
|
|
19446
19449
|
this.config = config;
|
|
19447
19450
|
this.client = new AbracadabraClient({
|
|
19448
19451
|
url: config.url,
|
|
@@ -19537,7 +19540,8 @@ var AbracadabraMCPServer = class {
|
|
|
19537
19540
|
provider.awareness.setLocalStateField("user", {
|
|
19538
19541
|
name: this.agentName,
|
|
19539
19542
|
color: this.agentColor,
|
|
19540
|
-
publicKey: this._userId
|
|
19543
|
+
publicKey: this._userId,
|
|
19544
|
+
isAgent: true
|
|
19541
19545
|
});
|
|
19542
19546
|
const conn = {
|
|
19543
19547
|
doc,
|
|
@@ -19590,7 +19594,8 @@ var AbracadabraMCPServer = class {
|
|
|
19590
19594
|
childProvider.awareness.setLocalStateField("user", {
|
|
19591
19595
|
name: this.agentName,
|
|
19592
19596
|
color: this.agentColor,
|
|
19593
|
-
publicKey: this._userId
|
|
19597
|
+
publicKey: this._userId,
|
|
19598
|
+
isAgent: true
|
|
19594
19599
|
});
|
|
19595
19600
|
this.childCache.set(docId, {
|
|
19596
19601
|
provider: childProvider,
|
|
@@ -19657,6 +19662,7 @@ var AbracadabraMCPServer = class {
|
|
|
19657
19662
|
const user = state["user"];
|
|
19658
19663
|
const senderName = user && typeof user === "object" && typeof user.name === "string" ? user.name : "Unknown";
|
|
19659
19664
|
console.error(`[abracadabra-mcp] Handling ai:task id=${id} from ${senderName}: ${text.slice(0, 80)}`);
|
|
19665
|
+
this.setAutoStatus("thinking");
|
|
19660
19666
|
this._dispatchAiTask({
|
|
19661
19667
|
id,
|
|
19662
19668
|
text,
|
|
@@ -19721,6 +19727,18 @@ var AbracadabraMCPServer = class {
|
|
|
19721
19727
|
const parts = channel.split(":");
|
|
19722
19728
|
if (parts.length === 3 && parts[1] !== this._userId && parts[2] !== this._userId) return;
|
|
19723
19729
|
}
|
|
19730
|
+
if (channel) {
|
|
19731
|
+
const rootProvider = this._activeConnection?.provider;
|
|
19732
|
+
if (rootProvider) rootProvider.sendStateless(JSON.stringify({
|
|
19733
|
+
type: "chat:mark_read",
|
|
19734
|
+
channel,
|
|
19735
|
+
timestamp: Math.floor(Date.now() / 1e3)
|
|
19736
|
+
}));
|
|
19737
|
+
this._lastChatChannel = channel;
|
|
19738
|
+
this.sendTypingIndicator(channel);
|
|
19739
|
+
this._startTypingInterval(channel);
|
|
19740
|
+
}
|
|
19741
|
+
this.setAutoStatus("thinking");
|
|
19724
19742
|
await this._serverRef.notification({
|
|
19725
19743
|
method: "notifications/claude/channel",
|
|
19726
19744
|
params: {
|
|
@@ -19738,8 +19756,65 @@ var AbracadabraMCPServer = class {
|
|
|
19738
19756
|
console.error(`[abracadabra-mcp] Chat message from ${data.sender_name ?? "unknown"} on ${channel}`);
|
|
19739
19757
|
} catch {}
|
|
19740
19758
|
}
|
|
19759
|
+
/**
|
|
19760
|
+
* Set the agent's status in root awareness with auto-clear after idle.
|
|
19761
|
+
*/
|
|
19762
|
+
setAutoStatus(status, docId) {
|
|
19763
|
+
const provider = this._activeConnection?.provider;
|
|
19764
|
+
if (!provider) return;
|
|
19765
|
+
if (this._statusClearTimer) {
|
|
19766
|
+
clearTimeout(this._statusClearTimer);
|
|
19767
|
+
this._statusClearTimer = null;
|
|
19768
|
+
}
|
|
19769
|
+
provider.awareness.setLocalStateField("status", status);
|
|
19770
|
+
if (docId !== void 0) provider.awareness.setLocalStateField("docId", docId);
|
|
19771
|
+
if (!status) this._stopTypingInterval();
|
|
19772
|
+
if (status) this._statusClearTimer = setTimeout(() => {
|
|
19773
|
+
provider.awareness.setLocalStateField("status", null);
|
|
19774
|
+
provider.awareness.setLocalStateField("activeToolCall", null);
|
|
19775
|
+
this._stopTypingInterval();
|
|
19776
|
+
}, 3e4);
|
|
19777
|
+
}
|
|
19778
|
+
/** Re-send typing indicator every 2s so dashboard keeps showing it (expires at 3s). */
|
|
19779
|
+
_startTypingInterval(channel) {
|
|
19780
|
+
this._stopTypingInterval();
|
|
19781
|
+
this._typingInterval = setInterval(() => {
|
|
19782
|
+
this.sendTypingIndicator(channel);
|
|
19783
|
+
}, 2e3);
|
|
19784
|
+
}
|
|
19785
|
+
_stopTypingInterval() {
|
|
19786
|
+
if (this._typingInterval) {
|
|
19787
|
+
clearInterval(this._typingInterval);
|
|
19788
|
+
this._typingInterval = null;
|
|
19789
|
+
}
|
|
19790
|
+
this._lastChatChannel = null;
|
|
19791
|
+
}
|
|
19792
|
+
/**
|
|
19793
|
+
* Broadcast which tool the agent is currently executing.
|
|
19794
|
+
* Dashboard renders this as a ChatTool indicator.
|
|
19795
|
+
*/
|
|
19796
|
+
setActiveToolCall(toolCall) {
|
|
19797
|
+
this._activeConnection?.provider?.awareness.setLocalStateField("activeToolCall", toolCall);
|
|
19798
|
+
}
|
|
19799
|
+
/**
|
|
19800
|
+
* Send a typing indicator to a chat channel.
|
|
19801
|
+
*/
|
|
19802
|
+
sendTypingIndicator(channel) {
|
|
19803
|
+
const rootProvider = this._activeConnection?.provider;
|
|
19804
|
+
if (!rootProvider) return;
|
|
19805
|
+
rootProvider.sendStateless(JSON.stringify({
|
|
19806
|
+
type: "chat:typing",
|
|
19807
|
+
channel,
|
|
19808
|
+
sender_name: this.agentName
|
|
19809
|
+
}));
|
|
19810
|
+
}
|
|
19741
19811
|
/** Graceful shutdown. */
|
|
19742
19812
|
async destroy() {
|
|
19813
|
+
this._stopTypingInterval();
|
|
19814
|
+
if (this._statusClearTimer) {
|
|
19815
|
+
clearTimeout(this._statusClearTimer);
|
|
19816
|
+
this._statusClearTimer = null;
|
|
19817
|
+
}
|
|
19743
19818
|
if (this.evictionTimer) {
|
|
19744
19819
|
clearInterval(this.evictionTimer);
|
|
19745
19820
|
this.evictionTimer = null;
|
|
@@ -19891,12 +19966,20 @@ function registerTreeTools(mcp, server) {
|
|
|
19891
19966
|
type: z.string().optional().describe("Page type: \"doc\", \"kanban\", \"calendar\", \"table\", \"outline\", \"gallery\", \"slides\", \"timeline\", \"whiteboard\", \"map\", \"dashboard\", \"mindmap\", \"graph\". Omit to inherit parent view."),
|
|
19892
19967
|
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.")
|
|
19893
19968
|
}, async ({ parentId, label, type, meta }) => {
|
|
19969
|
+
server.setAutoStatus("creating");
|
|
19970
|
+
server.setActiveToolCall({
|
|
19971
|
+
name: "create_document",
|
|
19972
|
+
target: label
|
|
19973
|
+
});
|
|
19894
19974
|
const treeMap = server.getTreeMap();
|
|
19895
19975
|
const rootDoc = server.rootDocument;
|
|
19896
|
-
if (!treeMap || !rootDoc)
|
|
19897
|
-
|
|
19898
|
-
|
|
19899
|
-
|
|
19976
|
+
if (!treeMap || !rootDoc) {
|
|
19977
|
+
server.setActiveToolCall(null);
|
|
19978
|
+
return { content: [{
|
|
19979
|
+
type: "text",
|
|
19980
|
+
text: "Not connected"
|
|
19981
|
+
}] };
|
|
19982
|
+
}
|
|
19900
19983
|
const id = crypto.randomUUID();
|
|
19901
19984
|
const normalizedParent = normalizeRootId(parentId, server);
|
|
19902
19985
|
const now = Date.now();
|
|
@@ -19911,6 +19994,7 @@ function registerTreeTools(mcp, server) {
|
|
|
19911
19994
|
updatedAt: now
|
|
19912
19995
|
});
|
|
19913
19996
|
});
|
|
19997
|
+
server.setActiveToolCall(null);
|
|
19914
19998
|
return { content: [{
|
|
19915
19999
|
type: "text",
|
|
19916
20000
|
text: JSON.stringify({
|
|
@@ -21145,6 +21229,11 @@ function yjsToMarkdown(fragment) {
|
|
|
21145
21229
|
function registerContentTools(mcp, server) {
|
|
21146
21230
|
mcp.tool("read_document", "Read a document's content as markdown, along with its immediate children. IMPORTANT: A document's full content is its body text PLUS all its children. An empty body does not mean empty content — the children ARE the content (sub-docs, kanban columns, table columns, calendar events, etc.). Always check the returned \"children\" array and read child documents too.", { docId: z.string().describe("Document ID to read.") }, async ({ docId }) => {
|
|
21147
21231
|
try {
|
|
21232
|
+
server.setAutoStatus("reading", docId);
|
|
21233
|
+
server.setActiveToolCall({
|
|
21234
|
+
name: "read_document",
|
|
21235
|
+
target: docId
|
|
21236
|
+
});
|
|
21148
21237
|
const { title, markdown } = yjsToMarkdown((await server.getChildProvider(docId)).document.getXmlFragment("default"));
|
|
21149
21238
|
server.setFocusedDoc(docId);
|
|
21150
21239
|
server.setDocCursor(docId, 0);
|
|
@@ -21170,6 +21259,7 @@ function registerContentTools(mcp, server) {
|
|
|
21170
21259
|
});
|
|
21171
21260
|
children.sort((a, b) => (treeMap.get(a.id)?.order ?? 0) - (treeMap.get(b.id)?.order ?? 0));
|
|
21172
21261
|
}
|
|
21262
|
+
server.setActiveToolCall(null);
|
|
21173
21263
|
const result = {
|
|
21174
21264
|
label,
|
|
21175
21265
|
type,
|
|
@@ -21182,6 +21272,7 @@ function registerContentTools(mcp, server) {
|
|
|
21182
21272
|
text: JSON.stringify(result, null, 2)
|
|
21183
21273
|
}] };
|
|
21184
21274
|
} catch (error) {
|
|
21275
|
+
server.setActiveToolCall(null);
|
|
21185
21276
|
return {
|
|
21186
21277
|
content: [{
|
|
21187
21278
|
type: "text",
|
|
@@ -21197,6 +21288,11 @@ function registerContentTools(mcp, server) {
|
|
|
21197
21288
|
mode: z.enum(["replace", "append"]).optional().describe("Write mode. \"replace\" clears existing content first (default). \"append\" adds to the end.")
|
|
21198
21289
|
}, async ({ docId, markdown, mode }) => {
|
|
21199
21290
|
try {
|
|
21291
|
+
server.setAutoStatus("writing", docId);
|
|
21292
|
+
server.setActiveToolCall({
|
|
21293
|
+
name: "write_document",
|
|
21294
|
+
target: docId
|
|
21295
|
+
});
|
|
21200
21296
|
const writeMode = mode ?? "replace";
|
|
21201
21297
|
const doc = (await server.getChildProvider(docId)).document;
|
|
21202
21298
|
const fragment = doc.getXmlFragment("default");
|
|
@@ -21226,11 +21322,13 @@ function registerContentTools(mcp, server) {
|
|
|
21226
21322
|
populateYDocFromMarkdown(fragment, body || markdown, title || "Untitled");
|
|
21227
21323
|
server.setFocusedDoc(docId);
|
|
21228
21324
|
server.setDocCursor(docId, fragment.length);
|
|
21325
|
+
server.setActiveToolCall(null);
|
|
21229
21326
|
return { content: [{
|
|
21230
21327
|
type: "text",
|
|
21231
21328
|
text: `Document ${docId} updated (${writeMode} mode)`
|
|
21232
21329
|
}] };
|
|
21233
21330
|
} catch (error) {
|
|
21331
|
+
server.setActiveToolCall(null);
|
|
21234
21332
|
return {
|
|
21235
21333
|
content: [{
|
|
21236
21334
|
type: "text",
|
|
@@ -21513,15 +21611,23 @@ function registerChannelTools(mcp, server) {
|
|
|
21513
21611
|
task_id: z.string().optional().describe("If replying to an ai:task, the task ID to clear from awareness.")
|
|
21514
21612
|
}, async ({ doc_id, text, task_id }) => {
|
|
21515
21613
|
try {
|
|
21614
|
+
server.setAutoStatus("writing", doc_id);
|
|
21615
|
+
server.setActiveToolCall({
|
|
21616
|
+
name: "reply",
|
|
21617
|
+
target: doc_id
|
|
21618
|
+
});
|
|
21516
21619
|
const treeMap = server.getTreeMap();
|
|
21517
21620
|
const rootDoc = server.rootDocument;
|
|
21518
|
-
if (!treeMap || !rootDoc)
|
|
21519
|
-
|
|
21520
|
-
|
|
21521
|
-
|
|
21522
|
-
|
|
21523
|
-
|
|
21524
|
-
|
|
21621
|
+
if (!treeMap || !rootDoc) {
|
|
21622
|
+
server.setActiveToolCall(null);
|
|
21623
|
+
return {
|
|
21624
|
+
content: [{
|
|
21625
|
+
type: "text",
|
|
21626
|
+
text: "Not connected"
|
|
21627
|
+
}],
|
|
21628
|
+
isError: true
|
|
21629
|
+
};
|
|
21630
|
+
}
|
|
21525
21631
|
const label = `AI Reply — ${(/* @__PURE__ */ new Date()).toISOString().replace("T", " ").slice(0, 19)}: ${text.slice(0, 40).replace(/\n/g, " ")}`;
|
|
21526
21632
|
const replyId = crypto.randomUUID();
|
|
21527
21633
|
const now = Date.now();
|
|
@@ -21537,6 +21643,7 @@ function registerChannelTools(mcp, server) {
|
|
|
21537
21643
|
});
|
|
21538
21644
|
populateYDocFromMarkdown((await server.getChildProvider(replyId)).document, text);
|
|
21539
21645
|
if (task_id) server.clearAiTask(task_id);
|
|
21646
|
+
server.setActiveToolCall(null);
|
|
21540
21647
|
return { content: [{
|
|
21541
21648
|
type: "text",
|
|
21542
21649
|
text: JSON.stringify({
|
|
@@ -21545,6 +21652,7 @@ function registerChannelTools(mcp, server) {
|
|
|
21545
21652
|
})
|
|
21546
21653
|
}] };
|
|
21547
21654
|
} catch (error) {
|
|
21655
|
+
server.setActiveToolCall(null);
|
|
21548
21656
|
return {
|
|
21549
21657
|
content: [{
|
|
21550
21658
|
type: "text",
|
|
@@ -21573,6 +21681,8 @@ function registerChannelTools(mcp, server) {
|
|
|
21573
21681
|
content: text,
|
|
21574
21682
|
sender_name: server.agentName
|
|
21575
21683
|
}));
|
|
21684
|
+
server.setAutoStatus(null);
|
|
21685
|
+
server.setActiveToolCall(null);
|
|
21576
21686
|
return { content: [{
|
|
21577
21687
|
type: "text",
|
|
21578
21688
|
text: `Sent to ${channel}`
|