@greenarmor/ges-mcp-server 1.2.5 → 1.2.6

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 (2) hide show
  1. package/dist/server.js +123 -18
  2. package/package.json +12 -12
package/dist/server.js CHANGED
@@ -9,7 +9,7 @@ import { generateScoreFile, formatScoreOutput, computeGrade, generateBadgeSvg, i
9
9
  import { runAudit, deduplicateFindings } from "@greenarmor/ges-audit-engine";
10
10
  import { GESF_VERSION, GES_DIR, COMPLIANCE_DIR, SECURITY_DIR, CONTROLS_DIR, POLICIES_DIR, CHECKLISTS_DIR, DOCS_DIR, REPORTS_DIR, DEFAULT_FRAMEWORKS } from "@greenarmor/ges-core";
11
11
  import { appendFixHistory, createFixHistoryEntry } from "@greenarmor/ges-core";
12
- import { addFrameworkToConfig, removeFrameworkFromConfig, saveControlOverride, loadControlsFromDisk, recordActivity } from "@greenarmor/ges-core";
12
+ import { addFrameworkToConfig, removeFrameworkFromConfig, loadControlsFromDisk, recordActivity, recordAIRecommendation } from "@greenarmor/ges-core";
13
13
  import { ProjectConfigSchema } from "@greenarmor/ges-core";
14
14
  import { generateComplianceDocs, generateSecurityDocs, generateConfigJson, generateMetadataJson, generateFrameworkVersionJson, generateScoreJson } from "@greenarmor/ges-doc-generator";
15
15
  import { generateAllWorkflows } from "@greenarmor/ges-cicd-generator";
@@ -200,13 +200,13 @@ const TOOLS = [
200
200
  },
201
201
  {
202
202
  name: "apply_control_override",
203
- description: "Mark a compliance control as not-applicable, pass, or another status in the project's .ges/control-overrides.json. Use this when a control doesn't apply to the project or has been verified manually.",
203
+ description: "Mark a compliance control as not-applicable. AI assistants CANNOT mark controls as 'pass' that must be verified by the GESF audit engine (run_audit). Use 'not-applicable' only when a control genuinely does not apply to the project. A detailed reason is required.",
204
204
  inputSchema: {
205
205
  type: "object",
206
206
  properties: {
207
207
  project_path: { type: "string", description: "Absolute path to the project root." },
208
208
  control_id: { type: "string", description: "Control ID to override (e.g. GDPR-ART32-004)" },
209
- status: { type: "string", description: "New status: 'not-applicable' or 'pass'" },
209
+ status: { type: "string", description: "Status: only 'not-applicable' is allowed via MCP. 'pass' must come from the audit engine." },
210
210
  reason: { type: "string", description: "Reason for the override" },
211
211
  },
212
212
  },
@@ -353,6 +353,24 @@ const TOOLS = [
353
353
  },
354
354
  },
355
355
  },
356
+ {
357
+ name: "record_recommendation",
358
+ description: "Record a recommendation or observation to .dev-logs/ai-recommendations/ for the GESF development team. Use this when you identify a potential improvement, weakness, or best-practice suggestion. Recommendations are NOT applied automatically — they are logged for human developers to review. This is the correct way to surface ideas instead of overriding controls.",
359
+ inputSchema: {
360
+ type: "object",
361
+ properties: {
362
+ project_path: { type: "string", description: "Absolute path to the project root." },
363
+ category: { type: "string", description: "Category: security, compliance, architecture, performance, best-practice, bug, or improvement." },
364
+ title: { type: "string", description: "Short title for the recommendation." },
365
+ description: { type: "string", description: "Detailed description of the observation." },
366
+ severity: { type: "string", description: "Severity: info, low, medium, or high." },
367
+ affected_controls: { type: "string", description: "Comma-separated control IDs affected (optional)." },
368
+ affected_files: { type: "string", description: "Comma-separated file paths affected (optional)." },
369
+ suggested_action: { type: "string", description: "What you suggest the developers do about this." },
370
+ },
371
+ required: ["project_path", "category", "title", "description", "suggested_action"],
372
+ },
373
+ },
356
374
  ];
