@rigstate/mcp 0.5.5 → 0.5.7

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/index.js CHANGED
@@ -1,10 +1,4 @@
1
1
  #!/usr/bin/env node
2
- var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
3
- get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
4
- }) : x)(function(x) {
5
- if (typeof require !== "undefined") return require.apply(this, arguments);
6
- throw Error('Dynamic require of "' + x + '" is not supported');
7
- });
8
2
 
9
3
  // src/index.ts
10
4
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
@@ -67,7 +61,7 @@ var ToolRegistry = class {
67
61
  */
68
62
  register(tool) {
69
63
  if (this.tools.has(tool.name)) {
70
- console.warn(`Tool '${tool.name}' is already registered. Overwriting.`);
64
+ console.error(`Tool '${tool.name}' is already registered. Overwriting.`);
71
65
  }
72
66
  this.tools.set(tool.name, tool);
73
67
  }
@@ -974,30 +968,29 @@ architecture rules, decisions, and constraints.`,
974
968
  }
975
969
  });
976
970
  async function generateQueryEmbedding(query) {
977
- const openRouterKey = process.env.OPENROUTER_API_KEY;
978
- const googleKey = process.env.GOOGLE_GENERATIVE_AI_API_KEY;
979
- if (!openRouterKey && !googleKey) {
971
+ const apiKey = process.env.RIGSTATE_API_KEY;
972
+ const apiUrl = process.env.RIGSTATE_API_URL || "http://localhost:3000/api/v1";
973
+ if (!apiKey) {
980
974
  return null;
981
975
  }
982
976
  try {
983
- const { embed } = await import("ai");
984
- if (openRouterKey) {
985
- const { createOpenRouter } = await import("@openrouter/ai-sdk-provider");
986
- const openrouter = createOpenRouter({ apiKey: openRouterKey });
987
- const { embedding } = await embed({
988
- model: openrouter.embedding("google/text-embedding-004"),
989
- value: query.replace(/\n/g, " ")
990
- });
991
- return embedding;
992
- } else {
993
- const { google } = await import("@ai-sdk/google");
994
- const { embedding } = await embed({
995
- model: google.embedding("text-embedding-004"),
996
- value: query.replace(/\n/g, " ")
997
- });
998
- return embedding;
977
+ const response = await fetch(`${apiUrl}/intelligence/embed`, {
978
+ method: "POST",
979
+ headers: {
980
+ "Content-Type": "application/json",
981
+ "Authorization": `Bearer ${apiKey}`
982
+ },
983
+ body: JSON.stringify({ text: query })
984
+ });
985
+ if (!response.ok) {
986
+ const errorText = await response.text();
987
+ console.error(`Embedding API error (${response.status}):`, errorText);
988
+ return null;
999
989
  }
990
+ const result = await response.json();
991
+ return result.data?.embedding || null;
1000
992
  } catch (error) {
993
+ console.error("Failed to generate embedding via Proxy:", error);
1001
994
  return null;
1002
995
  }
1003
996
  }
@@ -1037,6 +1030,13 @@ async function queryBrain(supabase, userId, projectId, query, limit = 8, thresho
1037
1030
  createdAt: m.created_at
1038
1031
  }));
1039
1032
  }
1033
+ let relevantFeatures = [];
1034
+ try {
1035
+ const { data: features } = await supabase.from("project_features").select("name, status, description").eq("project_id", projectId).or(`name.ilike.%${query}%,description.ilike.%${query}%`).limit(3);
1036
+ if (features) relevantFeatures = features;
1037
+ } catch (e) {
1038
+ console.warn("Feature fetch failed in brain query", e);
1039
+ }
1040
1040
  const contextLines = memories.map((m) => {
1041
1041
  const voteIndicator = m.netVotes && m.netVotes < 0 ? ` [\u26A0\uFE0F POORLY RATED: ${m.netVotes}]` : "";
1042
1042
  const tagStr = m.tags && m.tags.length > 0 ? ` [${m.tags.join(", ")}]` : "";
@@ -1044,17 +1044,28 @@ async function queryBrain(supabase, userId, projectId, query, limit = 8, thresho
1044
1044
  return `- [${category}]${tagStr}${voteIndicator}: ${m.content}`;
1045
1045
  });
1046
1046
  const searchType = embedding ? "TRIPLE-HYBRID (Vector + FTS + Fuzzy)" : "HYBRID (FTS + Fuzzy)";
1047
- const formatted = memories.length > 0 ? `=== PROJECT BRAIN: RELEVANT MEMORIES ===
1047
+ let formatted = `=== PROJECT BRAIN: RELEVANT MEMORIES ===
1048
1048
  Search Mode: ${searchType}
1049
- Query: "${query}"
1049
+ Query: "${query}"`;
1050
+ if (relevantFeatures.length > 0) {
1051
+ formatted += `
1052
+
1053
+ === RELATED FEATURES ===
1054
+ ` + relevantFeatures.map((f) => `- ${f.name} [${f.status}]`).join("\n");
1055
+ }
1056
+ formatted += `
1057
+
1050
1058
  Found ${memories.length} relevant memories:
1051
1059
 
1052
1060
  ${contextLines.join("\n")}
1053
1061
 
1054
- ==========================================` : `=== PROJECT BRAIN ===
1062
+ ==========================================`;
1063
+ if (memories.length === 0 && relevantFeatures.length === 0) {
1064
+ formatted = `=== PROJECT BRAIN ===
1055
1065
  Query: "${query}"
1056
- No relevant memories found for this query.
1066
+ No relevant memories or features found.
1057
1067
  =======================`;
1068
+ }
1058
1069
  return {
1059
1070
  query,
1060
1071
  memories,
@@ -1579,25 +1590,41 @@ var listFeaturesTool = {
1579
1590
  Useful for understanding the strategic context and major milestones.`,
1580
1591
  schema: InputSchema,
1581
1592
  handler: async ({ projectId }, { supabase, userId }) => {
1582
- const { data: project, error: projectError } = await supabase.from("projects").select("id").eq("id", projectId).eq("owner_id", userId).single();
1593
+ const { data: project, error: projectError } = await supabase.from("projects").select("id, functional_spec").eq("id", projectId).eq("owner_id", userId).single();
1583
1594
  if (projectError || !project) {
1584
1595
  throw new Error("Project not found or access denied");
1585
1596
  }
1586
- const { data: features, error } = await supabase.from("features").select("id, name, description, priority, status").eq("project_id", projectId).neq("status", "ARCHIVED").order("created_at", { ascending: false });
1587
- if (error) {
1588
- throw new Error(`Failed to fetch features: ${error.message}`);
1597
+ const { data: dbFeatures, error: dbError } = await supabase.from("project_features").select("id, name, description, status").eq("project_id", projectId).neq("status", "shadow").order("created_at", { ascending: false });
1598
+ let featuresList = [];
1599
+ let source = "DB";
1600
+ if (!dbError && dbFeatures && dbFeatures.length > 0) {
1601
+ featuresList = dbFeatures.map((f) => ({
1602
+ ...f,
1603
+ title: f.name
1604
+ // Map back to title specifically for uniform handling below
1605
+ }));
1606
+ } else {
1607
+ source = "FALLBACK_SPEC";
1608
+ console.error(`[WARN] Project ${projectId}: 'project_features' empty or missing. Falling back to 'functional_spec'.`);
1609
+ const spec = project.functional_spec;
1610
+ if (spec && typeof spec === "object" && Array.isArray(spec.features)) {
1611
+ featuresList = spec.features.map((f) => ({
1612
+ id: "legacy",
1613
+ title: f.name || f.title,
1614
+ description: f.description,
1615
+ status: f.status || "proposed"
1616
+ }));
1617
+ }
1589
1618
  }
1590
- const formatted = (features || []).length > 0 ? (features || []).map((f) => {
1591
- const priorityStr = f.priority === "MVP" ? "[MVP] " : "";
1592
- return `- ${priorityStr}${f.name} (${f.status})`;
1593
- }).join("\n") : "No active features found.";
1619
+ if (featuresList.length === 0) {
1620
+ return { content: [{ type: "text", text: "No active features found (checked DB and Spec)." }] };
1621
+ }
1622
+ const formatted = `=== PROJECT FEATURES (Source: ${source}) ===
1623
+ ` + featuresList.map((f) => {
1624
+ return `- ${f.title} [${f.status}]`;
1625
+ }).join("\n");
1594
1626
  return {
1595
- content: [
1596
- {
1597
- type: "text",
1598
- text: formatted
1599
- }
1600
- ]
1627
+ content: [{ type: "text", text: formatted }]
1601
1628
  };
1602
1629
  }
1603
1630
  };
@@ -1831,7 +1858,7 @@ async function analyzeDatabasePerformance(supabase, input) {
1831
1858
  }
1832
1859
  }
1833
1860
  } catch (e) {
1834
- console.warn(`Skipping file ${filePath}: ${e}`);
1861
+ console.error(`Skipping file ${filePath}: ${e}`);
1835
1862
  }
1836
1863
  }
1837
1864
  const highSev = issues.filter((i) => i.severity === "HIGH").length;
@@ -2054,6 +2081,58 @@ function checkAntiLazy(filePath, content) {
2054
2081
  return violations;
2055
2082
  }
2056
2083
 
2084
+ // src/tools/security-checks-arch.ts
2085
+ function checkArchitectureIntegrity(filePath, content) {
2086
+ const violations = [];
2087
+ const isUI = filePath.includes("/components/") || filePath.includes("/hooks/") || filePath.includes("/app/") && !filePath.includes("/api/") && !filePath.includes("actions.ts") && !filePath.includes("route.ts");
2088
+ if (isUI) {
2089
+ const illegalImportRegex = /(import.*from\s+['"]@supabase\/supabase-js['"])/g;
2090
+ if (illegalImportRegex.test(content)) {
2091
+ violations.push({
2092
+ id: "SEC-ARCH-01",
2093
+ type: "ARCHITECTURE_VIOLATION",
2094
+ severity: "FATAL",
2095
+ title: "Illegal Supabase Client in UI",
2096
+ description: "Direct import of @supabase/supabase-js in a UI component/hook is strictly forbidden. It bypasses the server boundary.",
2097
+ recommendation: "Use the `createClient` helper from our utils or Server Actions for data access."
2098
+ });
2099
+ }
2100
+ }
2101
+ if (isUI) {
2102
+ const dbActionRegex = /\.(from|select|insert|update|delete|rpc)\s*\(/g;
2103
+ const matches = content.match(dbActionRegex);
2104
+ if (matches) {
2105
+ const suspicious = matches.some((m) => !m.includes(".from") || m.includes(".from") && content.includes("supabase.from"));
2106
+ if (suspicious || matches.length > 0 && content.includes("supabase")) {
2107
+ violations.push({
2108
+ id: "SEC-ARCH-02",
2109
+ type: "ARCHITECTURE_VIOLATION",
2110
+ severity: "FATAL",
2111
+ title: "Direct Database Query in UI",
2112
+ description: "Detected direct database query pattern (select/insert/update/delete) in a UI component. This exposes logic to the client.",
2113
+ recommendation: "Move all data fetching logic to Server Components or Server Actions."
2114
+ });
2115
+ }
2116
+ }
2117
+ }
2118
+ const isActionFile = filePath.includes("/actions/") || filePath.includes("actions.ts");
2119
+ if (isActionFile) {
2120
+ const header = content.slice(0, 200);
2121
+ const hasUseServer = /['"]use server['"]/.test(header);
2122
+ if (!hasUseServer) {
2123
+ violations.push({
2124
+ id: "SEC-ARCH-03",
2125
+ type: "ARCHITECTURE_VIOLATION",
2126
+ severity: "FATAL",
2127
+ title: 'Missing "use server" Directive',
2128
+ description: 'File appears to be a Server Action module but lacks the "use server" directive.',
2129
+ recommendation: 'Add "use server" at the very top of the file.'
2130
+ });
2131
+ }
2132
+ }
2133
+ return violations;
2134
+ }
2135
+
2057
2136
  // src/tools/security-tools.ts
2058
2137
  registry.register({
2059
2138
  name: "audit_rls_status",
@@ -2141,6 +2220,8 @@ async function auditSecurityIntegrity(supabase, input) {
2141
2220
  if (depViolation) violations.push(depViolation);
2142
2221
  const lazyViolations = checkAntiLazy(filePath, content);
2143
2222
  violations.push(...lazyViolations);
2223
+ const archViolations = checkArchitectureIntegrity(filePath, content);
2224
+ violations.push(...archViolations);
2144
2225
  const score = Math.max(0, 100 - violations.length * 10);
2145
2226
  const passed = !violations.some((v) => v.severity === "HIGH" || v.severity === "FATAL");
2146
2227
  return {
@@ -2341,7 +2422,7 @@ async function completeRoadmapTask(supabase, projectId, summary, taskId, gitDiff
2341
2422
  metadata
2342
2423
  });
2343
2424
  if (reportError) {
2344
- console.warn("Failed to save mission report:", reportError.message);
2425
+ console.error("Failed to save mission report:", reportError.message);
2345
2426
  }
2346
2427
  try {
2347
2428
  const apiKey = process.env.RIGSTATE_API_KEY;
@@ -2460,7 +2541,7 @@ async function addRoadmapChunk(supabase, userId, input) {
2460
2541
  }
2461
2542
 
2462
2543
  // src/tools/arch-tools.ts
2463
- import { promises as fs2 } from "fs";
2544
+ import { promises as fs2, existsSync, statSync } from "fs";
2464
2545
  import * as path2 from "path";
2465
2546
  registry.register({
2466
2547
  name: "analyze_dependency_graph",
@@ -2480,22 +2561,38 @@ async function analyzeDependencyGraph(input) {
2480
2561
  error: `Directory not found: ${searchPath}. Ensure you are running the MCP server in the project root or provide an absolute path.`
2481
2562
  };
2482
2563
  }
2564
+ let externalDeps = {};
2565
+ const pkgPath = path2.join(process.cwd(), "package.json");
2566
+ if (existsSync(pkgPath)) {
2567
+ try {
2568
+ const pkgContent = await fs2.readFile(pkgPath, "utf-8");
2569
+ const pkg = JSON.parse(pkgContent);
2570
+ externalDeps = { ...pkg.dependencies, ...pkg.devDependencies };
2571
+ } catch (e) {
2572
+ console.error("Failed to parse package.json", e);
2573
+ }
2574
+ }
2483
2575
  const allFiles = await getAllFiles(searchPath);
2484
- const tsFiles = allFiles.filter((f) => /\.(ts|tsx|js|jsx)$/.test(f) && !f.includes("node_modules") && !f.includes(".next") && !f.includes("dist"));
2576
+ const tsFiles = allFiles.filter((f) => /\.(ts|tsx|js|jsx)$/.test(f) && !f.includes("node_modules") && !f.includes("dist") && !f.includes(".next"));
2485
2577
  const graph = {};
2486
- const fileSet = new Set(tsFiles);
2487
2578
  for (const file of tsFiles) {
2488
2579
  const content = await fs2.readFile(file, "utf-8");
2489
2580
  const imports = extractImports(content);
2490
2581
  const validDeps = [];
2582
+ const fileDir = path2.dirname(file);
2491
2583
  for (const imp of imports) {
2492
- const resolved = resolveImport(file, imp, searchPath);
2493
- if (resolved && fileSet.has(resolved)) {
2494
- validDeps.push(resolved);
2584
+ if (Object.keys(externalDeps).some((d) => imp === d || imp.startsWith(d + "/"))) {
2585
+ continue;
2586
+ }
2587
+ if (imp.startsWith(".") || imp.startsWith("@/")) {
2588
+ const resolved = resolveImportString(file, imp, searchPath);
2589
+ if (resolved && tsFiles.includes(resolved)) {
2590
+ validDeps.push(path2.relative(searchPath, resolved));
2591
+ }
2495
2592
  }
2496
2593
  }
2497
2594
  const relFile = path2.relative(searchPath, file);
2498
- graph[relFile] = validDeps.map((d) => path2.relative(searchPath, d));
2595
+ graph[relFile] = validDeps;
2499
2596
  }
2500
2597
  const cycles = detectCycles(graph);
2501
2598
  return {
@@ -2503,11 +2600,12 @@ async function analyzeDependencyGraph(input) {
2503
2600
  analyzedPath: searchPath,
2504
2601
  metrics: {
2505
2602
  totalFiles: tsFiles.length,
2506
- circularDependencies: cycles.length
2603
+ circularDependencies: cycles.length,
2604
+ externalDependencies: Object.keys(externalDeps).length
2507
2605
  },
2508
2606
  cycles,
2509
2607
  status: cycles.length > 0 ? "VIOLATION" : "PASS",
2510
- summary: cycles.length > 0 ? `FAILED. Detected ${cycles.length} circular dependencies. These must be resolved to maintain architectural integrity.` : `PASSED. No circular dependencies detected in ${tsFiles.length} files.`
2608
+ summary: cycles.length > 0 ? `FAILED. Detected ${cycles.length} circular dependencies. Einar demands resolution!` : `PASSED. Architecture is sound. No circular dependencies in ${tsFiles.length} files.`
2511
2609
  };
2512
2610
  }
2513
2611
  async function getAllFiles(dir) {
@@ -2527,24 +2625,24 @@ function extractImports(content) {
2527
2625
  }
2528
2626
  return imports;
2529
2627
  }
2530
- function resolveImport(importer, importPath, root) {
2531
- if (!importPath.startsWith(".") && !importPath.startsWith("@/")) {
2532
- return null;
2533
- }
2534
- let searchDir = path2.dirname(importer);
2628
+ function resolveImportString(importer, importPath, root) {
2629
+ let targetDir = path2.dirname(importer);
2535
2630
  let target = importPath;
2536
2631
  if (importPath.startsWith("@/")) {
2537
2632
  target = importPath.replace("@/", "");
2538
- searchDir = root;
2633
+ targetDir = root;
2539
2634
  }
2540
- const startPath = path2.resolve(searchDir, target);
2541
- const extensions = [".ts", ".tsx", ".js", ".jsx", "/index.ts", "/index.tsx", "/index.js", ""];
2635
+ const naivePath = path2.resolve(targetDir, target);
2636
+ const extensions = [".ts", ".tsx", ".js", ".jsx", "/index.ts", "/index.tsx"];
2542
2637
  for (const ext of extensions) {
2543
- const candidate = startPath + ext;
2544
- if (__require("fs").existsSync(candidate) && !__require("fs").statSync(candidate).isDirectory()) {
2638
+ const candidate = naivePath + ext;
2639
+ if (existsSync(candidate) && !statSync(candidate).isDirectory()) {
2545
2640
  return candidate;
2546
2641
  }
2547
2642
  }
2643
+ if (existsSync(naivePath) && !statSync(naivePath).isDirectory()) {
2644
+ return naivePath;
2645
+ }
2548
2646
  return null;
2549
2647
  }
2550
2648
  function detectCycles(graph) {
@@ -2876,7 +2974,6 @@ var watcherState = {
2876
2974
  async function startFrankWatcher(supabase, userId) {
2877
2975
  if (watcherState.isRunning) return;
2878
2976
  watcherState.isRunning = true;
2879
- console.error(`\u{1F916} Frank Watcher started for user ${userId}`);
2880
2977
  const checkTasks = async () => {
2881
2978
  try {
2882
2979
  watcherState.lastCheck = (/* @__PURE__ */ new Date()).toISOString();
@@ -2886,8 +2983,6 @@ async function startFrankWatcher(supabase, userId) {
2886
2983
  const task = tasks[0];
2887
2984
  watcherState.tasksFound++;
2888
2985
  if (task.proposal?.startsWith("ping") || task.task_id === null && !task.proposal?.startsWith("report")) {
2889
- console.error(`
2890
- \u26A1 HEARTBEAT: Frank received REAL-TIME PING for project ${task.project_id}. Response: PONG`);
2891
2986
  await supabase.from("agent_bridge").update({
2892
2987
  status: "COMPLETED",
2893
2988
  summary: "Pong! Frank is active and listening.",
@@ -2899,8 +2994,6 @@ async function startFrankWatcher(supabase, userId) {
2899
2994
  const parts = task.proposal.split(":");
2900
2995
  const signalType = parts[1];
2901
2996
  const reportType = signalType === "MANIFEST" ? "SYSTEM_MANIFEST" : "INVESTOR_REPORT";
2902
- console.error(`
2903
- \u{1F4C4} Frank is generating ${reportType} report...`);
2904
2997
  try {
2905
2998
  const result = await generateProfessionalPdf(supabase, userId, task.project_id, reportType);
2906
2999
  await supabase.from("agent_bridge").update({
@@ -2919,8 +3012,6 @@ async function startFrankWatcher(supabase, userId) {
2919
3012
  return;
2920
3013
  }
2921
3014
  if (task.status === "APPROVED") {
2922
- console.error(`
2923
- \u{1F3D7}\uFE0F Worker: EXECUTING approved task: [${task.id}]`);
2924
3015
  await supabase.from("agent_bridge").update({ status: "EXECUTING", updated_at: (/* @__PURE__ */ new Date()).toISOString() }).eq("id", task.id);
2925
3016
  await new Promise((resolve2) => setTimeout(resolve2, 2e3));
2926
3017
  const taskTitle2 = task.roadmap_chunks?.title || "manual objective";
@@ -3006,7 +3097,6 @@ async function main() {
3006
3097
  setupToolHandlers(server, { supabase, userId });
3007
3098
  const transport = new StdioServerTransport();
3008
3099
  await server.connect(transport);
3009
- console.error("\u{1F6F0}\uFE0F Rigstate MCP Server (Evolutionary) running on stdio");
3010
3100
  }
3011
3101
  main().catch((error) => {
3012
3102
  console.error("FATAL ERROR:", error);