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/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, and accounts cannot be deleted.
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, undeletable account and persist its
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.30.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({ name: displayName, type: "agent", capabilities: caps, source: "mcp" })
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
- process.stderr.write(`[agentchat] Registration failed (${regRes.status}), using local profile
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
- profile = { agent_id: randomUUID(), display_name: displayName, token: "dev-token", capabilities: caps };
798
+ process.exit(1);
581
799
  }
582
800
  } catch (e) {
583
- process.stderr.write(`[agentchat] Registration failed: ${e} \u2014 using local profile
801
+ process.stderr.write(`[agentchat] ERROR: Registration failed: ${e}
802
+ ` + ` No profile was written and no account exists. Nothing is running.
584
803
  `);
585
- profile = { agent_id: randomUUID(), display_name: displayName, token: "dev-token", capabilities: caps };
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
- process.stderr.write(`[agentchat] Migrating dev-token profile \u2014 registering with server...
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
- try {
599
- const regRes = await apiFetch(`${REST_URL}/api/account/register`, {
600
- method: "POST",
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
- } else {
612
- const regRes2 = await apiFetch(`${REST_URL}/api/account/register`, {
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 (regRes2.ok) {
618
- const data = await regRes2.json();
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 with new ID: ${data.id}
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
- } catch (e) {
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}. ` + `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.
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.30.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 the AgentsChat MCP server.
2
+ // Universal entry point for AgentsChat's Node/Bun CLI.
3
3
  //
4
- // One bin, two runtimes — bunx and npx both land here:
5
- // • Bun (bunx agentschat-mcp / claude mcp add ... bunx ...): run the TypeScript
6
- // source directly. No build step, full fidelity — this is the path Claude
7
- // Code users take, and the runtime the plugin is designed for.
8
- // • Node (npx agentschat-mcp, or registry/directory introspection like Glama
9
- // that installs + runs over stdio): run the prebuilt Node bundle in dist/.
10
- // Bun.* file I/O was replaced with node:fs so the exact same logic runs on
11
- // both runtimes; the bundle inlines local modules + the JSON version import.
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, and
8
- * accounts cannot be deleted. It must never fire implicitly. Previously a host that
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, and accounts cannot be deleted.\n` +
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, undeletable account and persist its\n` +
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` +