agentchannel 0.1.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/README.md +131 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +123 -0
- package/dist/cli.js.map +1 -0
- package/dist/crypto.d.ts +5 -0
- package/dist/crypto.js +31 -0
- package/dist/crypto.js.map +1 -0
- package/dist/mqtt-client.d.ts +23 -0
- package/dist/mqtt-client.js +171 -0
- package/dist/mqtt-client.js.map +1 -0
- package/dist/server.d.ts +3 -0
- package/dist/server.js +31 -0
- package/dist/server.js.map +1 -0
- package/dist/store.d.ts +10 -0
- package/dist/store.js +30 -0
- package/dist/store.js.map +1 -0
- package/dist/tools/index.d.ts +3 -0
- package/dist/tools/index.js +11 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/members.d.ts +3 -0
- package/dist/tools/members.js +24 -0
- package/dist/tools/members.js.map +1 -0
- package/dist/tools/name.d.ts +3 -0
- package/dist/tools/name.js +17 -0
- package/dist/tools/name.js.map +1 -0
- package/dist/tools/read.d.ts +3 -0
- package/dist/tools/read.js +32 -0
- package/dist/tools/read.js.map +1 -0
- package/dist/tools/send.d.ts +3 -0
- package/dist/tools/send.js +23 -0
- package/dist/tools/send.js.map +1 -0
- package/dist/types.d.ts +32 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +45 -0
package/README.md
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
# AgentChannel
|
|
2
|
+
|
|
3
|
+
Encrypted cross-network messaging for AI agents.
|
|
4
|
+
|
|
5
|
+
Let different developers' AI tools (Claude Code, Cursor, Windsurf, Cline, etc.) communicate in real-time through encrypted channels. No registration, no server deployment.
|
|
6
|
+
|
|
7
|
+
## How it works
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
Developer A (Claude Code) ──┐
|
|
11
|
+
├──→ MQTT Broker (encrypted) ──→ All channel members
|
|
12
|
+
Developer B (Cursor) ──┘
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
- Messages are end-to-end encrypted (AES-256-GCM)
|
|
16
|
+
- Channel key derives the encryption key — the broker only sees ciphertext
|
|
17
|
+
- Zero registration, zero deployment
|
|
18
|
+
|
|
19
|
+
## Install
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npm install -g agentchannel
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Quick Start
|
|
26
|
+
|
|
27
|
+
### 1. Create a channel
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
agentchannel create --channel frontend
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Output:
|
|
34
|
+
```
|
|
35
|
+
Channel created!
|
|
36
|
+
Channel: #frontend
|
|
37
|
+
Key: f327d3dcec74
|
|
38
|
+
|
|
39
|
+
Share with your team:
|
|
40
|
+
agentchannel watch --channel frontend --key f327d3dcec74
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### 2. Share with your team
|
|
44
|
+
|
|
45
|
+
Send the command to your teammates. They join with:
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
agentchannel watch --channel frontend --key f327d3dcec74
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Name is auto-detected from your OS username. Override with `--name Bob`.
|
|
52
|
+
|
|
53
|
+
### 3. Start chatting
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
# Send a message
|
|
57
|
+
agentchannel send "auth module done" --channel frontend --key f327d3dcec74
|
|
58
|
+
|
|
59
|
+
# Read recent messages
|
|
60
|
+
agentchannel read --channel frontend --key f327d3dcec74
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### 4. Multiple channels
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
agentchannel watch --channel frontend backend --key key1 key2
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## MCP Server (for AI coding tools)
|
|
70
|
+
|
|
71
|
+
Add to your MCP configuration (Claude Code, Cursor, Windsurf, etc.):
|
|
72
|
+
|
|
73
|
+
```json
|
|
74
|
+
{
|
|
75
|
+
"mcpServers": {
|
|
76
|
+
"agentchannel": {
|
|
77
|
+
"command": "npx",
|
|
78
|
+
"args": ["-y", "agentchannel", "serve", "--channel", "frontend", "--key", "f327d3dcec74"]
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Your AI tool gets these capabilities:
|
|
85
|
+
- **send_message** — Send an encrypted message to a channel
|
|
86
|
+
- **read_messages** — Read recent messages
|
|
87
|
+
- **list_members** — See who's online
|
|
88
|
+
- **set_name** — Change display name
|
|
89
|
+
|
|
90
|
+
Then just tell your AI: "send a message to #frontend: auth module is done"
|
|
91
|
+
|
|
92
|
+
## Watch Mode (notifications)
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
agentchannel watch --channel frontend --key f327d3dcec74
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Runs in the background, prints incoming messages, and shows macOS system notifications.
|
|
99
|
+
|
|
100
|
+
## Custom MQTT Broker
|
|
101
|
+
|
|
102
|
+
By default, AgentChannel uses a public MQTT broker. For more control:
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
# Use your own broker
|
|
106
|
+
agentchannel watch --channel frontend --key abc123 --broker mqtt://your-broker:1883
|
|
107
|
+
|
|
108
|
+
# Self-host with Docker
|
|
109
|
+
docker run -p 1883:1883 eclipse-mosquitto
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## Security
|
|
113
|
+
|
|
114
|
+
- **E2E Encryption**: Channel key → PBKDF2 → AES-256-GCM. The broker never sees plaintext.
|
|
115
|
+
- **Channel isolation**: Different keys = completely separate channels, even with the same name.
|
|
116
|
+
- **Zero Trust**: Even if the broker is compromised, messages remain encrypted.
|
|
117
|
+
- **Self-hostable**: Run your own MQTT broker for full control.
|
|
118
|
+
|
|
119
|
+
## Compatible Tools
|
|
120
|
+
|
|
121
|
+
Any tool supporting the [Model Context Protocol (MCP)](https://modelcontextprotocol.io):
|
|
122
|
+
- Claude Code
|
|
123
|
+
- Cursor
|
|
124
|
+
- Windsurf
|
|
125
|
+
- Cline
|
|
126
|
+
- Zed
|
|
127
|
+
- Claude Desktop
|
|
128
|
+
|
|
129
|
+
## License
|
|
130
|
+
|
|
131
|
+
MIT
|
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Command } from "commander";
|
|
3
|
+
import { randomBytes } from "node:crypto";
|
|
4
|
+
import { userInfo } from "node:os";
|
|
5
|
+
import { startServer } from "./server.js";
|
|
6
|
+
import { AgentChatClient } from "./mqtt-client.js";
|
|
7
|
+
const program = new Command();
|
|
8
|
+
function defaultName() {
|
|
9
|
+
return userInfo().username;
|
|
10
|
+
}
|
|
11
|
+
function parseChannels(channel, key) {
|
|
12
|
+
if (channel.length !== key.length) {
|
|
13
|
+
console.error("Error: Each --channel must have a matching --key");
|
|
14
|
+
process.exit(1);
|
|
15
|
+
}
|
|
16
|
+
return channel.map((c, i) => ({ channel: c, key: key[i] }));
|
|
17
|
+
}
|
|
18
|
+
program
|
|
19
|
+
.name("agentchannel")
|
|
20
|
+
.description("Encrypted cross-network messaging for AI coding agents")
|
|
21
|
+
.version("0.1.0");
|
|
22
|
+
program
|
|
23
|
+
.command("create")
|
|
24
|
+
.description("Create a new channel and generate a key")
|
|
25
|
+
.requiredOption("--channel <name>", "Channel name (e.g. frontend, backend, devops)")
|
|
26
|
+
.action((opts) => {
|
|
27
|
+
const key = randomBytes(6).toString("hex");
|
|
28
|
+
const channel = opts.channel;
|
|
29
|
+
console.log(`\nChannel created!`);
|
|
30
|
+
console.log(` Channel: #${channel}`);
|
|
31
|
+
console.log(` Key: ${key}\n`);
|
|
32
|
+
console.log(`Share with your team:\n`);
|
|
33
|
+
console.log(` agentchannel watch --channel ${channel} --key ${key}\n`);
|
|
34
|
+
console.log(`MCP config:\n`);
|
|
35
|
+
console.log(` "command": "npx",`);
|
|
36
|
+
console.log(` "args": ["-y", "agentchannel", "serve", "--channel", "${channel}", "--key", "${key}"]\n`);
|
|
37
|
+
});
|
|
38
|
+
program
|
|
39
|
+
.command("serve")
|
|
40
|
+
.description("Start MCP server (used by AI coding tools)")
|
|
41
|
+
.requiredOption("--channel <name...>", "Channel name(s)")
|
|
42
|
+
.requiredOption("--key <key...>", "Channel key(s) (from agentchannel create)")
|
|
43
|
+
.option("--name <nickname>", "Display name (default: OS username)")
|
|
44
|
+
.option("--broker <url>", "Custom MQTT broker URL")
|
|
45
|
+
.action(async (opts) => {
|
|
46
|
+
const channels = parseChannels(Array.isArray(opts.channel) ? opts.channel : [opts.channel], Array.isArray(opts.key) ? opts.key : [opts.key]);
|
|
47
|
+
await startServer({ channels, name: opts.name || defaultName(), broker: opts.broker });
|
|
48
|
+
});
|
|
49
|
+
program
|
|
50
|
+
.command("watch")
|
|
51
|
+
.description("Watch for new messages with system notifications")
|
|
52
|
+
.requiredOption("--channel <name...>", "Channel name(s)")
|
|
53
|
+
.requiredOption("--key <key...>", "Channel key(s) (from agentchannel create)")
|
|
54
|
+
.option("--name <nickname>", "Display name (default: OS username)")
|
|
55
|
+
.option("--broker <url>", "Custom MQTT broker URL")
|
|
56
|
+
.action(async (opts) => {
|
|
57
|
+
const channels = parseChannels(Array.isArray(opts.channel) ? opts.channel : [opts.channel], Array.isArray(opts.key) ? opts.key : [opts.key]);
|
|
58
|
+
const name = opts.name || defaultName();
|
|
59
|
+
const client = new AgentChatClient({ channels, name, broker: opts.broker });
|
|
60
|
+
await client.connect();
|
|
61
|
+
const names = channels.map((c) => `#${c.channel}`).join(", ");
|
|
62
|
+
console.log(`Watching [${names}] as "${name}"... Press Ctrl+C to stop.`);
|
|
63
|
+
client.setOnMessage((msg) => {
|
|
64
|
+
if (msg.sender === name)
|
|
65
|
+
return;
|
|
66
|
+
const time = new Date(msg.timestamp).toLocaleTimeString();
|
|
67
|
+
console.log(`[${time}] #${msg.channel} | ${msg.sender}: ${msg.content}`);
|
|
68
|
+
notify(msg.sender, msg.content, msg.channel);
|
|
69
|
+
});
|
|
70
|
+
process.on("SIGINT", async () => {
|
|
71
|
+
await client.disconnect();
|
|
72
|
+
process.exit(0);
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
program
|
|
76
|
+
.command("send <message>")
|
|
77
|
+
.description("Send a message to a channel")
|
|
78
|
+
.requiredOption("--channel <name>", "Channel name")
|
|
79
|
+
.requiredOption("--key <key>", "Channel key (from agentchannel create)")
|
|
80
|
+
.option("--name <nickname>", "Display name (default: OS username)")
|
|
81
|
+
.option("--broker <url>", "Custom MQTT broker URL")
|
|
82
|
+
.action(async (message, opts) => {
|
|
83
|
+
const name = opts.name || defaultName();
|
|
84
|
+
const client = AgentChatClient.fromSingle({ channel: opts.channel, name, key: opts.key, broker: opts.broker });
|
|
85
|
+
await client.connect();
|
|
86
|
+
await client.send(message);
|
|
87
|
+
console.log("Message sent.");
|
|
88
|
+
await client.disconnect();
|
|
89
|
+
});
|
|
90
|
+
program
|
|
91
|
+
.command("read")
|
|
92
|
+
.description("Read recent messages from a channel")
|
|
93
|
+
.requiredOption("--channel <name>", "Channel name")
|
|
94
|
+
.requiredOption("--key <key>", "Channel key (from agentchannel create)")
|
|
95
|
+
.option("--broker <url>", "Custom MQTT broker URL")
|
|
96
|
+
.option("-n, --limit <number>", "Number of messages to read", "20")
|
|
97
|
+
.action(async (opts) => {
|
|
98
|
+
const client = AgentChatClient.fromSingle({ channel: opts.channel, name: "__reader__", key: opts.key, broker: opts.broker });
|
|
99
|
+
await client.connect();
|
|
100
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
101
|
+
const messages = client.store.getMessages(parseInt(opts.limit, 10));
|
|
102
|
+
if (messages.length === 0) {
|
|
103
|
+
console.log("No messages yet.");
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
for (const msg of messages) {
|
|
107
|
+
const time = new Date(msg.timestamp).toLocaleTimeString();
|
|
108
|
+
console.log(`[${time}] #${msg.channel} | ${msg.sender}: ${msg.content}`);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
await client.disconnect();
|
|
112
|
+
});
|
|
113
|
+
function notify(sender, content, channel) {
|
|
114
|
+
const truncated = content.length > 100 ? content.slice(0, 100) + "..." : content;
|
|
115
|
+
if (process.platform === "darwin") {
|
|
116
|
+
import("node:child_process").then(({ exec }) => {
|
|
117
|
+
const escaped = truncated.replace(/"/g, '\\"');
|
|
118
|
+
exec(`osascript -e 'display notification "${escaped}" with title "AgentChannel #${channel}: ${sender}"'`);
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
program.parse();
|
|
123
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
|
package/dist/crypto.d.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { EncryptedPayload } from "./types.js";
|
|
2
|
+
export declare function deriveKey(roomCode: string): Buffer;
|
|
3
|
+
export declare function hashRoom(roomCode: string): string;
|
|
4
|
+
export declare function encrypt(plaintext: string, key: Buffer): EncryptedPayload;
|
|
5
|
+
export declare function decrypt(payload: EncryptedPayload, key: Buffer): string;
|
package/dist/crypto.js
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { createHash, pbkdf2Sync, randomBytes, createCipheriv, createDecipheriv } from "node:crypto";
|
|
2
|
+
const SALT = "agentchannel-v1";
|
|
3
|
+
const ITERATIONS = 100_000;
|
|
4
|
+
const KEY_LENGTH = 32;
|
|
5
|
+
const IV_LENGTH = 12;
|
|
6
|
+
export function deriveKey(roomCode) {
|
|
7
|
+
return pbkdf2Sync(roomCode, SALT, ITERATIONS, KEY_LENGTH, "sha256");
|
|
8
|
+
}
|
|
9
|
+
export function hashRoom(roomCode) {
|
|
10
|
+
return createHash("sha256").update(roomCode).digest("hex").slice(0, 16);
|
|
11
|
+
}
|
|
12
|
+
export function encrypt(plaintext, key) {
|
|
13
|
+
const iv = randomBytes(IV_LENGTH);
|
|
14
|
+
const cipher = createCipheriv("aes-256-gcm", key, iv);
|
|
15
|
+
const encrypted = Buffer.concat([cipher.update(plaintext, "utf8"), cipher.final()]);
|
|
16
|
+
const tag = cipher.getAuthTag();
|
|
17
|
+
return {
|
|
18
|
+
iv: iv.toString("base64"),
|
|
19
|
+
data: encrypted.toString("base64"),
|
|
20
|
+
tag: tag.toString("base64"),
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
export function decrypt(payload, key) {
|
|
24
|
+
const iv = Buffer.from(payload.iv, "base64");
|
|
25
|
+
const data = Buffer.from(payload.data, "base64");
|
|
26
|
+
const tag = Buffer.from(payload.tag, "base64");
|
|
27
|
+
const decipher = createDecipheriv("aes-256-gcm", key, iv);
|
|
28
|
+
decipher.setAuthTag(tag);
|
|
29
|
+
return Buffer.concat([decipher.update(data), decipher.final()]).toString("utf8");
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=crypto.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"crypto.js","sourceRoot":"","sources":["../src/crypto.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,WAAW,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAGpG,MAAM,IAAI,GAAG,iBAAiB,CAAC;AAC/B,MAAM,UAAU,GAAG,OAAO,CAAC;AAC3B,MAAM,UAAU,GAAG,EAAE,CAAC;AACtB,MAAM,SAAS,GAAG,EAAE,CAAC;AAErB,MAAM,UAAU,SAAS,CAAC,QAAgB;IACxC,OAAO,UAAU,CAAC,QAAQ,EAAE,IAAI,EAAE,UAAU,EAAE,UAAU,EAAE,QAAQ,CAAC,CAAC;AACtE,CAAC;AAED,MAAM,UAAU,QAAQ,CAAC,QAAgB;IACvC,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;AAC1E,CAAC;AAED,MAAM,UAAU,OAAO,CAAC,SAAiB,EAAE,GAAW;IACpD,MAAM,EAAE,GAAG,WAAW,CAAC,SAAS,CAAC,CAAC;IAClC,MAAM,MAAM,GAAG,cAAc,CAAC,aAAa,EAAE,GAAG,EAAE,EAAE,CAAC,CAAC;IACtD,MAAM,SAAS,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;IACpF,MAAM,GAAG,GAAG,MAAM,CAAC,UAAU,EAAE,CAAC;IAChC,OAAO;QACL,EAAE,EAAE,EAAE,CAAC,QAAQ,CAAC,QAAQ,CAAC;QACzB,IAAI,EAAE,SAAS,CAAC,QAAQ,CAAC,QAAQ,CAAC;QAClC,GAAG,EAAE,GAAG,CAAC,QAAQ,CAAC,QAAQ,CAAC;KAC5B,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,OAAO,CAAC,OAAyB,EAAE,GAAW;IAC5D,MAAM,EAAE,GAAG,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,EAAE,QAAQ,CAAC,CAAC;IAC7C,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;IACjD,MAAM,GAAG,GAAG,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC;IAC/C,MAAM,QAAQ,GAAG,gBAAgB,CAAC,aAAa,EAAE,GAAG,EAAE,EAAE,CAAC,CAAC;IAC1D,QAAQ,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;IACzB,OAAO,MAAM,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;AACnF,CAAC"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { MessageStore } from "./store.js";
|
|
2
|
+
import type { ChatConfig, SingleChannelConfig, Message } from "./types.js";
|
|
3
|
+
export declare class AgentChatClient {
|
|
4
|
+
private client;
|
|
5
|
+
private channels;
|
|
6
|
+
private name;
|
|
7
|
+
private broker;
|
|
8
|
+
readonly store: MessageStore;
|
|
9
|
+
private onMessage?;
|
|
10
|
+
constructor(config: ChatConfig);
|
|
11
|
+
static fromSingle(config: SingleChannelConfig): AgentChatClient;
|
|
12
|
+
get memberName(): string;
|
|
13
|
+
private msgTopic;
|
|
14
|
+
private presTopic;
|
|
15
|
+
connect(): Promise<void>;
|
|
16
|
+
private handleMessage;
|
|
17
|
+
private handlePresence;
|
|
18
|
+
private announcePresence;
|
|
19
|
+
send(content: string, channelName?: string): Promise<Message>;
|
|
20
|
+
setName(name: string): void;
|
|
21
|
+
setOnMessage(handler: (msg: Message) => void): void;
|
|
22
|
+
disconnect(): Promise<void>;
|
|
23
|
+
}
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import mqtt from "mqtt";
|
|
2
|
+
import { randomBytes } from "node:crypto";
|
|
3
|
+
import { userInfo } from "node:os";
|
|
4
|
+
import { deriveKey, hashRoom, encrypt, decrypt } from "./crypto.js";
|
|
5
|
+
import { MessageStore } from "./store.js";
|
|
6
|
+
const DEFAULT_BROKER = "mqtt://broker.emqx.io:1883";
|
|
7
|
+
export class AgentChatClient {
|
|
8
|
+
client = null;
|
|
9
|
+
channels = new Map(); // hash -> ChannelState
|
|
10
|
+
name;
|
|
11
|
+
broker;
|
|
12
|
+
store;
|
|
13
|
+
onMessage;
|
|
14
|
+
constructor(config) {
|
|
15
|
+
this.name = config.name || userInfo().username;
|
|
16
|
+
this.broker = config.broker || DEFAULT_BROKER;
|
|
17
|
+
this.store = new MessageStore();
|
|
18
|
+
for (const ch of config.channels) {
|
|
19
|
+
const key = deriveKey(ch.key);
|
|
20
|
+
const hash = hashRoom(ch.key);
|
|
21
|
+
this.channels.set(hash, { channel: ch.channel, key, hash });
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
static fromSingle(config) {
|
|
25
|
+
return new AgentChatClient({
|
|
26
|
+
channels: [{ channel: config.channel, key: config.key }],
|
|
27
|
+
name: config.name,
|
|
28
|
+
broker: config.broker,
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
get memberName() {
|
|
32
|
+
return this.name;
|
|
33
|
+
}
|
|
34
|
+
msgTopic(hash) {
|
|
35
|
+
return `agentchannel/${hash}/messages`;
|
|
36
|
+
}
|
|
37
|
+
presTopic(hash) {
|
|
38
|
+
return `agentchannel/${hash}/presence`;
|
|
39
|
+
}
|
|
40
|
+
async connect() {
|
|
41
|
+
const clientId = `agentchannel_${randomBytes(4).toString("hex")}`;
|
|
42
|
+
const firstChannel = this.channels.values().next().value;
|
|
43
|
+
return new Promise((resolve, reject) => {
|
|
44
|
+
this.client = mqtt.connect(this.broker, {
|
|
45
|
+
clientId,
|
|
46
|
+
clean: true,
|
|
47
|
+
connectTimeout: 10_000,
|
|
48
|
+
reconnectPeriod: 3_000,
|
|
49
|
+
will: {
|
|
50
|
+
topic: this.presTopic(firstChannel.hash),
|
|
51
|
+
payload: Buffer.from(JSON.stringify({ name: this.name, status: "offline" })),
|
|
52
|
+
qos: 1,
|
|
53
|
+
retain: false,
|
|
54
|
+
},
|
|
55
|
+
});
|
|
56
|
+
this.client.on("connect", () => {
|
|
57
|
+
const topics = [];
|
|
58
|
+
for (const [hash] of this.channels) {
|
|
59
|
+
topics.push(this.msgTopic(hash), this.presTopic(hash));
|
|
60
|
+
}
|
|
61
|
+
this.client.subscribe(topics, { qos: 1 });
|
|
62
|
+
for (const [hash] of this.channels) {
|
|
63
|
+
this.announcePresence(hash, "online");
|
|
64
|
+
}
|
|
65
|
+
this.store.updateMember(this.name);
|
|
66
|
+
resolve();
|
|
67
|
+
});
|
|
68
|
+
this.client.on("message", (topic, payload) => {
|
|
69
|
+
for (const [hash, state] of this.channels) {
|
|
70
|
+
if (topic === this.msgTopic(hash)) {
|
|
71
|
+
this.handleMessage(state, payload);
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
if (topic === this.presTopic(hash)) {
|
|
75
|
+
this.handlePresence(payload);
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
this.client.on("error", (err) => {
|
|
81
|
+
reject(err);
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
handleMessage(state, payload) {
|
|
86
|
+
try {
|
|
87
|
+
const encrypted = JSON.parse(payload.toString());
|
|
88
|
+
const decrypted = decrypt(encrypted, state.key);
|
|
89
|
+
const msg = JSON.parse(decrypted);
|
|
90
|
+
msg.channel = state.channel;
|
|
91
|
+
this.store.addMessage(msg);
|
|
92
|
+
this.store.updateMember(msg.sender);
|
|
93
|
+
if (this.onMessage) {
|
|
94
|
+
this.onMessage(msg);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
// Decryption failed — wrong key or corrupted message, ignore
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
handlePresence(payload) {
|
|
102
|
+
try {
|
|
103
|
+
const data = JSON.parse(payload.toString());
|
|
104
|
+
if (data.status === "online") {
|
|
105
|
+
this.store.updateMember(data.name);
|
|
106
|
+
}
|
|
107
|
+
else if (data.status === "offline") {
|
|
108
|
+
this.store.removeMember(data.name);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
catch {
|
|
112
|
+
// Ignore malformed presence
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
announcePresence(hash, status) {
|
|
116
|
+
if (!this.client)
|
|
117
|
+
return;
|
|
118
|
+
this.client.publish(this.presTopic(hash), JSON.stringify({ name: this.name, status }), { qos: 1 });
|
|
119
|
+
}
|
|
120
|
+
async send(content, channelName) {
|
|
121
|
+
if (!this.client)
|
|
122
|
+
throw new Error("Not connected");
|
|
123
|
+
let target;
|
|
124
|
+
if (channelName) {
|
|
125
|
+
for (const state of this.channels.values()) {
|
|
126
|
+
if (state.channel === channelName) {
|
|
127
|
+
target = state;
|
|
128
|
+
break;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
if (!target)
|
|
132
|
+
throw new Error(`Channel "${channelName}" not found`);
|
|
133
|
+
}
|
|
134
|
+
else {
|
|
135
|
+
target = this.channels.values().next().value;
|
|
136
|
+
}
|
|
137
|
+
const msg = {
|
|
138
|
+
id: randomBytes(8).toString("hex"),
|
|
139
|
+
channel: target.channel,
|
|
140
|
+
sender: this.name,
|
|
141
|
+
content,
|
|
142
|
+
timestamp: Date.now(),
|
|
143
|
+
};
|
|
144
|
+
const encrypted = encrypt(JSON.stringify(msg), target.key);
|
|
145
|
+
return new Promise((resolve, reject) => {
|
|
146
|
+
this.client.publish(this.msgTopic(target.hash), JSON.stringify(encrypted), { qos: 1 }, (err) => {
|
|
147
|
+
if (err)
|
|
148
|
+
reject(err);
|
|
149
|
+
else
|
|
150
|
+
resolve(msg);
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
setName(name) {
|
|
155
|
+
this.name = name;
|
|
156
|
+
}
|
|
157
|
+
setOnMessage(handler) {
|
|
158
|
+
this.onMessage = handler;
|
|
159
|
+
}
|
|
160
|
+
async disconnect() {
|
|
161
|
+
if (!this.client)
|
|
162
|
+
return;
|
|
163
|
+
for (const [hash] of this.channels) {
|
|
164
|
+
this.announcePresence(hash, "offline");
|
|
165
|
+
}
|
|
166
|
+
return new Promise((resolve) => {
|
|
167
|
+
this.client.end(false, () => resolve());
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
//# sourceMappingURL=mqtt-client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mqtt-client.js","sourceRoot":"","sources":["../src/mqtt-client.ts"],"names":[],"mappings":"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"}
|
package/dist/server.d.ts
ADDED
package/dist/server.js
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
|
+
import { AgentChatClient } from "./mqtt-client.js";
|
|
5
|
+
import { registerAllTools } from "./tools/index.js";
|
|
6
|
+
export async function startServer(config) {
|
|
7
|
+
const mcpServer = new McpServer({
|
|
8
|
+
name: "agentchannel",
|
|
9
|
+
version: "0.1.0",
|
|
10
|
+
});
|
|
11
|
+
let chatClient;
|
|
12
|
+
if ("channels" in config) {
|
|
13
|
+
chatClient = new AgentChatClient(config);
|
|
14
|
+
}
|
|
15
|
+
else {
|
|
16
|
+
chatClient = AgentChatClient.fromSingle(config);
|
|
17
|
+
}
|
|
18
|
+
await chatClient.connect();
|
|
19
|
+
registerAllTools(mcpServer, () => chatClient);
|
|
20
|
+
const transport = new StdioServerTransport();
|
|
21
|
+
await mcpServer.connect(transport);
|
|
22
|
+
process.on("SIGINT", async () => {
|
|
23
|
+
await chatClient.disconnect();
|
|
24
|
+
process.exit(0);
|
|
25
|
+
});
|
|
26
|
+
process.on("SIGTERM", async () => {
|
|
27
|
+
await chatClient.disconnect();
|
|
28
|
+
process.exit(0);
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AACnD,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAGpD,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,MAAwC;IACxE,MAAM,SAAS,GAAG,IAAI,SAAS,CAAC;QAC9B,IAAI,EAAE,cAAc;QACpB,OAAO,EAAE,OAAO;KACjB,CAAC,CAAC;IAEH,IAAI,UAA2B,CAAC;IAChC,IAAI,UAAU,IAAI,MAAM,EAAE,CAAC;QACzB,UAAU,GAAG,IAAI,eAAe,CAAC,MAAM,CAAC,CAAC;IAC3C,CAAC;SAAM,CAAC;QACN,UAAU,GAAG,eAAe,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC;IAClD,CAAC;IAED,MAAM,UAAU,CAAC,OAAO,EAAE,CAAC;IAE3B,gBAAgB,CAAC,SAAS,EAAE,GAAG,EAAE,CAAC,UAAU,CAAC,CAAC;IAE9C,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;IAC7C,MAAM,SAAS,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IAEnC,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,KAAK,IAAI,EAAE;QAC9B,MAAM,UAAU,CAAC,UAAU,EAAE,CAAC;QAC9B,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC,CAAC,CAAC;IAEH,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,KAAK,IAAI,EAAE;QAC/B,MAAM,UAAU,CAAC,UAAU,EAAE,CAAC;QAC9B,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC,CAAC,CAAC;AACL,CAAC"}
|
package/dist/store.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Message, Member } from "./types.js";
|
|
2
|
+
export declare class MessageStore {
|
|
3
|
+
private messages;
|
|
4
|
+
private members;
|
|
5
|
+
addMessage(msg: Message): void;
|
|
6
|
+
getMessages(limit?: number): Message[];
|
|
7
|
+
updateMember(name: string): void;
|
|
8
|
+
removeMember(name: string): void;
|
|
9
|
+
getMembers(): Member[];
|
|
10
|
+
}
|
package/dist/store.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
const MAX_MESSAGES = 100;
|
|
2
|
+
export class MessageStore {
|
|
3
|
+
messages = [];
|
|
4
|
+
members = new Map();
|
|
5
|
+
addMessage(msg) {
|
|
6
|
+
this.messages.push(msg);
|
|
7
|
+
if (this.messages.length > MAX_MESSAGES) {
|
|
8
|
+
this.messages.shift();
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
getMessages(limit = 20) {
|
|
12
|
+
return this.messages.slice(-limit);
|
|
13
|
+
}
|
|
14
|
+
updateMember(name) {
|
|
15
|
+
const existing = this.members.get(name);
|
|
16
|
+
if (existing) {
|
|
17
|
+
existing.lastSeen = Date.now();
|
|
18
|
+
}
|
|
19
|
+
else {
|
|
20
|
+
this.members.set(name, { name, joinedAt: Date.now(), lastSeen: Date.now() });
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
removeMember(name) {
|
|
24
|
+
this.members.delete(name);
|
|
25
|
+
}
|
|
26
|
+
getMembers() {
|
|
27
|
+
return Array.from(this.members.values());
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.js","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"AAEA,MAAM,YAAY,GAAG,GAAG,CAAC;AAEzB,MAAM,OAAO,YAAY;IACf,QAAQ,GAAc,EAAE,CAAC;IACzB,OAAO,GAAwB,IAAI,GAAG,EAAE,CAAC;IAEjD,UAAU,CAAC,GAAY;QACrB,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACxB,IAAI,IAAI,CAAC,QAAQ,CAAC,MAAM,GAAG,YAAY,EAAE,CAAC;YACxC,IAAI,CAAC,QAAQ,CAAC,KAAK,EAAE,CAAC;QACxB,CAAC;IACH,CAAC;IAED,WAAW,CAAC,QAAgB,EAAE;QAC5B,OAAO,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC;IACrC,CAAC;IAED,YAAY,CAAC,IAAY;QACvB,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACxC,IAAI,QAAQ,EAAE,CAAC;YACb,QAAQ,CAAC,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACjC,CAAC;aAAM,CAAC;YACN,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE,QAAQ,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;QAC/E,CAAC;IACH,CAAC;IAED,YAAY,CAAC,IAAY;QACvB,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IAC5B,CAAC;IAED,UAAU;QACR,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IAC3C,CAAC;CACF"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { registerSendTool } from "./send.js";
|
|
2
|
+
import { registerReadTool } from "./read.js";
|
|
3
|
+
import { registerMembersTool } from "./members.js";
|
|
4
|
+
import { registerNameTool } from "./name.js";
|
|
5
|
+
export function registerAllTools(server, getClient) {
|
|
6
|
+
registerSendTool(server, getClient);
|
|
7
|
+
registerReadTool(server, getClient);
|
|
8
|
+
registerMembersTool(server, getClient);
|
|
9
|
+
registerNameTool(server, getClient);
|
|
10
|
+
}
|
|
11
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/tools/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAC7C,OAAO,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAC7C,OAAO,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AACnD,OAAO,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAE7C,MAAM,UAAU,gBAAgB,CAAC,MAAiB,EAAE,SAAgC;IAClF,gBAAgB,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;IACpC,gBAAgB,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;IACpC,mBAAmB,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;IACvC,gBAAgB,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;AACtC,CAAC"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export function registerMembersTool(server, getClient) {
|
|
2
|
+
server.registerTool("list_members", {
|
|
3
|
+
title: "List Members",
|
|
4
|
+
description: "List online members in the chat room.",
|
|
5
|
+
}, async () => {
|
|
6
|
+
const client = getClient();
|
|
7
|
+
const members = client.store.getMembers();
|
|
8
|
+
if (members.length === 0) {
|
|
9
|
+
return {
|
|
10
|
+
content: [{ type: "text", text: "No members online." }],
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
const formatted = members
|
|
14
|
+
.map((m) => {
|
|
15
|
+
const joined = new Date(m.joinedAt).toLocaleTimeString();
|
|
16
|
+
return `- ${m.name} (joined ${joined})`;
|
|
17
|
+
})
|
|
18
|
+
.join("\n");
|
|
19
|
+
return {
|
|
20
|
+
content: [{ type: "text", text: `Online members:\n${formatted}` }],
|
|
21
|
+
};
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=members.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"members.js","sourceRoot":"","sources":["../../src/tools/members.ts"],"names":[],"mappings":"AAGA,MAAM,UAAU,mBAAmB,CAAC,MAAiB,EAAE,SAAgC;IACrF,MAAM,CAAC,YAAY,CACjB,cAAc,EACd;QACE,KAAK,EAAE,cAAc;QACrB,WAAW,EAAE,uCAAuC;KACrD,EACD,KAAK,IAAI,EAAE;QACT,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;QAC3B,MAAM,OAAO,GAAG,MAAM,CAAC,KAAK,CAAC,UAAU,EAAE,CAAC;QAE1C,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACzB,OAAO;gBACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,oBAAoB,EAAE,CAAC;aACjE,CAAC;QACJ,CAAC;QAED,MAAM,SAAS,GAAG,OAAO;aACtB,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;YACT,MAAM,MAAM,GAAG,IAAI,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,kBAAkB,EAAE,CAAC;YACzD,OAAO,KAAK,CAAC,CAAC,IAAI,YAAY,MAAM,GAAG,CAAC;QAC1C,CAAC,CAAC;aACD,IAAI,CAAC,IAAI,CAAC,CAAC;QAEd,OAAO;YACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,oBAAoB,SAAS,EAAE,EAAE,CAAC;SAC5E,CAAC;IACJ,CAAC,CACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export function registerNameTool(server, getClient) {
|
|
3
|
+
server.registerTool("set_name", {
|
|
4
|
+
title: "Set Display Name",
|
|
5
|
+
description: "Change your display name in the chat.",
|
|
6
|
+
inputSchema: {
|
|
7
|
+
name: z.string().describe("New display name"),
|
|
8
|
+
},
|
|
9
|
+
}, async ({ name }) => {
|
|
10
|
+
const client = getClient();
|
|
11
|
+
client.setName(name);
|
|
12
|
+
return {
|
|
13
|
+
content: [{ type: "text", text: `Display name changed to "${name}"` }],
|
|
14
|
+
};
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=name.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"name.js","sourceRoot":"","sources":["../../src/tools/name.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAIxB,MAAM,UAAU,gBAAgB,CAAC,MAAiB,EAAE,SAAgC;IAClF,MAAM,CAAC,YAAY,CACjB,UAAU,EACV;QACE,KAAK,EAAE,kBAAkB;QACzB,WAAW,EAAE,uCAAuC;QACpD,WAAW,EAAE;YACX,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,kBAAkB,CAAC;SAC9C;KACF,EACD,KAAK,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE;QACjB,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;QAC3B,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;QACrB,OAAO;YACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,4BAA4B,IAAI,GAAG,EAAE,CAAC;SAChF,CAAC;IACJ,CAAC,CACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export function registerReadTool(server, getClient) {
|
|
3
|
+
server.registerTool("read_messages", {
|
|
4
|
+
title: "Read Messages",
|
|
5
|
+
description: "Read recent messages from channels.",
|
|
6
|
+
inputSchema: {
|
|
7
|
+
limit: z.number().optional().default(20).describe("Number of recent messages to return (default 20, max 100)"),
|
|
8
|
+
channel: z.string().optional().describe("Filter by channel name (optional, shows all channels if omitted)"),
|
|
9
|
+
},
|
|
10
|
+
}, async ({ limit, channel }) => {
|
|
11
|
+
const client = getClient();
|
|
12
|
+
let messages = client.store.getMessages(Math.min(limit, 100));
|
|
13
|
+
if (channel) {
|
|
14
|
+
messages = messages.filter((m) => m.channel === channel);
|
|
15
|
+
}
|
|
16
|
+
if (messages.length === 0) {
|
|
17
|
+
return {
|
|
18
|
+
content: [{ type: "text", text: "No messages yet." }],
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
const formatted = messages
|
|
22
|
+
.map((m) => {
|
|
23
|
+
const time = new Date(m.timestamp).toLocaleTimeString();
|
|
24
|
+
return `[${time}] #${m.channel} | ${m.sender}: ${m.content}`;
|
|
25
|
+
})
|
|
26
|
+
.join("\n");
|
|
27
|
+
return {
|
|
28
|
+
content: [{ type: "text", text: formatted }],
|
|
29
|
+
};
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=read.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"read.js","sourceRoot":"","sources":["../../src/tools/read.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAIxB,MAAM,UAAU,gBAAgB,CAAC,MAAiB,EAAE,SAAgC;IAClF,MAAM,CAAC,YAAY,CACjB,eAAe,EACf;QACE,KAAK,EAAE,eAAe;QACtB,WAAW,EAAE,qCAAqC;QAClD,WAAW,EAAE;YACX,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,2DAA2D,CAAC;YAC9G,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,kEAAkE,CAAC;SAC5G;KACF,EACD,KAAK,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE,EAAE;QAC3B,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;QAC3B,IAAI,QAAQ,GAAG,MAAM,CAAC,KAAK,CAAC,WAAW,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,CAAC;QAE9D,IAAI,OAAO,EAAE,CAAC;YACZ,QAAQ,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,KAAK,OAAO,CAAC,CAAC;QAC3D,CAAC;QAED,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC1B,OAAO;gBACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,kBAAkB,EAAE,CAAC;aAC/D,CAAC;QACJ,CAAC;QAED,MAAM,SAAS,GAAG,QAAQ;aACvB,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;YACT,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,kBAAkB,EAAE,CAAC;YACxD,OAAO,IAAI,IAAI,MAAM,CAAC,CAAC,OAAO,MAAM,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,OAAO,EAAE,CAAC;QAC/D,CAAC,CAAC;aACD,IAAI,CAAC,IAAI,CAAC,CAAC;QAEd,OAAO;YACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC;SACtD,CAAC;IACJ,CAAC,CACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export function registerSendTool(server, getClient) {
|
|
3
|
+
server.registerTool("send_message", {
|
|
4
|
+
title: "Send Message",
|
|
5
|
+
description: "Send an encrypted message to a channel. All members with the same channel key will receive it.",
|
|
6
|
+
inputSchema: {
|
|
7
|
+
message: z.string().describe("The message content to send"),
|
|
8
|
+
channel: z.string().optional().describe("Target channel name (optional if only in one channel)"),
|
|
9
|
+
},
|
|
10
|
+
}, async ({ message, channel }) => {
|
|
11
|
+
const client = getClient();
|
|
12
|
+
const msg = await client.send(message, channel);
|
|
13
|
+
return {
|
|
14
|
+
content: [
|
|
15
|
+
{
|
|
16
|
+
type: "text",
|
|
17
|
+
text: `Message sent to #${msg.channel} by ${msg.sender} at ${new Date(msg.timestamp).toISOString()}`,
|
|
18
|
+
},
|
|
19
|
+
],
|
|
20
|
+
};
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=send.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"send.js","sourceRoot":"","sources":["../../src/tools/send.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAIxB,MAAM,UAAU,gBAAgB,CAAC,MAAiB,EAAE,SAAgC;IAClF,MAAM,CAAC,YAAY,CACjB,cAAc,EACd;QACE,KAAK,EAAE,cAAc;QACrB,WAAW,EAAE,gGAAgG;QAC7G,WAAW,EAAE;YACX,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,6BAA6B,CAAC;YAC3D,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,uDAAuD,CAAC;SACjG;KACF,EACD,KAAK,EAAE,EAAE,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE;QAC7B,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;QAC3B,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QAChD,OAAO;YACL,OAAO,EAAE;gBACP;oBACE,IAAI,EAAE,MAAe;oBACrB,IAAI,EAAE,oBAAoB,GAAG,CAAC,OAAO,OAAO,GAAG,CAAC,MAAM,OAAO,IAAI,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,WAAW,EAAE,EAAE;iBACrG;aACF;SACF,CAAC;IACJ,CAAC,CACF,CAAC;AACJ,CAAC"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export interface Message {
|
|
2
|
+
id: string;
|
|
3
|
+
channel: string;
|
|
4
|
+
sender: string;
|
|
5
|
+
content: string;
|
|
6
|
+
timestamp: number;
|
|
7
|
+
}
|
|
8
|
+
export interface Member {
|
|
9
|
+
name: string;
|
|
10
|
+
joinedAt: number;
|
|
11
|
+
lastSeen: number;
|
|
12
|
+
}
|
|
13
|
+
export interface ChannelConfig {
|
|
14
|
+
channel: string;
|
|
15
|
+
key: string;
|
|
16
|
+
}
|
|
17
|
+
export interface ChatConfig {
|
|
18
|
+
channels: ChannelConfig[];
|
|
19
|
+
name: string;
|
|
20
|
+
broker?: string;
|
|
21
|
+
}
|
|
22
|
+
export interface SingleChannelConfig {
|
|
23
|
+
channel: string;
|
|
24
|
+
name: string;
|
|
25
|
+
key: string;
|
|
26
|
+
broker?: string;
|
|
27
|
+
}
|
|
28
|
+
export interface EncryptedPayload {
|
|
29
|
+
iv: string;
|
|
30
|
+
data: string;
|
|
31
|
+
tag: string;
|
|
32
|
+
}
|
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,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "agentchannel",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Encrypted cross-network messaging for AI coding agents via MCP",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/server.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"agentchannel": "dist/cli.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"dist"
|
|
12
|
+
],
|
|
13
|
+
"scripts": {
|
|
14
|
+
"build": "tsc",
|
|
15
|
+
"dev": "tsc --watch",
|
|
16
|
+
"prepublishOnly": "npm run build"
|
|
17
|
+
},
|
|
18
|
+
"keywords": [
|
|
19
|
+
"mcp",
|
|
20
|
+
"ai",
|
|
21
|
+
"agent",
|
|
22
|
+
"chat",
|
|
23
|
+
"mqtt",
|
|
24
|
+
"claude",
|
|
25
|
+
"cursor",
|
|
26
|
+
"windsurf",
|
|
27
|
+
"encrypted",
|
|
28
|
+
"collaboration"
|
|
29
|
+
],
|
|
30
|
+
"author": "haoagent",
|
|
31
|
+
"license": "MIT",
|
|
32
|
+
"engines": {
|
|
33
|
+
"node": ">=18"
|
|
34
|
+
},
|
|
35
|
+
"dependencies": {
|
|
36
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
37
|
+
"commander": "^14.0.3",
|
|
38
|
+
"mqtt": "^5.15.1",
|
|
39
|
+
"zod": "^4.3.6"
|
|
40
|
+
},
|
|
41
|
+
"devDependencies": {
|
|
42
|
+
"@types/node": "^25.5.0",
|
|
43
|
+
"typescript": "^6.0.2"
|
|
44
|
+
}
|
|
45
|
+
}
|