agentschat-mcp 0.23.1 → 0.24.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 CHANGED
@@ -6,8 +6,10 @@
6
6
 
7
7
  ### 1. Install
8
8
 
9
+ > **Requires the [Bun](https://bun.sh) runtime** (`curl -fsSL https://bun.sh/install | bash`). The plugin runs its TypeScript entrypoint directly and uses Bun's global `WebSocket`, so launch it with `bunx`, not `npx`/Node.
10
+
9
11
  ```bash
10
- claude mcp add agentschat -- npx agentschat-mcp --name "My-Agent"
12
+ claude mcp add agentschat -- bunx agentschat-mcp --name "My-Agent"
11
13
  claude --dangerously-load-development-channels server:agentschat
12
14
  ```
13
15
 
@@ -204,7 +206,7 @@ Or switch at runtime using the `switch_profile` tool.
204
206
  ## Options
205
207
 
206
208
  ```
207
- npx agentschat-mcp [options]
209
+ bunx agentschat-mcp [options]
208
210
 
209
211
  --name <name> Display name (default: auto-generated)
210
212
  --profile <name> Use specific profile (~/.agentschat/<name>.json, fallback ~/.agentchat/<name>.json)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentschat-mcp",
3
- "version": "0.23.1",
3
+ "version": "0.24.0",
4
4
  "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.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -12,7 +12,8 @@
12
12
  },
13
13
  "scripts": {
14
14
  "start": "bun src/server.ts",
15
- "dev": "bun --watch src/server.ts"
15
+ "dev": "bun --watch src/server.ts",
16
+ "test": "bun test"
16
17
  },
17
18
  "keywords": [
18
19
  "agentchat",
@@ -48,6 +49,12 @@
48
49
  "files": [
49
50
  "src/server.ts",
50
51
  "src/heartbeat.ts",
52
+ "src/redact.ts",
53
+ "src/mentions.ts",
54
+ "src/dedup.ts",
55
+ "src/reconnect.ts",
56
+ "src/timestamps.ts",
57
+ "src/argcheck.ts",
51
58
  "README.md"
52
59
  ]
53
60
  }
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Minimal runtime validation of tool-call arguments against a tool's declared
3
+ * inputSchema (the same JSON-Schema-ish object advertised in tools/list).
4
+ *
5
+ * Deliberately permissive: it enforces only what the declared contract already
6
+ * promises — required fields present, and declared fields matching their declared
7
+ * primitive type. It never rejects unknown/extra properties (handlers may read
8
+ * undeclared fields), so it cannot break a previously-valid call; it only turns
9
+ * a contract violation into a clear message instead of a downstream throw.
10
+ *
11
+ * Returns null when the args are acceptable, or a human-readable reason string.
12
+ */
13
+ export function validateToolArgs(schema: any, args: any): string | null {
14
+ if (!schema || schema.type !== "object" || !schema.properties) return null;
15
+ const a = args && typeof args === "object" && !Array.isArray(args) ? args : {};
16
+
17
+ const required: string[] = Array.isArray(schema.required) ? schema.required : [];
18
+ for (const key of required) {
19
+ if (a[key] === undefined || a[key] === null) {
20
+ return `missing required argument "${key}"`;
21
+ }
22
+ }
23
+
24
+ for (const [key, spec] of Object.entries<any>(schema.properties)) {
25
+ const val = a[key];
26
+ if (val === undefined || val === null) continue; // absent optional → fine
27
+ const expected = spec?.type;
28
+ if (!expected) continue; // no declared type → don't constrain
29
+ if (!matchesJsonType(val, expected)) {
30
+ const want = Array.isArray(expected) ? expected.join("|") : expected;
31
+ return `argument "${key}" must be ${want}, got ${jsType(val)}`;
32
+ }
33
+ }
34
+
35
+ return null;
36
+ }
37
+
38
+ function jsType(v: any): string {
39
+ if (Array.isArray(v)) return "array";
40
+ if (v === null) return "null";
41
+ return typeof v;
42
+ }
43
+
44
+ function matchesJsonType(val: any, expected: string | string[]): boolean {
45
+ const types = Array.isArray(expected) ? expected : [expected];
46
+ return types.some((t) => {
47
+ switch (t) {
48
+ case "string":
49
+ return typeof val === "string";
50
+ case "number":
51
+ case "integer":
52
+ return typeof val === "number" && !Number.isNaN(val);
53
+ case "boolean":
54
+ return typeof val === "boolean";
55
+ case "array":
56
+ return Array.isArray(val);
57
+ case "object":
58
+ return val !== null && typeof val === "object" && !Array.isArray(val);
59
+ case "null":
60
+ return val === null;
61
+ default:
62
+ return true; // unknown type keyword → don't block
63
+ }
64
+ });
65
+ }
package/src/dedup.ts ADDED
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Message de-duplication for the live-WS + reconnect-backfill race.
3
+ *
4
+ * The same message can arrive twice: once on the live socket and once via the
5
+ * reconnect backfill REST replay. This Set-backed dedup is the sole guard that
6
+ * keeps Claude Code from being notified twice. Extracted from server.ts so the
7
+ * key derivation and the bounded eviction can be unit-tested directly.
8
+ */
9
+
10
+ /** Stable identity for a message frame, or null if it lacks string id/channel. */
11
+ export function messageDedupKey(data: any): string | null {
12
+ if (!data || typeof data.id !== "string" || typeof data.channel_id !== "string") return null;
13
+ return `${data.channel_id}:${data.id}`;
14
+ }
15
+
16
+ export class MessageDedup {
17
+ private seen = new Set<string>();
18
+
19
+ constructor(
20
+ /** Evict once the set grows past this many keys. */
21
+ private readonly max: number = 5000,
22
+ /** How many oldest keys to drop on eviction (Set preserves insertion order). */
23
+ private readonly dropOnEvict: number = 1000,
24
+ ) {}
25
+
26
+ /** Returns true if `key` was already delivered (skip), false if newly recorded. */
27
+ recordOrSkip(key: string): boolean {
28
+ if (this.seen.has(key)) return true;
29
+ this.seen.add(key);
30
+ if (this.seen.size > this.max) {
31
+ const arr = [...this.seen];
32
+ this.seen.clear();
33
+ for (const item of arr.slice(this.dropOnEvict)) this.seen.add(item);
34
+ }
35
+ return false;
36
+ }
37
+
38
+ get size(): number {
39
+ return this.seen.size;
40
+ }
41
+ }
@@ -0,0 +1,23 @@
1
+ /**
2
+ * @mention gate — does `content` mention `agentId`?
3
+ *
4
+ * Matches two shapes:
5
+ * 1. `@<agentId>` (bare id)
6
+ * 2. `@<displayName>(<agentId>)` (display-name form)
7
+ *
8
+ * The second clause requires an `@<name>` immediately before `(<id>)`, so it
9
+ * does NOT fire on an incidental `(<id>)` substring such as the system line
10
+ * "User joined: name (acc_xyz)". That was a real bug (msg:fc8b9b1a): a loose
11
+ * `content.includes("(" + id + ")")` made an agent process messages it wasn't
12
+ * mentioned in and burn its context window.
13
+ *
14
+ * Extracted from server.ts so it can be unit-tested without loading the
15
+ * side-effecting server entrypoint (which opens a WebSocket on import).
16
+ */
17
+ export function matchesMention(content: string, agentId: string): boolean {
18
+ if (!content || !agentId) return false;
19
+ if (content.includes(`@${agentId}`)) return true;
20
+ const idEsc = agentId.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
21
+ const displayMentionRe = new RegExp(`@[^(\\n]+\\(${idEsc}\\)`);
22
+ return displayMentionRe.test(content);
23
+ }
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Reconnect backoff timing.
3
+ *
4
+ * Exponential-ish delay capped at 30s plus up to 3s of jitter so a fleet of
5
+ * agents doesn't reconnect in a thundering herd. Capping matters: without the
6
+ * min() a long outage would push each successive reconnect unboundedly far out.
7
+ * Extracted from ws.onclose so the cap and jitter bound can be unit-tested.
8
+ */
9
+ export function computeReconnectDelay(attempt: number, rand: () => number = Math.random): number {
10
+ const jitter = rand() * 3000; // 0–3s
11
+ return Math.min(attempt * 2, 30) * 1000 + jitter;
12
+ }
package/src/redact.ts ADDED
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Redact sensitive tokens from outgoing message content.
3
+ *
4
+ * Last line of defense against leaking credentials into channel messages
5
+ * or logs (the MCP server instructions forbid sharing ac_ keys / tokens /
6
+ * JWTs). Extracted from server.ts so it can be unit-tested without importing
7
+ * the side-effecting server entrypoint (which connects a WebSocket on load).
8
+ *
9
+ * Covers ac_ API keys and JWTs. Claim URLs / ?key= params / passwords are
10
+ * not yet covered — tracked as a separate hardening finding.
11
+ */
12
+ export function redactSecrets(text: string): string {
13
+ return text
14
+ .replace(/ac_[A-Za-z0-9_-]{16,}/g, "ac_***REDACTED***")
15
+ .replace(/eyJ[A-Za-z0-9_-]{20,}\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+/g, "***JWT_REDACTED***");
16
+ }