@datasynx/agentic-ai-cartography 2.4.0 → 2.6.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.
@@ -1,9 +1,12 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
+ AuthorizationError,
3
4
  CartographyDB,
4
5
  RulesetSchema,
6
+ SqliteCredentialStore,
5
7
  assertSafeBind,
6
- checkBearer,
8
+ authorize,
9
+ bearerToken,
7
10
  cloudAwsScanner,
8
11
  cloudAzureScanner,
9
12
  cloudGcpScanner,
@@ -17,10 +20,11 @@ import {
17
20
  keyMetaOf,
18
21
  normalizeTenant,
19
22
  redactValue,
23
+ resolvePrincipal,
20
24
  sanitizeUntrusted,
21
25
  stableStringify,
22
26
  stripSensitive
23
- } from "./chunk-X5JA2UDT.js";
27
+ } from "./chunk-GA4427LB.js";
24
28
  import {
25
29
  EdgeSchema,
26
30
  NODE_TYPES,
@@ -1534,7 +1538,7 @@ async function executeNlQuery(db, sessionId, search, intent, opts = {}) {
1534
1538
 
1535
1539
  // src/mcp/server.ts
1536
1540
  var SERVER_NAME = "cartography";
1537
- var SERVER_VERSION = "2.4.0";
1541
+ var SERVER_VERSION = "2.6.0";
1538
1542
  var SERVICE_TYPES = NODE_TYPE_GROUPS.web;
1539
1543
  var DATA_TYPES = NODE_TYPE_GROUPS.data;
1540
1544
  var lexicalSearch = async (db, sessionId, query, opts) => db.searchNodes(sessionId, query, { types: opts.types, limit: opts.limit }).map((node) => ({ node }));
@@ -1936,6 +1940,14 @@ function createMcpServer(opts = {}) {
1936
1940
  annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: true }
1937
1941
  },
1938
1942
  async (args) => {
1943
+ if (opts.principal) {
1944
+ try {
1945
+ authorize(opts.principal, "discovery");
1946
+ } catch (err) {
1947
+ if (err instanceof AuthorizationError) return json({ error: `forbidden: role '${opts.principal.role}' may not run discovery (operator required)` });
1948
+ throw err;
1949
+ }
1950
+ }
1939
1951
  let sid = resolveSession();
1940
1952
  if (args.update) {
1941
1953
  if (!sid) return json({ error: "No session to update; run discovery first." });
@@ -2035,6 +2047,9 @@ import { randomUUID } from "crypto";
2035
2047
  import http from "http";
2036
2048
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
2037
2049
  import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
2050
+ function samePrincipal(a, b) {
2051
+ return a.subject === b.subject && a.tenant === b.tenant && a.role === b.role;
2052
+ }
2038
2053
  async function runStdio(server) {
2039
2054
  const transport = new StdioServerTransport();
2040
2055
  await server.connect(transport);
@@ -2079,6 +2094,14 @@ async function runHttp(factory, opts = {}) {
2079
2094
  assertSafeBind({ host, port, ...opts.allowedHosts ? { allowedHosts: opts.allowedHosts } : {}, ...opts.token ? { token: opts.token } : {} });
2080
2095
  const allowedHosts = opts.allowedHosts ?? defaultAllowedHosts(host, port);
2081
2096
  const token = opts.token;
2097
+ const authStore = opts.auth?.store;
2098
+ const defaultTenant = opts.defaultTenant;
2099
+ const resolveAuth = (header) => resolvePrincipal(bearerToken(header), {
2100
+ ...authStore ? { store: authStore } : {},
2101
+ ...token ? { sharedToken: token } : {},
2102
+ ...defaultTenant ? { defaultTenant } : {},
2103
+ ...opts.auth?.required ? { required: true } : {}
2104
+ });
2082
2105
  const transports = /* @__PURE__ */ new Map();
2083
2106
  const httpServer = http.createServer(async (req, res) => {
2084
2107
  try {
@@ -2088,7 +2111,8 @@ async function runHttp(factory, opts = {}) {
2088
2111
  res.writeHead(404, { "content-type": "application/json" }).end('{"error":"not found"}');
2089
2112
  return;
2090
2113
  }
2091
- if (!checkBearer(req.headers["authorization"], token)) {
2114
+ const principal = resolveAuth(req.headers["authorization"]);
2115
+ if (!principal) {
2092
2116
  res.writeHead(401, { "content-type": "application/json", "www-authenticate": "Bearer" }).end('{"error":"unauthorized"}');
2093
2117
  return;
2094
2118
  }
@@ -2115,8 +2139,12 @@ async function runHttp(factory, opts = {}) {
2115
2139
  const sessionId = req.headers["mcp-session-id"];
2116
2140
  const existing = sessionId ? transports.get(sessionId) : void 0;
2117
2141
  if (existing) {
2142
+ if (!samePrincipal(existing.principal, principal)) {
2143
+ res.writeHead(403, { "content-type": "application/json" }).end('{"error":"session belongs to a different principal"}');
2144
+ return;
2145
+ }
2118
2146
  const body2 = req.method === "POST" ? await readJsonBody(req) : void 0;
2119
- await existing.handleRequest(req, res, body2);
2147
+ await existing.transport.handleRequest(req, res, body2);
2120
2148
  return;
2121
2149
  }
2122
2150
  if (req.method !== "POST") {
@@ -2130,13 +2158,13 @@ async function runHttp(factory, opts = {}) {
2130
2158
  allowedHosts,
2131
2159
  ...opts.allowedOrigins ? { allowedOrigins: opts.allowedOrigins } : {},
2132
2160
  onsessioninitialized: (id) => {
2133
- transports.set(id, transport);
2161
+ transports.set(id, { transport, principal });
2134
2162
  }
2135
2163
  });
2136
2164
  transport.onclose = () => {
2137
2165
  if (transport.sessionId) transports.delete(transport.sessionId);
2138
2166
  };
2139
- await factory().connect(transport);
2167
+ await factory(principal).connect(transport);
2140
2168
  await transport.handleRequest(req, res, body);
2141
2169
  } catch (err) {
2142
2170
  process.stderr.write(`[cartography-mcp] HTTP request failed: ${err instanceof Error ? err.message : String(err)}
@@ -2558,7 +2586,8 @@ function parseMcpArgs(argv) {
2558
2586
  else if (a === "--anon-mode") {
2559
2587
  const m = argv[++i];
2560
2588
  if (m === "reject" || m === "strip") opts.anonMode = m;
2561
- } else if (a === "--help" || a === "-h") opts.help = true;
2589
+ } else if (a === "--auth-required") opts.authRequired = true;
2590
+ else if (a === "--help" || a === "-h") opts.help = true;
2562
2591
  }
2563
2592
  return opts;
2564
2593
  }
@@ -2573,8 +2602,21 @@ async function startMcp(opts = {}) {
2573
2602
  const discovery = localDiscoveryFn(void 0, plugins);
2574
2603
  const tenant = normalizeTenant(opts.tenant);
2575
2604
  const serverMode = opts.serverMode === true;
2576
- const org = serverMode ? tenant : void 0;
2577
- const factory = () => createMcpServer({ db, session: opts.session ?? "latest", tenant, search, discovery, ...org !== void 0 ? { org } : {} });
2605
+ const authStore = new SqliteCredentialStore(db);
2606
+ const rbacActive = authStore.count() > 0;
2607
+ const factory = (principal) => {
2608
+ const scopeTenant = rbacActive && principal ? principal.tenant : tenant;
2609
+ const orgArg = serverMode ? scopeTenant : void 0;
2610
+ return createMcpServer({
2611
+ db,
2612
+ session: opts.session ?? "latest",
2613
+ tenant: scopeTenant,
2614
+ search,
2615
+ discovery,
2616
+ ...principal ? { principal } : {},
2617
+ ...orgArg !== void 0 ? { org: orgArg } : {}
2618
+ });
2619
+ };
2578
2620
  const transport = serverMode ? "http" : opts.transport;
2579
2621
  if (transport === "http") {
2580
2622
  const port = opts.port ?? 3737;
@@ -2589,6 +2631,8 @@ async function startMcp(opts = {}) {
2589
2631
  await runHttp(factory, {
2590
2632
  port,
2591
2633
  host,
2634
+ auth: { store: authStore, ...opts.authRequired ? { required: true } : {} },
2635
+ defaultTenant: tenant,
2592
2636
  ...opts.allowedHosts ? { allowedHosts: opts.allowedHosts } : {},
2593
2637
  ...token ? { token } : {},
2594
2638
  ...onIngest ? { onIngest } : {}
@@ -2615,4 +2659,4 @@ export {
2615
2659
  parseMcpArgs,
2616
2660
  startMcp
2617
2661
  };
2618
- //# sourceMappingURL=chunk-B4QWX7CP.js.map
2662
+ //# sourceMappingURL=chunk-X3UWUX3G.js.map