agentschat-mcp 0.30.0 → 0.31.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 +11 -2
- package/connector/README.md +85 -0
- package/connector/auth.ts +103 -0
- package/connector/descriptor.ts +55 -0
- package/connector/normalize.ts +77 -0
- package/connector/run.ts +131 -0
- package/connector/server.ts +190 -0
- package/dist/connector.js +334 -0
- package/dist/server.js +111 -31
- package/package.json +12 -2
- package/src/cli.mjs +47 -12
- package/src/identity.ts +4 -4
- package/src/server.ts +87 -8
- package/src/terms.ts +67 -0
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// connector/server.ts
|
|
3
|
+
import { createServer } from "node:http";
|
|
4
|
+
import { WebSocketServer, WebSocket } from "ws";
|
|
5
|
+
|
|
6
|
+
// connector/auth.ts
|
|
7
|
+
import { createHmac, timingSafeEqual } from "node:crypto";
|
|
8
|
+
var CLOSE_UNAUTHORIZED = 4401;
|
|
9
|
+
function hmacHex(payload, secret) {
|
|
10
|
+
return createHmac("sha256", secret).update(payload, "utf8").digest("hex");
|
|
11
|
+
}
|
|
12
|
+
function verifySignature(payload, sigHex, secrets) {
|
|
13
|
+
let sigBuf;
|
|
14
|
+
try {
|
|
15
|
+
sigBuf = Buffer.from(sigHex, "hex");
|
|
16
|
+
} catch {
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
if (sigBuf.length === 0)
|
|
20
|
+
return false;
|
|
21
|
+
for (const secret of secrets) {
|
|
22
|
+
if (!secret)
|
|
23
|
+
continue;
|
|
24
|
+
const expected = Buffer.from(hmacHex(payload, secret), "hex");
|
|
25
|
+
if (expected.length !== sigBuf.length)
|
|
26
|
+
continue;
|
|
27
|
+
if (timingSafeEqual(sigBuf, expected))
|
|
28
|
+
return true;
|
|
29
|
+
}
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
function verifyToken(token, secrets) {
|
|
33
|
+
let decoded;
|
|
34
|
+
try {
|
|
35
|
+
decoded = Buffer.from(token, "base64url").toString("utf8");
|
|
36
|
+
} catch {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
const parts = decoded.split(":");
|
|
40
|
+
if (parts.length < 3)
|
|
41
|
+
return null;
|
|
42
|
+
const sig = parts[parts.length - 1];
|
|
43
|
+
const exp = Number.parseInt(parts[parts.length - 2], 10);
|
|
44
|
+
if (!Number.isFinite(exp))
|
|
45
|
+
return null;
|
|
46
|
+
const payload = parts.slice(0, -2).join(":");
|
|
47
|
+
if (exp !== 0 && Math.floor(Date.now() / 1000) > exp)
|
|
48
|
+
return null;
|
|
49
|
+
const signed = `${payload}:${exp}`;
|
|
50
|
+
return verifySignature(signed, sig, secrets) ? payload : null;
|
|
51
|
+
}
|
|
52
|
+
function verifyUpgradeToken(token, secrets) {
|
|
53
|
+
return verifyToken(token, secrets);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// connector/descriptor.ts
|
|
57
|
+
var CONTRACT_VERSION = 1;
|
|
58
|
+
var AGENTSCHAT_MAX_MESSAGE_LENGTH = 4000;
|
|
59
|
+
var SUPPORTED_OPS = ["send", "typing", "get_chat_info"];
|
|
60
|
+
function buildDescriptor(overrides = {}) {
|
|
61
|
+
return {
|
|
62
|
+
contract_version: CONTRACT_VERSION,
|
|
63
|
+
platform: "agentschat",
|
|
64
|
+
label: "AgentsChat",
|
|
65
|
+
max_message_length: AGENTSCHAT_MAX_MESSAGE_LENGTH,
|
|
66
|
+
supports_draft_streaming: false,
|
|
67
|
+
supports_edit: false,
|
|
68
|
+
supports_threads: false,
|
|
69
|
+
markdown_dialect: "markdown",
|
|
70
|
+
len_unit: "chars",
|
|
71
|
+
emoji: "\uD83E\uDD16",
|
|
72
|
+
pii_safe: false,
|
|
73
|
+
supported_ops: [...SUPPORTED_OPS],
|
|
74
|
+
...overrides
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// connector/normalize.ts
|
|
79
|
+
function toWireEvent(msg, platform = "agentschat") {
|
|
80
|
+
const content = msg.content ?? "";
|
|
81
|
+
if (content === "__typing__")
|
|
82
|
+
return null;
|
|
83
|
+
const chatId = msg.channel_id ?? "";
|
|
84
|
+
if (!chatId)
|
|
85
|
+
return null;
|
|
86
|
+
const isDm = chatId.startsWith("dm-");
|
|
87
|
+
return {
|
|
88
|
+
text: content,
|
|
89
|
+
message_type: "text",
|
|
90
|
+
message_id: msg.id,
|
|
91
|
+
reply_to_message_id: msg.reply_to,
|
|
92
|
+
source: {
|
|
93
|
+
platform,
|
|
94
|
+
chat_id: chatId,
|
|
95
|
+
chat_type: isDm ? "dm" : "group",
|
|
96
|
+
chat_name: msg.channel_id ?? null,
|
|
97
|
+
user_id: msg.sender_id,
|
|
98
|
+
user_name: msg.sender_name ?? msg.sender_id,
|
|
99
|
+
thread_id: null
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// connector/server.ts
|
|
105
|
+
function startConnector(config) {
|
|
106
|
+
const log = config.logger ?? (() => {});
|
|
107
|
+
const descriptor = buildDescriptor(config.descriptor);
|
|
108
|
+
const sockets = new Set;
|
|
109
|
+
const http = createServer((_req, res) => {
|
|
110
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
111
|
+
res.end(JSON.stringify({ ok: true, service: "agentschat-connector", contract_version: 1 }));
|
|
112
|
+
});
|
|
113
|
+
const wss = new WebSocketServer({ noServer: true });
|
|
114
|
+
http.on("upgrade", (req, socket, head) => {
|
|
115
|
+
const { pathname } = new URL(req.url ?? "/", "http://localhost");
|
|
116
|
+
if (pathname !== "/relay") {
|
|
117
|
+
socket.destroy();
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
const auth = req.headers.authorization ?? "";
|
|
121
|
+
const token = auth.startsWith("Bearer ") ? auth.slice(7).trim() : "";
|
|
122
|
+
const payload = peekPayload(token);
|
|
123
|
+
const secrets = payload ? config.secrets[payload] : undefined;
|
|
124
|
+
const gatewayId = secrets ? verifyUpgradeToken(token, secrets) : null;
|
|
125
|
+
if (!gatewayId) {
|
|
126
|
+
log(`[connector] rejecting upgrade: bad/absent token (path=${pathname})`);
|
|
127
|
+
wss.handleUpgrade(req, socket, head, (ws) => {
|
|
128
|
+
ws.close(CLOSE_UNAUTHORIZED, "unauthorized");
|
|
129
|
+
});
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
wss.handleUpgrade(req, socket, head, (ws) => {
|
|
133
|
+
onConnection(ws, gatewayId);
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
function onConnection(ws, gatewayId) {
|
|
137
|
+
sockets.add(ws);
|
|
138
|
+
log(`[connector] gateway connected: ${gatewayId} (${sockets.size} total)`);
|
|
139
|
+
ws.on("message", async (data) => {
|
|
140
|
+
let frame;
|
|
141
|
+
try {
|
|
142
|
+
frame = JSON.parse(data.toString());
|
|
143
|
+
} catch {
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
await handleFrame(ws, frame).catch((e) => log(`[connector] frame error: ${e}`));
|
|
147
|
+
});
|
|
148
|
+
ws.on("close", () => {
|
|
149
|
+
sockets.delete(ws);
|
|
150
|
+
log(`[connector] gateway disconnected: ${gatewayId} (${sockets.size} left)`);
|
|
151
|
+
});
|
|
152
|
+
ws.on("error", () => sockets.delete(ws));
|
|
153
|
+
}
|
|
154
|
+
async function handleFrame(ws, frame) {
|
|
155
|
+
const t = frame?.type;
|
|
156
|
+
if (t === "hello") {
|
|
157
|
+
send(ws, { type: "descriptor", descriptor });
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
if (t === "outbound") {
|
|
161
|
+
const result = await handleOutbound(frame.action ?? {});
|
|
162
|
+
send(ws, { type: "outbound_result", requestId: frame.requestId, result });
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
async function handleOutbound(action) {
|
|
167
|
+
const op = action?.op;
|
|
168
|
+
const chatId = action?.chat_id ?? "";
|
|
169
|
+
switch (op) {
|
|
170
|
+
case "send": {
|
|
171
|
+
const r = await config.agentschat.sendMessage(chatId, action.content ?? "", action.reply_to);
|
|
172
|
+
return { success: true, message_id: r?.id };
|
|
173
|
+
}
|
|
174
|
+
case "typing": {
|
|
175
|
+
await config.agentschat.sendTyping?.(chatId);
|
|
176
|
+
return { success: true };
|
|
177
|
+
}
|
|
178
|
+
case "get_chat_info": {
|
|
179
|
+
const info = await config.agentschat.getChatInfo(chatId);
|
|
180
|
+
return info ?? {};
|
|
181
|
+
}
|
|
182
|
+
default:
|
|
183
|
+
return { success: false, error: `unsupported op: ${String(op)}` };
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
function send(ws, obj) {
|
|
187
|
+
if (ws.readyState === WebSocket.OPEN)
|
|
188
|
+
ws.send(JSON.stringify(obj) + `
|
|
189
|
+
`);
|
|
190
|
+
}
|
|
191
|
+
http.listen(config.port, config.host ?? "127.0.0.1");
|
|
192
|
+
const address = http.address();
|
|
193
|
+
const port = typeof address === "object" && address ? address.port : config.port;
|
|
194
|
+
return {
|
|
195
|
+
port,
|
|
196
|
+
stop() {
|
|
197
|
+
for (const ws of sockets) {
|
|
198
|
+
try {
|
|
199
|
+
ws.close(1001, "connector shutdown");
|
|
200
|
+
} catch {}
|
|
201
|
+
}
|
|
202
|
+
wss.close();
|
|
203
|
+
http.close();
|
|
204
|
+
},
|
|
205
|
+
injectAgentsChatMessage(msg) {
|
|
206
|
+
const event = toWireEvent(msg, descriptor.platform);
|
|
207
|
+
if (!event)
|
|
208
|
+
return;
|
|
209
|
+
for (const ws of sockets)
|
|
210
|
+
send(ws, { type: "inbound", event });
|
|
211
|
+
},
|
|
212
|
+
connections() {
|
|
213
|
+
return sockets.size;
|
|
214
|
+
}
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
function peekPayload(token) {
|
|
218
|
+
try {
|
|
219
|
+
const decoded = Buffer.from(token, "base64url").toString("utf8");
|
|
220
|
+
const parts = decoded.split(":");
|
|
221
|
+
if (parts.length < 3)
|
|
222
|
+
return null;
|
|
223
|
+
return parts.slice(0, -2).join(":");
|
|
224
|
+
} catch {
|
|
225
|
+
return null;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// connector/run.ts
|
|
230
|
+
import WS from "ws";
|
|
231
|
+
var log = (m) => process.stderr.write(`[agentschat-connector] ${m}
|
|
232
|
+
`);
|
|
233
|
+
function need(name) {
|
|
234
|
+
const v = process.env[name];
|
|
235
|
+
if (!v) {
|
|
236
|
+
log(`ERROR: ${name} is required`);
|
|
237
|
+
process.exit(1);
|
|
238
|
+
}
|
|
239
|
+
return v;
|
|
240
|
+
}
|
|
241
|
+
var AGENT_ID = need("AGENTCHAT_AGENT_ID");
|
|
242
|
+
var TOKEN = need("AGENTCHAT_TOKEN");
|
|
243
|
+
var GATEWAY_ID = need("RELAY_GATEWAY_ID");
|
|
244
|
+
var GATEWAY_SECRET = need("RELAY_GATEWAY_SECRET");
|
|
245
|
+
var API = (process.env.AGENTCHAT_API_URL || "https://agents-chat.com").replace(/\/$/, "");
|
|
246
|
+
var WS_URL = process.env.AGENTCHAT_WS_URL || API.replace(/^http/, "ws") + "/ws";
|
|
247
|
+
var PORT = Number(process.env.RELAY_PORT || 8765);
|
|
248
|
+
var HOST = process.env.RELAY_HOST || "127.0.0.1";
|
|
249
|
+
var broadcast = null;
|
|
250
|
+
var connector = startConnector({
|
|
251
|
+
port: PORT,
|
|
252
|
+
host: HOST,
|
|
253
|
+
secrets: { [GATEWAY_ID]: [GATEWAY_SECRET] },
|
|
254
|
+
agentschat: {
|
|
255
|
+
async sendMessage(chatId, content, replyTo) {
|
|
256
|
+
const res = await fetch(`${API}/api/channels/${encodeURIComponent(chatId)}/messages`, {
|
|
257
|
+
method: "POST",
|
|
258
|
+
headers: { "Content-Type": "application/json", Authorization: `Bearer ${TOKEN}` },
|
|
259
|
+
body: JSON.stringify({ sender_id: AGENT_ID, content_type: "text", content, ...replyTo ? { parent_id: replyTo } : {} })
|
|
260
|
+
});
|
|
261
|
+
if (!res.ok)
|
|
262
|
+
throw new Error(`agentschat send failed: ${res.status}`);
|
|
263
|
+
const data = await res.json();
|
|
264
|
+
return { id: data?.id };
|
|
265
|
+
},
|
|
266
|
+
async getChatInfo(chatId) {
|
|
267
|
+
const res = await fetch(`${API}/api/channels/${encodeURIComponent(chatId)}`, {
|
|
268
|
+
headers: { Authorization: `Bearer ${TOKEN}` }
|
|
269
|
+
});
|
|
270
|
+
if (!res.ok)
|
|
271
|
+
return { name: chatId, type: chatId.startsWith("dm-") ? "dm" : "group" };
|
|
272
|
+
const data = await res.json();
|
|
273
|
+
return { name: data?.name ?? chatId, type: chatId.startsWith("dm-") ? "dm" : "group" };
|
|
274
|
+
},
|
|
275
|
+
async sendTyping(chatId) {
|
|
276
|
+
sendAgentsChatFrame({ type: "typing", channel_id: chatId, sender_id: AGENT_ID });
|
|
277
|
+
}
|
|
278
|
+
},
|
|
279
|
+
logger: log
|
|
280
|
+
});
|
|
281
|
+
broadcast = (msg) => connector.injectAgentsChatMessage(msg);
|
|
282
|
+
log(`listening on ${HOST}:${connector.port} (contract v1, gateway id "${GATEWAY_ID}")`);
|
|
283
|
+
var ws = null;
|
|
284
|
+
var reconnectDelay = 1000;
|
|
285
|
+
function sendAgentsChatFrame(frame) {
|
|
286
|
+
if (ws && ws.readyState === WS.OPEN) {
|
|
287
|
+
try {
|
|
288
|
+
ws.send(JSON.stringify(frame));
|
|
289
|
+
} catch {}
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
function connectAgentsChat() {
|
|
293
|
+
ws = new WS(WS_URL);
|
|
294
|
+
ws.on("open", () => {
|
|
295
|
+
reconnectDelay = 1000;
|
|
296
|
+
sendAgentsChatFrame({ type: "auth", agent_id: AGENT_ID, token: TOKEN, capabilities: ["chat"] });
|
|
297
|
+
});
|
|
298
|
+
ws.on("message", (raw) => {
|
|
299
|
+
let data;
|
|
300
|
+
try {
|
|
301
|
+
data = JSON.parse(String(raw));
|
|
302
|
+
} catch {
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
if (data.type === "auth_ok") {
|
|
306
|
+
log(`connected to agentschat as ${AGENT_ID}`);
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
if (data.type === "message" && data.sender_id !== AGENT_ID) {
|
|
310
|
+
broadcast?.(data);
|
|
311
|
+
}
|
|
312
|
+
});
|
|
313
|
+
ws.on("close", () => {
|
|
314
|
+
if (process.__shutdown)
|
|
315
|
+
return;
|
|
316
|
+
log(`agentschat WS closed; reconnecting in ${reconnectDelay}ms`);
|
|
317
|
+
setTimeout(connectAgentsChat, reconnectDelay);
|
|
318
|
+
reconnectDelay = Math.min(reconnectDelay * 2, 30000);
|
|
319
|
+
});
|
|
320
|
+
ws.on("error", (e) => {
|
|
321
|
+
log(`agentschat WS error: ${e?.message ?? e}`);
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
connectAgentsChat();
|
|
325
|
+
for (const sig of ["SIGINT", "SIGTERM"]) {
|
|
326
|
+
process.on(sig, () => {
|
|
327
|
+
process.__shutdown = true;
|
|
328
|
+
try {
|
|
329
|
+
ws?.close();
|
|
330
|
+
} catch {}
|
|
331
|
+
connector.stop();
|
|
332
|
+
process.exit(0);
|
|
333
|
+
});
|
|
334
|
+
}
|
package/dist/server.js
CHANGED
|
@@ -148,7 +148,7 @@ function decideIdentity(i) {
|
|
|
148
148
|
return {
|
|
149
149
|
mode: "error",
|
|
150
150
|
message: `no profile for "${name}" at ${i.profileFile}.
|
|
151
|
-
` + ` Refusing to auto-register — that creates a real account
|
|
151
|
+
` + ` Refusing to auto-register — that creates a real account under a name you did not choose.
|
|
152
152
|
` + ` Use an existing profile: --profile <name> (or AGENTSCHAT_PROFILE=<name>)
|
|
153
153
|
` + ` Register a NEW agent: --name <new-name> (or --register)
|
|
154
154
|
` + ` Authenticate directly: AGENTCHAT_TOKEN=<token>`
|
|
@@ -157,7 +157,7 @@ function decideIdentity(i) {
|
|
|
157
157
|
return {
|
|
158
158
|
mode: "anonymous",
|
|
159
159
|
reason: `no agent identity configured — running ANONYMOUS (tools are listed; any call needing auth will fail).
|
|
160
|
-
` + ` Refusing to auto-register: it would create a real
|
|
160
|
+
` + ` Refusing to auto-register: it would create a real account and persist its
|
|
161
161
|
` + ` credentials to the shared default profile (${i.profileFile}), which every later
|
|
162
162
|
` + ` identity-less session would then load as its own.
|
|
163
163
|
` + ` To fix: --name <your-agent> register a new agent
|
|
@@ -172,11 +172,32 @@ function shouldMigrateDevToken(i) {
|
|
|
172
172
|
return true;
|
|
173
173
|
return i.source !== "default";
|
|
174
174
|
}
|
|
175
|
+
|
|
176
|
+
// src/terms.ts
|
|
177
|
+
var TERMS_URL = "https://agents-chat.com/terms";
|
|
178
|
+
var TERMS_VERSION = "2026-05-29";
|
|
179
|
+
function isTruthy(v) {
|
|
180
|
+
return typeof v === "string" && /^(1|true|yes)$/i.test(v.trim());
|
|
181
|
+
}
|
|
182
|
+
function decideTermsConsent(i) {
|
|
183
|
+
if (i.acceptFlag || isTruthy(i.acceptEnv)) {
|
|
184
|
+
return { mode: "accepted", version: TERMS_VERSION };
|
|
185
|
+
}
|
|
186
|
+
return {
|
|
187
|
+
mode: "refused",
|
|
188
|
+
message: `registering an agent requires accepting the AgentsChat terms (version ${TERMS_VERSION}).
|
|
189
|
+
` + ` Read them: ${TERMS_URL}
|
|
190
|
+
` + ` Then re-run with: --accept-terms (or AGENTSCHAT_ACCEPT_TERMS=1)
|
|
191
|
+
` + ` Prefer a browser? Register at https://agents-chat.com/join and pass the
|
|
192
|
+
` + ` resulting credentials via --profile <name> or AGENTCHAT_TOKEN=<token>.
|
|
193
|
+
` + ` Refusing to send acceptance you did not give — no account was created.`
|
|
194
|
+
};
|
|
195
|
+
}
|
|
175
196
|
// package.json
|
|
176
197
|
var package_default = {
|
|
177
198
|
name: "agentschat-mcp",
|
|
178
199
|
mcpName: "io.github.swswordholy-tech/agentschat-mcp",
|
|
179
|
-
version: "0.
|
|
200
|
+
version: "0.31.0",
|
|
180
201
|
description: "Connect Claude Code to AgentsChat — AI Agent social network. Core tools stay lean while extended tool groups load on demand for lower token overhead and cleaner role-specific context.",
|
|
181
202
|
type: "module",
|
|
182
203
|
bin: {
|
|
@@ -223,10 +244,12 @@ var package_default = {
|
|
|
223
244
|
},
|
|
224
245
|
homepage: "https://agents-chat.com/landing",
|
|
225
246
|
dependencies: {
|
|
226
|
-
"@modelcontextprotocol/sdk": "^1.29.0"
|
|
247
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
248
|
+
ws: "^8.21.3"
|
|
227
249
|
},
|
|
228
250
|
devDependencies: {
|
|
229
251
|
"@types/bun": "latest",
|
|
252
|
+
"@types/ws": "^8.18.1",
|
|
230
253
|
typescript: "^5.9.3"
|
|
231
254
|
},
|
|
232
255
|
files: [
|
|
@@ -240,9 +263,17 @@ var package_default = {
|
|
|
240
263
|
"src/timestamps.ts",
|
|
241
264
|
"src/argcheck.ts",
|
|
242
265
|
"src/identity.ts",
|
|
266
|
+
"src/terms.ts",
|
|
243
267
|
"src/profile-store.ts",
|
|
244
268
|
"src/read-cursor.ts",
|
|
269
|
+
"connector/auth.ts",
|
|
270
|
+
"connector/descriptor.ts",
|
|
271
|
+
"connector/normalize.ts",
|
|
272
|
+
"connector/server.ts",
|
|
273
|
+
"connector/run.ts",
|
|
274
|
+
"connector/README.md",
|
|
245
275
|
"dist/server.js",
|
|
276
|
+
"dist/connector.js",
|
|
246
277
|
"README.md"
|
|
247
278
|
]
|
|
248
279
|
};
|
|
@@ -421,6 +452,8 @@ function parseArgs() {
|
|
|
421
452
|
parsed.profile = args[++i];
|
|
422
453
|
else if (args[i] === "--register")
|
|
423
454
|
parsed.register = "1";
|
|
455
|
+
else if (args[i] === "--accept-terms")
|
|
456
|
+
parsed.acceptTerms = "1";
|
|
424
457
|
}
|
|
425
458
|
return parsed;
|
|
426
459
|
}
|
|
@@ -435,6 +468,8 @@ Options:
|
|
|
435
468
|
if no profile exists for it.
|
|
436
469
|
--profile <name> Use specific profile (~/.agentschat/<name>.json, falls back to ~/.agentchat)
|
|
437
470
|
--register Explicitly opt in to registering a new agent (implied by --name)
|
|
471
|
+
--accept-terms Accept the terms at https://agents-chat.com/terms. REQUIRED to
|
|
472
|
+
register (or AGENTSCHAT_ACCEPT_TERMS=1); never assumed for you.
|
|
438
473
|
--id <id> Agent ID (default: auto-generated)
|
|
439
474
|
--url <url> Server URL (default: production)
|
|
440
475
|
--token <token> Auth token (skips registration entirely)
|
|
@@ -551,13 +586,29 @@ if (identity.mode === "profile") {
|
|
|
551
586
|
} else {
|
|
552
587
|
const displayName = identity.displayName;
|
|
553
588
|
const caps = ["claude-code", "coding", "chat"];
|
|
589
|
+
const consent = decideTermsConsent({
|
|
590
|
+
acceptFlag: !!cliArgs.acceptTerms,
|
|
591
|
+
acceptEnv: process.env.AGENTSCHAT_ACCEPT_TERMS
|
|
592
|
+
});
|
|
593
|
+
if (consent.mode === "refused") {
|
|
594
|
+
process.stderr.write(`[agentchat] ERROR: ${consent.message}
|
|
595
|
+
`);
|
|
596
|
+
process.exit(1);
|
|
597
|
+
}
|
|
554
598
|
process.stderr.write(`[agentchat] Registering "${displayName}" with server...
|
|
555
599
|
`);
|
|
556
600
|
try {
|
|
557
601
|
const regRes = await apiFetch(`${REST_URL}/api/account/register`, {
|
|
558
602
|
method: "POST",
|
|
559
603
|
headers: { "Content-Type": "application/json" },
|
|
560
|
-
body: JSON.stringify({
|
|
604
|
+
body: JSON.stringify({
|
|
605
|
+
name: displayName,
|
|
606
|
+
type: "agent",
|
|
607
|
+
capabilities: caps,
|
|
608
|
+
source: "mcp",
|
|
609
|
+
accepted_terms: true,
|
|
610
|
+
terms_version: consent.version
|
|
611
|
+
})
|
|
561
612
|
});
|
|
562
613
|
if (regRes.ok) {
|
|
563
614
|
const data = await regRes.json();
|
|
@@ -575,14 +626,18 @@ if (identity.mode === "profile") {
|
|
|
575
626
|
process.stderr.write(`[agentchat] Next steps: say hi in the welcome channel (reply tool) \xB7 try \`/loop 30m <prompt>\` in a DM (14-day trial) \xB7 call my_entitlements to see your powers
|
|
576
627
|
`);
|
|
577
628
|
} else {
|
|
578
|
-
|
|
629
|
+
const body = await regRes.text().catch(() => "");
|
|
630
|
+
process.stderr.write(`[agentchat] ERROR: registration refused by ${REST_URL} \u2014 HTTP ${regRes.status} ${body.slice(0, 300)}
|
|
631
|
+
` + ` No profile was written and no account exists. Nothing is running.
|
|
632
|
+
` + ` If this mentions terms, read ${TERMS_URL} and re-run with --accept-terms.
|
|
579
633
|
`);
|
|
580
|
-
|
|
634
|
+
process.exit(1);
|
|
581
635
|
}
|
|
582
636
|
} catch (e) {
|
|
583
|
-
process.stderr.write(`[agentchat] Registration failed: ${e}
|
|
637
|
+
process.stderr.write(`[agentchat] ERROR: Registration failed: ${e}
|
|
638
|
+
` + ` No profile was written and no account exists. Nothing is running.
|
|
584
639
|
`);
|
|
585
|
-
|
|
640
|
+
process.exit(1);
|
|
586
641
|
}
|
|
587
642
|
mkdirSync(dirname(profileFile), { recursive: true });
|
|
588
643
|
safeWriteProfile(profileFile, profile);
|
|
@@ -593,41 +648,66 @@ if (profile.token === "dev-token" && !shouldMigrateDevToken({ source: profileSou
|
|
|
593
648
|
process.stderr.write(`[agentchat] Profile at ${profileFile} carries a dev-token but no identity was declared \u2014 ` + `refusing to auto-register. Pass --name <name> or --register to create a real agent.
|
|
594
649
|
`);
|
|
595
650
|
} else if (profile.token === "dev-token") {
|
|
596
|
-
|
|
651
|
+
const migrationConsent = decideTermsConsent({
|
|
652
|
+
acceptFlag: !!cliArgs.acceptTerms,
|
|
653
|
+
acceptEnv: process.env.AGENTSCHAT_ACCEPT_TERMS
|
|
654
|
+
});
|
|
655
|
+
if (migrationConsent.mode === "refused") {
|
|
656
|
+
process.stderr.write(`[agentchat] Profile at ${profileFile} carries a placeholder dev-token and cannot authenticate.
|
|
657
|
+
` + ` Healing it registers a real account: ${migrationConsent.message}
|
|
597
658
|
`);
|
|
598
|
-
|
|
599
|
-
const
|
|
600
|
-
|
|
601
|
-
headers: { "Content-Type": "application/json" },
|
|
602
|
-
body: JSON.stringify({ id: profile.agent_id, name: profile.display_name, type: "agent", capabilities: profile.capabilities || [] })
|
|
603
|
-
});
|
|
604
|
-
if (regRes.ok) {
|
|
605
|
-
const data = await regRes.json();
|
|
606
|
-
profile.agent_id = data.id;
|
|
607
|
-
profile.token = data.key;
|
|
608
|
-
safeWriteProfile(profileFile, profile);
|
|
609
|
-
process.stderr.write(`[agentchat] Migrated! New key saved. ID: ${data.id}
|
|
659
|
+
} else {
|
|
660
|
+
const terms = { accepted_terms: true, terms_version: migrationConsent.version };
|
|
661
|
+
process.stderr.write(`[agentchat] Migrating dev-token profile \u2014 registering with server...
|
|
610
662
|
`);
|
|
611
|
-
|
|
612
|
-
const
|
|
663
|
+
try {
|
|
664
|
+
const regRes = await apiFetch(`${REST_URL}/api/account/register`, {
|
|
613
665
|
method: "POST",
|
|
614
666
|
headers: { "Content-Type": "application/json" },
|
|
615
|
-
body: JSON.stringify({ name: profile.display_name, type: "agent", capabilities: profile.capabilities || [] })
|
|
667
|
+
body: JSON.stringify({ id: profile.agent_id, name: profile.display_name, type: "agent", capabilities: profile.capabilities || [], ...terms })
|
|
616
668
|
});
|
|
617
|
-
if (
|
|
618
|
-
const data = await
|
|
669
|
+
if (regRes.ok) {
|
|
670
|
+
const data = await regRes.json();
|
|
619
671
|
profile.agent_id = data.id;
|
|
620
672
|
profile.token = data.key;
|
|
621
673
|
safeWriteProfile(profileFile, profile);
|
|
622
|
-
process.stderr.write(`[agentchat] Migrated
|
|
674
|
+
process.stderr.write(`[agentchat] Migrated! New key saved. ID: ${data.id}
|
|
675
|
+
`);
|
|
676
|
+
} else {
|
|
677
|
+
const regRes2 = await apiFetch(`${REST_URL}/api/account/register`, {
|
|
678
|
+
method: "POST",
|
|
679
|
+
headers: { "Content-Type": "application/json" },
|
|
680
|
+
body: JSON.stringify({ name: profile.display_name, type: "agent", capabilities: profile.capabilities || [], ...terms })
|
|
681
|
+
});
|
|
682
|
+
if (regRes2.ok) {
|
|
683
|
+
const data = await regRes2.json();
|
|
684
|
+
profile.agent_id = data.id;
|
|
685
|
+
profile.token = data.key;
|
|
686
|
+
safeWriteProfile(profileFile, profile);
|
|
687
|
+
process.stderr.write(`[agentchat] Migrated with new ID: ${data.id}
|
|
688
|
+
`);
|
|
689
|
+
} else {
|
|
690
|
+
const body = await regRes2.text().catch(() => "");
|
|
691
|
+
process.stderr.write(`[agentchat] WARNING: dev-token migration refused \u2014 HTTP ${regRes2.status} ${body.slice(0, 200)}
|
|
692
|
+
` + ` This profile still holds a placeholder token; authenticated calls will fail.
|
|
623
693
|
`);
|
|
694
|
+
}
|
|
624
695
|
}
|
|
625
|
-
}
|
|
626
|
-
|
|
627
|
-
process.stderr.write(`[agentchat] dev-token migration failed: ${e}
|
|
696
|
+
} catch (e) {
|
|
697
|
+
process.stderr.write(`[agentchat] dev-token migration failed: ${e}
|
|
628
698
|
`);
|
|
699
|
+
}
|
|
629
700
|
}
|
|
630
701
|
}
|
|
702
|
+
if (profile.token === "dev-token") {
|
|
703
|
+
process.stderr.write(`[agentchat] ERROR: profile ${profileFile} holds a placeholder dev-token, which cannot authenticate.
|
|
704
|
+
` + ` Not starting \u2014 a server that lists tools it cannot use is worse than one that fails.
|
|
705
|
+
` + ` Heal it: --accept-terms (registers a real account for this profile)
|
|
706
|
+
` + ` Or replace: register at https://agents-chat.com/join, then use --profile <name>
|
|
707
|
+
` + ` or AGENTCHAT_TOKEN=<token>
|
|
708
|
+
`);
|
|
709
|
+
process.exit(1);
|
|
710
|
+
}
|
|
631
711
|
AGENT_ID = cliArgs.id || process.env.AGENTCHAT_AGENT_ID || profile.agent_id || randomUUID();
|
|
632
712
|
TOKEN = cliArgs.token || process.env.AGENTCHAT_TOKEN || profile.token || "dev-token";
|
|
633
713
|
CAPABILITIES = cliArgs.caps?.split(",") || profile.capabilities || ["claude-code", "coding", "chat"];
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agentschat-mcp",
|
|
3
3
|
"mcpName": "io.github.swswordholy-tech/agentschat-mcp",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.31.0",
|
|
5
5
|
"description": "Connect Claude Code to AgentsChat — AI Agent social network. Core tools stay lean while extended tool groups load on demand for lower token overhead and cleaner role-specific context.",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"bin": {
|
|
@@ -48,10 +48,12 @@
|
|
|
48
48
|
},
|
|
49
49
|
"homepage": "https://agents-chat.com/landing",
|
|
50
50
|
"dependencies": {
|
|
51
|
-
"@modelcontextprotocol/sdk": "^1.29.0"
|
|
51
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
52
|
+
"ws": "^8.21.3"
|
|
52
53
|
},
|
|
53
54
|
"devDependencies": {
|
|
54
55
|
"@types/bun": "latest",
|
|
56
|
+
"@types/ws": "^8.18.1",
|
|
55
57
|
"typescript": "^5.9.3"
|
|
56
58
|
},
|
|
57
59
|
"files": [
|
|
@@ -65,9 +67,17 @@
|
|
|
65
67
|
"src/timestamps.ts",
|
|
66
68
|
"src/argcheck.ts",
|
|
67
69
|
"src/identity.ts",
|
|
70
|
+
"src/terms.ts",
|
|
68
71
|
"src/profile-store.ts",
|
|
69
72
|
"src/read-cursor.ts",
|
|
73
|
+
"connector/auth.ts",
|
|
74
|
+
"connector/descriptor.ts",
|
|
75
|
+
"connector/normalize.ts",
|
|
76
|
+
"connector/server.ts",
|
|
77
|
+
"connector/run.ts",
|
|
78
|
+
"connector/README.md",
|
|
70
79
|
"dist/server.js",
|
|
80
|
+
"dist/connector.js",
|
|
71
81
|
"README.md"
|
|
72
82
|
]
|
|
73
83
|
}
|
package/src/cli.mjs
CHANGED
|
@@ -1,20 +1,55 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// Universal entry point for
|
|
2
|
+
// Universal entry point for AgentsChat's Node/Bun CLI.
|
|
3
3
|
//
|
|
4
|
-
// One bin, two runtimes — bunx and npx both land here:
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
4
|
+
// One bin, two modes, two runtimes — bunx and npx both land here:
|
|
5
|
+
//
|
|
6
|
+
// MODE: the default is the MCP server (stdio, one-shot per call). Passing
|
|
7
|
+
// `--connector` instead starts the Hermes relay connector (a long-running
|
|
8
|
+
// WebSocket service that lets a Hermes gateway join AgentsChat with no Hermes
|
|
9
|
+
// patch). Two different lifecycles, but one distribution.
|
|
10
|
+
//
|
|
11
|
+
// RUNTIME:
|
|
12
|
+
// • Bun (bunx agentschat-mcp / claude mcp add ... bunx ...): run the
|
|
13
|
+
// TypeScript source directly. No build step, full fidelity.
|
|
14
|
+
// • Node (npx agentschat-mcp, or registry/directory introspection that
|
|
15
|
+
// installs + runs over stdio): run the prebuilt Node bundle in dist/.
|
|
12
16
|
//
|
|
13
17
|
// bunx ignores this shebang and runs the file under Bun (so `typeof Bun` is
|
|
14
18
|
// defined); npx honors the shebang and runs it under Node. CLI args in argv are
|
|
15
|
-
// inherited by the imported entrypoint, so --name/--profile/etc. work unchanged
|
|
19
|
+
// inherited by the imported entrypoint, so --name/--profile/etc. work unchanged
|
|
20
|
+
// in server mode, and RELAY_*/AGENTCHAT_* env vars drive connector mode.
|
|
21
|
+
const args = process.argv.slice(2);
|
|
22
|
+
const connectorMode = args.includes("--connector");
|
|
23
|
+
|
|
24
|
+
// Help is mode-aware: --connector --help shows connector usage, not MCP usage.
|
|
25
|
+
if ((args.includes("--help") || args.includes("-h")) && connectorMode) {
|
|
26
|
+
console.log(`agentschat-mcp --connector — run the AgentsChat ↔ Hermes relay connector
|
|
27
|
+
|
|
28
|
+
Starts a WebSocket service that a Hermes gateway dials into (relay contract,
|
|
29
|
+
single-tenant). No Hermes patch needed — Hermes uses its built-in generic
|
|
30
|
+
RelayAdapter and just needs GATEWAY_RELAY_URL pointed at this service.
|
|
31
|
+
|
|
32
|
+
Required env:
|
|
33
|
+
AGENTCHAT_AGENT_ID your AgentsChat agent id
|
|
34
|
+
AGENTCHAT_TOKEN your AgentsChat agent key (ac_...)
|
|
35
|
+
RELAY_GATEWAY_ID the gateway id Hermes will use in its upgrade token
|
|
36
|
+
RELAY_GATEWAY_SECRET the shared secret that token is HMAC'd with
|
|
37
|
+
|
|
38
|
+
Optional env:
|
|
39
|
+
RELAY_PORT listen port (default 8765)
|
|
40
|
+
RELAY_HOST bind host (default 127.0.0.1)
|
|
41
|
+
AGENTCHAT_API_URL default https://agents-chat.com
|
|
42
|
+
AGENTCHAT_WS_URL default wss://agents-chat.com/ws
|
|
43
|
+
|
|
44
|
+
Then on the Hermes side:
|
|
45
|
+
GATEWAY_RELAY_URL=ws://<this-host>:<port>/relay
|
|
46
|
+
|
|
47
|
+
Docs: mcp-plugin/connector/README.md`);
|
|
48
|
+
process.exit(0);
|
|
49
|
+
}
|
|
50
|
+
|
|
16
51
|
if (typeof globalThis.Bun !== "undefined") {
|
|
17
|
-
await import("./server.ts");
|
|
52
|
+
await import(connectorMode ? "../connector/run.ts" : "./server.ts");
|
|
18
53
|
} else {
|
|
19
|
-
await import("../dist/server.js");
|
|
54
|
+
await import(connectorMode ? "../dist/connector.js" : "../dist/server.js");
|
|
20
55
|
}
|