@greenarmor/ges-mcp-server 1.1.1 → 1.1.2

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025–2026 greenarmor
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/dist/server.js CHANGED
@@ -2,10 +2,14 @@
2
2
  import * as readline from "node:readline";
3
3
  import * as fs from "node:fs";
4
4
  import * as path from "node:path";
5
- import { getAllPacks, getPacksForProjectType, getPack } from "@greenarmor/ges-policy-engine";
6
- import { generateScoreFile, formatScoreOutput } from "@greenarmor/ges-scoring-engine";
5
+ import { getAllPacks, getPacksForProjectType, getPack, listPackIds } from "@greenarmor/ges-policy-engine";
6
+ import { generateScoreFile, formatScoreOutput, computeGrade, generateBadgeSvg, injectBadgeIntoReadme, generateScoreExplainer } from "@greenarmor/ges-scoring-engine";
7
7
  import { runAudit, deduplicateFindings } from "@greenarmor/ges-audit-engine";
8
- import { GESF_VERSION } from "@greenarmor/ges-core";
8
+ 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";
9
+ import { ProjectConfigSchema } from "@greenarmor/ges-core";
10
+ import { generateComplianceDocs, generateSecurityDocs, generateConfigJson, generateMetadataJson, generateFrameworkVersionJson, generateScoreJson } from "@greenarmor/ges-doc-generator";
11
+ import { generateAllWorkflows } from "@greenarmor/ges-cicd-generator";
12
+ import { detectProject, runAllScansWithSbom, formatScanResults, formatSbomResults } from "@greenarmor/ges-scanner-integration";
9
13
  const TOOLS = [
10
14
  {
11
15
  name: "check_compliance",
@@ -203,6 +207,137 @@ const TOOLS = [
203
207
  },
204
208
  },
205
209
  },
210
+ {
211
+ name: "generate_badge",
212
+ description: "Generate an SVG compliance score badge for a project's README. Reads the project's .ges/score.json and produces a shields.io-style SVG badge with the compliance score and grade. Optionally injects into README.md.",
213
+ inputSchema: {
214
+ type: "object",
215
+ properties: {
216
+ project_path: { type: "string", description: "Absolute path to the project root." },
217
+ output: { type: "string", description: "Output filename for the SVG badge (default: badge.svg)." },
218
+ readme: { type: "string", description: "Path to README file to inject badge into (default: README.md). Set to empty string to skip injection." },
219
+ },
220
+ },
221
+ },
222
+ {
223
+ name: "get_score",
224
+ description: "Read and display the compliance score from a project's .ges/score.json. Shows per-framework scores, grades, and overall compliance percentage.",
225
+ inputSchema: {
226
+ type: "object",
227
+ properties: {
228
+ project_path: { type: "string", description: "Absolute path to the project root." },
229
+ },
230
+ },
231
+ },
232
+ {
233
+ name: "init_project",
234
+ description: "Initialize GESF in a project directory. Creates the .ges/ directory structure, compliance/security documentation, controls, CI/CD workflows, and configuration files.",
235
+ inputSchema: {
236
+ type: "object",
237
+ properties: {
238
+ project_path: { type: "string", description: "Absolute path to the project root." },
239
+ project_name: { type: "string", description: "Project name (defaults to directory name)." },
240
+ project_type: { type: "string", description: "Project type (saas, ai-application, mcp-server, blockchain, wallet, government-system, healthcare-system, event-platform, photo-storage-platform, vulnerability-scanner, generic-web-application, api-backend, mobile-application)." },
241
+ frameworks: { type: "string", description: "Comma-separated framework names (default: GDPR,OWASP,CIS,NIST)." },
242
+ force: { type: "boolean", description: "Re-initialize even if GESF is already set up (default: false)." },
243
+ },
244
+ required: ["project_path"],
245
+ },
246
+ },
247
+ {
248
+ name: "run_scans",
249
+ description: "Run security scanner integrations on a project. Detects the ecosystem (Node.js, Python, etc.) and runs available scanners (npm audit, Trivy, Gitleaks, Semgrep, etc.) plus SBOM generation.",
250
+ inputSchema: {
251
+ type: "object",
252
+ properties: {
253
+ project_path: { type: "string", description: "Absolute path to the project root." },
254
+ },
255
+ },
256
+ },
257
+ {
258
+ name: "doctor",
259
+ description: "Diagnose GESF project health. Checks if the project is initialized, config files exist, score is available, required directories are present, and GitHub Actions are configured.",
260
+ inputSchema: {
261
+ type: "object",
262
+ properties: {
263
+ project_path: { type: "string", description: "Absolute path to the project root." },
264
+ },
265
+ },
266
+ },
267
+ {
268
+ name: "validate_project",
269
+ description: "Validate GESF project configuration and controls. Checks config.json against the schema, validates control files, and verifies required directories exist.",
270
+ inputSchema: {
271
+ type: "object",
272
+ properties: {
273
+ project_path: { type: "string", description: "Absolute path to the project root." },
274
+ },
275
+ },
276
+ },
277
+ {
278
+ name: "policy_list",
279
+ description: "List all available policy packs with their IDs, names, control counts, and supported project types.",
280
+ inputSchema: {
281
+ type: "object",
282
+ properties: {},
283
+ },
284
+ },
285
+ {
286
+ name: "policy_install",
287
+ description: "Install a policy pack into a project. Writes the pack's controls as a JSON file in the controls/ directory.",
288
+ inputSchema: {
289
+ type: "object",
290
+ properties: {
291
+ project_path: { type: "string", description: "Absolute path to the project root." },
292
+ pack_id: { type: "string", description: "Policy pack ID to install (e.g. gdpr, owasp, cis, nist, ai, blockchain, government). Use policy_list to see available packs." },
293
+ },
294
+ required: ["project_path", "pack_id"],
295
+ },
296
+ },
297
+ {
298
+ name: "policy_remove",
299
+ description: "Remove an installed policy pack from a project. Deletes the pack's directory from controls/.",
300
+ inputSchema: {
301
+ type: "object",
302
+ properties: {
303
+ project_path: { type: "string", description: "Absolute path to the project root." },
304
+ pack_id: { type: "string", description: "Policy pack ID to remove." },
305
+ },
306
+ required: ["project_path", "pack_id"],
307
+ },
308
+ },
309
+ {
310
+ name: "update_check",
311
+ description: "Check the current GESF version and get update instructions.",
312
+ inputSchema: {
313
+ type: "object",
314
+ properties: {},
315
+ },
316
+ },
317
+ {
318
+ name: "install_hooks",
319
+ description: "Install GESF git hooks (pre-commit) that run compliance checks before allowing commits. Also supports uninstalling hooks.",
320
+ inputSchema: {
321
+ type: "object",
322
+ properties: {
323
+ project_path: { type: "string", description: "Absolute path to the project root." },
324
+ action: { type: "string", description: "Action to perform: 'install' or 'uninstall' (default: install)." },
325
+ },
326
+ required: ["project_path"],
327
+ },
328
+ },
329
+ {
330
+ name: "start_dashboard",
331
+ description: "Get instructions and configuration for starting the GESF compliance web dashboard. The dashboard provides a browser-based view of compliance status, scores, and findings. Note: the actual server must be started via the CLI 'ges dashboard' command.",
332
+ inputSchema: {
333
+ type: "object",
334
+ properties: {
335
+ project_path: { type: "string", description: "Absolute path to the project root." },
336
+ port: { type: "number", description: "Port number for the dashboard (default: 3001)." },
337
+ host: { type: "string", description: "Host to bind to (default: localhost)." },
338
+ },
339
+ },
340
+ },
206
341
  ];
