mcp-nervous-system 1.3.0 → 1.4.0
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 +10 -16
- package/index.js +590 -7
- package/package.json +2 -2
- package/stdio.js +1 -1
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
7 mechanically enforced rules that prevent the most common failure modes when LLMs have access to real infrastructure: context loss, silent failures, file damage, goal drift, and overreach.
|
|
6
6
|
|
|
7
|
-
Built by [Arthur Palyan](https://www.levelsofself.com) at Palyan AI.
|
|
7
|
+
Built by [Arthur Palyan](https://www.levelsofself.com) at Palyan Family AI System. 12 tools including configuration drift detection and emergency kill switch. Battle-tested on an 11-member AI family running 28 processes 24/7 on a single VPS. 58+ violations logged, 0 bypassed.
|
|
8
8
|
|
|
9
9
|
## The Problem
|
|
10
10
|
|
|
@@ -60,17 +60,12 @@ Protocol: MCP 2024-11-05 (Streamable HTTP + SSE)
|
|
|
60
60
|
Authentication: None required
|
|
61
61
|
```
|
|
62
62
|
|
|
63
|
-
## NEW in v1.
|
|
63
|
+
## NEW in v1.4.0
|
|
64
64
|
|
|
65
|
-
**
|
|
66
|
-
|
|
65
|
+
**drift_audit** (free tier)
|
|
66
|
+
Configuration drift detection across 5 scopes: roles, versions, files, processes, and website. Scans source-of-truth files (family-roles.json, package.json, UNTOUCHABLE_FILES.txt) against all downstream references - HTML pages, JSON configs, markdown docs, and running PM2 processes. Change one file, drift_audit tells you everywhere else that needs updating. The closed loop that keeps your entire system consistent.
|
|
67
67
|
|
|
68
|
-
**
|
|
69
|
-
Decomposes user requests into numbered deliverables with confidence scoring. Below 80% confidence: the model should clarify before executing, not guess. Detects ambiguity, unresolved pronouns, hedging language, and implicit expectations. The 80% rule: understand first, execute second.
|
|
70
|
-
|
|
71
|
-
These tools work together. parse_user_intent breaks down what the person wants. classify_task_complexity routes each piece to the right model. The Nervous System enforces the rules while execution happens.
|
|
72
|
-
|
|
73
|
-
**Positioning: Auto mode (launching March 12) decides what Claude CAN do. The Nervous System governs HOW it behaves while doing it.**
|
|
68
|
+
**Positioning: Auto mode decides what Claude CAN do. The Nervous System governs HOW it behaves while doing it.**
|
|
74
69
|
|
|
75
70
|
## The 7 Rules
|
|
76
71
|
|
|
@@ -84,7 +79,7 @@ These tools work together. parse_user_intent breaks down what the person wants.
|
|
|
84
79
|
| 6 | **Ask Before Touching** | Unauthorized changes. Logic changes need human approval. |
|
|
85
80
|
| 7 | **Hand Off** | Context loss. Written handoffs every 3-4 exchanges. |
|
|
86
81
|
|
|
87
|
-
## MCP Tools (
|
|
82
|
+
## MCP Tools (12)
|
|
88
83
|
|
|
89
84
|
| Tool | Description |
|
|
90
85
|
|------|------------|
|
|
@@ -99,9 +94,7 @@ These tools work together. parse_user_intent breaks down what the person wants.
|
|
|
99
94
|
| `emergency_kill_switch` | Emergency shutdown of all PM2 processes. Requires kill switch secret. Logs to tamper-evident audit trail |
|
|
100
95
|
| `verify_audit_chain` | Walks the SHA-256 hash-chained audit log and verifies every entry. Returns chain integrity status |
|
|
101
96
|
| `dispatch_to_llm` | Spawns a background LLM agent to handle a task. Checks RAM, enforces max 2 concurrent dispatches |
|
|
102
|
-
| `
|
|
103
|
-
| `parse_user_intent` | Decomposes requests into numbered deliverables with confidence scoring. 80% gate |
|
|
104
|
-
| `get_positioning` | Returns competitive positioning and differentiation messaging |
|
|
97
|
+
| `drift_audit` | Configuration drift detection across roles, versions, files, processes, and website. Finds stale values everywhere. |
|
|
105
98
|
|
|
106
99
|
## Kill Switch
|
|
107
100
|
|
|
@@ -134,16 +127,17 @@ The Nervous System provides practical compliance tools for the EU AI Act. See th
|
|
|
134
127
|
|
|
135
128
|
https://api.100levelup.com/family/eu-ai-act.html
|
|
136
129
|
|
|
137
|
-
## Resources (
|
|
130
|
+
## Resources (5)
|
|
138
131
|
|
|
139
132
|
- `nervous-system://framework` - The complete framework
|
|
140
133
|
- `nervous-system://quick-start` - Quick start guide
|
|
141
134
|
- `nervous-system://rules` - The 7 core rules
|
|
142
135
|
- `nervous-system://templates` - Templates for handoffs, worklogs, preflight
|
|
136
|
+
- `nervous-system://drift-audit` - Configuration drift detection
|
|
143
137
|
|
|
144
138
|
## Production Stats
|
|
145
139
|
|
|
146
|
-
From the live Palyan AI deployment (Feb 28 - Mar 5, 2026):
|
|
140
|
+
From the live Palyan Family AI System deployment (Feb 28 - Mar 5, 2026):
|
|
147
141
|
|
|
148
142
|
- **58+** violations caught
|
|
149
143
|
- **29** edits blocked by preflight
|
package/index.js
CHANGED
|
@@ -129,7 +129,7 @@ const MCP_VERSION = '2024-11-05';
|
|
|
129
129
|
// Server info
|
|
130
130
|
const SERVER_INFO = {
|
|
131
131
|
name: 'nervous-system',
|
|
132
|
-
version: '1.
|
|
132
|
+
version: '1.4.0'
|
|
133
133
|
};
|
|
134
134
|
|
|
135
135
|
// ============================================================
|
|
@@ -138,7 +138,7 @@ const SERVER_INFO = {
|
|
|
138
138
|
|
|
139
139
|
const FRAMEWORK = {
|
|
140
140
|
name: 'The Nervous System',
|
|
141
|
-
version: '1.
|
|
141
|
+
version: '1.4.0',
|
|
142
142
|
author: 'Arthur Palyan',
|
|
143
143
|
tagline: 'Anthropic built the brain. Arthur built the nervous system that keeps it from hurting itself.',
|
|
144
144
|
problem: 'LLMs lose context between sessions, loop on problems instead of dispatching, silently fail without progress notes, edit protected files, drift from the real problem, and solve instead of asking.',
|
|
@@ -600,6 +600,22 @@ const TOOLS = [
|
|
|
600
600
|
},
|
|
601
601
|
required: ['task']
|
|
602
602
|
}
|
|
603
|
+
},
|
|
604
|
+
// NEW: Drift Audit
|
|
605
|
+
{
|
|
606
|
+
name: 'drift_audit',
|
|
607
|
+
annotations: { title: 'Configuration Drift Audit', readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
608
|
+
description: 'Scans for configuration drift - finds files, docs, and configs that reference outdated values. Detects when a file is renamed but references are not updated, when roles change but downstream docs still show old values, or when running processes do not match documentation.',
|
|
609
|
+
inputSchema: {
|
|
610
|
+
type: 'object',
|
|
611
|
+
properties: {
|
|
612
|
+
scope: {
|
|
613
|
+
type: 'string',
|
|
614
|
+
enum: ['full', 'roles', 'versions', 'files', 'processes', 'website'],
|
|
615
|
+
description: 'What to audit. full=everything, roles=family role consistency, versions=NS version numbers, files=file reference integrity, processes=PM2 vs docs, website=HTML pages and configs for stale values'
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
}
|
|
603
619
|
}
|
|
604
620
|
];
|
|
605
621
|
|
|
@@ -608,9 +624,566 @@ const RESOURCES = [
|
|
|
608
624
|
{ uri: 'nervous-system://framework', name: 'The Nervous System Framework', description: 'Complete behavioral enforcement framework for LLM management', mimeType: 'text/plain' },
|
|
609
625
|
{ uri: 'nervous-system://quick-start', name: 'Quick Start Guide', description: 'How to implement the nervous system in your own LLM deployment', mimeType: 'text/plain' },
|
|
610
626
|
{ uri: 'nervous-system://rules', name: 'The 7 Core Rules', description: 'All 7 behavioral rules with explanations and enforcement', mimeType: 'text/plain' },
|
|
611
|
-
{ uri: 'nervous-system://templates', name: 'Templates', description: 'Ready-to-use templates for handoffs, worklogs, preflight, and untouchable lists', mimeType: 'text/plain' }
|
|
627
|
+
{ uri: 'nervous-system://templates', name: 'Templates', description: 'Ready-to-use templates for handoffs, worklogs, preflight, and untouchable lists', mimeType: 'text/plain' },
|
|
628
|
+
{ uri: 'nervous-system://drift-audit', name: 'Drift Audit', description: 'Configuration drift detection - checks roles, versions, file references, and running processes against source-of-truth files', mimeType: 'text/plain' }
|
|
612
629
|
];
|
|
613
630
|
|
|
631
|
+
// ============================================================
|
|
632
|
+
// DRIFT AUDIT ENGINE
|
|
633
|
+
// ============================================================
|
|
634
|
+
|
|
635
|
+
const { execSync } = require('child_process');
|
|
636
|
+
|
|
637
|
+
function safeReadJSON(filePath) {
|
|
638
|
+
try {
|
|
639
|
+
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
640
|
+
} catch (e) {
|
|
641
|
+
return null;
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
function safeReadFile(filePath) {
|
|
646
|
+
try {
|
|
647
|
+
return fs.readFileSync(filePath, 'utf8');
|
|
648
|
+
} catch (e) {
|
|
649
|
+
return null;
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
function auditRoles() {
|
|
654
|
+
const drifts = [];
|
|
655
|
+
let cleanChecks = 0;
|
|
656
|
+
const rolesFile = '/root/family-data/family-roles.json';
|
|
657
|
+
const roles = safeReadJSON(rolesFile);
|
|
658
|
+
if (!roles || !roles.members) {
|
|
659
|
+
drifts.push({ type: 'missing_source', source: rolesFile, target: '', field: '', expected: 'valid JSON with members array', found: 'missing or invalid' });
|
|
660
|
+
return { drifts, cleanChecks };
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
const sourceRoles = {};
|
|
664
|
+
for (const m of roles.members) {
|
|
665
|
+
sourceRoles[m.id] = { name: m.name, aka: m.aka, role: m.role };
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
// Check family-status.json
|
|
669
|
+
const statusFile = '/root/family-data/family-status.json';
|
|
670
|
+
const status = safeReadJSON(statusFile);
|
|
671
|
+
if (status && status.members) {
|
|
672
|
+
for (const m of status.members) {
|
|
673
|
+
const src = sourceRoles[m.id];
|
|
674
|
+
if (!src) continue;
|
|
675
|
+
if (m.aka && m.aka !== src.aka) {
|
|
676
|
+
drifts.push({ type: 'role_mismatch', source: 'family-roles.json', target: 'family-status.json', field: `${m.id}.aka`, expected: src.aka, found: m.aka });
|
|
677
|
+
} else { cleanChecks++; }
|
|
678
|
+
if (m.role && m.role !== src.role) {
|
|
679
|
+
drifts.push({ type: 'role_mismatch', source: 'family-roles.json', target: 'family-status.json', field: `${m.id}.role`, expected: src.role, found: m.role });
|
|
680
|
+
} else { cleanChecks++; }
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
// Check system-config.json
|
|
685
|
+
const configFile = '/root/family-data/system-config.json';
|
|
686
|
+
const config = safeReadJSON(configFile);
|
|
687
|
+
if (config && config.family_members) {
|
|
688
|
+
for (const m of config.family_members) {
|
|
689
|
+
const src = sourceRoles[m.id];
|
|
690
|
+
if (!src) continue;
|
|
691
|
+
if (m.aka && m.aka !== src.aka) {
|
|
692
|
+
drifts.push({ type: 'role_mismatch', source: 'family-roles.json', target: 'system-config.json', field: `${m.id}.aka`, expected: src.aka, found: m.aka });
|
|
693
|
+
} else { cleanChecks++; }
|
|
694
|
+
if (m.role && m.role !== src.role) {
|
|
695
|
+
drifts.push({ type: 'role_mismatch', source: 'family-roles.json', target: 'system-config.json', field: `${m.id}.role`, expected: src.role, found: m.role });
|
|
696
|
+
} else { cleanChecks++; }
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
// Check family-guide.json
|
|
701
|
+
const guideFile = '/root/family-data/family-guide.json';
|
|
702
|
+
const guide = safeReadJSON(guideFile);
|
|
703
|
+
if (guide && guide.members) {
|
|
704
|
+
for (const m of guide.members) {
|
|
705
|
+
const src = sourceRoles[m.id];
|
|
706
|
+
if (!src) continue;
|
|
707
|
+
if (m.aka && m.aka !== src.aka) {
|
|
708
|
+
drifts.push({ type: 'role_mismatch', source: 'family-roles.json', target: 'family-guide.json', field: `${m.id}.aka`, expected: src.aka, found: m.aka });
|
|
709
|
+
} else { cleanChecks++; }
|
|
710
|
+
if (m.role && m.role !== src.role) {
|
|
711
|
+
drifts.push({ type: 'role_mismatch', source: 'family-roles.json', target: 'family-guide.json', field: `${m.id}.role`, expected: src.role, found: m.role });
|
|
712
|
+
} else { cleanChecks++; }
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
// Check HTML files for role references
|
|
717
|
+
const htmlFiles = [
|
|
718
|
+
{ path: '/root/family-home/index.html', name: 'index.html' },
|
|
719
|
+
{ path: '/root/family-home/explorer.html', name: 'explorer.html' },
|
|
720
|
+
{ path: '/root/family-home/meet.html', name: 'meet.html' }
|
|
721
|
+
];
|
|
722
|
+
for (const hf of htmlFiles) {
|
|
723
|
+
const content = safeReadFile(hf.path);
|
|
724
|
+
if (!content) continue;
|
|
725
|
+
for (const [id, src] of Object.entries(sourceRoles)) {
|
|
726
|
+
if (content.includes(src.name)) { cleanChecks++; }
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
// Check mcp-ops-server.js
|
|
731
|
+
const opsContent = safeReadFile('/root/mcp-ops-server.js');
|
|
732
|
+
if (opsContent) {
|
|
733
|
+
for (const [id, src] of Object.entries(sourceRoles)) {
|
|
734
|
+
if (opsContent.includes(`"${src.aka}"`) || opsContent.includes(`'${src.aka}'`)) {
|
|
735
|
+
cleanChecks++;
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
return { drifts, cleanChecks };
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
function auditVersions() {
|
|
744
|
+
const drifts = [];
|
|
745
|
+
let cleanChecks = 0;
|
|
746
|
+
const pkgFile = '/root/github-repos/mcp-nervous-system/package.json';
|
|
747
|
+
const pkg = safeReadJSON(pkgFile);
|
|
748
|
+
const expectedVersion = pkg ? pkg.version : null;
|
|
749
|
+
if (!expectedVersion) {
|
|
750
|
+
drifts.push({ type: 'missing_source', source: pkgFile, target: '', field: 'version', expected: 'valid version', found: 'missing' });
|
|
751
|
+
return { drifts, cleanChecks };
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
// Check SERVER_INFO.version and FRAMEWORK.version in index.js
|
|
755
|
+
const indexContent = safeReadFile('/root/github-repos/mcp-nervous-system/index.js');
|
|
756
|
+
if (indexContent) {
|
|
757
|
+
const siMatch = indexContent.match(/SERVER_INFO\s*=\s*\{[^}]*version:\s*'([^']+)'/);
|
|
758
|
+
if (siMatch) {
|
|
759
|
+
if (siMatch[1] !== expectedVersion) {
|
|
760
|
+
drifts.push({ type: 'version_mismatch', source: 'package.json', target: 'index.js SERVER_INFO', field: 'version', expected: expectedVersion, found: siMatch[1] });
|
|
761
|
+
} else { cleanChecks++; }
|
|
762
|
+
}
|
|
763
|
+
const fwMatch = indexContent.match(/FRAMEWORK\s*=\s*\{[^}]*version:\s*'([^']+)'/);
|
|
764
|
+
if (fwMatch) {
|
|
765
|
+
if (fwMatch[1] !== expectedVersion) {
|
|
766
|
+
drifts.push({ type: 'version_mismatch', source: 'package.json', target: 'index.js FRAMEWORK', field: 'version', expected: expectedVersion, found: fwMatch[1] });
|
|
767
|
+
} else { cleanChecks++; }
|
|
768
|
+
}
|
|
769
|
+
// Check health endpoint version
|
|
770
|
+
const healthMatch = indexContent.match(/version:\s*'([^']+)'.*?service:\s*'nervous-system/);
|
|
771
|
+
if (!healthMatch) {
|
|
772
|
+
const healthMatch2 = indexContent.match(/service:\s*'nervous-system-mcp',\s*version:\s*'([^']+)'/);
|
|
773
|
+
if (healthMatch2) {
|
|
774
|
+
if (healthMatch2[1] !== expectedVersion) {
|
|
775
|
+
drifts.push({ type: 'version_mismatch', source: 'package.json', target: 'index.js health endpoint', field: 'version', expected: expectedVersion, found: healthMatch2[1] });
|
|
776
|
+
} else { cleanChecks++; }
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
// Check startup log version
|
|
780
|
+
const startupMatch = indexContent.match(/Nervous System v([0-9.]+) running/);
|
|
781
|
+
if (startupMatch) {
|
|
782
|
+
if (startupMatch[1] !== expectedVersion) {
|
|
783
|
+
drifts.push({ type: 'version_mismatch', source: 'package.json', target: 'index.js startup log', field: 'version', expected: expectedVersion, found: startupMatch[1] });
|
|
784
|
+
} else { cleanChecks++; }
|
|
785
|
+
}
|
|
786
|
+
// Check root endpoint version
|
|
787
|
+
const rootMatch = indexContent.match(/name:\s*'The Nervous System MCP Server'[\s\S]*?version:\s*'([^']+)'/);
|
|
788
|
+
if (rootMatch) {
|
|
789
|
+
if (rootMatch[1] !== expectedVersion) {
|
|
790
|
+
drifts.push({ type: 'version_mismatch', source: 'package.json', target: 'index.js root endpoint', field: 'version', expected: expectedVersion, found: rootMatch[1] });
|
|
791
|
+
} else { cleanChecks++; }
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
// Check BUSINESS_BUILDER.md
|
|
796
|
+
const bbContent = safeReadFile('/root/family-data/BUSINESS_BUILDER.md');
|
|
797
|
+
if (bbContent) {
|
|
798
|
+
const bbMatch = bbContent.match(/[Nn]ervous [Ss]ystem.*?v?(\d+\.\d+\.\d+)/);
|
|
799
|
+
if (bbMatch && bbMatch[1] !== expectedVersion) {
|
|
800
|
+
drifts.push({ type: 'version_mismatch', source: 'package.json', target: 'BUSINESS_BUILDER.md', field: 'ns_version', expected: expectedVersion, found: bbMatch[1] });
|
|
801
|
+
} else if (bbMatch) { cleanChecks++; }
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
// Check gateway.html
|
|
805
|
+
const gwContent = safeReadFile('/root/family-home/gateway.html');
|
|
806
|
+
if (gwContent) {
|
|
807
|
+
const gwMatch = gwContent.match(/[Vv]ersion[:\s]*v?(\d+\.\d+\.\d+)/);
|
|
808
|
+
if (gwMatch && gwMatch[1] !== expectedVersion) {
|
|
809
|
+
drifts.push({ type: 'version_mismatch', source: 'package.json', target: 'gateway.html', field: 'version', expected: expectedVersion, found: gwMatch[1] });
|
|
810
|
+
} else if (gwMatch) { cleanChecks++; }
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
// Check README.md
|
|
814
|
+
const readmeContent = safeReadFile('/root/github-repos/mcp-nervous-system/README.md');
|
|
815
|
+
if (readmeContent) {
|
|
816
|
+
const rmMatch = readmeContent.match(/[Vv]ersion[:\s]*v?(\d+\.\d+\.\d+)/);
|
|
817
|
+
if (rmMatch && rmMatch[1] !== expectedVersion) {
|
|
818
|
+
drifts.push({ type: 'version_mismatch', source: 'package.json', target: 'README.md', field: 'version', expected: expectedVersion, found: rmMatch[1] });
|
|
819
|
+
} else if (rmMatch) { cleanChecks++; }
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
// Check family-roles.json stats
|
|
823
|
+
const roles = safeReadJSON('/root/family-data/family-roles.json');
|
|
824
|
+
if (roles && roles.stats) {
|
|
825
|
+
if (roles.stats.ns_version && roles.stats.ns_version !== expectedVersion) {
|
|
826
|
+
drifts.push({ type: 'version_mismatch', source: 'package.json', target: 'family-roles.json', field: 'stats.ns_version', expected: expectedVersion, found: roles.stats.ns_version });
|
|
827
|
+
} else if (roles.stats.ns_version) { cleanChecks++; }
|
|
828
|
+
|
|
829
|
+
// Check tool count
|
|
830
|
+
const actualToolCount = TOOLS.length;
|
|
831
|
+
if (roles.stats.ns_tools && roles.stats.ns_tools !== actualToolCount) {
|
|
832
|
+
drifts.push({ type: 'tool_count_mismatch', source: 'TOOLS array', target: 'family-roles.json', field: 'stats.ns_tools', expected: String(actualToolCount), found: String(roles.stats.ns_tools) });
|
|
833
|
+
} else if (roles.stats.ns_tools) { cleanChecks++; }
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
return { drifts, cleanChecks };
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
function auditFiles() {
|
|
840
|
+
const drifts = [];
|
|
841
|
+
let cleanChecks = 0;
|
|
842
|
+
|
|
843
|
+
// Check UNTOUCHABLE_FILES.txt - verify each file exists
|
|
844
|
+
const untouchableContent = safeReadFile('/root/family-data/UNTOUCHABLE_FILES.txt');
|
|
845
|
+
if (untouchableContent) {
|
|
846
|
+
const lines = untouchableContent.split('\n').map(l => l.trim()).filter(l => l && !l.startsWith('#'));
|
|
847
|
+
for (const filePath of lines) {
|
|
848
|
+
if (fs.existsSync(filePath)) {
|
|
849
|
+
cleanChecks++;
|
|
850
|
+
} else {
|
|
851
|
+
drifts.push({ type: 'missing_file', source: 'UNTOUCHABLE_FILES.txt', target: filePath, field: 'exists', expected: 'true', found: 'false' });
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
// Check LLM_STARTUP.md and BUSINESS_BUILDER.md for file references
|
|
857
|
+
const docsToCheck = [
|
|
858
|
+
{ path: '/root/family-data/LLM_STARTUP.md', name: 'LLM_STARTUP.md' },
|
|
859
|
+
{ path: '/root/family-data/BUSINESS_BUILDER.md', name: 'BUSINESS_BUILDER.md' }
|
|
860
|
+
];
|
|
861
|
+
|
|
862
|
+
// Get PM2 running scripts
|
|
863
|
+
let pm2Scripts = {};
|
|
864
|
+
try {
|
|
865
|
+
const pm2Output = execSync('pm2 jlist', { timeout: 10000 }).toString();
|
|
866
|
+
const pm2List = JSON.parse(pm2Output);
|
|
867
|
+
for (const proc of pm2List) {
|
|
868
|
+
pm2Scripts[proc.name] = proc.pm2_env ? proc.pm2_env.pm_exec_path : (proc.script || '');
|
|
869
|
+
}
|
|
870
|
+
} catch (e) {}
|
|
871
|
+
|
|
872
|
+
for (const doc of docsToCheck) {
|
|
873
|
+
const content = safeReadFile(doc.path);
|
|
874
|
+
if (!content) continue;
|
|
875
|
+
// Look for .js file references
|
|
876
|
+
const jsRefs = content.match(/\/root\/[^\s)]+\.js/g) || [];
|
|
877
|
+
for (const ref of jsRefs) {
|
|
878
|
+
if (fs.existsSync(ref)) {
|
|
879
|
+
cleanChecks++;
|
|
880
|
+
// Check if PM2 is running something different
|
|
881
|
+
const basename = ref.split('/').pop();
|
|
882
|
+
for (const [procName, scriptPath] of Object.entries(pm2Scripts)) {
|
|
883
|
+
const procBasename = scriptPath.split('/').pop();
|
|
884
|
+
// If the doc references a versioned file like tamara-v5.js but PM2 runs tamara-v6.js
|
|
885
|
+
const refBase = basename.replace(/-v\d+/, '');
|
|
886
|
+
const procBase = procBasename.replace(/-v\d+/, '');
|
|
887
|
+
if (refBase === procBase && basename !== procBasename && ref !== scriptPath) {
|
|
888
|
+
drifts.push({ type: 'file_version_mismatch', source: doc.name, target: `PM2 process ${procName}`, field: refBase, expected: basename, found: procBasename });
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
} else {
|
|
892
|
+
drifts.push({ type: 'missing_file_ref', source: doc.name, target: ref, field: 'exists', expected: 'true', found: 'false' });
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
// Check system-config.json syntax_check_scripts
|
|
898
|
+
const config = safeReadJSON('/root/family-data/system-config.json');
|
|
899
|
+
if (config && config.syntax_check_scripts) {
|
|
900
|
+
for (const script of config.syntax_check_scripts) {
|
|
901
|
+
if (fs.existsSync(script)) {
|
|
902
|
+
cleanChecks++;
|
|
903
|
+
} else {
|
|
904
|
+
drifts.push({ type: 'missing_file', source: 'system-config.json syntax_check_scripts', target: script, field: 'exists', expected: 'true', found: 'false' });
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
return { drifts, cleanChecks };
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
function auditProcesses() {
|
|
913
|
+
const drifts = [];
|
|
914
|
+
let cleanChecks = 0;
|
|
915
|
+
|
|
916
|
+
let pm2Procs = [];
|
|
917
|
+
try {
|
|
918
|
+
const pm2Output = execSync('pm2 jlist', { timeout: 10000 }).toString();
|
|
919
|
+
pm2Procs = JSON.parse(pm2Output);
|
|
920
|
+
} catch (e) {
|
|
921
|
+
drifts.push({ type: 'pm2_error', source: 'pm2 jlist', target: '', field: '', expected: 'valid pm2 output', found: e.message });
|
|
922
|
+
return { drifts, cleanChecks };
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
const config = safeReadJSON('/root/family-data/system-config.json');
|
|
926
|
+
if (!config || !config.processes) {
|
|
927
|
+
// Try to compare against family-roles.json procs
|
|
928
|
+
const roles = safeReadJSON('/root/family-data/family-roles.json');
|
|
929
|
+
if (roles && roles.members) {
|
|
930
|
+
const expectedProcs = [];
|
|
931
|
+
for (const m of roles.members) {
|
|
932
|
+
if (m.procs) expectedProcs.push(...m.procs);
|
|
933
|
+
}
|
|
934
|
+
const runningNames = pm2Procs.map(p => p.name);
|
|
935
|
+
for (const ep of expectedProcs) {
|
|
936
|
+
if (runningNames.includes(ep)) {
|
|
937
|
+
cleanChecks++;
|
|
938
|
+
} else {
|
|
939
|
+
drifts.push({ type: 'missing_process', source: 'family-roles.json', target: 'pm2', field: ep, expected: 'running', found: 'not found in pm2' });
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
return { drifts, cleanChecks };
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
// Compare config.processes against pm2
|
|
947
|
+
const runningNames = pm2Procs.map(p => p.name);
|
|
948
|
+
if (Array.isArray(config.processes)) {
|
|
949
|
+
for (const ep of config.processes) {
|
|
950
|
+
const procName = typeof ep === 'string' ? ep : ep.name;
|
|
951
|
+
if (runningNames.includes(procName)) {
|
|
952
|
+
cleanChecks++;
|
|
953
|
+
} else {
|
|
954
|
+
drifts.push({ type: 'missing_process', source: 'system-config.json', target: 'pm2', field: procName, expected: 'running', found: 'not found in pm2' });
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
// Check script paths match
|
|
960
|
+
for (const proc of pm2Procs) {
|
|
961
|
+
const scriptPath = proc.pm2_env ? proc.pm2_env.pm_exec_path : '';
|
|
962
|
+
if (scriptPath && !fs.existsSync(scriptPath)) {
|
|
963
|
+
drifts.push({ type: 'broken_script_path', source: `pm2 process ${proc.name}`, target: scriptPath, field: 'exists', expected: 'true', found: 'false' });
|
|
964
|
+
} else if (scriptPath) {
|
|
965
|
+
cleanChecks++;
|
|
966
|
+
}
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
return { drifts, cleanChecks };
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
function auditWebsite() {
|
|
973
|
+
const drifts = [];
|
|
974
|
+
let cleanChecks = 0;
|
|
975
|
+
|
|
976
|
+
// Source of truth values
|
|
977
|
+
const pkgFile = '/root/github-repos/mcp-nervous-system/package.json';
|
|
978
|
+
const pkg = safeReadJSON(pkgFile);
|
|
979
|
+
const expectedVersion = pkg ? pkg.version : '1.4.0';
|
|
980
|
+
const actualToolCount = TOOLS.length;
|
|
981
|
+
const actualResourceCount = RESOURCES.length;
|
|
982
|
+
|
|
983
|
+
const roles = safeReadJSON('/root/family-data/family-roles.json');
|
|
984
|
+
const expectedMemberCount = roles && roles.stats ? roles.stats.member_count : 11;
|
|
985
|
+
const expectedProcessCount = roles && roles.stats ? roles.stats.process_count : 28;
|
|
986
|
+
|
|
987
|
+
// Count protected files (non-comment, non-blank lines starting with /)
|
|
988
|
+
const untouchableContent = safeReadFile('/root/family-data/UNTOUCHABLE_FILES.txt');
|
|
989
|
+
let protectedFileCount = 0;
|
|
990
|
+
if (untouchableContent) {
|
|
991
|
+
protectedFileCount = untouchableContent.split('\n').filter(l => l.trim() && !l.trim().startsWith('#') && l.trim().startsWith('/')).length;
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
// Get role names from family-roles.json
|
|
995
|
+
const roleNames = roles && roles.members ? roles.members.map(m => m.name) : [];
|
|
996
|
+
|
|
997
|
+
// 1. Check all .html files in /root/family-home/
|
|
998
|
+
const familyHomeDir = '/root/family-home/';
|
|
999
|
+
let htmlFiles = [];
|
|
1000
|
+
try {
|
|
1001
|
+
htmlFiles = fs.readdirSync(familyHomeDir).filter(f => f.endsWith('.html')).map(f => familyHomeDir + f);
|
|
1002
|
+
} catch (e) {}
|
|
1003
|
+
|
|
1004
|
+
for (const htmlFile of htmlFiles) {
|
|
1005
|
+
const content = safeReadFile(htmlFile);
|
|
1006
|
+
if (!content) continue;
|
|
1007
|
+
const fname = htmlFile.split('/').pop();
|
|
1008
|
+
|
|
1009
|
+
// Check for old version references (not matching expected)
|
|
1010
|
+
const versionMatches = content.match(/v(\d+\.\d+\.\d+)/g) || [];
|
|
1011
|
+
for (const vm of versionMatches) {
|
|
1012
|
+
const ver = vm.substring(1);
|
|
1013
|
+
if (ver !== expectedVersion && /^1\.\d+\.\d+$/.test(ver)) {
|
|
1014
|
+
drifts.push({ type: 'stale_version', source: 'package.json', target: fname, field: 'version', expected: expectedVersion, found: ver });
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
|
|
1018
|
+
// Check for stale tool count references
|
|
1019
|
+
const toolCountMatches = content.match(/(\d+)\s*(?:MCP\s+)?tools/gi) || [];
|
|
1020
|
+
for (const tcm of toolCountMatches) {
|
|
1021
|
+
const num = parseInt(tcm);
|
|
1022
|
+
if (num > 0 && num !== actualToolCount && num < 50) {
|
|
1023
|
+
drifts.push({ type: 'stale_tool_count', source: 'TOOLS array', target: fname, field: 'tool_count', expected: String(actualToolCount), found: String(num) });
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
// Check for stale agent/member count
|
|
1028
|
+
const agentMatches = content.match(/(\d+)\s*(?:AI\s+)?(?:family\s+)?(?:members|agents)/gi) || [];
|
|
1029
|
+
for (const am of agentMatches) {
|
|
1030
|
+
const num = parseInt(am);
|
|
1031
|
+
if (num > 0 && num !== expectedMemberCount && num < 50) {
|
|
1032
|
+
drifts.push({ type: 'stale_agent_count', source: 'family-roles.json', target: fname, field: 'member_count', expected: String(expectedMemberCount), found: String(num) });
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
// Check for stale protected file count
|
|
1037
|
+
const protMatches = content.match(/(\d+)\s*protected\s*files/gi) || [];
|
|
1038
|
+
for (const pm of protMatches) {
|
|
1039
|
+
const num = parseInt(pm);
|
|
1040
|
+
if (num > 0 && num !== protectedFileCount) {
|
|
1041
|
+
drifts.push({ type: 'stale_protected_count', source: 'UNTOUCHABLE_FILES.txt', target: fname, field: 'protected_files', expected: String(protectedFileCount), found: String(num) });
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
// Check for stale process count
|
|
1046
|
+
const procMatches = content.match(/(\d+)\s*(?:live\s+)?processes/gi) || [];
|
|
1047
|
+
for (const pcm of procMatches) {
|
|
1048
|
+
const num = parseInt(pcm);
|
|
1049
|
+
if (num > 0 && num !== expectedProcessCount && num < 100) {
|
|
1050
|
+
drifts.push({ type: 'stale_process_count', source: 'family-roles.json', target: fname, field: 'process_count', expected: String(expectedProcessCount), found: String(num) });
|
|
1051
|
+
}
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1054
|
+
// If no drifts found for this file, count as clean
|
|
1055
|
+
if (!drifts.some(d => d.target === fname)) {
|
|
1056
|
+
cleanChecks++;
|
|
1057
|
+
}
|
|
1058
|
+
}
|
|
1059
|
+
|
|
1060
|
+
// 2. Check family-guide.json
|
|
1061
|
+
const guide = safeReadJSON('/root/family-data/family-guide.json');
|
|
1062
|
+
if (guide) {
|
|
1063
|
+
const guideStr = JSON.stringify(guide);
|
|
1064
|
+
// Check version refs
|
|
1065
|
+
const guideVersions = guideStr.match(/v(\d+\.\d+\.\d+)/g) || [];
|
|
1066
|
+
for (const gv of guideVersions) {
|
|
1067
|
+
const ver = gv.substring(1);
|
|
1068
|
+
if (ver !== expectedVersion && /^1\.\d+\.\d+$/.test(ver)) {
|
|
1069
|
+
drifts.push({ type: 'stale_version', source: 'package.json', target: 'family-guide.json', field: 'version', expected: expectedVersion, found: ver });
|
|
1070
|
+
}
|
|
1071
|
+
}
|
|
1072
|
+
// Check tool count refs
|
|
1073
|
+
const guideToolMatches = guideStr.match(/(\d+)\s*(?:MCP\s+)?tools/gi) || [];
|
|
1074
|
+
for (const gtm of guideToolMatches) {
|
|
1075
|
+
const num = parseInt(gtm);
|
|
1076
|
+
if (num > 0 && num !== actualToolCount && num < 50) {
|
|
1077
|
+
drifts.push({ type: 'stale_tool_count', source: 'TOOLS array', target: 'family-guide.json', field: 'tool_count', expected: String(actualToolCount), found: String(num) });
|
|
1078
|
+
}
|
|
1079
|
+
}
|
|
1080
|
+
// Check for references to removed tools
|
|
1081
|
+
if (guideStr.includes('classify_task_complexity') || guideStr.includes('parse_user_intent')) {
|
|
1082
|
+
drifts.push({ type: 'stale_tool_reference', source: 'TOOLS array', target: 'family-guide.json', field: 'removed_tools', expected: 'drift_audit', found: 'classify_task_complexity/parse_user_intent' });
|
|
1083
|
+
}
|
|
1084
|
+
} else { cleanChecks++; }
|
|
1085
|
+
|
|
1086
|
+
// 3. Check mcp-stripe-checkout.js for version refs
|
|
1087
|
+
const checkoutContent = safeReadFile('/root/mcp-stripe-checkout.js');
|
|
1088
|
+
if (checkoutContent) {
|
|
1089
|
+
const checkoutVersions = checkoutContent.match(/v(\d+\.\d+\.\d+)/g) || [];
|
|
1090
|
+
for (const cv of checkoutVersions) {
|
|
1091
|
+
const ver = cv.substring(1);
|
|
1092
|
+
if (ver !== expectedVersion && /^1\.\d+\.\d+$/.test(ver)) {
|
|
1093
|
+
drifts.push({ type: 'stale_version', source: 'package.json', target: 'mcp-stripe-checkout.js', field: 'version', expected: expectedVersion, found: ver });
|
|
1094
|
+
}
|
|
1095
|
+
}
|
|
1096
|
+
if (!checkoutVersions.length) cleanChecks++;
|
|
1097
|
+
}
|
|
1098
|
+
|
|
1099
|
+
// 4. Check system-config.json for version/tool counts
|
|
1100
|
+
const sysConfig = safeReadJSON('/root/family-data/system-config.json');
|
|
1101
|
+
if (sysConfig) {
|
|
1102
|
+
const scStr = JSON.stringify(sysConfig);
|
|
1103
|
+
const scVersions = scStr.match(/v(\d+\.\d+\.\d+)/g) || [];
|
|
1104
|
+
for (const sv of scVersions) {
|
|
1105
|
+
const ver = sv.substring(1);
|
|
1106
|
+
if (ver !== expectedVersion && /^1\.\d+\.\d+$/.test(ver)) {
|
|
1107
|
+
drifts.push({ type: 'stale_version', source: 'package.json', target: 'system-config.json', field: 'version', expected: expectedVersion, found: ver });
|
|
1108
|
+
}
|
|
1109
|
+
}
|
|
1110
|
+
if (!scVersions.length) cleanChecks++;
|
|
1111
|
+
}
|
|
1112
|
+
|
|
1113
|
+
// 5. Check FREE_TOOLS in mcp-api-middleware.js match actual tool names
|
|
1114
|
+
const middlewareContent = safeReadFile('/root/mcp-api-middleware.js');
|
|
1115
|
+
if (middlewareContent) {
|
|
1116
|
+
const freeToolsMatch = middlewareContent.match(/'nervous-system':\s*\[([^\]]+)\]/);
|
|
1117
|
+
if (freeToolsMatch) {
|
|
1118
|
+
const freeToolNames = freeToolsMatch[1].match(/'([^']+)'/g);
|
|
1119
|
+
if (freeToolNames) {
|
|
1120
|
+
const actualToolNames = TOOLS.map(t => t.name);
|
|
1121
|
+
for (const ft of freeToolNames) {
|
|
1122
|
+
const toolName = ft.replace(/'/g, '');
|
|
1123
|
+
if (actualToolNames.includes(toolName)) {
|
|
1124
|
+
cleanChecks++;
|
|
1125
|
+
} else {
|
|
1126
|
+
drifts.push({ type: 'invalid_free_tool', source: 'TOOLS array', target: 'mcp-api-middleware.js', field: 'FREE_TOOLS', expected: 'valid tool name', found: toolName });
|
|
1127
|
+
}
|
|
1128
|
+
}
|
|
1129
|
+
}
|
|
1130
|
+
}
|
|
1131
|
+
}
|
|
1132
|
+
|
|
1133
|
+
// 6. Check sitemap.xml has all public pages
|
|
1134
|
+
const sitemapContent = safeReadFile('/root/family-home/sitemap.xml');
|
|
1135
|
+
if (sitemapContent && htmlFiles.length > 0) {
|
|
1136
|
+
const publicPages = htmlFiles.filter(f => {
|
|
1137
|
+
const name = f.split('/').pop();
|
|
1138
|
+
return !['404.html', 'arthur.html', 'aram-consent.html', 'explorer.html', 'checklist.html'].includes(name);
|
|
1139
|
+
});
|
|
1140
|
+
for (const page of publicPages) {
|
|
1141
|
+
const pageName = page.split('/').pop();
|
|
1142
|
+
if (pageName === 'index.html') {
|
|
1143
|
+
if (sitemapContent.includes('/family/')) cleanChecks++;
|
|
1144
|
+
} else {
|
|
1145
|
+
if (sitemapContent.includes(pageName)) {
|
|
1146
|
+
cleanChecks++;
|
|
1147
|
+
} else {
|
|
1148
|
+
drifts.push({ type: 'missing_from_sitemap', source: 'sitemap.xml', target: pageName, field: 'listed', expected: 'true', found: 'false' });
|
|
1149
|
+
}
|
|
1150
|
+
}
|
|
1151
|
+
}
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
return { drifts, cleanChecks };
|
|
1155
|
+
}
|
|
1156
|
+
|
|
1157
|
+
function runDriftAudit(scope) {
|
|
1158
|
+
const timestamp = new Date().toISOString();
|
|
1159
|
+
const allDrifts = [];
|
|
1160
|
+
let totalClean = 0;
|
|
1161
|
+
const scopes = scope === 'full' ? ['roles', 'versions', 'files', 'processes', 'website'] : [scope];
|
|
1162
|
+
|
|
1163
|
+
for (const s of scopes) {
|
|
1164
|
+
let result;
|
|
1165
|
+
switch (s) {
|
|
1166
|
+
case 'roles': result = auditRoles(); break;
|
|
1167
|
+
case 'versions': result = auditVersions(); break;
|
|
1168
|
+
case 'files': result = auditFiles(); break;
|
|
1169
|
+
case 'processes': result = auditProcesses(); break;
|
|
1170
|
+
case 'website': result = auditWebsite(); break;
|
|
1171
|
+
default: result = { drifts: [{ type: 'unknown_scope', source: '', target: '', field: s, expected: 'valid scope', found: 'unknown' }], cleanChecks: 0 };
|
|
1172
|
+
}
|
|
1173
|
+
allDrifts.push(...result.drifts);
|
|
1174
|
+
totalClean += result.cleanChecks;
|
|
1175
|
+
}
|
|
1176
|
+
|
|
1177
|
+
return {
|
|
1178
|
+
scope,
|
|
1179
|
+
timestamp,
|
|
1180
|
+
status: allDrifts.length === 0 ? 'clean' : 'drift_detected',
|
|
1181
|
+
drift_count: allDrifts.length,
|
|
1182
|
+
drifts: allDrifts,
|
|
1183
|
+
clean_checks: totalClean
|
|
1184
|
+
};
|
|
1185
|
+
}
|
|
1186
|
+
|
|
614
1187
|
// ============================================================
|
|
615
1188
|
// Handle tool calls
|
|
616
1189
|
// ============================================================
|
|
@@ -708,6 +1281,11 @@ function handleToolCall(name, args) {
|
|
|
708
1281
|
return dispatchToLLM(args.task, args.max_turns);
|
|
709
1282
|
}
|
|
710
1283
|
|
|
1284
|
+
case 'drift_audit': {
|
|
1285
|
+
const scope = args.scope || 'full';
|
|
1286
|
+
return runDriftAudit(scope);
|
|
1287
|
+
}
|
|
1288
|
+
|
|
711
1289
|
default:
|
|
712
1290
|
return { error: 'Unknown tool' };
|
|
713
1291
|
}
|
|
@@ -746,6 +1324,11 @@ ${FRAMEWORK.before_any_change.map(s => `- ${s}`).join('\n')}`;
|
|
|
746
1324
|
case 'nervous-system://templates':
|
|
747
1325
|
return `## SESSION HANDOFF TEMPLATE\n${SESSION_HANDOFF_TEMPLATE.template}\n\n---\n\n## WORKLOG FORMAT\n${WORKLOG_TEMPLATE.format}\n\n---\n\n## PREFLIGHT SCRIPT\n${PREFLIGHT_PATTERN.script_template}\n\n---\n\n## UNTOUCHABLE FILES TEMPLATE\n${PREFLIGHT_PATTERN.untouchable_template}`;
|
|
748
1326
|
|
|
1327
|
+
case 'nervous-system://drift-audit': {
|
|
1328
|
+
const result = runDriftAudit('full');
|
|
1329
|
+
return `## Drift Audit Report\nTimestamp: ${result.timestamp}\nStatus: ${result.status}\nDrifts found: ${result.drift_count}\nClean checks: ${result.clean_checks}\n\n${result.drifts.map(d => `- [${d.type}] ${d.source} -> ${d.target}: ${d.field} expected="${d.expected}" found="${d.found}"`).join('\n') || 'No drifts detected.'}`;
|
|
1330
|
+
}
|
|
1331
|
+
|
|
749
1332
|
default:
|
|
750
1333
|
return null;
|
|
751
1334
|
}
|
|
@@ -824,7 +1407,7 @@ const server = http.createServer((req, res) => {
|
|
|
824
1407
|
// Health check
|
|
825
1408
|
if (req.method === 'GET' && url.pathname === '/health') {
|
|
826
1409
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
827
|
-
res.end(JSON.stringify({ status: 'ok', service: 'nervous-system-mcp', version: '1.
|
|
1410
|
+
res.end(JSON.stringify({ status: 'ok', service: 'nervous-system-mcp', version: '1.4.0', protocol: MCP_VERSION }));
|
|
828
1411
|
return;
|
|
829
1412
|
}
|
|
830
1413
|
|
|
@@ -941,7 +1524,7 @@ const server = http.createServer((req, res) => {
|
|
|
941
1524
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
942
1525
|
res.end(JSON.stringify({
|
|
943
1526
|
name: 'The Nervous System MCP Server',
|
|
944
|
-
version: '1.
|
|
1527
|
+
version: '1.4.0',
|
|
945
1528
|
protocol: MCP_VERSION,
|
|
946
1529
|
description: 'LLM behavioral enforcement framework. 7 core rules, preflight checks, session handoffs, worklogs, violation logging, kill switch, hash-chained audit, and forced reflection cycles. Built by Arthur Palyan.',
|
|
947
1530
|
endpoints: {
|
|
@@ -963,8 +1546,8 @@ const server = http.createServer((req, res) => {
|
|
|
963
1546
|
migrateExistingViolations();
|
|
964
1547
|
|
|
965
1548
|
server.listen(PORT, '127.0.0.1', () => {
|
|
966
|
-
console.error(`[MCP Server] Nervous System v1.
|
|
1549
|
+
console.error(`[MCP Server] Nervous System v1.4.0 running on port ${PORT}`);
|
|
967
1550
|
console.error(`[MCP Server] SSE: /sse | HTTP: /mcp | Health: /health | Kill: POST /kill | Audit: GET /audit/verify | Dispatches: GET /dispatches`);
|
|
968
1551
|
console.error(`[MCP Server] Protocol: ${MCP_VERSION}`);
|
|
969
|
-
console.error(`[MCP Server] Tools: ${TOOLS.length} (including kill switch, audit chain, dispatch)`);
|
|
1552
|
+
console.error(`[MCP Server] Tools: ${TOOLS.length} (including kill switch, audit chain, dispatch, drift audit)`);
|
|
970
1553
|
});
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mcp-nervous-system",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "The Nervous System - LLM Behavioral Enforcement Framework. 7 mechanically enforced rules,
|
|
3
|
+
"version": "1.4.0",
|
|
4
|
+
"description": "The Nervous System - LLM Behavioral Enforcement Framework. 7 mechanically enforced rules, 12 tools including kill switch, audit chain, dispatch, and drift audit. MCP server for Claude Desktop, Claude Code, and any MCP-compatible client.",
|
|
5
5
|
"main": "server.js",
|
|
6
6
|
"bin": {
|
|
7
7
|
"mcp-nervous-system": "./stdio.js"
|
package/stdio.js
CHANGED
|
@@ -51,7 +51,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
51
51
|
return { content: [{ type: "text", text: JSON.stringify({ name: "The Nervous System", version: "1.1.1", description: "LLM Behavioral Enforcement Framework", rules: RULES, total_tools: 11, production_stats: { violations_caught: 56, rules_bypassed: 0, edits_blocked: 32, processes_monitored: 22 } }, null, 2) }] };
|
|
52
52
|
|
|
53
53
|
case "get_nervous_system_info":
|
|
54
|
-
return { content: [{ type: "text", text: JSON.stringify({ name: "The Nervous System", version: "1.1.1", author: "Arthur Palyan", company: "Palyan AI / Levels of Self LLC", website: "https://www.levelsofself.com", demo: "https://api.100levelup.com/family/arthur.html?guest=1", github: "https://github.com/levelsofself/mcp-nervous-system", npm: "https://www.npmjs.com/package/mcp-nervous-system", tools: 11, rules: 7, production_stats: { violations: 56, bypasses: 0, blocked_edits: 32, uptime_days: 25, monthly_cost: "$12" } }, null, 2) }] };
|
|
54
|
+
return { content: [{ type: "text", text: JSON.stringify({ name: "The Nervous System", version: "1.1.1", author: "Arthur Palyan", company: "Palyan Family AI System / Levels of Self LLC", website: "https://www.levelsofself.com", demo: "https://api.100levelup.com/family/arthur.html?guest=1", github: "https://github.com/levelsofself/mcp-nervous-system", npm: "https://www.npmjs.com/package/mcp-nervous-system", tools: 11, rules: 7, production_stats: { violations: 56, bypasses: 0, blocked_edits: 32, uptime_days: 25, monthly_cost: "$12" } }, null, 2) }] };
|
|
55
55
|
|
|
56
56
|
case "check_preflight":
|
|
57
57
|
const fp = args?.file_path || "unknown";
|