@abraca/mcp 1.0.12 → 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
package/dist/index.d.ts
CHANGED
|
@@ -8728,6 +8728,9 @@ declare class AbracadabraMCPServer {
|
|
|
8728
8728
|
private _serverRef;
|
|
8729
8729
|
private _handledTaskIds;
|
|
8730
8730
|
private _userId;
|
|
8731
|
+
private _statusClearTimer;
|
|
8732
|
+
private _typingInterval;
|
|
8733
|
+
private _lastChatChannel;
|
|
8731
8734
|
constructor(config: MCPServerConfig);
|
|
8732
8735
|
get agentName(): string;
|
|
8733
8736
|
get agentColor(): string;
|
|
@@ -8778,6 +8781,25 @@ declare class AbracadabraMCPServer {
|
|
|
8778
8781
|
clearAiTask(taskId: string): void;
|
|
8779
8782
|
/** Handle incoming stateless chat messages and dispatch as channel notifications. */
|
|
8780
8783
|
private _handleStatelessChat;
|
|
8784
|
+
/**
|
|
8785
|
+
* Set the agent's status in root awareness with auto-clear after idle.
|
|
8786
|
+
*/
|
|
8787
|
+
setAutoStatus(status: string | null, docId?: string): void;
|
|
8788
|
+
/** Re-send typing indicator every 2s so dashboard keeps showing it (expires at 3s). */
|
|
8789
|
+
private _startTypingInterval;
|
|
8790
|
+
private _stopTypingInterval;
|
|
8791
|
+
/**
|
|
8792
|
+
* Broadcast which tool the agent is currently executing.
|
|
8793
|
+
* Dashboard renders this as a ChatTool indicator.
|
|
8794
|
+
*/
|
|
8795
|
+
setActiveToolCall(toolCall: {
|
|
8796
|
+
name: string;
|
|
8797
|
+
target?: string;
|
|
8798
|
+
} | null): void;
|
|
8799
|
+
/**
|
|
8800
|
+
* Send a typing indicator to a chat channel.
|
|
8801
|
+
*/
|
|
8802
|
+
sendTypingIndicator(channel: string): void;
|
|
8781
8803
|
/** Graceful shutdown. */
|
|
8782
8804
|
destroy(): Promise<void>;
|
|
8783
8805
|
}
|
package/package.json
CHANGED
package/src/server.ts
CHANGED
|
@@ -48,6 +48,9 @@ export class AbracadabraMCPServer {
|
|
|
48
48
|
private _serverRef: Server | null = null
|
|
49
49
|
private _handledTaskIds = new Set<string>()
|
|
50
50
|
private _userId: string | null = null
|
|
51
|
+
private _statusClearTimer: ReturnType<typeof setTimeout> | null = null
|
|
52
|
+
private _typingInterval: ReturnType<typeof setInterval> | null = null
|
|
53
|
+
private _lastChatChannel: string | null = null
|
|
51
54
|
|
|
52
55
|
constructor(config: MCPServerConfig) {
|
|
53
56
|
this.config = config
|
|
@@ -176,6 +179,7 @@ export class AbracadabraMCPServer {
|
|
|
176
179
|
name: this.agentName,
|
|
177
180
|
color: this.agentColor,
|
|
178
181
|
publicKey: this._userId,
|
|
182
|
+
isAgent: true,
|
|
179
183
|
})
|
|
180
184
|
|
|
181
185
|
const conn: SpaceConnection = { doc, provider, docId }
|
|
@@ -240,6 +244,7 @@ export class AbracadabraMCPServer {
|
|
|
240
244
|
name: this.agentName,
|
|
241
245
|
color: this.agentColor,
|
|
242
246
|
publicKey: this._userId,
|
|
247
|
+
isAgent: true,
|
|
243
248
|
})
|
|
244
249
|
|
|
245
250
|
this.childCache.set(docId, {
|
|
@@ -323,6 +328,7 @@ export class AbracadabraMCPServer {
|
|
|
323
328
|
: 'Unknown'
|
|
324
329
|
|
|
325
330
|
console.error(`[abracadabra-mcp] Handling ai:task id=${id} from ${senderName}: ${text.slice(0, 80)}`)
|
|
331
|
+
this.setAutoStatus('thinking')
|
|
326
332
|
this._dispatchAiTask({
|
|
327
333
|
id,
|
|
328
334
|
text,
|
|
@@ -400,6 +406,25 @@ export class AbracadabraMCPServer {
|
|
|
400
406
|
}
|
|
401
407
|
}
|
|
402
408
|
|
|
409
|
+
// Auto-mark channel as read so sender sees a read receipt
|
|
410
|
+
if (channel) {
|
|
411
|
+
const rootProvider = this._activeConnection?.provider
|
|
412
|
+
if (rootProvider) {
|
|
413
|
+
rootProvider.sendStateless(JSON.stringify({
|
|
414
|
+
type: 'chat:mark_read',
|
|
415
|
+
channel,
|
|
416
|
+
timestamp: Math.floor(Date.now() / 1000),
|
|
417
|
+
}))
|
|
418
|
+
}
|
|
419
|
+
// Send immediate typing indicator and start periodic re-sends
|
|
420
|
+
this._lastChatChannel = channel
|
|
421
|
+
this.sendTypingIndicator(channel)
|
|
422
|
+
this._startTypingInterval(channel)
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
// Set status to "thinking" so dashboard shows AI is processing
|
|
426
|
+
this.setAutoStatus('thinking')
|
|
427
|
+
|
|
403
428
|
await this._serverRef.notification({
|
|
404
429
|
method: 'notifications/claude/channel',
|
|
405
430
|
params: {
|
|
@@ -420,8 +445,82 @@ export class AbracadabraMCPServer {
|
|
|
420
445
|
}
|
|
421
446
|
}
|
|
422
447
|
|
|
448
|
+
/**
|
|
449
|
+
* Set the agent's status in root awareness with auto-clear after idle.
|
|
450
|
+
*/
|
|
451
|
+
setAutoStatus(status: string | null, docId?: string): void {
|
|
452
|
+
const provider = this._activeConnection?.provider
|
|
453
|
+
if (!provider) return
|
|
454
|
+
|
|
455
|
+
if (this._statusClearTimer) {
|
|
456
|
+
clearTimeout(this._statusClearTimer)
|
|
457
|
+
this._statusClearTimer = null
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
provider.awareness.setLocalStateField('status', status)
|
|
461
|
+
if (docId !== undefined) {
|
|
462
|
+
provider.awareness.setLocalStateField('docId', docId)
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
// When clearing status, also stop typing interval
|
|
466
|
+
if (!status) {
|
|
467
|
+
this._stopTypingInterval()
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
// Auto-clear status after 30s of no updates
|
|
471
|
+
if (status) {
|
|
472
|
+
this._statusClearTimer = setTimeout(() => {
|
|
473
|
+
provider.awareness.setLocalStateField('status', null)
|
|
474
|
+
provider.awareness.setLocalStateField('activeToolCall', null)
|
|
475
|
+
this._stopTypingInterval()
|
|
476
|
+
}, 30_000)
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
/** Re-send typing indicator every 2s so dashboard keeps showing it (expires at 3s). */
|
|
481
|
+
private _startTypingInterval(channel: string): void {
|
|
482
|
+
this._stopTypingInterval()
|
|
483
|
+
this._typingInterval = setInterval(() => {
|
|
484
|
+
this.sendTypingIndicator(channel)
|
|
485
|
+
}, 2000)
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
private _stopTypingInterval(): void {
|
|
489
|
+
if (this._typingInterval) {
|
|
490
|
+
clearInterval(this._typingInterval)
|
|
491
|
+
this._typingInterval = null
|
|
492
|
+
}
|
|
493
|
+
this._lastChatChannel = null
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
/**
|
|
497
|
+
* Broadcast which tool the agent is currently executing.
|
|
498
|
+
* Dashboard renders this as a ChatTool indicator.
|
|
499
|
+
*/
|
|
500
|
+
setActiveToolCall(toolCall: { name: string; target?: string } | null): void {
|
|
501
|
+
this._activeConnection?.provider?.awareness.setLocalStateField('activeToolCall', toolCall)
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
/**
|
|
505
|
+
* Send a typing indicator to a chat channel.
|
|
506
|
+
*/
|
|
507
|
+
sendTypingIndicator(channel: string): void {
|
|
508
|
+
const rootProvider = this._activeConnection?.provider
|
|
509
|
+
if (!rootProvider) return
|
|
510
|
+
rootProvider.sendStateless(JSON.stringify({
|
|
511
|
+
type: 'chat:typing',
|
|
512
|
+
channel,
|
|
513
|
+
sender_name: this.agentName,
|
|
514
|
+
}))
|
|
515
|
+
}
|
|
516
|
+
|
|
423
517
|
/** Graceful shutdown. */
|
|
424
518
|
async destroy(): Promise<void> {
|
|
519
|
+
this._stopTypingInterval()
|
|
520
|
+
if (this._statusClearTimer) {
|
|
521
|
+
clearTimeout(this._statusClearTimer)
|
|
522
|
+
this._statusClearTimer = null
|
|
523
|
+
}
|
|
425
524
|
if (this.evictionTimer) {
|
|
426
525
|
clearInterval(this.evictionTimer)
|
|
427
526
|
this.evictionTimer = null
|
package/src/tools/channel.ts
CHANGED
|
@@ -17,9 +17,13 @@ export function registerChannelTools(mcp: McpServer, server: AbracadabraMCPServe
|
|
|
17
17
|
},
|
|
18
18
|
async ({ doc_id, text, task_id }) => {
|
|
19
19
|
try {
|
|
20
|
+
server.setAutoStatus('writing', doc_id)
|
|
21
|
+
server.setActiveToolCall({ name: 'reply', target: doc_id })
|
|
22
|
+
|
|
20
23
|
const treeMap = server.getTreeMap()
|
|
21
24
|
const rootDoc = server.rootDocument
|
|
22
25
|
if (!treeMap || !rootDoc) {
|
|
26
|
+
server.setActiveToolCall(null)
|
|
23
27
|
return { content: [{ type: 'text' as const, text: 'Not connected' }], isError: true }
|
|
24
28
|
}
|
|
25
29
|
|
|
@@ -47,10 +51,13 @@ export function registerChannelTools(mcp: McpServer, server: AbracadabraMCPServe
|
|
|
47
51
|
server.clearAiTask(task_id)
|
|
48
52
|
}
|
|
49
53
|
|
|
54
|
+
server.setActiveToolCall(null)
|
|
55
|
+
|
|
50
56
|
return {
|
|
51
57
|
content: [{ type: 'text' as const, text: JSON.stringify({ replyDocId: replyId, parentId: doc_id }) }],
|
|
52
58
|
}
|
|
53
59
|
} catch (error: any) {
|
|
60
|
+
server.setActiveToolCall(null)
|
|
54
61
|
return {
|
|
55
62
|
content: [{ type: 'text' as const, text: `Error: ${error.message}` }],
|
|
56
63
|
isError: true,
|
|
@@ -80,6 +87,10 @@ export function registerChannelTools(mcp: McpServer, server: AbracadabraMCPServe
|
|
|
80
87
|
sender_name: server.agentName,
|
|
81
88
|
}))
|
|
82
89
|
|
|
90
|
+
// Clear thinking/typing status after sending the reply
|
|
91
|
+
server.setAutoStatus(null)
|
|
92
|
+
server.setActiveToolCall(null)
|
|
93
|
+
|
|
83
94
|
return { content: [{ type: 'text' as const, text: `Sent to ${channel}` }] }
|
|
84
95
|
} catch (error: any) {
|
|
85
96
|
return {
|
package/src/tools/content.ts
CHANGED
|
@@ -17,6 +17,9 @@ export function registerContentTools(mcp: McpServer, server: AbracadabraMCPServe
|
|
|
17
17
|
},
|
|
18
18
|
async ({ docId }) => {
|
|
19
19
|
try {
|
|
20
|
+
server.setAutoStatus('reading', docId)
|
|
21
|
+
server.setActiveToolCall({ name: 'read_document', target: docId })
|
|
22
|
+
|
|
20
23
|
const provider = await server.getChildProvider(docId)
|
|
21
24
|
const fragment = provider.document.getXmlFragment('default')
|
|
22
25
|
|
|
@@ -48,6 +51,8 @@ export function registerContentTools(mcp: McpServer, server: AbracadabraMCPServe
|
|
|
48
51
|
children.sort((a: any, b: any) => ((treeMap.get(a.id)?.order ?? 0) - (treeMap.get(b.id)?.order ?? 0)))
|
|
49
52
|
}
|
|
50
53
|
|
|
54
|
+
server.setActiveToolCall(null)
|
|
55
|
+
|
|
51
56
|
const result: Record<string, unknown> = { label, type, meta, markdown, children }
|
|
52
57
|
return {
|
|
53
58
|
content: [{
|
|
@@ -56,6 +61,7 @@ export function registerContentTools(mcp: McpServer, server: AbracadabraMCPServe
|
|
|
56
61
|
}],
|
|
57
62
|
}
|
|
58
63
|
} catch (error: any) {
|
|
64
|
+
server.setActiveToolCall(null)
|
|
59
65
|
return {
|
|
60
66
|
content: [{ type: 'text', text: `Error reading document: ${error.message}` }],
|
|
61
67
|
isError: true,
|
|
@@ -74,6 +80,9 @@ export function registerContentTools(mcp: McpServer, server: AbracadabraMCPServe
|
|
|
74
80
|
},
|
|
75
81
|
async ({ docId, markdown, mode }) => {
|
|
76
82
|
try {
|
|
83
|
+
server.setAutoStatus('writing', docId)
|
|
84
|
+
server.setActiveToolCall({ name: 'write_document', target: docId })
|
|
85
|
+
|
|
77
86
|
const writeMode = mode ?? 'replace'
|
|
78
87
|
const provider = await server.getChildProvider(docId)
|
|
79
88
|
const doc = provider.document
|
|
@@ -119,10 +128,13 @@ export function registerContentTools(mcp: McpServer, server: AbracadabraMCPServe
|
|
|
119
128
|
server.setFocusedDoc(docId)
|
|
120
129
|
server.setDocCursor(docId, fragment.length)
|
|
121
130
|
|
|
131
|
+
server.setActiveToolCall(null)
|
|
132
|
+
|
|
122
133
|
return {
|
|
123
134
|
content: [{ type: 'text', text: `Document ${docId} updated (${writeMode} mode)` }],
|
|
124
135
|
}
|
|
125
136
|
} catch (error: any) {
|
|
137
|
+
server.setActiveToolCall(null)
|
|
126
138
|
return {
|
|
127
139
|
content: [{ type: 'text', text: `Error writing document: ${error.message}` }],
|
|
128
140
|
isError: true,
|
package/src/tools/tree.ts
CHANGED
|
@@ -186,9 +186,15 @@ export function registerTreeTools(mcp: McpServer, server: AbracadabraMCPServer)
|
|
|
186
186
|
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.'),
|
|
187
187
|
},
|
|
188
188
|
async ({ parentId, label, type, meta }) => {
|
|
189
|
+
server.setAutoStatus('creating')
|
|
190
|
+
server.setActiveToolCall({ name: 'create_document', target: label })
|
|
191
|
+
|
|
189
192
|
const treeMap = server.getTreeMap()
|
|
190
193
|
const rootDoc = server.rootDocument
|
|
191
|
-
if (!treeMap || !rootDoc)
|
|
194
|
+
if (!treeMap || !rootDoc) {
|
|
195
|
+
server.setActiveToolCall(null)
|
|
196
|
+
return { content: [{ type: 'text', text: 'Not connected' }] }
|
|
197
|
+
}
|
|
192
198
|
|
|
193
199
|
const id = crypto.randomUUID()
|
|
194
200
|
const normalizedParent = normalizeRootId(parentId, server)
|
|
@@ -205,6 +211,8 @@ export function registerTreeTools(mcp: McpServer, server: AbracadabraMCPServer)
|
|
|
205
211
|
})
|
|
206
212
|
})
|
|
207
213
|
|
|
214
|
+
server.setActiveToolCall(null)
|
|
215
|
+
|
|
208
216
|
return {
|
|
209
217
|
content: [{
|
|
210
218
|
type: 'text',
|