@massu/core 0.6.1 → 0.6.3

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.
Files changed (29) hide show
  1. package/commands/_shared-preamble.md +14 -0
  2. package/commands/_shared-references/verification-table.md +0 -3
  3. package/commands/massu-ci-fix.md +2 -2
  4. package/commands/massu-gap-enhancement-analyzer.md +85 -345
  5. package/commands/massu-golden-path/references/approval-points.md +9 -12
  6. package/commands/massu-golden-path/references/competitive-mode.md +9 -7
  7. package/commands/massu-golden-path/references/error-handling.md +4 -2
  8. package/commands/massu-golden-path/references/phase-0-requirements.md +3 -3
  9. package/commands/massu-golden-path/references/phase-1-plan-creation.md +41 -52
  10. package/commands/massu-golden-path/references/phase-2-implementation.md +50 -157
  11. package/commands/massu-golden-path/references/phase-2.5-gap-analyzer.md +14 -48
  12. package/commands/massu-golden-path/references/phase-3-simplify.md +5 -5
  13. package/commands/massu-golden-path/references/phase-4-commit.md +20 -46
  14. package/commands/massu-golden-path/references/phase-5-push.md +14 -47
  15. package/commands/massu-golden-path/references/phase-6-completion.md +8 -58
  16. package/commands/massu-golden-path.md +27 -43
  17. package/commands/massu-loop/references/checkpoint-audit.md +14 -18
  18. package/commands/massu-loop/references/guardrails.md +3 -3
  19. package/commands/massu-loop/references/iteration-structure.md +46 -14
  20. package/commands/massu-loop/references/loop-controller.md +72 -63
  21. package/commands/massu-loop/references/plan-extraction.md +19 -11
  22. package/commands/massu-loop/references/vr-plan-spec.md +20 -28
  23. package/commands/massu-loop.md +36 -56
  24. package/commands/massu-review.md +2 -2
  25. package/dist/cli.js +442 -81
  26. package/package.json +1 -1
  27. package/src/mcp-bridge-tools.ts +459 -0
  28. package/src/tools.ts +8 -0
  29. package/commands/massu-golden-path/references/phase-3.5-security-audit.md +0 -108
package/dist/cli.js CHANGED
@@ -989,12 +989,12 @@ function addSummary(db, sessionId, summary) {
989
989
  Math.floor(now.getTime() / 1e3)
990
990
  );
991
991
  }
992
- function addUserPrompt(db, sessionId, text18, promptNumber) {
992
+ function addUserPrompt(db, sessionId, text19, promptNumber) {
993
993
  const now = /* @__PURE__ */ new Date();
994
994
  db.prepare(`
995
995
  INSERT INTO user_prompts (session_id, prompt_text, prompt_number, created_at, created_at_epoch)
996
996
  VALUES (?, ?, ?, ?, ?)
997
- `).run(sessionId, text18, promptNumber, now.toISOString(), Math.floor(now.getTime() / 1e3));
997
+ `).run(sessionId, text19, promptNumber, now.toISOString(), Math.floor(now.getTime() / 1e3));
998
998
  }
999
999
  function searchObservations(db, query, opts) {
1000
1000
  const limit = opts?.limit ?? 20;
@@ -3980,7 +3980,7 @@ function parseFromImportLine(line, lineNum) {
3980
3980
  function parsePlainImportLine(line, lineNum) {
3981
3981
  const results = [];
3982
3982
  const rest = line.replace(/^import\s+/, "");
3983
- const parts = rest.split(",").map((p18) => p18.trim()).filter((p18) => p18.length > 0);
3983
+ const parts = rest.split(",").map((p19) => p19.trim()).filter((p19) => p19.length > 0);
3984
3984
  for (const part of parts) {
3985
3985
  const asMatch = part.match(/^(\S+)\s+as\s+(\S+)$/);
3986
3986
  const moduleName = asMatch ? asMatch[1] : part;
@@ -4141,8 +4141,8 @@ function joinLogicalLines(source) {
4141
4141
  }
4142
4142
  return logical;
4143
4143
  }
4144
- function extractQuotedString(text18) {
4145
- const m = text18.match(/(['"])(.*?)\1/);
4144
+ function extractQuotedString(text19) {
4145
+ const m = text19.match(/(['"])(.*?)\1/);
4146
4146
  return m ? m[2] : null;
4147
4147
  }
4148
4148
  function extractResponseModel(argStr) {
@@ -4209,8 +4209,8 @@ function parsePythonRoutes(source) {
4209
4209
  const logicalLines = joinLogicalLines(source);
4210
4210
  const pendingDecorators = [];
4211
4211
  for (let i = 0; i < logicalLines.length; i++) {
4212
- const { text: text18, startLine } = logicalLines[i];
4213
- const trimmed = text18.trim();
4212
+ const { text: text19, startLine } = logicalLines[i];
4213
+ const trimmed = text19.trim();
4214
4214
  const decoratorMatch = trimmed.match(
4215
4215
  /^@\s*(\w+)\s*\.\s*(\w+)\s*\((.*)\)\s*$/s
4216
4216
  );
@@ -4445,11 +4445,11 @@ function parseRelationship(argsStr) {
4445
4445
  back_populates: bpMatch ? bpMatch[1] : null
4446
4446
  };
4447
4447
  }
4448
- function findClosingParen(text18, openIndex, openChar, closeChar) {
4448
+ function findClosingParen(text19, openIndex, openChar, closeChar) {
4449
4449
  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) {
4450
+ for (let i = openIndex + 1; i < text19.length; i++) {
4451
+ if (text19[i] === openChar) depth++;
4452
+ else if (text19[i] === closeChar) {
4453
4453
  depth--;
4454
4454
  if (depth === 0) return i;
4455
4455
  }
@@ -4725,16 +4725,16 @@ function extractTableAndColumn(argsStr) {
4725
4725
  }
4726
4726
  return { table, column };
4727
4727
  }
4728
- function splitTopLevelCommas(text18) {
4728
+ function splitTopLevelCommas(text19) {
4729
4729
  const parts = [];
4730
4730
  let current = "";
4731
4731
  let depth = 0;
4732
4732
  let inString = null;
4733
- for (let i = 0; i < text18.length; i++) {
4734
- const ch = text18[i];
4733
+ for (let i = 0; i < text19.length; i++) {
4734
+ const ch = text19[i];
4735
4735
  if (inString) {
4736
4736
  current += ch;
4737
- if (ch === inString && text18[i - 1] !== "\\") {
4737
+ if (ch === inString && text19[i - 1] !== "\\") {
4738
4738
  inString = null;
4739
4739
  }
4740
4740
  continue;
@@ -5579,7 +5579,7 @@ function handleDocsAudit(args2) {
5579
5579
  const fileName = basename3(file);
5580
5580
  if (mapping.routers.includes(fileName)) {
5581
5581
  const procedures = extractProcedureNames(file);
5582
- const undocumented = procedures.filter((p18) => !contentMentions(content, p18));
5582
+ const undocumented = procedures.filter((p19) => !contentMentions(content, p19));
5583
5583
  if (undocumented.length > 0) {
5584
5584
  staleReasons.push(
5585
5585
  `Router ${fileName}: procedures not documented: ${undocumented.slice(0, 5).join(", ")}${undocumented.length > 5 ? ` (+${undocumented.length - 5} more)` : ""}`
@@ -5905,26 +5905,26 @@ function handleToolPatterns(args2, db) {
5905
5905
  case "tool":
5906
5906
  lines.push("| Tool | Calls | Successes | Failures | Success Rate | Avg Output Size | Avg Input Size |");
5907
5907
  lines.push("|------|-------|-----------|----------|--------------|-----------------|----------------|");
5908
- for (const p18 of patterns) {
5909
- const total = p18.call_count;
5910
- const successes = p18.successes;
5911
- const failures = p18.failures;
5908
+ for (const p19 of patterns) {
5909
+ const total = p19.call_count;
5910
+ const successes = p19.successes;
5911
+ const failures = p19.failures;
5912
5912
  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)} |`);
5913
+ lines.push(`| ${p19.tool_name} | ${total} | ${successes} | ${failures} | ${rate}% | ${Math.round(p19.avg_output_size ?? 0)} | ${Math.round(p19.avg_input_size ?? 0)} |`);
5914
5914
  }
