@massu/core 0.6.2 → 0.7.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/cli.js CHANGED
@@ -125,7 +125,7 @@ function getResolvedPaths() {
125
125
  settingsLocalPath: resolve(root, claudeDirName, "settings.local.json")
126
126
  };
127
127
  }
128
- var DomainConfigSchema, PatternRuleConfigSchema, CostModelSchema, AnalyticsConfigSchema, CustomPatternSchema, GovernanceConfigSchema, SecurityPatternSchema, SecurityConfigSchema, TeamConfigSchema, RegressionConfigSchema, CloudConfigSchema, ConventionsConfigSchema, PythonDomainConfigSchema, PythonConfigSchema, PathsConfigSchema, RawConfigSchema, _config, _projectRoot;
128
+ var DomainConfigSchema, PatternRuleConfigSchema, CostModelSchema, AnalyticsConfigSchema, CustomPatternSchema, GovernanceConfigSchema, SecurityPatternSchema, SecurityConfigSchema, TeamConfigSchema, RegressionConfigSchema, AutoLearningConfigSchema, CloudConfigSchema, ConventionsConfigSchema, PythonDomainConfigSchema, PythonConfigSchema, PathsConfigSchema, RawConfigSchema, _config, _projectRoot;
129
129
  var init_config = __esm({
130
130
  "src/config.ts"() {
131
131
  "use strict";
@@ -248,6 +248,32 @@ var init_config = __esm({
248
248
  warning: z.number().default(50)
249
249
  }).optional()
250
250
  }).optional();
251
+ AutoLearningConfigSchema = z.object({
252
+ enabled: z.boolean().default(true),
253
+ incidentDir: z.string().default("docs/incidents"),
254
+ memoryDir: z.string().default("memory"),
255
+ memoryIndexFile: z.string().default("MEMORY.md"),
256
+ enforcementHooksDir: z.string().default("scripts/hooks"),
257
+ fixDetection: z.object({
258
+ enabled: z.boolean().default(true),
259
+ lookbackDays: z.number().default(7),
260
+ signals: z.array(z.string()).default([
261
+ "removed_broken_code",
262
+ "added_error_handling",
263
+ "method_name_correction",
264
+ "auth_fix",
265
+ "nil_handling_fix",
266
+ "concurrency_fix",
267
+ "async_pattern_fix",
268
+ "added_missing_import"
269
+ ])
270
+ }).default({}),
271
+ pipeline: z.object({
272
+ requireIncidentReport: z.boolean().default(true),
273
+ requirePreventionRule: z.boolean().default(true),
274
+ requireEnforcement: z.boolean().default(true)
275
+ }).default({})
276
+ }).optional();
251
277
  CloudConfigSchema = z.object({
252
278
  enabled: z.boolean().default(false),
253
279
  apiKey: z.string().optional(),
@@ -337,7 +363,8 @@ var init_config = __esm({
337
363
  regression: RegressionConfigSchema,
338
364
  cloud: CloudConfigSchema,
339
365
  conventions: ConventionsConfigSchema,
340
- python: PythonConfigSchema
366
+ python: PythonConfigSchema,
367
+ autoLearning: AutoLearningConfigSchema
341
368
  }).passthrough();
342
369
  _config = null;
343
370
  _projectRoot = null;
@@ -989,12 +1016,12 @@ function addSummary(db, sessionId, summary) {
989
1016
  Math.floor(now.getTime() / 1e3)
990
1017
  );
991
1018
  }
992
- function addUserPrompt(db, sessionId, text18, promptNumber) {
1019
+ function addUserPrompt(db, sessionId, text19, promptNumber) {
993
1020
  const now = /* @__PURE__ */ new Date();
994
1021
  db.prepare(`
995
1022
  INSERT INTO user_prompts (session_id, prompt_text, prompt_number, created_at, created_at_epoch)
996
1023
  VALUES (?, ?, ?, ?, ?)
997
- `).run(sessionId, text18, promptNumber, now.toISOString(), Math.floor(now.getTime() / 1e3));
1024
+ `).run(sessionId, text19, promptNumber, now.toISOString(), Math.floor(now.getTime() / 1e3));
998
1025
  }
999
1026
  function searchObservations(db, query, opts) {
1000
1027
  const limit = opts?.limit ?? 20;
@@ -3980,7 +4007,7 @@ function parseFromImportLine(line, lineNum) {
3980
4007
  function parsePlainImportLine(line, lineNum) {
3981
4008
  const results = [];
3982
4009
  const rest = line.replace(/^import\s+/, "");
3983
- const parts = rest.split(",").map((p18) => p18.trim()).filter((p18) => p18.length > 0);
4010
+ const parts = rest.split(",").map((p19) => p19.trim()).filter((p19) => p19.length > 0);
3984
4011
  for (const part of parts) {
3985
4012
  const asMatch = part.match(/^(\S+)\s+as\s+(\S+)$/);
3986
4013
  const moduleName = asMatch ? asMatch[1] : part;
@@ -4141,8 +4168,8 @@ function joinLogicalLines(source) {
4141
4168
  }
4142
4169
  return logical;
4143
4170
  }
4144
- function extractQuotedString(text18) {
4145
- const m = text18.match(/(['"])(.*?)\1/);
4171
+ function extractQuotedString(text19) {
4172
+ const m = text19.match(/(['"])(.*?)\1/);
4146
4173
  return m ? m[2] : null;
4147
4174
  }
4148
4175
  function extractResponseModel(argStr) {
@@ -4209,8 +4236,8 @@ function parsePythonRoutes(source) {
4209
4236
  const logicalLines = joinLogicalLines(source);
4210
4237
  const pendingDecorators = [];
4211
4238
  for (let i = 0; i < logicalLines.length; i++) {
4212
- const { text: text18, startLine } = logicalLines[i];
4213
- const trimmed = text18.trim();
4239
+ const { text: text19, startLine } = logicalLines[i];
4240
+ const trimmed = text19.trim();
4214
4241
  const decoratorMatch = trimmed.match(
4215
4242
  /^@\s*(\w+)\s*\.\s*(\w+)\s*\((.*)\)\s*$/s
4216
4243
  );
@@ -4445,11 +4472,11 @@ function parseRelationship(argsStr) {
4445
4472
  back_populates: bpMatch ? bpMatch[1] : null
4446
4473
  };
4447
4474
  }
4448
- function findClosingParen(text18, openIndex, openChar, closeChar) {
4475
+ function findClosingParen(text19, openIndex, openChar, closeChar) {
4449
4476
  let depth = 1;
4450
- for (let i = openIndex + 1; i < text18.length; i++) {
4451
- if (text18[i] === openChar) depth++;
4452
- else if (text18[i] === closeChar) {
4477
+ for (let i = openIndex + 1; i < text19.length; i++) {
4478
+ if (text19[i] === openChar) depth++;
4479
+ else if (text19[i] === closeChar) {
4453
4480
  depth--;
4454
4481
  if (depth === 0) return i;
4455
4482
  }
@@ -4725,16 +4752,16 @@ function extractTableAndColumn(argsStr) {
4725
4752
  }
4726
4753
  return { table, column };
4727
4754
  }
4728
- function splitTopLevelCommas(text18) {
4755
+ function splitTopLevelCommas(text19) {
4729
4756
  const parts = [];
4730
4757
  let current = "";
4731
4758
  let depth = 0;
4732
4759
  let inString = null;
4733
- for (let i = 0; i < text18.length; i++) {
4734
- const ch = text18[i];
4760
+ for (let i = 0; i < text19.length; i++) {
4761
+ const ch = text19[i];
4735
4762
  if (inString) {
4736
4763
  current += ch;
4737
- if (ch === inString && text18[i - 1] !== "\\") {
4764
+ if (ch === inString && text19[i - 1] !== "\\") {
4738
4765
  inString = null;
4739
4766
  }
4740
4767
  continue;
@@ -5579,7 +5606,7 @@ function handleDocsAudit(args2) {
5579
5606
  const fileName = basename3(file);
5580
5607
  if (mapping.routers.includes(fileName)) {
5581
5608
  const procedures = extractProcedureNames(file);
5582
- const undocumented = procedures.filter((p18) => !contentMentions(content, p18));
5609
+ const undocumented = procedures.filter((p19) => !contentMentions(content, p19));
5583
5610
  if (undocumented.length > 0) {
5584
5611
  staleReasons.push(
5585
5612
  `Router ${fileName}: procedures not documented: ${undocumented.slice(0, 5).join(", ")}${undocumented.length > 5 ? ` (+${undocumented.length - 5} more)` : ""}`
@@ -5905,26 +5932,26 @@ function handleToolPatterns(args2, db) {
5905
5932
  case "tool":
5906
5933
  lines.push("| Tool | Calls | Successes | Failures | Success Rate | Avg Output Size | Avg Input Size |");
5907
5934
  lines.push("|------|-------|-----------|----------|--------------|-----------------|----------------|");
5908
- for (const p18 of patterns) {
5909
- const total = p18.call_count;
5910
- const successes = p18.successes;
5911
- const failures = p18.failures;
5935
+ for (const p19 of patterns) {
5936
+ const total = p19.call_count;
5937
+ const successes = p19.successes;
5938
+ const failures = p19.failures;
5912
5939
  const rate = total > 0 ? Math.round(successes / total * 100) : 0;
5913
- lines.push(`| ${p18.tool_name} | ${total} | ${successes} | ${failures} | ${rate}% | ${Math.round(p18.avg_output_size ?? 0)} | ${Math.round(p18.avg_input_size ?? 0)} |`);
5940
+ lines.push(`| ${p19.tool_name} | ${total} | ${successes} | ${failures} | ${rate}% | ${Math.round(p19.avg_output_size ?? 0)} | ${Math.round(p19.avg_input_size ?? 0)} |`);
5914
5941
  }
5915
5942
  break;
5916
5943
  case "session":
5917
5944
  lines.push("| Session | Calls | Unique Tools | Successes | Failures | Avg Output Size |");
5918
5945
  lines.push("|---------|-------|--------------|-----------|----------|-----------------|");
5919
- for (const p18 of patterns) {
5920
- lines.push(`| ${p18.session_id.slice(0, 8)}... | ${p18.call_count} | ${p18.unique_tools} | ${p18.successes} | ${p18.failures} | ${Math.round(p18.avg_output_size ?? 0)} |`);
5946
+ for (const p19 of patterns) {
5947
+ lines.push(`| ${p19.session_id.slice(0, 8)}... | ${p19.call_count} | ${p19.unique_tools} | ${p19.successes} | ${p19.failures} | ${Math.round(p19.avg_output_size ?? 0)} |`);
5921
5948
  }
5922
5949
  break;
5923
5950
  case "day":
5924
5951
  lines.push("| Day | Calls | Unique Tools | Successes |");
5925
5952
  lines.push("|-----|-------|--------------|-----------|");
5926
- for (const p18 of patterns) {
5927
- lines.push(`| ${p18.day} | ${p18.call_count} | ${p18.unique_tools} | ${p18.successes} |`);
5953
+ for (const p19 of patterns) {
5954
+ lines.push(`| ${p19.day} | ${p19.call_count} | ${p19.unique_tools} | ${p19.successes} |`);
5928
5955
  }
5929
5956
  break;
5930
5957
  }
@@ -6572,15 +6599,15 @@ function handleDetail2(args2, db) {
6572
6599
  }
6573
6600
  if (detail.procedures.length > 0) {
6574
6601
  lines.push(`### Procedures (${detail.procedures.length})`);
6575
- for (const p18 of detail.procedures) {
6576
- lines.push(`- ${p18.router_name}.${p18.procedure_name}${p18.procedure_type ? " (" + p18.procedure_type + ")" : ""}`);
6602
+ for (const p19 of detail.procedures) {
6603
+ lines.push(`- ${p19.router_name}.${p19.procedure_name}${p19.procedure_type ? " (" + p19.procedure_type + ")" : ""}`);
6577
6604
  }
6578
6605
  lines.push("");
6579
6606
  }
6580
6607
  if (detail.pages.length > 0) {
6581
6608
  lines.push(`### Pages (${detail.pages.length})`);
6582
- for (const p18 of detail.pages) {
6583
- lines.push(`- ${p18.page_route}${p18.portal ? " (" + p18.portal + ")" : ""}`);
6609
+ for (const p19 of detail.pages) {
6610
+ lines.push(`- ${p19.page_route}${p19.portal ? " (" + p19.portal + ")" : ""}`);
6584
6611
  }
6585
6612
  lines.push("");
6586
6613
  }
@@ -6663,7 +6690,7 @@ function handleValidate(args2, db) {
6663
6690
  lines.push(` Missing components: ${item.missing_components.join(", ")}`);
6664
6691
  }
6665
6692
  if (item.missing_procedures.length > 0) {
6666
- lines.push(` Missing procedures: ${item.missing_procedures.map((p18) => `${p18.router}.${p18.procedure}`).join(", ")}`);
6693
+ lines.push(` Missing procedures: ${item.missing_procedures.map((p19) => `${p19.router}.${p19.procedure}`).join(", ")}`);
6667
6694
  }
6668
6695
  if (item.missing_pages.length > 0) {
6669
6696
  lines.push(` Missing pages: ${item.missing_pages.join(", ")}`);
@@ -6710,14 +6737,14 @@ function handleRegister(args2, db) {
6710
6737
  }
6711
6738
  const procedures = args2.procedures;
6712
6739
  if (procedures) {
6713
- for (const p18 of procedures) {
6714
- linkProcedure(db, featureId, p18.router, p18.procedure, p18.type);
6740
+ for (const p19 of procedures) {
6741
+ linkProcedure(db, featureId, p19.router, p19.procedure, p19.type);
6715
6742
  }
6716
6743
  }
6717
6744
  const pages = args2.pages;
6718
6745
  if (pages) {
6719
- for (const p18 of pages) {
6720
- linkPage(db, featureId, p18.route, p18.portal);
6746
+ for (const p19 of pages) {
6747
+ linkPage(db, featureId, p19.route, p19.portal);
6721
6748
  }
6722
6749
  }
6723
6750
  logChange(db, featureId, "created", `Registered via ${p4("sentinel_register")}`);
@@ -9652,22 +9679,22 @@ function buildCrossReferences(db) {
9652
9679
  }
9653
9680
  const chunks = db.prepare("SELECT id, content, metadata FROM knowledge_chunks").all();
9654
9681
  for (const chunk of chunks) {
9655
- const text18 = chunk.content;
9656
- const crRefs = text18.match(/CR-\d+/g);
9682
+ const text19 = chunk.content;
9683
+ const crRefs = text19.match(/CR-\d+/g);
9657
9684
  if (crRefs) {
9658
9685
  for (const cr of [...new Set(crRefs)]) {
9659
9686
  insertEdge.run("chunk", String(chunk.id), "cr", cr, "references");
9660
9687
  edgeCount++;
9661
9688
  }
9662
9689
  }
9663
- const vrRefs = text18.match(/VR-[\w-]+/g);
9690
+ const vrRefs = text19.match(/VR-[\w-]+/g);
9664
9691
  if (vrRefs) {
9665
9692
  for (const vr of [...new Set(vrRefs)]) {
9666
9693
  insertEdge.run("chunk", String(chunk.id), "vr", vr, "references");
9667
9694
  edgeCount++;
9668
9695
  }
9669
9696
  }
9670
- const incRefs = text18.match(/Incident #(\d+)/gi);
9697
+ const incRefs = text19.match(/Incident #(\d+)/gi);
9671
9698
  if (incRefs) {
9672
9699
  for (const ref of incRefs) {
9673
9700
  const numMatch = ref.match(/\d+/);
@@ -9713,7 +9740,7 @@ function indexAllKnowledge(db) {
9713
9740
  }
9714
9741
  if (existsSync19(paths.docsDir)) {
9715
9742
  const excludePatterns = getConfig().conventions?.excludePatterns ?? ["/ARCHIVE/", "/SESSION-HISTORY/"];
9716
- const docsFiles = discoverMarkdownFiles(paths.docsDir).filter((f) => !f.includes("/plans/") && !excludePatterns.some((p18) => f.includes(p18)));
9743
+ const docsFiles = discoverMarkdownFiles(paths.docsDir).filter((f) => !f.includes("/plans/") && !excludePatterns.some((p19) => f.includes(p19)));
9717
9744
  files.push(...docsFiles);
9718
9745
  }
9719
9746
  const now = /* @__PURE__ */ new Date();
@@ -9880,7 +9907,7 @@ function isKnowledgeStale(db) {
9880
9907
  }
9881
9908
  if (existsSync19(paths.docsDir)) {
9882
9909
  const excludePatterns = getConfig().conventions?.excludePatterns ?? ["/ARCHIVE/", "/SESSION-HISTORY/"];
9883
- const docsFiles = discoverMarkdownFiles(paths.docsDir).filter((f) => !f.includes("/plans/") && !excludePatterns.some((p18) => f.includes(p18)));
9910
+ const docsFiles = discoverMarkdownFiles(paths.docsDir).filter((f) => !f.includes("/plans/") && !excludePatterns.some((p19) => f.includes(p19)));
9884
9911
  files.push(...docsFiles);
9885
9912
  }
9886
9913
  for (const filePath of files) {
@@ -11734,13 +11761,368 @@ var init_python_tools = __esm({
11734
11761
  }
11735
11762
  });
11736
11763
 
11737
- // src/tools.ts
11764
+ // src/mcp-bridge-tools.ts
11765
+ import { spawn } from "child_process";
11738
11766
  import { readFileSync as readFileSync22, existsSync as existsSync22 } from "fs";
11739
- import { resolve as resolve18, basename as basename7 } from "path";
11767
+ import { resolve as resolve18 } from "path";
11768
+ function p17(baseName) {
11769
+ return `${getConfig().toolPrefix}_${baseName}`;
11770
+ }
11771
+ function buildSubprocessEnv() {
11772
+ const env = {};
11773
+ const projectName = getConfig().project?.name?.toUpperCase() || "";
11774
+ const safePrefixes = projectName ? [`${projectName}_CONFIG_`, `${projectName}_LOG_`] : [];
11775
+ for (const [key, value] of Object.entries(process.env)) {
11776
+ if (!value) continue;
11777
+ if (ENV_DENY_PATTERNS.some((pat) => key.includes(pat))) continue;
11778
+ if (ENV_ALLOW_LIST.has(key)) {
11779
+ env[key] = value;
11780
+ } else if (safePrefixes.length > 0 && safePrefixes.some((pfx) => key.startsWith(pfx))) {
11781
+ env[key] = value;
11782
+ }
11783
+ }
11784
+ return env;
11785
+ }
11786
+ function loadMcpConfig() {
11787
+ const root = getProjectRoot();
11788
+ const mcpPath = resolve18(root, ".mcp.json");
11789
+ if (!existsSync22(mcpPath)) return {};
11790
+ try {
11791
+ const raw = JSON.parse(readFileSync22(mcpPath, "utf-8"));
11792
+ const servers = {};
11793
+ const mcpServers = raw.mcpServers || {};
11794
+ const pfx = getConfig().toolPrefix;
11795
+ for (const [name, config] of Object.entries(mcpServers)) {
11796
+ if (name === pfx) continue;
11797
+ servers[name] = config;
11798
+ }
11799
+ return servers;
11800
+ } catch (e) {
11801
+ console.error("[mcp-bridge] Failed to parse .mcp.json:", e);
11802
+ return {};
11803
+ }
11804
+ }
11805
+ async function mcpRequest(conn, method, params = {}) {
11806
+ if (!conn.process || !conn.process.stdin || !conn.process.stdout) {
11807
+ throw new Error("MCP process not connected");
11808
+ }
11809
+ conn.requestId++;
11810
+ const request = {
11811
+ jsonrpc: "2.0",
11812
+ id: conn.requestId,
11813
+ method,
11814
+ params
11815
+ };
11816
+ return new Promise((resolve22, reject) => {
11817
+ const timeout = setTimeout(() => {
11818
+ conn.process?.stdout?.removeListener("data", onData);
11819
+ reject(new Error(`MCP request timed out: ${method}`));
11820
+ }, 3e4);
11821
+ let buffer2 = "";
11822
+ const onData = (data) => {
11823
+ buffer2 += data.toString("utf-8");
11824
+ const lines = buffer2.split("\n");
11825
+ buffer2 = lines.pop() || "";
11826
+ for (const line of lines) {
11827
+ if (!line.trim()) continue;
11828
+ try {
11829
+ const response = JSON.parse(line);
11830
+ if (response.id === conn.requestId) {
11831
+ clearTimeout(timeout);
11832
+ conn.process?.stdout?.removeListener("data", onData);
11833
+ if (response.error) {
11834
+ reject(new Error(`MCP error ${response.error.code}: ${response.error.message}`));
11835
+ } else {
11836
+ resolve22(response.result || {});
11837
+ }
11838
+ return;
11839
+ }
11840
+ } catch (e) {
11841
+ clearTimeout(timeout);
11842
+ conn.process?.stdout?.removeListener("data", onData);
11843
+ reject(new Error(`Failed to parse MCP response: ${e}`));
11844
+ return;
11845
+ }
11846
+ }
11847
+ };
11848
+ conn.process.stdout.on("data", onData);
11849
+ conn.process.stdin.write(JSON.stringify(request) + "\n");
11850
+ });
11851
+ }
11852
+ async function connectToServer(name, config) {
11853
+ const existing = connections.get(name);
11854
+ if (existing?.connected && existing.process && !existing.process.killed) {
11855
+ return existing;
11856
+ }
11857
+ const root = getProjectRoot();
11858
+ const cwd = config.cwd ? resolve18(root, config.cwd) : root;
11859
+ const args2 = config.args || [];
11860
+ const proc = spawn(config.command, args2, {
11861
+ cwd,
11862
+ stdio: ["pipe", "pipe", "pipe"],
11863
+ env: buildSubprocessEnv()
11864
+ });
11865
+ const conn = {
11866
+ config,
11867
+ process: proc,
11868
+ connected: false,
11869
+ tools: [],
11870
+ requestId: 0
11871
+ };
11872
+ try {
11873
+ await mcpRequest(conn, "initialize", {
11874
+ protocolVersion: "2024-11-05",
11875
+ capabilities: {},
11876
+ clientInfo: { name: "massu-mcp-bridge", version: "1.0.0" }
11877
+ });
11878
+ if (proc.stdin) {
11879
+ proc.stdin.write(JSON.stringify({
11880
+ jsonrpc: "2.0",
11881
+ method: "notifications/initialized",
11882
+ params: {}
11883
+ }) + "\n");
11884
+ }
11885
+ conn.connected = true;
11886
+ const toolsResult = await mcpRequest(conn, "tools/list", {});
11887
+ conn.tools = toolsResult.tools || [];
11888
+ connections.set(name, conn);
11889
+ return conn;
11890
+ } catch (e) {
11891
+ if (!proc.killed) proc.kill("SIGTERM");
11892
+ throw e;
11893
+ }
11894
+ }
11895
+ function disconnectServer(name) {
11896
+ const conn = connections.get(name);
11897
+ if (conn) {
11898
+ conn.connected = false;
11899
+ if (conn.process && !conn.process.killed) {
11900
+ conn.process.kill("SIGTERM");
11901
+ const proc = conn.process;
11902
+ setTimeout(() => {
11903
+ if (!proc.killed) {
11904
+ try {
11905
+ proc.kill("SIGKILL");
11906
+ } catch {
11907
+ }
11908
+ }
11909
+ }, 3e3);
11910
+ }
11911
+ connections.delete(name);
11912
+ }
11913
+ }
11914
+ function text17(s) {
11915
+ return { content: [{ type: "text", text: s }] };
11916
+ }
11917
+ function getMcpBridgeToolDefinitions() {
11918
+ return [
11919
+ {
11920
+ name: p17("mcp_servers"),
11921
+ description: "List all configured MCP servers from .mcp.json and their connection status.",
11922
+ inputSchema: {
11923
+ type: "object",
11924
+ properties: {},
11925
+ required: []
11926
+ }
11927
+ },
11928
+ {
11929
+ name: p17("mcp_tools"),
11930
+ description: "List tools available from a specific MCP server. Connects to the server if not already connected.",
11931
+ inputSchema: {
11932
+ type: "object",
11933
+ properties: {
11934
+ server: { type: "string", description: "MCP server name from .mcp.json" }
11935
+ },
11936
+ required: ["server"]
11937
+ }
11938
+ },
11939
+ {
11940
+ name: p17("mcp_call"),
11941
+ description: "Call a tool on a connected MCP server. Connects automatically if needed.",
11942
+ inputSchema: {
11943
+ type: "object",
11944
+ properties: {
11945
+ server: { type: "string", description: "MCP server name from .mcp.json" },
11946
+ tool: { type: "string", description: "Tool name to call on the remote server" },
11947
+ arguments: {
11948
+ type: "object",
11949
+ description: "Arguments to pass to the remote tool",
11950
+ additionalProperties: true
11951
+ }
11952
+ },
11953
+ required: ["server", "tool"]
11954
+ }
11955
+ },
11956
+ {
11957
+ name: p17("mcp_status"),
11958
+ description: "Health check all MCP server connections. Shows which are connected, disconnected, or errored.",
11959
+ inputSchema: {
11960
+ type: "object",
11961
+ properties: {},
11962
+ required: []
11963
+ }
11964
+ }
11965
+ ];
11966
+ }
11967
+ function isMcpBridgeTool(name) {
11968
+ const pfx = getConfig().toolPrefix;
11969
+ return name.startsWith(`${pfx}_mcp_`);
11970
+ }
11971
+ async function handleMcpBridgeToolCall(name, args2) {
11972
+ const pfx = getConfig().toolPrefix;
11973
+ const baseName = name.startsWith(`${pfx}_`) ? name.slice(pfx.length + 1) : name;
11974
+ switch (baseName) {
11975
+ case "mcp_servers":
11976
+ return handleMcpServers();
11977
+ case "mcp_tools":
11978
+ return await handleMcpTools(args2.server);
11979
+ case "mcp_call":
11980
+ return await handleMcpCall(
11981
+ args2.server,
11982
+ args2.tool,
11983
+ args2.arguments || {}
11984
+ );
11985
+ case "mcp_status":
11986
+ return handleMcpStatus();
11987
+ default:
11988
+ return text17(`Unknown MCP bridge tool: ${name}`);
11989
+ }
11990
+ }
11991
+ function handleMcpServers() {
11992
+ const configs = loadMcpConfig();
11993
+ const servers = Object.entries(configs).map(([name, config]) => {
11994
+ const conn = connections.get(name);
11995
+ return {
11996
+ name,
11997
+ command: config.command,
11998
+ args: config.args || [],
11999
+ cwd: config.cwd || ".",
12000
+ status: conn?.connected ? "connected" : "disconnected",
12001
+ toolCount: conn?.tools.length || 0
12002
+ };
12003
+ });
12004
+ if (servers.length === 0) {
12005
+ return text17("No MCP servers configured in .mcp.json (excluding self).");
12006
+ }
12007
+ const lines = ["# MCP Servers\n"];
12008
+ for (const srv of servers) {
12009
+ const status = srv.status === "connected" ? "CONNECTED" : "DISCONNECTED";
12010
+ lines.push(`- **${srv.name}** [${status}] \u2014 \`${srv.command} ${srv.args.join(" ")}\` (${srv.toolCount} tools)`);
12011
+ }
12012
+ return text17(lines.join("\n"));
12013
+ }
12014
+ async function handleMcpTools(server) {
12015
+ const configs = loadMcpConfig();
12016
+ const config = configs[server];
12017
+ if (!config) {
12018
+ return text17(`MCP server "${server}" not found in .mcp.json. Available: ${Object.keys(configs).join(", ") || "none"}`);
12019
+ }
12020
+ try {
12021
+ const conn = await connectToServer(server, config);
12022
+ if (conn.tools.length === 0) {
12023
+ return text17(`MCP server "${server}" is connected but has no tools.`);
12024
+ }
12025
+ const lines = [`# Tools from ${server} (${conn.tools.length})
12026
+ `];
12027
+ for (const tool of conn.tools) {
12028
+ lines.push(`- **${tool.name}**: ${tool.description}`);
12029
+ }
12030
+ return text17(lines.join("\n"));
12031
+ } catch (e) {
12032
+ return text17(`Failed to connect to MCP server "${server}": ${e}`);
12033
+ }
12034
+ }
12035
+ async function handleMcpCall(server, tool, args2) {
12036
+ const configs = loadMcpConfig();
12037
+ const config = configs[server];
12038
+ if (!config) {
12039
+ return text17(`MCP server "${server}" not found in .mcp.json.`);
12040
+ }
12041
+ try {
12042
+ const conn = await connectToServer(server, config);
12043
+ const result = await mcpRequest(conn, "tools/call", { name: tool, arguments: args2 });
12044
+ const content = result.content;
12045
+ if (Array.isArray(content)) {
12046
+ const texts = content.filter((c) => c.type === "text").map((c) => c.text);
12047
+ if (result.isError) {
12048
+ return text17(`MCP tool error: ${texts.join("\n")}`);
12049
+ }
12050
+ return text17(texts.join("\n"));
12051
+ }
12052
+ return text17(JSON.stringify(result, null, 2));
12053
+ } catch (e) {
12054
+ const conn = connections.get(server);
12055
+ if (conn) conn.connected = false;
12056
+ return text17(`MCP call failed (${server}/${tool}): ${e}`);
12057
+ }
12058
+ }
12059
+ function handleMcpStatus() {
12060
+ const configs = loadMcpConfig();
12061
+ const lines = ["# MCP Connection Status\n"];
12062
+ for (const [name] of Object.entries(configs)) {
12063
+ const conn = connections.get(name);
12064
+ if (!conn) {
12065
+ lines.push(`- **${name}**: NOT CONNECTED`);
12066
+ } else if (conn.connected && conn.process && !conn.process.killed) {
12067
+ lines.push(`- **${name}**: HEALTHY (pid=${conn.process.pid}, ${conn.tools.length} tools)`);
12068
+ } else {
12069
+ lines.push(`- **${name}**: DISCONNECTED`);
12070
+ disconnectServer(name);
12071
+ }
12072
+ }
12073
+ if (Object.keys(configs).length === 0) {
12074
+ lines.push("No MCP servers configured.");
12075
+ }
12076
+ return text17(lines.join("\n"));
12077
+ }
12078
+ var connections, ENV_ALLOW_LIST, ENV_DENY_PATTERNS;
12079
+ var init_mcp_bridge_tools = __esm({
12080
+ "src/mcp-bridge-tools.ts"() {
12081
+ "use strict";
12082
+ init_config();
12083
+ connections = /* @__PURE__ */ new Map();
12084
+ process.on("exit", () => {
12085
+ for (const [, conn] of connections) {
12086
+ if (conn.process && !conn.process.killed) {
12087
+ try {
12088
+ conn.process.kill("SIGTERM");
12089
+ } catch {
12090
+ }
12091
+ }
12092
+ }
12093
+ });
12094
+ process.on("SIGTERM", () => {
12095
+ for (const [name] of connections) disconnectServer(name);
12096
+ process.exit(0);
12097
+ });
12098
+ ENV_ALLOW_LIST = /* @__PURE__ */ new Set([
12099
+ "PATH",
12100
+ "HOME",
12101
+ "LANG",
12102
+ "LC_ALL",
12103
+ "TERM",
12104
+ "PYTHONPATH",
12105
+ "NODE_PATH"
12106
+ ]);
12107
+ ENV_DENY_PATTERNS = [
12108
+ "PRIVATE_KEY",
12109
+ "SECRET_KEY",
12110
+ "API_SECRET",
12111
+ "AUTH_DISABLED",
12112
+ "COLD_STORAGE",
12113
+ "PASSWORD",
12114
+ "TOKEN"
12115
+ ];
12116
+ }
12117
+ });
12118
+
12119
+ // src/tools.ts
12120
+ import { readFileSync as readFileSync23, existsSync as existsSync23 } from "fs";
12121
+ import { resolve as resolve19, basename as basename7 } from "path";
11740
12122
  function prefix() {
11741
12123
  return getConfig().toolPrefix;
11742
12124
  }
11743
- function p17(name) {
12125
+ function p18(name) {
11744
12126
  return `${prefix()}_${name}`;
11745
12127
  }
11746
12128
  function stripPrefix3(name) {
@@ -11771,7 +12153,7 @@ function ensureIndexes(dataDb2, codegraphDb2, force = false) {
11771
12153
  if (config.python?.root) {
11772
12154
  const pythonRoot = config.python.root;
11773
12155
  const excludeDirs = config.python.exclude_dirs || ["__pycache__", ".venv", "venv", ".mypy_cache", ".pytest_cache"];
11774
- if (force || isPythonDataStale(dataDb2, resolve18(getProjectRoot(), pythonRoot))) {
12156
+ if (force || isPythonDataStale(dataDb2, resolve19(getProjectRoot(), pythonRoot))) {
11775
12157
  const pyImports = buildPythonImportIndex(dataDb2, pythonRoot, excludeDirs);
11776
12158
  results.push(`Python imports: ${pyImports}`);
11777
12159
  const pyRoutes = buildPythonRouteIndex(dataDb2, pythonRoot, excludeDirs);
@@ -11820,11 +12202,13 @@ function getToolDefinitions() {
11820
12202
  ...getKnowledgeToolDefinitions(),
11821
12203
  // Python code intelligence tools
11822
12204
  ...getPythonToolDefinitions(),
12205
+ // MCP bridge tools (cross-project tool mesh)
12206
+ ...getMcpBridgeToolDefinitions(),
11823
12207
  // License tools (always available)
11824
12208
  ...getLicenseToolDefinitions(),
11825
12209
  // Core tools
11826
12210
  {
11827
- name: p17("sync"),
12211
+ name: p18("sync"),
11828
12212
  description: "Force rebuild all indexes (import edges, tRPC mappings, page deps, middleware tree). Run this after significant code changes.",
11829
12213
  inputSchema: {
11830
12214
  type: "object",
@@ -11833,7 +12217,7 @@ function getToolDefinitions() {
11833
12217
  }
11834
12218
  },
11835
12219
  {
11836
- name: p17("context"),
12220
+ name: p18("context"),
11837
12221
  description: "Get context for a file: applicable rules, pattern warnings, schema mismatch alerts, and whether the file is in the middleware import tree.",
11838
12222
  inputSchema: {
11839
12223
  type: "object",
@@ -11845,7 +12229,7 @@ function getToolDefinitions() {
11845
12229
  },
11846
12230
  ...config.framework.router === "trpc" ? [
11847
12231
  {
11848
- name: p17("trpc_map"),
12232
+ name: p18("trpc_map"),
11849
12233
  description: "Map tRPC procedures to their UI call sites. Find which components call a router, which procedures have no UI callers, or list all procedures for a router.",
11850
12234
  inputSchema: {
11851
12235
  type: "object",
@@ -11858,7 +12242,7 @@ function getToolDefinitions() {
11858
12242
  }
11859
12243
  },
11860
12244
  {
11861
- name: p17("coupling_check"),
12245
+ name: p18("coupling_check"),
11862
12246
  description: "Automated coupling check. Finds all procedures with zero UI callers and components not rendered in any page.",
11863
12247
  inputSchema: {
11864
12248
  type: "object",
@@ -11874,7 +12258,7 @@ function getToolDefinitions() {
11874
12258
  }
11875
12259
  ] : [],
11876
12260
  {
11877
- name: p17("impact"),
12261
+ name: p18("impact"),
11878
12262
  description: "Full impact analysis for a file: which pages are affected, which database tables are in the chain, middleware tree membership, domain crossings.",
11879
12263
  inputSchema: {
11880
12264
  type: "object",
@@ -11885,7 +12269,7 @@ function getToolDefinitions() {
11885
12269
  }
11886
12270
  },
11887
12271
  ...config.domains.length > 0 ? [{
11888
- name: p17("domains"),
12272
+ name: p18("domains"),
11889
12273
  description: "Domain boundary information. Classify a file into its domain, show cross-domain imports, or list all files in a domain.",
11890
12274
  inputSchema: {
11891
12275
  type: "object",
@@ -11898,7 +12282,7 @@ function getToolDefinitions() {
11898
12282
  }
11899
12283
  }] : [],
11900
12284
  ...config.framework.orm === "prisma" ? [{
11901
- name: p17("schema"),
12285
+ name: p18("schema"),
11902
12286
  description: "Prisma schema cross-reference. Show columns for a table, detect mismatches between code and schema, or verify column references in a file.",
11903
12287
  inputSchema: {
11904
12288
  type: "object",
@@ -11916,7 +12300,7 @@ async function handleToolCall(name, args2, dataDb2, codegraphDb2) {
11916
12300
  const userTier = await getCurrentTier();
11917
12301
  const requiredTier = getToolTier(name);
11918
12302
  if (!isToolAllowed(name, userTier)) {
11919
- return text17(`This tool requires ${requiredTier} tier. Current tier: ${userTier}. Upgrade at https://massu.ai/pricing`);
12303
+ return text18(`This tool requires ${requiredTier} tier. Current tier: ${userTier}. Upgrade at https://massu.ai/pricing`);
11920
12304
  }
11921
12305
  const syncMessage = ensureIndexes(dataDb2, codegraphDb2);
11922
12306
  const pfx = prefix();
@@ -12034,6 +12418,9 @@ async function handleToolCall(name, args2, dataDb2, codegraphDb2) {
12034
12418
  if (isPythonTool(name)) {
12035
12419
  return handlePythonToolCall(name, args2, dataDb2);
12036
12420
  }
12421
+ if (isMcpBridgeTool(name)) {
12422
+ return await handleMcpBridgeToolCall(name, args2);
12423
+ }
12037
12424
  if (isLicenseTool(name)) {
12038
12425
  const memDb = getMemoryDb();
12039
12426
  try {
@@ -12059,26 +12446,26 @@ async function handleToolCall(name, args2, dataDb2, codegraphDb2) {
12059
12446
  case "schema":
12060
12447
  return handleSchema(args2);
12061
12448
  default:
12062
- return text17(`Unknown tool: ${name}`);
12449
+ return text18(`Unknown tool: ${name}`);
12063
12450
  }
12064
12451
  } catch (error) {
12065
12452
  const msg = error instanceof Error ? error.message : String(error);
12066
12453
  const safeMsg = msg.split("\n")[0].replace(/\/(Users|home|var|tmp)\/[^\s:]+/g, "<path>");
12067
- return text17(`Error in ${name}: ${safeMsg}`);
12454
+ return text18(`Error in ${name}: ${safeMsg}`);
12068
12455
  }
12069
12456
  }
12070
- function text17(content) {
12457
+ function text18(content) {
12071
12458
  return { content: [{ type: "text", text: content }] };
12072
12459
  }
12073
12460
  function handleSync(dataDb2, codegraphDb2) {
12074
12461
  const result = ensureIndexes(dataDb2, codegraphDb2, true);
12075
12462
  try {
12076
12463
  const scanResult = runFeatureScan(dataDb2);
12077
- return text17(`${result}
12464
+ return text18(`${result}
12078
12465
 
12079
12466
  Feature scan: ${scanResult.registered} features registered (${scanResult.fromProcedures} from procedures, ${scanResult.fromPages} from pages, ${scanResult.fromComponents} from components)`);
12080
12467
  } catch (error) {
12081
- return text17(`${result}
12468
+ return text18(`${result}
12082
12469
 
12083
12470
  Feature scan failed: ${error instanceof Error ? error.message : String(error)}`);
12084
12471
  }
@@ -12170,9 +12557,9 @@ function handleContext(file, dataDb2, codegraphDb2) {
12170
12557
  try {
12171
12558
  const resolvedPaths = getResolvedPaths();
12172
12559
  const root = getProjectRoot();
12173
- const absFilePath = ensureWithinRoot(resolve18(resolvedPaths.srcDir, "..", file), root);
12174
- if (existsSync22(absFilePath)) {
12175
- const fileContent = readFileSync22(absFilePath, "utf-8").slice(0, 3e3);
12560
+ const absFilePath = ensureWithinRoot(resolve19(resolvedPaths.srcDir, "..", file), root);
12561
+ if (existsSync23(absFilePath)) {
12562
+ const fileContent = readFileSync23(absFilePath, "utf-8").slice(0, 3e3);
12176
12563
  const keywords = [];
12177
12564
  if (fileContent.includes("ctx.db")) keywords.push("database", "schema");
12178
12565
  if (fileContent.includes("BigInt") || fileContent.includes("Decimal")) keywords.push("BigInt", "serialization");
@@ -12294,7 +12681,7 @@ function handleContext(file, dataDb2, codegraphDb2) {
12294
12681
  } finally {
12295
12682
  memDb?.close();
12296
12683
  }
12297
- return text17(lines.join("\n") || "No context available for this file.");
12684
+ return text18(lines.join("\n") || "No context available for this file.");
12298
12685
  }
12299
12686
  function handleTrpcMap(args2, dataDb2) {
12300
12687
  const lines = [];
@@ -12369,7 +12756,7 @@ function handleTrpcMap(args2, dataDb2) {
12369
12756
  lines.push('Use { router: "name" } to see details for a specific router.');
12370
12757
  lines.push("Use { uncoupled: true } to see all procedures without UI callers.");
12371
12758
  }
12372
- return text17(lines.join("\n"));
12759
+ return text18(lines.join("\n"));
12373
12760
  }
12374
12761
  function handleCouplingCheck(args2, dataDb2, codegraphDb2) {
12375
12762
  const lines = [];
@@ -12427,7 +12814,7 @@ function handleCouplingCheck(args2, dataDb2, codegraphDb2) {
12427
12814
  }
12428
12815
  const totalIssues = uncoupledProcs.length + orphanComponents.length;
12429
12816
  lines.push(`### RESULT: ${totalIssues === 0 ? "PASS" : `FAIL (${totalIssues} issues)`}`);
12430
- return text17(lines.join("\n"));
12817
+ return text18(lines.join("\n"));
12431
12818
  }
12432
12819
  function handleImpact2(file, dataDb2, codegraphDb2) {
12433
12820
  const lines = [];
@@ -12435,9 +12822,9 @@ function handleImpact2(file, dataDb2, codegraphDb2) {
12435
12822
  lines.push("");
12436
12823
  const affectedPages = findAffectedPages(dataDb2, file);
12437
12824
  if (affectedPages.length > 0) {
12438
- const portals = [...new Set(affectedPages.map((p18) => p18.portal))];
12439
- const allTables = [...new Set(affectedPages.flatMap((p18) => p18.tables))];
12440
- const allRouters = [...new Set(affectedPages.flatMap((p18) => p18.routers))];
12825
+ const portals = [...new Set(affectedPages.map((p19) => p19.portal))];
12826
+ const allTables = [...new Set(affectedPages.flatMap((p19) => p19.tables))];
12827
+ const allRouters = [...new Set(affectedPages.flatMap((p19) => p19.routers))];
12441
12828
  lines.push(`### Pages Affected: ${affectedPages.length}`);
12442
12829
  for (const page of affectedPages) {
12443
12830
  lines.push(`- ${page.route} (${page.portal})`);
@@ -12489,7 +12876,7 @@ function handleImpact2(file, dataDb2, codegraphDb2) {
12489
12876
  lines.push(`- -> ${crossing}`);
12490
12877
  }
12491
12878
  }
12492
- return text17(lines.join("\n"));
12879
+ return text18(lines.join("\n"));
12493
12880
  }
12494
12881
  function handleDomains(args2, dataDb2, codegraphDb2) {
12495
12882
  const lines = [];
@@ -12534,7 +12921,7 @@ function handleDomains(args2, dataDb2, codegraphDb2) {
12534
12921
  for (const r of files.routers) lines.push(`- ${r}`);
12535
12922
  lines.push("");
12536
12923
  lines.push(`### Pages (${files.pages.length})`);
12537
- for (const p18 of files.pages) lines.push(`- ${p18}`);
12924
+ for (const p19 of files.pages) lines.push(`- ${p19}`);
12538
12925
  lines.push("");
12539
12926
  lines.push(`### Components (${files.components.length})`);
12540
12927
  for (const c of files.components.slice(0, 30)) lines.push(`- ${c}`);
@@ -12545,7 +12932,7 @@ function handleDomains(args2, dataDb2, codegraphDb2) {
12545
12932
  lines.push(`- **${domain.name}**: ${domain.routers.length} router patterns, imports from: ${domain.allowedImportsFrom.join(", ") || "any"}`);
12546
12933
  }
12547
12934
  }
12548
- return text17(lines.join("\n"));
12935
+ return text18(lines.join("\n"));
12549
12936
  }
12550
12937
  function handleSchema(args2) {
12551
12938
  const lines = [];
@@ -12570,7 +12957,7 @@ function handleSchema(args2) {
12570
12957
  const tableName = args2.table;
12571
12958
  const model = models.find((m) => m.tableName === tableName || m.name === tableName);
12572
12959
  if (!model) {
12573
- return text17(`Model/table "${tableName}" not found in Prisma schema.`);
12960
+ return text18(`Model/table "${tableName}" not found in Prisma schema.`);
12574
12961
  }
12575
12962
  lines.push(`## ${model.name} (table: ${model.tableName})`);
12576
12963
  lines.push("");
@@ -12596,11 +12983,11 @@ function handleSchema(args2) {
12596
12983
  lines.push("Checking all column references against Prisma schema...");
12597
12984
  lines.push("");
12598
12985
  const projectRoot = getProjectRoot();
12599
- const absPath = ensureWithinRoot(resolve18(projectRoot, file), projectRoot);
12600
- if (!existsSync22(absPath)) {
12601
- return text17(`File not found: ${file}`);
12986
+ const absPath = ensureWithinRoot(resolve19(projectRoot, file), projectRoot);
12987
+ if (!existsSync23(absPath)) {
12988
+ return text18(`File not found: ${file}`);
12602
12989
  }
12603
- const source = readFileSync22(absPath, "utf-8");
12990
+ const source = readFileSync23(absPath, "utf-8");
12604
12991
  const config = getConfig();
12605
12992
  const dbPattern = config.dbAccessPattern ?? "ctx.db.{table}";
12606
12993
  const regexStr = dbPattern.replace(/[.*+?^${}()|[\]\\]/g, "\\$&").replace("\\{table\\}", "(\\w+)");
@@ -12633,7 +13020,7 @@ function handleSchema(args2) {
12633
13020
  }
12634
13021
  }
12635
13022
  }
12636
- return text17(lines.join("\n"));
13023
+ return text18(lines.join("\n"));
12637
13024
  }
12638
13025
  var init_tools = __esm({
12639
13026
  "src/tools.ts"() {
@@ -12673,13 +13060,14 @@ var init_tools = __esm({
12673
13060
  init_python_tools();
12674
13061
  init_config();
12675
13062
  init_license();
13063
+ init_mcp_bridge_tools();
12676
13064
  }
12677
13065
  });
12678
13066
 
12679
13067
  // src/server.ts
12680
13068
  var server_exports = {};
12681
- import { readFileSync as readFileSync23 } from "fs";
12682
- import { resolve as resolve19, dirname as dirname11 } from "path";
13069
+ import { readFileSync as readFileSync24 } from "fs";
13070
+ import { resolve as resolve20, dirname as dirname11 } from "path";
12683
13071
  import { fileURLToPath as fileURLToPath4 } from "url";
12684
13072
  function getDb() {
12685
13073
  if (!codegraphDb) codegraphDb = getCodeGraphDb();
@@ -12774,7 +13162,7 @@ var init_server = __esm({
12774
13162
  __dirname4 = dirname11(fileURLToPath4(import.meta.url));
12775
13163
  PKG_VERSION = (() => {
12776
13164
  try {
12777
- const pkg = JSON.parse(readFileSync23(resolve19(__dirname4, "..", "package.json"), "utf-8"));
13165
+ const pkg = JSON.parse(readFileSync24(resolve20(__dirname4, "..", "package.json"), "utf-8"));
12778
13166
  return pkg.version ?? "0.0.0";
12779
13167
  } catch {
12780
13168
  return "0.0.0";
@@ -12838,8 +13226,8 @@ var init_server = __esm({
12838
13226
  });
12839
13227
 
12840
13228
  // src/cli.ts
12841
- import { readFileSync as readFileSync24 } from "fs";
12842
- import { resolve as resolve20, dirname as dirname12 } from "path";
13229
+ import { readFileSync as readFileSync25 } from "fs";
13230
+ import { resolve as resolve21, dirname as dirname12 } from "path";
12843
13231
  import { fileURLToPath as fileURLToPath5 } from "url";
12844
13232
  var __filename4 = fileURLToPath5(import.meta.url);
12845
13233
  var __dirname5 = dirname12(__filename4);
@@ -12913,7 +13301,7 @@ Documentation: https://massu.ai/docs
12913
13301
  }
12914
13302
  function printVersion() {
12915
13303
  try {
12916
- const pkg = JSON.parse(readFileSync24(resolve20(__dirname5, "../package.json"), "utf-8"));
13304
+ const pkg = JSON.parse(readFileSync25(resolve21(__dirname5, "../package.json"), "utf-8"));
12917
13305
  console.log(`massu v${pkg.version}`);
12918
13306
  } catch {
12919
13307
  console.log("massu v0.1.0");