207
342
  function send(message) {
208
343
  process.stdout.write(JSON.stringify(message) + "\n");
@@ -2349,6 +2484,463 @@ export function handleRequest(request) {
2349
2484
  resultText = lines.join("\n");
2350
2485
  break;
2351
2486
  }
2487
+ case "generate_badge": {
2488
+ const projectPath = resolveProjectPath(args.project_path);
2489
+ if (!fs.existsSync(path.join(projectPath, ".ges"))) {
2490
+ resultText = `GESF not initialized at ${projectPath}. Run 'ges init' first.`;
2491
+ break;
2492
+ }
2493
+ const score = readJsonFileSafe(path.join(projectPath, ".ges", "score.json"));
2494
+ if (!score || !score.frameworks || Object.keys(score.frameworks).length === 0) {
2495
+ resultText = `No compliance score available at ${projectPath}. Run 'ges audit' then 'ges score' first.`;
2496
+ break;
2497
+ }
2498
+ const svg = generateBadgeSvg(score);
2499
+ const outputName = args.output || "badge.svg";
2500
+ const outputPath = path.resolve(projectPath, outputName);
2501
+ fs.mkdirSync(path.dirname(outputPath), { recursive: true });
2502
+ fs.writeFileSync(outputPath, svg);
2503
+ const explainer = generateScoreExplainer(score);
2504
+ const lines = [];
2505
+ lines.push(`# Compliance Badge Generated\n`);
2506
+ lines.push(`**File**: ${outputPath}`);
2507
+ lines.push(`**Score**: ${score.overall}% (${score.overall_grade ?? computeGrade(score.overall)})\n`);
2508
+ if (args.readme !== "") {
2509
+ const readmeName = args.readme || "README.md";
2510
+ const readmePath = path.resolve(projectPath, readmeName);
2511
+ if (fs.existsSync(readmePath)) {
2512
+ const readmeContent = fs.readFileSync(readmePath, "utf-8");
2513
+ const relativeBadgePath = path.relative(path.dirname(readmePath), outputPath);
2514
+ const updated = injectBadgeIntoReadme(readmeContent, relativeBadgePath, explainer);
2515
+ fs.writeFileSync(readmePath, updated);
2516
+ lines.push(`Badge injected into ${readmeName}`);
2517
+ }
2518
+ else {
2519
+ lines.push(`${readmeName} not found — badge SVG saved but not injected into README.`);
2520
+ lines.push(`Manually add: ![GESF Compliance](${outputName})`);
2521
+ }
2522
+ }
2523
+ else {
2524
+ lines.push(`Badge SVG saved to ${outputName}. Add to README manually if desired.`);
2525
+ }
2526
+ lines.push(`\n### Badge Preview\n`);
2527
+ lines.push(`![compliance ${score.overall}% ${score.overall_grade ?? computeGrade(score.overall)}]`);
2528
+ lines.push(`\n### Per-Framework Scores\n`);
2529
+ for (const [fw, data] of Object.entries(score.frameworks)) {
2530
+ lines.push(`- ${fw}: ${data.score}% (${data.grade})`);
2531
+ }
2532
+ resultText = lines.join("\n");
2533
+ break;
2534
+ }
2535
+ case "get_score": {
2536
+ const projectPath = resolveProjectPath(args.project_path);
2537
+ const score = readJsonFileSafe(path.join(projectPath, ".ges", "score.json"));
2538
+ if (!score || !score.frameworks || Object.keys(score.frameworks).length === 0) {
2539
+ resultText = `No compliance score available at ${projectPath}. Run 'ges audit' then 'ges score' first.`;
2540
+ break;
2541
+ }
2542
+ resultText = formatScoreOutput(score) + `\nLast evaluated: ${score.evaluated_at}`;
2543
+ break;
2544
+ }
2545
+ case "init_project": {
2546
+ const projectPath = resolveProjectPath(args.project_path);
2547
+ if (!fs.existsSync(projectPath)) {
2548
+ resultText = `Project path does not exist: ${projectPath}`;
2549
+ break;
2550
+ }
2551
+ const gesDir = path.join(projectPath, GES_DIR);
2552
+ if (fs.existsSync(gesDir) && args.force !== "true") {
2553
+ resultText = `GESF is already initialized at ${projectPath}. Use force: true to re-initialize.`;
2554
+ break;
2555
+ }
2556
+ if (fs.existsSync(gesDir)) {
2557
+ fs.rmSync(gesDir, { recursive: true, force: true });
2558
+ }
2559
+ const projectName = args.project_name || path.basename(projectPath);
2560
+ const projectType = (args.project_type || "generic-web-application");
2561
+ const frameworksStr = args.frameworks || DEFAULT_FRAMEWORKS.join(",");
2562
+ const frameworks = frameworksStr.split(",").map(f => f.trim());
2563
+ const now = new Date().toISOString();
2564
+ const config = {
2565
+ project_name: projectName,
2566
+ project_type: projectType,
2567
+ frameworks,
2568
+ requirements: {
2569
+ encryption: { required: true },
2570
+ mfa: { required: true },
2571
+ audit_logs: { required: true },
2572
+ backups: { required: true },
2573
+ retention_policy: { required: true },
2574
+ vulnerability_scanning: { required: true },
2575
+ authentication: { required: true },
2576
+ authorization: { required: true },
2577
+ secrets_management: { required: true },
2578
+ logging: { required: true },
2579
+ monitoring: { required: true },
2580
+ data_classification: { required: true },
2581
+ disaster_recovery: { required: true },
2582
+ incident_response: { required: true },
2583
+ privacy_controls: { required: true },
2584
+ },
2585
+ created_at: now,
2586
+ version: GESF_VERSION,
2587
+ };
2588
+ fs.mkdirSync(gesDir, { recursive: true });
2589
+ const configJson = generateConfigJson(config);
2590
+ fs.writeFileSync(path.join(gesDir, "config.json"), configJson.content);
2591
+ const metadataJson = generateMetadataJson(config);
2592
+ fs.writeFileSync(path.join(gesDir, "metadata.json"), metadataJson.content);
2593
+ const frameworkVersionJson = generateFrameworkVersionJson();
2594
+ fs.writeFileSync(path.join(gesDir, "framework-version.json"), frameworkVersionJson.content);
2595
+ const scoreJson = generateScoreJson();
2596
+ fs.writeFileSync(path.join(gesDir, "score.json"), scoreJson.content);
2597
+ const dirs = [COMPLIANCE_DIR, SECURITY_DIR, CONTROLS_DIR, POLICIES_DIR, CHECKLISTS_DIR, DOCS_DIR, REPORTS_DIR];
2598
+ for (const dir of dirs) {
2599
+ fs.mkdirSync(path.join(projectPath, dir), { recursive: true });
2600
+ }
2601
+ const complianceDocs = generateComplianceDocs(projectName, projectType);
2602
+ for (const doc of complianceDocs) {
2603
+ const filePath = path.join(projectPath, COMPLIANCE_DIR, doc.filePath);
2604
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
2605
+ fs.writeFileSync(filePath, doc.content);
2606
+ }
2607
+ const securityDocs = generateSecurityDocs(projectName, projectType);
2608
+ for (const doc of securityDocs) {
2609
+ const filePath = path.join(projectPath, SECURITY_DIR, doc.filePath);
2610
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
2611
+ fs.writeFileSync(filePath, doc.content);
2612
+ }
2613
+ const packs = getPacksForProjectType(projectType);
2614
+ for (const pack of packs) {
2615
+ const packDir = path.join(projectPath, CONTROLS_DIR, pack.id);
2616
+ fs.mkdirSync(packDir, { recursive: true });
2617
+ fs.writeFileSync(path.join(packDir, "controls.json"), JSON.stringify(pack.controls, null, 2));
2618
+ }
2619
+ const workflows = generateAllWorkflows(config);
2620
+ const workflowsDir = path.join(projectPath, ".github", "workflows");
2621
+ fs.mkdirSync(workflowsDir, { recursive: true });
2622
+ for (const wf of workflows) {
2623
+ fs.writeFileSync(path.join(workflowsDir, wf.filePath.replace(/^\.github\/workflows\//, "")), wf.content);
2624
+ }
2625
+ const lines = [];
2626
+ lines.push(`# GESF Project Initialized\n`);
2627
+ lines.push(`**Project**: ${projectName}`);
2628
+ lines.push(`**Type**: ${projectType}`);
2629
+ lines.push(`**Frameworks**: ${frameworks.join(", ")}`);
2630
+ lines.push(`**Path**: ${projectPath}\n`);
2631
+ lines.push(`## Created Structure`);
2632
+ lines.push(`- \`.ges/\` — Configuration and score files`);
2633
+ lines.push(`- \`compliance/\` — ${complianceDocs.length} compliance documents`);
2634
+ lines.push(`- \`security/\` — ${securityDocs.length} security documents`);
2635
+ lines.push(`- \`controls/\` — ${packs.length} control packs`);
2636
+ lines.push(`- \`.github/workflows/\` — ${workflows.length} CI/CD workflows`);
2637
+ lines.push(`- \`policies/\`, \`checklists/\`, \`docs/\`, \`reports/\`\n`);
2638
+ lines.push(`## Next Steps`);
2639
+ lines.push(`1. Run \`ges audit\` to scan the project for security issues`);
2640
+ lines.push(`2. Run \`ges score\` to calculate compliance score`);
2641
+ lines.push(`3. Run \`ges badge\` to generate a compliance badge for README`);
2642
+ lines.push(`4. Review and customize documents in compliance/ and security/`);
2643
+ resultText = lines.join("\n");
2644
+ break;
2645
+ }
2646
+ case "run_scans": {
2647
+ const projectPath = resolveProjectPath(args.project_path);
2648
+ if (!fs.existsSync(projectPath)) {
2649
+ resultText = `Project path does not exist: ${projectPath}`;
2650
+ break;
2651
+ }
2652
+ if (!fs.existsSync(path.join(projectPath, ".ges"))) {
2653
+ resultText = `GESF not initialized at ${projectPath}. Run 'ges init' first.`;
2654
+ break;
2655
+ }
2656
+ const detection = detectProject(projectPath);
2657
+ const ecosystemDetail = detection.ecosystem === "node" && detection.nodePackageManager
2658
+ ? `node (${detection.nodePackageManager})`
2659
+ : detection.ecosystem === "python" && detection.pythonToolchain
2660
+ ? `python (${detection.pythonToolchain})`
2661
+ : detection.ecosystem;
2662
+ const results = runAllScansWithSbom(detection);
2663
+ const lines = [];
2664
+ lines.push(`# Security Scan Results\n`);
2665
+ lines.push(`**Project**: ${projectPath}`);
2666
+ lines.push(`**Ecosystem**: ${ecosystemDetail}\n`);
2667
+ lines.push(formatScanResults(results));
2668
+ lines.push(formatSbomResults(results));
2669
+ const failed = results.filter(r => r.status === "fail");
2670
+ if (failed.length > 0) {
2671
+ lines.push(`\n**${failed.length} scanner(s) reported failures.** Review findings above.`);
2672
+ }
2673
+ resultText = lines.join("\n");
2674
+ break;
2675
+ }
2676
+ case "doctor": {
2677
+ const projectPath = resolveProjectPath(args.project_path);
2678
+ const checks = [];
2679
+ const gesDir = path.join(projectPath, GES_DIR);
2680
+ if (fs.existsSync(gesDir)) {
2681
+ checks.push({ name: "GESF initialized", status: "OK", detail: projectPath });
2682
+ }
2683
+ else {
2684
+ checks.push({ name: "GESF initialized", status: "FAIL", detail: "Run 'init_project' first" });
2685
+ }
2686
+ if (fs.existsSync(gesDir)) {
2687
+ const configPath = path.join(gesDir, "config.json");
2688
+ checks.push({
2689
+ name: "Config file",
2690
+ status: fs.existsSync(configPath) ? "OK" : "WARN",
2691
+ detail: fs.existsSync(configPath) ? configPath : "config.json not found",
2692
+ });
2693
+ const score = readJsonFileSafe(path.join(gesDir, "score.json"));
2694
+ checks.push({
2695
+ name: "Score file",
2696
+ status: score ? "OK" : "WARN",
2697
+ detail: score ? `Overall: ${score.overall}%` : "Run audit then score",
2698
+ });
2699
+ const config = readJsonFileSafe(configPath);
2700
+ if (config) {
2701
+ checks.push({ name: "Project", status: "OK", detail: `${config.project_name} (${config.project_type})` });
2702
+ checks.push({ name: "Frameworks", status: "OK", detail: config.frameworks.join(", ") });
2703
+ }
2704
+ const dirs = [COMPLIANCE_DIR, SECURITY_DIR, CONTROLS_DIR, POLICIES_DIR, CHECKLISTS_DIR, DOCS_DIR, REPORTS_DIR];
2705
+ for (const dir of dirs) {
2706
+ const exists = fs.existsSync(path.join(projectPath, dir));
2707
+ checks.push({ name: `${dir}/ directory`, status: exists ? "OK" : "MISSING" });
2708
+ }
2709
+ const ghWorkflows = path.join(projectPath, ".github", "workflows");
2710
+ if (fs.existsSync(ghWorkflows)) {
2711
+ const workflows = fs.readdirSync(ghWorkflows).filter(f => f.endsWith(".yml"));
2712
+ checks.push({ name: "GitHub Actions", status: "OK", detail: `${workflows.length} workflow(s)` });
2713
+ }
2714
+ else {
2715
+ checks.push({ name: "GitHub Actions", status: "WARN", detail: "No .github/workflows found" });
2716
+ }
2717
+ }
2718
+ checks.push({ name: "GESF Version", status: "OK", detail: GESF_VERSION });
2719
+ const lines = [];
2720
+ lines.push(`# GESF Doctor - Diagnostic Report\n`);
2721
+ lines.push(`**Project**: ${projectPath}\n`);
2722
+ const ok = checks.filter(c => c.status === "OK").length;
2723
+ const warns = checks.filter(c => c.status === "WARN").length;
2724
+ const fails = checks.filter(c => c.status === "FAIL" || c.status === "MISSING").length;
2725
+ lines.push(`**Summary**: ${ok} OK, ${warns} warnings, ${fails} issues\n`);
2726
+ lines.push("| Check | Status | Detail |");
2727
+ lines.push("|-------|--------|--------|");
2728
+ for (const check of checks) {
2729
+ lines.push(`| ${check.name} | ${check.status} | ${check.detail || "—"} |`);
2730
+ }
2731
+ if (fails > 0) {
2732
+ lines.push(`\n**Action Required**: Fix the issues marked as FAIL or MISSING above.`);
2733
+ }
2734
+ else if (warns > 0) {
2735
+ lines.push(`\n**Note**: Some warnings detected. Review the items above.`);
2736
+ }
2737
+ else {
2738
+ lines.push(`\n**All checks passed.** Project is healthy.`);
2739
+ }
2740
+ resultText = lines.join("\n");
2741
+ break;
2742
+ }
2743
+ case "validate_project": {
2744
+ const projectPath = resolveProjectPath(args.project_path);
2745
+ const lines = [];
2746
+ let hasErrors = false;
2747
+ lines.push(`# GESF Validation Report\n`);
2748
+ lines.push(`**Project**: ${projectPath}\n`);
2749
+ const configPath = path.join(projectPath, GES_DIR, "config.json");
2750
+ const config = readJsonFileSafe(configPath);
2751
+ if (!config) {
2752
+ lines.push("❌ **config.json** not found or invalid");
2753
+ hasErrors = true;
2754
+ }
2755
+ else {
2756
+ const result = ProjectConfigSchema.safeParse(config);
2757
+ if (result.success) {
2758
+ lines.push("✅ **Configuration** is valid");
2759
+ }
2760
+ else {
2761
+ lines.push("❌ **Configuration** validation errors:");
2762
+ for (const error of result.error.errors) {
2763
+ lines.push(` - ${error.path.join(".")}: ${error.message}`);
2764
+ }
2765
+ hasErrors = true;
2766
+ }
2767
+ }
2768
+ const controlsDir = path.join(projectPath, CONTROLS_DIR);
2769
+ if (fs.existsSync(controlsDir)) {
2770
+ const packDirs = fs.readdirSync(controlsDir);
2771
+ for (const packDir of packDirs) {
2772
+ const controlsFile = path.join(controlsDir, packDir, "controls.json");
2773
+ if (fs.existsSync(controlsFile)) {
2774
+ const raw = readJsonFileSafe(controlsFile);
2775
+ const controls = Array.isArray(raw) ? raw : Array.isArray(raw?.controls) ? raw.controls : null;
2776
+ if (controls && Array.isArray(controls)) {
2777
+ lines.push(`✅ **${packDir}**: ${controls.length} controls`);
2778
+ }
2779
+ else {
2780
+ lines.push(`❌ **${packDir}**: Invalid controls.json`);
2781
+ hasErrors = true;
2782
+ }
2783
+ }
2784
+ }
2785
+ }
2786
+ else {
2787
+ lines.push(`⚠️ No controls/ directory found`);
2788
+ }
2789
+ const requiredDirs = [COMPLIANCE_DIR, SECURITY_DIR, CONTROLS_DIR];
2790
+ for (const dir of requiredDirs) {
2791
+ if (fs.existsSync(path.join(projectPath, dir))) {
2792
+ lines.push(`✅ **${dir}/** directory exists`);
2793
+ }
2794
+ else {
2795
+ lines.push(`❌ **${dir}/** directory missing`);
2796
+ hasErrors = true;
2797
+ }
2798
+ }
2799
+ const score = readJsonFileSafe(path.join(projectPath, GES_DIR, "score.json"));
2800
+ if (score) {
2801
+ lines.push(`✅ **Score file** exists (${score.overall}%)`);
2802
+ }
2803
+ else {
2804
+ lines.push(`⚠️ **Score file** not found — run audit then score`);
2805
+ }
2806
+ lines.push(hasErrors ? "\n❌ **Validation failed.** Fix the issues above." : "\n✅ **All validations passed.**");
2807
+ resultText = lines.join("\n");
2808
+ break;
2809
+ }
2810
+ case "policy_list": {
2811
+ const packs = getAllPacks();
2812
+ const lines = [];
2813
+ lines.push(`# Available Policy Packs (${packs.length} total)\n`);
2814
+ lines.push("| ID | Name | Controls | Project Types |");
2815
+ lines.push("|----|------|----------|---------------|");
2816
+ for (const pack of packs) {
2817
+ lines.push(`| ${pack.id} | ${pack.name} | ${pack.controls.length} | ${pack.project_types.join(", ")} |`);
2818
+ }
2819
+ lines.push(`\nUse \`policy_install\` with a pack_id to install a pack into your project.`);
2820
+ resultText = lines.join("\n");
2821
+ break;
2822
+ }
2823
+ case "policy_install": {
2824
+ const projectPath = resolveProjectPath(args.project_path);
2825
+ const packId = args.pack_id || "";
2826
+ if (!fs.existsSync(path.join(projectPath, ".ges"))) {
2827
+ resultText = `GESF not initialized at ${projectPath}. Run 'init_project' first.`;
2828
+ break;
2829
+ }
2830
+ if (!packId) {
2831
+ resultText = `Error: pack_id is required. Available packs: ${listPackIds().join(", ")}`;
2832
+ break;
2833
+ }
2834
+ const packs = getAllPacks();
2835
+ const pack = packs.find(p => p.id === packId);
2836
+ if (!pack) {
2837
+ resultText = `Error: Pack '${packId}' not found. Available: ${listPackIds().join(", ")}`;
2838
+ break;
2839
+ }
2840
+ const packDir = path.join(projectPath, CONTROLS_DIR, pack.id);
2841
+ fs.mkdirSync(packDir, { recursive: true });
2842
+ fs.writeFileSync(path.join(packDir, "controls.json"), JSON.stringify(pack.controls, null, 2));
2843
+ resultText = `✅ Installed policy pack: **${pack.id}** (${pack.name})\n${pack.controls.length} controls written to ${CONTROLS_DIR}/${pack.id}/controls.json`;
2844
+ break;
2845
+ }
2846
+ case "policy_remove": {
2847
+ const projectPath = resolveProjectPath(args.project_path);
2848
+ const packId = args.pack_id || "";
2849
+ if (!packId) {
2850
+ resultText = `Error: pack_id is required.`;
2851
+ break;
2852
+ }
2853
+ const packDir = path.join(projectPath, CONTROLS_DIR, packId);
2854
+ if (!fs.existsSync(packDir)) {
2855
+ resultText = `Error: Pack '${packId}' is not installed at ${projectPath}.`;
2856
+ break;
2857
+ }
2858
+ fs.rmSync(packDir, { recursive: true, force: true });
2859
+ resultText = `✅ Removed policy pack: **${packId}** from ${projectPath}`;
2860
+ break;
2861
+ }
2862
+ case "update_check": {
2863
+ resultText = `# GESF Update Check\n\n**Current Version**: ${GESF_VERSION}\n\nTo update:\n\`\`\`bash\nnpm update -g @greenarmor/ges\n# or\npnpm update -g @greenarmor/ges\n\`\`\`\nFor project-local installs:\n\`\`\`bash\nnpm update @greenarmor/ges\n# or\npnpm update @greenarmor/ges\n\`\`\``;
2864
+ break;
2865
+ }
2866
+ case "install_hooks": {
2867
+ const projectPath = resolveProjectPath(args.project_path);
2868
+ const action = args.action || "install";
2869
+ if (!fs.existsSync(path.join(projectPath, ".ges"))) {
2870
+ resultText = `GESF not initialized at ${projectPath}. Run 'init_project' first.`;
2871
+ break;
2872
+ }
2873
+ const gitDir = path.join(projectPath, ".git");
2874
+ if (!fs.existsSync(gitDir)) {
2875
+ resultText = `No Git repository found at ${projectPath}. Git hooks require a .git directory. Run 'git init' first.`;
2876
+ break;
2877
+ }
2878
+ const hooksDir = path.join(gitDir, "hooks");
2879
+ const hookPath = path.join(hooksDir, "pre-commit");
2880
+ if (action === "uninstall") {
2881
+ if (fs.existsSync(hookPath)) {
2882
+ const content = fs.readFileSync(hookPath, "utf-8");
2883
+ if (content.includes("ges audit")) {
2884
+ fs.unlinkSync(hookPath);
2885
+ resultText = `✅ Uninstalled pre-commit hook from ${hookPath}`;
2886
+ }
2887
+ else {
2888
+ resultText = `Pre-commit hook exists but was not installed by GESF. Not removing it.`;
2889
+ }
2890
+ }
2891
+ else {
2892
+ resultText = `No pre-commit hook found at ${hookPath}. Nothing to uninstall.`;
2893
+ }
2894
+ break;
2895
+ }
2896
+ const hookContent = `#!/bin/sh\n# GESF pre-commit hook - runs compliance audit before allowing commits\nnpx ges audit --ci\nif [ $? -ne 0 ]; then\n echo "GESF audit failed. Fix issues or use --no-verify to bypass."\n exit 1\nfi\n`;
2897
+ fs.mkdirSync(hooksDir, { recursive: true });
2898
+ if (fs.existsSync(hookPath)) {
2899
+ const existing = fs.readFileSync(hookPath, "utf-8");
2900
+ if (existing.includes("ges audit")) {
2901
+ resultText = `Pre-commit hook already installed at ${hookPath}`;
2902
+ break;
2903
+ }
2904
+ resultText = `A pre-commit hook already exists at ${hookPath}. Not overwriting.\nManually add 'npx ges audit --ci' to your pre-commit hook.`;
2905
+ break;
2906
+ }
2907
+ fs.writeFileSync(hookPath, hookContent);
2908
+ fs.chmodSync(hookPath, 0o755);
2909
+ resultText = `✅ Installed pre-commit hook at ${hookPath}\n\nThe hook will run 'ges audit --ci' before allowing commits.\n- To bypass: \`git commit --no-verify\`\n- To remove: use \`install_hooks\` with action: "uninstall"`;
2910
+ break;
2911
+ }
2912
+ case "start_dashboard": {
2913
+ const projectPath = resolveProjectPath(args.project_path);
2914
+ const port = args.port || 3001;
2915
+ const host = args.host || "localhost";
2916
+ if (!fs.existsSync(path.join(projectPath, ".ges"))) {
2917
+ resultText = `GESF not initialized at ${projectPath}. Run 'init_project' first.`;
2918
+ break;
2919
+ }
2920
+ const lines = [];
2921
+ lines.push(`# GESF Web Dashboard\n`);
2922
+ lines.push(`**Project**: ${projectPath}`);
2923
+ lines.push(`**Host**: ${host}`);
2924
+ lines.push(`**Port**: ${port}\n`);
2925
+ lines.push(`## Starting the Dashboard\n`);
2926
+ lines.push(`The dashboard must be started via the GESF CLI. Run:\n`);
2927
+ lines.push(`\`\`\`bash`);
2928
+ lines.push(`cd ${projectPath}`);
2929
+ lines.push(`ges dashboard --port ${port} --host ${host}`);
2930
+ lines.push(`\`\`\`\n`);
2931
+ lines.push(`## Available Endpoints\n`);
2932
+ lines.push(`- **Dashboard UI**: http://${host}:${port}`);
2933
+ lines.push(`- **JSON API**: http://${host}:${port}/api/data`);
2934
+ lines.push(`- **Health Check**: http://${host}:${port}/health\n`);
2935
+ lines.push(`## Dashboard Features`);
2936
+ lines.push(`- Visual compliance score overview`);
2937
+ lines.push(`- Per-framework breakdown with grades`);
2938
+ lines.push(`- Security findings list`);
2939
+ lines.push(`- Control status matrix`);
2940
+ lines.push(`- Audit history timeline`);
2941
+ resultText = lines.join("\n");
2942
+ break;
2943
+ }
2352
2944
  default:
2353
2945
  return {
2354
2946
  jsonrpc: "2.0",
@@ -35,10 +35,10 @@ describe("MCP Protocol", () => {
35
35
  const res = handleRequest({ jsonrpc: "2.0", method: "ping" });
36
36
  expect(res).toBeNull();
37
37
  });
38
- it("responds to tools/list with 17 tools", () => {
38
+ it("responds to tools/list with 29 tools", () => {
39
39
  const res = handleRequest(req("tools/list"));
40
40
  const tools = res.result.tools;
41
- expect(tools.length).toBe(17);
41
+ expect(tools.length).toBe(29);
42
42
  });
43
43
  it("returns error for unknown method", () => {
44
44
  const res = handleRequest(req("unknown/method"));
@@ -67,6 +67,18 @@ describe("tools/list content", () => {
67
67
  expect(names).toContain("generate_dpa");
68
68
  expect(names).toContain("generate_data_inventory");
69
69
  expect(names).toContain("generate_processing_records");
70
+ expect(names).toContain("generate_badge");
71
+ expect(names).toContain("get_score");
72
+ expect(names).toContain("init_project");
73
+ expect(names).toContain("run_scans");
74
+ expect(names).toContain("doctor");
75
+ expect(names).toContain("validate_project");
76
+ expect(names).toContain("policy_list");
77
+ expect(names).toContain("policy_install");
78
+ expect(names).toContain("policy_remove");
79
+ expect(names).toContain("update_check");
80
+ expect(names).toContain("install_hooks");
81
+ expect(names).toContain("start_dashboard");
70
82
  });
71
83
  });
72
84
  describe("check_compliance tool", () => {
@@ -150,3 +162,43 @@ describe("unknown tool", () => {
150
162
  expect(res.error).toBeDefined();
151
163
  });
152
164
  });
165
+ describe("new tools", () => {
166
+ it("update_check returns version info", () => {
167
+ const res = handleRequest(callTool("update_check"));
168
+ const text = getResultText(res);
169
+ expect(text).toContain("1.1.1");
170
+ expect(text).toContain("npm update");
171
+ });
172
+ it("policy_list returns packs", () => {
173
+ const res = handleRequest(callTool("policy_list"));
174
+ const text = getResultText(res);
175
+ expect(text).toContain("gdpr");
176
+ expect(text).toContain("owasp");
177
+ });
178
+ it("doctor returns diagnostic for valid project", () => {
179
+ const res = handleRequest(callTool("doctor", { project_path: "/Users/tata/gesf" }));
180
+ const text = getResultText(res);
181
+ expect(text).toContain("GESF initialized");
182
+ expect(text).toContain("OK");
183
+ });
184
+ it("validate_project validates config", () => {
185
+ const res = handleRequest(callTool("validate_project", { project_path: "/Users/tata/gesf" }));
186
+ const text = getResultText(res);
187
+ expect(text).toContain("Configuration");
188
+ });
189
+ it("get_score returns score for valid project", () => {
190
+ const res = handleRequest(callTool("get_score", { project_path: "/Users/tata/gesf" }));
191
+ const text = getResultText(res);
192
+ expect(text).toContain("GDPR");
193
+ });
194
+ it("install_hooks returns error without git", () => {
195
+ const res = handleRequest(callTool("install_hooks", { project_path: "/tmp/nonexistent" }));
196
+ const text = getResultText(res);
197
+ expect(text.length).toBeGreaterThan(0);
198
+ });
199
+ it("start_dashboard returns instructions", () => {
200
+ const res = handleRequest(callTool("start_dashboard", { project_path: "/Users/tata/gesf" }));
201
+ const text = getResultText(res);
202
+ expect(text).toContain("dashboard");
203
+ });
204
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@greenarmor/ges-mcp-server",
3
- "version": "1.1.1",
3
+ "version": "1.1.2",
4
4
  "description": "GESF MCP Server - AI Compliance Assistant for GDPR, OWASP, NIST, CIS. Check compliance, generate policies, assess risks via MCP protocol.",
5
5
  "keywords": [
6
6
  "ai",
@@ -44,20 +44,16 @@
44
44
  "access": "public",
45
45
  "registry": "https://registry.npmjs.org/"
46
46
  },
47
- "scripts": {
48
- "build": "tsc",
49
- "clean": "rm -rf dist bundle tsconfig.tsbuildinfo",
50
- "prepublishOnly": "tsc",
51
- "test": "vitest run"
52
- },
53
47
  "dependencies": {
54
48
  "@greenarmor/ges-audit-engine": "1.1.1",
55
49
  "@greenarmor/ges-compliance-engine": "1.1.1",
56
50
  "@greenarmor/ges-core": "1.1.1",
51
+ "@greenarmor/ges-cicd-generator": "1.1.1",
57
52
  "@greenarmor/ges-doc-generator": "1.1.1",
58
53
  "@greenarmor/ges-policy-engine": "1.1.1",
59
54
  "@greenarmor/ges-report-generator": "1.1.1",
60
55
  "@greenarmor/ges-rules-engine": "1.1.1",
56
+ "@greenarmor/ges-scanner-integration": "1.1.1",
61
57
  "@greenarmor/ges-scoring-engine": "1.1.1"
62
58
  },
63
59
  "devDependencies": {
@@ -68,5 +64,10 @@
68
64
  },
69
65
  "engines": {
70
66
  "node": ">=20.0.0"
67
+ },
68
+ "scripts": {
69
+ "build": "tsc",
70
+ "clean": "rm -rf dist bundle tsconfig.tsbuildinfo",
71
+ "test": "vitest run"
71
72
  }
72
- }
73
+ }