@rigour-labs/mcp 2.9.4 → 2.11.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/README.md ADDED
@@ -0,0 +1,72 @@
1
+ # 🛡️ Rigour MCP Server
2
+
3
+ **The Quality Gate for AI-Assisted Engineering.**
4
+
5
+ Rigour is a local-first Model Context Protocol (MCP) server that forces AI agents (Claude, Cursor, Windsurf, etc.) to meet strict engineering standards before marking tasks as complete.
6
+
7
+ [![Registry](https://img.shields.io/badge/MCP-Registry-brightgreen)](https://github.com/mcp)
8
+ [![npm version](https://img.shields.io/npm/v/@rigour-labs/mcp?color=cyan)](https://www.npmjs.com/package/@rigour-labs/mcp)
9
+
10
+ ---
11
+
12
+ ## 🚀 Overview
13
+
14
+ Rigour moves code quality enforcement from the "Post-Commit" phase to the "In-Progress" phase. By running as an MCP server inside your editor, it provides the AI with a deterministic PASS/FAIL loop, preventing "Vibe Coding" and broken builds.
15
+
16
+ ### Key Features:
17
+ - **Quality Gates**: Deterministic checks for file size, complexity, and hygiene.
18
+ - **Context Memory**: Persistent memory that tracks project rules and patterns across sessions.
19
+ - **Pattern Reinvention Blocking**: Warns or blocks the AI when it tries to rewrite existing utilities.
20
+ - **Security Audits**: Real-time CVE detection for dependencies the AI is suggesting.
21
+ - **Zero Cloud**: 100% local analysis. Your code never leaves your machine.
22
+
23
+ ---
24
+
25
+ ## 🛠️ Available Tools
26
+
27
+ | Tool | Description |
28
+ |:---|:---|
29
+ | `rigour_check` | Runs all configured quality gates on the current workspace. |
30
+ | `rigour_explain` | Explains why a specific gate failed and provides actionable fix instructions. |
31
+ | `rigour_check_pattern` | Checks if a proposed code pattern already exists in the codebase. |
32
+ | `rigour_remember` | Stores project-specific context or rules in Rigour's persistent memory. |
33
+ | `rigour_recall` | Retrieves stored context to guide AI generation. |
34
+
35
+ ---
36
+
37
+ ## 📦 Installation
38
+
39
+ ### 1. Install via npm
40
+ ```bash
41
+ npm install -g @rigour-labs/mcp
42
+ ```
43
+
44
+ ### 2. Configure your IDE
45
+
46
+ #### Cursor / Claude Desktop
47
+ Add the following to your MCP settings:
48
+ ```json
49
+ {
50
+ "mcpServers": {
51
+ "rigour": {
52
+ "command": "npx",
53
+ "args": ["-y", "@rigour-labs/mcp"],
54
+ "env": {
55
+ "RIGOUR_CWD": "/path/to/your/project"
56
+ }
57
+ }
58
+ }
59
+ }
60
+ ```
61
+
62
+ ---
63
+
64
+ ## 📖 Documentation
65
+
66
+ For full configuration and advanced usage, visit **[docs.rigour.run](https://docs.rigour.run)**.
67
+
68
+ ---
69
+
70
+ ## 📜 License
71
+
72
+ MIT © [Rigour Labs](https://github.com/rigour-labs)
package/dist/index.js CHANGED
@@ -1,17 +1,14 @@
1
1
  #!/usr/bin/env node
2
- "use strict";
3
- var __importDefault = (this && this.__importDefault) || function (mod) {
4
- return (mod && mod.__esModule) ? mod : { "default": mod };
5
- };
6
- Object.defineProperty(exports, "__esModule", { value: true });
7
- const index_js_1 = require("@modelcontextprotocol/sdk/server/index.js");
8
- const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
9
- const types_js_1 = require("@modelcontextprotocol/sdk/types.js");
10
- const fs_extra_1 = __importDefault(require("fs-extra"));
11
- const path_1 = __importDefault(require("path"));
12
- const yaml_1 = __importDefault(require("yaml"));
13
- const core_1 = require("@rigour-labs/core");
14
- const server = new index_js_1.Server({
2
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
3
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
+ import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
5
+ import fs from "fs-extra";
6
+ import path from "path";
7
+ import yaml from "yaml";
8
+ import { randomUUID } from "crypto";
9
+ import { GateRunner, ConfigSchema, } from "@rigour-labs/core";
10
+ import { PatternMatcher, loadPatternIndex, getDefaultIndexPath, StalenessDetector, SecurityDetector } from "@rigour-labs/core/pattern-index";
11
+ const server = new Server({
15
12
  name: "rigour-mcp",
16
13
  version: "1.0.0",
17
14
  }, {
@@ -20,31 +17,48 @@ const server = new index_js_1.Server({
20
17
  },
21
18
  });
22
19
  async function loadConfig(cwd) {
23
- const configPath = path_1.default.join(cwd, "rigour.yml");
24
- if (!(await fs_extra_1.default.pathExists(configPath))) {
20
+ const configPath = path.join(cwd, "rigour.yml");
21
+ if (!(await fs.pathExists(configPath))) {
25
22
  throw new Error("Rigour configuration (rigour.yml) not found. The agent must run `rigour init` first to establish engineering standards.");
26
23
  }
27
- const configContent = await fs_extra_1.default.readFile(configPath, "utf-8");
28
- return core_1.ConfigSchema.parse(yaml_1.default.parse(configContent));
24
+ const configContent = await fs.readFile(configPath, "utf-8");
25
+ return ConfigSchema.parse(yaml.parse(configContent));
29
26
  }
30
27
  async function getMemoryPath(cwd) {
31
- const rigourDir = path_1.default.join(cwd, ".rigour");
32
- await fs_extra_1.default.ensureDir(rigourDir);
33
- return path_1.default.join(rigourDir, "memory.json");
28
+ const rigourDir = path.join(cwd, ".rigour");
29
+ await fs.ensureDir(rigourDir);
30
+ return path.join(rigourDir, "memory.json");
34
31
  }
35
32
  async function loadMemory(cwd) {
36
33
  const memPath = await getMemoryPath(cwd);
37
- if (await fs_extra_1.default.pathExists(memPath)) {
38
- const content = await fs_extra_1.default.readFile(memPath, "utf-8");
34
+ if (await fs.pathExists(memPath)) {
35
+ const content = await fs.readFile(memPath, "utf-8");
39
36
  return JSON.parse(content);
40
37
  }
41
38
  return { memories: {} };
42
39
  }
43
40
  async function saveMemory(cwd, store) {
44
41
  const memPath = await getMemoryPath(cwd);
45
- await fs_extra_1.default.writeFile(memPath, JSON.stringify(store, null, 2));
42
+ await fs.writeFile(memPath, JSON.stringify(store, null, 2));
46
43
  }
47
- server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => {
44
+ // Helper to log events for Rigour Studio
45
+ async function logStudioEvent(cwd, event) {
46
+ try {
47
+ const rigourDir = path.join(cwd, ".rigour");
48
+ await fs.ensureDir(rigourDir);
49
+ const eventsPath = path.join(rigourDir, "events.jsonl");
50
+ const logEntry = JSON.stringify({
51
+ id: randomUUID(),
52
+ timestamp: new Date().toISOString(),
53
+ ...event
54
+ }) + "\n";
55
+ await fs.appendFile(eventsPath, logEntry);
56
+ }
57
+ catch {
58
+ // Silent fail - Studio logging is non-blocking and zero-telemetry
59
+ }
60
+ }
61
+ server.setRequestHandler(ListToolsRequestSchema, async () => {
48
62
  return {
49
63
  tools: [
50
64
  {
@@ -189,19 +203,67 @@ server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => {
189
203
  required: ["cwd", "key"],
190
204
  },
191
205
  },
206
+ {
207
+ name: "rigour_check_pattern",
208
+ description: "Checks if a proposed code pattern (function, component, etc.) already exists, is stale, or has security vulnerabilities (CVEs). CALL THIS BEFORE CREATING NEW CODE.",
209
+ inputSchema: {
210
+ type: "object",
211
+ properties: {
212
+ cwd: {
213
+ type: "string",
214
+ description: "Absolute path to the project root.",
215
+ },
216
+ name: {
217
+ type: "string",
218
+ description: "The name of the function, class, or component you want to create.",
219
+ },
220
+ type: {
221
+ type: "string",
222
+ description: "The type of pattern (e.g., 'function', 'component', 'hook', 'type').",
223
+ },
224
+ intent: {
225
+ type: "string",
226
+ description: "What the code is for (e.g., 'format dates', 'user authentication').",
227
+ },
228
+ },
229
+ required: ["cwd", "name"],
230
+ },
231
+ },
232
+ {
233
+ name: "rigour_security_audit",
234
+ description: "Runs a live security audit (CVE check) on the project dependencies.",
235
+ inputSchema: {
236
+ type: "object",
237
+ properties: {
238
+ cwd: {
239
+ type: "string",
240
+ description: "Absolute path to the project root.",
241
+ },
242
+ },
243
+ required: ["cwd"],
244
+ },
245
+ },
192
246
  ],
193
247
  };
194
248
  });
195
- server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
249
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
196
250
  const { name, arguments: args } = request.params;
197
251
  const cwd = args?.cwd || process.cwd();
252
+ const requestId = randomUUID();
198
253
  try {
254
+ await logStudioEvent(cwd, {
255
+ type: "tool_call",
256
+ requestId,
257
+ tool: name,
258
+ arguments: args
259
+ });
199
260
  const config = await loadConfig(cwd);
200
- const runner = new core_1.GateRunner(config);
261
+ const runner = new GateRunner(config);
262
+ let result;
201
263
  switch (name) {
202
264
  case "rigour_check": {
203
265
  const report = await runner.run(cwd);
204
- return {
266
+ result = {
205
267
  content: [
206
268
  {
207
269
  type: "text",
@@ -209,11 +271,14 @@ server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
209
271
  },
210
272
  ],
211
273
  };
274
+ // Add the report to the tool_response log for high-fidelity Studio visualization
275
+ result._rigour_report = report;
276
+ break;
212
277
  }
213
278
  case "rigour_explain": {
214
279
  const report = await runner.run(cwd);
215
280
  if (report.status === "PASS") {
216
- return {
281
+ result = {
217
282
  content: [
218
283
  {
219
284
  type: "text",
@@ -222,21 +287,24 @@ server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
222
287
  ],
223
288
  };
224
289
  }
225
- const bullets = report.failures.map((f, i) => {
226
- return `${i + 1}. [${f.id.toUpperCase()}] ${f.title}: ${f.details}${f.hint ? ` (Hint: ${f.hint})` : ''}`;
227
- }).join("\n");
228
- return {
229
- content: [
230
- {
231
- type: "text",
232
- text: `RIGOUR EXPLAIN:\n\n${bullets}`,
233
- },
234
- ],
235
- };
290
+ else {
291
+ const bullets = report.failures.map((f, i) => {
292
+ return `${i + 1}. [${f.id.toUpperCase()}] ${f.title}: ${f.details}${f.hint ? ` (Hint: ${f.hint})` : ''}`;
293
+ }).join("\n");
294
+ result = {
295
+ content: [
296
+ {
297
+ type: "text",
298
+ text: `RIGOUR EXPLAIN:\n\n${bullets}`,
299
+ },
300
+ ],
301
+ };
302
+ }
303
+ break;
236
304
  }
237
305
  case "rigour_status": {
238
306
  const report = await runner.run(cwd);
239
- return {
307
+ result = {
240
308
  content: [
241
309
  {
242
310
  type: "text",
@@ -249,11 +317,12 @@ server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
249
317
  },
250
318
  ],
251
319
  };
320
+ break;
252
321
  }
253
322
  case "rigour_get_fix_packet": {
254
323
  const report = await runner.run(cwd);
255
324
  if (report.status === "PASS") {
256
- return {
325
+ result = {
257
326
  content: [
258
327
  {
259
328
  type: "text",
@@ -262,28 +331,31 @@ server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
262
331
  ],
263
332
  };
264
333
  }
265
- const packet = report.failures.map((f, i) => {
266
- let text = `FIX TASK ${i + 1}: [${f.id.toUpperCase()}] ${f.title}\n`;
267
- text += ` - CONTEXT: ${f.details}\n`;
268
- if (f.files && f.files.length > 0) {
269
- text += ` - TARGET FILES: ${f.files.join(", ")}\n`;
270
- }
271
- if (f.hint) {
272
- text += ` - REFACTORING GUIDANCE: ${f.hint}\n`;
273
- }
274
- return text;
275
- }).join("\n---\n");
276
- return {
277
- content: [
278
- {
279
- type: "text",
280
- text: `ENGINEERING REFINEMENT REQUIRED:\n\nThe project state violated ${report.failures.length} quality gates. You MUST address these failures before declaring the task complete:\n\n${packet}`,
281
- },
282
- ],
283
- };
334
+ else {
335
+ const packet = report.failures.map((f, i) => {
336
+ let text = `FIX TASK ${i + 1}: [${f.id.toUpperCase()}] ${f.title}\n`;
337
+ text += ` - CONTEXT: ${f.details}\n`;
338
+ if (f.files && f.files.length > 0) {
339
+ text += ` - TARGET FILES: ${f.files.join(", ")}\n`;
340
+ }
341
+ if (f.hint) {
342
+ text += ` - REFACTORING GUIDANCE: ${f.hint}\n`;
343
+ }
344
+ return text;
345
+ }).join("\n---\n");
346
+ result = {
347
+ content: [
348
+ {
349
+ type: "text",
350
+ text: `ENGINEERING REFINEMENT REQUIRED:\n\nThe project state violated ${report.failures.length} quality gates. You MUST address these failures before declaring the task complete:\n\n${packet}`,
351
+ },
352
+ ],
353
+ };
354
+ }
355
+ break;
284
356
  }
285
357
  case "rigour_list_gates":
286
- return {
358
+ result = {
287
359
  content: [
288
360
  {
289
361
  type: "text",
@@ -296,8 +368,9 @@ server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
296
368
  },
297
369
  ],
298
370
  };
371
+ break;
299
372
  case "rigour_get_config":
300
- return {
373
+ result = {
301
374
  content: [
302
375
  {
303
376
  type: "text",
@@ -305,6 +378,7 @@ server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
305
378
  },
306
379
  ],
307
380
  };
381
+ break;
308
382
  case "rigour_remember": {
309
383
  const { key, value } = args;
310
384
  const store = await loadMemory(cwd);
@@ -313,7 +387,7 @@ server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
313
387
  timestamp: new Date().toISOString(),
314
388
  };
315
389
  await saveMemory(cwd, store);
316
- return {
390
+ result = {
317
391
  content: [
318
392
  {
319
393
  type: "text",
@@ -321,6 +395,7 @@ server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
321
395
  },
322
396
  ],
323
397
  };
398
+ break;
324
399
  }
325
400
  case "rigour_recall": {
326
401
  const { key } = args;
@@ -328,7 +403,7 @@ server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
328
403
  if (key) {
329
404
  const memory = store.memories[key];
330
405
  if (!memory) {
331
- return {
406
+ result = {
332
407
  content: [
333
408
  {
334
409
  type: "text",
@@ -337,69 +412,169 @@ server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
337
412
  ],
338
413
  };
339
414
  }
340
- return {
415
+ else {
416
+ result = {
417
+ content: [
418
+ {
419
+ type: "text",
420
+ text: `RECALLED MEMORY [${key}]:\n${memory.value}\n\n(Stored: ${memory.timestamp})`,
421
+ },
422
+ ],
423
+ };
424
+ }
425
+ }
426
+ else {
427
+ const keys = Object.keys(store.memories);
428
+ if (keys.length === 0) {
429
+ result = {
430
+ content: [
431
+ {
432
+ type: "text",
433
+ text: "NO MEMORIES STORED. Use rigour_remember to persist important instructions.",
434
+ },
435
+ ],
436
+ };
437
+ }
438
+ else {
439
+ const allMemories = keys.map(k => {
440
+ const mem = store.memories[k];
441
+ return `## ${k}\n${mem.value}\n(Stored: ${mem.timestamp})`;
442
+ }).join("\n\n---\n\n");
443
+ result = {
444
+ content: [
445
+ {
446
+ type: "text",
447
+ text: `RECALLED ALL MEMORIES (${keys.length} items):\n\n${allMemories}\n\n---\nIMPORTANT: Follow these stored instructions throughout this session.`,
448
+ },
449
+ ],
450
+ };
451
+ }
452
+ }
453
+ break;
454
+ }
455
+ case "rigour_forget": {
456
+ const { key } = args;
457
+ const store = await loadMemory(cwd);
458
+ if (!store.memories[key]) {
459
+ result = {
341
460
  content: [
342
461
  {
343
462
  type: "text",
344
- text: `RECALLED MEMORY [${key}]:\n${memory.value}\n\n(Stored: ${memory.timestamp})`,
463
+ text: `NO MEMORY FOUND for key "${key}". Nothing to forget.`,
345
464
  },
346
465
  ],
347
466
  };
348
467
  }
349
- const keys = Object.keys(store.memories);
350
- if (keys.length === 0) {
351
- return {
468
+ else {
469
+ delete store.memories[key];
470
+ await saveMemory(cwd, store);
471
+ result = {
352
472
  content: [
353
473
  {
354
474
  type: "text",
355
- text: "NO MEMORIES STORED. Use rigour_remember to persist important instructions.",
475
+ text: `MEMORY DELETED: "${key}" has been removed.`,
356
476
  },
357
477
  ],
358
478
  };
359
479
  }
360
- const allMemories = keys.map(k => {
361
- const mem = store.memories[k];
362
- return `## ${k}\n${mem.value}\n(Stored: ${mem.timestamp})`;
363
- }).join("\n\n---\n\n");
364
- return {
480
+ break;
481
+ }
482
+ case "rigour_check_pattern": {
483
+ const { name: patternName, type, intent } = args;
484
+ const indexPath = getDefaultIndexPath(cwd);
485
+ const index = await loadPatternIndex(indexPath);
486
+ let resultText = "";
487
+ // 1. Check for Reinvention
488
+ if (index) {
489
+ const matcher = new PatternMatcher(index);
490
+ const matchResult = await matcher.match({ name: patternName, type, intent });
491
+ if (matchResult.status === "FOUND_SIMILAR") {
492
+ resultText += `🚨 PATTERN REINVENTION DETECTED\n`;
493
+ resultText += `Similar pattern already exists: "${matchResult.matches[0].pattern.name}" in ${matchResult.matches[0].pattern.file}\n`;
494
+ resultText += `SUGGESTION: ${matchResult.suggestion}\n\n`;
495
+ }
496
+ }
497
+ else {
498
+ resultText += `⚠️ Pattern index not found. Run 'rigour index' to enable reinvention detection.\n\n`;
499
+ }
500
+ // 2. Check for Staleness/Best Practices
501
+ const detector = new StalenessDetector(cwd);
502
+ const staleness = await detector.checkStaleness(`${type || 'function'} ${patternName} {}`);
503
+ if (staleness.status !== "FRESH") {
504
+ resultText += `⚠️ STALENESS/ANTI-PATTERN WARNING\n`;
505
+ for (const issue of staleness.issues) {
506
+ resultText += `- ${issue.reason}\n REPLACEMENT: ${issue.replacement}\n`;
507
+ }
508
+ resultText += `\n`;
509
+ }
510
+ // 3. Check Security for this library (if it's an import)
511
+ if (intent && intent.includes('import')) {
512
+ const security = new SecurityDetector(cwd);
513
+ const audit = await security.runAudit();
514
+ const relatedVulns = audit.vulnerabilities.filter(v => patternName.toLowerCase().includes(v.packageName.toLowerCase()) ||
515
+ intent.toLowerCase().includes(v.packageName.toLowerCase()));
516
+ if (relatedVulns.length > 0) {
517
+ resultText += `🛡️ SECURITY/CVE WARNING\n`;
518
+ for (const v of relatedVulns) {
519
+ resultText += `- [${v.severity.toUpperCase()}] ${v.packageName}: ${v.title} (${v.url})\n`;
520
+ }
521
+ resultText += `\n`;
522
+ }
523
+ }
524
+ if (!resultText) {
525
+ resultText = `✅ Pattern "${patternName}" is fresh, secure, and unique to the codebase.\n\nRECOMMENDED ACTION: Proceed with implementation.`;
526
+ }
527
+ else {
528
+ let recommendation = "Proceed with caution, addressing the warnings above.";
529
+ if (resultText.includes("🚨 PATTERN REINVENTION")) {
530
+ recommendation = "STOP and REUSE the existing pattern mentioned above. Do not create a duplicate.";
531
+ }
532
+ else if (resultText.includes("🛡️ SECURITY/CVE WARNING")) {
533
+ recommendation = "STOP and update your dependencies or find an alternative library. Do not proceed with vulnerable code.";
534
+ }
535
+ else if (resultText.includes("⚠️ STALENESS")) {
536
+ recommendation = "Follow the replacement suggestion to ensure best practices.";
537
+ }
538
+ resultText += `\nRECOMMENDED ACTION: ${recommendation}`;
539
+ }
540
+ result = {
365
541
  content: [
366
542
  {
367
543
  type: "text",
368
- text: `RECALLED ALL MEMORIES (${keys.length} items):\n\n${allMemories}\n\n---\nIMPORTANT: Follow these stored instructions throughout this session.`,
544
+ text: resultText,
369
545
  },
370
546
  ],
371
547
  };
548
+ break;
372
549
  }
373
- case "rigour_forget": {
374
- const { key } = args;
375
- const store = await loadMemory(cwd);
376
- if (!store.memories[key]) {
377
- return {
378
- content: [
379
- {
380
- type: "text",
381
- text: `NO MEMORY FOUND for key "${key}". Nothing to forget.`,
382
- },
383
- ],
384
- };
385
- }
386
- delete store.memories[key];
387
- await saveMemory(cwd, store);
388
- return {
550
+ case "rigour_security_audit": {
551
+ const security = new SecurityDetector(cwd);
552
+ const summary = await security.getSecuritySummary();
553
+ result = {
389
554
  content: [
390
555
  {
391
556
  type: "text",
392
- text: `MEMORY DELETED: "${key}" has been removed.`,
557
+ text: summary,
393
558
  },
394
559
  ],
395
560
  };
561
+ break;
396
562
  }
397
563
  default:
398
564
  throw new Error(`Unknown tool: ${name}`);
399
565
  }
566
+ await logStudioEvent(cwd, {
567
+ type: "tool_response",
568
+ requestId,
569
+ tool: name,
570
+ status: "success",
571
+ content: result.content,
572
+ _rigour_report: result._rigour_report
573
+ });
574
+ return result;
400
575
  }
401
576
  catch (error) {
402
- return {
577
+ const errorResponse = {
403
578
  content: [
404
579
  {
405
580
  type: "text",
@@ -408,10 +583,19 @@ server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
408
583
  ],
409
584
  isError: true,
410
585
  };
586
+ await logStudioEvent(cwd, {
587
+ type: "tool_response",
588
+ requestId,
589
+ tool: name,
590
+ status: "error",
591
+ error: error.message,
592
+ content: errorResponse.content
593
+ });
594
+ return errorResponse;
411
595
  }
412
596
  });
413
597
  async function main() {
414
- const transport = new stdio_js_1.StdioServerTransport();
598
+ const transport = new StdioServerTransport();
415
599
  await server.connect(transport);
416
600
  console.error("Rigour MCP server v1.0.0 running on stdio");
417
601
  }
@@ -1,8 +1,6 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- const vitest_1 = require("vitest");
4
- (0, vitest_1.describe)('MCP Smoke Test', () => {
5
- (0, vitest_1.it)('should pass', async () => {
6
- (0, vitest_1.expect)(true).toBe(true);
1
+ import { describe, it, expect } from 'vitest';
2
+ describe('MCP Smoke Test', () => {
3
+ it('should pass', async () => {
4
+ expect(true).toBe(true);
7
5
  });
8
6
  });
package/package.json CHANGED
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "name": "@rigour-labs/mcp",
3
- "version": "2.9.4",
3
+ "version": "2.11.0",
4
+ "type": "module",
4
5
  "mcpName": "io.github.rigour-labs/rigour",
5
6
  "description": "Quality gates for AI-generated code. Forces AI agents to meet strict engineering standards with PASS/FAIL enforcement.",
6
7
  "bin": {
@@ -18,7 +19,7 @@
18
19
  "@modelcontextprotocol/sdk": "^1.25.2",
19
20
  "fs-extra": "^11.2.0",
20
21
  "yaml": "^2.8.2",
21
- "@rigour-labs/core": "2.9.4"
22
+ "@rigour-labs/core": "2.11.0"
22
23
  },
23
24
  "devDependencies": {
24
25
  "@types/node": "^25.0.3"
package/src/index.ts CHANGED
@@ -8,7 +8,19 @@ import {
8
8
  import fs from "fs-extra";
9
9
  import path from "path";
10
10
  import yaml from "yaml";
11
- import { GateRunner, ConfigSchema, Report } from "@rigour-labs/core";
11
+ import { randomUUID } from "crypto";
12
+ import {
13
+ GateRunner,
14
+ ConfigSchema,
15
+ Report,
16
+ } from "@rigour-labs/core";
17
+ import {
18
+ PatternMatcher,
19
+ loadPatternIndex,
20
+ getDefaultIndexPath,
21
+ StalenessDetector,
22
+ SecurityDetector
23
+ } from "@rigour-labs/core/pattern-index";
12
24
 
13
25
  const server = new Server(
14
26
  {
@@ -56,6 +68,23 @@ async function saveMemory(cwd: string, store: MemoryStore): Promise<void> {
56
68
  await fs.writeFile(memPath, JSON.stringify(store, null, 2));
57
69
  }
58
70
 
71
+ // Helper to log events for Rigour Studio
72
+ async function logStudioEvent(cwd: string, event: any) {
73
+ try {
74
+ const rigourDir = path.join(cwd, ".rigour");
75
+ await fs.ensureDir(rigourDir);
76
+ const eventsPath = path.join(rigourDir, "events.jsonl");
77
+ const logEntry = JSON.stringify({
78
+ id: randomUUID(),
79
+ timestamp: new Date().toISOString(),
80
+ ...event
81
+ }) + "\n";
82
+ await fs.appendFile(eventsPath, logEntry);
83
+ } catch {
84
+ // Silent fail - Studio logging is non-blocking and zero-telemetry
85
+ }
86
+ }
87
+
59
88
  server.setRequestHandler(ListToolsRequestSchema, async () => {
60
89
  return {
61
90
  tools: [
@@ -201,6 +230,46 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
201
230
  required: ["cwd", "key"],
202
231
  },
203
232
  },
233
+ {
234
+ name: "rigour_check_pattern",
235
+ description: "Checks if a proposed code pattern (function, component, etc.) already exists, is stale, or has security vulnerabilities (CVEs). CALL THIS BEFORE CREATING NEW CODE.",
236
+ inputSchema: {
237
+ type: "object",
238
+ properties: {
239
+ cwd: {
240
+ type: "string",
241
+ description: "Absolute path to the project root.",
242
+ },
243
+ name: {
244
+ type: "string",
245
+ description: "The name of the function, class, or component you want to create.",
246
+ },
247
+ type: {
248
+ type: "string",
249
+ description: "The type of pattern (e.g., 'function', 'component', 'hook', 'type').",
250
+ },
251
+ intent: {
252
+ type: "string",
253
+ description: "What the code is for (e.g., 'format dates', 'user authentication').",
254
+ },
255
+ },
256
+ required: ["cwd", "name"],
257
+ },
258
+ },
259
+ {
260
+ name: "rigour_security_audit",
261
+ description: "Runs a live security audit (CVE check) on the project dependencies.",
262
+ inputSchema: {
263
+ type: "object",
264
+ properties: {
265
+ cwd: {
266
+ type: "string",
267
+ description: "Absolute path to the project root.",
268
+ },
269
+ },
270
+ required: ["cwd"],
271
+ },
272
+ },
204
273
  ],
205
274
  };
206
275
  });
@@ -208,15 +277,25 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
208
277
  server.setRequestHandler(CallToolRequestSchema, async (request) => {
209
278
  const { name, arguments: args } = request.params;
210
279
  const cwd = (args as any)?.cwd || process.cwd();
280
+ const requestId = randomUUID();
211
281
 
212
282
  try {
283
+ await logStudioEvent(cwd, {
284
+ type: "tool_call",
285
+ requestId,
286
+ tool: name,
287
+ arguments: args
288
+ });
289
+
213
290
  const config = await loadConfig(cwd);
214
291
  const runner = new GateRunner(config);
215
292
 
293
+ let result: any;
294
+
216
295
  switch (name) {
217
296
  case "rigour_check": {
218
297
  const report = await runner.run(cwd);
219
- return {
298
+ result = {
220
299
  content: [
221
300
  {
222
301
  type: "text",
@@ -224,12 +303,16 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
224
303
  },
225
304
  ],
226
305
  };
306
+
307
+ // Add the report to the tool_response log for high-fidelity Studio visualization
308
+ (result as any)._rigour_report = report;
309
+ break;
227
310
  }
228
311
 
229
312
  case "rigour_explain": {
230
313
  const report = await runner.run(cwd);
231
314
  if (report.status === "PASS") {
232
- return {
315
+ result = {
233
316
  content: [
234
317
  {
235
318
  type: "text",
@@ -237,25 +320,26 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
237
320
  },
238
321
  ],
239
322
  };
240
- }
241
-
242
- const bullets = report.failures.map((f, i) => {
243
- return `${i + 1}. [${f.id.toUpperCase()}] ${f.title}: ${f.details}${f.hint ? ` (Hint: ${f.hint})` : ''}`;
244
- }).join("\n");
323
+ } else {
324
+ const bullets = report.failures.map((f, i) => {
325
+ return `${i + 1}. [${f.id.toUpperCase()}] ${f.title}: ${f.details}${f.hint ? ` (Hint: ${f.hint})` : ''}`;
326
+ }).join("\n");
245
327
 
246
- return {
247
- content: [
248
- {
249
- type: "text",
250
- text: `RIGOUR EXPLAIN:\n\n${bullets}`,
251
- },
252
- ],
253
- };
328
+ result = {
329
+ content: [
330
+ {
331
+ type: "text",
332
+ text: `RIGOUR EXPLAIN:\n\n${bullets}`,
333
+ },
334
+ ],
335
+ };
336
+ }
337
+ break;
254
338
  }
255
339
 
256
340
  case "rigour_status": {
257
341
  const report = await runner.run(cwd);
258
- return {
342
+ result = {
259
343
  content: [
260
344
  {
261
345
  type: "text",
@@ -268,12 +352,13 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
268
352
  },
269
353
  ],
270
354
  };
355
+ break;
271
356
  }
272
357
 
273
358
  case "rigour_get_fix_packet": {
274
359
  const report = await runner.run(cwd);
275
360
  if (report.status === "PASS") {
276
- return {
361
+ result = {
277
362
  content: [
278
363
  {
279
364
  type: "text",
@@ -281,32 +366,33 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
281
366
  },
282
367
  ],
283
368
  };
369
+ } else {
370
+ const packet = report.failures.map((f, i) => {
371
+ let text = `FIX TASK ${i + 1}: [${f.id.toUpperCase()}] ${f.title}\n`;
372
+ text += ` - CONTEXT: ${f.details}\n`;
373
+ if (f.files && f.files.length > 0) {
374
+ text += ` - TARGET FILES: ${f.files.join(", ")}\n`;
375
+ }
376
+ if (f.hint) {
377
+ text += ` - REFACTORING GUIDANCE: ${f.hint}\n`;
378
+ }
379
+ return text;
380
+ }).join("\n---\n");
381
+
382
+ result = {
383
+ content: [
384
+ {
385
+ type: "text",
386
+ text: `ENGINEERING REFINEMENT REQUIRED:\n\nThe project state violated ${report.failures.length} quality gates. You MUST address these failures before declaring the task complete:\n\n${packet}`,
387
+ },
388
+ ],
389
+ };
284
390
  }
285
-
286
- const packet = report.failures.map((f, i) => {
287
- let text = `FIX TASK ${i + 1}: [${f.id.toUpperCase()}] ${f.title}\n`;
288
- text += ` - CONTEXT: ${f.details}\n`;
289
- if (f.files && f.files.length > 0) {
290
- text += ` - TARGET FILES: ${f.files.join(", ")}\n`;
291
- }
292
- if (f.hint) {
293
- text += ` - REFACTORING GUIDANCE: ${f.hint}\n`;
294
- }
295
- return text;
296
- }).join("\n---\n");
297
-
298
- return {
299
- content: [
300
- {
301
- type: "text",
302
- text: `ENGINEERING REFINEMENT REQUIRED:\n\nThe project state violated ${report.failures.length} quality gates. You MUST address these failures before declaring the task complete:\n\n${packet}`,
303
- },
304
- ],
305
- };
391
+ break;
306
392
  }
307
393
 
308
394
  case "rigour_list_gates":
309
- return {
395
+ result = {
310
396
  content: [
311
397
  {
312
398
  type: "text",
@@ -319,9 +405,10 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
319
405
  },
320
406
  ],
321
407
  };
408
+ break;
322
409
 
323
410
  case "rigour_get_config":
324
- return {
411
+ result = {
325
412
  content: [
326
413
  {
327
414
  type: "text",
@@ -329,6 +416,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
329
416
  },
330
417
  ],
331
418
  };
419
+ break;
332
420
 
333
421
  case "rigour_remember": {
334
422
  const { key, value } = args as any;
@@ -338,7 +426,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
338
426
  timestamp: new Date().toISOString(),
339
427
  };
340
428
  await saveMemory(cwd, store);
341
- return {
429
+ result = {
342
430
  content: [
343
431
  {
344
432
  type: "text",
@@ -346,6 +434,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
346
434
  },
347
435
  ],
348
436
  };
437
+ break;
349
438
  }
350
439
 
351
440
  case "rigour_recall": {
@@ -355,7 +444,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
355
444
  if (key) {
356
445
  const memory = store.memories[key];
357
446
  if (!memory) {
358
- return {
447
+ result = {
359
448
  content: [
360
449
  {
361
450
  type: "text",
@@ -363,77 +452,183 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
363
452
  },
364
453
  ],
365
454
  };
455
+ } else {
456
+ result = {
457
+ content: [
458
+ {
459
+ type: "text",
460
+ text: `RECALLED MEMORY [${key}]:\n${memory.value}\n\n(Stored: ${memory.timestamp})`,
461
+ },
462
+ ],
463
+ };
464
+ }
465
+ } else {
466
+ const keys = Object.keys(store.memories);
467
+ if (keys.length === 0) {
468
+ result = {
469
+ content: [
470
+ {
471
+ type: "text",
472
+ text: "NO MEMORIES STORED. Use rigour_remember to persist important instructions.",
473
+ },
474
+ ],
475
+ };
476
+ } else {
477
+ const allMemories = keys.map(k => {
478
+ const mem = store.memories[k];
479
+ return `## ${k}\n${mem.value}\n(Stored: ${mem.timestamp})`;
480
+ }).join("\n\n---\n\n");
481
+
482
+ result = {
483
+ content: [
484
+ {
485
+ type: "text",
486
+ text: `RECALLED ALL MEMORIES (${keys.length} items):\n\n${allMemories}\n\n---\nIMPORTANT: Follow these stored instructions throughout this session.`,
487
+ },
488
+ ],
489
+ };
366
490
  }
367
- return {
491
+ }
492
+ break;
493
+ }
494
+
495
+ case "rigour_forget": {
496
+ const { key } = args as any;
497
+ const store = await loadMemory(cwd);
498
+
499
+ if (!store.memories[key]) {
500
+ result = {
368
501
  content: [
369
502
  {
370
503
  type: "text",
371
- text: `RECALLED MEMORY [${key}]:\n${memory.value}\n\n(Stored: ${memory.timestamp})`,
504
+ text: `NO MEMORY FOUND for key "${key}". Nothing to forget.`,
372
505
  },
373
506
  ],
374
507
  };
375
- }
508
+ } else {
509
+ delete store.memories[key];
510
+ await saveMemory(cwd, store);
376
511
 
377
- const keys = Object.keys(store.memories);
378
- if (keys.length === 0) {
379
- return {
512
+ result = {
380
513
  content: [
381
514
  {
382
515
  type: "text",
383
- text: "NO MEMORIES STORED. Use rigour_remember to persist important instructions.",
516
+ text: `MEMORY DELETED: "${key}" has been removed.`,
384
517
  },
385
518
  ],
386
519
  };
387
520
  }
521
+ break;
522
+ }
523
+
524
+ case "rigour_check_pattern": {
525
+ const { name: patternName, type, intent } = args as any;
526
+ const indexPath = getDefaultIndexPath(cwd);
527
+ const index = await loadPatternIndex(indexPath);
528
+
529
+ let resultText = "";
530
+
531
+ // 1. Check for Reinvention
532
+ if (index) {
533
+ const matcher = new PatternMatcher(index);
534
+ const matchResult = await matcher.match({ name: patternName, type, intent });
535
+
536
+ if (matchResult.status === "FOUND_SIMILAR") {
537
+ resultText += `🚨 PATTERN REINVENTION DETECTED\n`;
538
+ resultText += `Similar pattern already exists: "${matchResult.matches[0].pattern.name}" in ${matchResult.matches[0].pattern.file}\n`;
539
+ resultText += `SUGGESTION: ${matchResult.suggestion}\n\n`;
540
+ }
541
+ } else {
542
+ resultText += `⚠️ Pattern index not found. Run 'rigour index' to enable reinvention detection.\n\n`;
543
+ }
388
544
 
389
- const allMemories = keys.map(k => {
390
- const mem = store.memories[k];
391
- return `## ${k}\n${mem.value}\n(Stored: ${mem.timestamp})`;
392
- }).join("\n\n---\n\n");
545
+ // 2. Check for Staleness/Best Practices
546
+ const detector = new StalenessDetector(cwd);
547
+ const staleness = await detector.checkStaleness(`${type || 'function'} ${patternName} {}`);
548
+
549
+ if (staleness.status !== "FRESH") {
550
+ resultText += `⚠️ STALENESS/ANTI-PATTERN WARNING\n`;
551
+ for (const issue of staleness.issues) {
552
+ resultText += `- ${issue.reason}\n REPLACEMENT: ${issue.replacement}\n`;
553
+ }
554
+ resultText += `\n`;
555
+ }
556
+
557
+ // 3. Check Security for this library (if it's an import)
558
+ if (intent && intent.includes('import')) {
559
+ const security = new SecurityDetector(cwd);
560
+ const audit = await security.runAudit();
561
+ const relatedVulns = audit.vulnerabilities.filter(v =>
562
+ patternName.toLowerCase().includes(v.packageName.toLowerCase()) ||
563
+ intent.toLowerCase().includes(v.packageName.toLowerCase())
564
+ );
565
+
566
+ if (relatedVulns.length > 0) {
567
+ resultText += `🛡️ SECURITY/CVE WARNING\n`;
568
+ for (const v of relatedVulns) {
569
+ resultText += `- [${v.severity.toUpperCase()}] ${v.packageName}: ${v.title} (${v.url})\n`;
570
+ }
571
+ resultText += `\n`;
572
+ }
573
+ }
393
574
 
394
- return {
575
+ if (!resultText) {
576
+ resultText = `✅ Pattern "${patternName}" is fresh, secure, and unique to the codebase.\n\nRECOMMENDED ACTION: Proceed with implementation.`;
577
+ } else {
578
+ let recommendation = "Proceed with caution, addressing the warnings above.";
579
+ if (resultText.includes("🚨 PATTERN REINVENTION")) {
580
+ recommendation = "STOP and REUSE the existing pattern mentioned above. Do not create a duplicate.";
581
+ } else if (resultText.includes("🛡️ SECURITY/CVE WARNING")) {
582
+ recommendation = "STOP and update your dependencies or find an alternative library. Do not proceed with vulnerable code.";
583
+ } else if (resultText.includes("⚠️ STALENESS")) {
584
+ recommendation = "Follow the replacement suggestion to ensure best practices.";
585
+ }
586
+
587
+ resultText += `\nRECOMMENDED ACTION: ${recommendation}`;
588
+ }
589
+
590
+ result = {
395
591
  content: [
396
592
  {
397
593
  type: "text",
398
- text: `RECALLED ALL MEMORIES (${keys.length} items):\n\n${allMemories}\n\n---\nIMPORTANT: Follow these stored instructions throughout this session.`,
594
+ text: resultText,
399
595
  },
400
596
  ],
401
597
  };
598
+ break;
402
599
  }
403
600
 
404
- case "rigour_forget": {
405
- const { key } = args as any;
406
- const store = await loadMemory(cwd);
407
-
408
- if (!store.memories[key]) {
409
- return {
410
- content: [
411
- {
412
- type: "text",
413
- text: `NO MEMORY FOUND for key "${key}". Nothing to forget.`,
414
- },
415
- ],
416
- };
417
- }
418
-
419
- delete store.memories[key];
420
- await saveMemory(cwd, store);
421
-
422
- return {
601
+ case "rigour_security_audit": {
602
+ const security = new SecurityDetector(cwd);
603
+ const summary = await security.getSecuritySummary();
604
+ result = {
423
605
  content: [
424
606
  {
425
607
  type: "text",
426
- text: `MEMORY DELETED: "${key}" has been removed.`,
608
+ text: summary,
427
609
  },
428
610
  ],
429
611
  };
612
+ break;
430
613
  }
431
614
 
432
615
  default:
433
616
  throw new Error(`Unknown tool: ${name}`);
434
617
  }
618
+
619
+ await logStudioEvent(cwd, {
620
+ type: "tool_response",
621
+ requestId,
622
+ tool: name,
623
+ status: "success",
624
+ content: result.content,
625
+ _rigour_report: (result as any)._rigour_report
626
+ });
627
+
628
+ return result;
629
+
435
630
  } catch (error: any) {
436
- return {
631
+ const errorResponse = {
437
632
  content: [
438
633
  {
439
634
  type: "text",
@@ -442,6 +637,17 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
442
637
  ],
443
638
  isError: true,
444
639
  };
640
+
641
+ await logStudioEvent(cwd, {
642
+ type: "tool_response",
643
+ requestId,
644
+ tool: name,
645
+ status: "error",
646
+ error: error.message,
647
+ content: errorResponse.content
648
+ });
649
+
650
+ return errorResponse;
445
651
  }
446
652
  });
447
653