agentschat-mcp 0.29.0 → 0.29.1

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/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "agentschat-mcp",
3
3
  "mcpName": "io.github.swswordholy-tech/agentschat-mcp",
4
- "version": "0.29.0",
4
+ "version": "0.29.1",
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": {
8
- "agentschat-mcp": "src/server.ts",
9
- "agentchat-mcp": "src/server.ts"
8
+ "agentschat-mcp": "src/cli.mjs",
9
+ "agentchat-mcp": "src/cli.mjs"
10
10
  },
11
11
  "engines": {
12
12
  "bun": ">=1.0.0"
@@ -16,8 +16,10 @@
16
16
  "dev": "bun --watch src/server.ts",
17
17
  "test": "bun test",
18
18
  "typecheck": "tsc --noEmit",
19
+ "build": "bun scripts/build.mjs",
19
20
  "check:version": "bun scripts/check-version-sync.mjs",
20
- "verify": "bun scripts/check-version-sync.mjs && tsc --noEmit && bun test"
21
+ "verify": "bun run build && bun scripts/check-version-sync.mjs && tsc --noEmit && bun test",
22
+ "prepublishOnly": "bun run build"
21
23
  },
22
24
  "keywords": [
23
25
  "agentchat",
@@ -52,6 +54,7 @@
52
54
  "typescript": "^5.9.3"
53
55
  },
54
56
  "files": [
57
+ "src/cli.mjs",
55
58
  "src/server.ts",
56
59
  "src/heartbeat.ts",
57
60
  "src/redact.ts",
@@ -60,6 +63,7 @@
60
63
  "src/reconnect.ts",
61
64
  "src/timestamps.ts",
62
65
  "src/argcheck.ts",
66
+ "dist/server.js",
63
67
  "README.md"
64
68
  ]
65
69
  }
package/src/cli.mjs ADDED
@@ -0,0 +1,20 @@
1
+ #!/usr/bin/env node
2
+ // Universal entry point for the AgentsChat MCP server.
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.
12
+ //
13
+ // bunx ignores this shebang and runs the file under Bun (so `typeof Bun` is
14
+ // 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.
16
+ if (typeof globalThis.Bun !== "undefined") {
17
+ await import("./server.ts");
18
+ } else {
19
+ await import("../dist/server.js");
20
+ }
package/src/server.ts CHANGED
@@ -4,16 +4,14 @@
4
4
  * 像 weixin 插件一样:WebSocket 消息 → MCP channel notification → Claude Code 对话
5
5
  */
6
6
 
7
- // Runtime guard: this plugin ships its TypeScript entrypoint directly and
8
- // relies on Bun's global WebSocket, so it must run under Bun (launch via
9
- // `bunx agentschat-mcp`, not npx/node). Fail fast with a clear message rather
10
- // than a cryptic parse/"WebSocket is not defined" error downstream.
11
- if (typeof (globalThis as any).Bun === "undefined") {
12
- process.stderr.write(
13
- "[agentchat] agentschat-mcp requires the Bun runtime. Install Bun (https://bun.sh) and launch with `bunx agentschat-mcp` (not npx/node).\n",
14
- );
15
- process.exit(1);
16
- }
7
+ // Runtime: Bun is the primary/first-class runtime (`bunx agentschat-mcp` runs
8
+ // this .ts source directly, no build step). It ALSO runs on plain Node via the
9
+ // prebuilt bundle in dist/ (see src/cli.mjs launcher) so directory/registry
10
+ // tooling that introspects over stdio with Node/npx (e.g. Glama) can install
11
+ // and list tools. All Bun-specific I/O was replaced with node:fs; the WebSocket
12
+ // connect is wrapped in try/catch, so a missing global WebSocket (older Node)
13
+ // degrades to a reconnect log rather than crashing stdio/tools-list. Node 22+
14
+ // (global WebSocket/fetch/FormData) gets full functionality.
17
15
 
18
16
  // ── Proxy bypass ──────────────────────────────────────────────────
19
17
  // MCP subprocess inherits the parent's HTTP_PROXY/HTTPS_PROXY which
@@ -1610,13 +1608,14 @@ function mimeFromPath(p: string): string {
1610
1608
  }
1611
1609
  // Upload a local file via multipart POST /api/upload → { url, mime, size }.
