@mcptoolshop/claude-synergy 1.1.0 → 1.2.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/CHANGELOG.md +5 -0
- package/README.es.md +25 -22
- package/README.fr.md +7 -4
- package/README.hi.md +7 -4
- package/README.it.md +27 -24
- package/README.ja.md +7 -4
- package/README.md +11 -8
- package/README.pt-BR.md +7 -4
- package/README.zh.md +29 -26
- package/dist/{chunk-H3466JDH.js → chunk-CEIOLMDT.js} +197 -885
- package/dist/{chunk-HZEQG3WT.js → chunk-KFAQPOGV.js} +21 -142
- package/dist/chunk-MTW6UZBF.js +743 -0
- package/dist/chunk-MZLFGICO.js +133 -0
- package/dist/chunk-X25ZTSCJ.js +1150 -0
- package/dist/cli.js +22 -1156
- package/dist/embed-OCOZWLXF.js +10 -0
- package/dist/fetch-XS3IETWW.js +11 -0
- package/dist/{ingest-Z45YH7OX.js → ingest-D23NTE25.js} +2 -1
- package/dist/mcp-server.js +174 -2
- package/package.json +1 -1
package/dist/mcp-server.js
CHANGED
|
@@ -4,15 +4,18 @@ import {
|
|
|
4
4
|
compareVersions,
|
|
5
5
|
entityFrequency,
|
|
6
6
|
getChangesSince,
|
|
7
|
+
getSyncStatus,
|
|
7
8
|
getSynergy,
|
|
8
9
|
hybridSearch,
|
|
9
10
|
listProducts,
|
|
10
11
|
listSynergies,
|
|
11
12
|
lookupEntity,
|
|
12
|
-
openDb,
|
|
13
13
|
recentReleases,
|
|
14
14
|
searchChanges
|
|
15
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-MTW6UZBF.js";
|
|
16
|
+
import {
|
|
17
|
+
openDb
|
|
18
|
+
} from "./chunk-CEIOLMDT.js";
|
|
16
19
|
|
|
17
20
|
// src/mcp-server.ts
|
|
18
21
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
@@ -34,6 +37,7 @@ var { version: PKG_VERSION } = _require("../package.json");
|
|
|
34
37
|
var SYNERGY_NAME_RE = /^[a-z0-9_-]+$/i;
|
|
35
38
|
var DB_PATH = process.env.CLAUDE_SYNERGY_DB ?? process.argv[2] ?? join(process.cwd(), "data", "claude-synergy.db");
|
|
36
39
|
var SYNERGIES_DIR = process.env.CLAUDE_SYNERGY_SYNERGIES_DIR ?? resolveSynergiesDir();
|
|
40
|
+
var PRODUCTS_ROOT = process.env.CLAUDE_SYNERGY_PRODUCTS_ROOT ?? join(dirname(DB_PATH), "..", "products");
|
|
37
41
|
function resolveSynergiesDir() {
|
|
38
42
|
const dbDir = dirname(DB_PATH);
|
|
39
43
|
const candidates = [
|
|
@@ -195,6 +199,32 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
195
199
|
},
|
|
196
200
|
required: ["product", "from_version", "to_version"]
|
|
197
201
|
}
|
|
202
|
+
},
|
|
203
|
+
{
|
|
204
|
+
name: "sync_status",
|
|
205
|
+
description: "Per-product sync freshness. Returns one row per product with last-fetched timestamp, hours since last fetch, and ingested release count. Use BEFORE trusting latest_releases / search results to know if the corpus is stale, and BEFORE calling sync_now to know what needs refreshing.",
|
|
206
|
+
inputSchema: {
|
|
207
|
+
type: "object",
|
|
208
|
+
properties: {
|
|
209
|
+
product: { type: "string", description: "Limit to one product" },
|
|
210
|
+
stale_only: { type: "boolean", description: "Only return products older than stale_hours (or never fetched)", default: false },
|
|
211
|
+
stale_hours: { type: "number", description: "Threshold for stale_only (default 24)", default: 24 }
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
},
|
|
215
|
+
{
|
|
216
|
+
name: "sync_now",
|
|
217
|
+
description: "Refresh the corpus by running fetch \u2192 ingest \u2192 embed (mirrors `hk sync`). Pass dry_run=true to enumerate what would be fetched without writes. By default runs the full pipeline so new releases are immediately queryable via search/lookup_entity. Concurrency: rejected with InvalidParams if another sync_now is already in flight. Does NOT commit to git \u2014 caller decides.",
|
|
218
|
+
inputSchema: {
|
|
219
|
+
type: "object",
|
|
220
|
+
properties: {
|
|
221
|
+
product: { type: "string", description: "Limit to one product" },
|
|
222
|
+
dry_run: { type: "boolean", description: "Enumerate targets and report what would be fetched, no writes", default: false },
|
|
223
|
+
include_ingest: { type: "boolean", description: "Run ingest step after fetch (default true)", default: true },
|
|
224
|
+
include_embed: { type: "boolean", description: "Run embed step after ingest (requires Ollama; default true)", default: true },
|
|
225
|
+
timeout_ms: { type: "number", description: "Hard timeout for the entire sync (default 300000 = 5 min, max 600000)", default: 3e5 }
|
|
226
|
+
}
|
|
227
|
+
}
|
|
198
228
|
}
|
|
199
229
|
]
|
|
200
230
|
}));
|
|
@@ -243,6 +273,14 @@ function optInt(rec, field, tool, min = 1, max = 1e4) {
|
|
|
243
273
|
}
|
|
244
274
|
return v;
|
|
245
275
|
}
|
|
276
|
+
function optBool(rec, field, tool) {
|
|
277
|
+
const v = rec[field];
|
|
278
|
+
if (v === void 0 || v === null) return void 0;
|
|
279
|
+
if (typeof v !== "boolean") {
|
|
280
|
+
throw new McpError(ErrorCode.InvalidParams, `${tool}: ${field} must be a boolean`);
|
|
281
|
+
}
|
|
282
|
+
return v;
|
|
283
|
+
}
|
|
246
284
|
function optEnum(rec, field, allowed, tool) {
|
|
247
285
|
const v = rec[field];
|
|
248
286
|
if (v === void 0 || v === null) return void 0;
|
|
@@ -431,6 +469,32 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
431
469
|
]
|
|
432
470
|
};
|
|
433
471
|
}
|
|
472
|
+
case "sync_status": {
|
|
473
|
+
const r = asRecord(args ?? {}, "sync_status");
|
|
474
|
+
return {
|
|
475
|
+
content: [
|
|
476
|
+
{
|
|
477
|
+
type: "text",
|
|
478
|
+
text: handleSyncStatus({
|
|
479
|
+
product: optString(r, "product", "sync_status"),
|
|
480
|
+
staleOnly: optBool(r, "stale_only", "sync_status"),
|
|
481
|
+
staleHours: optInt(r, "stale_hours", "sync_status", 1, 1e5)
|
|
482
|
+
})
|
|
483
|
+
}
|
|
484
|
+
]
|
|
485
|
+
};
|
|
486
|
+
}
|
|
487
|
+
case "sync_now": {
|
|
488
|
+
const r = asRecord(args ?? {}, "sync_now");
|
|
489
|
+
const text = await handleSyncNow({
|
|
490
|
+
product: optString(r, "product", "sync_now"),
|
|
491
|
+
dryRun: optBool(r, "dry_run", "sync_now"),
|
|
492
|
+
includeIngest: optBool(r, "include_ingest", "sync_now"),
|
|
493
|
+
includeEmbed: optBool(r, "include_embed", "sync_now"),
|
|
494
|
+
timeoutMs: optInt(r, "timeout_ms", "sync_now", 1e3, 6e5)
|
|
495
|
+
});
|
|
496
|
+
return { content: [{ type: "text", text }] };
|
|
497
|
+
}
|
|
434
498
|
default:
|
|
435
499
|
throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${name}`);
|
|
436
500
|
}
|
|
@@ -701,6 +765,114 @@ function handleSearchBreakingChanges(args) {
|
|
|
701
765
|
lines.push(`${results.length} breaking change${results.length === 1 ? "" : "s"}`);
|
|
702
766
|
return lines.join("\n");
|
|
703
767
|
}
|
|
768
|
+
function formatHours(h) {
|
|
769
|
+
if (h == null) return "never";
|
|
770
|
+
if (h < 1) return `${Math.round(h * 60)}m`;
|
|
771
|
+
if (h < 48) return `${h.toFixed(1)}h`;
|
|
772
|
+
return `${Math.round(h / 24)}d`;
|
|
773
|
+
}
|
|
774
|
+
function handleSyncStatus(args) {
|
|
775
|
+
const rows = getSyncStatus(db, {
|
|
776
|
+
product: args.product,
|
|
777
|
+
staleOnly: args.staleOnly,
|
|
778
|
+
staleHours: args.staleHours
|
|
779
|
+
});
|
|
780
|
+
if (rows.length === 0) {
|
|
781
|
+
return args.staleOnly ? `(no stale products \u2014 threshold ${args.staleHours ?? 24}h)` : "(no products in DB \u2014 run `hk ingest` first)";
|
|
782
|
+
}
|
|
783
|
+
const lines = [
|
|
784
|
+
"Product Strategy Last fetch Hours Ingested Latest release",
|
|
785
|
+
"\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 \u2500\u2500\u2500\u2500\u2500 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"
|
|
786
|
+
];
|
|
787
|
+
for (const r of rows) {
|
|
788
|
+
const product = (r.product ?? "").padEnd(36);
|
|
789
|
+
const strategy = (r.fetch_strategy ?? "\u2014").padEnd(14);
|
|
790
|
+
const lastFetch = r.last_fetch_attempt ? r.last_fetch_attempt.split("T")[0] : "never ";
|
|
791
|
+
const hours = formatHours(r.hours_since_fetch).padStart(5);
|
|
792
|
+
const ingested = String(r.releases_ingested).padStart(8);
|
|
793
|
+
const latest = r.last_release_at ? r.last_release_at.split("T")[0] : "\u2014";
|
|
794
|
+
lines.push(`${product} ${strategy} ${lastFetch} ${hours} ${ingested} ${latest}`);
|
|
795
|
+
}
|
|
796
|
+
lines.push("");
|
|
797
|
+
lines.push(`${rows.length} product${rows.length === 1 ? "" : "s"}`);
|
|
798
|
+
return lines.join("\n");
|
|
799
|
+
}
|
|
800
|
+
var syncInProgress = false;
|
|
801
|
+
async function handleSyncNow(args) {
|
|
802
|
+
if (syncInProgress) {
|
|
803
|
+
throw new McpError(
|
|
804
|
+
ErrorCode.InvalidParams,
|
|
805
|
+
"sync_now: another sync is already in progress \u2014 retry after it completes (or call sync_status to see progress)"
|
|
806
|
+
);
|
|
807
|
+
}
|
|
808
|
+
const dryRun = args.dryRun ?? false;
|
|
809
|
+
const includeIngest = args.includeIngest ?? true;
|
|
810
|
+
const includeEmbed = args.includeEmbed ?? true;
|
|
811
|
+
const timeoutMs = args.timeoutMs ?? 3e5;
|
|
812
|
+
const { fetchAll, listFetchTargets } = await import("./fetch-XS3IETWW.js");
|
|
813
|
+
if (dryRun) {
|
|
814
|
+
const targets = args.product ? listFetchTargets().filter((t) => t.product === args.product) : listFetchTargets();
|
|
815
|
+
if (targets.length === 0) {
|
|
816
|
+
throw new McpError(ErrorCode.InvalidParams, `sync_now: unknown product: ${args.product}`);
|
|
817
|
+
}
|
|
818
|
+
const lines = [`(dry_run \u2014 would fetch ${targets.length} product${targets.length === 1 ? "" : "s"})`, ""];
|
|
819
|
+
for (const t of targets) {
|
|
820
|
+
const marker = db.prepare(`SELECT version FROM markers WHERE product = ? AND name = 'last_fetched_release_at'`).get(t.product);
|
|
821
|
+
const since = marker?.version ?? "2026-01-01 (no marker)";
|
|
822
|
+
lines.push(` ${t.product.padEnd(36)} ${t.strategy.padEnd(14)} since ${since}`);
|
|
823
|
+
}
|
|
824
|
+
return lines.join("\n");
|
|
825
|
+
}
|
|
826
|
+
syncInProgress = true;
|
|
827
|
+
const t0 = Date.now();
|
|
828
|
+
try {
|
|
829
|
+
const fetchStats = await fetchAll(db, PRODUCTS_ROOT, {
|
|
830
|
+
product: args.product,
|
|
831
|
+
timeoutMs
|
|
832
|
+
});
|
|
833
|
+
const fetched = fetchStats.summary;
|
|
834
|
+
let ingestSummary = "";
|
|
835
|
+
if (includeIngest) {
|
|
836
|
+
const { ingestAll } = await import("./ingest-D23NTE25.js");
|
|
837
|
+
const ingestStats = ingestAll(db, PRODUCTS_ROOT);
|
|
838
|
+
ingestSummary = `ingested: +${ingestStats.releasesAdded} releases, +${ingestStats.changesAdded} changes, +${ingestStats.entitiesAdded} entities`;
|
|
839
|
+
}
|
|
840
|
+
let embedSummary = "";
|
|
841
|
+
let embedError = "";
|
|
842
|
+
if (includeEmbed) {
|
|
843
|
+
try {
|
|
844
|
+
const { embedAll } = await import("./embed-OCOZWLXF.js");
|
|
845
|
+
const embedStats = await embedAll(db, {
|
|
846
|
+
contextProviderName: "structured",
|
|
847
|
+
embeddingProviderName: "ollama"
|
|
848
|
+
});
|
|
849
|
+
embedSummary = `embedded: +${embedStats.chunksCreated} chunks via ${embedStats.contextProvider} + ${embedStats.embeddingProvider}`;
|
|
850
|
+
} catch (e) {
|
|
851
|
+
embedError = `embed skipped: ${e.message ?? String(e)}`;
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
const elapsedMs = Date.now() - t0;
|
|
855
|
+
const lines = [
|
|
856
|
+
`sync_now complete in ${(elapsedMs / 1e3).toFixed(1)}s`,
|
|
857
|
+
"",
|
|
858
|
+
`fetched: ${fetched.succeeded}/${fetched.total} products ok, ${fetched.failed} failed, ${fetched.skipped} skipped, +${fetched.newChanges} new releases`
|
|
859
|
+
];
|
|
860
|
+
if (fetched.errors.length > 0) {
|
|
861
|
+
lines.push("");
|
|
862
|
+
lines.push("fetch errors:");
|
|
863
|
+
for (const e of fetched.errors.slice(0, 10)) {
|
|
864
|
+
lines.push(` ${e.product}: ${e.error}`);
|
|
865
|
+
}
|
|
866
|
+
if (fetched.errors.length > 10) lines.push(` \u2026 ${fetched.errors.length - 10} more`);
|
|
867
|
+
}
|
|
868
|
+
if (ingestSummary) lines.push(ingestSummary);
|
|
869
|
+
if (embedSummary) lines.push(embedSummary);
|
|
870
|
+
if (embedError) lines.push(embedError);
|
|
871
|
+
return lines.join("\n");
|
|
872
|
+
} finally {
|
|
873
|
+
syncInProgress = false;
|
|
874
|
+
}
|
|
875
|
+
}
|
|
704
876
|
function handleCompareVersions(args) {
|
|
705
877
|
let results;
|
|
706
878
|
try {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mcptoolshop/claude-synergy",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "Local mirror of Anthropic product changelogs + curated cross-product synergies. So the LLM agent inside the harness knows what the harness can do.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|