357
375
  function send(message) {
358
376
  process.stdout.write(JSON.stringify(message) + "\n");
@@ -2456,8 +2474,28 @@ export function handleRequest(request) {
2456
2474
  resultText = "Error: control_id is required.";
2457
2475
  break;
2458
2476
  }
2459
- if (!["not-applicable", "pass"].includes(status)) {
2460
- resultText = `Error: status must be 'not-applicable' or 'pass'. Got: ${status}`;
2477
+ if (status === "pass") {
2478
+ resultText = [
2479
+ `# Cannot Override to "pass" via MCP\n`,
2480
+ `AI assistants cannot mark controls as "pass" — this must be verified by GESF's own audit engine.`,
2481
+ ``,
2482
+ `**Why**: Allowing AI to set "pass" creates false compliance scores. The audit engine scans actual source code to verify controls are implemented correctly.`,
2483
+ ``,
2484
+ `**Instead**:`,
2485
+ `1. Use \`implement_control\` to generate implementation files`,
2486
+ `2. Run \`run_audit\` — GESF will verify the implementation and update the score automatically`,
2487
+ `3. If the control is genuinely not applicable, use status: "not-applicable" with a detailed reason`,
2488
+ ``,
2489
+ `**For CLI users**: \`ges control <id> pass --reason "..."'\` is available for human developers who have manually verified.`,
2490
+ ].join("\n");
2491
+ break;
2492
+ }
2493
+ if (!["not-applicable"].includes(status)) {
2494
+ resultText = `Error: MCP only allows 'not-applicable'. Use 'not-applicable' for controls that don't apply to this project. Status 'pass' must come from the audit engine.`;
2495
+ break;
2496
+ }
2497
+ if (!reason || reason.trim().length < 10) {
2498
+ resultText = `Error: A detailed reason (at least 10 characters) is required when marking a control as not-applicable. Explain why this control does not apply to your project.`;
2461
2499
  break;
2462
2500
  }
2463
2501
  if (!fs.existsSync(path.join(projectPath, ".ges"))) {
@@ -2486,7 +2524,7 @@ export function handleRequest(request) {
2486
2524
  `# Control Override Applied\n`,
2487
2525
  `**Control**: ${controlId}`,
2488
2526
  `**Status**: ${status}`,
2489
- `**Reason**: ${reason || "(none provided)"}`,
2527
+ `**Reason**: ${reason}`,
2490
2528
  `**File**: ${overridePath}`,
2491
2529
  `**Total overrides**: ${overrides.length}\n`,
2492
2530
  `The override will take effect on the next \`ges audit\` or \`ges score\` run.`,
@@ -2582,9 +2620,9 @@ export function handleRequest(request) {
2582
2620
  }
2583
2621
  }
2584
2622
  if (appliedCount > 0 || skippedCount > 0) {
2585
- saveControlOverride(projectPath, controlId, "pass", `Auto-implemented by GESF (${plan.name})`);
2586
- lines.push(`\n✅ Control **${controlId}** marked as **pass** in .ges/control-overrides.json`);
2587
- lines.push(` The dashboard will reflect this immediately.`);
2623
+ lines.push(`\n⚠️ **Control status is NOT automatically changed.**`);
2624
+ lines.push(` GESF does not allow AI to self-verify implementations.`);
2625
+ lines.push(` The control will be verified when you run \`ges audit\`.`);
2588
2626
  }
2589
2627
  const npmInstalls = getNpmInstallsFromActions(plan.actions);
2590
2628
  if (npmInstalls.length > 0) {
@@ -2601,13 +2639,13 @@ export function handleRequest(request) {
2601
2639
  lines.push(`\n## Next Steps`);
2602
2640
  lines.push("1. Install any npm packages listed above");
2603
2641
  lines.push("2. Import and integrate the generated files into your app");
2604
- lines.push("3. Run `ges audit` to verify the control is now passing");
2642
+ lines.push("3. Run `ges audit` GESF will verify the implementation and update the score");
2605
2643
  resultText = lines.join("\n");
2606
2644
  recordActivity(projectPath, {
2607
2645
  source: "mcp",
2608
2646
  action: "implement_control",
2609
2647
  title: `Control implemented: ${controlId} (${plan.name})`,
2610
- description: `Auto-implemented control ${controlId} (${plan.name}). ${appliedCount} actions applied, ${skippedCount} skipped. Control marked as pass.`,
2648
+ description: `Auto-implemented control ${controlId} (${plan.name}). ${appliedCount} actions applied, ${skippedCount} skipped. Awaiting audit verification.`,
2611
2649
  details: { controls_affected: [controlId], files_created: plan.actions.filter(a => a.type === "create").map(a => a.filePath) },
2612
2650
  });
2613
2651
  break;
@@ -2722,10 +2760,22 @@ export function handleRequest(request) {
2722
2760
  fs.writeFileSync(path.join(gesDir, "framework-version.json"), frameworkVersionJson.content);
2723
2761
  const scoreJson = generateScoreJson();
2724
2762
  fs.writeFileSync(path.join(gesDir, "score.json"), scoreJson.content);
2725
- const dirs = [COMPLIANCE_DIR, SECURITY_DIR, CONTROLS_DIR, POLICIES_DIR, CHECKLISTS_DIR, DOCS_DIR, REPORTS_DIR];
2763
+ const dirs = [COMPLIANCE_DIR, SECURITY_DIR, CONTROLS_DIR, POLICIES_DIR, CHECKLISTS_DIR, DOCS_DIR, REPORTS_DIR, ".dev-logs"];
2726
2764
  for (const dir of dirs) {
2727
2765
  fs.mkdirSync(path.join(projectPath, dir), { recursive: true });
2728
2766
  }
2767
+ const gitignorePath = path.join(projectPath, ".gitignore");
2768
+ const devLogsIgnore = ".dev-logs/\n";
2769
+ if (fs.existsSync(gitignorePath)) {
2770
+ const existingGitignore = fs.readFileSync(gitignorePath, "utf-8");
2771
+ if (!existingGitignore.includes(".dev-logs/")) {
2772
+ fs.appendFileSync(gitignorePath, `\n# GESF developer logs (not for remote)\n${devLogsIgnore}`);
2773
+ }
2774
+ }
2775
+ else {
2776
+ fs.writeFileSync(gitignorePath, `# GESF developer logs (not for remote)\n${devLogsIgnore}\n`);
2777
+ }
2778
+ fs.writeFileSync(path.join(projectPath, ".dev-logs", "README.md"), `# Developer Logs\n\nThis directory is for GESF development notes, session logs, AI recommendations, and release notes.\n\n**This directory is gitignored and intended for developers only. Do not submit to remote.**\n`);
2729
2779
  const complianceDocs = generateComplianceDocs(projectName, projectType);
2730
2780
  for (const doc of complianceDocs) {
2731
2781
  const filePath = path.join(projectPath, COMPLIANCE_DIR, doc.filePath);
@@ -2989,13 +3039,18 @@ export function handleRequest(request) {
2989
3039
  const packDir = path.join(projectPath, CONTROLS_DIR, pack.id);
2990
3040
  fs.mkdirSync(packDir, { recursive: true });
2991
3041
  fs.writeFileSync(path.join(packDir, "controls.json"), JSON.stringify(pack.controls, null, 2));
2992
- const addedToConfig = addFrameworkToConfig(projectPath, pack.id.toUpperCase());
3042
+ const frameworksAdded = [];
3043
+ for (const fw of pack.frameworks) {
3044
+ if (addFrameworkToConfig(projectPath, fw)) {
3045
+ frameworksAdded.push(fw);
3046
+ }
3047
+ }
2993
3048
  const lines = [
2994
3049
  `✅ Installed policy pack: **${pack.id}** (${pack.name})`,
2995
3050
  `${pack.controls.length} controls written to ${CONTROLS_DIR}/${pack.id}/controls.json`,
2996
3051
  ];
2997
- if (addedToConfig) {
2998
- lines.push(`Added ${pack.id.toUpperCase()} to project frameworks in .ges/config.json`);
3052
+ if (frameworksAdded.length > 0) {
3053
+ lines.push(`Added ${frameworksAdded.join(", ")} to project frameworks in .ges/config.json`);
2999
3054
  }
3000
3055
  lines.push(`The web dashboard will now reflect this pack's controls.`);
3001
3056
  resultText = lines.join("\n");
@@ -3003,8 +3058,8 @@ export function handleRequest(request) {
3003
3058
  source: "mcp",
3004
3059
  action: "policy_install",
3005
3060
  title: `MCP installed pack: ${pack.name}`,
3006
- description: `Installed ${pack.controls.length} controls from ${pack.id} pack.${addedToConfig ? ` Added ${pack.id.toUpperCase()} to config frameworks.` : ""}`,
3007
- details: { packs_affected: [pack.id], frameworks_added: addedToConfig ? [pack.id.toUpperCase()] : [] },
3061
+ description: `Installed ${pack.controls.length} controls from ${pack.id} pack.${frameworksAdded.length > 0 ? ` Added ${frameworksAdded.join(", ")} to config frameworks.` : ""}`,
3062
+ details: { packs_affected: [pack.id], frameworks_added: frameworksAdded },
3008
3063
  });
3009
3064
  break;
3010
3065
  }
@@ -3021,7 +3076,16 @@ export function handleRequest(request) {
3021
3076
  break;
3022
3077
  }
3023
3078
  fs.rmSync(packDir, { recursive: true, force: true });
3024
- removeFrameworkFromConfig(projectPath, packId.toUpperCase());
3079
+ const allPacks = getAllPacks();
3080
+ const removedPack = allPacks.find(p => p.id === packId);
3081
+ if (removedPack) {
3082
+ for (const fw of removedPack.frameworks) {
3083
+ removeFrameworkFromConfig(projectPath, fw);
3084
+ }
3085
+ }
3086
+ else {
3087
+ removeFrameworkFromConfig(projectPath, packId.toUpperCase());
3088
+ }
3025
3089
  resultText = `✅ Removed policy pack: **${packId}** from ${projectPath}`;
3026
3090
  recordActivity(projectPath, {
3027
3091
  source: "mcp",
@@ -3137,6 +3201,47 @@ export function handleRequest(request) {
3137
3201
  }
3138
3202
  break;
3139
3203
  }
3204
+ case "record_recommendation": {
3205
+ const projectPath = resolveProjectPath(args.project_path);
3206
+ const category = (args.category || "improvement");
3207
+ const title = args.title || "";
3208
+ const description = args.description || "";
3209
+ const severity = (args.severity || "info");
3210
+ const suggestedAction = args.suggested_action || "";
3211
+ const affectedControls = args.affected_controls ? args.affected_controls.split(",").map((s) => s.trim()).filter(Boolean) : [];
3212
+ const affectedFiles = args.affected_files ? args.affected_files.split(",").map((s) => s.trim()).filter(Boolean) : [];
3213
+ if (!title || !description || !suggestedAction) {
3214
+ resultText = "Error: title, description, and suggested_action are all required.";
3215
+ break;
3216
+ }
3217
+ if (!fs.existsSync(projectPath)) {
3218
+ resultText = `Project path does not exist: ${projectPath}`;
3219
+ break;
3220
+ }
3221
+ const rec = recordAIRecommendation(projectPath, {
3222
+ category,
3223
+ title,
3224
+ description,
3225
+ severity,
3226
+ affected_controls: affectedControls,
3227
+ affected_files: affectedFiles,
3228
+ suggested_action: suggestedAction,
3229
+ });
3230
+ resultText = [
3231
+ `# Recommendation Recorded\n`,
3232
+ `**ID**: ${rec.id}`,
3233
+ `**Category**: ${rec.category}`,
3234
+ `**Severity**: ${rec.severity}`,
3235
+ `**Title**: ${rec.title}`,
3236
+ ``,
3237
+ `**Written to**: \`.dev-logs/ai-recommendations/\``,
3238
+ ``,
3239
+ `This recommendation has been logged for the development team to review.`,
3240
+ `It will NOT be automatically applied to the project.`,
3241
+ `Recommendations are gitignored and intended for developers only.`,
3242
+ ].join("\n");
3243
+ break;
3244
+ }
3140
3245
  default:
3141
3246
  return {
3142
3247
  jsonrpc: "2.0",
package/package.json CHANGED
@@ -3,17 +3,17 @@
3
3
  "ges-mcp": "dist/server.js"
4
4
  },
5
5
  "dependencies": {
6
- "@greenarmor/ges-audit-engine": "1.2.5",
7
- "@greenarmor/ges-cicd-generator": "1.2.5",
8
- "@greenarmor/ges-compliance-engine": "1.2.5",
9
- "@greenarmor/ges-core": "1.2.5",
10
- "@greenarmor/ges-doc-generator": "1.2.5",
11
- "@greenarmor/ges-policy-engine": "1.2.5",
12
- "@greenarmor/ges-report-generator": "1.2.5",
13
- "@greenarmor/ges-rules-engine": "1.2.5",
14
- "@greenarmor/ges-scanner-integration": "1.2.5",
15
- "@greenarmor/ges-scoring-engine": "1.2.5",
16
- "@greenarmor/ges-web-dashboard": "1.2.5"
6
+ "@greenarmor/ges-audit-engine": "1.2.6",
7
+ "@greenarmor/ges-cicd-generator": "1.2.6",
8
+ "@greenarmor/ges-compliance-engine": "1.2.6",
9
+ "@greenarmor/ges-core": "1.2.6",
10
+ "@greenarmor/ges-doc-generator": "1.2.6",
11
+ "@greenarmor/ges-policy-engine": "1.2.6",
12
+ "@greenarmor/ges-report-generator": "1.2.6",
13
+ "@greenarmor/ges-rules-engine": "1.2.6",
14
+ "@greenarmor/ges-scanner-integration": "1.2.6",
15
+ "@greenarmor/ges-scoring-engine": "1.2.6",
16
+ "@greenarmor/ges-web-dashboard": "1.2.6"
17
17
  },
18
18
  "description": "GESF MCP Server - AI Compliance Assistant for GDPR, OWASP, NIST, CIS. Check compliance, generate policies, assess risks via MCP protocol.",
19
19
  "devDependencies": {
@@ -67,7 +67,7 @@
67
67
  },
68
68
  "type": "module",
69
69
  "types": "./dist/index.d.ts",
70
- "version": "1.2.5",
70
+ "version": "1.2.6",
71
71
  "scripts": {
72
72
  "build": "tsc",
73
73
  "clean": "rm -rf dist bundle tsconfig.tsbuildinfo",