@deus-ai/channel-core 1.0.0
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/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/dist/message-buffer.d.ts +20 -0
- package/dist/message-buffer.d.ts.map +1 -0
- package/dist/message-buffer.js +37 -0
- package/dist/message-buffer.js.map +1 -0
- package/dist/server-base.d.ts +9 -0
- package/dist/server-base.d.ts.map +1 -0
- package/dist/server-base.js +86 -0
- package/dist/server-base.js.map +1 -0
- package/dist/types.d.ts +63 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +28 -0
- package/src/index.ts +8 -0
- package/src/message-buffer.ts +45 -0
- package/src/server-base.ts +137 -0
- package/src/types.ts +75 -0
- package/tsconfig.json +17 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AACvD,YAAY,EACV,eAAe,EACf,aAAa,EACb,QAAQ,EACR,eAAe,GAChB,MAAM,YAAY,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { IncomingMessage } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Ring buffer for incoming messages.
|
|
4
|
+
* Supports cursor-based pagination for the get_new_messages polling tool.
|
|
5
|
+
*/
|
|
6
|
+
export declare class MessageBuffer {
|
|
7
|
+
private messages;
|
|
8
|
+
private nextCursor;
|
|
9
|
+
push(msg: IncomingMessage): void;
|
|
10
|
+
/**
|
|
11
|
+
* Get messages since the given cursor.
|
|
12
|
+
* Returns the messages and a new cursor for the next call.
|
|
13
|
+
*/
|
|
14
|
+
getSince(cursor?: string): {
|
|
15
|
+
messages: IncomingMessage[];
|
|
16
|
+
cursor: string;
|
|
17
|
+
};
|
|
18
|
+
get size(): number;
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=message-buffer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"message-buffer.d.ts","sourceRoot":"","sources":["../src/message-buffer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAI7C;;;GAGG;AACH,qBAAa,aAAa;IACxB,OAAO,CAAC,QAAQ,CAAyB;IACzC,OAAO,CAAC,UAAU,CAAK;IAEvB,IAAI,CAAC,GAAG,EAAE,eAAe,GAAG,IAAI;IAWhC;;;OAGG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG;QAAE,QAAQ,EAAE,eAAe,EAAE,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE;IAc1E,IAAI,IAAI,IAAI,MAAM,CAEjB;CACF"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
const MAX_BUFFER_SIZE = 1000;
|
|
2
|
+
/**
|
|
3
|
+
* Ring buffer for incoming messages.
|
|
4
|
+
* Supports cursor-based pagination for the get_new_messages polling tool.
|
|
5
|
+
*/
|
|
6
|
+
export class MessageBuffer {
|
|
7
|
+
messages = [];
|
|
8
|
+
nextCursor = 0;
|
|
9
|
+
push(msg) {
|
|
10
|
+
this.messages.push(msg);
|
|
11
|
+
this.nextCursor++;
|
|
12
|
+
// Trim old messages to prevent unbounded growth
|
|
13
|
+
if (this.messages.length > MAX_BUFFER_SIZE) {
|
|
14
|
+
const excess = this.messages.length - MAX_BUFFER_SIZE;
|
|
15
|
+
this.messages.splice(0, excess);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Get messages since the given cursor.
|
|
20
|
+
* Returns the messages and a new cursor for the next call.
|
|
21
|
+
*/
|
|
22
|
+
getSince(cursor) {
|
|
23
|
+
const from = cursor ? parseInt(cursor, 10) : 0;
|
|
24
|
+
// Calculate the offset into the current buffer
|
|
25
|
+
const bufferStart = this.nextCursor - this.messages.length;
|
|
26
|
+
const startIndex = Math.max(0, from - bufferStart);
|
|
27
|
+
const newMessages = this.messages.slice(startIndex);
|
|
28
|
+
return {
|
|
29
|
+
messages: newMessages,
|
|
30
|
+
cursor: String(this.nextCursor),
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
get size() {
|
|
34
|
+
return this.messages.length;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=message-buffer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"message-buffer.js","sourceRoot":"","sources":["../src/message-buffer.ts"],"names":[],"mappings":"AAEA,MAAM,eAAe,GAAG,IAAI,CAAC;AAE7B;;;GAGG;AACH,MAAM,OAAO,aAAa;IAChB,QAAQ,GAAsB,EAAE,CAAC;IACjC,UAAU,GAAG,CAAC,CAAC;IAEvB,IAAI,CAAC,GAAoB;QACvB,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACxB,IAAI,CAAC,UAAU,EAAE,CAAC;QAElB,gDAAgD;QAChD,IAAI,IAAI,CAAC,QAAQ,CAAC,MAAM,GAAG,eAAe,EAAE,CAAC;YAC3C,MAAM,MAAM,GAAG,IAAI,CAAC,QAAQ,CAAC,MAAM,GAAG,eAAe,CAAC;YACtD,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC;QAClC,CAAC;IACH,CAAC;IAED;;;OAGG;IACH,QAAQ,CAAC,MAAe;QACtB,MAAM,IAAI,GAAG,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QAE/C,+CAA+C;QAC/C,MAAM,WAAW,GAAG,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC;QAC3D,MAAM,UAAU,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,GAAG,WAAW,CAAC,CAAC;QACnD,MAAM,WAAW,GAAG,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;QAEpD,OAAO;YACL,QAAQ,EAAE,WAAW;YACrB,MAAM,EAAE,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC;SAChC,CAAC;IACJ,CAAC;IAED,IAAI,IAAI;QACN,OAAO,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC;IAC9B,CAAC;CACF"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
import { MessageBuffer } from './message-buffer.js';
|
|
3
|
+
import type { ChannelProvider } from './types.js';
|
|
4
|
+
/**
|
|
5
|
+
* Register the common MCP tools that all channel servers share.
|
|
6
|
+
* Channel-specific tools (e.g., WhatsApp auth) are registered separately.
|
|
7
|
+
*/
|
|
8
|
+
export declare function registerCommonTools(server: McpServer, provider: ChannelProvider): MessageBuffer;
|
|
9
|
+
//# sourceMappingURL=server-base.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server-base.d.ts","sourceRoot":"","sources":["../src/server-base.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAGpE,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,KAAK,EAAE,eAAe,EAAmB,MAAM,YAAY,CAAC;AAEnE;;;GAGG;AACH,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,SAAS,EACjB,QAAQ,EAAE,eAAe,GACxB,aAAa,CA2Hf"}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { MessageBuffer } from './message-buffer.js';
|
|
3
|
+
/**
|
|
4
|
+
* Register the common MCP tools that all channel servers share.
|
|
5
|
+
* Channel-specific tools (e.g., WhatsApp auth) are registered separately.
|
|
6
|
+
*/
|
|
7
|
+
export function registerCommonTools(server, provider) {
|
|
8
|
+
const buffer = new MessageBuffer();
|
|
9
|
+
// Wire provider's message handler → buffer + MCP notification
|
|
10
|
+
provider.onMessage = (msg) => {
|
|
11
|
+
buffer.push(msg);
|
|
12
|
+
// Push to MCP client via logging notification (real-time path)
|
|
13
|
+
server.server.sendLoggingMessage({
|
|
14
|
+
level: 'info',
|
|
15
|
+
logger: 'incoming_message',
|
|
16
|
+
data: msg,
|
|
17
|
+
});
|
|
18
|
+
};
|
|
19
|
+
// ── Core messaging ──────────────────────────────────────────────────
|
|
20
|
+
server.tool('send_message', 'Send a message to a chat or group', { chat_id: z.string(), text: z.string() }, async (args) => {
|
|
21
|
+
if (!provider.isConnected() && provider.waitForReady) {
|
|
22
|
+
await Promise.race([
|
|
23
|
+
provider.waitForReady(),
|
|
24
|
+
new Promise((resolve) => setTimeout(resolve, 15_000)),
|
|
25
|
+
]);
|
|
26
|
+
}
|
|
27
|
+
await provider.sendMessage(args.chat_id, args.text);
|
|
28
|
+
return { content: [{ type: 'text', text: 'Message sent.' }] };
|
|
29
|
+
});
|
|
30
|
+
server.tool('send_typing', 'Show or hide typing indicator', { chat_id: z.string(), is_typing: z.boolean() }, async (args) => {
|
|
31
|
+
if (provider.setTyping) {
|
|
32
|
+
await provider.setTyping(args.chat_id, args.is_typing);
|
|
33
|
+
}
|
|
34
|
+
return { content: [{ type: 'text', text: 'OK' }] };
|
|
35
|
+
});
|
|
36
|
+
// ── Status and discovery ────────────────────────────────────────────
|
|
37
|
+
server.tool('get_status', 'Get connection status and channel info', {}, async () => {
|
|
38
|
+
// If the provider is still connecting, wait briefly for it to be ready
|
|
39
|
+
if (!provider.isConnected() && provider.waitForReady) {
|
|
40
|
+
await Promise.race([
|
|
41
|
+
provider.waitForReady(),
|
|
42
|
+
new Promise((resolve) => setTimeout(resolve, 15_000)),
|
|
43
|
+
]);
|
|
44
|
+
}
|
|
45
|
+
const status = provider.getStatus();
|
|
46
|
+
return {
|
|
47
|
+
content: [{ type: 'text', text: JSON.stringify(status) }],
|
|
48
|
+
};
|
|
49
|
+
});
|
|
50
|
+
server.tool('list_chats', 'List known chats and groups', {}, async () => {
|
|
51
|
+
const chats = provider.listChats ? await provider.listChats() : [];
|
|
52
|
+
return {
|
|
53
|
+
content: [{ type: 'text', text: JSON.stringify(chats) }],
|
|
54
|
+
};
|
|
55
|
+
});
|
|
56
|
+
server.tool('sync_groups', 'Refresh group and chat metadata from the platform', { force: z.boolean().optional() }, async () => {
|
|
57
|
+
if (!provider.isConnected() && provider.waitForReady) {
|
|
58
|
+
await Promise.race([
|
|
59
|
+
provider.waitForReady(),
|
|
60
|
+
new Promise((resolve) => setTimeout(resolve, 15_000)),
|
|
61
|
+
]);
|
|
62
|
+
}
|
|
63
|
+
const groups = provider.syncGroups ? await provider.syncGroups() : [];
|
|
64
|
+
return {
|
|
65
|
+
content: [{ type: 'text', text: JSON.stringify(groups) }],
|
|
66
|
+
};
|
|
67
|
+
});
|
|
68
|
+
// ── Polling fallback ────────────────────────────────────────────────
|
|
69
|
+
server.tool('get_new_messages', 'Poll for incoming messages since the last call. Use the returned cursor for subsequent calls.', { since_cursor: z.string().optional() }, async (args) => {
|
|
70
|
+
const result = buffer.getSince(args.since_cursor);
|
|
71
|
+
return {
|
|
72
|
+
content: [{ type: 'text', text: JSON.stringify(result) }],
|
|
73
|
+
};
|
|
74
|
+
});
|
|
75
|
+
// ── Lifecycle ───────────────────────────────────────────────────────
|
|
76
|
+
server.tool('connect', 'Connect to the messaging platform', {}, async () => {
|
|
77
|
+
await provider.connect();
|
|
78
|
+
return { content: [{ type: 'text', text: 'Connected.' }] };
|
|
79
|
+
});
|
|
80
|
+
server.tool('disconnect', 'Disconnect from the messaging platform', {}, async () => {
|
|
81
|
+
await provider.disconnect();
|
|
82
|
+
return { content: [{ type: 'text', text: 'Disconnected.' }] };
|
|
83
|
+
});
|
|
84
|
+
return buffer;
|
|
85
|
+
}
|
|
86
|
+
//# sourceMappingURL=server-base.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server-base.js","sourceRoot":"","sources":["../src/server-base.ts"],"names":[],"mappings":"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"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/** Normalized incoming message from any channel. */
|
|
2
|
+
export interface IncomingMessage {
|
|
3
|
+
id: string;
|
|
4
|
+
chat_id: string;
|
|
5
|
+
sender: string;
|
|
6
|
+
sender_name: string;
|
|
7
|
+
content: string;
|
|
8
|
+
timestamp: string;
|
|
9
|
+
is_from_me?: boolean;
|
|
10
|
+
is_group?: boolean;
|
|
11
|
+
chat_name?: string;
|
|
12
|
+
/** Channel-specific metadata (e.g., reply context, media info). */
|
|
13
|
+
metadata?: Record<string, unknown>;
|
|
14
|
+
}
|
|
15
|
+
/** Connection status returned by get_status. */
|
|
16
|
+
export interface ChannelStatus {
|
|
17
|
+
connected: boolean;
|
|
18
|
+
channel: string;
|
|
19
|
+
identity?: string;
|
|
20
|
+
uptime_seconds?: number;
|
|
21
|
+
}
|
|
22
|
+
/** Chat/group info returned by list_chats. */
|
|
23
|
+
export interface ChatInfo {
|
|
24
|
+
id: string;
|
|
25
|
+
name: string;
|
|
26
|
+
is_group: boolean;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Interface that each channel implementation must provide.
|
|
30
|
+
* The base server wraps this in MCP tools.
|
|
31
|
+
*/
|
|
32
|
+
export interface ChannelProvider {
|
|
33
|
+
/** Channel name (e.g., 'whatsapp', 'telegram'). */
|
|
34
|
+
readonly name: string;
|
|
35
|
+
/** Connect to the messaging platform. */
|
|
36
|
+
connect(): Promise<void>;
|
|
37
|
+
/** Disconnect from the messaging platform. */
|
|
38
|
+
disconnect(): Promise<void>;
|
|
39
|
+
/** Whether the channel is currently connected. */
|
|
40
|
+
isConnected(): boolean;
|
|
41
|
+
/** Send a text message to a chat. */
|
|
42
|
+
sendMessage(chatId: string, text: string): Promise<void>;
|
|
43
|
+
/** Get current connection status. */
|
|
44
|
+
getStatus(): ChannelStatus;
|
|
45
|
+
/** Show/hide typing indicator. Optional — not all platforms support it. */
|
|
46
|
+
setTyping?(chatId: string, isTyping: boolean): Promise<void>;
|
|
47
|
+
/** List known chats/groups. Optional — not all platforms support listing. */
|
|
48
|
+
listChats?(): Promise<ChatInfo[]>;
|
|
49
|
+
/** Refresh group/chat metadata from the platform. */
|
|
50
|
+
syncGroups?(): Promise<ChatInfo[]>;
|
|
51
|
+
/**
|
|
52
|
+
* Wait for the channel to be ready (connected).
|
|
53
|
+
* Optional — if not implemented, tools assume the channel is ready immediately.
|
|
54
|
+
* Should resolve when connected, or reject/timeout if connection fails.
|
|
55
|
+
*/
|
|
56
|
+
waitForReady?(): Promise<void>;
|
|
57
|
+
/**
|
|
58
|
+
* Called by the base server to inject the message handler.
|
|
59
|
+
* The channel calls this function whenever a new message arrives.
|
|
60
|
+
*/
|
|
61
|
+
onMessage: (msg: IncomingMessage) => void;
|
|
62
|
+
}
|
|
63
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,oDAAoD;AACpD,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,mEAAmE;IACnE,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACpC;AAED,gDAAgD;AAChD,MAAM,WAAW,aAAa;IAC5B,SAAS,EAAE,OAAO,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,8CAA8C;AAC9C,MAAM,WAAW,QAAQ;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED;;;GAGG;AACH,MAAM,WAAW,eAAe;IAC9B,mDAAmD;IACnD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAEtB,yCAAyC;IACzC,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAEzB,8CAA8C;IAC9C,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAE5B,kDAAkD;IAClD,WAAW,IAAI,OAAO,CAAC;IAEvB,qCAAqC;IACrC,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAEzD,qCAAqC;IACrC,SAAS,IAAI,aAAa,CAAC;IAE3B,2EAA2E;IAC3E,SAAS,CAAC,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAE7D,6EAA6E;IAC7E,SAAS,CAAC,IAAI,OAAO,CAAC,QAAQ,EAAE,CAAC,CAAC;IAElC,qDAAqD;IACrD,UAAU,CAAC,IAAI,OAAO,CAAC,QAAQ,EAAE,CAAC,CAAC;IAEnC;;;;OAIG;IACH,YAAY,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAE/B;;;OAGG;IACH,SAAS,EAAE,CAAC,GAAG,EAAE,eAAe,KAAK,IAAI,CAAC;CAC3C"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
|
package/package.json
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@deus-ai/channel-core",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Shared types and base server logic for Deus MCP channel servers",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"scripts": {
|
|
9
|
+
"build": "tsc",
|
|
10
|
+
"test": "vitest run"
|
|
11
|
+
},
|
|
12
|
+
"dependencies": {
|
|
13
|
+
"@modelcontextprotocol/sdk": "^1.12.1",
|
|
14
|
+
"zod": "^4.3.6"
|
|
15
|
+
},
|
|
16
|
+
"devDependencies": {
|
|
17
|
+
"@types/node": "^22.10.7",
|
|
18
|
+
"typescript": "^6.0.2",
|
|
19
|
+
"vitest": "^4.1.2"
|
|
20
|
+
},
|
|
21
|
+
"engines": {
|
|
22
|
+
"node": ">=20"
|
|
23
|
+
},
|
|
24
|
+
"publishConfig": {
|
|
25
|
+
"access": "public"
|
|
26
|
+
},
|
|
27
|
+
"license": "MIT"
|
|
28
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { IncomingMessage } from './types.js';
|
|
2
|
+
|
|
3
|
+
const MAX_BUFFER_SIZE = 1000;
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Ring buffer for incoming messages.
|
|
7
|
+
* Supports cursor-based pagination for the get_new_messages polling tool.
|
|
8
|
+
*/
|
|
9
|
+
export class MessageBuffer {
|
|
10
|
+
private messages: IncomingMessage[] = [];
|
|
11
|
+
private nextCursor = 0;
|
|
12
|
+
|
|
13
|
+
push(msg: IncomingMessage): void {
|
|
14
|
+
this.messages.push(msg);
|
|
15
|
+
this.nextCursor++;
|
|
16
|
+
|
|
17
|
+
// Trim old messages to prevent unbounded growth
|
|
18
|
+
if (this.messages.length > MAX_BUFFER_SIZE) {
|
|
19
|
+
const excess = this.messages.length - MAX_BUFFER_SIZE;
|
|
20
|
+
this.messages.splice(0, excess);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Get messages since the given cursor.
|
|
26
|
+
* Returns the messages and a new cursor for the next call.
|
|
27
|
+
*/
|
|
28
|
+
getSince(cursor?: string): { messages: IncomingMessage[]; cursor: string } {
|
|
29
|
+
const from = cursor ? parseInt(cursor, 10) : 0;
|
|
30
|
+
|
|
31
|
+
// Calculate the offset into the current buffer
|
|
32
|
+
const bufferStart = this.nextCursor - this.messages.length;
|
|
33
|
+
const startIndex = Math.max(0, from - bufferStart);
|
|
34
|
+
const newMessages = this.messages.slice(startIndex);
|
|
35
|
+
|
|
36
|
+
return {
|
|
37
|
+
messages: newMessages,
|
|
38
|
+
cursor: String(this.nextCursor),
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
get size(): number {
|
|
43
|
+
return this.messages.length;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
|
|
4
|
+
import { MessageBuffer } from './message-buffer.js';
|
|
5
|
+
import type { ChannelProvider, IncomingMessage } from './types.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Register the common MCP tools that all channel servers share.
|
|
9
|
+
* Channel-specific tools (e.g., WhatsApp auth) are registered separately.
|
|
10
|
+
*/
|
|
11
|
+
export function registerCommonTools(
|
|
12
|
+
server: McpServer,
|
|
13
|
+
provider: ChannelProvider,
|
|
14
|
+
): MessageBuffer {
|
|
15
|
+
const buffer = new MessageBuffer();
|
|
16
|
+
|
|
17
|
+
// Wire provider's message handler → buffer + MCP notification
|
|
18
|
+
provider.onMessage = (msg: IncomingMessage) => {
|
|
19
|
+
buffer.push(msg);
|
|
20
|
+
|
|
21
|
+
// Push to MCP client via logging notification (real-time path)
|
|
22
|
+
server.server.sendLoggingMessage({
|
|
23
|
+
level: 'info',
|
|
24
|
+
logger: 'incoming_message',
|
|
25
|
+
data: msg,
|
|
26
|
+
});
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
// ── Core messaging ──────────────────────────────────────────────────
|
|
30
|
+
|
|
31
|
+
server.tool(
|
|
32
|
+
'send_message',
|
|
33
|
+
'Send a message to a chat or group',
|
|
34
|
+
{ chat_id: z.string(), text: z.string() },
|
|
35
|
+
async (args) => {
|
|
36
|
+
if (!provider.isConnected() && provider.waitForReady) {
|
|
37
|
+
await Promise.race([
|
|
38
|
+
provider.waitForReady(),
|
|
39
|
+
new Promise((resolve) => setTimeout(resolve, 15_000)),
|
|
40
|
+
]);
|
|
41
|
+
}
|
|
42
|
+
await provider.sendMessage(args.chat_id, args.text);
|
|
43
|
+
return { content: [{ type: 'text' as const, text: 'Message sent.' }] };
|
|
44
|
+
},
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
server.tool(
|
|
48
|
+
'send_typing',
|
|
49
|
+
'Show or hide typing indicator',
|
|
50
|
+
{ chat_id: z.string(), is_typing: z.boolean() },
|
|
51
|
+
async (args) => {
|
|
52
|
+
if (provider.setTyping) {
|
|
53
|
+
await provider.setTyping(args.chat_id, args.is_typing);
|
|
54
|
+
}
|
|
55
|
+
return { content: [{ type: 'text' as const, text: 'OK' }] };
|
|
56
|
+
},
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
// ── Status and discovery ────────────────────────────────────────────
|
|
60
|
+
|
|
61
|
+
server.tool(
|
|
62
|
+
'get_status',
|
|
63
|
+
'Get connection status and channel info',
|
|
64
|
+
{},
|
|
65
|
+
async () => {
|
|
66
|
+
// If the provider is still connecting, wait briefly for it to be ready
|
|
67
|
+
if (!provider.isConnected() && provider.waitForReady) {
|
|
68
|
+
await Promise.race([
|
|
69
|
+
provider.waitForReady(),
|
|
70
|
+
new Promise((resolve) => setTimeout(resolve, 15_000)),
|
|
71
|
+
]);
|
|
72
|
+
}
|
|
73
|
+
const status = provider.getStatus();
|
|
74
|
+
return {
|
|
75
|
+
content: [{ type: 'text' as const, text: JSON.stringify(status) }],
|
|
76
|
+
};
|
|
77
|
+
},
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
server.tool('list_chats', 'List known chats and groups', {}, async () => {
|
|
81
|
+
const chats = provider.listChats ? await provider.listChats() : [];
|
|
82
|
+
return {
|
|
83
|
+
content: [{ type: 'text' as const, text: JSON.stringify(chats) }],
|
|
84
|
+
};
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
server.tool(
|
|
88
|
+
'sync_groups',
|
|
89
|
+
'Refresh group and chat metadata from the platform',
|
|
90
|
+
{ force: z.boolean().optional() },
|
|
91
|
+
async () => {
|
|
92
|
+
if (!provider.isConnected() && provider.waitForReady) {
|
|
93
|
+
await Promise.race([
|
|
94
|
+
provider.waitForReady(),
|
|
95
|
+
new Promise((resolve) => setTimeout(resolve, 15_000)),
|
|
96
|
+
]);
|
|
97
|
+
}
|
|
98
|
+
const groups = provider.syncGroups ? await provider.syncGroups() : [];
|
|
99
|
+
return {
|
|
100
|
+
content: [{ type: 'text' as const, text: JSON.stringify(groups) }],
|
|
101
|
+
};
|
|
102
|
+
},
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
// ── Polling fallback ────────────────────────────────────────────────
|
|
106
|
+
|
|
107
|
+
server.tool(
|
|
108
|
+
'get_new_messages',
|
|
109
|
+
'Poll for incoming messages since the last call. Use the returned cursor for subsequent calls.',
|
|
110
|
+
{ since_cursor: z.string().optional() },
|
|
111
|
+
async (args) => {
|
|
112
|
+
const result = buffer.getSince(args.since_cursor);
|
|
113
|
+
return {
|
|
114
|
+
content: [{ type: 'text' as const, text: JSON.stringify(result) }],
|
|
115
|
+
};
|
|
116
|
+
},
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
// ── Lifecycle ───────────────────────────────────────────────────────
|
|
120
|
+
|
|
121
|
+
server.tool('connect', 'Connect to the messaging platform', {}, async () => {
|
|
122
|
+
await provider.connect();
|
|
123
|
+
return { content: [{ type: 'text' as const, text: 'Connected.' }] };
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
server.tool(
|
|
127
|
+
'disconnect',
|
|
128
|
+
'Disconnect from the messaging platform',
|
|
129
|
+
{},
|
|
130
|
+
async () => {
|
|
131
|
+
await provider.disconnect();
|
|
132
|
+
return { content: [{ type: 'text' as const, text: 'Disconnected.' }] };
|
|
133
|
+
},
|
|
134
|
+
);
|
|
135
|
+
|
|
136
|
+
return buffer;
|
|
137
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/** Normalized incoming message from any channel. */
|
|
2
|
+
export interface IncomingMessage {
|
|
3
|
+
id: string;
|
|
4
|
+
chat_id: string;
|
|
5
|
+
sender: string;
|
|
6
|
+
sender_name: string;
|
|
7
|
+
content: string;
|
|
8
|
+
timestamp: string;
|
|
9
|
+
is_from_me?: boolean;
|
|
10
|
+
is_group?: boolean;
|
|
11
|
+
chat_name?: string;
|
|
12
|
+
/** Channel-specific metadata (e.g., reply context, media info). */
|
|
13
|
+
metadata?: Record<string, unknown>;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** Connection status returned by get_status. */
|
|
17
|
+
export interface ChannelStatus {
|
|
18
|
+
connected: boolean;
|
|
19
|
+
channel: string;
|
|
20
|
+
identity?: string;
|
|
21
|
+
uptime_seconds?: number;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Chat/group info returned by list_chats. */
|
|
25
|
+
export interface ChatInfo {
|
|
26
|
+
id: string;
|
|
27
|
+
name: string;
|
|
28
|
+
is_group: boolean;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Interface that each channel implementation must provide.
|
|
33
|
+
* The base server wraps this in MCP tools.
|
|
34
|
+
*/
|
|
35
|
+
export interface ChannelProvider {
|
|
36
|
+
/** Channel name (e.g., 'whatsapp', 'telegram'). */
|
|
37
|
+
readonly name: string;
|
|
38
|
+
|
|
39
|
+
/** Connect to the messaging platform. */
|
|
40
|
+
connect(): Promise<void>;
|
|
41
|
+
|
|
42
|
+
/** Disconnect from the messaging platform. */
|
|
43
|
+
disconnect(): Promise<void>;
|
|
44
|
+
|
|
45
|
+
/** Whether the channel is currently connected. */
|
|
46
|
+
isConnected(): boolean;
|
|
47
|
+
|
|
48
|
+
/** Send a text message to a chat. */
|
|
49
|
+
sendMessage(chatId: string, text: string): Promise<void>;
|
|
50
|
+
|
|
51
|
+
/** Get current connection status. */
|
|
52
|
+
getStatus(): ChannelStatus;
|
|
53
|
+
|
|
54
|
+
/** Show/hide typing indicator. Optional — not all platforms support it. */
|
|
55
|
+
setTyping?(chatId: string, isTyping: boolean): Promise<void>;
|
|
56
|
+
|
|
57
|
+
/** List known chats/groups. Optional — not all platforms support listing. */
|
|
58
|
+
listChats?(): Promise<ChatInfo[]>;
|
|
59
|
+
|
|
60
|
+
/** Refresh group/chat metadata from the platform. */
|
|
61
|
+
syncGroups?(): Promise<ChatInfo[]>;
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Wait for the channel to be ready (connected).
|
|
65
|
+
* Optional — if not implemented, tools assume the channel is ready immediately.
|
|
66
|
+
* Should resolve when connected, or reject/timeout if connection fails.
|
|
67
|
+
*/
|
|
68
|
+
waitForReady?(): Promise<void>;
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Called by the base server to inject the message handler.
|
|
72
|
+
* The channel calls this function whenever a new message arrives.
|
|
73
|
+
*/
|
|
74
|
+
onMessage: (msg: IncomingMessage) => void;
|
|
75
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "NodeNext",
|
|
5
|
+
"moduleResolution": "NodeNext",
|
|
6
|
+
"outDir": "./dist",
|
|
7
|
+
"rootDir": "./src",
|
|
8
|
+
"strict": true,
|
|
9
|
+
"esModuleInterop": true,
|
|
10
|
+
"skipLibCheck": true,
|
|
11
|
+
"declaration": true,
|
|
12
|
+
"declarationMap": true,
|
|
13
|
+
"sourceMap": true
|
|
14
|
+
},
|
|
15
|
+
"include": ["src/**/*"],
|
|
16
|
+
"exclude": ["node_modules", "dist"]
|
|
17
|
+
}
|