iso27001-mcp 0.8.3 → 0.8.5

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 (3) hide show
  1. package/README.md +1 -1
  2. package/dist/index.js +303 -16
  3. package/package.json +2 -1
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  **Turn Claude into an ISO 27001 compliance assistant** — controls, risk register, policies, evidence tracking, SoA generation, and full audit workflows in one local encrypted MCP server.
4
4
 
5
- [![Socket Badge](https://badge.socket.dev/npm/package/iso27001-mcp/0.8.3)](https://socket.dev/npm/package/iso27001-mcp/overview/0.8.3)
5
+ [![Socket Badge](https://badge.socket.dev/npm/package/iso27001-mcp/0.8.5)](https://socket.dev/npm/package/iso27001-mcp/overview/0.8.5)
6
6
  [![npm version](https://img.shields.io/npm/v/iso27001-mcp.svg)](https://npmjs.com/package/iso27001-mcp)
7
7
  [![npm downloads](https://img.shields.io/npm/dt/iso27001-mcp.svg)](https://npmjs.com/package/iso27001-mcp)
8
8
  [![CI](https://github.com/Sushegaad/MCP-Server-for-ISO27001/actions/workflows/ci.yml/badge.svg)](https://github.com/Sushegaad/MCP-Server-for-ISO27001/actions/workflows/ci.yml)
package/dist/index.js CHANGED
@@ -24797,7 +24797,8 @@ var require_package = __commonJS({
24797
24797
  "package.json"(exports2, module2) {
24798
24798
  module2.exports = {
24799
24799
  name: "iso27001-mcp",
24800
- version: "0.8.3",
24800
+ version: "0.8.5",
24801
+ mcpName: "io.github.Sushegaad/iso27001-mcp",
24801
24802
  description: "ISO 27001 compliance workspace for Claude \u2014 controls, risks, policies, evidence, audits, and SoA in one local encrypted MCP server",
24802
24803
  license: "MIT",
24803
24804
  repository: {
@@ -26259,13 +26260,24 @@ var MIGRATION_0006 = `-- =======================================================
26259
26260
  -- without a default; prev_hash is nullable (NULL = first in chain).
26260
26261
  ALTER TABLE audit_log ADD COLUMN prev_hash TEXT;
26261
26262
  `;
26263
+ var MIGRATION_0007 = `-- ============================================================
26264
+ -- iso27001-mcp Migration 0007 \u2014 Organisation Profile Branding
26265
+ -- Adds optional branding and document personalisation fields.
26266
+ -- !! Never edit this file after it has been applied !!
26267
+ -- ============================================================
26268
+ ALTER TABLE organization_profile ADD COLUMN logo_url TEXT;
26269
+ ALTER TABLE organization_profile ADD COLUMN primary_color TEXT;
26270
+ ALTER TABLE organization_profile ADD COLUMN document_footer TEXT;
26271
+ ALTER TABLE organization_profile ADD COLUMN certification_body TEXT;
26272
+ `;
26262
26273
  var MIGRATIONS = [
26263
26274
  { filename: "0001_initial.sql", sql: MIGRATION_0001 },
26264
26275
  { filename: "0002_fts_index.sql", sql: MIGRATION_0002 },
26265
26276
  { filename: "0003_org_profile_procedures.sql", sql: MIGRATION_0003 },
26266
26277
  { filename: "0004_management_review_improvement.sql", sql: MIGRATION_0004 },
26267
26278
  { filename: "0005_evidence_documents.sql", sql: MIGRATION_0005 },
26268
- { filename: "0006_audit_log_hmac.sql", sql: MIGRATION_0006 }
26279
+ { filename: "0006_audit_log_hmac.sql", sql: MIGRATION_0006 },
26280
+ { filename: "0007_org_profile_branding.sql", sql: MIGRATION_0007 }
26269
26281
  ];
26270
26282
 
26271
26283
  // src/security/secrets.ts
@@ -33674,8 +33686,6 @@ var paginationLimit = import_zod.z.number().int().min(1).max(100).optional().def
33674
33686
  var paginationOffset = import_zod.z.number().int().min(0).optional().default(0);
33675
33687
  var versionEnum = import_zod.z.enum(["2022", "2013"]);
33676
33688
  var formatMarkdownCsvJson = import_zod.z.enum(["markdown", "csv", "json"]);
33677
- var formatMarkdownCsv = import_zod.z.enum(["markdown", "csv"]);
33678
- var formatMarkdownJson = import_zod.z.enum(["markdown", "json"]);
33679
33689
  var riskLevelEnum = import_zod.z.enum(["Low", "Medium", "High", "Critical"]);
33680
33690
  var likelihood1to5 = import_zod.z.number().int().min(1).max(5);
33681
33691
  var roleEnum = import_zod.z.enum(["viewer", "analyst", "admin"]);
@@ -33934,7 +33944,7 @@ var UpdateSoaEntrySchema = import_zod.z.object({
33934
33944
  });
33935
33945
  var ExportSoaSchema = import_zod.z.object({
33936
33946
  soa_id: uuid,
33937
- format: formatMarkdownCsv
33947
+ format: import_zod.z.enum(["markdown", "csv", "html"])
33938
33948
  });
33939
33949
  var CreateAuditSchema = import_zod.z.object({
33940
33950
  name: shortText(200),
@@ -33971,7 +33981,7 @@ var UpdateCorrectiveActionSchema = import_zod.z.object({
33971
33981
  });
33972
33982
  var GenerateAuditReportSchema = import_zod.z.object({
33973
33983
  audit_id: uuid,
33974
- format: formatMarkdownJson
33984
+ format: import_zod.z.enum(["markdown", "json", "html"])
33975
33985
  });
33976
33986
  var RegisterEvidenceSchema = import_zod.z.object({
33977
33987
  control_id: import_zod.z.string().min(1).max(20),
@@ -34036,7 +34046,11 @@ var SetOrganizationProfileSchema = import_zod.z.object({
34036
34046
  isms_manager: shortText(200).optional(),
34037
34047
  internal_auditor: shortText(200).optional()
34038
34048
  }).optional(),
34039
- review_cadence_months: import_zod.z.number().int().min(1).max(36).optional().default(12)
34049
+ review_cadence_months: import_zod.z.number().int().min(1).max(36).optional().default(12),
34050
+ logo_url: import_zod.z.string().url().max(2e3).optional(),
34051
+ primary_color: import_zod.z.string().regex(/^#[0-9a-fA-F]{6}$/, "must be 6-digit hex e.g. #1e3a5f").optional(),
34052
+ document_footer: import_zod.z.string().max(500).optional(),
34053
+ certification_body: import_zod.z.string().max(200).optional()
34040
34054
  });
34041
34055
  var GetOrganizationProfileSchema = import_zod.z.object({});
34042
34056
  var procedureTypeEnum = import_zod.z.enum([
@@ -34090,7 +34104,7 @@ var ListProceduresSchema = import_zod.z.object({
34090
34104
  });
34091
34105
  var ExportProcedureSchema = import_zod.z.object({
34092
34106
  procedure_id: uuid,
34093
- format: formatMarkdownJson
34107
+ format: import_zod.z.enum(["markdown", "json", "html"])
34094
34108
  });
34095
34109
  var reviewStatusEnum = import_zod.z.enum(["planned", "in_progress", "completed"]);
34096
34110
  var reviewInputCategoryEnum = import_zod.z.enum([
@@ -35415,6 +35429,171 @@ function stripFrontmatter(raw) {
35415
35429
  }
35416
35430
  return { template, clauseMappings, controlMappings };
35417
35431
  }
35432
+ function markdownToHtml(md) {
35433
+ const lines = md.split("\n");
35434
+ const out = [];
35435
+ let inTable = false;
35436
+ let inList = false;
35437
+ let tableHeaderDone = false;
35438
+ const esc = (s) => s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
35439
+ const inline = (s) => s.replace(/\*\*([^*]+)\*\*/g, "<strong>$1</strong>").replace(/\*([^*]+)\*/g, "<em>$1</em>").replace(/`([^`]+)`/g, "<code>$1</code>");
35440
+ for (const line of lines) {
35441
+ const h1 = line.match(/^# (.+)/);
35442
+ const h2 = line.match(/^## (.+)/);
35443
+ const h3 = line.match(/^### (.+)/);
35444
+ if (h1 || h2 || h3) {
35445
+ if (inList) {
35446
+ out.push("</ul>");
35447
+ inList = false;
35448
+ }
35449
+ if (inTable) {
35450
+ out.push("</tbody></table>");
35451
+ inTable = false;
35452
+ tableHeaderDone = false;
35453
+ }
35454
+ if (h1) {
35455
+ out.push(`<h1>${inline(esc(h1[1]))}</h1>`);
35456
+ continue;
35457
+ }
35458
+ if (h2) {
35459
+ out.push(`<h2>${inline(esc(h2[1]))}</h2>`);
35460
+ continue;
35461
+ }
35462
+ if (h3) {
35463
+ out.push(`<h3>${inline(esc(h3[1]))}</h3>`);
35464
+ continue;
35465
+ }
35466
+ }
35467
+ if (/^---+$/.test(line.trim())) {
35468
+ if (inList) {
35469
+ out.push("</ul>");
35470
+ inList = false;
35471
+ }
35472
+ if (inTable) {
35473
+ out.push("</tbody></table>");
35474
+ inTable = false;
35475
+ tableHeaderDone = false;
35476
+ }
35477
+ out.push("<hr>");
35478
+ continue;
35479
+ }
35480
+ if (line.trim().startsWith("|")) {
35481
+ if (inList) {
35482
+ out.push("</ul>");
35483
+ inList = false;
35484
+ }
35485
+ if (/^\|[-| :]+\|$/.test(line.trim())) {
35486
+ if (!tableHeaderDone) {
35487
+ out.push("<tbody>");
35488
+ tableHeaderDone = true;
35489
+ }
35490
+ continue;
35491
+ }
35492
+ const cells = line.split("|").slice(1, -1).map((c) => c.trim());
35493
+ if (!inTable) {
35494
+ out.push("<table><thead><tr>");
35495
+ cells.forEach((c) => out.push(`<th>${inline(esc(c))}</th>`));
35496
+ out.push("</tr></thead>");
35497
+ inTable = true;
35498
+ tableHeaderDone = false;
35499
+ continue;
35500
+ }
35501
+ out.push("<tr>");
35502
+ cells.forEach((c) => out.push(`<td>${inline(esc(c))}</td>`));
35503
+ out.push("</tr>");
35504
+ continue;
35505
+ }
35506
+ if (inTable) {
35507
+ out.push("</tbody></table>");
35508
+ inTable = false;
35509
+ tableHeaderDone = false;
35510
+ }
35511
+ if (/^[-*] /.test(line)) {
35512
+ if (!inList) {
35513
+ out.push("<ul>");
35514
+ inList = true;
35515
+ }
35516
+ out.push(`<li>${inline(esc(line.replace(/^[-*] /, "")))}</li>`);
35517
+ continue;
35518
+ }
35519
+ if (inList && line.trim() === "") {
35520
+ out.push("</ul>");
35521
+ inList = false;
35522
+ }
35523
+ if (line.trim() === "") {
35524
+ out.push("");
35525
+ continue;
35526
+ }
35527
+ if (!inList) out.push(`<p>${inline(esc(line))}</p>`);
35528
+ }
35529
+ if (inList) out.push("</ul>");
35530
+ if (inTable) out.push("</tbody></table>");
35531
+ return out.join("\n");
35532
+ }
35533
+ function renderHtmlDocument(bodyHtml, meta) {
35534
+ const color = meta.primary_color ?? "#1e3a5f";
35535
+ const footer = meta.document_footer ?? meta.organisation_name;
35536
+ const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
35537
+ return `<!DOCTYPE html>
35538
+ <html lang="en">
35539
+ <head>
35540
+ <meta charset="UTF-8">
35541
+ <meta name="viewport" content="width=device-width,initial-scale=1.0">
35542
+ <title>${meta.title} \u2014 ${meta.organisation_name}</title>
35543
+ <style>
35544
+ *,*::before,*::after{box-sizing:border-box}
35545
+ body{font-family:'Segoe UI',Arial,sans-serif;font-size:11pt;line-height:1.6;color:#1a1a1a;margin:0;padding:0;background:#fff}
35546
+ .doc-header{background:${color};color:#fff;padding:24px 40px 18px;display:flex;align-items:center;gap:20px}
35547
+ .doc-header-logo{max-height:48px;max-width:160px;object-fit:contain}
35548
+ .doc-header-text{flex:1}
35549
+ .doc-header h1{margin:0 0 4px;font-size:18pt;font-weight:700;color:#fff}
35550
+ .doc-header-sub{font-size:10pt;opacity:0.85}
35551
+ .doc-meta-bar{background:#f5f7fa;border-bottom:2px solid ${color};padding:10px 40px;display:flex;gap:32px;font-size:9pt;color:#444}
35552
+ .doc-meta-item strong{color:#111}
35553
+ .doc-body{padding:28px 40px;max-width:900px}
35554
+ h1{font-size:16pt;color:${color};margin-top:24px;border-bottom:2px solid ${color};padding-bottom:4px}
35555
+ h2{font-size:13pt;color:${color};margin-top:20px}
35556
+ h3{font-size:11pt;color:#333;margin-top:16px}
35557
+ table{width:100%;border-collapse:collapse;margin:14px 0;font-size:10pt}
35558
+ th{background:${color};color:#fff;padding:7px 10px;text-align:left;font-weight:600}
35559
+ td{padding:6px 10px;border-bottom:1px solid #e0e4ea;vertical-align:top}
35560
+ tr:nth-child(even) td{background:#f8f9fb}
35561
+ ul{padding-left:20px;margin:8px 0}
35562
+ li{margin:3px 0}
35563
+ hr{border:none;border-top:1px solid #dce1ea;margin:20px 0}
35564
+ p{margin:8px 0}
35565
+ code{background:#f0f2f5;padding:1px 5px;border-radius:3px;font-family:monospace;font-size:10pt}
35566
+ .doc-footer{margin-top:40px;padding:14px 40px;background:#f5f7fa;border-top:2px solid ${color};font-size:9pt;color:#666;display:flex;justify-content:space-between}
35567
+ @media print{
35568
+ body{font-size:10pt}
35569
+ .doc-header,.doc-meta-bar,th,.doc-footer{-webkit-print-color-adjust:exact;print-color-adjust:exact}
35570
+ h1,h2{page-break-after:avoid}
35571
+ table{page-break-inside:avoid}
35572
+ }
35573
+ </style>
35574
+ </head>
35575
+ <body>
35576
+ <div class="doc-header">
35577
+ ${meta.logo_url ? `<img class="doc-header-logo" src="${meta.logo_url}" alt="${meta.organisation_name} logo">` : ""}
35578
+ <div class="doc-header-text">
35579
+ <h1>${meta.title}</h1>
35580
+ <div class="doc-header-sub">${meta.organisation_name}${meta.doc_type ? ` \xB7 ${meta.doc_type}` : ""}</div>
35581
+ </div>
35582
+ </div>
35583
+ <div class="doc-meta-bar">
35584
+ ${meta.version ? `<span><strong>Version:</strong> ${meta.version}</span>` : ""}
35585
+ ${meta.effective_date ? `<span><strong>Effective:</strong> ${meta.effective_date}</span>` : ""}
35586
+ ${meta.owner ? `<span><strong>Owner:</strong> ${meta.owner}</span>` : ""}
35587
+ <span><strong>Generated:</strong> ${today}</span>
35588
+ </div>
35589
+ <div class="doc-body">${bodyHtml}</div>
35590
+ <div class="doc-footer">
35591
+ <span>${footer}</span>
35592
+ <span>INTERNAL \u2014 ISMS Controlled Document \xB7 Generated ${today}</span>
35593
+ </div>
35594
+ </body>
35595
+ </html>`;
35596
+ }
35418
35597
 
35419
35598
  // src/tools/org-profile.ts
35420
35599
  var ORG_PROFILE_ID = "00000000-0000-4000-8000-000000000001";
@@ -35422,11 +35601,15 @@ function ok4(data) {
35422
35601
  return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }], isError: false };
35423
35602
  }
35424
35603
  function loadOrgProfileDefaults(db) {
35425
- const row = db.prepare("SELECT legal_entity_name, isms_scope_statement FROM organization_profile WHERE id = ?").get(ORG_PROFILE_ID);
35604
+ const row = db.prepare("SELECT legal_entity_name, isms_scope_statement, logo_url, primary_color, document_footer, certification_body FROM organization_profile WHERE id = ?").get(ORG_PROFILE_ID);
35426
35605
  if (!row) return null;
35427
35606
  return {
35428
35607
  organisation_name: row.legal_entity_name,
35429
- scope: row.isms_scope_statement
35608
+ scope: row.isms_scope_statement,
35609
+ logo_url: row.logo_url,
35610
+ primary_color: row.primary_color,
35611
+ document_footer: row.document_footer,
35612
+ certification_body: row.certification_body
35430
35613
  };
35431
35614
  }
35432
35615
  function handleSetOrganizationProfile(args2) {
@@ -35438,17 +35621,22 @@ function handleSetOrganizationProfile(args2) {
35438
35621
  isms_scope_statement,
35439
35622
  declared_exclusions,
35440
35623
  raci_roles,
35441
- review_cadence_months = 12
35624
+ review_cadence_months = 12,
35625
+ logo_url,
35626
+ primary_color,
35627
+ document_footer,
35628
+ certification_body
35442
35629
  } = args2;
35443
35630
  const ts = now();
35444
35631
  getDb().prepare(`
35445
35632
  INSERT OR REPLACE INTO organization_profile
35446
35633
  (id, legal_entity_name, registered_jurisdiction, regulatory_licences,
35447
35634
  in_scope_activities, isms_scope_statement, declared_exclusions,
35448
- raci_roles, review_cadence_months, created_at, updated_at)
35635
+ raci_roles, review_cadence_months, created_at, updated_at,
35636
+ logo_url, primary_color, document_footer, certification_body)
35449
35637
  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, COALESCE(
35450
35638
  (SELECT created_at FROM organization_profile WHERE id = ?), ?
35451
- ), ?)
35639
+ ), ?, ?, ?, ?, ?)
35452
35640
  `).run(
35453
35641
  ORG_PROFILE_ID,
35454
35642
  legal_entity_name,
@@ -35461,7 +35649,11 @@ function handleSetOrganizationProfile(args2) {
35461
35649
  review_cadence_months,
35462
35650
  ORG_PROFILE_ID,
35463
35651
  ts,
35464
- ts
35652
+ ts,
35653
+ logo_url ?? null,
35654
+ primary_color ?? null,
35655
+ document_footer ?? null,
35656
+ certification_body ?? null
35465
35657
  );
35466
35658
  return ok4({
35467
35659
  id: ORG_PROFILE_ID,
@@ -35829,6 +36021,44 @@ function handleExportSoa(args2) {
35829
36021
  `).all(soa.isms_version, soa_id);
35830
36022
  const includedCount = entries.filter((e) => e.included === 1).length;
35831
36023
  const excludedCount = entries.filter((e) => e.included === 0).length;
36024
+ const ORG_PROFILE_ID_SOA = "00000000-0000-4000-8000-000000000001";
36025
+ if (format === "html") {
36026
+ const profileRow = db.prepare(
36027
+ "SELECT legal_entity_name, logo_url, primary_color, document_footer FROM organization_profile WHERE id = ?"
36028
+ ).get(ORG_PROFILE_ID_SOA);
36029
+ const tableRows = entries.map((e) => {
36030
+ const inc = e.included === 1;
36031
+ return `<tr>
36032
+ <td><strong>${e.control_id}</strong></td>
36033
+ <td>${e.control_name ?? "\u2014"}</td>
36034
+ <td>${e.theme ?? "\u2014"}</td>
36035
+ <td style="color:${inc ? "#065f46" : "#991b1b"};font-weight:600">${inc ? "\u2713 Yes" : "\u2717 No"}</td>
36036
+ <td>${e.justification}</td>
36037
+ <td>${e.status ?? "\u2014"}</td>
36038
+ <td>${e.responsible_party ?? "\u2014"}</td>
36039
+ </tr>`;
36040
+ }).join("\n");
36041
+ const bodyHtml = `
36042
+ <p><strong>ISMS Version:</strong> ISO 27001:${soa.isms_version} &nbsp;\xB7&nbsp;
36043
+ <strong>Total:</strong> ${entries.length} &nbsp;\xB7&nbsp;
36044
+ <strong>Included:</strong> ${includedCount} &nbsp;\xB7&nbsp;
36045
+ <strong>Excluded:</strong> ${excludedCount}</p>
36046
+ <table>
36047
+ <thead>
36048
+ <tr><th>Control ID</th><th>Name</th><th>Theme</th><th>Included</th><th>Justification</th><th>Status</th><th>Responsible Party</th></tr>
36049
+ </thead>
36050
+ <tbody>${tableRows}</tbody>
36051
+ </table>`;
36052
+ const html = renderHtmlDocument(bodyHtml, {
36053
+ title: "Statement of Applicability",
36054
+ organisation_name: profileRow?.legal_entity_name ?? "Organisation",
36055
+ logo_url: profileRow?.logo_url,
36056
+ primary_color: profileRow?.primary_color,
36057
+ document_footer: profileRow?.document_footer,
36058
+ doc_type: `ISO 27001:${soa.isms_version}`
36059
+ });
36060
+ return ok6({ format: "html", content: html });
36061
+ }
35832
36062
  if (format === "csv") {
35833
36063
  const header = "control_id,name,theme,included,justification,status,responsible_party";
35834
36064
  const rows = entries.map(
@@ -36062,6 +36292,46 @@ function handleGenerateAuditReport(args2) {
36062
36292
  carsByFinding.set(car.finding_id, existing);
36063
36293
  }
36064
36294
  const countByType = (type) => findings.filter((f) => f.type === type).length;
36295
+ if (args2.format === "html") {
36296
+ const mdLines = [
36297
+ `# ${audit.name}`,
36298
+ ``,
36299
+ `**Auditor:** ${audit.auditor} `,
36300
+ `**Planned Date:** ${audit.planned_date} `,
36301
+ `**Status:** ${audit.status} `,
36302
+ `**Scope:** ${audit.scope}`,
36303
+ ``,
36304
+ `## Findings (${findings.length})`,
36305
+ ``,
36306
+ `| ID | Type | Severity | Clause/Control | Description |`,
36307
+ `|---|---|---|---|---|`,
36308
+ ...findings.map(
36309
+ (f) => `| ${f.id.slice(-8)} | ${f.type.toUpperCase()} | ${f.severity ?? "\u2014"} | ${f.clause_or_control} | ${f.description.slice(0, 80)} |`
36310
+ ),
36311
+ ``,
36312
+ `## Corrective Actions (${cars.length})`,
36313
+ ``,
36314
+ `| CAR ID | Finding | Owner | Due Date | Status | Verified |`,
36315
+ `|---|---|---|---|---|---|`,
36316
+ ...cars.map(
36317
+ (c) => `| ${c.id.slice(-8)} | ${c.finding_id.slice(-8)} | ${c.owner} | ${c.due_date} | ${c.status} | ${c.effectiveness_verified ? "\u2713 Yes" : "No"} |`
36318
+ )
36319
+ ];
36320
+ const db2 = getDb();
36321
+ const ORG_PROFILE_ID_AUDIT = "00000000-0000-4000-8000-000000000001";
36322
+ const profileRow = db2.prepare(
36323
+ "SELECT legal_entity_name, logo_url, primary_color, document_footer FROM organization_profile WHERE id = ?"
36324
+ ).get(ORG_PROFILE_ID_AUDIT);
36325
+ const html = renderHtmlDocument(markdownToHtml(mdLines.join("\n")), {
36326
+ title: audit.name,
36327
+ organisation_name: profileRow?.legal_entity_name ?? "Organisation",
36328
+ logo_url: profileRow?.logo_url,
36329
+ primary_color: profileRow?.primary_color,
36330
+ document_footer: profileRow?.document_footer,
36331
+ doc_type: "Internal Audit Report"
36332
+ });
36333
+ return ok7({ format: "html", content: html });
36334
+ }
36065
36335
  if (format === "json") {
36066
36336
  return ok7({
36067
36337
  audit: shapeAudit(audit),
@@ -36657,6 +36927,23 @@ function handleExportProcedure(args2) {
36657
36927
  const db = getDb();
36658
36928
  const row = db.prepare("SELECT * FROM procedures WHERE id = ?").get(procedure_id);
36659
36929
  if (!row) throw notFound("procedure", procedure_id);
36930
+ if (format === "html") {
36931
+ const db2 = getDb();
36932
+ const defaults = loadOrgProfileDefaults(db2);
36933
+ const bodyHtml = markdownToHtml(row.content);
36934
+ const html = renderHtmlDocument(bodyHtml, {
36935
+ title: row.procedure_type.replace(/_/g, " ").replace(/\b\w/g, (l) => l.toUpperCase()) + " Procedure",
36936
+ organisation_name: row.organisation_name,
36937
+ logo_url: defaults?.logo_url,
36938
+ primary_color: defaults?.primary_color,
36939
+ document_footer: defaults?.document_footer,
36940
+ version: String(row.version),
36941
+ effective_date: row.effective_date,
36942
+ owner: row.owner,
36943
+ doc_type: "Procedure"
36944
+ });
36945
+ return ok9({ format: "html", content: html });
36946
+ }
36660
36947
  if (format === "markdown") {
36661
36948
  const relatedControls = fromJsonArray(row.related_controls);
36662
36949
  const controlsSection = relatedControls.length > 0 ? `
@@ -38626,12 +38913,12 @@ ${divider}
38626
38913
  } else {
38627
38914
  try {
38628
38915
  const rows = openDb(dbPath2).prepare("SELECT filename FROM _migrations ORDER BY id").all();
38629
- const passed2 = rows.length >= 6;
38916
+ const passed2 = rows.length >= 7;
38630
38917
  check(
38631
38918
  "Migrations",
38632
38919
  passed2,
38633
38920
  false,
38634
- `${rows.length}/6 applied${passed2 ? "" : " \u2014 DB may need re-initialisation"}`
38921
+ `${rows.length}/7 applied${passed2 ? "" : " \u2014 DB may need re-initialisation"}`
38635
38922
  );
38636
38923
  record(passed2);
38637
38924
  } catch {
package/package.json CHANGED
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "name": "iso27001-mcp",
3
- "version": "0.8.3",
3
+ "version": "0.8.5",
4
+ "mcpName": "io.github.Sushegaad/iso27001-mcp",
4
5
  "description": "ISO 27001 compliance workspace for Claude — controls, risks, policies, evidence, audits, and SoA in one local encrypted MCP server",
5
6
  "license": "MIT",
6
7
  "repository": {