5915
5915
  break;
5916
5916
  case "session":
5917
5917
  lines.push("| Session | Calls | Unique Tools | Successes | Failures | Avg Output Size |");
5918
5918
  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)} |`);
5919
+ for (const p19 of patterns) {
5920
+ 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
5921
  }
5922
5922
  break;
5923
5923
  case "day":
5924
5924
  lines.push("| Day | Calls | Unique Tools | Successes |");
5925
5925
  lines.push("|-----|-------|--------------|-----------|");
5926
- for (const p18 of patterns) {
5927
- lines.push(`| ${p18.day} | ${p18.call_count} | ${p18.unique_tools} | ${p18.successes} |`);
5926
+ for (const p19 of patterns) {
5927
+ lines.push(`| ${p19.day} | ${p19.call_count} | ${p19.unique_tools} | ${p19.successes} |`);
5928
5928
  }
5929
5929
  break;
5930
5930
  }
@@ -6572,15 +6572,15 @@ function handleDetail2(args2, db) {
6572
6572
  }
6573
6573
  if (detail.procedures.length > 0) {
6574
6574
  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 + ")" : ""}`);
6575
+ for (const p19 of detail.procedures) {
6576
+ lines.push(`- ${p19.router_name}.${p19.procedure_name}${p19.procedure_type ? " (" + p19.procedure_type + ")" : ""}`);
6577
6577
  }
6578
6578
  lines.push("");
6579
6579
  }
