@datasynx/agentic-ai-cartography 2.6.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/api-bin.js +2 -2
- package/dist/{chunk-X3UWUX3G.js → chunk-5D5ZZEZM.js} +242 -19
- package/dist/chunk-5D5ZZEZM.js.map +1 -0
- package/dist/{chunk-PQ7Q6MI5.js → chunk-TBPGFEMQ.js} +2 -2
- package/dist/{chunk-GA4427LB.js → chunk-YVV6NIT2.js} +11 -1
- package/dist/chunk-YVV6NIT2.js.map +1 -0
- package/dist/cli.js +38 -6
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +228 -14
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +159 -12
- package/dist/index.d.ts +159 -12
- package/dist/index.js +223 -14
- package/dist/index.js.map +1 -1
- package/dist/mcp-bin.js +2 -2
- package/package.json +3 -2
- package/server.json +2 -2
- package/dist/chunk-GA4427LB.js.map +0 -1
- package/dist/chunk-X3UWUX3G.js.map +0 -1
- /package/dist/{chunk-PQ7Q6MI5.js.map → chunk-TBPGFEMQ.js.map} +0 -0
package/dist/api-bin.js
CHANGED
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
AuthorizationError,
|
|
4
4
|
CartographyDB,
|
|
5
5
|
RulesetSchema,
|
|
6
|
+
SCHEMA_VERSION,
|
|
6
7
|
SqliteCredentialStore,
|
|
7
8
|
assertSafeBind,
|
|
8
9
|
authorize,
|
|
@@ -24,7 +25,7 @@ import {
|
|
|
24
25
|
sanitizeUntrusted,
|
|
25
26
|
stableStringify,
|
|
26
27
|
stripSensitive
|
|
27
|
-
} from "./chunk-
|
|
28
|
+
} from "./chunk-YVV6NIT2.js";
|
|
28
29
|
import {
|
|
29
30
|
EdgeSchema,
|
|
30
31
|
NODE_TYPES,
|
|
@@ -1538,7 +1539,7 @@ async function executeNlQuery(db, sessionId, search, intent, opts = {}) {
|
|
|
1538
1539
|
|
|
1539
1540
|
// src/mcp/server.ts
|
|
1540
1541
|
var SERVER_NAME = "cartography";
|
|
1541
|
-
var SERVER_VERSION = "2.
|
|
1542
|
+
var SERVER_VERSION = "2.8.0";
|
|
1542
1543
|
var SERVICE_TYPES = NODE_TYPE_GROUPS.web;
|
|
1543
1544
|
var DATA_TYPES = NODE_TYPE_GROUPS.data;
|
|
1544
1545
|
var lexicalSearch = async (db, sessionId, query, opts) => db.searchNodes(sessionId, query, { types: opts.types, limit: opts.limit }).map((node) => ({ node }));
|
|
@@ -1613,9 +1614,10 @@ function createMcpServer(opts = {}) {
|
|
|
1613
1614
|
"graph-summary",
|
|
1614
1615
|
"cartography://graph/summary",
|
|
1615
1616
|
{ title: "Topology summary", description: "Low-token aggregate index of the whole landscape \u2014 read this first.", mimeType: "text/markdown" },
|
|
1616
|
-
(uri) => {
|
|
1617
|
+
async (uri) => {
|
|
1617
1618
|
if (org !== void 0) {
|
|
1618
|
-
|
|
1619
|
+
const s = opts.orgSummary ? await opts.orgSummary(org) : db.getOrgSummary(org);
|
|
1620
|
+
return { contents: [{ uri: uri.href, mimeType: "text/markdown", text: summaryText(s) }] };
|
|
1619
1621
|
}
|
|
1620
1622
|
const sid = resolveSession();
|
|
1621
1623
|
if (!sid) return { contents: [{ uri: uri.href, mimeType: "text/markdown", text: "No discovery session found. Run discovery first." }] };
|
|
@@ -1690,8 +1692,8 @@ function createMcpServer(opts = {}) {
|
|
|
1690
1692
|
server.registerTool(
|
|
1691
1693
|
"get_summary",
|
|
1692
1694
|
{ title: "Get topology summary", description: "Low-token overview of the whole landscape (counts, types, domains, most-connected, anomalies).", inputSchema: {}, annotations: readOnly },
|
|
1693
|
-
() => {
|
|
1694
|
-
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));
|
|
1695
1697
|
const sid = resolveSession();
|
|
1696
1698
|
if (!sid) return json({ error: "No discovery session found." });
|
|
1697
1699
|
return json(db.getGraphSummary(sid));
|
|
@@ -2106,6 +2108,16 @@ async function runHttp(factory, opts = {}) {
|
|
|
2106
2108
|
const httpServer = http.createServer(async (req, res) => {
|
|
2107
2109
|
try {
|
|
2108
2110
|
const url = req.url ?? "";
|
|
2111
|
+
const probePath = new URL(url || "/", "http://probe").pathname;
|
|
2112
|
+
if (probePath === "/healthz") {
|
|
2113
|
+
res.writeHead(200, { "content-type": "application/json" }).end('{"status":"ok"}');
|
|
2114
|
+
return;
|
|
2115
|
+
}
|
|
2116
|
+
if (probePath === "/readyz") {
|
|
2117
|
+
const r = opts.readiness ? opts.readiness() : { ready: true };
|
|
2118
|
+
res.writeHead(r.ready ? 200 : 503, { "content-type": "application/json" }).end(JSON.stringify({ status: r.ready ? "ready" : "unready" }));
|
|
2119
|
+
return;
|
|
2120
|
+
}
|
|
2109
2121
|
const isIngest = url.startsWith("/ingest") && opts.onIngest !== void 0;
|
|
2110
2122
|
if (!url.startsWith("/mcp") && !isIngest) {
|
|
2111
2123
|
res.writeHead(404, { "content-type": "application/json" }).end('{"error":"not found"}');
|
|
@@ -2132,8 +2144,8 @@ async function runHttp(factory, opts = {}) {
|
|
|
2132
2144
|
res.writeHead(413, { "content-type": "application/json" }).end('{"error":"payload too large"}');
|
|
2133
2145
|
return;
|
|
2134
2146
|
}
|
|
2135
|
-
const out = onIngest(value);
|
|
2136
|
-
res.writeHead(out.status, { "content-type": "application/json" }).end(JSON.stringify(out.body));
|
|
2147
|
+
const out = await onIngest(value);
|
|
2148
|
+
res.writeHead(out.status, { "content-type": "application/json", ...out.headers ?? {} }).end(JSON.stringify(out.body));
|
|
2137
2149
|
return;
|
|
2138
2150
|
}
|
|
2139
2151
|
const sessionId = req.headers["mcp-session-id"];
|
|
@@ -2371,6 +2383,148 @@ var SqliteStoreBackend = class {
|
|
|
2371
2383
|
}
|
|
2372
2384
|
};
|
|
2373
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
|
+
|
|
2374
2528
|
// src/central/ingest.ts
|
|
2375
2529
|
import { z as z3 } from "zod";
|
|
2376
2530
|
|
|
@@ -2461,7 +2615,7 @@ var ContributorSchema = z3.object({
|
|
|
2461
2615
|
});
|
|
2462
2616
|
var IngestEnvelopeSchema = z3.object({
|
|
2463
2617
|
schemaVersion: z3.literal(INGEST_SCHEMA_VERSION),
|
|
2464
|
-
org: z3.string().min(1).optional(),
|
|
2618
|
+
org: z3.string().min(1).max(128).optional(),
|
|
2465
2619
|
items: z3.array(z3.object({
|
|
2466
2620
|
contentHash: z3.string(),
|
|
2467
2621
|
kind: z3.enum(["node", "edge"]),
|
|
@@ -2471,9 +2625,9 @@ var IngestEnvelopeSchema = z3.object({
|
|
|
2471
2625
|
contributor: ContributorSchema.optional(),
|
|
2472
2626
|
anonymizationLevel: z3.enum(["none", "anonymized", "full"]).optional()
|
|
2473
2627
|
});
|
|
2474
|
-
function ingestEnvelope(store, envelope, opts = {}) {
|
|
2628
|
+
async function ingestEnvelope(store, envelope, opts = {}) {
|
|
2475
2629
|
const anonMode = opts.anonMode ?? "reject";
|
|
2476
|
-
const org = envelope.org ?? opts.defaultOrg
|
|
2630
|
+
const org = normalizeTenant(envelope.org ?? opts.defaultOrg);
|
|
2477
2631
|
const level = envelope.anonymizationLevel ?? "anonymized";
|
|
2478
2632
|
const at = (/* @__PURE__ */ new Date()).toISOString();
|
|
2479
2633
|
const contributor = {
|
|
@@ -2524,7 +2678,7 @@ function ingestEnvelope(store, envelope, opts = {}) {
|
|
|
2524
2678
|
}
|
|
2525
2679
|
const safe = check.node;
|
|
2526
2680
|
const identity = computeIdentity(org, safe);
|
|
2527
|
-
const outcome = store.upsertNode(org, safe, identity, { ...contributor, confidence: safe.confidence });
|
|
2681
|
+
const outcome = await store.upsertNode(org, safe, identity, { ...contributor, confidence: safe.confidence });
|
|
2528
2682
|
accepted += 1;
|
|
2529
2683
|
if (outcome === "merged") merged += 1;
|
|
2530
2684
|
acceptedNodeIds.add(safe.id);
|
|
@@ -2540,7 +2694,7 @@ function ingestEnvelope(store, envelope, opts = {}) {
|
|
|
2540
2694
|
if (acceptedNodeIds.size > 0 && (!acceptedNodeIds.has(edge.sourceId) || !acceptedNodeIds.has(edge.targetId))) {
|
|
2541
2695
|
continue;
|
|
2542
2696
|
}
|
|
2543
|
-
store.insertEdge(org, edge);
|
|
2697
|
+
await store.insertEdge(org, edge);
|
|
2544
2698
|
edges += 1;
|
|
2545
2699
|
}
|
|
2546
2700
|
logInfo("ingest", { org, accepted, merged, rejected, edges, violations, level, anonMode });
|
|
@@ -2549,15 +2703,24 @@ function ingestEnvelope(store, envelope, opts = {}) {
|
|
|
2549
2703
|
|
|
2550
2704
|
// src/central/server.ts
|
|
2551
2705
|
function createIngestHandler(store, opts = {}) {
|
|
2552
|
-
|
|
2706
|
+
const quota = opts.quota;
|
|
2707
|
+
return async (body) => {
|
|
2553
2708
|
const parsed = IngestEnvelopeSchema.safeParse(body);
|
|
2554
2709
|
if (!parsed.success) {
|
|
2555
2710
|
const issues = parsed.error.issues.map((i) => `${i.path.join(".") || "(root)"}: ${i.message}`);
|
|
2556
2711
|
logWarn("ingest: rejected invalid envelope", { issues });
|
|
2557
2712
|
return { status: 400, body: { error: "invalid envelope", issues } };
|
|
2558
2713
|
}
|
|
2714
|
+
if (quota) {
|
|
2715
|
+
const org = normalizeTenant(parsed.data.org ?? opts.defaultOrg);
|
|
2716
|
+
const decision = quota.take(org);
|
|
2717
|
+
if (!decision.allowed) {
|
|
2718
|
+
logWarn("ingest: rate limited", { org, retryAfterSec: decision.retryAfterSec });
|
|
2719
|
+
return { status: 429, body: { error: "too many requests" }, headers: { "retry-after": String(decision.retryAfterSec) } };
|
|
2720
|
+
}
|
|
2721
|
+
}
|
|
2559
2722
|
try {
|
|
2560
|
-
const result = ingestEnvelope(store, parsed.data, opts);
|
|
2723
|
+
const result = await ingestEnvelope(store, parsed.data, opts);
|
|
2561
2724
|
return { status: 200, body: result };
|
|
2562
2725
|
} catch (err) {
|
|
2563
2726
|
logWarn("ingest: failed", { error: err instanceof Error ? err.message : String(err) });
|
|
@@ -2566,7 +2729,41 @@ function createIngestHandler(store, opts = {}) {
|
|
|
2566
2729
|
};
|
|
2567
2730
|
}
|
|
2568
2731
|
|
|
2732
|
+
// src/central/quota.ts
|
|
2733
|
+
var DEFAULT_INGEST_QUOTA = { capacity: 120, refillMs: 6e4 };
|
|
2734
|
+
var MAX_KEYS = 1e4;
|
|
2735
|
+
var RateLimiter = class {
|
|
2736
|
+
constructor(cfg = DEFAULT_INGEST_QUOTA, now = () => Date.now()) {
|
|
2737
|
+
this.cfg = cfg;
|
|
2738
|
+
this.now = now;
|
|
2739
|
+
}
|
|
2740
|
+
buckets = /* @__PURE__ */ new Map();
|
|
2741
|
+
/** Consume one token for `key`. Returns whether the request is allowed (+ Retry-After when not). */
|
|
2742
|
+
take(key) {
|
|
2743
|
+
const t = this.now();
|
|
2744
|
+
const ratePerMs = this.cfg.capacity / this.cfg.refillMs;
|
|
2745
|
+
let b = this.buckets.get(key);
|
|
2746
|
+
if (!b) {
|
|
2747
|
+
if (this.buckets.size >= MAX_KEYS) {
|
|
2748
|
+
const oldest = this.buckets.keys().next().value;
|
|
2749
|
+
if (oldest !== void 0) this.buckets.delete(oldest);
|
|
2750
|
+
}
|
|
2751
|
+
b = { tokens: this.cfg.capacity, last: t };
|
|
2752
|
+
this.buckets.set(key, b);
|
|
2753
|
+
}
|
|
2754
|
+
b.tokens = Math.min(this.cfg.capacity, b.tokens + Math.max(0, t - b.last) * ratePerMs);
|
|
2755
|
+
b.last = t;
|
|
2756
|
+
if (b.tokens >= 1) {
|
|
2757
|
+
b.tokens -= 1;
|
|
2758
|
+
return { allowed: true, retryAfterSec: 0 };
|
|
2759
|
+
}
|
|
2760
|
+
const waitMs = (1 - b.tokens) / ratePerMs;
|
|
2761
|
+
return { allowed: false, retryAfterSec: Math.max(1, Math.ceil(waitMs / 1e3)) };
|
|
2762
|
+
}
|
|
2763
|
+
};
|
|
2764
|
+
|
|
2569
2765
|
// src/mcp/start.ts
|
|
2766
|
+
var EXPECTED_USER_VERSION = SCHEMA_VERSION;
|
|
2570
2767
|
function parseMcpArgs(argv) {
|
|
2571
2768
|
const opts = {};
|
|
2572
2769
|
for (let i = 0; i < argv.length; i++) {
|
|
@@ -2604,6 +2801,7 @@ async function startMcp(opts = {}) {
|
|
|
2604
2801
|
const serverMode = opts.serverMode === true;
|
|
2605
2802
|
const authStore = new SqliteCredentialStore(db);
|
|
2606
2803
|
const rbacActive = authStore.count() > 0;
|
|
2804
|
+
let resolvedStore;
|
|
2607
2805
|
const factory = (principal) => {
|
|
2608
2806
|
const scopeTenant = rbacActive && principal ? principal.tenant : tenant;
|
|
2609
2807
|
const orgArg = serverMode ? scopeTenant : void 0;
|
|
@@ -2614,7 +2812,8 @@ async function startMcp(opts = {}) {
|
|
|
2614
2812
|
search,
|
|
2615
2813
|
discovery,
|
|
2616
2814
|
...principal ? { principal } : {},
|
|
2617
|
-
...orgArg !== void 0 ? { org: orgArg } : {}
|
|
2815
|
+
...orgArg !== void 0 ? { org: orgArg } : {},
|
|
2816
|
+
...resolvedStore && orgArg !== void 0 ? { orgSummary: (o) => resolvedStore.getSummary(o) } : {}
|
|
2618
2817
|
});
|
|
2619
2818
|
};
|
|
2620
2819
|
const transport = serverMode ? "http" : opts.transport;
|
|
@@ -2624,19 +2823,43 @@ async function startMcp(opts = {}) {
|
|
|
2624
2823
|
const token = opts.token ?? process.env["CARTOGRAPHY_HTTP_TOKEN"] ?? process.env["CARTOGRAPHY_CENTRAL_TOKEN"];
|
|
2625
2824
|
let onIngest;
|
|
2626
2825
|
if (serverMode) {
|
|
2627
|
-
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;
|
|
2628
2833
|
const anonMode = opts.anonMode ?? "reject";
|
|
2629
|
-
|
|
2834
|
+
const quota = new RateLimiter(opts.ingestQuota ?? DEFAULT_INGEST_QUOTA);
|
|
2835
|
+
onIngest = createIngestHandler(store, { anonMode, defaultOrg: tenant, quota });
|
|
2630
2836
|
}
|
|
2837
|
+
const readiness = () => {
|
|
2838
|
+
try {
|
|
2839
|
+
const v = db.rawConnection().pragma("user_version", { simple: true });
|
|
2840
|
+
return { ready: v === EXPECTED_USER_VERSION, detail: { schema: v } };
|
|
2841
|
+
} catch (err) {
|
|
2842
|
+
return { ready: false, detail: { error: err instanceof Error ? err.message : String(err) } };
|
|
2843
|
+
}
|
|
2844
|
+
};
|
|
2631
2845
|
await runHttp(factory, {
|
|
2632
2846
|
port,
|
|
2633
2847
|
host,
|
|
2848
|
+
readiness,
|
|
2634
2849
|
auth: { store: authStore, ...opts.authRequired ? { required: true } : {} },
|
|
2635
2850
|
defaultTenant: tenant,
|
|
2636
2851
|
...opts.allowedHosts ? { allowedHosts: opts.allowedHosts } : {},
|
|
2637
2852
|
...token ? { token } : {},
|
|
2638
2853
|
...onIngest ? { onIngest } : {}
|
|
2639
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
|
+
}
|
|
2640
2863
|
const modeNote = serverMode ? ` [central collector: POST /ingest enabled, anon-mode=${opts.anonMode ?? "reject"}]` : "";
|
|
2641
2864
|
log(`Cartography MCP server (Streamable HTTP) on http://${host}:${port}/mcp${token ? " (auth: bearer token required)" : ""} (tenant: ${tenant})${modeNote}`);
|
|
2642
2865
|
} else {
|
|
@@ -2659,4 +2882,4 @@ export {
|
|
|
2659
2882
|
parseMcpArgs,
|
|
2660
2883
|
startMcp
|
|
2661
2884
|
};
|
|
2662
|
-
//# sourceMappingURL=chunk-
|
|
2885
|
+
//# sourceMappingURL=chunk-5D5ZZEZM.js.map
|