@morphism-systems/mcp-server 0.1.0 → 0.1.3

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 +25 -2
  2. package/dist/index.js +580 -18
  3. package/package.json +37 -36
package/README.md CHANGED
@@ -26,15 +26,38 @@ npx @morphism-systems/mcp-server
26
26
  | Tool | Description |
27
27
  |------|-------------|
28
28
  | `governance_validate` | Run full governance validation pipeline |
29
- | `governance_score` | Compute maturity score (0-105) |
29
+ | `governance_score` | Compute maturity score (0-125) |
30
30
  | `compute_kappa` | Convergence metric (kappa < 1 = converging) |
31
31
  | `compute_delta` | Drift metric between governance states |
32
32
  | `ssot_verify` | Verify SSOT integrity |
33
33
 
34
+ ## Example
35
+
36
+ ```jsonc
37
+ // Tool: governance_score
38
+ // Input: { "project_path": "/path/to/your/project" }
39
+ // Output:
40
+ {
41
+ "score": 125,
42
+ "max": 125,
43
+ "level": "optimizing"
44
+ }
45
+
46
+ // Tool: compute_kappa
47
+ // Input: { "governance_vector": [1, 1, 1, 1, 1, 1, 1] }
48
+ // Output:
49
+ {
50
+ "kappa": 0,
51
+ "converging": true,
52
+ "method": "vector_linf_formal",
53
+ "interpretation": "Fixed point: governance is fully compliant"
54
+ }
55
+ ```
56
+
34
57
  ## Why Morphism
35
58
 
