@datasynx/agentic-ai-cartography 2.7.0 → 2.8.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/dist/{chunk-HLWNO3RF.js → chunk-5D5ZZEZM.js} +174 -15
- package/dist/chunk-5D5ZZEZM.js.map +1 -0
- package/dist/cli.js +15 -3
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +159 -12
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +99 -10
- package/dist/index.d.ts +99 -10
- package/dist/index.js +157 -12
- package/dist/index.js.map +1 -1
- package/dist/mcp-bin.js +1 -1
- package/package.json +3 -2
- package/server.json +2 -2
- package/dist/chunk-HLWNO3RF.js.map +0 -1
|
@@ -1539,7 +1539,7 @@ async function executeNlQuery(db, sessionId, search, intent, opts = {}) {
|
|
|
1539
1539
|
|
|
1540
1540
|
// src/mcp/server.ts
|
|
1541
1541
|
var SERVER_NAME = "cartography";
|
|
1542
|
-
var SERVER_VERSION = "2.
|
|
1542
|
+
var SERVER_VERSION = "2.8.0";
|
|
1543
1543
|
var SERVICE_TYPES = NODE_TYPE_GROUPS.web;
|
|
1544
1544
|
var DATA_TYPES = NODE_TYPE_GROUPS.data;
|
|
1545
1545
|
var lexicalSearch = async (db, sessionId, query, opts) => db.searchNodes(sessionId, query, { types: opts.types, limit: opts.limit }).map((node) => ({ node }));
|
|
@@ -1614,9 +1614,10 @@ function createMcpServer(opts = {}) {
|
|
|
1614
1614
|
"graph-summary",
|
|
1615
1615
|
"cartography://graph/summary",
|
|
1616
1616
|
{ title: "Topology summary", description: "Low-token aggregate index of the whole landscape \u2014 read this first.", mimeType: "text/markdown" },
|
|
1617
|
-
(uri) => {
|
|
1617
|
+
async (uri) => {
|
|
1618
1618
|
if (org !== void 0) {
|
|
1619
|
-
|
|
1619
|
+
const s = opts.orgSummary ? await opts.orgSummary(org) : db.getOrgSummary(org);
|
|
1620
|
+
return { contents: [{ uri: uri.href, mimeType: "text/markdown", text: summaryText(s) }] };
|
|
1620
1621
|
}
|
|
1621
1622
|
const sid = resolveSession();
|
|
1622
1623
|
if (!sid) return { contents: [{ uri: uri.href, mimeType: "text/markdown", text: "No discovery session found. Run discovery first." }] };
|
|
@@ -1691,8 +1692,8 @@ function createMcpServer(opts = {}) {
|
|
|
1691
1692
|
server.registerTool(
|
|
1692
1693
|
"get_summary",
|
|
1693
1694
|
{ title: "Get topology summary", description: "Low-token overview of the whole landscape (counts, types, domains, most-connected, anomalies).", inputSchema: {}, annotations: readOnly },
|
|
1694
|
-
() => {
|
|
1695
|
-
if (org !== void 0) return json(db.getOrgSummary(org));
|
|
1695
|
+
async () => {
|
|
1696
|
+
if (org !== void 0) return json(opts.orgSummary ? await opts.orgSummary(org) : db.getOrgSummary(org));
|
|
1696
1697
|
const sid = resolveSession();
|
|
1697
1698
|
if (!sid) return json({ error: "No discovery session found." });
|
|
1698
1699
|
return json(db.getGraphSummary(sid));
|
|
@@ -2143,7 +2144,7 @@ async function runHttp(factory, opts = {}) {
|
|
|
2143
2144
|
res.writeHead(413, { "content-type": "application/json" }).end('{"error":"payload too large"}');
|
|
2144
2145
|
return;
|
|
2145
2146
|
}
|
|
2146
|
-
const out = onIngest(value);
|
|
2147
|
+
const out = await onIngest(value);
|
|
2147
2148
|
res.writeHead(out.status, { "content-type": "application/json", ...out.headers ?? {} }).end(JSON.stringify(out.body));
|
|
2148
2149
|
return;
|
|
2149
2150
|
}
|
|
@@ -2382,6 +2383,148 @@ var SqliteStoreBackend = class {
|
|
|
2382
2383
|
}
|
|
2383
2384
|
};
|
|
2384
2385
|
|
|
2386
|
+
// src/store/graph.ts
|
|
2387
|
+
function toNum(v) {
|
|
2388
|
+
if (typeof v === "number") return v;
|
|
2389
|
+
if (v && typeof v === "object" && "toNumber" in v && typeof v.toNumber === "function") {
|
|
2390
|
+
return v.toNumber();
|
|
2391
|
+
}
|
|
2392
|
+
return Number(v ?? 0);
|
|
2393
|
+
}
|
|
2394
|
+
var GraphStoreBackend = class {
|
|
2395
|
+
constructor(driver) {
|
|
2396
|
+
this.driver = driver;
|
|
2397
|
+
}
|
|
2398
|
+
async run(cypher, params) {
|
|
2399
|
+
const session = this.driver.session();
|
|
2400
|
+
try {
|
|
2401
|
+
return await session.run(cypher, params);
|
|
2402
|
+
} finally {
|
|
2403
|
+
await session.close();
|
|
2404
|
+
}
|
|
2405
|
+
}
|
|
2406
|
+
async upsertNode(org, node, identity, contributor) {
|
|
2407
|
+
const res = await this.run(
|
|
2408
|
+
`MERGE (n:Node {org: $org, globalId: $globalId})
|
|
2409
|
+
ON CREATE SET n._created = true
|
|
2410
|
+
ON MATCH SET n._created = false
|
|
2411
|
+
SET n.id = $id, n.contentHash = $contentHash, n.type = $type, n.name = $name,
|
|
2412
|
+
n.domain = $domain, n.owner = $owner,
|
|
2413
|
+
n.confidence = CASE WHEN n.confidence IS NULL OR $confidence > n.confidence THEN $confidence ELSE n.confidence END
|
|
2414
|
+
MERGE (c:Contributor {org: $org, globalId: $globalId, machineId: $machineId})
|
|
2415
|
+
SET c.hostname = $hostname, c.user = $user, c.at = $at,
|
|
2416
|
+
c.confidence = CASE WHEN c.confidence IS NULL OR $contribConfidence > c.confidence THEN $contribConfidence ELSE c.confidence END
|
|
2417
|
+
RETURN n._created AS created`,
|
|
2418
|
+
{
|
|
2419
|
+
org,
|
|
2420
|
+
globalId: identity.globalId,
|
|
2421
|
+
contentHash: identity.contentHash,
|
|
2422
|
+
id: node.id,
|
|
2423
|
+
type: node.type,
|
|
2424
|
+
name: node.name,
|
|
2425
|
+
domain: node.domain ?? null,
|
|
2426
|
+
owner: node.owner ?? null,
|
|
2427
|
+
confidence: node.confidence,
|
|
2428
|
+
machineId: contributor.machineId,
|
|
2429
|
+
hostname: contributor.hostname,
|
|
2430
|
+
user: contributor.user,
|
|
2431
|
+
at: contributor.at,
|
|
2432
|
+
contribConfidence: contributor.confidence
|
|
2433
|
+
}
|
|
2434
|
+
);
|
|
2435
|
+
return res.records[0]?.get("created") === true ? "created" : "merged";
|
|
2436
|
+
}
|
|
2437
|
+
async insertEdge(org, edge) {
|
|
2438
|
+
await this.run(
|
|
2439
|
+
`MATCH (s:Node {org: $org, id: $source})
|
|
2440
|
+
MATCH (t:Node {org: $org, id: $target})
|
|
2441
|
+
MERGE (s)-[r:DEPENDS {relationship: $rel}]->(t)
|
|
2442
|
+
SET r.evidence = $evidence, r.confidence = $confidence`,
|
|
2443
|
+
{ org, source: edge.sourceId, target: edge.targetId, rel: edge.relationship, evidence: edge.evidence, confidence: edge.confidence }
|
|
2444
|
+
);
|
|
2445
|
+
}
|
|
2446
|
+
async getSummary(org) {
|
|
2447
|
+
const totals = await this.run(
|
|
2448
|
+
`MATCH (n:Node {org: $org})
|
|
2449
|
+
OPTIONAL MATCH (n)-[r:DEPENDS]->(:Node {org: $org})
|
|
2450
|
+
RETURN count(DISTINCT n) AS nodes, count(r) AS edges`,
|
|
2451
|
+
{ org }
|
|
2452
|
+
);
|
|
2453
|
+
const byType = await this.run(`MATCH (n:Node {org: $org}) RETURN n.type AS k, count(*) AS c`, { org });
|
|
2454
|
+
const byDomain = await this.run(`MATCH (n:Node {org: $org}) RETURN coalesce(n.domain, '(none)') AS k, count(*) AS c`, { org });
|
|
2455
|
+
const byRel = await this.run(`MATCH (:Node {org: $org})-[r:DEPENDS]->(:Node {org: $org}) RETURN r.relationship AS k, count(*) AS c`, { org });
|
|
2456
|
+
const top = await this.run(
|
|
2457
|
+
`MATCH (n:Node {org: $org})
|
|
2458
|
+
OPTIONAL MATCH (n)-[r:DEPENDS]-(:Node {org: $org})
|
|
2459
|
+
RETURN n.id AS id, n.name AS name, n.type AS type, count(r) AS degree
|
|
2460
|
+
ORDER BY degree DESC, id ASC LIMIT 10`,
|
|
2461
|
+
{ org }
|
|
2462
|
+
);
|
|
2463
|
+
const contrib = await this.run(`MATCH (c:Contributor {org: $org}) RETURN count(DISTINCT c.machineId) AS contributors`, { org });
|
|
2464
|
+
const counts = (r) => {
|
|
2465
|
+
const out = {};
|
|
2466
|
+
for (const rec of r.records) out[String(rec.get("k"))] = toNum(rec.get("c"));
|
|
2467
|
+
return out;
|
|
2468
|
+
};
|
|
2469
|
+
return {
|
|
2470
|
+
org,
|
|
2471
|
+
totals: { nodes: toNum(totals.records[0]?.get("nodes")), edges: toNum(totals.records[0]?.get("edges")) },
|
|
2472
|
+
nodesByType: counts(byType),
|
|
2473
|
+
nodesByDomain: counts(byDomain),
|
|
2474
|
+
edgesByRelationship: counts(byRel),
|
|
2475
|
+
topConnected: top.records.map((rec) => ({
|
|
2476
|
+
id: String(rec.get("id")),
|
|
2477
|
+
name: String(rec.get("name")),
|
|
2478
|
+
type: String(rec.get("type")),
|
|
2479
|
+
degree: toNum(rec.get("degree"))
|
|
2480
|
+
})),
|
|
2481
|
+
contributors: toNum(contrib.records[0]?.get("contributors"))
|
|
2482
|
+
};
|
|
2483
|
+
}
|
|
2484
|
+
async getContributors(globalId2) {
|
|
2485
|
+
const res = await this.run(
|
|
2486
|
+
`MATCH (c:Contributor {globalId: $globalId})
|
|
2487
|
+
RETURN c.machineId AS machineId, c.hostname AS hostname, c.user AS user, c.org AS org, c.at AS at, c.confidence AS confidence`,
|
|
2488
|
+
{ globalId: globalId2 }
|
|
2489
|
+
);
|
|
2490
|
+
return res.records.map((rec) => ({
|
|
2491
|
+
machineId: String(rec.get("machineId")),
|
|
2492
|
+
hostname: String(rec.get("hostname")),
|
|
2493
|
+
user: String(rec.get("user")),
|
|
2494
|
+
organization: rec.get("org") != null ? String(rec.get("org")) : void 0,
|
|
2495
|
+
at: String(rec.get("at")),
|
|
2496
|
+
confidence: toNum(rec.get("confidence"))
|
|
2497
|
+
}));
|
|
2498
|
+
}
|
|
2499
|
+
async close() {
|
|
2500
|
+
await this.driver.close();
|
|
2501
|
+
}
|
|
2502
|
+
};
|
|
2503
|
+
|
|
2504
|
+
// src/store/index.ts
|
|
2505
|
+
async function defaultNeo4jDriver(url, user, password) {
|
|
2506
|
+
const mod = await import("neo4j-driver");
|
|
2507
|
+
return mod.default.driver(url, mod.default.auth.basic(user, password));
|
|
2508
|
+
}
|
|
2509
|
+
async function openStoreBackend(db, opts = {}) {
|
|
2510
|
+
if (opts.backend === "graph" && opts.graphUrl) {
|
|
2511
|
+
try {
|
|
2512
|
+
const make = opts.driverFactory ?? defaultNeo4jDriver;
|
|
2513
|
+
const driver = await make(opts.graphUrl, opts.graphUser ?? "neo4j", opts.graphPassword ?? "");
|
|
2514
|
+
if (driver.verifyConnectivity) await driver.verifyConnectivity();
|
|
2515
|
+
logInfo("central store: graph backend active", { host: stripSensitive(opts.graphUrl) });
|
|
2516
|
+
return new GraphStoreBackend(driver);
|
|
2517
|
+
} catch (err) {
|
|
2518
|
+
logWarn("central store: graph backend unavailable \u2014 falling back to SQLite", {
|
|
2519
|
+
host: stripSensitive(opts.graphUrl),
|
|
2520
|
+
reason: err instanceof Error ? err.message : String(err)
|
|
2521
|
+
});
|
|
2522
|
+
return new SqliteStoreBackend(db);
|
|
2523
|
+
}
|
|
2524
|
+
}
|
|
2525
|
+
return new SqliteStoreBackend(db);
|
|
2526
|
+
}
|
|
2527
|
+
|
|
2385
2528
|
// src/central/ingest.ts
|
|
2386
2529
|
import { z as z3 } from "zod";
|
|
2387
2530
|
|
|
@@ -2482,9 +2625,9 @@ var IngestEnvelopeSchema = z3.object({
|
|
|
2482
2625
|
contributor: ContributorSchema.optional(),
|
|
2483
2626
|
anonymizationLevel: z3.enum(["none", "anonymized", "full"]).optional()
|
|
2484
2627
|
});
|
|
2485
|
-
function ingestEnvelope(store, envelope, opts = {}) {
|
|
2628
|
+
async function ingestEnvelope(store, envelope, opts = {}) {
|
|
2486
2629
|
const anonMode = opts.anonMode ?? "reject";
|
|
2487
|
-
const org = envelope.org ?? opts.defaultOrg
|
|
2630
|
+
const org = normalizeTenant(envelope.org ?? opts.defaultOrg);
|
|
2488
2631
|
const level = envelope.anonymizationLevel ?? "anonymized";
|
|
2489
2632
|
const at = (/* @__PURE__ */ new Date()).toISOString();
|
|
2490
2633
|
const contributor = {
|
|
@@ -2535,7 +2678,7 @@ function ingestEnvelope(store, envelope, opts = {}) {
|
|
|
2535
2678
|
}
|
|
2536
2679
|
const safe = check.node;
|
|
2537
2680
|
const identity = computeIdentity(org, safe);
|
|
2538
|
-
const outcome = store.upsertNode(org, safe, identity, { ...contributor, confidence: safe.confidence });
|
|
2681
|
+
const outcome = await store.upsertNode(org, safe, identity, { ...contributor, confidence: safe.confidence });
|
|
2539
2682
|
accepted += 1;
|
|
2540
2683
|
if (outcome === "merged") merged += 1;
|
|
2541
2684
|
acceptedNodeIds.add(safe.id);
|
|
@@ -2551,7 +2694,7 @@ function ingestEnvelope(store, envelope, opts = {}) {
|
|
|
2551
2694
|
if (acceptedNodeIds.size > 0 && (!acceptedNodeIds.has(edge.sourceId) || !acceptedNodeIds.has(edge.targetId))) {
|
|
2552
2695
|
continue;
|
|
2553
2696
|
}
|
|
2554
|
-
store.insertEdge(org, edge);
|
|
2697
|
+
await store.insertEdge(org, edge);
|
|
2555
2698
|
edges += 1;
|
|
2556
2699
|
}
|
|
2557
2700
|
logInfo("ingest", { org, accepted, merged, rejected, edges, violations, level, anonMode });
|
|
@@ -2561,7 +2704,7 @@ function ingestEnvelope(store, envelope, opts = {}) {
|
|
|
2561
2704
|
// src/central/server.ts
|
|
2562
2705
|
function createIngestHandler(store, opts = {}) {
|
|
2563
2706
|
const quota = opts.quota;
|
|
2564
|
-
return (body) => {
|
|
2707
|
+
return async (body) => {
|
|
2565
2708
|
const parsed = IngestEnvelopeSchema.safeParse(body);
|
|
2566
2709
|
if (!parsed.success) {
|
|
2567
2710
|
const issues = parsed.error.issues.map((i) => `${i.path.join(".") || "(root)"}: ${i.message}`);
|
|
@@ -2577,7 +2720,7 @@ function createIngestHandler(store, opts = {}) {
|
|
|
2577
2720
|
}
|
|
2578
2721
|
}
|
|
2579
2722
|
try {
|
|
2580
|
-
const result = ingestEnvelope(store, parsed.data, opts);
|
|
2723
|
+
const result = await ingestEnvelope(store, parsed.data, opts);
|
|
2581
2724
|
return { status: 200, body: result };
|
|
2582
2725
|
} catch (err) {
|
|
2583
2726
|
logWarn("ingest: failed", { error: err instanceof Error ? err.message : String(err) });
|
|
@@ -2658,6 +2801,7 @@ async function startMcp(opts = {}) {
|
|
|
2658
2801
|
const serverMode = opts.serverMode === true;
|
|
2659
2802
|
const authStore = new SqliteCredentialStore(db);
|
|
2660
2803
|
const rbacActive = authStore.count() > 0;
|
|
2804
|
+
let resolvedStore;
|
|
2661
2805
|
const factory = (principal) => {
|
|
2662
2806
|
const scopeTenant = rbacActive && principal ? principal.tenant : tenant;
|
|
2663
2807
|
const orgArg = serverMode ? scopeTenant : void 0;
|
|
@@ -2668,7 +2812,8 @@ async function startMcp(opts = {}) {
|
|
|
2668
2812
|
search,
|
|
2669
2813
|
discovery,
|
|
2670
2814
|
...principal ? { principal } : {},
|
|
2671
|
-
...orgArg !== void 0 ? { org: orgArg } : {}
|
|
2815
|
+
...orgArg !== void 0 ? { org: orgArg } : {},
|
|
2816
|
+
...resolvedStore && orgArg !== void 0 ? { orgSummary: (o) => resolvedStore.getSummary(o) } : {}
|
|
2672
2817
|
});
|
|
2673
2818
|
};
|
|
2674
2819
|
const transport = serverMode ? "http" : opts.transport;
|
|
@@ -2678,7 +2823,13 @@ async function startMcp(opts = {}) {
|
|
|
2678
2823
|
const token = opts.token ?? process.env["CARTOGRAPHY_HTTP_TOKEN"] ?? process.env["CARTOGRAPHY_CENTRAL_TOKEN"];
|
|
2679
2824
|
let onIngest;
|
|
2680
2825
|
if (serverMode) {
|
|
2681
|
-
const store =
|
|
2826
|
+
const store = await openStoreBackend(db, {
|
|
2827
|
+
backend: opts.storeBackend ?? (process.env["CARTOGRAPHY_GRAPH_URL"] ? "graph" : "sqlite"),
|
|
2828
|
+
...opts.graphUrl ?? process.env["CARTOGRAPHY_GRAPH_URL"] ? { graphUrl: opts.graphUrl ?? process.env["CARTOGRAPHY_GRAPH_URL"] } : {},
|
|
2829
|
+
...opts.graphUser ?? process.env["CARTOGRAPHY_GRAPH_USER"] ? { graphUser: opts.graphUser ?? process.env["CARTOGRAPHY_GRAPH_USER"] } : {},
|
|
2830
|
+
...opts.graphPassword ?? process.env["CARTOGRAPHY_GRAPH_PASSWORD"] ? { graphPassword: opts.graphPassword ?? process.env["CARTOGRAPHY_GRAPH_PASSWORD"] } : {}
|
|
2831
|
+
});
|
|
2832
|
+
resolvedStore = store;
|
|
2682
2833
|
const anonMode = opts.anonMode ?? "reject";
|
|
2683
2834
|
const quota = new RateLimiter(opts.ingestQuota ?? DEFAULT_INGEST_QUOTA);
|
|
2684
2835
|
onIngest = createIngestHandler(store, { anonMode, defaultOrg: tenant, quota });
|
|
@@ -2701,6 +2852,14 @@ async function startMcp(opts = {}) {
|
|
|
2701
2852
|
...token ? { token } : {},
|
|
2702
2853
|
...onIngest ? { onIngest } : {}
|
|
2703
2854
|
});
|
|
2855
|
+
if (resolvedStore) {
|
|
2856
|
+
const closeStore = () => {
|
|
2857
|
+
void Promise.resolve(resolvedStore?.close()).catch(() => {
|
|
2858
|
+
});
|
|
2859
|
+
};
|
|
2860
|
+
process.once("SIGINT", closeStore);
|
|
2861
|
+
process.once("SIGTERM", closeStore);
|
|
2862
|
+
}
|
|
2704
2863
|
const modeNote = serverMode ? ` [central collector: POST /ingest enabled, anon-mode=${opts.anonMode ?? "reject"}]` : "";
|
|
2705
2864
|
log(`Cartography MCP server (Streamable HTTP) on http://${host}:${port}/mcp${token ? " (auth: bearer token required)" : ""} (tenant: ${tenant})${modeNote}`);
|
|
2706
2865
|
} else {
|
|
@@ -2723,4 +2882,4 @@ export {
|
|
|
2723
2882
|
parseMcpArgs,
|
|
2724
2883
|
startMcp
|
|
2725
2884
|
};
|
|
2726
|
-
//# sourceMappingURL=chunk-
|
|
2885
|
+
//# sourceMappingURL=chunk-5D5ZZEZM.js.map
|