@rigour-labs/mcp 5.5.0 → 5.5.1

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 CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  **AI Agent Governance via Model Context Protocol — quality gates, DLP, drift detection, and deep analysis.**
4
4
 
5
- Rigour is a local-first MCP server that governs AI agents (Claude, Cursor, Cline, Windsurf) with deterministic quality gates, credential interception, and memory governance.
5
+ Rigour is a local-first MCP server that governs AI agents (Claude, Cursor, Cline, Windsurf) with deterministic quality gates, credential warnings, and memory governance.
6
6
 
7
7
  [![Registry](https://img.shields.io/badge/MCP-Registry-brightgreen)](https://github.com/mcp)
8
8
  [![npm version](https://img.shields.io/npm/v/@rigour-labs/mcp?color=cyan)](https://www.npmjs.com/package/@rigour-labs/mcp)
@@ -0,0 +1 @@
1
+ export declare function getPinnedCheckerCommand(): string;
@@ -0,0 +1,30 @@
1
+ import { readJsonSync } from 'fs-extra';
2
+ import path from 'path';
3
+ import { fileURLToPath } from 'url';
4
+ function readVersion(packagePath) {
5
+ try {
6
+ const pkg = readJsonSync(packagePath);
7
+ const version = pkg.version?.trim();
8
+ return version && /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/.test(version)
9
+ ? version
10
+ : null;
11
+ }
12
+ catch {
13
+ return null;
14
+ }
15
+ }
16
+ export function getPinnedCheckerCommand() {
17
+ const thisDir = path.dirname(fileURLToPath(import.meta.url));
18
+ const packageCandidates = [
19
+ path.resolve(thisDir, '../../../cli/package.json'),
20
+ path.resolve(thisDir, '../../../rigour-cli/package.json'),
21
+ path.resolve(thisDir, '../../package.json'),
22
+ ];
23
+ const version = packageCandidates
24
+ .map(readVersion)
25
+ .find((candidate) => candidate !== null);
26
+ if (!version) {
27
+ throw new Error('Unable to resolve a compatible Rigour CLI version');
28
+ }
29
+ return `npx --yes @rigour-labs/cli@${version} hooks check`;
30
+ }
@@ -441,7 +441,7 @@ export const TOOL_DEFINITIONS = [
441
441
  },
442
442
  {
443
443
  name: "rigour_hooks_init",
444
- description: "Generate hook configs for AI coding tools (Claude, Cursor, Cline, Windsurf). Installs real-time quality checks AND DLP (credential interception) hooks by default. DLP hooks intercept credentials in user input BEFORE they reach the AI agent. Pass dlp=false to disable DLP hooks only.",
444
+ description: "Generate hook configs for AI coding tools (Claude, Cursor, Cline, Windsurf). Installs real-time quality checks and non-blocking DLP credential warnings by default. Pass dlp=false to disable DLP hooks only.",
445
445
  inputSchema: {
446
446
  type: "object",
447
447
  properties: {
@@ -449,7 +449,7 @@ export const TOOL_DEFINITIONS = [
449
449
  tool: { type: "string", description: "Target tool: 'claude', 'cursor', 'cline', or 'windsurf'." },
450
450
  force: { type: "boolean", description: "Overwrite existing hook files (default: false)." },
451
451
  dryRun: { type: "boolean", description: "Preview changes without writing files (default: false)." },
452
- dlp: { type: "boolean", description: "Generate DLP (Data Loss Prevention) hooks for pre-input credential interception (default: true). Set false to skip DLP hooks." },
452
+ dlp: { type: "boolean", description: "Generate DLP hooks for pre-input credential warnings (default: true). Set false to skip DLP hooks." },
453
453
  },
454
454
  required: ["cwd", "tool"],
455
455
  },
@@ -4,7 +4,7 @@
4
4
  * Handlers for: rigour_input_scan, rigour_dlp_init
5
5
  *
6
6
  * AI Agent Data Loss Prevention — scans user input for credentials
7
- * before they reach AI agents.
7
+ * before agent actions.
8
8
  *
9
9
  * @since v4.2.0 — AI Agent DLP layer
10
10
  */
@@ -19,18 +19,18 @@ type ToolResult = {
19
19
  * rigour_input_scan — Scan text for credentials before agent processing.
20
20
  *
21
21
  * This is the core DLP tool. Agents can call this to validate user input
22
- * contains no secrets before processing it.
22
+ * may contain secrets before processing it.
23
23
  */
24
24
  export declare function handleInputScan(cwd: string, text: string, agent?: string, block?: boolean): Promise<ToolResult>;
25
25
  /**
26
26
  * rigour_dlp_init — Generate DLP hook configs for AI coding tools.
27
27
  *
28
- * Creates pre-input hooks that intercept credentials before they
29
- * reach the AI agent.
28
+ * Creates pre-input hooks that warn about possible credentials before
29
+ * agent actions.
30
30
  */
31
31
  export declare function handleDLPInit(cwd: string, tool: string, force?: boolean, dryRun?: boolean): Promise<ToolResult>;
32
32
  /**
33
- * rigour_dlp_audit — Query the DLP audit log for recent credential interceptions.
33
+ * rigour_dlp_audit — Query the DLP audit log for recent credential detections.
34
34
  */
35
35
  export declare function handleDLPAudit(cwd: string, limit?: number): Promise<ToolResult>;
36
36
  export {};
@@ -4,18 +4,20 @@
4
4
  * Handlers for: rigour_input_scan, rigour_dlp_init
5
5
  *
6
6
  * AI Agent Data Loss Prevention — scans user input for credentials
7
- * before they reach AI agents.
7
+ * before agent actions.
8
8
  *
9
9
  * @since v4.2.0 — AI Agent DLP layer
10
10
  */
11
11
  import { scanInputForCredentials, formatDLPAlert, createDLPAuditEntry, generateDLPHookFiles } from '@rigour-labs/core';
12
12
  import fs from 'fs-extra';
13
13
  import path from 'path';
14
+ import { getPinnedCheckerCommand } from './cli-command.js';
15
+ import { formatHookWriteResult, writeHookFiles } from './hook-file-writer.js';
14
16
  /**
15
17
  * rigour_input_scan — Scan text for credentials before agent processing.
16
18
  *
17
19
  * This is the core DLP tool. Agents can call this to validate user input
18
- * contains no secrets before processing it.
20
+ * may contain secrets before processing it.
19
21
  */
20
22
  export async function handleInputScan(cwd, text, agent, block) {
21
23
  if (!text || text.trim().length === 0) {
@@ -28,7 +30,7 @@ export async function handleInputScan(cwd, text, agent, block) {
28
30
  }
29
31
  const result = scanInputForCredentials(text, {
30
32
  enabled: true,
31
- block_on_detection: block ?? true,
33
+ block_on_detection: block ?? false,
32
34
  audit_log: true,
33
35
  });
34
36
  // Log to audit trail
@@ -66,13 +68,13 @@ export async function handleInputScan(cwd, text, agent, block) {
66
68
  /**
67
69
  * rigour_dlp_init — Generate DLP hook configs for AI coding tools.
68
70
  *
69
- * Creates pre-input hooks that intercept credentials before they
70
- * reach the AI agent.
71
+ * Creates pre-input hooks that warn about possible credentials before
72
+ * agent actions.
71
73
  */
72
74
  export async function handleDLPInit(cwd, tool, force = false, dryRun = false) {
73
75
  try {
74
76
  const hookTool = tool;
75
- const checkerCommand = 'rigour hooks check';
77
+ const checkerCommand = getPinnedCheckerCommand();
76
78
  const files = generateDLPHookFiles(hookTool, checkerCommand);
77
79
  if (dryRun) {
78
80
  const preview = files.map(f => `${f.path}:\n${f.content.slice(0, 200)}...`).join('\n\n');
@@ -83,30 +85,12 @@ export async function handleDLPInit(cwd, tool, force = false, dryRun = false) {
83
85
  }],
84
86
  };
85
87
  }
86
- const written = [];
87
- const skipped = [];
88
- for (const file of files) {
89
- const fullPath = path.join(cwd, file.path);
90
- if (!force && await fs.pathExists(fullPath)) {
91
- skipped.push(file.path);
92
- continue;
93
- }
94
- await fs.ensureDir(path.dirname(fullPath));
95
- await fs.writeFile(fullPath, file.content);
96
- if (file.executable) {
97
- await fs.chmod(fullPath, 0o755);
98
- }
99
- written.push(file.path);
100
- }
101
- const parts = [];
102
- if (written.length > 0)
103
- parts.push(`✓ Created: ${written.join(', ')}`);
104
- if (skipped.length > 0)
105
- parts.push(`⊘ Skipped (exists): ${skipped.join(', ')}. Use force=true to overwrite.`);
88
+ const writeResult = await writeHookFiles(cwd, files, force);
89
+ const parts = formatHookWriteResult(writeResult);
106
90
  parts.push(`Tool: ${tool}`);
107
- parts.push('DLP Protection: AWS keys, API tokens, database URLs, private keys, JWTs, passwords');
91
+ parts.push('DLP warnings: AWS keys, API tokens, database URLs, private keys, JWTs, passwords');
108
92
  parts.push('');
109
- parts.push('🛑 Credentials will now be intercepted BEFORE reaching the AI agent.');
93
+ parts.push('⚠ Possible credentials will be reported before agent actions.');
110
94
  return {
111
95
  content: [{ type: 'text', text: parts.join('\n') }],
112
96
  };
@@ -122,7 +106,7 @@ export async function handleDLPInit(cwd, tool, force = false, dryRun = false) {
122
106
  }
123
107
  }
124
108
  /**
125
- * rigour_dlp_audit — Query the DLP audit log for recent credential interceptions.
109
+ * rigour_dlp_audit — Query the DLP audit log for recent credential detections.
126
110
  */
127
111
  export async function handleDLPAudit(cwd, limit = 20) {
128
112
  try {
@@ -0,0 +1,14 @@
1
+ interface HookFile {
2
+ path: string;
3
+ content: string;
4
+ executable?: boolean;
5
+ }
6
+ export declare function mergeHookFiles(files: HookFile[]): HookFile[];
7
+ export interface HookWriteResult {
8
+ written: string[];
9
+ skipped: string[];
10
+ incompatible: string[];
11
+ }
12
+ export declare function writeHookFiles(cwd: string, files: HookFile[], force: boolean): Promise<HookWriteResult>;
13
+ export declare function formatHookWriteResult(result: HookWriteResult): string[];
14
+ export {};
@@ -0,0 +1,71 @@
1
+ import fs from 'fs-extra';
2
+ import path from 'path';
3
+ function parseHookConfig(content, filePath) {
4
+ try {
5
+ return JSON.parse(content);
6
+ }
7
+ catch (error) {
8
+ const reason = error instanceof Error ? error.message : String(error);
9
+ throw new Error(`Cannot merge generated hook config ${filePath}: ${reason}`);
10
+ }
11
+ }
12
+ export function mergeHookFiles(files) {
13
+ const merged = new Map();
14
+ for (const file of files) {
15
+ const existing = merged.get(file.path);
16
+ if (!existing) {
17
+ merged.set(file.path, file);
18
+ continue;
19
+ }
20
+ const existingConfig = parseHookConfig(existing.content, file.path);
21
+ const nextConfig = parseHookConfig(file.content, file.path);
22
+ merged.set(file.path, {
23
+ ...existing,
24
+ content: JSON.stringify({
25
+ ...existingConfig,
26
+ ...nextConfig,
27
+ hooks: { ...existingConfig.hooks, ...nextConfig.hooks },
28
+ }, null, 2),
29
+ executable: existing.executable || file.executable,
30
+ });
31
+ }
32
+ return [...merged.values()];
33
+ }
34
+ export async function writeHookFiles(cwd, files, force) {
35
+ const result = { written: [], skipped: [], incompatible: [] };
36
+ for (const file of files) {
37
+ const fullPath = path.join(cwd, file.path);
38
+ if (!force && await fs.pathExists(fullPath)) {
39
+ result.skipped.push(file.path);
40
+ continue;
41
+ }
42
+ try {
43
+ await fs.ensureDir(path.dirname(fullPath));
44
+ await fs.writeFile(fullPath, file.content);
45
+ if (file.executable)
46
+ await fs.chmod(fullPath, 0o755);
47
+ result.written.push(file.path);
48
+ }
49
+ catch (error) {
50
+ const code = error instanceof Error && 'code' in error
51
+ ? String(error.code)
52
+ : '';
53
+ if (code !== 'ENOTDIR' && code !== 'EEXIST')
54
+ throw error;
55
+ result.incompatible.push(file.path);
56
+ }
57
+ }
58
+ return result;
59
+ }
60
+ export function formatHookWriteResult(result) {
61
+ const parts = [];
62
+ if (result.written.length > 0)
63
+ parts.push(`✓ Created: ${result.written.join(', ')}`);
64
+ if (result.skipped.length > 0) {
65
+ parts.push(`⊘ Skipped (exists): ${result.skipped.join(', ')}. Use force=true to overwrite.`);
66
+ }
67
+ if (result.incompatible.length > 0) {
68
+ parts.push(`⚠ Not configured (parent path is a file): ${result.incompatible.join(', ')}`);
69
+ }
70
+ return parts;
71
+ }
@@ -18,7 +18,7 @@ export declare function handleHooksCheck(cwd: string, files: string[], timeout?:
18
18
  * rigour_hooks_init — Generate hook configs for AI coding tools.
19
19
  *
20
20
  * NEW in v4.2.0: When `dlp` param is true, also generates pre-input
21
- * DLP hooks that intercept credentials before agent processing.
21
+ * DLP hooks that warn about possible credentials before agent processing.
22
22
  */
23
23
  export declare function handleHooksInit(cwd: string, tool: string, force?: boolean, dryRun?: boolean, dlp?: boolean): Promise<ToolResult>;
24
24
  export {};
@@ -5,12 +5,14 @@
5
5
  * Now includes DLP (Data Loss Prevention) capabilities.
6
6
  *
7
7
  * @since v3.0.0 — real-time hooks for AI coding tools
8
- * @since v4.2.0 — AI Agent DLP layer (credential interception)
8
+ * @since v4.2.0 — AI Agent DLP warning layer
9
9
  */
10
10
  import { runHookChecker, generateHookFiles } from "@rigour-labs/core";
11
11
  import { scanInputForCredentials, formatDLPAlert, createDLPAuditEntry, generateDLPHookFiles } from "@rigour-labs/core";
12
12
  import fs from "fs-extra";
13
13
  import path from "path";
14
+ import { getPinnedCheckerCommand } from "./cli-command.js";
15
+ import { formatHookWriteResult, mergeHookFiles, writeHookFiles } from "./hook-file-writer.js";
14
16
  /**
15
17
  * rigour_hooks_check — Run the fast hook checker on specific files.
16
18
  * This is the same check that runs inside IDE hooks (Claude, Cursor, Cline, Windsurf).
@@ -24,7 +26,7 @@ export async function handleHooksCheck(cwd, files, timeout, text, agent) {
24
26
  if (text) {
25
27
  const result = scanInputForCredentials(text, {
26
28
  enabled: true,
27
- block_on_detection: true,
29
+ block_on_detection: false,
28
30
  audit_log: true,
29
31
  });
30
32
  // Log to audit trail (with rotation to prevent unbounded growth)
@@ -92,17 +94,17 @@ export async function handleHooksCheck(cwd, files, timeout, text, agent) {
92
94
  * rigour_hooks_init — Generate hook configs for AI coding tools.
93
95
  *
94
96
  * NEW in v4.2.0: When `dlp` param is true, also generates pre-input
95
- * DLP hooks that intercept credentials before agent processing.
97
+ * DLP hooks that warn about possible credentials before agent processing.
96
98
  */
97
99
  export async function handleHooksInit(cwd, tool, force = false, dryRun = false, dlp = true) {
98
100
  try {
99
101
  const hookTool = tool;
100
- const checkerCommand = 'rigour hooks check';
102
+ const checkerCommand = getPinnedCheckerCommand();
101
103
  // Generate post-output hooks (existing)
102
104
  const files = generateHookFiles(hookTool, checkerCommand);
103
105
  // Generate DLP pre-input hooks (new)
104
106
  const dlpFiles = dlp ? generateDLPHookFiles(hookTool, checkerCommand) : [];
105
- const allFiles = [...files, ...dlpFiles];
107
+ const allFiles = mergeHookFiles([...files, ...dlpFiles]);
106
108
  if (dryRun) {
107
109
  const preview = allFiles.map(f => `${f.path}:\n${f.content.slice(0, 300)}...`).join('\n\n');
108
110
  return {
@@ -112,32 +114,14 @@ export async function handleHooksInit(cwd, tool, force = false, dryRun = false,
112
114
  }],
113
115
  };
114
116
  }
115
- const written = [];
116
- const skipped = [];
117
- for (const file of allFiles) {
118
- const fullPath = path.join(cwd, file.path);
119
- if (!force && await fs.pathExists(fullPath)) {
120
- skipped.push(file.path);
121
- continue;
122
- }
123
- await fs.ensureDir(path.dirname(fullPath));
124
- await fs.writeFile(fullPath, file.content);
125
- if (file.executable) {
126
- await fs.chmod(fullPath, 0o755);
127
- }
128
- written.push(file.path);
129
- }
130
- const parts = [];
131
- if (written.length > 0)
132
- parts.push(`✓ Created: ${written.join(', ')}`);
133
- if (skipped.length > 0)
134
- parts.push(`⊘ Skipped (exists): ${skipped.join(', ')}. Use force=true to overwrite.`);
117
+ const writeResult = await writeHookFiles(cwd, allFiles, force);
118
+ const parts = formatHookWriteResult(writeResult);
135
119
  parts.push(`Tool: ${tool}`);
136
120
  parts.push('Checks: file-size, security-patterns, hallucinated-imports, command-injection');
137
121
  if (dlp) {
138
122
  parts.push('');
139
- parts.push('🛑 DLP Protection: AWS keys, API tokens, database URLs, private keys, JWTs, passwords');
140
- parts.push('Credentials will be intercepted BEFORE reaching the AI agent.');
123
+ parts.push('⚠ DLP warnings: AWS keys, API tokens, database URLs, private keys, JWTs, passwords');
124
+ parts.push('Possible credentials will be reported before agent actions.');
141
125
  }
142
126
  return {
143
127
  content: [{ type: "text", text: parts.join('\n') }],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rigour-labs/mcp",
3
- "version": "5.5.0",
3
+ "version": "5.5.1",
4
4
  "description": "MCP server + live dashboard for AI code governance — OWASP LLM Top 10 (10/10), real-time MCP App UI, 25+ security patterns, Bayesian learning Brain, hallucinated import detection, multi-agent governance. Works with Claude, Cursor, VS Code, ChatGPT, Goose, Windsurf. Industry presets for HIPAA, SOC2, FedRAMP.",
5
5
  "license": "MIT",
6
6
  "homepage": "https://rigour.run",
@@ -62,7 +62,7 @@
62
62
  "execa": "^8.0.1",
63
63
  "fs-extra": "^11.2.0",
64
64
  "yaml": "^2.8.2",
65
- "@rigour-labs/core": "5.5.0"
65
+ "@rigour-labs/core": "5.5.1"
66
66
  },
67
67
  "devDependencies": {
68
68
  "@types/node": "^25.0.3",