agentschat-mcp 0.30.0 → 0.32.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 +56 -2
- package/connector/README.md +110 -0
- package/connector/auth.ts +103 -0
- package/connector/descriptor.ts +55 -0
- package/connector/identities.ts +96 -0
- package/connector/normalize.ts +77 -0
- package/connector/run.ts +180 -0
- package/connector/server.ts +281 -0
- package/dist/connector.js +459 -0
- package/dist/server.js +314 -32
- package/package.json +14 -2
- package/src/cli.mjs +53 -12
- package/src/identity.ts +4 -4
- package/src/server.ts +147 -8
- package/src/terms.ts +67 -0
- package/src/wake.ts +302 -0
package/dist/server.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// @bun
|
|
3
|
+
import { createRequire } from "node:module";
|
|
4
|
+
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
3
5
|
|
|
4
6
|
// src/server.ts
|
|
5
7
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
@@ -148,7 +150,7 @@ function decideIdentity(i) {
|
|
|
148
150
|
return {
|
|
149
151
|
mode: "error",
|
|
150
152
|
message: `no profile for "${name}" at ${i.profileFile}.
|
|
151
|
-
` + ` Refusing to auto-register — that creates a real account
|
|
153
|
+
` + ` Refusing to auto-register — that creates a real account under a name you did not choose.
|
|
152
154
|
` + ` Use an existing profile: --profile <name> (or AGENTSCHAT_PROFILE=<name>)
|
|
153
155
|
` + ` Register a NEW agent: --name <new-name> (or --register)
|
|
154
156
|
` + ` Authenticate directly: AGENTCHAT_TOKEN=<token>`
|
|
@@ -157,7 +159,7 @@ function decideIdentity(i) {
|
|
|
157
159
|
return {
|
|
158
160
|
mode: "anonymous",
|
|
159
161
|
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
|
|
162
|
+
` + ` Refusing to auto-register: it would create a real account and persist its
|
|
161
163
|
` + ` credentials to the shared default profile (${i.profileFile}), which every later
|
|
162
164
|
` + ` identity-less session would then load as its own.
|
|
163
165
|
` + ` To fix: --name <your-agent> register a new agent
|
|
@@ -172,11 +174,181 @@ function shouldMigrateDevToken(i) {
|
|
|
172
174
|
return true;
|
|
173
175
|
return i.source !== "default";
|
|
174
176
|
}
|
|
177
|
+
|
|
178
|
+
// src/terms.ts
|
|
179
|
+
var TERMS_URL = "https://agents-chat.com/terms";
|
|
180
|
+
var TERMS_VERSION = "2026-05-29";
|
|
181
|
+
function isTruthy(v) {
|
|
182
|
+
return typeof v === "string" && /^(1|true|yes)$/i.test(v.trim());
|
|
183
|
+
}
|
|
184
|
+
function decideTermsConsent(i) {
|
|
185
|
+
if (i.acceptFlag || isTruthy(i.acceptEnv)) {
|
|
186
|
+
return { mode: "accepted", version: TERMS_VERSION };
|
|
187
|
+
}
|
|
188
|
+
return {
|
|
189
|
+
mode: "refused",
|
|
190
|
+
message: `registering an agent requires accepting the AgentsChat terms (version ${TERMS_VERSION}).
|
|
191
|
+
` + ` Read them: ${TERMS_URL}
|
|
192
|
+
` + ` Then re-run with: --accept-terms (or AGENTSCHAT_ACCEPT_TERMS=1)
|
|
193
|
+
` + ` Prefer a browser? Register at https://agents-chat.com/join and pass the
|
|
194
|
+
` + ` resulting credentials via --profile <name> or AGENTCHAT_TOKEN=<token>.
|
|
195
|
+
` + ` Refusing to send acceptance you did not give — no account was created.`
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// src/wake.ts
|
|
200
|
+
import { createHmac, timingSafeEqual } from "node:crypto";
|
|
201
|
+
|
|
202
|
+
// src/redact.ts
|
|
203
|
+
function redactSecrets2(text) {
|
|
204
|
+
return text.replace(/ac_[A-Za-z0-9_-]{16,}/g, "ac_***REDACTED***").replace(/eyJ[A-Za-z0-9_-]{20,}\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+/g, "***JWT_REDACTED***");
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// src/wake.ts
|
|
208
|
+
var WAKE_SIG_HEADER = "x-agentschat-signature";
|
|
209
|
+
var WAKE_CONTENT_MAX = 500;
|
|
210
|
+
function buildWakePayload(msg) {
|
|
211
|
+
const content = typeof msg.content === "string" ? redactSecrets2(msg.content.slice(0, WAKE_CONTENT_MAX)) : undefined;
|
|
212
|
+
return {
|
|
213
|
+
type: typeof msg.type === "string" ? msg.type : "message",
|
|
214
|
+
channel_id: msg.channel_id,
|
|
215
|
+
message_id: msg.id,
|
|
216
|
+
sender_id: msg.sender_id,
|
|
217
|
+
content,
|
|
218
|
+
mentioned_ids: Array.isArray(msg.mentioned_ids) ? msg.mentioned_ids.filter((x) => typeof x === "string") : undefined,
|
|
219
|
+
timestamp: typeof msg.timestamp === "string" ? msg.timestamp : undefined
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
function signWakeBody(body, secret) {
|
|
223
|
+
return createHmac("sha256", secret).update(body, "utf8").digest("hex");
|
|
224
|
+
}
|
|
225
|
+
async function fireWake(msg, cfg) {
|
|
226
|
+
const log = cfg.logger ?? (() => {});
|
|
227
|
+
const url = (cfg.url ?? "").trim();
|
|
228
|
+
if (!url)
|
|
229
|
+
return;
|
|
230
|
+
const payload = buildWakePayload(msg);
|
|
231
|
+
const body = JSON.stringify(payload);
|
|
232
|
+
const headers = { "Content-Type": "application/json" };
|
|
233
|
+
if (cfg.secret)
|
|
234
|
+
headers[WAKE_SIG_HEADER] = signWakeBody(body, cfg.secret);
|
|
235
|
+
const doPost = async () => {
|
|
236
|
+
const f = cfg.fetchImpl ?? fetch;
|
|
237
|
+
const ctrl = new AbortController;
|
|
238
|
+
const t = setTimeout(() => ctrl.abort(), cfg.timeoutMs ?? 5000);
|
|
239
|
+
try {
|
|
240
|
+
const res = await f(url, { method: "POST", headers, body, signal: ctrl.signal });
|
|
241
|
+
if (!res.ok)
|
|
242
|
+
log(`[agentchat] wake POST ${url} → HTTP ${res.status}`);
|
|
243
|
+
} finally {
|
|
244
|
+
clearTimeout(t);
|
|
245
|
+
}
|
|
246
|
+
};
|
|
247
|
+
try {
|
|
248
|
+
await doPost();
|
|
249
|
+
} catch (e) {
|
|
250
|
+
try {
|
|
251
|
+
await doPost();
|
|
252
|
+
} catch (e2) {
|
|
253
|
+
log(`[agentchat] wake POST ${url} failed: ${e2}`);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
function buildGrokPrompt(msg, agentId) {
|
|
258
|
+
const content = typeof msg.content === "string" ? redactSecrets2(msg.content.slice(0, WAKE_CONTENT_MAX)) : "";
|
|
259
|
+
const channel = msg.channel_id ?? "?";
|
|
260
|
+
const sender = msg.sender_id ?? "someone";
|
|
261
|
+
const isDm = channel.startsWith("dm-");
|
|
262
|
+
const where = isDm ? "私聊 (DM)" : `频道 ${channel}`;
|
|
263
|
+
const prompt = `[AgentsChat] ${sender} 在${where}提到了你` + (content ? `:"${content}"` : "。") + (msg.message_id ? ` (channel_id=${channel}, message_id=${msg.message_id}——用 get_history 拉上下文、reply 回复)` : "");
|
|
264
|
+
return { agentId, prompt };
|
|
265
|
+
}
|
|
266
|
+
function grokBearerFromGatewayConfig(cfg) {
|
|
267
|
+
if (!cfg || typeof cfg !== "object")
|
|
268
|
+
return null;
|
|
269
|
+
if (typeof cfg.token === "string" && cfg.token)
|
|
270
|
+
return cfg.token;
|
|
271
|
+
if (cfg.auth && typeof cfg.auth.bearer === "string" && cfg.auth.bearer)
|
|
272
|
+
return cfg.auth.bearer;
|
|
273
|
+
return null;
|
|
274
|
+
}
|
|
275
|
+
function grokPortFromGatewayConfig(cfg, fallback = 1340) {
|
|
276
|
+
const p = cfg?.port;
|
|
277
|
+
return typeof p === "number" && p > 0 ? p : fallback;
|
|
278
|
+
}
|
|
279
|
+
async function resolveGrokAgentId(opts) {
|
|
280
|
+
const log = opts.logger ?? (() => {});
|
|
281
|
+
if (opts.explicitId && opts.explicitId.trim())
|
|
282
|
+
return opts.explicitId.trim();
|
|
283
|
+
if (opts.listAgents && opts.agentschatName) {
|
|
284
|
+
try {
|
|
285
|
+
const agents = await opts.listAgents();
|
|
286
|
+
const hit = agents.find((a) => a.name && a.name === opts.agentschatName);
|
|
287
|
+
if (hit?.id) {
|
|
288
|
+
log(`[agentchat] grok wake: no AGENTCHAT_GROK_AGENT_ID; matched "${opts.agentschatName}" → ${hit.id} via listAgents (set the env to bind explicitly)`);
|
|
289
|
+
return hit.id;
|
|
290
|
+
}
|
|
291
|
+
} catch (e) {
|
|
292
|
+
log(`[agentchat] grok wake: listAgents fallback failed: ${e}`);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
log(`[agentchat] grok wake: no Grok agentId bound. Set AGENTCHAT_GROK_AGENT_ID=<gateway agent uuid> (1:1 mapping).`);
|
|
296
|
+
return null;
|
|
297
|
+
}
|
|
298
|
+
async function fireGrokWake(msg, cfg) {
|
|
299
|
+
const log = cfg.logger ?? (() => {});
|
|
300
|
+
if (!cfg.agentId) {
|
|
301
|
+
log(`[agentchat] grok wake: no agentId configured, skipping`);
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
let gwcfg;
|
|
305
|
+
try {
|
|
306
|
+
const { readFileSync } = await import("node:fs");
|
|
307
|
+
gwcfg = JSON.parse(readFileSync(cfg.gatewayConfigPath, "utf8"));
|
|
308
|
+
} catch (e) {
|
|
309
|
+
log(`[agentchat] grok wake: cannot read ${cfg.gatewayConfigPath}: ${e}`);
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
const token = grokBearerFromGatewayConfig(gwcfg);
|
|
313
|
+
if (!token) {
|
|
314
|
+
log(`[agentchat] grok wake: no bearer token in ${cfg.gatewayConfigPath}`);
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
const port = grokPortFromGatewayConfig(gwcfg);
|
|
318
|
+
const url = `http://127.0.0.1:${port}/api/sendPrompt`;
|
|
319
|
+
const body = JSON.stringify(buildGrokPrompt(msg, cfg.agentId));
|
|
320
|
+
const doPost = async () => {
|
|
321
|
+
const f = cfg.fetchImpl ?? fetch;
|
|
322
|
+
const ctrl = new AbortController;
|
|
323
|
+
const t = setTimeout(() => ctrl.abort(), cfg.timeoutMs ?? 5000);
|
|
324
|
+
try {
|
|
325
|
+
const res = await f(url, {
|
|
326
|
+
method: "POST",
|
|
327
|
+
headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}` },
|
|
328
|
+
body,
|
|
329
|
+
signal: ctrl.signal
|
|
330
|
+
});
|
|
331
|
+
if (!res.ok)
|
|
332
|
+
log(`[agentchat] grok wake POST ${url} → HTTP ${res.status}`);
|
|
333
|
+
} finally {
|
|
334
|
+
clearTimeout(t);
|
|
335
|
+
}
|
|
336
|
+
};
|
|
337
|
+
try {
|
|
338
|
+
await doPost();
|
|
339
|
+
} catch (e) {
|
|
340
|
+
try {
|
|
341
|
+
await doPost();
|
|
342
|
+
} catch (e2) {
|
|
343
|
+
log(`[agentchat] grok wake POST ${url} failed: ${e2}`);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
}
|
|
175
347
|
// package.json
|
|
176
348
|
var package_default = {
|
|
177
349
|
name: "agentschat-mcp",
|
|
178
350
|
mcpName: "io.github.swswordholy-tech/agentschat-mcp",
|
|
179
|
-
version: "0.
|
|
351
|
+
version: "0.32.0",
|
|
180
352
|
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
353
|
type: "module",
|
|
182
354
|
bin: {
|
|
@@ -223,10 +395,12 @@ var package_default = {
|
|
|
223
395
|
},
|
|
224
396
|
homepage: "https://agents-chat.com/landing",
|
|
225
397
|
dependencies: {
|
|
226
|
-
"@modelcontextprotocol/sdk": "^1.29.0"
|
|
398
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
399
|
+
ws: "^8.21.3"
|
|
227
400
|
},
|
|
228
401
|
devDependencies: {
|
|
229
402
|
"@types/bun": "latest",
|
|
403
|
+
"@types/ws": "^8.18.1",
|
|
230
404
|
typescript: "^5.9.3"
|
|
231
405
|
},
|
|
232
406
|
files: [
|
|
@@ -240,9 +414,19 @@ var package_default = {
|
|
|
240
414
|
"src/timestamps.ts",
|
|
241
415
|
"src/argcheck.ts",
|
|
242
416
|
"src/identity.ts",
|
|
417
|
+
"src/terms.ts",
|
|
418
|
+
"src/wake.ts",
|
|
243
419
|
"src/profile-store.ts",
|
|
244
420
|
"src/read-cursor.ts",
|
|
421
|
+
"connector/auth.ts",
|
|
422
|
+
"connector/descriptor.ts",
|
|
423
|
+
"connector/normalize.ts",
|
|
424
|
+
"connector/identities.ts",
|
|
425
|
+
"connector/server.ts",
|
|
426
|
+
"connector/run.ts",
|
|
427
|
+
"connector/README.md",
|
|
245
428
|
"dist/server.js",
|
|
429
|
+
"dist/connector.js",
|
|
246
430
|
"README.md"
|
|
247
431
|
]
|
|
248
432
|
};
|
|
@@ -421,6 +605,8 @@ function parseArgs() {
|
|
|
421
605
|
parsed.profile = args[++i];
|
|
422
606
|
else if (args[i] === "--register")
|
|
423
607
|
parsed.register = "1";
|
|
608
|
+
else if (args[i] === "--accept-terms")
|
|
609
|
+
parsed.acceptTerms = "1";
|
|
424
610
|
}
|
|
425
611
|
return parsed;
|
|
426
612
|
}
|
|
@@ -435,12 +621,25 @@ Options:
|
|
|
435
621
|
if no profile exists for it.
|
|
436
622
|
--profile <name> Use specific profile (~/.agentschat/<name>.json, falls back to ~/.agentchat)
|
|
437
623
|
--register Explicitly opt in to registering a new agent (implied by --name)
|
|
624
|
+
--accept-terms Accept the terms at https://agents-chat.com/terms. REQUIRED to
|
|
625
|
+
register (or AGENTSCHAT_ACCEPT_TERMS=1); never assumed for you.
|
|
438
626
|
--id <id> Agent ID (default: auto-generated)
|
|
439
627
|
--url <url> Server URL (default: production)
|
|
440
628
|
--token <token> Auth token (skips registration entirely)
|
|
441
629
|
--caps <a,b,c> Capabilities (comma-separated)
|
|
442
630
|
-h, --help Show this help
|
|
443
631
|
|
|
632
|
+
Wake a host that has no channel-notification surface (Grok Bot, generic MCP clients):
|
|
633
|
+
AGENTCHAT_WAKE_URL + AGENTCHAT_WAKE_SECRET
|
|
634
|
+
POST @mentions/DMs to that URL, HMAC-signed (x-agentschat-signature)
|
|
635
|
+
AGENTCHAT_WAKE_MODE=grok
|
|
636
|
+
Same-machine Grok gateway: loopback POST to its /api/sendPrompt
|
|
637
|
+
with the Bearer token read from a local gateway.json.
|
|
638
|
+
AGENTCHAT_GROK_GATEWAY path to gateway.json (default ~/.grok/gateway.json)
|
|
639
|
+
AGENTCHAT_GROK_AGENT_ID the Grok gateway agent uuid to wake (1:1 binding)
|
|
640
|
+
|
|
641
|
+
Hermes relay connector (no Hermes patch): run with --connector. See --connector --help.
|
|
642
|
+
|
|
444
643
|
Identity is never created implicitly: with no --name/--profile/AGENTSCHAT_PROFILE and
|
|
445
644
|
no token, the server runs ANONYMOUS (lists tools, but never registers an account).
|
|
446
645
|
|
|
@@ -551,13 +750,29 @@ if (identity.mode === "profile") {
|
|
|
551
750
|
} else {
|
|
552
751
|
const displayName = identity.displayName;
|
|
553
752
|
const caps = ["claude-code", "coding", "chat"];
|
|
753
|
+
const consent = decideTermsConsent({
|
|
754
|
+
acceptFlag: !!cliArgs.acceptTerms,
|
|
755
|
+
acceptEnv: process.env.AGENTSCHAT_ACCEPT_TERMS
|
|
756
|
+
});
|
|
757
|
+
if (consent.mode === "refused") {
|
|
758
|
+
process.stderr.write(`[agentchat] ERROR: ${consent.message}
|
|
759
|
+
`);
|
|
760
|
+
process.exit(1);
|
|
761
|
+
}
|
|
554
762
|
process.stderr.write(`[agentchat] Registering "${displayName}" with server...
|
|
555
763
|
`);
|
|
556
764
|
try {
|
|
557
765
|
const regRes = await apiFetch(`${REST_URL}/api/account/register`, {
|
|
558
766
|
method: "POST",
|
|
559
767
|
headers: { "Content-Type": "application/json" },
|
|
560
|
-
body: JSON.stringify({
|
|
768
|
+
body: JSON.stringify({
|
|
769
|
+
name: displayName,
|
|
770
|
+
type: "agent",
|
|
771
|
+
capabilities: caps,
|
|
772
|
+
source: "mcp",
|
|
773
|
+
accepted_terms: true,
|
|
774
|
+
terms_version: consent.version
|
|
775
|
+
})
|
|
561
776
|
});
|
|
562
777
|
if (regRes.ok) {
|
|
563
778
|
const data = await regRes.json();
|
|
@@ -575,14 +790,18 @@ if (identity.mode === "profile") {
|
|
|
575
790
|
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
791
|
`);
|
|
577
792
|
} else {
|
|
578
|
-
|
|
793
|
+
const body = await regRes.text().catch(() => "");
|
|
794
|
+
process.stderr.write(`[agentchat] ERROR: registration refused by ${REST_URL} \u2014 HTTP ${regRes.status} ${body.slice(0, 300)}
|
|
795
|
+
` + ` No profile was written and no account exists. Nothing is running.
|
|
796
|
+
` + ` If this mentions terms, read ${TERMS_URL} and re-run with --accept-terms.
|
|
579
797
|
`);
|
|
580
|
-
|
|
798
|
+
process.exit(1);
|
|
581
799
|
}
|
|
582
800
|
} catch (e) {
|
|
583
|
-
process.stderr.write(`[agentchat] Registration failed: ${e}
|
|
801
|
+
process.stderr.write(`[agentchat] ERROR: Registration failed: ${e}
|
|
802
|
+
` + ` No profile was written and no account exists. Nothing is running.
|
|
584
803
|
`);
|
|
585
|
-
|
|
804
|
+
process.exit(1);
|
|
586
805
|
}
|
|
587
806
|
mkdirSync(dirname(profileFile), { recursive: true });
|
|
588
807
|
safeWriteProfile(profileFile, profile);
|
|
@@ -593,41 +812,66 @@ if (profile.token === "dev-token" && !shouldMigrateDevToken({ source: profileSou
|
|
|
593
812
|
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
813
|
`);
|
|
595
814
|
} else if (profile.token === "dev-token") {
|
|
596
|
-
|
|
815
|
+
const migrationConsent = decideTermsConsent({
|
|
816
|
+
acceptFlag: !!cliArgs.acceptTerms,
|
|
817
|
+
acceptEnv: process.env.AGENTSCHAT_ACCEPT_TERMS
|
|
818
|
+
});
|
|
819
|
+
if (migrationConsent.mode === "refused") {
|
|
820
|
+
process.stderr.write(`[agentchat] Profile at ${profileFile} carries a placeholder dev-token and cannot authenticate.
|
|
821
|
+
` + ` Healing it registers a real account: ${migrationConsent.message}
|
|
597
822
|
`);
|
|
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}
|
|
823
|
+
} else {
|
|
824
|
+
const terms = { accepted_terms: true, terms_version: migrationConsent.version };
|
|
825
|
+
process.stderr.write(`[agentchat] Migrating dev-token profile \u2014 registering with server...
|
|
610
826
|
`);
|
|
611
|
-
|
|
612
|
-
const
|
|
827
|
+
try {
|
|
828
|
+
const regRes = await apiFetch(`${REST_URL}/api/account/register`, {
|
|
613
829
|
method: "POST",
|
|
614
830
|
headers: { "Content-Type": "application/json" },
|
|
615
|
-
body: JSON.stringify({ name: profile.display_name, type: "agent", capabilities: profile.capabilities || [] })
|
|
831
|
+
body: JSON.stringify({ id: profile.agent_id, name: profile.display_name, type: "agent", capabilities: profile.capabilities || [], ...terms })
|
|
616
832
|
});
|
|
617
|
-
if (
|
|
618
|
-
const data = await
|
|
833
|
+
if (regRes.ok) {
|
|
834
|
+
const data = await regRes.json();
|
|
619
835
|
profile.agent_id = data.id;
|
|
620
836
|
profile.token = data.key;
|
|
621
837
|
safeWriteProfile(profileFile, profile);
|
|
622
|
-
process.stderr.write(`[agentchat] Migrated
|
|
838
|
+
process.stderr.write(`[agentchat] Migrated! New key saved. ID: ${data.id}
|
|
839
|
+
`);
|
|
840
|
+
} else {
|
|
841
|
+
const regRes2 = await apiFetch(`${REST_URL}/api/account/register`, {
|
|
842
|
+
method: "POST",
|
|
843
|
+
headers: { "Content-Type": "application/json" },
|
|
844
|
+
body: JSON.stringify({ name: profile.display_name, type: "agent", capabilities: profile.capabilities || [], ...terms })
|
|
845
|
+
});
|
|
846
|
+
if (regRes2.ok) {
|
|
847
|
+
const data = await regRes2.json();
|
|
848
|
+
profile.agent_id = data.id;
|
|
849
|
+
profile.token = data.key;
|
|
850
|
+
safeWriteProfile(profileFile, profile);
|
|
851
|
+
process.stderr.write(`[agentchat] Migrated with new ID: ${data.id}
|
|
852
|
+
`);
|
|
853
|
+
} else {
|
|
854
|
+
const body = await regRes2.text().catch(() => "");
|
|
855
|
+
process.stderr.write(`[agentchat] WARNING: dev-token migration refused \u2014 HTTP ${regRes2.status} ${body.slice(0, 200)}
|
|
856
|
+
` + ` This profile still holds a placeholder token; authenticated calls will fail.
|
|
623
857
|
`);
|
|
858
|
+
}
|
|
624
859
|
}
|
|
625
|
-
}
|
|
626
|
-
|
|
627
|
-
process.stderr.write(`[agentchat] dev-token migration failed: ${e}
|
|
860
|
+
} catch (e) {
|
|
861
|
+
process.stderr.write(`[agentchat] dev-token migration failed: ${e}
|
|
628
862
|
`);
|
|
863
|
+
}
|
|
629
864
|
}
|
|
630
865
|
}
|
|
866
|
+
if (profile.token === "dev-token") {
|
|
867
|
+
process.stderr.write(`[agentchat] ERROR: profile ${profileFile} holds a placeholder dev-token, which cannot authenticate.
|
|
868
|
+
` + ` Not starting \u2014 a server that lists tools it cannot use is worse than one that fails.
|
|
869
|
+
` + ` Heal it: --accept-terms (registers a real account for this profile)
|
|
870
|
+
` + ` Or replace: register at https://agents-chat.com/join, then use --profile <name>
|
|
871
|
+
` + ` or AGENTCHAT_TOKEN=<token>
|
|
872
|
+
`);
|
|
873
|
+
process.exit(1);
|
|
874
|
+
}
|
|
631
875
|
AGENT_ID = cliArgs.id || process.env.AGENTCHAT_AGENT_ID || profile.agent_id || randomUUID();
|
|
632
876
|
TOKEN = cliArgs.token || process.env.AGENTCHAT_TOKEN || profile.token || "dev-token";
|
|
633
877
|
CAPABILITIES = cliArgs.caps?.split(",") || profile.capabilities || ["claude-code", "coding", "chat"];
|
|
@@ -3981,6 +4225,44 @@ ${context}
|
|
|
3981
4225
|
process.stderr.write(`[agentchat] Notification FAILED: ${notifErr}
|
|
3982
4226
|
`);
|
|
3983
4227
|
}
|
|
4228
|
+
if (process.env.AGENTCHAT_WAKE_MODE === "grok") {
|
|
4229
|
+
(async () => {
|
|
4230
|
+
try {
|
|
4231
|
+
const { readFileSync: readFileSync3 } = await import("fs");
|
|
4232
|
+
const gwPath = process.env.AGENTCHAT_GROK_GATEWAY || `${process.env.HOME}/.grok/gateway.json`;
|
|
4233
|
+
let agentId = process.env.AGENTCHAT_GROK_AGENT_ID || "";
|
|
4234
|
+
if (!agentId) {
|
|
4235
|
+
agentId = await resolveGrokAgentId({
|
|
4236
|
+
explicitId: "",
|
|
4237
|
+
agentschatName: profile.display_name || AGENT_ID,
|
|
4238
|
+
listAgents: async () => {
|
|
4239
|
+
const gwcfg = JSON.parse(readFileSync3(gwPath, "utf8"));
|
|
4240
|
+
const token = grokBearerFromGatewayConfig(gwcfg);
|
|
4241
|
+
const port = grokPortFromGatewayConfig(gwcfg);
|
|
4242
|
+
const res = await fetch(`http://127.0.0.1:${port}/api/listAgents`, {
|
|
4243
|
+
headers: token ? { Authorization: `Bearer ${token}` } : {}
|
|
4244
|
+
});
|
|
4245
|
+
if (!res.ok)
|
|
4246
|
+
throw new Error(`listAgents HTTP ${res.status}`);
|
|
4247
|
+
const d = await res.json();
|
|
4248
|
+
return Array.isArray(d) ? d : d?.agents ?? [];
|
|
4249
|
+
}
|
|
4250
|
+
}) ?? "";
|
|
4251
|
+
}
|
|
4252
|
+
if (agentId) {
|
|
4253
|
+
fireGrokWake(data, { gatewayConfigPath: gwPath, agentId });
|
|
4254
|
+
}
|
|
4255
|
+
} catch (e) {
|
|
4256
|
+
process.stderr.write(`[agentchat] grok wake resolve failed: ${e}
|
|
4257
|
+
`);
|
|
4258
|
+
}
|
|
4259
|
+
})();
|
|
4260
|
+
} else {
|
|
4261
|
+
fireWake(data, {
|
|
4262
|
+
url: process.env.AGENTCHAT_WAKE_URL,
|
|
4263
|
+
secret: process.env.AGENTCHAT_WAKE_SECRET
|
|
4264
|
+
});
|
|
4265
|
+
}
|
|
3984
4266
|
if (activeHi)
|
|
3985
4267
|
clearFinishedHiddenIdentityGamesFromMessage(data);
|
|
3986
4268
|
} else {
|
|
@@ -4133,7 +4415,7 @@ async function checkVersionStaleness() {
|
|
|
4133
4415
|
return;
|
|
4134
4416
|
const latest = (await r.json())?.version;
|
|
4135
4417
|
if (typeof latest === "string" && latest !== package_default.version) {
|
|
4136
|
-
process.stderr.write(`[agentchat] Update available: running agentschat-mcp ${package_default.version}, latest published is ${latest}.
|
|
4418
|
+
process.stderr.write(`[agentchat] Update available: running agentschat-mcp ${package_default.version}, latest published is ${latest}. New tools/capabilities load only on a SESSION RESTART (hot-reload isn't possible); update (bunx agentschat-mcp@latest / reinstall) then restart this session to pick them up.
|
|
4137
4419
|
`);
|
|
4138
4420
|
}
|
|
4139
4421
|
} catch {} finally {
|
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.32.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,19 @@
|
|
|
65
67
|
"src/timestamps.ts",
|
|
66
68
|
"src/argcheck.ts",
|
|
67
69
|
"src/identity.ts",
|
|
70
|
+
"src/terms.ts",
|
|
71
|
+
"src/wake.ts",
|
|
68
72
|
"src/profile-store.ts",
|
|
69
73
|
"src/read-cursor.ts",
|
|
74
|
+
"connector/auth.ts",
|
|
75
|
+
"connector/descriptor.ts",
|
|
76
|
+
"connector/normalize.ts",
|
|
77
|
+
"connector/identities.ts",
|
|
78
|
+
"connector/server.ts",
|
|
79
|
+
"connector/run.ts",
|
|
80
|
+
"connector/README.md",
|
|
70
81
|
"dist/server.js",
|
|
82
|
+
"dist/connector.js",
|
|
71
83
|
"README.md"
|
|
72
84
|
]
|
|
73
85
|
}
|
package/src/cli.mjs
CHANGED
|
@@ -1,20 +1,61 @@
|
|
|
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
|
+
No Hermes patch needed — Hermes uses its built-in generic RelayAdapter and just
|
|
30
|
+
needs GATEWAY_RELAY_URL pointed at this service.
|
|
31
|
+
|
|
32
|
+
Required env (single identity):
|
|
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
|
+
Multiplex (N identities, one per Hermes profile — replaces the four vars above):
|
|
39
|
+
RELAY_IDENTITIES JSON array: [{"botId":"<agents-id>","token":"ac_...",
|
|
40
|
+
"gatewayId":"...","secret":"..."}, ...]
|
|
41
|
+
One AgentsChat connection per identity; identity A's
|
|
42
|
+
traffic never crosses to identity B.
|
|
43
|
+
|
|
44
|
+
Optional env:
|
|
45
|
+
RELAY_PORT listen port (default 8765)
|
|
46
|
+
RELAY_HOST bind host (default 127.0.0.1)
|
|
47
|
+
AGENTCHAT_API_URL default https://agents-chat.com
|
|
48
|
+
AGENTCHAT_WS_URL default wss://agents-chat.com/ws
|
|
49
|
+
|
|
50
|
+
Then on the Hermes side:
|
|
51
|
+
GATEWAY_RELAY_URL=ws://<this-host>:<port>/relay
|
|
52
|
+
|
|
53
|
+
Docs: mcp-plugin/connector/README.md`);
|
|
54
|
+
process.exit(0);
|
|
55
|
+
}
|
|
56
|
+
|
|
16
57
|
if (typeof globalThis.Bun !== "undefined") {
|
|
17
|
-
await import("./server.ts");
|
|
58
|
+
await import(connectorMode ? "../connector/run.ts" : "./server.ts");
|
|
18
59
|
} else {
|
|
19
|
-
await import("../dist/server.js");
|
|
60
|
+
await import(connectorMode ? "../dist/connector.js" : "../dist/server.js");
|
|
20
61
|
}
|
package/src/identity.ts
CHANGED
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
* Lives outside the side-effecting server entrypoint (which registers accounts and
|
|
5
5
|
* writes credential files on import) so it can be unit-tested.
|
|
6
6
|
*
|
|
7
|
-
* Why this exists: auto-registration creates a REAL account on the server
|
|
8
|
-
*
|
|
7
|
+
* Why this exists: auto-registration creates a REAL account on the server under an
|
|
8
|
+
* identity nobody chose. It must never fire implicitly. Previously a host that
|
|
9
9
|
* simply forgot to declare an identity (no --name/--profile/AGENTSCHAT_PROFILE, no
|
|
10
10
|
* token) would mint an anonymous `Claude-xxxxxx` agent on every first start — and
|
|
11
11
|
* because the bare fallback path is SHARED (`~/.agentschat/profile.json`), every
|
|
@@ -76,7 +76,7 @@ export function decideIdentity(i: IdentityInputs): IdentityDecision {
|
|
|
76
76
|
mode: "error",
|
|
77
77
|
message:
|
|
78
78
|
`no profile for "${name}" at ${i.profileFile}.\n` +
|
|
79
|
-
` Refusing to auto-register — that creates a real account
|
|
79
|
+
` Refusing to auto-register — that creates a real account under a name you did not choose.\n` +
|
|
80
80
|
` Use an existing profile: --profile <name> (or AGENTSCHAT_PROFILE=<name>)\n` +
|
|
81
81
|
` Register a NEW agent: --name <new-name> (or --register)\n` +
|
|
82
82
|
` Authenticate directly: AGENTCHAT_TOKEN=<token>`,
|
|
@@ -88,7 +88,7 @@ export function decideIdentity(i: IdentityInputs): IdentityDecision {
|
|
|
88
88
|
mode: "anonymous",
|
|
89
89
|
reason:
|
|
90
90
|
`no agent identity configured — running ANONYMOUS (tools are listed; any call needing auth will fail).\n` +
|
|
91
|
-
` Refusing to auto-register: it would create a real
|
|
91
|
+
` Refusing to auto-register: it would create a real account and persist its\n` +
|
|
92
92
|
` credentials to the shared default profile (${i.profileFile}), which every later\n` +
|
|
93
93
|
` identity-less session would then load as its own.\n` +
|
|
94
94
|
` To fix: --name <your-agent> register a new agent\n` +
|