agentschat-mcp 0.21.0 → 0.22.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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/server.ts +55 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentschat-mcp",
3
- "version": "0.21.0",
3
+ "version": "0.22.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": {
package/src/server.ts CHANGED
@@ -381,6 +381,7 @@ const CORE_TOOL_NAMES = new Set([
381
381
  "list_skills",
382
382
  "load_skill",
383
383
  "save_skill",
384
+ "sync_skill",
384
385
  "list_loops",
385
386
  "my_entitlements",
386
387
  "channel_brief",
@@ -864,6 +865,19 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
864
865
  required: ["chat_id", "name", "description"],
865
866
  },
866
867
  },
868
+ {
869
+ name: "sync_skill",
870
+ description: "Lazy-sync a channel skill to a local file, fetching the body ONLY if your local copy is missing or stale (version-aware). Cheap: it checks the skill's current version via the docs list (no body) and SKIPS the download when you already have that version — 'have it + version matches → use directly, else sync then use'. Returns the local path; read that file to run the skill in your own runtime.",
871
+ inputSchema: {
872
+ type: "object" as const,
873
+ properties: {
874
+ chat_id: { type: "string", description: "The skill's channel id" },
875
+ doc_id: { type: "string", description: "The skill's doc id" },
876
+ dir: { type: "string", description: "Optional local dir to sync into (default ~/.agentchat/skills)" },
877
+ },
878
+ required: ["chat_id", "doc_id"],
879
+ },
880
+ },
867
881
  {
868
882
  name: "list_tool_groups",
869
883
  description: "List available extended tool groups, including whether each group is already loaded.",
@@ -1407,6 +1421,47 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1407
1421
  }
1408
1422
  }
1409
1423
 
1424
+ if (name === "sync_skill") {
1425
+ const a = (args || {}) as { chat_id?: string; doc_id?: string; dir?: string };
1426
+ if (!a.chat_id || !a.doc_id) {
1427
+ return { content: [{ type: "text", text: "sync_skill needs chat_id + doc_id (the skill's channel + doc)." }] };
1428
+ }
1429
+ const home = process.env.HOME || process.env.USERPROFILE || ".";
1430
+ const cacheDir = (a.dir && String(a.dir).trim()) || `${home}/.agentchat/skills`;
1431
+ const safe = (s: string) => String(s).replace(/[^A-Za-z0-9_.-]/g, "_");
1432
+ const base = `${cacheDir}/${safe(a.chat_id)}__${safe(a.doc_id)}`;
1433
+ const mdPath = `${base}.md`;
1434
+ const metaPath = `${base}.json`;
1435
+ try {
1436
+ // 1. Cheap version check: docs-list returns each doc's version WITHOUT the
1437
+ // body, so "is my local copy current?" costs one light call.
1438
+ const listR = await fetch(`${REST_URL}/api/channels/${encodeURIComponent(a.chat_id)}/docs`, { headers: { "Authorization": `Bearer ${TOKEN}` } });
1439
+ if (!listR.ok) return { content: [{ type: "text", text: `sync_skill: docs-list failed (${listR.status})` }] };
1440
+ const docs = extractChannelDocsPayload(JSON.parse(await listR.text()));
1441
+ const meta = docs.find((d: any) => (d?.id ?? d?.doc_id) === a.doc_id);
1442
+ if (!meta) return { content: [{ type: "text", text: `sync_skill: skill "${a.doc_id}" not found in channel ${a.chat_id}` }] };
1443
+ const currentVersion = Number(meta.version ?? 0);
1444
+ // 2. Local cache check — skip the body fetch if we already have this version.
1445
+ let cachedVersion: number | null = null;
1446
+ try { cachedVersion = Number(JSON.parse(await Bun.file(metaPath).text()).version); } catch {}
1447
+ if (cachedVersion !== null && cachedVersion === currentVersion) {
1448
+ return { content: [{ type: "text", text: `up-to-date: "${a.doc_id}" v${currentVersion} already at ${mdPath} — no download. Read that file to run it.` }] };
1449
+ }
1450
+ // 3. Missing/stale → fetch the body (the only expensive call, on the cold path).
1451
+ const docR = await fetch(`${REST_URL}/api/channels/${encodeURIComponent(a.chat_id)}/docs/${encodeURIComponent(a.doc_id)}`, { headers: { "Authorization": `Bearer ${TOKEN}` } });
1452
+ if (!docR.ok) return { content: [{ type: "text", text: `sync_skill: fetch body failed (${docR.status})` }] };
1453
+ const doc = JSON.parse(await docR.text());
1454
+ const body = String(doc?.body_markdown ?? doc?.bodyMarkdown ?? "");
1455
+ // 4. Write the local mirror + version sidecar.
1456
+ await Bun.write(mdPath, body);
1457
+ 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() }));
1458
+ const was = cachedVersion === null ? "missing" : `stale v${cachedVersion}`;
1459
+ 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.` }] };
1460
+ } catch (e: any) {
1461
+ return { content: [{ type: "text", text: `sync_skill error: ${String(e?.message || e).slice(0, 140)}` }] };
1462
+ }
1463
+ }
1464
+
1410
1465
  if (name === "list_tool_groups") {
1411
1466
  return {
1412
1467
  content: [{