mcp-nervous-system 1.4.0 → 1.5.1
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 +2 -2
- package/README.md.bak.1772777593 +172 -0
- package/index.js +393 -7
- package/package.json +2 -2
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 Family AI System.
|
|
7
|
+
Built by [Arthur Palyan](https://www.levelsofself.com) at Palyan Family AI System. 15 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,7 +60,7 @@ 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.5.0
|
|
64
64
|
|
|
65
65
|
**drift_audit** (free tier)
|
|
66
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.
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
# The Nervous System
|
|
2
|
+
|
|
3
|
+
**LLM Behavioral Enforcement Framework**
|
|
4
|
+
|
|
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
|
+
|
|
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
|
+
|
|
9
|
+
## The Problem
|
|
10
|
+
|
|
11
|
+
When you give an LLM access to your file system, bash, and production infrastructure, it will eventually:
|
|
12
|
+
|
|
13
|
+
- Edit a file it shouldn't touch
|
|
14
|
+
- Lose context between sessions and start over
|
|
15
|
+
- Drift from the original objective during long tasks
|
|
16
|
+
- Fail silently when a session times out
|
|
17
|
+
- Make logic changes without asking
|
|
18
|
+
- Disappear into debug loops
|
|
19
|
+
|
|
20
|
+
The Nervous System solves all of these with rules enforced by external mechanisms the LLM cannot override.
|
|
21
|
+
|
|
22
|
+
## Install
|
|
23
|
+
|
|
24
|
+
### Claude Desktop
|
|
25
|
+
|
|
26
|
+
Add to your `claude_desktop_config.json`:
|
|
27
|
+
|
|
28
|
+
```json
|
|
29
|
+
{
|
|
30
|
+
"mcpServers": {
|
|
31
|
+
"nervous-system": {
|
|
32
|
+
"command": "npx",
|
|
33
|
+
"args": ["-y", "mcp-nervous-system"]
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### Claude Code
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
claude mcp add nervous-system npx mcp-nervous-system
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### Direct
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
npx mcp-nervous-system
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Server starts on port 3475 with SSE, HTTP, and health endpoints.
|
|
52
|
+
|
|
53
|
+
### Hosted (No Install)
|
|
54
|
+
|
|
55
|
+
The server is live and ready to use:
|
|
56
|
+
|
|
57
|
+
```
|
|
58
|
+
URL: https://api.100levelup.com/mcp-ns/
|
|
59
|
+
Protocol: MCP 2024-11-05 (Streamable HTTP + SSE)
|
|
60
|
+
Authentication: None required
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## NEW in v1.4.0
|
|
64
|
+
|
|
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
|
+
|
|
68
|
+
**Positioning: Auto mode decides what Claude CAN do. The Nervous System governs HOW it behaves while doing it.**
|
|
69
|
+
|
|
70
|
+
## The 7 Rules
|
|
71
|
+
|
|
72
|
+
| # | Rule | What It Prevents |
|
|
73
|
+
|---|------|-----------------|
|
|
74
|
+
| 1 | **Dispatch Don't Do** | Debug loops, rabbit holes. Tasks > 2 messages get dispatched. |
|
|
75
|
+
| 2 | **Untouchable** | File damage. Protected files mechanically blocked from editing. |
|
|
76
|
+
| 3 | **Write Progress** | Silent failures. Progress noted before each action. |
|
|
77
|
+
| 4 | **Step Back Every 4** | Goal drift. Forced reflection every 4 messages. |
|
|
78
|
+
| 5 | **Delegate and Return** | Invisible work. Background tasks reported immediately. |
|
|
79
|
+
| 6 | **Ask Before Touching** | Unauthorized changes. Logic changes need human approval. |
|
|
80
|
+
| 7 | **Hand Off** | Context loss. Written handoffs every 3-4 exchanges. |
|
|
81
|
+
|
|
82
|
+
## MCP Tools (12)
|
|
83
|
+
|
|
84
|
+
| Tool | Description |
|
|
85
|
+
|------|------------|
|
|
86
|
+
| `get_framework` | Complete framework: all rules, permission protocol, enforcement patterns |
|
|
87
|
+
| `guardrail_rules` | The 7 core rules with triggers, enforcement, and failure modes |
|
|
88
|
+
| `preflight_check` | File protection system: shell script blocks edits to protected files |
|
|
89
|
+
| `session_handoff` | Context preservation: templates for handoff documents |
|
|
90
|
+
| `worklog` | Progress documentation pattern |
|
|
91
|
+
| `violation_logging` | Audit trail: timestamp, type, context for every violation |
|
|
92
|
+
| `step_back_check` | Forced reflection system |
|
|
93
|
+
| `get_nervous_system_info` | System overview and operational stats |
|
|
94
|
+
| `emergency_kill_switch` | Emergency shutdown of all PM2 processes. Requires kill switch secret. Logs to tamper-evident audit trail |
|
|
95
|
+
| `verify_audit_chain` | Walks the SHA-256 hash-chained audit log and verifies every entry. Returns chain integrity status |
|
|
96
|
+
| `dispatch_to_llm` | Spawns a background LLM agent to handle a task. Checks RAM, enforces max 2 concurrent dispatches |
|
|
97
|
+
| `drift_audit` | Configuration drift detection across roles, versions, files, processes, and website. Finds stale values everywhere. |
|
|
98
|
+
|
|
99
|
+
## Kill Switch
|
|
100
|
+
|
|
101
|
+
The `emergency_kill_switch` tool provides an emergency shutdown capability. Send a POST request to `/kill` with the kill switch secret to immediately stop all PM2 processes. Every activation is logged to the tamper-evident audit trail with SHA-256 hash chaining, so kill switch events cannot be hidden or altered after the fact.
|
|
102
|
+
|
|
103
|
+
- Requires authentication (kill switch secret)
|
|
104
|
+
- Logs to hash-chained audit trail
|
|
105
|
+
- Returns confirmation with affected process count
|
|
106
|
+
|
|
107
|
+
## Tamper-Evident Audit Trail
|
|
108
|
+
|
|
109
|
+
Every guardrail violation, kill switch activation, and dispatch event is recorded in a SHA-256 hash-chained audit log. Each entry includes the hash of the previous entry, making it cryptographically impossible to alter or delete past records without breaking the chain.
|
|
110
|
+
|
|
111
|
+
- Use `verify_audit_chain` to walk the entire chain and verify integrity
|
|
112
|
+
- Returns: valid/invalid status, entry count, and break point if tampered
|
|
113
|
+
- 58+ violations logged, 0 bypassed, 0 chain breaks
|
|
114
|
+
|
|
115
|
+
## Dispatch to LLM
|
|
116
|
+
|
|
117
|
+
The `dispatch_to_llm` tool enables a brain + agents architecture. Instead of one LLM session doing everything, complex tasks get dispatched to background agents that run independently under the same 7 rules.
|
|
118
|
+
|
|
119
|
+
- Checks available RAM (requires 500MB+)
|
|
120
|
+
- Enforces max 2 concurrent dispatches
|
|
121
|
+
- Returns PID and log file path for monitoring
|
|
122
|
+
- Every dispatched agent runs under the same nervous system guardrails
|
|
123
|
+
|
|
124
|
+
## EU AI Act Compliance
|
|
125
|
+
|
|
126
|
+
The Nervous System provides practical compliance tools for the EU AI Act. See the full compliance page at:
|
|
127
|
+
|
|
128
|
+
https://api.100levelup.com/family/eu-ai-act.html
|
|
129
|
+
|
|
130
|
+
## Resources (5)
|
|
131
|
+
|
|
132
|
+
- `nervous-system://framework` - The complete framework
|
|
133
|
+
- `nervous-system://quick-start` - Quick start guide
|
|
134
|
+
- `nervous-system://rules` - The 7 core rules
|
|
135
|
+
- `nervous-system://templates` - Templates for handoffs, worklogs, preflight
|
|
136
|
+
- `nervous-system://drift-audit` - Configuration drift detection
|
|
137
|
+
|
|
138
|
+
## Production Stats
|
|
139
|
+
|
|
140
|
+
From the live Palyan Family AI System deployment (Feb 28 - Mar 5, 2026):
|
|
141
|
+
|
|
142
|
+
- **58+** violations caught
|
|
143
|
+
- **29** edits blocked by preflight
|
|
144
|
+
- **13** unique files protected
|
|
145
|
+
- **0** rules bypassed
|
|
146
|
+
- **28** processes monitored
|
|
147
|
+
- **7** days continuous operation
|
|
148
|
+
|
|
149
|
+
## Live Demo
|
|
150
|
+
|
|
151
|
+
Try it yourself (no login required):
|
|
152
|
+
|
|
153
|
+
- **[Interactive Demo](https://api.100levelup.com/family/arthur.html?guest=1)** - Talk to a governed LLM and try to break the rules
|
|
154
|
+
- **[Audit Dashboard](https://api.100levelup.com/family/audit.html)** - See real violation history with timeline
|
|
155
|
+
- **[System Status](https://api.100levelup.com/family/status.html)** - Live health checks
|
|
156
|
+
- **[API Documentation](https://api.100levelup.com/family/api-docs.html)** - Full tool and resource reference
|
|
157
|
+
- **[Case Study](https://api.100levelup.com/family/case-study.html)** - Production deployment data
|
|
158
|
+
- **[Plain English Rules](https://api.100levelup.com/family/rules-plain.html)** - For non-technical stakeholders
|
|
159
|
+
- **[Incident Response](https://api.100levelup.com/family/incident-response.html)** - Detection, containment, resolution
|
|
160
|
+
- **[EU AI Act Compliance](https://api.100levelup.com/family/eu-ai-act.html)** - Practical EU AI Act compliance tools
|
|
161
|
+
|
|
162
|
+
## Philosophy
|
|
163
|
+
|
|
164
|
+
> "LLMs can't reliably self-enforce promises. Guardrails work via preflight.sh, violation logs, and catching drift. Build enforcement systems, don't make promises."
|
|
165
|
+
|
|
166
|
+
If a guardrail can be violated by the thing it guards, it is not a guardrail. It is a suggestion.
|
|
167
|
+
|
|
168
|
+
Every rule in the Nervous System is enforced by an external mechanism: a shell script, a timer, a separate monitoring process. The LLM cannot override, circumvent, or ignore them.
|
|
169
|
+
|
|
170
|
+
## License
|
|
171
|
+
|
|
172
|
+
MIT
|
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.5.1'
|
|
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.5.1',
|
|
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.',
|
|
@@ -616,6 +616,39 @@ const TOOLS = [
|
|
|
616
616
|
}
|
|
617
617
|
}
|
|
618
618
|
}
|
|
619
|
+
},
|
|
620
|
+
// NEW: Security Audit
|
|
621
|
+
{
|
|
622
|
+
name: 'security_audit',
|
|
623
|
+
annotations: { title: 'Security Audit', readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
624
|
+
description: 'Scans for security vulnerabilities - hardcoded passwords in HTML, exposed API keys, missing TLS, missing rate limiting, exposed bot tokens, and insecure file permissions.',
|
|
625
|
+
inputSchema: { type: 'object', properties: {} }
|
|
626
|
+
},
|
|
627
|
+
// NEW: Auto Propagate
|
|
628
|
+
{
|
|
629
|
+
name: 'auto_propagate',
|
|
630
|
+
annotations: { title: 'Auto Propagate', readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
631
|
+
description: 'Runs all 3 propagators (role, version, content) and reports what changed vs what was already current. Ensures all downstream files match source-of-truth values.',
|
|
632
|
+
inputSchema: { type: 'object', properties: {} }
|
|
633
|
+
},
|
|
634
|
+
// NEW: Session Close
|
|
635
|
+
{
|
|
636
|
+
name: 'session_close',
|
|
637
|
+
annotations: { title: 'Session Close', readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false },
|
|
638
|
+
description: 'One-call session close. Runs drift_audit scope=full, then all 3 propagators. Returns combined results. The end-of-session button.',
|
|
639
|
+
inputSchema: { type: 'object', properties: {} }
|
|
640
|
+
},
|
|
641
|
+
// NEW: Page Health
|
|
642
|
+
{
|
|
643
|
+
name: 'page_health',
|
|
644
|
+
annotations: { title: 'Page Health Check', readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
|
|
645
|
+
description: 'Checks HTML pages in /root/family-home/ for broken links, broken fetches, missing mobile nav, placeholder text, missing images, stale links, missing OG tags, missing favicon, JS syntax issues, and empty sections. Catches what drift_audit does not - actual page functionality and UX issues.',
|
|
646
|
+
inputSchema: {
|
|
647
|
+
type: 'object',
|
|
648
|
+
properties: {
|
|
649
|
+
page: { type: 'string', description: "Specific page to check (e.g. 'gateway.html'), or 'all' for everything" }
|
|
650
|
+
}
|
|
651
|
+
}
|
|
619
652
|
}
|
|
620
653
|
];
|
|
621
654
|
|
|
@@ -976,7 +1009,7 @@ function auditWebsite() {
|
|
|
976
1009
|
// Source of truth values
|
|
977
1010
|
const pkgFile = '/root/github-repos/mcp-nervous-system/package.json';
|
|
978
1011
|
const pkg = safeReadJSON(pkgFile);
|
|
979
|
-
const expectedVersion = pkg ? pkg.version : '1.
|
|
1012
|
+
const expectedVersion = pkg ? pkg.version : '1.5.0';
|
|
980
1013
|
const actualToolCount = TOOLS.length;
|
|
981
1014
|
const actualResourceCount = RESOURCES.length;
|
|
982
1015
|
|
|
@@ -1184,6 +1217,336 @@ function runDriftAudit(scope) {
|
|
|
1184
1217
|
};
|
|
1185
1218
|
}
|
|
1186
1219
|
|
|
1220
|
+
// ============================================================
|
|
1221
|
+
// SECURITY AUDIT ENGINE
|
|
1222
|
+
// ============================================================
|
|
1223
|
+
|
|
1224
|
+
function runSecurityAudit() {
|
|
1225
|
+
const vulnerabilities = [];
|
|
1226
|
+
let checksPassed = 0;
|
|
1227
|
+
|
|
1228
|
+
// 1. Scan HTML files for hardcoded passwords/secrets
|
|
1229
|
+
const htmlDir = '/root/family-home/';
|
|
1230
|
+
const secretPatterns = [
|
|
1231
|
+
/Liarzhek1\$/g,
|
|
1232
|
+
/Levelsofself1\$/g,
|
|
1233
|
+
/\d{10}:AA[A-Za-z0-9_-]{30,}/g,
|
|
1234
|
+
/sk-ant-[a-zA-Z0-9_-]+/g,
|
|
1235
|
+
/npm_[A-Za-z0-9]{20,}/g,
|
|
1236
|
+
/ghp_[A-Za-z0-9]{20,}/g,
|
|
1237
|
+
/BOT_TOKEN\s*[:=]\s*['"][^'"]+['"]/gi,
|
|
1238
|
+
/PAPA_FULL\s*[:=]\s*['"][^'"]+['"]/gi,
|
|
1239
|
+
/PAPA_READ\s*[:=]\s*['"][^'"]+['"]/gi
|
|
1240
|
+
];
|
|
1241
|
+
try {
|
|
1242
|
+
const htmlFiles = fs.readdirSync(htmlDir).filter(f => f.endsWith('.html'));
|
|
1243
|
+
for (const hf of htmlFiles) {
|
|
1244
|
+
const content = safeReadFile(htmlDir + hf);
|
|
1245
|
+
if (!content) continue;
|
|
1246
|
+
let fileClean = true;
|
|
1247
|
+
for (const pat of secretPatterns) {
|
|
1248
|
+
pat.lastIndex = 0;
|
|
1249
|
+
const matches = content.match(pat);
|
|
1250
|
+
if (matches && matches.length > 0) {
|
|
1251
|
+
vulnerabilities.push({ type: 'hardcoded_secret', file: hf, pattern: pat.source, count: matches.length });
|
|
1252
|
+
fileClean = false;
|
|
1253
|
+
}
|
|
1254
|
+
}
|
|
1255
|
+
if (fileClean) checksPassed++;
|
|
1256
|
+
}
|
|
1257
|
+
} catch (e) {
|
|
1258
|
+
vulnerabilities.push({ type: 'scan_error', file: 'html_scan', detail: e.message });
|
|
1259
|
+
}
|
|
1260
|
+
|
|
1261
|
+
// 2. Check auth endpoints use server-side validation
|
|
1262
|
+
const serverContent = safeReadFile('/root/family-home/server.js');
|
|
1263
|
+
if (serverContent) {
|
|
1264
|
+
if (serverContent.includes('getSessionFromReq') || serverContent.includes('getAccessLevel')) {
|
|
1265
|
+
checksPassed++;
|
|
1266
|
+
} else {
|
|
1267
|
+
vulnerabilities.push({ type: 'missing_server_auth', file: 'server.js', detail: 'No server-side auth validation found' });
|
|
1268
|
+
}
|
|
1269
|
+
}
|
|
1270
|
+
|
|
1271
|
+
// 3. Verify GUEST_HIDDEN_FILES covers sensitive files
|
|
1272
|
+
if (serverContent) {
|
|
1273
|
+
const sensitiveFiles = ['api-credentials.json', 'family-roles.json', 'system-config.json', 'llm-providers.json'];
|
|
1274
|
+
for (const sf of sensitiveFiles) {
|
|
1275
|
+
if (serverContent.includes('"' + sf + '"') || serverContent.includes("'" + sf + "'")) {
|
|
1276
|
+
checksPassed++;
|
|
1277
|
+
} else {
|
|
1278
|
+
vulnerabilities.push({ type: 'unhidden_sensitive_file', file: sf, detail: 'Not in GUEST_HIDDEN_FILES' });
|
|
1279
|
+
}
|
|
1280
|
+
}
|
|
1281
|
+
}
|
|
1282
|
+
|
|
1283
|
+
// 4. Check Caddy TLS
|
|
1284
|
+
const caddyContent = safeReadFile('/etc/caddy/Caddyfile');
|
|
1285
|
+
if (caddyContent) {
|
|
1286
|
+
if (caddyContent.includes('tls') || caddyContent.includes('https://') || caddyContent.includes('100levelup.com')) {
|
|
1287
|
+
checksPassed++;
|
|
1288
|
+
} else {
|
|
1289
|
+
vulnerabilities.push({ type: 'missing_tls', file: 'Caddyfile', detail: 'No TLS configuration found' });
|
|
1290
|
+
}
|
|
1291
|
+
}
|
|
1292
|
+
|
|
1293
|
+
// 5. Check bridge rate limiting
|
|
1294
|
+
if (serverContent && serverContent.includes('rate') || fs.existsSync('/root/rate-limit.js') || fs.existsSync('/root/bridge-ratelimit.js')) {
|
|
1295
|
+
checksPassed++;
|
|
1296
|
+
} else {
|
|
1297
|
+
vulnerabilities.push({ type: 'missing_rate_limit', file: 'bridge', detail: 'No rate limiting found for bridge' });
|
|
1298
|
+
}
|
|
1299
|
+
|
|
1300
|
+
// 6. Check bot tokens not in public HTML
|
|
1301
|
+
try {
|
|
1302
|
+
const htmlFiles = fs.readdirSync(htmlDir).filter(f => f.endsWith('.html'));
|
|
1303
|
+
let tokenFound = false;
|
|
1304
|
+
for (const hf of htmlFiles) {
|
|
1305
|
+
const content = safeReadFile(htmlDir + hf);
|
|
1306
|
+
if (!content) continue;
|
|
1307
|
+
const tokenMatch = content.match(/\d{10}:AA[A-Za-z0-9_-]{30,}/g);
|
|
1308
|
+
if (tokenMatch) {
|
|
1309
|
+
vulnerabilities.push({ type: 'exposed_bot_token', file: hf, count: tokenMatch.length });
|
|
1310
|
+
tokenFound = true;
|
|
1311
|
+
}
|
|
1312
|
+
}
|
|
1313
|
+
if (!tokenFound) checksPassed++;
|
|
1314
|
+
} catch (e) {}
|
|
1315
|
+
|
|
1316
|
+
// 7. Check api-credentials.json permissions
|
|
1317
|
+
try {
|
|
1318
|
+
const credFile = '/root/family-data/api-credentials.json';
|
|
1319
|
+
if (fs.existsSync(credFile)) {
|
|
1320
|
+
const stats = fs.statSync(credFile);
|
|
1321
|
+
const mode = (stats.mode & 0o777).toString(8);
|
|
1322
|
+
if (mode === '600') {
|
|
1323
|
+
checksPassed++;
|
|
1324
|
+
} else {
|
|
1325
|
+
vulnerabilities.push({ type: 'insecure_permissions', file: 'api-credentials.json', detail: 'Mode is ' + mode + ', should be 600' });
|
|
1326
|
+
}
|
|
1327
|
+
} else {
|
|
1328
|
+
checksPassed++; // No creds file = no risk
|
|
1329
|
+
}
|
|
1330
|
+
} catch (e) {}
|
|
1331
|
+
|
|
1332
|
+
// 8. Check for Telegram tokens, API keys, npm tokens in family-home
|
|
1333
|
+
try {
|
|
1334
|
+
const allFiles = fs.readdirSync(htmlDir);
|
|
1335
|
+
const dangerPatterns = [
|
|
1336
|
+
{ name: 'telegram_token', pat: /\d{10}:AA[A-Za-z0-9_-]{30,}/g },
|
|
1337
|
+
{ name: 'anthropic_key', pat: /sk-ant-[a-zA-Z0-9_-]{20,}/g },
|
|
1338
|
+
{ name: 'npm_token', pat: /npm_[A-Za-z0-9]{20,}/g }
|
|
1339
|
+
];
|
|
1340
|
+
for (const f of allFiles) {
|
|
1341
|
+
if (f.endsWith('.html') || f.endsWith('.js') || f.endsWith('.json')) {
|
|
1342
|
+
const content = safeReadFile(htmlDir + f);
|
|
1343
|
+
if (!content) continue;
|
|
1344
|
+
for (const dp of dangerPatterns) {
|
|
1345
|
+
dp.pat.lastIndex = 0;
|
|
1346
|
+
const m = content.match(dp.pat);
|
|
1347
|
+
if (m) {
|
|
1348
|
+
vulnerabilities.push({ type: 'exposed_' + dp.name, file: f, count: m.length });
|
|
1349
|
+
}
|
|
1350
|
+
}
|
|
1351
|
+
}
|
|
1352
|
+
}
|
|
1353
|
+
checksPassed++;
|
|
1354
|
+
} catch (e) {}
|
|
1355
|
+
|
|
1356
|
+
return {
|
|
1357
|
+
status: vulnerabilities.length === 0 ? 'secure' : 'vulnerabilities_found',
|
|
1358
|
+
vulnerability_count: vulnerabilities.length,
|
|
1359
|
+
checks_passed: checksPassed,
|
|
1360
|
+
vulnerabilities
|
|
1361
|
+
};
|
|
1362
|
+
}
|
|
1363
|
+
|
|
1364
|
+
// ============================================================
|
|
1365
|
+
// AUTO PROPAGATE ENGINE
|
|
1366
|
+
// ============================================================
|
|
1367
|
+
|
|
1368
|
+
function runAutoPropagators() {
|
|
1369
|
+
const results = [];
|
|
1370
|
+
const scripts = [
|
|
1371
|
+
{ name: 'role', path: '/root/family-workers/role-propagator.js' },
|
|
1372
|
+
{ name: 'version', path: '/root/family-workers/version-propagator.js' },
|
|
1373
|
+
{ name: 'content', path: '/root/family-workers/content-propagator.js' }
|
|
1374
|
+
];
|
|
1375
|
+
for (const script of scripts) {
|
|
1376
|
+
try {
|
|
1377
|
+
const out = execSync('node ' + script.path + ' 2>&1', { timeout: 15000 }).toString();
|
|
1378
|
+
const current = out.indexOf('Already current') !== -1;
|
|
1379
|
+
results.push({ propagator: script.name, status: current ? 'current' : 'updated', output: out.trim().substring(0, 500) });
|
|
1380
|
+
} catch (e) {
|
|
1381
|
+
results.push({ propagator: script.name, status: 'error', error: e.message.substring(0, 200) });
|
|
1382
|
+
}
|
|
1383
|
+
}
|
|
1384
|
+
return {
|
|
1385
|
+
timestamp: new Date().toISOString(),
|
|
1386
|
+
propagators_run: results.length,
|
|
1387
|
+
results
|
|
1388
|
+
};
|
|
1389
|
+
}
|
|
1390
|
+
|
|
1391
|
+
// ============================================================
|
|
1392
|
+
// PAGE HEALTH ENGINE
|
|
1393
|
+
// ============================================================
|
|
1394
|
+
|
|
1395
|
+
function runPageHealth(page) {
|
|
1396
|
+
const FAMILY_HOME = '/root/family-home';
|
|
1397
|
+
const issues = [];
|
|
1398
|
+
|
|
1399
|
+
let htmlFiles;
|
|
1400
|
+
if (page && page !== 'all') {
|
|
1401
|
+
const target = page.endsWith('.html') ? page : page + '.html';
|
|
1402
|
+
const fullPath = `${FAMILY_HOME}/${target}`;
|
|
1403
|
+
if (!fs.existsSync(fullPath)) return { status: 'error', error: `File not found: ${target}` };
|
|
1404
|
+
htmlFiles = [target];
|
|
1405
|
+
} else {
|
|
1406
|
+
try {
|
|
1407
|
+
htmlFiles = fs.readdirSync(FAMILY_HOME).filter(f => f.endsWith('.html'));
|
|
1408
|
+
} catch (e) {
|
|
1409
|
+
return { status: 'error', error: `Cannot read ${FAMILY_HOME}: ${e.message}` };
|
|
1410
|
+
}
|
|
1411
|
+
}
|
|
1412
|
+
|
|
1413
|
+
for (const file of htmlFiles) {
|
|
1414
|
+
const filePath = `${FAMILY_HOME}/${file}`;
|
|
1415
|
+
let content;
|
|
1416
|
+
try { content = fs.readFileSync(filePath, 'utf8'); } catch (e) { continue; }
|
|
1417
|
+
|
|
1418
|
+
// 1. BROKEN LINKS - local href/src that don't exist
|
|
1419
|
+
const localRefs = [];
|
|
1420
|
+
const hrefMatches = content.matchAll(/(?:href|src)=["'](?!https?:\/\/|mailto:|tel:|#|javascript:|data:)([^"'#?]+)/gi);
|
|
1421
|
+
for (const m of hrefMatches) {
|
|
1422
|
+
const ref = m[1].trim();
|
|
1423
|
+
if (!ref || ref.startsWith('//') || ref.startsWith('{')) continue;
|
|
1424
|
+
localRefs.push(ref);
|
|
1425
|
+
}
|
|
1426
|
+
for (const ref of localRefs) {
|
|
1427
|
+
const resolved = ref.startsWith('/') ? ref : `${FAMILY_HOME}/${ref}`;
|
|
1428
|
+
if (!fs.existsSync(resolved)) {
|
|
1429
|
+
issues.push({ page: file, type: 'broken_link', detail: `Local reference "${ref}" - file not found` });
|
|
1430
|
+
}
|
|
1431
|
+
}
|
|
1432
|
+
|
|
1433
|
+
// 2. BROKEN FETCHES - check fetch() endpoints respond on localhost
|
|
1434
|
+
const fetchMatches = content.matchAll(/fetch\s*\(\s*['"`]([^'"`]+)['"`]/g);
|
|
1435
|
+
for (const m of fetchMatches) {
|
|
1436
|
+
const url = m[1];
|
|
1437
|
+
if (url.includes('${')) continue; // skip template literals with variables
|
|
1438
|
+
if (url.startsWith('http://localhost') || url.startsWith('http://127.0.0.1') || url.startsWith('/')) {
|
|
1439
|
+
let testUrl = url;
|
|
1440
|
+
if (url.startsWith('/')) {
|
|
1441
|
+
// Try to figure out port from context, default to common ports
|
|
1442
|
+
testUrl = `http://localhost:3000${url}`;
|
|
1443
|
+
}
|
|
1444
|
+
try {
|
|
1445
|
+
execSync(`curl -s -o /dev/null -w "%{http_code}" --max-time 2 "${testUrl}"`, { encoding: 'utf8', timeout: 3000 });
|
|
1446
|
+
} catch (e) {
|
|
1447
|
+
issues.push({ page: file, type: 'broken_fetch', detail: `fetch("${url}") - endpoint not responding` });
|
|
1448
|
+
}
|
|
1449
|
+
}
|
|
1450
|
+
}
|
|
1451
|
+
|
|
1452
|
+
// 3. MOBILE NAV - has nav-links but no hamburger/mobile menu
|
|
1453
|
+
const hasNavLinks = /class=["'][^"']*nav-links/i.test(content) || /<nav[\s>]/i.test(content);
|
|
1454
|
+
const hasHamburger = /hamburger|mobile-menu|menu-toggle|nav-toggle|burger/i.test(content) || /class=["'][^"']*toggle/i.test(content);
|
|
1455
|
+
if (hasNavLinks && !hasHamburger) {
|
|
1456
|
+
issues.push({ page: file, type: 'no_mobile_menu', detail: 'nav-links found but no hamburger toggle for mobile' });
|
|
1457
|
+
}
|
|
1458
|
+
|
|
1459
|
+
// 4. PLACEHOLDER TEXT - "--" as default in stat/value elements
|
|
1460
|
+
const placeholderMatches = content.matchAll(/id=["']([^"']+)["'][^>]*>\s*--\s*</g);
|
|
1461
|
+
for (const m of placeholderMatches) {
|
|
1462
|
+
issues.push({ page: file, type: 'placeholder_text', detail: `Element "${m[1]}" shows "--" (live data not loading)` });
|
|
1463
|
+
}
|
|
1464
|
+
// Also check spans/divs with class containing stat/value/count
|
|
1465
|
+
const statPlaceholders = content.matchAll(/class=["'][^"']*(?:stat|value|count|metric)[^"']*["'][^>]*>\s*--\s*</gi);
|
|
1466
|
+
for (const m of statPlaceholders) {
|
|
1467
|
+
issues.push({ page: file, type: 'placeholder_text', detail: 'Stat/value element shows "--" (live data not loading)' });
|
|
1468
|
+
}
|
|
1469
|
+
|
|
1470
|
+
// 5. MISSING IMAGES - img src referencing local files that don't exist
|
|
1471
|
+
const imgMatches = content.matchAll(/<img[^>]+src=["'](?!https?:\/\/|data:)([^"']+)["']/gi);
|
|
1472
|
+
for (const m of imgMatches) {
|
|
1473
|
+
const src = m[1].trim();
|
|
1474
|
+
if (!src || src.startsWith('{')) continue;
|
|
1475
|
+
const resolved = src.startsWith('/') ? src : `${FAMILY_HOME}/${src}`;
|
|
1476
|
+
if (!fs.existsSync(resolved)) {
|
|
1477
|
+
issues.push({ page: file, type: 'missing_image', detail: `Image "${src}" not found` });
|
|
1478
|
+
}
|
|
1479
|
+
}
|
|
1480
|
+
|
|
1481
|
+
// 6. STALE LINKS - external links to app stores, npm, github format check
|
|
1482
|
+
const extLinkMatches = content.matchAll(/href=["'](https?:\/\/[^"']+)["']/gi);
|
|
1483
|
+
for (const m of extLinkMatches) {
|
|
1484
|
+
const url = m[1];
|
|
1485
|
+
if (/play\.google\.com/.test(url) && !/play\.google\.com\/store\/apps\/details\?id=/.test(url)) {
|
|
1486
|
+
issues.push({ page: file, type: 'stale_link', detail: `Malformed Play Store link: ${url}` });
|
|
1487
|
+
}
|
|
1488
|
+
if (/apps\.apple\.com/.test(url) && !/apps\.apple\.com\/.*\/app\//.test(url)) {
|
|
1489
|
+
issues.push({ page: file, type: 'stale_link', detail: `Malformed App Store link: ${url}` });
|
|
1490
|
+
}
|
|
1491
|
+
if (/npmjs\.com/.test(url) && !/npmjs\.com\/package\//.test(url)) {
|
|
1492
|
+
issues.push({ page: file, type: 'stale_link', detail: `Malformed npm link: ${url}` });
|
|
1493
|
+
}
|
|
1494
|
+
if (/github\.com/.test(url) && /github\.com\/?["']/.test(url)) {
|
|
1495
|
+
issues.push({ page: file, type: 'stale_link', detail: `Generic GitHub link (no repo): ${url}` });
|
|
1496
|
+
}
|
|
1497
|
+
}
|
|
1498
|
+
|
|
1499
|
+
// 7. MISSING OG TAGS
|
|
1500
|
+
const ogTags = ['og:title', 'og:description', 'og:image'];
|
|
1501
|
+
for (const tag of ogTags) {
|
|
1502
|
+
if (!content.includes(`property="${tag}"`) && !content.includes(`property='${tag}'`)) {
|
|
1503
|
+
issues.push({ page: file, type: 'missing_og_tag', detail: `Missing ${tag} meta tag` });
|
|
1504
|
+
}
|
|
1505
|
+
}
|
|
1506
|
+
|
|
1507
|
+
// 8. MISSING FAVICON
|
|
1508
|
+
if (!/rel=["'](?:icon|shortcut icon)["']/i.test(content)) {
|
|
1509
|
+
issues.push({ page: file, type: 'missing_favicon', detail: 'No favicon link tag found' });
|
|
1510
|
+
}
|
|
1511
|
+
|
|
1512
|
+
// 9. CONSOLE ERRORS - JS syntax issues (unclosed tags, mismatched brackets)
|
|
1513
|
+
const scriptBlocks = content.matchAll(/<script[^>]*>([\s\S]*?)<\/script>/gi);
|
|
1514
|
+
for (const m of scriptBlocks) {
|
|
1515
|
+
const js = m[1].trim();
|
|
1516
|
+
if (!js) continue;
|
|
1517
|
+
// Check bracket balance
|
|
1518
|
+
let parens = 0, braces = 0, brackets = 0;
|
|
1519
|
+
for (const ch of js) {
|
|
1520
|
+
if (ch === '(') parens++;
|
|
1521
|
+
else if (ch === ')') parens--;
|
|
1522
|
+
else if (ch === '{') braces++;
|
|
1523
|
+
else if (ch === '}') braces--;
|
|
1524
|
+
else if (ch === '[') brackets++;
|
|
1525
|
+
else if (ch === ']') brackets--;
|
|
1526
|
+
}
|
|
1527
|
+
if (parens !== 0) issues.push({ page: file, type: 'js_syntax', detail: `Mismatched parentheses in script block (balance: ${parens})` });
|
|
1528
|
+
if (braces !== 0) issues.push({ page: file, type: 'js_syntax', detail: `Mismatched braces in script block (balance: ${braces})` });
|
|
1529
|
+
if (brackets !== 0) issues.push({ page: file, type: 'js_syntax', detail: `Mismatched brackets in script block (balance: ${brackets})` });
|
|
1530
|
+
}
|
|
1531
|
+
|
|
1532
|
+
// 10. EMPTY SECTIONS
|
|
1533
|
+
const sectionMatches = content.matchAll(/<section[^>]*>([\s\S]*?)<\/section>/gi);
|
|
1534
|
+
for (const m of sectionMatches) {
|
|
1535
|
+
const inner = m[1].replace(/<!--[\s\S]*?-->/g, '').replace(/<[^>]+>/g, '').trim();
|
|
1536
|
+
if (!inner) {
|
|
1537
|
+
issues.push({ page: file, type: 'empty_section', detail: 'Section tag with no visible content' });
|
|
1538
|
+
}
|
|
1539
|
+
}
|
|
1540
|
+
}
|
|
1541
|
+
|
|
1542
|
+
return {
|
|
1543
|
+
status: issues.length === 0 ? 'healthy' : 'issues_found',
|
|
1544
|
+
pages_checked: htmlFiles.length,
|
|
1545
|
+
issue_count: issues.length,
|
|
1546
|
+
issues
|
|
1547
|
+
};
|
|
1548
|
+
}
|
|
1549
|
+
|
|
1187
1550
|
// ============================================================
|
|
1188
1551
|
// Handle tool calls
|
|
1189
1552
|
// ============================================================
|
|
@@ -1286,6 +1649,29 @@ function handleToolCall(name, args) {
|
|
|
1286
1649
|
return runDriftAudit(scope);
|
|
1287
1650
|
}
|
|
1288
1651
|
|
|
1652
|
+
case 'security_audit': {
|
|
1653
|
+
return runSecurityAudit();
|
|
1654
|
+
}
|
|
1655
|
+
|
|
1656
|
+
case 'auto_propagate': {
|
|
1657
|
+
return runAutoPropagators();
|
|
1658
|
+
}
|
|
1659
|
+
|
|
1660
|
+
case 'session_close': {
|
|
1661
|
+
const driftResult = runDriftAudit('full');
|
|
1662
|
+
const propagateResult = runAutoPropagators();
|
|
1663
|
+
return {
|
|
1664
|
+
timestamp: new Date().toISOString(),
|
|
1665
|
+
drift_audit: driftResult,
|
|
1666
|
+
propagation: propagateResult,
|
|
1667
|
+
summary: driftResult.drift_count === 0 ? 'Session clean - no drifts, propagators run' : `${driftResult.drift_count} drifts found - review before closing`
|
|
1668
|
+
};
|
|
1669
|
+
}
|
|
1670
|
+
|
|
1671
|
+
case 'page_health': {
|
|
1672
|
+
return runPageHealth(args.page || 'all');
|
|
1673
|
+
}
|
|
1674
|
+
|
|
1289
1675
|
default:
|
|
1290
1676
|
return { error: 'Unknown tool' };
|
|
1291
1677
|
}
|
|
@@ -1407,7 +1793,7 @@ const server = http.createServer((req, res) => {
|
|
|
1407
1793
|
// Health check
|
|
1408
1794
|
if (req.method === 'GET' && url.pathname === '/health') {
|
|
1409
1795
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
1410
|
-
res.end(JSON.stringify({ status: 'ok', service: 'nervous-system-mcp', version: '1.
|
|
1796
|
+
res.end(JSON.stringify({ status: 'ok', service: 'nervous-system-mcp', version: '1.5.0', protocol: MCP_VERSION }));
|
|
1411
1797
|
return;
|
|
1412
1798
|
}
|
|
1413
1799
|
|
|
@@ -1524,7 +1910,7 @@ const server = http.createServer((req, res) => {
|
|
|
1524
1910
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
1525
1911
|
res.end(JSON.stringify({
|
|
1526
1912
|
name: 'The Nervous System MCP Server',
|
|
1527
|
-
version: '1.
|
|
1913
|
+
version: '1.5.1',
|
|
1528
1914
|
protocol: MCP_VERSION,
|
|
1529
1915
|
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.',
|
|
1530
1916
|
endpoints: {
|
|
@@ -1546,8 +1932,8 @@ const server = http.createServer((req, res) => {
|
|
|
1546
1932
|
migrateExistingViolations();
|
|
1547
1933
|
|
|
1548
1934
|
server.listen(PORT, '127.0.0.1', () => {
|
|
1549
|
-
console.error(`[MCP Server] Nervous System v1.
|
|
1935
|
+
console.error(`[MCP Server] Nervous System v1.5.1 running on port ${PORT}`);
|
|
1550
1936
|
console.error(`[MCP Server] SSE: /sse | HTTP: /mcp | Health: /health | Kill: POST /kill | Audit: GET /audit/verify | Dispatches: GET /dispatches`);
|
|
1551
1937
|
console.error(`[MCP Server] Protocol: ${MCP_VERSION}`);
|
|
1552
|
-
console.error(`[MCP Server] Tools: ${TOOLS.length} (including kill switch, audit chain, dispatch, drift audit)`);
|
|
1938
|
+
console.error(`[MCP Server] Tools: ${TOOLS.length} (including kill switch, audit chain, dispatch, drift audit, page health)`);
|
|
1553
1939
|
});
|
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.5.1",
|
|
4
|
+
"description": "The Nervous System - LLM Behavioral Enforcement Framework. 7 mechanically enforced rules, 16 tools including kill switch, audit chain, dispatch, drift audit, security audit, page health, and session close. 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"
|