agentschat-mcp 0.21.0 → 0.23.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/package.json +1 -1
- package/src/server.ts +115 -11
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agentschat-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.23.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",
|
|
@@ -850,18 +851,31 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
850
851
|
},
|
|
851
852
|
{
|
|
852
853
|
name: "save_skill",
|
|
853
|
-
description: "Save/publish a reusable skill
|
|
854
|
+
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.",
|
|
854
855
|
inputSchema: {
|
|
855
856
|
type: "object" as const,
|
|
856
857
|
properties: {
|
|
857
|
-
chat_id: { type: "string", description: "Channel to save
|
|
858
|
+
chat_id: { type: "string", description: "Channel to save into (CHANNEL skill). OMIT for a PERSONAL skill (per-owner, follows you across agents)." },
|
|
858
859
|
name: { type: "string", description: "Skill name (short)" },
|
|
859
860
|
description: { type: "string", description: "One line: what it does / when to use it" },
|
|
860
861
|
body: { type: "string", description: "The skill content in markdown — the instructions an agent follows" },
|
|
861
|
-
doc_id: { type: "string", description: "Optional stable id (default: a slug of name). Reuse to update
|
|
862
|
-
level: { type: "number", description: "
|
|
862
|
+
doc_id: { type: "string", description: "Optional stable id/slug (default: a slug of name). Reuse to update." },
|
|
863
|
+
level: { type: "number", description: "CHANNEL only: doc tier 1-4 (default 3 = any member may write; 1-2 require channel admin)" },
|
|
864
|
+
},
|
|
865
|
+
required: ["name", "description"],
|
|
866
|
+
},
|
|
867
|
+
},
|
|
868
|
+
{
|
|
869
|
+
name: "sync_skill",
|
|
870
|
+
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
|
+
inputSchema: {
|
|
872
|
+
type: "object" as const,
|
|
873
|
+
properties: {
|
|
874
|
+
name: { type: "string", description: "PERSONAL skill name (per-owner). Use this OR chat_id+doc_id." },
|
|
875
|
+
chat_id: { type: "string", description: "CHANNEL skill's channel id (paired with doc_id)" },
|
|
876
|
+
doc_id: { type: "string", description: "CHANNEL skill's doc id (paired with chat_id)" },
|
|
877
|
+
dir: { type: "string", description: "Optional local dir to sync into (default ~/.agentchat/skills)" },
|
|
863
878
|
},
|
|
864
|
-
required: ["chat_id", "name", "description"],
|
|
865
879
|
},
|
|
866
880
|
},
|
|
867
881
|
{
|
|
@@ -1315,6 +1329,11 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1315
1329
|
out.channel_skills_error = `network/parse error: ${String(e?.message || e).slice(0, 120)}`;
|
|
1316
1330
|
}
|
|
1317
1331
|
}
|
|
1332
|
+
// Personal skills (per-owner, follow you across agents). sync_skill(name=…) / load via GET /api/skills/:name.
|
|
1333
|
+
try {
|
|
1334
|
+
const pr = await fetch(`${REST_URL}/api/skills`, { headers: { "Authorization": `Bearer ${TOKEN}` } });
|
|
1335
|
+
if (pr.ok) out.personal_skills = (JSON.parse(await pr.text()).skills) || [];
|
|
1336
|
+
} catch { /* best-effort */ }
|
|
1318
1337
|
return { content: [{ type: "text", text: JSON.stringify(out, null, 2) }] };
|
|
1319
1338
|
}
|
|
1320
1339
|
|
|
@@ -1371,16 +1390,33 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1371
1390
|
|
|
1372
1391
|
if (name === "save_skill") {
|
|
1373
1392
|
const a = (args || {}) as { chat_id?: string; name?: string; description?: string; body?: string; doc_id?: string; level?: number };
|
|
1374
|
-
if (!a.
|
|
1375
|
-
return { content: [{ type: "text", text: "save_skill needs
|
|
1393
|
+
if (!a.name || !a.description) {
|
|
1394
|
+
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." }] };
|
|
1376
1395
|
}
|
|
1377
|
-
// Stable doc id: caller-supplied, else a slug of the name.
|
|
1378
|
-
const slug = String(a.name).toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 48) || "skill";
|
|
1379
|
-
const docId = (a.doc_id && String(a.doc_id).trim()) || `skill-${slug}`;
|
|
1380
1396
|
// Skill markdown = YAML frontmatter (name, description) + body — the exact
|
|
1381
|
-
// shape parseSkillFrontmatter + the
|
|
1397
|
+
// shape parseSkillFrontmatter + the skill validators expect.
|
|
1382
1398
|
const oneLine = (s: string) => String(s).replace(/\r?\n/g, " ").slice(0, 480);
|
|
1383
1399
|
const md = `---\nname: ${oneLine(a.name)}\ndescription: ${oneLine(a.description)}\n---\n\n${a.body || ""}`;
|
|
1400
|
+
// PERSONAL skill (no chat_id): per-owner store, shared across YOUR agents.
|
|
1401
|
+
if (!a.chat_id) {
|
|
1402
|
+
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";
|
|
1403
|
+
try {
|
|
1404
|
+
const r = await fetch(`${REST_URL}/api/skills/${encodeURIComponent(pslug)}`, {
|
|
1405
|
+
method: "PUT",
|
|
1406
|
+
headers: { "Authorization": `Bearer ${TOKEN}`, "Content-Type": "application/json" },
|
|
1407
|
+
body: JSON.stringify({ body_markdown: md }),
|
|
1408
|
+
});
|
|
1409
|
+
const text = await r.text();
|
|
1410
|
+
if (!r.ok) return { content: [{ type: "text", text: `save_skill (personal) failed (${r.status}): ${text.slice(0, 240)}` }] };
|
|
1411
|
+
const resp = JSON.parse(text);
|
|
1412
|
+
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.` }] };
|
|
1413
|
+
} catch (e: any) {
|
|
1414
|
+
return { content: [{ type: "text", text: `save_skill (personal) network error: ${String(e?.message || e).slice(0, 120)}` }] };
|
|
1415
|
+
}
|
|
1416
|
+
}
|
|
1417
|
+
// CHANNEL skill (chat_id given). Stable doc id: caller-supplied, else slug of name.
|
|
1418
|
+
const slug = String(a.name).toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 48) || "skill";
|
|
1419
|
+
const docId = (a.doc_id && String(a.doc_id).trim()) || `skill-${slug}`;
|
|
1384
1420
|
const level = (typeof a.level === "number" && a.level >= 1 && a.level <= 4) ? a.level : 3; // 3 = member-writable
|
|
1385
1421
|
try {
|
|
1386
1422
|
// If-Match: fetch current version (0 = create). The doc store uses
|
|
@@ -1407,6 +1443,74 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1407
1443
|
}
|
|
1408
1444
|
}
|
|
1409
1445
|
|
|
1446
|
+
if (name === "sync_skill") {
|
|
1447
|
+
const a = (args || {}) as { chat_id?: string; doc_id?: string; name?: string; dir?: string };
|
|
1448
|
+
const home = process.env.HOME || process.env.USERPROFILE || ".";
|
|
1449
|
+
const cacheDir = (a.dir && String(a.dir).trim()) || `${home}/.agentchat/skills`;
|
|
1450
|
+
const safe = (s: string) => String(s).replace(/[^A-Za-z0-9_.-]/g, "_");
|
|
1451
|
+
// PERSONAL skill: sync_skill({name}) — version from GET /api/skills (cheap,
|
|
1452
|
+
// no body), body from GET /api/skills/:name only when missing/stale.
|
|
1453
|
+
if (a.name && !a.chat_id) {
|
|
1454
|
+
const pBase = `${cacheDir}/personal__${safe(a.name)}`;
|
|
1455
|
+
const pMd = `${pBase}.md`; const pMeta = `${pBase}.json`;
|
|
1456
|
+
try {
|
|
1457
|
+
const listR = await fetch(`${REST_URL}/api/skills`, { headers: { "Authorization": `Bearer ${TOKEN}` } });
|
|
1458
|
+
if (!listR.ok) return { content: [{ type: "text", text: `sync_skill (personal): list failed (${listR.status})` }] };
|
|
1459
|
+
const meta = (JSON.parse(await listR.text()).skills || []).find((s: any) => s.name === a.name);
|
|
1460
|
+
if (!meta) return { content: [{ type: "text", text: `sync_skill: personal skill "${a.name}" not found (save it with save_skill — no chat_id).` }] };
|
|
1461
|
+
const currentVersion = Number(meta.version ?? 0);
|
|
1462
|
+
let cachedVersion: number | null = null;
|
|
1463
|
+
try { cachedVersion = Number(JSON.parse(await Bun.file(pMeta).text()).version); } catch {}
|
|
1464
|
+
if (cachedVersion !== null && cachedVersion === currentVersion) {
|
|
1465
|
+
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.` }] };
|
|
1466
|
+
}
|
|
1467
|
+
const bodyR = await fetch(`${REST_URL}/api/skills/${encodeURIComponent(a.name)}`, { headers: { "Authorization": `Bearer ${TOKEN}` } });
|
|
1468
|
+
if (!bodyR.ok) return { content: [{ type: "text", text: `sync_skill (personal): body fetch failed (${bodyR.status})` }] };
|
|
1469
|
+
const doc = JSON.parse(await bodyR.text());
|
|
1470
|
+
await Bun.write(pMd, String(doc?.body_markdown ?? ""));
|
|
1471
|
+
await Bun.write(pMeta, JSON.stringify({ version: currentVersion, name: a.name, syncedAt: new Date().toISOString() }));
|
|
1472
|
+
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.` }] };
|
|
1473
|
+
} catch (e: any) {
|
|
1474
|
+
return { content: [{ type: "text", text: `sync_skill (personal) error: ${String(e?.message || e).slice(0, 140)}` }] };
|
|
1475
|
+
}
|
|
1476
|
+
}
|
|
1477
|
+
// CHANNEL skill: chat_id + doc_id.
|
|
1478
|
+
if (!a.chat_id || !a.doc_id) {
|
|
1479
|
+
return { content: [{ type: "text", text: "sync_skill needs (chat_id + doc_id) for a CHANNEL skill, or (name) for a PERSONAL skill." }] };
|
|
1480
|
+
}
|
|
1481
|
+
const base = `${cacheDir}/${safe(a.chat_id)}__${safe(a.doc_id)}`;
|
|
1482
|
+
const mdPath = `${base}.md`;
|
|
1483
|
+
const metaPath = `${base}.json`;
|
|
1484
|
+
try {
|
|
1485
|
+
// 1. Cheap version check: docs-list returns each doc's version WITHOUT the
|
|
1486
|
+
// body, so "is my local copy current?" costs one light call.
|
|
1487
|
+
const listR = await fetch(`${REST_URL}/api/channels/${encodeURIComponent(a.chat_id)}/docs`, { headers: { "Authorization": `Bearer ${TOKEN}` } });
|
|
1488
|
+
if (!listR.ok) return { content: [{ type: "text", text: `sync_skill: docs-list failed (${listR.status})` }] };
|
|
1489
|
+
const docs = extractChannelDocsPayload(JSON.parse(await listR.text()));
|
|
1490
|
+
const meta = docs.find((d: any) => (d?.id ?? d?.doc_id) === a.doc_id);
|
|
1491
|
+
if (!meta) return { content: [{ type: "text", text: `sync_skill: skill "${a.doc_id}" not found in channel ${a.chat_id}` }] };
|
|
1492
|
+
const currentVersion = Number(meta.version ?? 0);
|
|
1493
|
+
// 2. Local cache check — skip the body fetch if we already have this version.
|
|
1494
|
+
let cachedVersion: number | null = null;
|
|
1495
|
+
try { cachedVersion = Number(JSON.parse(await Bun.file(metaPath).text()).version); } catch {}
|
|
1496
|
+
if (cachedVersion !== null && cachedVersion === currentVersion) {
|
|
1497
|
+
return { content: [{ type: "text", text: `up-to-date: "${a.doc_id}" v${currentVersion} already at ${mdPath} — no download. Read that file to run it.` }] };
|
|
1498
|
+
}
|
|
1499
|
+
// 3. Missing/stale → fetch the body (the only expensive call, on the cold path).
|
|
1500
|
+
const docR = await fetch(`${REST_URL}/api/channels/${encodeURIComponent(a.chat_id)}/docs/${encodeURIComponent(a.doc_id)}`, { headers: { "Authorization": `Bearer ${TOKEN}` } });
|
|
1501
|
+
if (!docR.ok) return { content: [{ type: "text", text: `sync_skill: fetch body failed (${docR.status})` }] };
|
|
1502
|
+
const doc = JSON.parse(await docR.text());
|
|
1503
|
+
const body = String(doc?.body_markdown ?? doc?.bodyMarkdown ?? "");
|
|
1504
|
+
// 4. Write the local mirror + version sidecar.
|
|
1505
|
+
await Bun.write(mdPath, body);
|
|
1506
|
+
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() }));
|
|
1507
|
+
const was = cachedVersion === null ? "missing" : `stale v${cachedVersion}`;
|
|
1508
|
+
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.` }] };
|
|
1509
|
+
} catch (e: any) {
|
|
1510
|
+
return { content: [{ type: "text", text: `sync_skill error: ${String(e?.message || e).slice(0, 140)}` }] };
|
|
1511
|
+
}
|
|
1512
|
+
}
|
|
1513
|
+
|
|
1410
1514
|
if (name === "list_tool_groups") {
|
|
1411
1515
|
return {
|
|
1412
1516
|
content: [{
|