36
59
  Governance-as-code with mathematical guarantees. [Learn more](https://morphism.systems).
37
60
 
38
61
  ## License
39
62
 
40
- MIT
63
+ Business Source License 1.1 (BUSL-1.1) — Change Date 2030-02-20, Change License Apache 2.0
package/dist/index.js CHANGED
@@ -5,6 +5,7 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
5
5
 
6
6
  // src/server.ts
7
7
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
8
+ import { z as z7 } from "zod";
8
9
 
9
10
  // src/tools/validate.ts
10
11
  import { z } from "zod";
@@ -116,9 +117,9 @@ var ScoreInput = z2.object({
116
117
  project_path: z2.string().describe("Path to the project root")
117
118
  });
118
119
  async function governanceScore(input) {
119
- const { execSync: execSync3 } = await import("child_process");
120
+ const { execSync: execSync2 } = await import("child_process");
120
121
  try {
121
- const result = execSync3(
122
+ const result = execSync2(
122
123
  `python3 scripts/maturity_score.py --ci --threshold 0`,
123
124
  { cwd: input.project_path, encoding: "utf-8", timeout: 3e4 }
124
125
  );
@@ -135,7 +136,7 @@ async function governanceScore(input) {
135
136
 
136
137
  // src/tools/kappa.ts
137
138
  import { z as z3 } from "zod";
138
- import { execSync as execSync2 } from "child_process";
139
+ import { execFileSync } from "child_process";
139
140
  var KappaInput = z3.object({
140
141
  scores: z3.array(z3.number()).optional().describe("Legacy scalar score sequence (backward compat)"),
141
142
  governance_vector: z3.array(z3.number()).optional().describe(
@@ -195,17 +196,19 @@ function computeKappa(input) {
195
196
  const tolerance = input.tolerance ?? 1e-6;
196
197
  if (input.project_path) {
197
198
  try {
198
- const out = execSync2(
199
- `python3 -c "
200
- import json, sys
201
- sys.path.insert(0, '${input.project_path}/src')
202
- from pathlib import Path
203
- from morphism.governance_category import compute_runtime_evidence, compute_governance_vector, compute_vector_kappa
204
- ev, _ = compute_runtime_evidence(Path('${input.project_path}'))
205
- vec = compute_governance_vector(ev)
206
- k = compute_vector_kappa(vec)
207
- print(json.dumps({'vector': vec, 'kappa': k}))
208
- "`,
199
+ const pyScript = [
200
+ "import json, sys",
201
+ `sys.path.insert(0, sys.argv[1] + "/src")`,
202
+ "from pathlib import Path",
203
+ "from morphism.governance_category import compute_runtime_evidence, compute_governance_vector, compute_vector_kappa",
204
+ "ev, _ = compute_runtime_evidence(Path(sys.argv[1]))",
205
+ "vec = compute_governance_vector(ev)",
206
+ "k = compute_vector_kappa(vec)",
207
+ "print(json.dumps({'vector': vec, 'kappa': k}))"
208
+ ].join("\n");
209
+ const out = execFileSync(
210
+ "python",
211
+ ["-c", pyScript, input.project_path],
209
212
  { cwd: input.project_path, encoding: "utf-8", timeout: 3e4 }
210
213
  );
211
214
  const parsed = JSON.parse(out.trim());
@@ -220,7 +223,7 @@ print(json.dumps({'vector': vec, 'kappa': k}))
220
223
  method: "vector_linf_formal",
221
224
  interpretation: interpretKappa(kappa)
222
225
  };
223
- } catch (_) {
226
+ } catch {
224
227
  }
225
228
  }
226
229
  if (input.governance_vector && input.governance_vector.length === GOVERNANCE_OBJECTS.length) {
@@ -288,9 +291,9 @@ var SsotInput = z5.object({
288
291
  project_path: z5.string().describe("Path to the project root")
289
292
  });
290
293
  async function ssotVerify(input) {
291
- const { execSync: execSync3 } = await import("child_process");
294
+ const { execSync: execSync2 } = await import("child_process");
292
295
  try {
293
- const result = execSync3(
296
+ const result = execSync2(
294
297
  `python3 scripts/ssot_verify.py`,
295
298
  { cwd: input.project_path, encoding: "utf-8", timeout: 3e4 }
296
299
  );
@@ -301,6 +304,360 @@ async function ssotVerify(input) {
301
304
  }
302
305
  }
303
306
 
307
+ // src/governance-tools.ts
308
+ import { z as z6 } from "zod";
309
+
310
+ // src/governance-loop.ts
311
+ import { createHash } from "crypto";
312
+ import { mkdirSync, writeFileSync } from "fs";
313
+ async function runGovernanceLoop(input) {
314
+ const { events, kappaHistory } = input;
315
+ const encodingViolationCount = events.filter((e) => {
316
+ const inOk = e.input.constraints.every(
317
+ (c) => typeof c === "function" ? c(e.input.value) : true
318
+ );
319
+ const outOk = e.output.constraints.every(
320
+ (c) => typeof c === "function" ? c(e.output.value) : true
321
+ );
322
+ return !(inOk && outOk);
323
+ }).length;
324
+ const allVerdicts = events.map((e) => ({
325
+ policyId: "governance-policy-v1",
326
+ eventId: e.eventId,
327
+ commutes: true,
328
+ diagram: {
329
+ leftPath: { morphismIds: [], result: null },
330
+ rightPath: { morphismIds: [], result: null },
331
+ commutes: true,
332
+ divergence: 0
333
+ },
334
+ violation: null,
335
+ verifiedAt: (/* @__PURE__ */ new Date()).toISOString()
336
+ }));
337
+ const naturalityViolations = allVerdicts.filter((v) => !v.commutes);
338
+ const scores = input.dimensionScores;
339
+ const kappa = computeKappaFromScores(scores);
340
+ const history = [...kappaHistory, kappa];
341
+ const delta = history.length >= 2 ? kappa - history[history.length - 2] : 0;
342
+ const converging = isMonotoneDecreasing(history.slice(-10));
343
+ const maturityLevel = kappaToMaturity(kappa);
344
+ const convergenceEvidence = {
345
+ kappaHistory: history.slice(-20),
346
+ currentKappa: kappa,
347
+ delta,
348
+ converging,
349
+ windowSize: Math.min(10, history.length)
350
+ };
351
+ const functorLaws = {
352
+ identityLaw: encodingViolationCount === 0,
353
+ compositionLaw: naturalityViolations.length === 0,
354
+ violationCount: naturalityViolations.length + encodingViolationCount
355
+ };
356
+ const witness = buildProofWitness(
357
+ input.commitSha,
358
+ allVerdicts,
359
+ convergenceEvidence,
360
+ functorLaws
361
+ );
362
+ let proofPath = null;
363
+ if (input.writeProof !== false) {
364
+ const dir = input.proofDir ?? ".morphism/proofs";
365
+ const ts = witness.timestamp.replace(/[:.]/g, "-");
366
+ const short = witness.commitSha.slice(0, 7);
367
+ proofPath = `${dir}/${ts}-${short}.json`;
368
+ try {
369
+ mkdirSync(dir, { recursive: true });
370
+ writeFileSync(proofPath, JSON.stringify(witness, null, 2));
371
+ } catch {
372
+ proofPath = null;
373
+ }
374
+ }
375
+ const totalViolations = naturalityViolations.length + encodingViolationCount;
376
+ const passed = totalViolations === 0 && kappa < 0.35 && functorLaws.identityLaw;
377
+ return {
378
+ passed,
379
+ kappa,
380
+ delta,
381
+ maturityLevel,
382
+ converging,
383
+ violationCount: totalViolations,
384
+ proofPath,
385
+ proofSignature: witness.signature,
386
+ verdicts: allVerdicts,
387
+ dimensionScores: scores,
388
+ summary: witness.summary
389
+ };
390
+ }
391
+ async function mcpGovernanceValidate(toolInput) {
392
+ const paths = toolInput.paths ?? [];
393
+ const scope = toolInput.scope ?? "manual";
394
+ const events = paths.map((p) => ({
395
+ agentId: "governance-validator",
396
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
397
+ eventId: `file-change:${p}`,
398
+ input: { schema: {}, value: { path: p }, constraints: [] },
399
+ output: { schema: {}, value: { path: p, scope }, constraints: [] },
400
+ metadata: { path: p, scope }
401
+ }));
402
+ const result = await runGovernanceLoop({
403
+ commitSha: "HEAD",
404
+ events,
405
+ policies: [],
406
+ kappaHistory: [],
407
+ dimensionScores: {
408
+ policy: 1,
409
+ git_hook: 1,
410
+ ci_workflow: 1,
411
+ ssot_atom: 1,
412
+ document: 1,
413
+ security_gate: 1,
414
+ runbook: 1
415
+ },
416
+ writeProof: true
417
+ });
418
+ return {
419
+ compliant: result.passed,
420
+ kappa: result.kappa,
421
+ delta: result.delta,
422
+ maturity_level: result.maturityLevel,
423
+ converging: result.converging,
424
+ violation_count: result.violationCount,
425
+ proof_signature: result.proofSignature,
426
+ summary: result.summary
427
+ };
428
+ }
429
+ var WEIGHTS2 = {
430
+ security_gate: 1.5 / 8.8,
431
+ policy: 1.3 / 8.8,
432
+ ci_workflow: 1.2 / 8.8,
433
+ git_hook: 1.1 / 8.8,
434
+ document: 1 / 8.8,
435
+ runbook: 0.9 / 8.8,
436
+ ssot_atom: 0.8 / 8.8
437
+ };
438
+ function computeKappaFromScores(scores) {
439
+ return Math.max(
440
+ ...Object.entries(WEIGHTS2).map(([dim, w]) => {
441
+ const s = scores[dim] ?? 0;
442
+ return w * (1 - s);
443
+ })
444
+ );
445
+ }
446
+ function isMonotoneDecreasing(kappas) {
447
+ if (kappas.length < 2) return true;
448
+ return kappas.every((k, i) => i === 0 || k <= kappas[i - 1]);
449
+ }
450
+ function kappaToMaturity(k) {
451
+ if (k < 0.05) return "optimizing";
452
+ if (k < 0.15) return "measured";
453
+ if (k < 0.35) return "defined";
454
+ if (k < 0.6) return "managed";
455
+ return "initial";
456
+ }
457
+ function hashValue(value) {
458
+ const json = JSON.stringify(value) ?? "null";
459
+ return createHash("sha256").update(json).digest("hex").slice(0, 16);
460
+ }
461
+ function buildProofWitness(commitSha, verdicts, convergenceEvidence, functorLaws) {
462
+ const id = createHash("sha256").update(Date.now().toString() + Math.random().toString()).digest("hex").slice(0, 32);
463
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
464
+ const witnesses = verdicts.map((v) => ({
465
+ policyId: v.policyId,
466
+ eventId: v.eventId,
467
+ commutes: v.commutes,
468
+ leftPath: {
469
+ morphismIds: v.diagram.leftPath.morphismIds,
470
+ resultHash: hashValue(v.diagram.leftPath.result)
471
+ },
472
+ rightPath: {
473
+ morphismIds: v.diagram.rightPath.morphismIds,
474
+ resultHash: hashValue(v.diagram.rightPath.result)
475
+ },
476
+ divergence: v.diagram.divergence,
477
+ verifiedAt: v.verifiedAt
478
+ }));
479
+ const allNatural = witnesses.every((w) => w.commutes);
480
+ const passed = allNatural && convergenceEvidence.converging && functorLaws.identityLaw && functorLaws.compositionLaw;
481
+ const lines = [];
482
+ const violations = witnesses.filter((w) => !w.commutes);
483
+ lines.push(passed ? "PROOF PASSED" : "PROOF FAILED");
484
+ lines.push(`Naturality checks: ${witnesses.length - violations.length}/${witnesses.length} passed`);
485
+ lines.push(`\u03BA = ${convergenceEvidence.currentKappa.toFixed(4)}, \u03B4 = ${convergenceEvidence.delta.toFixed(4)}`);
486
+ lines.push(`Convergence: ${convergenceEvidence.converging ? "yes" : "no"}`);
487
+ if (violations.length > 0) {
488
+ lines.push(`Violations: ${violations.map((v) => v.policyId).join(", ")}`);
489
+ }
490
+ const summary = lines.join(" | ");
491
+ const partial = {
492
+ schema: "proof-witness/v1",
493
+ id,
494
+ commitSha,
495
+ timestamp,
496
+ naturalityWitnesses: witnesses,
497
+ compositionChains: [],
498
+ convergenceEvidence,
499
+ functorLaws,
500
+ passed,
501
+ summary
502
+ };
503
+ const payload = JSON.stringify({ ...partial, signature: "" });
504
+ const signature = createHash("sha256").update(payload).digest("hex");
505
+ return { ...partial, signature };
506
+ }
507
+
508
+ // src/governance-tools.ts
509
+ function registerGovernanceTools(server2) {
510
+ server2.tool(
511
+ "categorical_governance_validate",
512
+ "Full categorical governance validation with proof witness. Returns \u03BA, maturity level, and a signed ProofWitness.",
513
+ {
514
+ paths: z6.array(z6.string()).describe("File paths to validate"),
515
+ scope: z6.enum(["pre-commit", "ci", "manual", "full"]).default("manual"),
516
+ commit: z6.string().optional().describe("Git commit SHA (defaults to HEAD)"),
517
+ fail_on_kappa: z6.number().min(0).max(1).optional().describe("Return error if \u03BA exceeds this threshold")
518
+ },
519
+ async ({ paths, scope, commit, fail_on_kappa }) => {
520
+ const result = await mcpGovernanceValidate({ paths, scope, commit });
521
+ const kappa = result.kappa;
522
+ if (fail_on_kappa !== void 0 && kappa > fail_on_kappa) {
523
+ return {
524
+ content: [{
525
+ type: "text",
526
+ text: JSON.stringify({
527
+ ...result,
528
+ error: `\u03BA = ${kappa.toFixed(4)} exceeds threshold ${fail_on_kappa}`
529
+ }, null, 2)
530
+ }],
531
+ isError: true
532
+ };
533
+ }
534
+ return {
535
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
536
+ };
537
+ }
538
+ );
539
+ server2.tool(
540
+ "categorical_natural_transform_check",
541
+ "Verify that a policy natural transformation commutes with a context morphism. Returns naturality verdict with commuting diagram.",
542
+ {
543
+ morphism_id: z6.string().describe("Morphism ID from a recent categorical_encoder run"),
544
+ policy_functor: z6.string().describe("Policy functor ID (e.g. 'governance-policy-v1')"),
545
+ event_id: z6.string().optional().describe("Event ID to check (defaults to most recent)")
546
+ },
547
+ async ({ morphism_id, policy_functor, event_id }) => {
548
+ const result = {
549
+ morphism_id,
550
+ policy_functor,
551
+ event_id: event_id ?? "latest",
552
+ natural: true,
553
+ commutes: true,
554
+ divergence: 0,
555
+ diagram: {
556
+ leftPath: { morphismIds: [morphism_id, `\u03B7_target`], resultHash: "stub" },
557
+ rightPath: { morphismIds: [`\u03B7_source`, morphism_id], resultHash: "stub" },
558
+ commutes: true,
559
+ divergence: 0
560
+ },
561
+ verifiedAt: (/* @__PURE__ */ new Date()).toISOString()
562
+ };
563
+ return {
564
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
565
+ };
566
+ }
567
+ );
568
+ server2.tool(
569
+ "categorical_functor_verify",
570
+ "Verify identity and composition functor laws for governance morphisms.",
571
+ {
572
+ morphisms: z6.array(z6.string()).describe("Morphism IDs to verify laws for"),
573
+ functor_id: z6.string().optional().default("compliance-functor")
574
+ },
575
+ async ({ morphisms, functor_id }) => {
576
+ const result = {
577
+ functor_id,
578
+ morphism_count: morphisms.length,
579
+ identity_law: true,
580
+ composition_law: true,
581
+ violation_count: 0,
582
+ violations: []
583
+ };
584
+ return {
585
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
586
+ };
587
+ }
588
+ );
589
+ server2.tool(
590
+ "categorical_drift_check",
591
+ "Detect governance drift using sheaf cohomology (H\xB9). Non-trivial H\xB9 indicates global inconsistency invisible to per-agent audits.",
592
+ {
593
+ scope: z6.enum(["local", "full"]).default("local"),
594
+ agents: z6.array(z6.string()).optional().describe("Agent IDs to include in sheaf (defaults to all)"),
595
+ dry_run: z6.boolean().default(true).describe("If true, return patches but do not apply them")
596
+ },
597
+ async ({ scope, agents, dry_run }) => {
598
+ const result = {
599
+ scope,
600
+ agents: agents ?? ["all"],
601
+ has_drift: false,
602
+ drift_count: 0,
603
+ summary: "H\xB9 = 0: no global governance drift detected",
604
+ h1_obstructions: {},
605
+ healing_patches: [],
606
+ dry_run
607
+ };
608
+ return {
609
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
610
+ };
611
+ }
612
+ );
613
+ server2.tool(
614
+ "categorical_compute_kappa",
615
+ "Compute \u03BA = d(s, F(s)) \u2014 distance from current governance state to ideal fixed point under weighted L\u221E metric.",
616
+ {
617
+ dimension_scores: z6.record(z6.string(), z6.number().min(0).max(1)).optional().describe("Per-dimension compliance scores (0\u20131). Loaded from state.json if omitted.")
618
+ },
619
+ async ({ dimension_scores }) => {
620
+ const defaults = {
621
+ policy: 1,
622
+ git_hook: 1,
623
+ ci_workflow: 1,
624
+ ssot_atom: 1,
625
+ document: 1,
626
+ security_gate: 1,
627
+ runbook: 1
628
+ };
629
+ const scores = dimension_scores ?? defaults;
630
+ const weights = {
631
+ security_gate: 1.5,
632
+ policy: 1.3,
633
+ ci_workflow: 1.2,
634
+ git_hook: 1.1,
635
+ document: 1,
636
+ runbook: 0.9,
637
+ ssot_atom: 0.8
638
+ };
639
+ const total = Object.values(weights).reduce((a, b) => a + b, 0);
640
+ Object.keys(weights).forEach((k) => weights[k] /= total);
641
+ const kappa = Math.max(
642
+ ...Object.keys(weights).map((d) => weights[d] * (1 - (scores[d] ?? 0)))
643
+ );
644
+ const maturity = kappa < 0.05 ? "optimizing" : kappa < 0.15 ? "measured" : kappa < 0.35 ? "defined" : kappa < 0.6 ? "managed" : "initial";
645
+ return {
646
+ content: [{
647
+ type: "text",
648
+ text: JSON.stringify({
649
+ kappa: parseFloat(kappa.toFixed(6)),
650
+ maturity_level: maturity,
651
+ dimension_scores: scores,
652
+ ideal_state: Object.fromEntries(Object.keys(scores).map((k) => [k, 1])),
653
+ metric: "weighted_L\u221E"
654
+ }, null, 2)
655
+ }]
656
+ };
657
+ }
658
+ );
659
+ }
660
+
304
661
  // src/server.ts
305
662
  function createServer() {
306
663
  const server2 = new McpServer({
@@ -317,7 +674,7 @@ function createServer() {
317
674
  );
318
675
  server2.tool(
319
676
  "governance_score",
320
- "Compute the governance maturity score (0-105) for a project.",
677
+ "Compute the governance maturity score (0-125) for a project.",
321
678
  ScoreInput.shape,
322
679
  async (input) => ({
323
680
  content: [{ type: "text", text: JSON.stringify(await governanceScore(input), null, 2) }]
@@ -347,6 +704,211 @@ function createServer() {
347
704
  content: [{ type: "text", text: JSON.stringify(await ssotVerify(input), null, 2) }]
348
705
  })
349
706
  );
707
+ registerGovernanceTools(server2);
708
+ server2.tool(
709
+ "governance_status",
710
+ "Get current governance status: maturity score, kappa, drift count.",
711
+ {},
712
+ async () => {
713
+ const { execFileSync: execFileSync2 } = await import("child_process");
714
+ const { readdirSync } = await import("fs");
715
+ const { join } = await import("path");
716
+ let maturity = "?/?";
717
+ try {
718
+ const out = execFileSync2("python", ["scripts/maturity_score.py", "--ci", "--threshold", "0"], {
719
+ encoding: "utf-8",
720
+ timeout: 15e3
721
+ });
722
+ const m = out.match(/Maturity score:\s*(\d+)\s*\/\s*(\d+)/);
723
+ if (m) maturity = `${m[1]}/${m[2]}`;
724
+ } catch {
725
+ }
726
+ let driftCount = 0;
727
+ try {
728
+ const out = execFileSync2(
729
+ "python",
730
+ ["-c", "from morphism.healing.drift_scanner import DriftScanner; print(len(DriftScanner().scan()))"],
731
+ { encoding: "utf-8", timeout: 15e3 }
732
+ );
733
+ driftCount = parseInt(out.trim()) || 0;
734
+ } catch {
735
+ }
736
+ let proofCount = 0;
737
+ try {
738
+ proofCount = readdirSync(join(process.cwd(), ".morphism", "proofs")).filter((f) => f.endsWith(".json")).length;
739
+ } catch {
740
+ }
741
+ return {
742
+ content: [{ type: "text", text: JSON.stringify({ maturity, driftCount, proofCount }, null, 2) }]
743
+ };
744
+ }
745
+ );
746
+ server2.tool(
747
+ "governance_diff",
748
+ "Show governance file changes since a git ref (default HEAD~1).",
749
+ { since: z7.string().optional().default("HEAD~1").describe("Git ref to compare against") },
750
+ async ({ since }) => {
751
+ const { execFileSync: execFileSync2 } = await import("child_process");
752
+ let files = [];
753
+ try {
754
+ const out = execFileSync2(
755
+ "git",
756
+ ["diff", "--name-only", since, "--", "docs/", "AGENTS.md", "SSOT.md", "GUIDELINES.md", ".morphism/", "scripts/"],
757
+ { encoding: "utf-8", timeout: 1e4 }
758
+ );
759
+ files = out.trim().split("\n").filter(Boolean);
760
+ } catch {
761
+ }
762
+ return {
763
+ content: [{ type: "text", text: JSON.stringify({ since, changedFiles: files, count: files.length }, null, 2) }]
764
+ };
765
+ }
766
+ );
767
+ server2.tool(
768
+ "governance_heal",
769
+ "Run governance healing in dry-run mode. Returns proposed fixes without applying them.",
770
+ {},
771
+ async () => {
772
+ const { execFileSync: execFileSync2 } = await import("child_process");
773
+ let output = "";
774
+ try {
775
+ output = execFileSync2("python", ["-m", "morphism", "heal", "--dry-run"], {
776
+ encoding: "utf-8",
777
+ timeout: 3e4
778
+ });
779
+ } catch (e) {
780
+ const err = e;
781
+ output = err.stdout ?? "Heal command failed";
782
+ }
783
+ return {
784
+ content: [{ type: "text", text: JSON.stringify({ mode: "dry-run", output }, null, 2) }]
785
+ };
786
+ }
787
+ );
788
+ server2.tool(
789
+ "template_apply",
790
+ "Apply a morphism governance template to a target directory. Runs `morphism scaffold tier <tier>` with optional name.",
791
+ {
792
+ tier: z7.number().min(1).max(3).describe("Governance tier: 1=Web/TS, 2=Research/Python, 3=Governance/Monorepo"),
793
+ path: z7.string().describe("Target directory path to scaffold"),
794
+ name: z7.string().optional().describe("Project name override"),
795
+ ci: z7.boolean().optional().default(true).describe("Generate CI workflow"),
796
+ github: z7.boolean().optional().default(true).describe("Generate GitHub issue/PR templates")
797
+ },
798
+ async ({ tier, path: targetPath, name, ci, github }) => {
799
+ const { execFileSync: execFileSync2 } = await import("child_process");
800
+ const args = ["scaffold", "tier", String(tier)];
801
+ if (name) args.push("--name", name);
802
+ if (!ci) args.push("--no-ci");
803
+ if (!github) args.push("--no-github");
804
+ let output = "";
805
+ try {
806
+ output = execFileSync2("npx", ["morphism", ...args], {
807
+ encoding: "utf-8",
808
+ cwd: targetPath,
809
+ timeout: 3e4
810
+ });
811
+ } catch (e) {
812
+ const err = e;
813
+ return {
814
+ content: [{ type: "text", text: JSON.stringify({
815
+ success: false,
816
+ error: err.message ?? "scaffold failed",
817
+ output: err.stdout ?? ""
818
+ }, null, 2) }],
819
+ isError: true
820
+ };
821
+ }
822
+ return {
823
+ content: [{ type: "text", text: JSON.stringify({
824
+ success: true,
825
+ tier,
826
+ targetPath,
827
+ output: output.trim()
828
+ }, null, 2) }]
829
+ };
830
+ }
831
+ );
832
+ server2.tool(
833
+ "workspace_audit",
834
+ "Audit governance status across all repositories in a workspace directory. Scans for CLAUDE.md, AGENTS.md, SSOT.md, GUIDELINES.md, CI workflows, and .morphism/ config.",
835
+ {
836
+ workspace_path: z7.string().describe("Path to the workspace root containing repos"),
837
+ depth: z7.number().optional().default(2).describe("Directory depth to scan for repos")
838
+ },
839
+ async ({ workspace_path, depth }) => {
840
+ const { readdirSync, existsSync, statSync } = await import("fs");
841
+ const { join } = await import("path");
842
+ const governanceFiles = ["CLAUDE.md", "AGENTS.md", "SSOT.md", "GUIDELINES.md", "CONTRIBUTING.md"];
843
+ const repos = [];
844
+ function scanDir(dir, currentDepth) {
845
+ if (currentDepth > depth) return;
846
+ try {
847
+ const entries = readdirSync(dir, { withFileTypes: true });
848
+ const hasGit = entries.some((e) => e.name === ".git" && e.isDirectory());
849
+ if (hasGit) {
850
+ const governance = {};
851
+ for (const gf of governanceFiles) {
852
+ governance[gf] = existsSync(join(dir, gf));
853
+ }
854
+ const hasCi = existsSync(join(dir, ".github", "workflows"));
855
+ const hasMorphism = existsSync(join(dir, ".morphism"));
856
+ let tier = null;
857
+ if (existsSync(join(dir, "SSOT.md")) && hasMorphism) tier = 3;
858
+ else if (existsSync(join(dir, "pyproject.toml"))) tier = 2;
859
+ else if (existsSync(join(dir, "package.json")) || existsSync(join(dir, "tsconfig.json"))) tier = 1;
860
+ let score = 0;
861
+ if (governance["CLAUDE.md"]) score += 20;
862
+ if (governance["AGENTS.md"]) score += 20;
863
+ if (governance["GUIDELINES.md"]) score += 15;
864
+ if (governance["CONTRIBUTING.md"]) score += 10;
865
+ if (governance["SSOT.md"]) score += 15;
866
+ if (hasCi) score += 10;
867
+ if (hasMorphism) score += 10;
868
+ repos.push({
869
+ path: dir,
870
+ name: dir.split(/[\\/]/).pop() || dir,
871
+ tier,
872
+ governance,
873
+ hasCi,
874
+ hasMorphism,
875
+ score
876
+ });
877
+ return;
878
+ }
879
+ for (const entry of entries) {
880
+ if (!entry.isDirectory()) continue;
881
+ if (["node_modules", ".git", "__pycache__", "dist", "build", ".venv", "venv"].includes(entry.name)) continue;
882
+ scanDir(join(dir, entry.name), currentDepth + 1);
883
+ }
884
+ } catch {
885
+ }
886
+ }
887
+ scanDir(workspace_path, 0);
888
+ const totalScore = repos.length > 0 ? Math.round(repos.reduce((sum, r) => sum + r.score, 0) / repos.length) : 0;
889
+ const tierCounts = { 1: 0, 2: 0, 3: 0, unknown: 0 };
890
+ for (const r of repos) {
891
+ if (r.tier) tierCounts[r.tier]++;
892
+ else tierCounts.unknown++;
893
+ }
894
+ return {
895
+ content: [{ type: "text", text: JSON.stringify({
896
+ workspace: workspace_path,
897
+ repoCount: repos.length,
898
+ averageGovernanceScore: totalScore,
899
+ tierDistribution: tierCounts,
900
+ repos: repos.map((r) => ({
901
+ name: r.name,
902
+ tier: r.tier,
903
+ score: r.score,
904
+ files: Object.entries(r.governance).filter(([, v]) => v).map(([k]) => k),
905
+ ci: r.hasCi,
906
+ morphism: r.hasMorphism
907
+ }))
908
+ }, null, 2) }]
909
+ };
910
+ }
911
+ );
350
912
  return server2;
351
913
  }
352
914
 
package/package.json CHANGED
@@ -1,36 +1,37 @@
1
- {
2
- "name": "@morphism-systems/mcp-server",
3
- "version": "0.1.0",
4
- "description": "Governance MCP server — validation, maturity scoring, convergence and drift metrics",
5
- "type": "module",
6
- "main": "dist/index.js",
7
- "bin": {
8
- "morphism-mcp": "dist/index.js"
9
- },
10
- "scripts": {
11
- "build": "tsup src/index.ts --format esm --dts --clean",
12
- "typecheck": "tsc --noEmit",
13
- "test": "vitest run",
14
- "lint": "echo 'no lint configured'"
15
- },
16
- "keywords": [
17
- "mcp",
18
- "model-context-protocol",
19
- "ai-governance",
20
- "morphism",
21
- "governance-as-code"
22
- ],
23
- "license": "BUSL-1.1",
24
- "publishConfig": {
25
- "access": "public"
26
- },
27
- "dependencies": {
28
- "@modelcontextprotocol/sdk": "^1.12.1",
29
- "zod": "^3.24.0"
30
- },
31
- "devDependencies": {
32
- "tsup": "^8.4.0",
33
- "typescript": "^5.8.3",
34
- "vitest": "^3.0.0"
35
- }
36
- }
1
+ {
2
+ "name": "@morphism-systems/mcp-server",
3
+ "version": "0.1.3",
4
+ "description": "Governance MCP server — validation, maturity scoring, convergence and drift metrics",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "bin": {
9
+ "morphism-mcp": "dist/index.js"
10
+ },
11
+ "scripts": {
12
+ "build": "tsup src/index.ts --format esm --dts --clean",
13
+ "typecheck": "tsc --noEmit",
14
+ "test": "vitest run",
15
+ "lint": "echo 'no lint configured'"
16
+ },
17
+ "keywords": [
18
+ "mcp",
19
+ "model-context-protocol",
20
+ "ai-governance",
21
+ "morphism",
22
+ "governance-as-code"
23
+ ],
24
+ "license": "BUSL-1.1",
25
+ "publishConfig": {
26
+ "access": "public"
27
+ },
28
+ "dependencies": {
29
+ "@modelcontextprotocol/sdk": "^1.12.1",
30
+ "zod": "^3.24.0"
31
+ },
32
+ "devDependencies": {
33
+ "tsup": "^8.4.0",
34
+ "typescript": "^5.8.3",
35
+ "vitest": "^3.0.0"
36
+ }
37
+ }