@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.
- package/README.md +25 -2
- package/dist/index.js +580 -18
- 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-
|
|
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
|
-
|
|
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:
|
|
120
|
+
const { execSync: execSync2 } = await import("child_process");
|
|
120
121
|
try {
|
|
121
|
-
const result =
|
|
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 {
|
|
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
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
from
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
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:
|
|
294
|
+
const { execSync: execSync2 } = await import("child_process");
|
|
292
295
|
try {
|
|
293
|
-
const result =
|
|
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-
|
|
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.
|
|
4
|
-
"description": "Governance MCP server — validation, maturity scoring, convergence and drift metrics",
|
|
5
|
-
"type": "module",
|
|
6
|
-
"main": "dist/index.js",
|
|
7
|
-
"
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
"
|
|
13
|
-
"
|
|
14
|
-
"
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
"
|
|
19
|
-
"
|
|
20
|
-
"
|
|
21
|
-
"
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
"
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
"
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
"
|
|
34
|
-
"
|
|
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
|
+
}
|