6580
6580
  if (detail.pages.length > 0) {
6581
6581
  lines.push(`### Pages (${detail.pages.length})`);
6582
- for (const p18 of detail.pages) {
6583
- lines.push(`- ${p18.page_route}${p18.portal ? " (" + p18.portal + ")" : ""}`);
6582
+ for (const p19 of detail.pages) {
6583
+ lines.push(`- ${p19.page_route}${p19.portal ? " (" + p19.portal + ")" : ""}`);
6584
6584
  }
6585
6585
  lines.push("");
6586
6586
  }
@@ -6663,7 +6663,7 @@ function handleValidate(args2, db) {
6663
6663
  lines.push(` Missing components: ${item.missing_components.join(", ")}`);
6664
6664
  }
6665
6665
  if (item.missing_procedures.length > 0) {
6666
- lines.push(` Missing procedures: ${item.missing_procedures.map((p18) => `${p18.router}.${p18.procedure}`).join(", ")}`);
6666
+ lines.push(` Missing procedures: ${item.missing_procedures.map((p19) => `${p19.router}.${p19.procedure}`).join(", ")}`);
6667
6667
  }
6668
6668
  if (item.missing_pages.length > 0) {
6669
6669
  lines.push(` Missing pages: ${item.missing_pages.join(", ")}`);
@@ -6710,14 +6710,14 @@ function handleRegister(args2, db) {
6710
6710
  }
6711
6711
  const procedures = args2.procedures;
6712
6712
  if (procedures) {
6713
- for (const p18 of procedures) {
6714
- linkProcedure(db, featureId, p18.router, p18.procedure, p18.type);
6713
+ for (const p19 of procedures) {
6714
+ linkProcedure(db, featureId, p19.router, p19.procedure, p19.type);
6715
6715
  }
6716
6716
  }
6717
6717
  const pages = args2.pages;
6718
6718
  if (pages) {
6719
- for (const p18 of pages) {
6720
- linkPage(db, featureId, p18.route, p18.portal);
6719
+ for (const p19 of pages) {
6720
+ linkPage(db, featureId, p19.route, p19.portal);
6721
6721
  }
6722
6722
  }
6723
6723
  logChange(db, featureId, "created", `Registered via ${p4("sentinel_register")}`);
@@ -9652,22 +9652,22 @@ function buildCrossReferences(db) {
9652
9652
  }
9653
9653
  const chunks = db.prepare("SELECT id, content, metadata FROM knowledge_chunks").all();
9654
9654
  for (const chunk of chunks) {
9655
- const text18 = chunk.content;
9656
- const crRefs = text18.match(/CR-\d+/g);
9655
+ const text19 = chunk.content;
9656
+ const crRefs = text19.match(/CR-\d+/g);
9657
9657
  if (crRefs) {
9658
9658
  for (const cr of [...new Set(crRefs)]) {
9659
9659
  insertEdge.run("chunk", String(chunk.id), "cr", cr, "references");
9660
9660
  edgeCount++;
9661
9661
  }
9662
9662
  }
9663
- const vrRefs = text18.match(/VR-[\w-]+/g);
9663
+ const vrRefs = text19.match(/VR-[\w-]+/g);
9664
9664
  if (vrRefs) {
9665
9665
  for (const vr of [...new Set(vrRefs)]) {
9666
9666
  insertEdge.run("chunk", String(chunk.id), "vr", vr, "references");
9667
9667
  edgeCount++;
9668
9668
  }
9669
9669
  }
9670
- const incRefs = text18.match(/Incident #(\d+)/gi);
9670
+ const incRefs = text19.match(/Incident #(\d+)/gi);
9671
9671
  if (incRefs) {
9672
9672
  for (const ref of incRefs) {
9673
9673
  const numMatch = ref.match(/\d+/);
@@ -9713,7 +9713,7 @@ function indexAllKnowledge(db) {
9713
9713
  }
9714
9714
  if (existsSync19(paths.docsDir)) {
9715
9715
  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)));
9716
+ const docsFiles = discoverMarkdownFiles(paths.docsDir).filter((f) => !f.includes("/plans/") && !excludePatterns.some((p19) => f.includes(p19)));
9717
9717
  files.push(...docsFiles);
9718
9718
  }
9719
9719
  const now = /* @__PURE__ */ new Date();
@@ -9880,7 +9880,7 @@ function isKnowledgeStale(db) {
9880
9880
  }
9881
9881
  if (existsSync19(paths.docsDir)) {
9882
9882
  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)));
9883
+ const docsFiles = discoverMarkdownFiles(paths.docsDir).filter((f) => !f.includes("/plans/") && !excludePatterns.some((p19) => f.includes(p19)));
9884
9884
  files.push(...docsFiles);
9885
9885
  }
9886
9886
  for (const filePath of files) {
@@ -11734,13 +11734,368 @@ var init_python_tools = __esm({
11734
11734
  }
11735
11735
  });
11736
11736
 
11737
- // src/tools.ts
11737
+ // src/mcp-bridge-tools.ts
11738
+ import { spawn } from "child_process";
11738
11739
  import { readFileSync as readFileSync22, existsSync as existsSync22 } from "fs";
11739
- import { resolve as resolve18, basename as basename7 } from "path";
11740
+ import { resolve as resolve18 } from "path";
11741
+ function p17(baseName) {
11742
+ return `${getConfig().toolPrefix}_${baseName}`;
11743
+ }
11744
+ function buildSubprocessEnv() {
11745
+ const env = {};
11746
+ const projectName = getConfig().project?.name?.toUpperCase() || "";
11747
+ const safePrefixes = projectName ? [`${projectName}_CONFIG_`, `${projectName}_LOG_`] : [];
11748
+ for (const [key, value] of Object.entries(process.env)) {
11749
+ if (!value) continue;
11750
+ if (ENV_DENY_PATTERNS.some((pat) => key.includes(pat))) continue;
11751
+ if (ENV_ALLOW_LIST.has(key)) {
11752
+ env[key] = value;
11753
+ } else if (safePrefixes.length > 0 && safePrefixes.some((pfx) => key.startsWith(pfx))) {
11754
+ env[key] = value;
11755
+ }
11756
+ }
11757
+ return env;
11758
+ }
11759
+ function loadMcpConfig() {
11760
+ const root = getProjectRoot();
11761
+ const mcpPath = resolve18(root, ".mcp.json");
11762
+ if (!existsSync22(mcpPath)) return {};
11763
+ try {
11764
+ const raw = JSON.parse(readFileSync22(mcpPath, "utf-8"));
11765
+ const servers = {};
11766
+ const mcpServers = raw.mcpServers || {};
11767
+ const pfx = getConfig().toolPrefix;
11768
+ for (const [name, config] of Object.entries(mcpServers)) {
11769
+ if (name === pfx) continue;
11770
+ servers[name] = config;
11771
+ }
11772
+ return servers;
11773
+ } catch (e) {
11774
+ console.error("[mcp-bridge] Failed to parse .mcp.json:", e);
11775
+ return {};
11776
+ }
11777
+ }
11778
+ async function mcpRequest(conn, method, params = {}) {
11779
+ if (!conn.process || !conn.process.stdin || !conn.process.stdout) {
11780
+ throw new Error("MCP process not connected");
11781
+ }
11782
+ conn.requestId++;
11783
+ const request = {
11784
+ jsonrpc: "2.0",
11785
+ id: conn.requestId,
11786
+ method,
11787
+ params
11788
+ };
11789
+ return new Promise((resolve22, reject) => {
11790
+ const timeout = setTimeout(() => {
11791
+ conn.process?.stdout?.removeListener("data", onData);
11792
+ reject(new Error(`MCP request timed out: ${method}`));
11793
+ }, 3e4);
11794
+ let buffer2 = "";
11795
+ const onData = (data) => {
11796
+ buffer2 += data.toString("utf-8");
11797
+ const lines = buffer2.split("\n");
11798
+ buffer2 = lines.pop() || "";
11799
+ for (const line of lines) {
11800
+ if (!line.trim()) continue;
11801
+ try {
11802
+ const response = JSON.parse(line);
11803
+ if (response.id === conn.requestId) {
11804
+ clearTimeout(timeout);
11805
+ conn.process?.stdout?.removeListener("data", onData);
11806
+ if (response.error) {
11807
+ reject(new Error(`MCP error ${response.error.code}: ${response.error.message}`));
11808
+ } else {
11809
+ resolve22(response.result || {});
11810
+ }
11811
+ return;
11812
+ }
11813
+ } catch (e) {
11814
+ clearTimeout(timeout);
11815
+ conn.process?.stdout?.removeListener("data", onData);
11816
+ reject(new Error(`Failed to parse MCP response: ${e}`));
11817
+ return;
11818
+ }
11819
+ }
11820
+ };
11821
+ conn.process.stdout.on("data", onData);
11822
+ conn.process.stdin.write(JSON.stringify(request) + "\n");
11823
+ });
11824
+ }
11825
+ async function connectToServer(name, config) {
11826
+ const existing = connections.get(name);
11827
+ if (existing?.connected && existing.process && !existing.process.killed) {
11828
+ return existing;
11829
+ }
11830
+ const root = getProjectRoot();
11831
+ const cwd = config.cwd ? resolve18(root, config.cwd) : root;
11832
+ const args2 = config.args || [];
11833
+ const proc = spawn(config.command, args2, {
11834
+ cwd,
11835
+ stdio: ["pipe", "pipe", "pipe"],
11836
+ env: buildSubprocessEnv()
11837
+ });
11838
+ const conn = {
11839
+ config,
11840
+ process: proc,
11841
+ connected: false,
11842
+ tools: [],
11843
+ requestId: 0
11844
+ };
11845
+ try {
11846
+ await mcpRequest(conn, "initialize", {
11847
+ protocolVersion: "2024-11-05",
11848
+ capabilities: {},
11849
+ clientInfo: { name: "massu-mcp-bridge", version: "1.0.0" }
11850
+ });
11851
+ if (proc.stdin) {
11852
+ proc.stdin.write(JSON.stringify({
11853
+ jsonrpc: "2.0",
11854
+ method: "notifications/initialized",
11855
+ params: {}
11856
+ }) + "\n");
11857
+ }
11858
+ conn.connected = true;
11859
+ const toolsResult = await mcpRequest(conn, "tools/list", {});
11860
+ conn.tools = toolsResult.tools || [];
11861
+ connections.set(name, conn);
11862
+ return conn;
11863
+ } catch (e) {
11864
+ if (!proc.killed) proc.kill("SIGTERM");
11865
+ throw e;
11866
+ }
11867
+ }
11868
+ function disconnectServer(name) {
11869
+ const conn = connections.get(name);
11870
+ if (conn) {
11871
+ conn.connected = false;
11872
+ if (conn.process && !conn.process.killed) {
11873
+ conn.process.kill("SIGTERM");
11874
+ const proc = conn.process;
11875
+ setTimeout(() => {
11876
+ if (!proc.killed) {
11877
+ try {
11878
+ proc.kill("SIGKILL");
11879
+ } catch {
11880
+ }
11881
+ }
11882
+ }, 3e3);
11883
+ }
11884
+ connections.delete(name);
11885
+ }
11886
+ }
11887
+ function text17(s) {
11888
+ return { content: [{ type: "text", text: s }] };
11889
+ }
11890
+ function getMcpBridgeToolDefinitions() {
11891
+ return [
11892
+ {
11893
+ name: p17("mcp_servers"),
11894
+ description: "List all configured MCP servers from .mcp.json and their connection status.",
11895
+ inputSchema: {
11896
+ type: "object",
11897
+ properties: {},
11898
+ required: []
11899
+ }
11900
+ },
11901
+ {
11902
+ name: p17("mcp_tools"),
11903
+ description: "List tools available from a specific MCP server. Connects to the server if not already connected.",
11904
+ inputSchema: {
11905
+ type: "object",
11906
+ properties: {
11907
+ server: { type: "string", description: "MCP server name from .mcp.json" }
11908
+ },
11909
+ required: ["server"]
11910
+ }
11911
+ },
11912
+ {
11913
+ name: p17("mcp_call"),
11914
+ description: "Call a tool on a connected MCP server. Connects automatically if needed.",
11915
+ inputSchema: {
11916
+ type: "object",
11917
+ properties: {
11918
+ server: { type: "string", description: "MCP server name from .mcp.json" },
11919
+ tool: { type: "string", description: "Tool name to call on the remote server" },
11920
+ arguments: {
11921
+ type: "object",
11922
+ description: "Arguments to pass to the remote tool",
11923
+ additionalProperties: true
11924
+ }
11925
+ },
11926
+ required: ["server", "tool"]
11927
+ }
11928
+ },
11929
+ {
11930
+ name: p17("mcp_status"),
11931
+ description: "Health check all MCP server connections. Shows which are connected, disconnected, or errored.",
11932
+ inputSchema: {
11933
+ type: "object",
11934
+ properties: {},
11935
+ required: []
11936
+ }
11937
+ }
11938
+ ];
11939
+ }
11940
+ function isMcpBridgeTool(name) {
11941
+ const pfx = getConfig().toolPrefix;
11942
+ return name.startsWith(`${pfx}_mcp_`);
11943
+ }
11944
+ async function handleMcpBridgeToolCall(name, args2) {
11945
+ const pfx = getConfig().toolPrefix;
11946
+ const baseName = name.startsWith(`${pfx}_`) ? name.slice(pfx.length + 1) : name;
11947
+ switch (baseName) {
11948
+ case "mcp_servers":
11949
+ return handleMcpServers();
11950
+ case "mcp_tools":
11951
+ return await handleMcpTools(args2.server);
11952
+ case "mcp_call":
11953
+ return await handleMcpCall(
11954
+ args2.server,
11955
+ args2.tool,
11956
+ args2.arguments || {}
11957
+ );
11958
+ case "mcp_status":
11959
+ return handleMcpStatus();
11960
+ default:
11961
+ return text17(`Unknown MCP bridge tool: ${name}`);
11962
+ }
11963
+ }
11964
+ function handleMcpServers() {
11965
+ const configs = loadMcpConfig();
11966
+ const servers = Object.entries(configs).map(([name, config]) => {
11967
+ const conn = connections.get(name);
11968
+ return {
11969
+ name,
11970
+ command: config.command,
11971
+ args: config.args || [],
11972
+ cwd: config.cwd || ".",
11973
+ status: conn?.connected ? "connected" : "disconnected",
11974
+ toolCount: conn?.tools.length || 0
11975
+ };
11976
+ });
11977
+ if (servers.length === 0) {
11978
+ return text17("No MCP servers configured in .mcp.json (excluding self).");
11979
+ }
11980
+ const lines = ["# MCP Servers\n"];
11981
+ for (const srv of servers) {
11982
+ const status = srv.status === "connected" ? "CONNECTED" : "DISCONNECTED";
11983
+ lines.push(`- **${srv.name}** [${status}] \u2014 \`${srv.command} ${srv.args.join(" ")}\` (${srv.toolCount} tools)`);
11984
+ }
11985
+ return text17(lines.join("\n"));
11986
+ }
11987
+ async function handleMcpTools(server) {
11988
+ const configs = loadMcpConfig();
11989
+ const config = configs[server];
11990
+ if (!config) {
11991
+ return text17(`MCP server "${server}" not found in .mcp.json. Available: ${Object.keys(configs).join(", ") || "none"}`);
11992
+ }
11993
+ try {
11994
+ const conn = await connectToServer(server, config);
11995
+ if (conn.tools.length === 0) {
11996
+ return text17(`MCP server "${server}" is connected but has no tools.`);
11997
+ }
11998
+ const lines = [`# Tools from ${server} (${conn.tools.length})
11999
+ `];
12000
+ for (const tool of conn.tools) {
12001
+ lines.push(`- **${tool.name}**: ${tool.description}`);
12002
+ }
12003
+ return text17(lines.join("\n"));
12004
+ } catch (e) {
12005
+ return text17(`Failed to connect to MCP server "${server}": ${e}`);
12006
+ }
12007
+ }
12008
+ async function handleMcpCall(server, tool, args2) {
12009
+ const configs = loadMcpConfig();
12010
+ const config = configs[server];
12011
+ if (!config) {
12012
+ return text17(`MCP server "${server}" not found in .mcp.json.`);
12013
+ }
12014
+ try {
12015
+ const conn = await connectToServer(server, config);
12016
+ const result = await mcpRequest(conn, "tools/call", { name: tool, arguments: args2 });
12017
+ const content = result.content;
12018
+ if (Array.isArray(content)) {
12019
+ const texts = content.filter((c) => c.type === "text").map((c) => c.text);
12020
+ if (result.isError) {
12021
+ return text17(`MCP tool error: ${texts.join("\n")}`);
12022
+ }
12023
+ return text17(texts.join("\n"));
12024
+ }
12025
+ return text17(JSON.stringify(result, null, 2));
12026
+ } catch (e) {
12027
+ const conn = connections.get(server);
12028
+ if (conn) conn.connected = false;
12029
+ return text17(`MCP call failed (${server}/${tool}): ${e}`);
12030
+ }
12031
+ }
12032
+ function handleMcpStatus() {
12033
+ const configs = loadMcpConfig();
12034
+ const lines = ["# MCP Connection Status\n"];
12035
+ for (const [name] of Object.entries(configs)) {
12036
+ const conn = connections.get(name);
12037
+ if (!conn) {
12038
+ lines.push(`- **${name}**: NOT CONNECTED`);
12039
+ } else if (conn.connected && conn.process && !conn.process.killed) {
12040
+ lines.push(`- **${name}**: HEALTHY (pid=${conn.process.pid}, ${conn.tools.length} tools)`);
12041
+ } else {
12042
+ lines.push(`- **${name}**: DISCONNECTED`);
12043
+ disconnectServer(name);
12044
+ }
12045
+ }
12046
+ if (Object.keys(configs).length === 0) {
12047
+ lines.push("No MCP servers configured.");
12048
+ }
12049
+ return text17(lines.join("\n"));
12050
+ }
12051
+ var connections, ENV_ALLOW_LIST, ENV_DENY_PATTERNS;
12052
+ var init_mcp_bridge_tools = __esm({
12053
+ "src/mcp-bridge-tools.ts"() {
12054
+ "use strict";
12055
+ init_config();
12056
+ connections = /* @__PURE__ */ new Map();
12057
+ process.on("exit", () => {
12058
+ for (const [, conn] of connections) {
12059
+ if (conn.process && !conn.process.killed) {
12060
+ try {
12061
+ conn.process.kill("SIGTERM");
12062
+ } catch {
12063
+ }
12064
+ }
12065
+ }
12066
+ });
12067
+ process.on("SIGTERM", () => {
12068
+ for (const [name] of connections) disconnectServer(name);
12069
+ process.exit(0);
12070
+ });
12071
+ ENV_ALLOW_LIST = /* @__PURE__ */ new Set([
12072
+ "PATH",
12073
+ "HOME",
12074
+ "LANG",
12075
+ "LC_ALL",
12076
+ "TERM",
12077
+ "PYTHONPATH",
12078
+ "NODE_PATH"
12079
+ ]);
12080
+ ENV_DENY_PATTERNS = [
12081
+ "PRIVATE_KEY",
12082
+ "SECRET_KEY",
12083
+ "API_SECRET",
12084
+ "AUTH_DISABLED",
12085
+ "COLD_STORAGE",
12086
+ "PASSWORD",
12087
+ "TOKEN"
12088
+ ];
12089
+ }
12090
+ });
12091
+
12092
+ // src/tools.ts
12093
+ import { readFileSync as readFileSync23, existsSync as existsSync23 } from "fs";
12094
+ import { resolve as resolve19, basename as basename7 } from "path";
11740
12095
  function prefix() {
11741
12096
  return getConfig().toolPrefix;
11742
12097
  }
11743
- function p17(name) {
12098
+ function p18(name) {
11744
12099
  return `${prefix()}_${name}`;
11745
12100
  }
11746
12101
  function stripPrefix3(name) {
@@ -11771,7 +12126,7 @@ function ensureIndexes(dataDb2, codegraphDb2, force = false) {
11771
12126
  if (config.python?.root) {
11772
12127
  const pythonRoot = config.python.root;
11773
12128
  const excludeDirs = config.python.exclude_dirs || ["__pycache__", ".venv", "venv", ".mypy_cache", ".pytest_cache"];
11774
- if (force || isPythonDataStale(dataDb2, resolve18(getProjectRoot(), pythonRoot))) {
12129
+ if (force || isPythonDataStale(dataDb2, resolve19(getProjectRoot(), pythonRoot))) {
11775
12130
  const pyImports = buildPythonImportIndex(dataDb2, pythonRoot, excludeDirs);
11776
12131
  results.push(`Python imports: ${pyImports}`);
11777
12132
  const pyRoutes = buildPythonRouteIndex(dataDb2, pythonRoot, excludeDirs);
@@ -11820,11 +12175,13 @@ function getToolDefinitions() {
11820
12175
  ...getKnowledgeToolDefinitions(),
11821
12176
  // Python code intelligence tools
11822
12177
  ...getPythonToolDefinitions(),
12178
+ // MCP bridge tools (cross-project tool mesh)
12179
+ ...getMcpBridgeToolDefinitions(),
11823
12180
  // License tools (always available)
11824
12181
  ...getLicenseToolDefinitions(),
11825
12182
  // Core tools
11826
12183
  {
11827
- name: p17("sync"),
12184
+ name: p18("sync"),
11828
12185
  description: "Force rebuild all indexes (import edges, tRPC mappings, page deps, middleware tree). Run this after significant code changes.",
11829
12186
  inputSchema: {
11830
12187
  type: "object",
@@ -11833,7 +12190,7 @@ function getToolDefinitions() {
11833
12190
  }
11834
12191
  },
11835
12192
  {
11836
- name: p17("context"),
12193
+ name: p18("context"),
11837
12194
  description: "Get context for a file: applicable rules, pattern warnings, schema mismatch alerts, and whether the file is in the middleware import tree.",
11838
12195
  inputSchema: {
11839
12196
  type: "object",
@@ -11845,7 +12202,7 @@ function getToolDefinitions() {
11845
12202
  },
11846
12203
  ...config.framework.router === "trpc" ? [
11847
12204
  {
11848
- name: p17("trpc_map"),
12205
+ name: p18("trpc_map"),
11849
12206
  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
12207
  inputSchema: {
11851
12208
  type: "object",
@@ -11858,7 +12215,7 @@ function getToolDefinitions() {
11858
12215
  }
11859
12216
  },
11860
12217
  {
11861
- name: p17("coupling_check"),
12218
+ name: p18("coupling_check"),
11862
12219
  description: "Automated coupling check. Finds all procedures with zero UI callers and components not rendered in any page.",
11863
12220
  inputSchema: {
11864
12221
  type: "object",
@@ -11874,7 +12231,7 @@ function getToolDefinitions() {
11874
12231
  }
11875
12232
  ] : [],
11876
12233
  {
11877
- name: p17("impact"),
12234
+ name: p18("impact"),
11878
12235
  description: "Full impact analysis for a file: which pages are affected, which database tables are in the chain, middleware tree membership, domain crossings.",
11879
12236
  inputSchema: {
11880
12237
  type: "object",
@@ -11885,7 +12242,7 @@ function getToolDefinitions() {
11885
12242
  }
11886
12243
  },
11887
12244
  ...config.domains.length > 0 ? [{
11888
- name: p17("domains"),
12245
+ name: p18("domains"),
11889
12246
  description: "Domain boundary information. Classify a file into its domain, show cross-domain imports, or list all files in a domain.",
11890
12247
  inputSchema: {
11891
12248
  type: "object",
@@ -11898,7 +12255,7 @@ function getToolDefinitions() {
11898
12255
  }
11899
12256
  }] : [],
11900
12257
  ...config.framework.orm === "prisma" ? [{
11901
- name: p17("schema"),
12258
+ name: p18("schema"),
11902
12259
  description: "Prisma schema cross-reference. Show columns for a table, detect mismatches between code and schema, or verify column references in a file.",
11903
12260
  inputSchema: {
11904
12261
  type: "object",
@@ -11916,7 +12273,7 @@ async function handleToolCall(name, args2, dataDb2, codegraphDb2) {
11916
12273
  const userTier = await getCurrentTier();
11917
12274
  const requiredTier = getToolTier(name);
11918
12275
  if (!isToolAllowed(name, userTier)) {
11919
- return text17(`This tool requires ${requiredTier} tier. Current tier: ${userTier}. Upgrade at https://massu.ai/pricing`);
12276
+ return text18(`This tool requires ${requiredTier} tier. Current tier: ${userTier}. Upgrade at https://massu.ai/pricing`);
11920
12277
  }
11921
12278
  const syncMessage = ensureIndexes(dataDb2, codegraphDb2);
11922
12279
  const pfx = prefix();
@@ -12034,6 +12391,9 @@ async function handleToolCall(name, args2, dataDb2, codegraphDb2) {
12034
12391
  if (isPythonTool(name)) {
12035
12392
  return handlePythonToolCall(name, args2, dataDb2);
12036
12393
  }
12394
+ if (isMcpBridgeTool(name)) {
12395
+ return await handleMcpBridgeToolCall(name, args2);
12396
+ }
12037
12397
  if (isLicenseTool(name)) {
12038
12398
  const memDb = getMemoryDb();
12039
12399
  try {
@@ -12059,26 +12419,26 @@ async function handleToolCall(name, args2, dataDb2, codegraphDb2) {
12059
12419
  case "schema":
12060
12420
  return handleSchema(args2);
12061
12421
  default:
12062
- return text17(`Unknown tool: ${name}`);
12422
+ return text18(`Unknown tool: ${name}`);
12063
12423
  }
12064
12424
  } catch (error) {
12065
12425
  const msg = error instanceof Error ? error.message : String(error);
12066
12426
  const safeMsg = msg.split("\n")[0].replace(/\/(Users|home|var|tmp)\/[^\s:]+/g, "<path>");
12067
- return text17(`Error in ${name}: ${safeMsg}`);
12427
+ return text18(`Error in ${name}: ${safeMsg}`);
12068
12428
  }
12069
12429
  }
12070
- function text17(content) {
12430
+ function text18(content) {
12071
12431
  return { content: [{ type: "text", text: content }] };
12072
12432
  }
12073
12433
  function handleSync(dataDb2, codegraphDb2) {
12074
12434
  const result = ensureIndexes(dataDb2, codegraphDb2, true);
12075
12435
  try {
12076
12436
  const scanResult = runFeatureScan(dataDb2);
12077
- return text17(`${result}
12437
+ return text18(`${result}
12078
12438
 
12079
12439
  Feature scan: ${scanResult.registered} features registered (${scanResult.fromProcedures} from procedures, ${scanResult.fromPages} from pages, ${scanResult.fromComponents} from components)`);
12080
12440
  } catch (error) {
12081
- return text17(`${result}
12441
+ return text18(`${result}
12082
12442
 
12083
12443
  Feature scan failed: ${error instanceof Error ? error.message : String(error)}`);
12084
12444
  }
@@ -12170,9 +12530,9 @@ function handleContext(file, dataDb2, codegraphDb2) {
12170
12530
  try {
12171
12531
  const resolvedPaths = getResolvedPaths();
12172
12532
  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);
12533
+ const absFilePath = ensureWithinRoot(resolve19(resolvedPaths.srcDir, "..", file), root);
12534
+ if (existsSync23(absFilePath)) {
12535
+ const fileContent = readFileSync23(absFilePath, "utf-8").slice(0, 3e3);
12176
12536
  const keywords = [];
12177
12537
  if (fileContent.includes("ctx.db")) keywords.push("database", "schema");
12178
12538
  if (fileContent.includes("BigInt") || fileContent.includes("Decimal")) keywords.push("BigInt", "serialization");
@@ -12294,7 +12654,7 @@ function handleContext(file, dataDb2, codegraphDb2) {
12294
12654
  } finally {
12295
12655
  memDb?.close();
12296
12656
  }
12297
- return text17(lines.join("\n") || "No context available for this file.");
12657
+ return text18(lines.join("\n") || "No context available for this file.");
12298
12658
  }
12299
12659
  function handleTrpcMap(args2, dataDb2) {
12300
12660
  const lines = [];
@@ -12369,7 +12729,7 @@ function handleTrpcMap(args2, dataDb2) {
12369
12729
  lines.push('Use { router: "name" } to see details for a specific router.');
12370
12730
  lines.push("Use { uncoupled: true } to see all procedures without UI callers.");
12371
12731
  }
12372
- return text17(lines.join("\n"));
12732
+ return text18(lines.join("\n"));
12373
12733
  }
12374
12734
  function handleCouplingCheck(args2, dataDb2, codegraphDb2) {
12375
12735
  const lines = [];
@@ -12427,7 +12787,7 @@ function handleCouplingCheck(args2, dataDb2, codegraphDb2) {
12427
12787
  }
12428
12788
  const totalIssues = uncoupledProcs.length + orphanComponents.length;
12429
12789
  lines.push(`### RESULT: ${totalIssues === 0 ? "PASS" : `FAIL (${totalIssues} issues)`}`);
12430
- return text17(lines.join("\n"));
12790
+ return text18(lines.join("\n"));
12431
12791
  }
12432
12792
  function handleImpact2(file, dataDb2, codegraphDb2) {
12433
12793
  const lines = [];
@@ -12435,9 +12795,9 @@ function handleImpact2(file, dataDb2, codegraphDb2) {
12435
12795
  lines.push("");
12436
12796
  const affectedPages = findAffectedPages(dataDb2, file);
12437
12797
  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))];
12798
+ const portals = [...new Set(affectedPages.map((p19) => p19.portal))];
12799
+ const allTables = [...new Set(affectedPages.flatMap((p19) => p19.tables))];
12800
+ const allRouters = [...new Set(affectedPages.flatMap((p19) => p19.routers))];
12441
12801
  lines.push(`### Pages Affected: ${affectedPages.length}`);
12442
12802
  for (const page of affectedPages) {
12443
12803
  lines.push(`- ${page.route} (${page.portal})`);
@@ -12489,7 +12849,7 @@ function handleImpact2(file, dataDb2, codegraphDb2) {
12489
12849
  lines.push(`- -> ${crossing}`);
12490
12850
  }
12491
12851
  }
12492
- return text17(lines.join("\n"));
12852
+ return text18(lines.join("\n"));
12493
12853
  }
12494
12854
  function handleDomains(args2, dataDb2, codegraphDb2) {
12495
12855
  const lines = [];
@@ -12534,7 +12894,7 @@ function handleDomains(args2, dataDb2, codegraphDb2) {
12534
12894
  for (const r of files.routers) lines.push(`- ${r}`);
12535
12895
  lines.push("");
12536
12896
  lines.push(`### Pages (${files.pages.length})`);
12537
- for (const p18 of files.pages) lines.push(`- ${p18}`);
12897
+ for (const p19 of files.pages) lines.push(`- ${p19}`);
12538
12898
  lines.push("");
12539
12899
  lines.push(`### Components (${files.components.length})`);
12540
12900
  for (const c of files.components.slice(0, 30)) lines.push(`- ${c}`);
@@ -12545,7 +12905,7 @@ function handleDomains(args2, dataDb2, codegraphDb2) {
12545
12905
  lines.push(`- **${domain.name}**: ${domain.routers.length} router patterns, imports from: ${domain.allowedImportsFrom.join(", ") || "any"}`);
12546
12906
  }
12547
12907
  }
12548
- return text17(lines.join("\n"));
12908
+ return text18(lines.join("\n"));
12549
12909
  }
12550
12910
  function handleSchema(args2) {
12551
12911
  const lines = [];
@@ -12570,7 +12930,7 @@ function handleSchema(args2) {
12570
12930
  const tableName = args2.table;
12571
12931
  const model = models.find((m) => m.tableName === tableName || m.name === tableName);
12572
12932
  if (!model) {
12573
- return text17(`Model/table "${tableName}" not found in Prisma schema.`);
12933
+ return text18(`Model/table "${tableName}" not found in Prisma schema.`);
12574
12934
  }
12575
12935
  lines.push(`## ${model.name} (table: ${model.tableName})`);
12576
12936
  lines.push("");
@@ -12596,11 +12956,11 @@ function handleSchema(args2) {
12596
12956
  lines.push("Checking all column references against Prisma schema...");
12597
12957
  lines.push("");
12598
12958
  const projectRoot = getProjectRoot();
12599
- const absPath = ensureWithinRoot(resolve18(projectRoot, file), projectRoot);
12600
- if (!existsSync22(absPath)) {
12601
- return text17(`File not found: ${file}`);
12959
+ const absPath = ensureWithinRoot(resolve19(projectRoot, file), projectRoot);
12960
+ if (!existsSync23(absPath)) {
12961
+ return text18(`File not found: ${file}`);
12602
12962
  }
12603
- const source = readFileSync22(absPath, "utf-8");
12963
+ const source = readFileSync23(absPath, "utf-8");
12604
12964
  const config = getConfig();
12605
12965
  const dbPattern = config.dbAccessPattern ?? "ctx.db.{table}";
12606
12966
  const regexStr = dbPattern.replace(/[.*+?^${}()|[\]\\]/g, "\\$&").replace("\\{table\\}", "(\\w+)");
@@ -12633,7 +12993,7 @@ function handleSchema(args2) {
12633
12993
  }
12634
12994
  }
12635
12995
  }
12636
- return text17(lines.join("\n"));
12996
+ return text18(lines.join("\n"));
12637
12997
  }
12638
12998
  var init_tools = __esm({
12639
12999
  "src/tools.ts"() {
@@ -12673,13 +13033,14 @@ var init_tools = __esm({
12673
13033
  init_python_tools();
12674
13034
  init_config();
12675
13035
  init_license();
13036
+ init_mcp_bridge_tools();
12676
13037
  }
12677
13038
  });
12678
13039
 
12679
13040
  // src/server.ts
12680
13041
  var server_exports = {};
12681
- import { readFileSync as readFileSync23 } from "fs";
12682
- import { resolve as resolve19, dirname as dirname11 } from "path";
13042
+ import { readFileSync as readFileSync24 } from "fs";
13043
+ import { resolve as resolve20, dirname as dirname11 } from "path";
12683
13044
  import { fileURLToPath as fileURLToPath4 } from "url";
12684
13045
  function getDb() {
12685
13046
  if (!codegraphDb) codegraphDb = getCodeGraphDb();
@@ -12774,7 +13135,7 @@ var init_server = __esm({
12774
13135
  __dirname4 = dirname11(fileURLToPath4(import.meta.url));
12775
13136
  PKG_VERSION = (() => {
12776
13137
  try {
12777
- const pkg = JSON.parse(readFileSync23(resolve19(__dirname4, "..", "package.json"), "utf-8"));
13138
+ const pkg = JSON.parse(readFileSync24(resolve20(__dirname4, "..", "package.json"), "utf-8"));
12778
13139
  return pkg.version ?? "0.0.0";
12779
13140
  } catch {
12780
13141
  return "0.0.0";
@@ -12838,8 +13199,8 @@ var init_server = __esm({
12838
13199
  });
12839
13200
 
12840
13201
  // src/cli.ts
12841
- import { readFileSync as readFileSync24 } from "fs";
12842
- import { resolve as resolve20, dirname as dirname12 } from "path";
13202
+ import { readFileSync as readFileSync25 } from "fs";
13203
+ import { resolve as resolve21, dirname as dirname12 } from "path";
12843
13204
  import { fileURLToPath as fileURLToPath5 } from "url";
12844
13205
  var __filename4 = fileURLToPath5(import.meta.url);
12845
13206
  var __dirname5 = dirname12(__filename4);
@@ -12913,7 +13274,7 @@ Documentation: https://massu.ai/docs
12913
13274
  }
12914
13275
  function printVersion() {
12915
13276
  try {
12916
- const pkg = JSON.parse(readFileSync24(resolve20(__dirname5, "../package.json"), "utf-8"));
13277
+ const pkg = JSON.parse(readFileSync25(resolve21(__dirname5, "../package.json"), "utf-8"));
12917
13278
  console.log(`massu v${pkg.version}`);
12918
13279
  } catch {
12919
13280
  console.log("massu v0.1.0");