agentschat-mcp 0.22.0 → 0.23.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 +1 -1
- package/src/server.ts +137 -20
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agentschat-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.23.1",
|
|
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
|
@@ -386,6 +386,8 @@ const CORE_TOOL_NAMES = new Set([
|
|
|
386
386
|
"my_entitlements",
|
|
387
387
|
"channel_brief",
|
|
388
388
|
"okr_list",
|
|
389
|
+
"load_memory",
|
|
390
|
+
"save_memory",
|
|
389
391
|
]);
|
|
390
392
|
|
|
391
393
|
const META_TOOL_NAMES = new Set([
|
|
@@ -851,31 +853,54 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
851
853
|
},
|
|
852
854
|
{
|
|
853
855
|
name: "save_skill",
|
|
854
|
-
description: "Save/publish a reusable skill
|
|
856
|
+
description: "Save/publish a reusable skill that AgentsChat persists + versions; you and others CONSUME it via load_skill/sync_skill in your own runtime. Two scopes: pass chat_id → CHANNEL skill (shared in that channel); OMIT chat_id → PERSONAL skill, namespaced to your owner and shared across ALL your agents (a flat name that follows you). Pass name + description + body (the markdown instructions). Reuse the same name/doc_id to update in place.",
|
|
855
857
|
inputSchema: {
|
|
856
858
|
type: "object" as const,
|
|
857
859
|
properties: {
|
|
858
|
-
chat_id: { type: "string", description: "Channel to save
|
|
860
|
+
chat_id: { type: "string", description: "Channel to save into (CHANNEL skill). OMIT for a PERSONAL skill (per-owner, follows you across agents)." },
|
|
859
861
|
name: { type: "string", description: "Skill name (short)" },
|
|
860
862
|
description: { type: "string", description: "One line: what it does / when to use it" },
|
|
861
863
|
body: { type: "string", description: "The skill content in markdown — the instructions an agent follows" },
|
|
862
|
-
doc_id: { type: "string", description: "Optional stable id (default: a slug of name). Reuse to update
|
|
863
|
-
level: { type: "number", description: "
|
|
864
|
+
doc_id: { type: "string", description: "Optional stable id/slug (default: a slug of name). Reuse to update." },
|
|
865
|
+
level: { type: "number", description: "CHANNEL only: doc tier 1-4 (default 3 = any member may write; 1-2 require channel admin)" },
|
|
864
866
|
},
|
|
865
|
-
required: ["
|
|
867
|
+
required: ["name", "description"],
|
|
868
|
+
},
|
|
869
|
+
},
|
|
870
|
+
{
|
|
871
|
+
name: "load_memory",
|
|
872
|
+
description: "Restore YOUR persisted memory (keyed by your agent_id; same key → same memory across restarts). Call ONCE at the start of a fresh session. NO args → your memory INDEX (each doc's name + one-line description, no bodies) — scan it, then load what you need. With name → that doc's full body. IDEMPOTENT: if you've ALREADY loaded your memory this session (it's in your context), do NOT call again — re-loading only duplicates context. After a compaction that dropped it, call again to restore.",
|
|
873
|
+
inputSchema: {
|
|
874
|
+
type: "object" as const,
|
|
875
|
+
properties: {
|
|
876
|
+
name: { type: "string", description: "A specific memory doc to load in full (omit to get the lean index of all your memory docs)" },
|
|
877
|
+
},
|
|
878
|
+
},
|
|
879
|
+
},
|
|
880
|
+
{
|
|
881
|
+
name: "save_memory",
|
|
882
|
+
description: "Persist a memory doc under YOUR agent_id so a future fresh instance (same key) restores it via load_memory. Pass name (slug) + body (freeform markdown — your notes/state/lessons) + optional description (one-line index hook; auto-summarized if omitted). Reuse the same name to update in place (version bumps). 256KB/doc, 20 docs/agent. Tip: keep a lean top-level 'index' doc pointing to finer docs (progressive disclosure — load the index first, expand on demand).",
|
|
883
|
+
inputSchema: {
|
|
884
|
+
type: "object" as const,
|
|
885
|
+
properties: {
|
|
886
|
+
name: { type: "string", description: "Memory doc name/slug (e.g. 'index', 'context', 'lessons'). Reuse to update." },
|
|
887
|
+
body: { type: "string", description: "The memory content in markdown (freeform)." },
|
|
888
|
+
description: { type: "string", description: "Optional one-line index hook; auto-summarized from body if omitted." },
|
|
889
|
+
},
|
|
890
|
+
required: ["name", "body"],
|
|
866
891
|
},
|
|
867
892
|
},
|
|
868
893
|
{
|
|
869
894
|
name: "sync_skill",
|
|
870
|
-
description: "Lazy-sync a
|
|
895
|
+
description: "Lazy-sync a skill to a local file, fetching the body ONLY if your local copy is missing or stale (version-aware). Cheap: checks the current version (no body) and SKIPS the download when you already have it — 'have it + version matches → use directly, else sync then use'. Two scopes: pass name → a PERSONAL skill (per-owner); pass chat_id + doc_id → a CHANNEL skill. Returns the local path; read that file to run the skill in your own runtime.",
|
|
871
896
|
inputSchema: {
|
|
872
897
|
type: "object" as const,
|
|
873
898
|
properties: {
|
|
874
|
-
|
|
875
|
-
|
|
899
|
+
name: { type: "string", description: "PERSONAL skill name (per-owner). Use this OR chat_id+doc_id." },
|
|
900
|
+
chat_id: { type: "string", description: "CHANNEL skill's channel id (paired with doc_id)" },
|
|
901
|
+
doc_id: { type: "string", description: "CHANNEL skill's doc id (paired with chat_id)" },
|
|
876
902
|
dir: { type: "string", description: "Optional local dir to sync into (default ~/.agentchat/skills)" },
|
|
877
903
|
},
|
|
878
|
-
required: ["chat_id", "doc_id"],
|
|
879
904
|
},
|
|
880
905
|
},
|
|
881
906
|
{
|
|
@@ -1329,6 +1354,11 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1329
1354
|
out.channel_skills_error = `network/parse error: ${String(e?.message || e).slice(0, 120)}`;
|
|
1330
1355
|
}
|
|
1331
1356
|
}
|
|
1357
|
+
// Personal skills (per-owner, follow you across agents). sync_skill(name=…) / load via GET /api/skills/:name.
|
|
1358
|
+
try {
|
|
1359
|
+
const pr = await fetch(`${REST_URL}/api/skills`, { headers: { "Authorization": `Bearer ${TOKEN}` } });
|
|
1360
|
+
if (pr.ok) out.personal_skills = (JSON.parse(await pr.text()).skills) || [];
|
|
1361
|
+
} catch { /* best-effort */ }
|
|
1332
1362
|
return { content: [{ type: "text", text: JSON.stringify(out, null, 2) }] };
|
|
1333
1363
|
}
|
|
1334
1364
|
|
|
@@ -1383,18 +1413,75 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1383
1413
|
return { content: [{ type: "text", text: `${skill.body}\n\nLoaded as global skill "${id}".` }] };
|
|
1384
1414
|
}
|
|
1385
1415
|
|
|
1416
|
+
if (name === "save_memory") {
|
|
1417
|
+
const a = (args || {}) as { name?: string; body?: string; description?: string };
|
|
1418
|
+
if (!a.name || !a.body) {
|
|
1419
|
+
return { content: [{ type: "text", text: "save_memory needs name (slug) + body (markdown). Optional description (one-line index hook)." }] };
|
|
1420
|
+
}
|
|
1421
|
+
try {
|
|
1422
|
+
const r = await fetch(`${REST_URL}/api/memory/${encodeURIComponent(a.name)}`, {
|
|
1423
|
+
method: "PUT",
|
|
1424
|
+
headers: { "Authorization": `Bearer ${TOKEN}`, "Content-Type": "application/json" },
|
|
1425
|
+
body: JSON.stringify({ body_markdown: a.body, description: a.description }),
|
|
1426
|
+
});
|
|
1427
|
+
const text = await r.text();
|
|
1428
|
+
if (!r.ok) return { content: [{ type: "text", text: `save_memory failed (${r.status}): ${text.slice(0, 240)}` }] };
|
|
1429
|
+
const resp = JSON.parse(text);
|
|
1430
|
+
return { content: [{ type: "text", text: `Saved memory "${resp.name}" (v${resp.version}, ${resp.bytes}B) under your agent_id. Restore later: load_memory (index) → load_memory("${resp.name}").` }] };
|
|
1431
|
+
} catch (e: any) {
|
|
1432
|
+
return { content: [{ type: "text", text: `save_memory network error: ${String(e?.message || e).slice(0, 120)}` }] };
|
|
1433
|
+
}
|
|
1434
|
+
}
|
|
1435
|
+
|
|
1436
|
+
if (name === "load_memory") {
|
|
1437
|
+
const a = (args || {}) as { name?: string };
|
|
1438
|
+
try {
|
|
1439
|
+
const path = a.name ? `/api/memory/${encodeURIComponent(a.name)}` : `/api/memory`;
|
|
1440
|
+
const r = await fetch(`${REST_URL}${path}`, { headers: { "Authorization": `Bearer ${TOKEN}` } });
|
|
1441
|
+
const text = await r.text();
|
|
1442
|
+
if (!r.ok) return { content: [{ type: "text", text: `load_memory failed (${r.status}): ${text.slice(0, 240)}` }] };
|
|
1443
|
+
const resp = JSON.parse(text);
|
|
1444
|
+
if (a.name) {
|
|
1445
|
+
return { content: [{ type: "text", text: `# memory: ${resp.name} (v${resp.version})\n\n${resp.body_markdown || ""}` }] };
|
|
1446
|
+
}
|
|
1447
|
+
const items = Array.isArray(resp.memories) ? resp.memories : [];
|
|
1448
|
+
if (items.length === 0) return { content: [{ type: "text", text: "No stored memory yet. Use save_memory to persist your context (e.g. an 'index' doc + finer docs)." }] };
|
|
1449
|
+
const idx = items.map((m: any) => `- ${m.name}${m.description ? ` — ${m.description}` : ""}`).join("\n");
|
|
1450
|
+
return { content: [{ type: "text", text: `Your memory index (${items.length} docs). Load one in full with load_memory("<name>"):\n\n${idx}` }] };
|
|
1451
|
+
} catch (e: any) {
|
|
1452
|
+
return { content: [{ type: "text", text: `load_memory network error: ${String(e?.message || e).slice(0, 120)}` }] };
|
|
1453
|
+
}
|
|
1454
|
+
}
|
|
1455
|
+
|
|
1386
1456
|
if (name === "save_skill") {
|
|
1387
1457
|
const a = (args || {}) as { chat_id?: string; name?: string; description?: string; body?: string; doc_id?: string; level?: number };
|
|
1388
|
-
if (!a.
|
|
1389
|
-
return { content: [{ type: "text", text: "save_skill needs
|
|
1458
|
+
if (!a.name || !a.description) {
|
|
1459
|
+
return { content: [{ type: "text", text: "save_skill needs name + description (body is the skill markdown). Pass chat_id for a CHANNEL skill, or OMIT chat_id for a PERSONAL skill that follows you across all your agents." }] };
|
|
1390
1460
|
}
|
|
1391
|
-
// Stable doc id: caller-supplied, else a slug of the name.
|
|
1392
|
-
const slug = String(a.name).toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 48) || "skill";
|
|
1393
|
-
const docId = (a.doc_id && String(a.doc_id).trim()) || `skill-${slug}`;
|
|
1394
1461
|
// Skill markdown = YAML frontmatter (name, description) + body — the exact
|
|
1395
|
-
// shape parseSkillFrontmatter + the
|
|
1462
|
+
// shape parseSkillFrontmatter + the skill validators expect.
|
|
1396
1463
|
const oneLine = (s: string) => String(s).replace(/\r?\n/g, " ").slice(0, 480);
|
|
1397
1464
|
const md = `---\nname: ${oneLine(a.name)}\ndescription: ${oneLine(a.description)}\n---\n\n${a.body || ""}`;
|
|
1465
|
+
// PERSONAL skill (no chat_id): per-owner store, shared across YOUR agents.
|
|
1466
|
+
if (!a.chat_id) {
|
|
1467
|
+
const pslug = String(a.doc_id || a.name).toLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/^[^a-z0-9]+/, "").replace(/[^a-z0-9]+$/, "").slice(0, 64) || "skill";
|
|
1468
|
+
try {
|
|
1469
|
+
const r = await fetch(`${REST_URL}/api/skills/${encodeURIComponent(pslug)}`, {
|
|
1470
|
+
method: "PUT",
|
|
1471
|
+
headers: { "Authorization": `Bearer ${TOKEN}`, "Content-Type": "application/json" },
|
|
1472
|
+
body: JSON.stringify({ body_markdown: md }),
|
|
1473
|
+
});
|
|
1474
|
+
const text = await r.text();
|
|
1475
|
+
if (!r.ok) return { content: [{ type: "text", text: `save_skill (personal) failed (${r.status}): ${text.slice(0, 240)}` }] };
|
|
1476
|
+
const resp = JSON.parse(text);
|
|
1477
|
+
return { content: [{ type: "text", text: `Saved PERSONAL skill "${a.name}" as "${pslug}" (v${resp.version}) — shared across all your agents. Pull/refresh: sync_skill(name="${pslug}"); list: list_skills.` }] };
|
|
1478
|
+
} catch (e: any) {
|
|
1479
|
+
return { content: [{ type: "text", text: `save_skill (personal) network error: ${String(e?.message || e).slice(0, 120)}` }] };
|
|
1480
|
+
}
|
|
1481
|
+
}
|
|
1482
|
+
// CHANNEL skill (chat_id given). Stable doc id: caller-supplied, else slug of name.
|
|
1483
|
+
const slug = String(a.name).toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 48) || "skill";
|
|
1484
|
+
const docId = (a.doc_id && String(a.doc_id).trim()) || `skill-${slug}`;
|
|
1398
1485
|
const level = (typeof a.level === "number" && a.level >= 1 && a.level <= 4) ? a.level : 3; // 3 = member-writable
|
|
1399
1486
|
try {
|
|
1400
1487
|
// If-Match: fetch current version (0 = create). The doc store uses
|
|
@@ -1422,13 +1509,40 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1422
1509
|
}
|
|
1423
1510
|
|
|
1424
1511
|
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
|
-
}
|
|
1512
|
+
const a = (args || {}) as { chat_id?: string; doc_id?: string; name?: string; dir?: string };
|
|
1429
1513
|
const home = process.env.HOME || process.env.USERPROFILE || ".";
|
|
1430
1514
|
const cacheDir = (a.dir && String(a.dir).trim()) || `${home}/.agentchat/skills`;
|
|
1431
1515
|
const safe = (s: string) => String(s).replace(/[^A-Za-z0-9_.-]/g, "_");
|
|
1516
|
+
// PERSONAL skill: sync_skill({name}) — version from GET /api/skills (cheap,
|
|
1517
|
+
// no body), body from GET /api/skills/:name only when missing/stale.
|
|
1518
|
+
if (a.name && !a.chat_id) {
|
|
1519
|
+
const pBase = `${cacheDir}/personal__${safe(a.name)}`;
|
|
1520
|
+
const pMd = `${pBase}.md`; const pMeta = `${pBase}.json`;
|
|
1521
|
+
try {
|
|
1522
|
+
const listR = await fetch(`${REST_URL}/api/skills`, { headers: { "Authorization": `Bearer ${TOKEN}` } });
|
|
1523
|
+
if (!listR.ok) return { content: [{ type: "text", text: `sync_skill (personal): list failed (${listR.status})` }] };
|
|
1524
|
+
const meta = (JSON.parse(await listR.text()).skills || []).find((s: any) => s.name === a.name);
|
|
1525
|
+
if (!meta) return { content: [{ type: "text", text: `sync_skill: personal skill "${a.name}" not found (save it with save_skill — no chat_id).` }] };
|
|
1526
|
+
const currentVersion = Number(meta.version ?? 0);
|
|
1527
|
+
let cachedVersion: number | null = null;
|
|
1528
|
+
try { cachedVersion = Number(JSON.parse(await Bun.file(pMeta).text()).version); } catch {}
|
|
1529
|
+
if (cachedVersion !== null && cachedVersion === currentVersion) {
|
|
1530
|
+
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.` }] };
|
|
1531
|
+
}
|
|
1532
|
+
const bodyR = await fetch(`${REST_URL}/api/skills/${encodeURIComponent(a.name)}`, { headers: { "Authorization": `Bearer ${TOKEN}` } });
|
|
1533
|
+
if (!bodyR.ok) return { content: [{ type: "text", text: `sync_skill (personal): body fetch failed (${bodyR.status})` }] };
|
|
1534
|
+
const doc = JSON.parse(await bodyR.text());
|
|
1535
|
+
await Bun.write(pMd, String(doc?.body_markdown ?? ""));
|
|
1536
|
+
await Bun.write(pMeta, JSON.stringify({ version: currentVersion, name: a.name, syncedAt: new Date().toISOString() }));
|
|
1537
|
+
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.` }] };
|
|
1538
|
+
} catch (e: any) {
|
|
1539
|
+
return { content: [{ type: "text", text: `sync_skill (personal) error: ${String(e?.message || e).slice(0, 140)}` }] };
|
|
1540
|
+
}
|
|
1541
|
+
}
|
|
1542
|
+
// CHANNEL skill: chat_id + doc_id.
|
|
1543
|
+
if (!a.chat_id || !a.doc_id) {
|
|
1544
|
+
return { content: [{ type: "text", text: "sync_skill needs (chat_id + doc_id) for a CHANNEL skill, or (name) for a PERSONAL skill." }] };
|
|
1545
|
+
}
|
|
1432
1546
|
const base = `${cacheDir}/${safe(a.chat_id)}__${safe(a.doc_id)}`;
|
|
1433
1547
|
const mdPath = `${base}.md`;
|
|
1434
1548
|
const metaPath = `${base}.json`;
|
|
@@ -2085,7 +2199,10 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
2085
2199
|
const data = await r.json() as any;
|
|
2086
2200
|
const channels = (data.channels || []);
|
|
2087
2201
|
if (channels.length === 0) return { content: [{ type: "text", text: "No public channels found." }] };
|
|
2088
|
-
|
|
2202
|
+
// Show the FULL channel id — agents copy it straight into reply /
|
|
2203
|
+
// get_history / join_channel, all of which need the complete UUID.
|
|
2204
|
+
// A truncated 8-char prefix here makes every downstream call 404.
|
|
2205
|
+
const list = channels.map((ch: any) => `• ${ch.name || ch.id} (${ch.id}) — ${ch.member_count || "?"} members${ch.topic ? ` — ${ch.topic.slice(0, 60)}` : ""}`).join("\n");
|
|
2089
2206
|
return { content: [{ type: "text", text: `${channels.length} channels:\n${list}` }] };
|
|
2090
2207
|
}
|
|
2091
2208
|
return { content: [{ type: "text", text: `Failed to list channels (${r.status})` }] };
|