1612
1610
  async function uploadLocalFile(path: string): Promise<{ url: string; mime: string; size: number }> {
1613
- const f = Bun.file(path);
1614
- if (!(await f.exists())) throw new Error(`file not found: ${path}`);
1615
- const mime = f.type && f.type !== "application/octet-stream" ? f.type : mimeFromPath(path);
1616
- const bytes = await f.arrayBuffer();
1611
+ // node:fs (not Bun.file) so this runs on plain Node too — Bun supports node:fs
1612
+ // identically, and the Node path is what registry introspection (Glama) uses.
1613
+ if (!existsSync(path)) throw new Error(`file not found: ${path}`);
1614
+ const mime = mimeFromPath(path);
1615
+ const buf = readFileSync(path);
1617
1616
  const name = path.split("/").pop() || "upload";
1618
1617
  const form = new FormData();
1619
- form.append("file", new Blob([bytes], { type: mime }), name);
1618
+ form.append("file", new Blob([new Uint8Array(buf)], { type: mime }), name);
1620
1619
  // No Content-Type header → fetch derives the multipart boundary itself.
1621
1620
  const r = await apiFetch(`${REST_URL}/api/upload`, { method: "POST", headers: { "Authorization": `Bearer ${TOKEN}` }, body: form });
1622
1621
  const text = await r.text();
@@ -1624,7 +1623,7 @@ async function uploadLocalFile(path: string): Promise<{ url: string; mime: strin
1624
1623
  let data: any;
1625
1624
  try { data = JSON.parse(text); } catch { throw new Error(`upload returned non-JSON: ${text.slice(0, 120)}`); }
1626
1625
  if (!data?.url) throw new Error(`upload response missing url: ${text.slice(0, 120)}`);
1627
- return { url: data.url as string, mime: (data.type as string) || mime, size: (data.size as number) ?? bytes.byteLength };
1626
+ return { url: data.url as string, mime: (data.type as string) || mime, size: (data.size as number) ?? buf.byteLength };
1628
1627
  }
1629
1628
  // Post a message carrying exactly one media attachment (image|audio).
1630
1629
  async function sendMediaMessage(kind: "image" | "audio", args: any): Promise<{ content: any[]; isError?: boolean }> {
@@ -1981,15 +1980,18 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1981
1980
  if (!meta) return { content: [{ type: "text", text: `sync_skill: personal skill "${a.name}" not found (save it with save_skill — no chat_id).` }], isError: true };
1982
1981
  const currentVersion = Number(meta.version ?? 0);
1983
1982
  let cachedVersion: number | null = null;
1984
- try { cachedVersion = Number(JSON.parse(await Bun.file(pMeta).text()).version); } catch {}
1983
+ try { cachedVersion = Number(JSON.parse(readFileSync(pMeta, "utf8")).version); } catch {}
1985
1984
  if (cachedVersion !== null && cachedVersion === currentVersion) {
1986
1985
  return { content: [{ type: "text", text: `up-to-date: personal skill "${a.name}" v${currentVersion} already at ${pMd} — no download. Read that file to run it.` }] };
1987
1986
  }
1988
1987
  const bodyR = await apiFetch(`${REST_URL}/api/skills/${encodeURIComponent(a.name)}`, { headers: { "Authorization": `Bearer ${TOKEN}` } });
1989
1988
  if (!bodyR.ok) return { content: [{ type: "text", text: `sync_skill (personal): body fetch failed (${bodyR.status})` }], isError: true };
1990
1989
  const doc = JSON.parse(await bodyR.text());
1991
- await Bun.write(pMd, String(doc?.body_markdown ?? ""));
1992
- await Bun.write(pMeta, JSON.stringify({ version: currentVersion, name: a.name, syncedAt: new Date().toISOString() }));
1990
+ // node:fs (not Bun.write) — runs on plain Node too. Bun.write auto-creates
1991
+ // parent dirs; writeFileSync does not, so mkdir the cache dir first.
1992
+ mkdirSync(dirname(pMd), { recursive: true });
1993
+ writeFileSync(pMd, String(doc?.body_markdown ?? ""));
1994
+ writeFileSync(pMeta, JSON.stringify({ version: currentVersion, name: a.name, syncedAt: new Date().toISOString() }));
1993
1995
  return { content: [{ type: "text", text: `synced personal skill "${a.name}" v${currentVersion} → ${pMd} (was ${cachedVersion === null ? "missing" : `stale v${cachedVersion}`}). Read that file to run it.` }] };
1994
1996
  } catch (e: any) {
1995
1997
  return { content: [{ type: "text", text: `sync_skill (personal) error: ${String(e?.message || e).slice(0, 140)}` }], isError: true };
@@ -2013,7 +2015,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
2013
2015
  const currentVersion = Number(meta.version ?? 0);
2014
2016
  // 2. Local cache check — skip the body fetch if we already have this version.
2015
2017
  let cachedVersion: number | null = null;
2016
- try { cachedVersion = Number(JSON.parse(await Bun.file(metaPath).text()).version); } catch {}
2018
+ try { cachedVersion = Number(JSON.parse(readFileSync(metaPath, "utf8")).version); } catch {}
2017
2019
  if (cachedVersion !== null && cachedVersion === currentVersion) {
2018
2020
  return { content: [{ type: "text", text: `up-to-date: "${a.doc_id}" v${currentVersion} already at ${mdPath} — no download. Read that file to run it.` }] };
2019
2021
  }
@@ -2022,9 +2024,11 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
2022
2024
  if (!docR.ok) return { content: [{ type: "text", text: `sync_skill: fetch body failed (${docR.status})` }], isError: true };
2023
2025
  const doc = JSON.parse(await docR.text());
2024
2026
  const body = String(doc?.body_markdown ?? doc?.bodyMarkdown ?? "");
2025
- // 4. Write the local mirror + version sidecar.
2026
- await Bun.write(mdPath, body);
2027
- await Bun.write(metaPath, JSON.stringify({ version: currentVersion, title: doc?.title, doc_id: a.doc_id, chat_id: a.chat_id, syncedAt: new Date().toISOString() }));
2027
+ // 4. Write the local mirror + version sidecar (node:fs, not Bun.write, for
2028
+ // Node compat; mkdir the cache dir first since writeFileSync won't auto-create).
2029
+ mkdirSync(dirname(mdPath), { recursive: true });
2030
+ writeFileSync(mdPath, body);
2031
+ writeFileSync(metaPath, JSON.stringify({ version: currentVersion, title: doc?.title, doc_id: a.doc_id, chat_id: a.chat_id, syncedAt: new Date().toISOString() }));
2028
2032
  const was = cachedVersion === null ? "missing" : `stale v${cachedVersion}`;
2029
2033
  return { content: [{ type: "text", text: `synced "${doc?.title || a.doc_id}" v${currentVersion} → ${mdPath} (was ${was}). Read that file to run it in your runtime.` }] };
2030
2034
  } catch (e: any) {