hyperdrive-mesh 0.0.0-stage → 1.0.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/Dockerfile +19 -0
- package/LICENSE +21 -0
- package/README.md +147 -2
- package/SPEC.md +93 -0
- package/package.json +37 -4
- package/server.json +25 -0
- package/smithery.yaml +6 -0
- package/src/cli/index.mjs +170 -0
- package/src/core/drive.mjs +80 -0
- package/src/core/guard.mjs +154 -0
- package/src/core/storage.mjs +20 -0
- package/src/core/swarm.mjs +36 -0
- package/src/index.mjs +12 -0
- package/src/mcp/server.mjs +347 -0
package/Dockerfile
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
FROM node:20-slim
|
|
2
|
+
|
|
3
|
+
# Arbeitsverzeichnis setzen
|
|
4
|
+
WORKDIR /app
|
|
5
|
+
|
|
6
|
+
# Abhängigkeiten kopieren und installieren
|
|
7
|
+
COPY package*.json ./
|
|
8
|
+
RUN npm ci --omit=dev
|
|
9
|
+
|
|
10
|
+
# Quellcode kopieren
|
|
11
|
+
COPY . .
|
|
12
|
+
|
|
13
|
+
# Unprivileged User nutzen
|
|
14
|
+
USER node
|
|
15
|
+
|
|
16
|
+
# Stdio-Kommunikation unbuffered
|
|
17
|
+
ENV NODE_ENV=production
|
|
18
|
+
|
|
19
|
+
ENTRYPOINT ["node", "src/index.mjs"]
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Diamantenschmiede & Wolfsrudel
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,148 @@
|
|
|
1
|
-
#
|
|
1
|
+
# 💎 HyperDrive Mesh (P2P Agent-Mesh Storage)
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://nodejs.org)
|
|
4
|
+
[-339933.svg?logo=node.js)](https://nodejs.org)
|
|
5
|
+
[](https://hypercore-protocol.org/)
|
|
6
|
+
[](https://modelcontextprotocol.io/)
|
|
7
|
+
[](./LICENSE)
|
|
8
|
+
|
|
9
|
+
Ein ultra-leichtes, **100 % dezentrales und serverloses P2P-Speichersystem mit nativem Model Context Protocol (MCP)** für autonome KI-Agenten, Entwickler-Teams und Multi-Maschinen-Setups.
|
|
10
|
+
|
|
11
|
+
Geborgen und veredelt aus dem historischen Open-Source-Ökosystem des **Beaker Browsers** – befreit von monolithischem Electron-Ballast und neu geschmiedet für die KI-Ära.
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## ⚡ Highlights & Engineering-Features
|
|
16
|
+
|
|
17
|
+
* **🌐 100 % Serverlos & Dezentral:** Direkte Peer-to-Peer Verbindung über das weltweite **Hyperswarm DHT**. Automatisches NAT-Holepunching. Keine offenen Ports am Router, keine statischen IP-Adressen und **Null Cloud-Kosten**.
|
|
18
|
+
* **🔒 End-to-End Verschlüsselt:** Alle Datenübertragungen laufen kryptografisch abgesichert über das Noise-Protokoll mit Blake2b-Hashes und Ed25519-Schlüsselpaaren.
|
|
19
|
+
* **⚡ Sparse Content-Streaming (vs. Syncthing):** Daten müssen nicht komplett heruntergeladen werden. KI-Agenten und Edge-Nodes streamen gezielt genau die Bytes on-demand, die sie gerade lesen.
|
|
20
|
+
* **⏳ Time-Travel & Snapshot-Versionierung:** Integriertes kryptografisches Append-Only Log. Ermöglicht atomare Rollbacks und versionsgenaue Checkpoints für Multi-Agenten-Transaktionen.
|
|
21
|
+
* **🤖 Nativer MCP-Server:** Vollständige Stdio-Integration für **Antigravity, Claude Desktop, Cursor und CrewAI**. Agenten können dezentrale Speicher-Drives per Funktionsaufruf erstellen, teilen und synchronisieren.
|
|
22
|
+
* **🪶 Leichtgewichtig & Portabel:** Läuft auf Windows, Linux und macOS als reiner Standard-Nutzer ohne Admin-Rechte, ohne C++ Compiler-Hölle und mit minimalem RAM-Footprint (~35 MB).
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## 📊 Architektur & Paketstruktur
|
|
27
|
+
|
|
28
|
+
Die Codebase folgt strikter **Separation of Concerns (SoC)**:
|
|
29
|
+
|
|
30
|
+
```text
|
|
31
|
+
diamantenschmiede/hyperdrive_mesh/
|
|
32
|
+
├── SPEC.md # Detaillierte Architektur-Spezifikation
|
|
33
|
+
├── README.md # Projekt-Dokumentation & Case Study
|
|
34
|
+
├── package.json # Package Manifest & Skripte
|
|
35
|
+
├── src/
|
|
36
|
+
│ ├── index.mjs # MCP Server Stdio Launcher
|
|
37
|
+
│ ├── core/
|
|
38
|
+
│ │ ├── storage.mjs # Corestore-Initialisierung & ephemere Pfade
|
|
39
|
+
│ │ ├── drive.mjs # Hyperdrive Virtual File System (VFS) & Snapshots
|
|
40
|
+
│ │ └── swarm.mjs # Hyperswarm DHT Discovery & Replikations-Events
|
|
41
|
+
│ ├── cli/
|
|
42
|
+
│ │ └── index.mjs # CLI Router (create, share, pull, mcp)
|
|
43
|
+
│ └── mcp/
|
|
44
|
+
│ └── server.mjs # Offizielle Model Context Protocol Implementierung
|
|
45
|
+
└── tests/
|
|
46
|
+
├── test_core_drive.mjs # Lokaler CRUD- & VFS-Test
|
|
47
|
+
├── test_p2p_replication.mjs # 2-Peers Live DHT Replikationstest
|
|
48
|
+
└── test_mcp_interface.mjs # In-Memory MCP Client/Server Tool-Calling Test
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## 🚀 Schnellstart
|
|
54
|
+
|
|
55
|
+
### 1. Installation
|
|
56
|
+
```bash
|
|
57
|
+
# Repository klonen & Abhängigkeiten installieren
|
|
58
|
+
cd diamantenschmiede/hyperdrive_mesh
|
|
59
|
+
npm install
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### 2. Als CLI-Tool verwenden
|
|
63
|
+
|
|
64
|
+
#### A. Ordner im dezentralen P2P-Netzwerk teilen:
|
|
65
|
+
```bash
|
|
66
|
+
node src/cli/index.mjs share ./mein-projekt-ordner
|
|
67
|
+
```
|
|
68
|
+
*Gibt einen 64-stelligen Public Key aus (z. B. `0ba2132d70493d2c...`).*
|
|
69
|
+
|
|
70
|
+
#### B. Auf einem anderen Rechner (z. B. ThinkStation oder Tuf) synchronisieren:
|
|
71
|
+
```bash
|
|
72
|
+
node src/cli/index.mjs pull <PUBLIC_KEY> ./ziel-ordner
|
|
73
|
+
```
|
|
74
|
+
*Findet den Peer über den DHT und lädt die Daten vollautomatisch herunter.*
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
## 🤖 Einbindung als MCP-Server für KI-Agenten
|
|
79
|
+
|
|
80
|
+
Trage HyperDrive Mesh in deine `mcpSettings.json` (z. B. in Antigravity oder Claude Desktop) ein:
|
|
81
|
+
|
|
82
|
+
```json
|
|
83
|
+
{
|
|
84
|
+
"mcpServers": {
|
|
85
|
+
"hyperdrive_mesh": {
|
|
86
|
+
"command": "node",
|
|
87
|
+
"args": ["C:/Users/UserS2025/Desktop/Development/diamantenschmiede/hyperdrive_mesh/src/index.mjs"]
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### Verfügbare MCP-Tools für Agenten:
|
|
94
|
+
1. `p2p_drive_create({ name })`: Erstellt ein neues P2P-Drive und seedet es im DHT.
|
|
95
|
+
2. `p2p_drive_connect({ key })`: Verbindet den Agenten mit einem Remote-Drive via Public Key.
|
|
96
|
+
3. `p2p_file_write({ key, filePath, content })`: Schreibt Daten ins dezentrale Dateisystem.
|
|
97
|
+
4. `p2p_file_read({ key, filePath })`: Liest dezentral gestreamte Daten aus.
|
|
98
|
+
5. `p2p_drive_list({ key, prefix })`: Inspiziert Verzeichnisstrukturen.
|
|
99
|
+
6. `p2p_swarm_status()`: Zeigt aktive Drives und verbundene P2P-Peers.
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
## 🧪 Automatisierte Test-Suite
|
|
104
|
+
|
|
105
|
+
Alle Module sind durch automatisierte Tests verifiziert:
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
# 1. Lokales File-System & VFS prüfen
|
|
109
|
+
npm test
|
|
110
|
+
|
|
111
|
+
# 2. Live P2P Replikation über weltweiten DHT testen
|
|
112
|
+
npm run test:p2p
|
|
113
|
+
|
|
114
|
+
# 3. MCP Tool-Calling Schnittstelle prüfen
|
|
115
|
+
node tests/test_mcp_interface.mjs
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
---
|
|
119
|
+
|
|
120
|
+
## 🐺 Case Study: Spec-Driven Multi-Agent Engineering
|
|
121
|
+
|
|
122
|
+
> *"Wie kann man brachliegende historische Durchbrüche aus dem Open-Source-Sumpf bergen und ohne Vibe-Coding-Schludrigkeit zu produktionsreifen Werkzeugen veredeln?"*
|
|
123
|
+
|
|
124
|
+
### 1. Das Identifizierte Problem
|
|
125
|
+
Auf Plattformen wie X/Twitter und in Entwicklerforen herrscht akute Ratlosigkeit bei der Frage:
|
|
126
|
+
*„Wie synchronisieren autonome Agenten ihren Zustand und ihr Gedächtnis dezentral über mehrere Maschinen hinweg?“*
|
|
127
|
+
Gängige Antworten scheitern an der Praxis:
|
|
128
|
+
* **Syncthing:** Ein reines Dateisynchronisations-Tool für Menschen. Wenn mehrere Agenten parallel schreiben, entstehen Dateikonflikte (`.sync-conflict`) und korrupte Datenbank-Locks. Zudem fehlt Sparse-Streaming.
|
|
129
|
+
* **Cloud-Monopole:** Das Hochladen sensibler Zwischenzustände in zentrale AWS-S3-Buckets oder Google Drive widerspricht dem Gedanken privater, lokaler KI-Infrastruktur.
|
|
130
|
+
|
|
131
|
+
### 2. Die historische Bergung (Operation Deep Mine)
|
|
132
|
+
Ein autonomes Multi-Agenten-Team (**Wolfsrudel**) durchsuchte nach First-Principles-Kriterien die Open-Source-Geschichte nach verlassenen 80%-Lösungen.
|
|
133
|
+
Dabei stießen die Scouts auf den **Beaker Browser** (~6.7k Stars). Das Team um Paul Frazee hatte mit Hypercore/Hyperdrive ein Meisterwerk geschaffen, das Projekt jedoch in einem monolithischen Electron-Webbrowser vergraben. Nach dem Ende der Browser-Entwicklung verstaubte der Kern.
|
|
134
|
+
|
|
135
|
+
### 3. Der ingenieurmäßige Hebel
|
|
136
|
+
Statt das Rad neu zu erfinden, schnitt das Agenten-Team den Browser-Ballast vollständig ab:
|
|
137
|
+
1. **Core-Extraktion:** Portierung auf das moderne Hypercore v10 Ökosystem (`corestore`, `hyperswarm`, `hyperdrive`).
|
|
138
|
+
2. **Ephemeres Speicher-Management:** Saubere Trennung von persistenten und flüchtigen RAM-Puffern für Windows-Standardnutzer ohne Admin-Rechte.
|
|
139
|
+
3. **MCP-Standardisierung:** Kapselung der P2P-Primitive in ein standardisiertes Model Context Protocol Interface.
|
|
140
|
+
|
|
141
|
+
**Ergebnis:** Ein einsatzbereites, robustes Werkzeug, das der gesamten Open-Source-Community geschenkt werden kann – als Beweis für die Kraft zielgerichteter, disziplinierter Multi-Agenten-Orchestrierung.
|
|
142
|
+
|
|
143
|
+
---
|
|
144
|
+
|
|
145
|
+
## 📜 Lizenz
|
|
146
|
+
|
|
147
|
+
Dieses Projekt ist unter der [MIT-Lizenz](./LICENSE) lizenziert.
|
|
148
|
+
Basiert auf den genialen Vorarbeiten der Holepunch- und Beaker-Browser-Entwickler.
|
package/SPEC.md
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# 💎 Diamantenschmiede – Projekt-Spezifikation (SPEC-01)
|
|
2
|
+
# HyperDrive Mesh (Headless P2P Storage & MCP Server)
|
|
3
|
+
|
|
4
|
+
[](https://nodejs.org)
|
|
5
|
+
[](https://hypercore-protocol.org/)
|
|
6
|
+
[](https://modelcontextprotocol.io/)
|
|
7
|
+
[](./LICENSE)
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## 1. 🎯 Executive Summary & Mission
|
|
12
|
+
|
|
13
|
+
**HyperDrive Mesh** ist eine schlanke, 100 % dezentrale und lokale Peer-to-Peer Dateisystem- und Speicher-Engine mit nativer **Model Context Protocol (MCP)** Unterstützung für autonome KI-Agenten und Multi-Maschinen-Setups.
|
|
14
|
+
|
|
15
|
+
### Der historische Kontext (Die Rettung aus der Diamantenmine):
|
|
16
|
+
Dieses Projekt entstand im Rahmen der **Operation Deep Mine** als experimentelle Fallstudie für **Spec-Driven Multi-Agent Engineering**:
|
|
17
|
+
* **Die Vorarbeit:** Das Team hinter dem [Beaker Browser](https://github.com/beakerbrowser/beaker) (~6.7k Stars) leistete Pionierarbeit mit dem Hypercore/Hyperdrive-Protokoll für dezentrales Web-Hosting und echtzeitfähige P2P-Ordner.
|
|
18
|
+
* **Der Flaschenhals (Der Korken):** Die brillante P2P-Technologie wurde in einem monolithischen, proprietären Electron-Webbrowser eingesperrt. Als die Finanzierung ausging und die Nutzer keinen neuen Browser adaptieren wollten, wurde das Projekt 2022 archiviert.
|
|
19
|
+
* **Unser Refinement-Hebel:** Wir haben den gesamten Electron- und Browser-Ballast abgeschnitten und den kryptografischen Kern isoliert. Das Ergebnis ist ein universeller, ressourcenschonender Headless-Daemon, der sich nahtlos in moderne KI-Workflows einfügt.
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## 2. 🏛️ Architektur & Modul-Design
|
|
24
|
+
|
|
25
|
+
Das System folgt strikter **Separation of Concerns (SoC)** als reines ES-Modul (Node.js 20+):
|
|
26
|
+
|
|
27
|
+
```text
|
|
28
|
+
diamantenschmiede/hyperdrive_mesh/
|
|
29
|
+
├── package.json # Paket-Definition & Skripte
|
|
30
|
+
├── SPEC.md # Diese Spezifikation
|
|
31
|
+
├── README.md # Öffentliche Dokumentation & Case Study
|
|
32
|
+
├── .gitignore # Schutz von Keys, Storage & node_modules
|
|
33
|
+
├── src/
|
|
34
|
+
│ ├── index.mjs # Haupteinstiegspunkt
|
|
35
|
+
│ ├── core/
|
|
36
|
+
│ │ ├── storage.mjs # Corestore-Initialisierung & Persistenz
|
|
37
|
+
│ │ ├── drive.mjs # Hyperdrive CRUD, Sparse Sync & Snapshot Engine
|
|
38
|
+
│ │ └── swarm.mjs # Hyperswarm DHT Discovery, Connection Pooling & NAT-Traverse
|
|
39
|
+
│ ├── cli/
|
|
40
|
+
│ │ ├── index.mjs # CLI Router
|
|
41
|
+
│ │ ├── cmd_init.mjs # Drive initialisieren / Key generieren
|
|
42
|
+
│ │ ├── cmd_share.mjs # Lokalen Ordner in den P2P-Schwarm seeden
|
|
43
|
+
│ │ ├── cmd_sync.mjs # Remote Drive via P2P-Key lokal mounten/spiegeln
|
|
44
|
+
│ │ └── cmd_status.mjs # Aktive Peers, Bandbreite & Schwarm-Statistik
|
|
45
|
+
│ └── mcp/
|
|
46
|
+
│ ├── server.mjs # Stdio MCP Server Implementierung
|
|
47
|
+
│ └── tools.mjs # Agent-Tools (p2p_put, p2p_get, p2p_sync, p2p_snapshot)
|
|
48
|
+
└── tests/
|
|
49
|
+
├── test_core_drive.mjs # Lokaler CRUD- & Key-Test
|
|
50
|
+
├── test_p2p_replication.mjs # 2-Peers In-Memory Replication Test
|
|
51
|
+
└── test_mcp_interface.mjs # Validierung der MCP Stdio Schnittstelle
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
## 3. ⚙️ Kern-Funktionalitäten
|
|
57
|
+
|
|
58
|
+
### A. Sparse Content-Streaming (vs. Syncthing)
|
|
59
|
+
* Lädt Daten nicht starr blockweise auf die Festplatte, sondern streamt genau die Bytes on-demand, die ein Agent anfordert.
|
|
60
|
+
* Ermöglicht das Teilen von Gigabyte-großen Repositories, während ein Agent auf einem Edge-Node nur eine 4 KB Konfigurationsdatei liest.
|
|
61
|
+
|
|
62
|
+
### B. Time-Travel & Snapshot Versioning
|
|
63
|
+
* Jeder Schreibvorgang wandert in ein kryptografisches Append-Only Log.
|
|
64
|
+
* Agenten können atomare Checkpoints erstellen und historische Zustände inspizieren:
|
|
65
|
+
```javascript
|
|
66
|
+
const historicalDrive = drive.checkout(version)
|
|
67
|
+
const pastConfig = await historicalDrive.get('/config.json')
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### C. Zero-Cloud P2P Schwarm
|
|
71
|
+
* End-to-End verschlüsselt via Noise Protocol.
|
|
72
|
+
* Automatisches NAT-Holepunching über Hyperswarm DHT. Keine Portfreigaben am Router oder feste IPs nötig.
|
|
73
|
+
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
## 4. 🤖 MCP-Werkzeuge für KI-Agenten
|
|
77
|
+
|
|
78
|
+
Der integrierte MCP-Server stellt folgende Tools über `stdio` bereit:
|
|
79
|
+
|
|
80
|
+
1. **`p2p_drive_create`**: Erstellt ein neues P2P-Drive und liefert den hexadezimalen Public-Key zurück.
|
|
81
|
+
2. **`p2p_drive_share`**: Teilt einen lokalen Pfad im Schwarm und schützt ihn optional mit Schreibrechten.
|
|
82
|
+
3. **`p2p_drive_pull`**: Verbindet sich mit einem Remote-Key und synchronisiert spezifische Dateien oder ganze Verzeichnisse.
|
|
83
|
+
4. **`p2p_file_read` / `p2p_file_write`**: Liest und schreibt direkt im kryptografischen Virtual File System.
|
|
84
|
+
5. **`p2p_snapshot_create`**: Erzeugt einen versionierten Restore-Point für Multi-Agenten-Transaktionen.
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
## 5. 🐺 Case Study Leitplanken (Whisper-Pill-Standard)
|
|
89
|
+
|
|
90
|
+
In der Dokumentation und im Code werden folgende Grundsätze strikt eingehalten:
|
|
91
|
+
1. **Kein Vibe-Coding:** Jeder Schritt wird testgetrieben verifiziert (`tests/`).
|
|
92
|
+
2. **Standard-Nutzer Kompatibilität:** Läuft portabel ohne Windows-Admin-Rechte, ohne Registry-Eingriffe und ohne C++ Compiler-Zwänge.
|
|
93
|
+
3. **Open-Source Attribution:** Klare Nennung der historischen Wurzeln (Paul Frazee, Beaker Browser, Holepunch/Hypercore-Team) mit klarer Abgrenzung unserer Optimierung.
|
package/package.json
CHANGED
|
@@ -1,6 +1,39 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hyperdrive-mesh",
|
|
3
|
-
"version": "
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Headless P2P Storage Engine & MCP Server extracted from the Beaker/Hypercore ecosystem",
|
|
5
|
+
"main": "src/index.mjs",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"bin": {
|
|
8
|
+
"hyperdrive-mesh": "src/cli/index.mjs"
|
|
9
|
+
},
|
|
10
|
+
"repository": {
|
|
11
|
+
"type": "git",
|
|
12
|
+
"url": "git+https://github.com/tastenkasperle/hyperdrive-mesh.git"
|
|
13
|
+
},
|
|
14
|
+
"scripts": {
|
|
15
|
+
"test": "node tests/test_core_drive.mjs",
|
|
16
|
+
"test:p2p": "node tests/test_p2p_replication.mjs",
|
|
17
|
+
"start": "node src/index.mjs"
|
|
18
|
+
},
|
|
19
|
+
"keywords": [
|
|
20
|
+
"hypercore",
|
|
21
|
+
"hyperdrive",
|
|
22
|
+
"hyperswarm",
|
|
23
|
+
"p2p",
|
|
24
|
+
"local-first",
|
|
25
|
+
"mcp",
|
|
26
|
+
"agentic-ai",
|
|
27
|
+
"decentralized"
|
|
28
|
+
],
|
|
29
|
+
"author": "THE HIVE // BSO (Maintained by C. H., BSO Steward)",
|
|
30
|
+
"license": "MIT",
|
|
31
|
+
"dependencies": {
|
|
32
|
+
"@modelcontextprotocol/sdk": "^1.31.0",
|
|
33
|
+
"b4a": "^1.6.7",
|
|
34
|
+
"corestore": "^7.12.6",
|
|
35
|
+
"hyperdrive": "^13.3.4",
|
|
36
|
+
"hyperswarm": "^4.17.2",
|
|
37
|
+
"random-access-memory": "^6.2.1"
|
|
38
|
+
}
|
|
39
|
+
}
|
package/server.json
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"name": "hyperdrive-mesh",
|
|
4
|
+
"version": "1.0.0",
|
|
5
|
+
"description": "Serverless P2P File System & Decentralized Memory Mesh for Autonomous AI Agents",
|
|
6
|
+
"author": {
|
|
7
|
+
"name": "Diamantenschmiede",
|
|
8
|
+
"url": "https://github.com/tastenkasperle"
|
|
9
|
+
},
|
|
10
|
+
"homepage": "https://github.com/tastenkasperle/hyperdrive-mesh",
|
|
11
|
+
"repository": {
|
|
12
|
+
"type": "git",
|
|
13
|
+
"url": "git+https://github.com/tastenkasperle/hyperdrive-mesh.git"
|
|
14
|
+
},
|
|
15
|
+
"capabilities": {
|
|
16
|
+
"tools": true
|
|
17
|
+
},
|
|
18
|
+
"transport": {
|
|
19
|
+
"type": "stdio",
|
|
20
|
+
"command": "node",
|
|
21
|
+
"args": [
|
|
22
|
+
"src/index.mjs"
|
|
23
|
+
]
|
|
24
|
+
}
|
|
25
|
+
}
|
package/smithery.yaml
ADDED
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { initStorage } from '../core/storage.mjs'
|
|
3
|
+
import { MeshDrive } from '../core/drive.mjs'
|
|
4
|
+
import { MeshSwarm } from '../core/swarm.mjs'
|
|
5
|
+
import path from 'path'
|
|
6
|
+
import fs from 'fs'
|
|
7
|
+
|
|
8
|
+
const args = process.argv.slice(2)
|
|
9
|
+
const command = args[0]
|
|
10
|
+
|
|
11
|
+
function printHelp() {
|
|
12
|
+
console.log(`
|
|
13
|
+
💎 HyperDrive Mesh CLI (P2P Dezentral)
|
|
14
|
+
Usage:
|
|
15
|
+
node src/cli/index.mjs create [name] - Erstellt ein neues P2P-Drive & gibt den Key aus
|
|
16
|
+
node src/cli/index.mjs share <ordner> - Teilt einen lokalen Ordner im weltweiten P2P-DHT
|
|
17
|
+
node src/cli/index.mjs pull <key> <zielordner> - Synchronisiert ein P2P-Drive in einen lokalen Ordner
|
|
18
|
+
node src/cli/index.mjs mcp - Startet den MCP-Server via Stdio für KI-Agenten
|
|
19
|
+
`)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async function main() {
|
|
23
|
+
if (!command || command === '--help' || command === '-h') {
|
|
24
|
+
printHelp()
|
|
25
|
+
return
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
switch (command) {
|
|
29
|
+
case 'create': {
|
|
30
|
+
const name = args[1] || 'vault'
|
|
31
|
+
const store = initStorage(path.join('./.storage', name))
|
|
32
|
+
const drive = new MeshDrive(store)
|
|
33
|
+
await drive.init()
|
|
34
|
+
|
|
35
|
+
const swarm = new MeshSwarm(store)
|
|
36
|
+
await swarm.join(drive.drive.discoveryKey, { server: true, client: true })
|
|
37
|
+
|
|
38
|
+
console.log(`\n🎉 Drive "${name}" erfolgreich initialisiert!`)
|
|
39
|
+
console.log(`🔑 Public Key (zum Teilen): ${drive.keyHex}`)
|
|
40
|
+
console.log(`📡 Verbunden mit Hyperswarm DHT. Halte diesen Prozess aktiv, um zu seeden...\n`)
|
|
41
|
+
|
|
42
|
+
// Prozess offenhalten
|
|
43
|
+
process.on('SIGINT', async () => {
|
|
44
|
+
console.log('\nBeende Seeder...')
|
|
45
|
+
await swarm.destroy()
|
|
46
|
+
await drive.close()
|
|
47
|
+
process.exit(0)
|
|
48
|
+
})
|
|
49
|
+
break
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
case 'share': {
|
|
53
|
+
const localFolder = args[1]
|
|
54
|
+
if (!localFolder || !fs.existsSync(localFolder)) {
|
|
55
|
+
console.error('❌ Fehler: Bitte einen gültigen lokalen Ordner angeben!')
|
|
56
|
+
process.exit(1)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
console.log(`📦 Lese Ordner ein: ${localFolder}...`)
|
|
60
|
+
const store = initStorage('./.storage_seed')
|
|
61
|
+
const drive = new MeshDrive(store)
|
|
62
|
+
await drive.init()
|
|
63
|
+
|
|
64
|
+
// Rekursiv Dateien einlesen
|
|
65
|
+
async function addFiles(dir, baseDir) {
|
|
66
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true })
|
|
67
|
+
for (const entry of entries) {
|
|
68
|
+
const fullPath = path.join(dir, entry.name)
|
|
69
|
+
const relPath = '/' + path.relative(baseDir, fullPath).replace(/\\/g, '/')
|
|
70
|
+
if (entry.isDirectory()) {
|
|
71
|
+
await addFiles(fullPath, baseDir)
|
|
72
|
+
} else {
|
|
73
|
+
const data = fs.readFileSync(fullPath)
|
|
74
|
+
await drive.writeFile(relPath, data)
|
|
75
|
+
console.log(` ➕ Gequeued: ${relPath} (${data.length} Bytes)`)
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
await addFiles(localFolder, localFolder)
|
|
81
|
+
|
|
82
|
+
const swarm = new MeshSwarm(store)
|
|
83
|
+
await swarm.join(drive.drive.discoveryKey, { server: true, client: false })
|
|
84
|
+
|
|
85
|
+
console.log(`\n🚀 Ordner erfolgreich in P2P-Hyperdrive geladen!`)
|
|
86
|
+
console.log(`🔑 Public Key: ${drive.keyHex}`)
|
|
87
|
+
console.log(`🌐 Im DHT veröffentlicht. Drücke Strg+C zum Beenden.\n`)
|
|
88
|
+
|
|
89
|
+
process.on('SIGINT', async () => {
|
|
90
|
+
await swarm.destroy()
|
|
91
|
+
await drive.close()
|
|
92
|
+
process.exit(0)
|
|
93
|
+
})
|
|
94
|
+
break
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
case 'pull': {
|
|
98
|
+
const key = args[1]
|
|
99
|
+
const targetFolder = args[2]
|
|
100
|
+
|
|
101
|
+
if (!key || !targetFolder) {
|
|
102
|
+
console.error('❌ Fehler: Syntax: node src/cli/index.mjs pull <key> <zielordner>')
|
|
103
|
+
process.exit(1)
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (!fs.existsSync(targetFolder)) {
|
|
107
|
+
fs.mkdirSync(targetFolder, { recursive: true })
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
console.log(`🔍 Suche P2P-Drive [${key.slice(0, 8)}...] im DHT...`)
|
|
111
|
+
const store = initStorage('./.storage_pull')
|
|
112
|
+
const drive = new MeshDrive(store, key)
|
|
113
|
+
await drive.init()
|
|
114
|
+
|
|
115
|
+
const swarm = new MeshSwarm(store)
|
|
116
|
+
await swarm.join(drive.drive.discoveryKey, { server: false, client: true })
|
|
117
|
+
|
|
118
|
+
console.log('📡 Verbunden mit Schwarm. Warte auf Datei-Stream...')
|
|
119
|
+
|
|
120
|
+
// Polling & Download
|
|
121
|
+
const downloaded = new Set()
|
|
122
|
+
const interval = setInterval(async () => {
|
|
123
|
+
try {
|
|
124
|
+
const files = await drive.listFiles('/')
|
|
125
|
+
for (const f of files) {
|
|
126
|
+
if (!downloaded.has(f.key)) {
|
|
127
|
+
const content = await drive.drive.get(f.key)
|
|
128
|
+
if (content) {
|
|
129
|
+
const outPath = path.join(targetFolder, f.key)
|
|
130
|
+
fs.mkdirSync(path.dirname(outPath), { recursive: true })
|
|
131
|
+
fs.writeFileSync(outPath, content)
|
|
132
|
+
console.log(` ⬇️ Empfangen: ${f.key} (${content.length} Bytes)`)
|
|
133
|
+
downloaded.add(f.key)
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (files.length > 0 && downloaded.size === files.length) {
|
|
139
|
+
console.log(`\n🎉 Alle ${downloaded.size} Dateien erfolgreich dezentral synchronisiert!`)
|
|
140
|
+
clearInterval(interval)
|
|
141
|
+
await swarm.destroy()
|
|
142
|
+
await drive.close()
|
|
143
|
+
process.exit(0)
|
|
144
|
+
}
|
|
145
|
+
} catch (e) {
|
|
146
|
+
// Sync läuft im Hintergrund
|
|
147
|
+
}
|
|
148
|
+
}, 500)
|
|
149
|
+
|
|
150
|
+
break
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
case 'mcp': {
|
|
154
|
+
const { MeshMCPServer } = await import('../mcp/server.mjs')
|
|
155
|
+
const mcp = new MeshMCPServer()
|
|
156
|
+
await mcp.start()
|
|
157
|
+
console.error('[HYPERDRIVE-MESH] MCP Server läuft auf Stdio.')
|
|
158
|
+
break
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
default:
|
|
162
|
+
console.error(`Unbekannter Befehl: ${command}`)
|
|
163
|
+
printHelp()
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
main().catch(err => {
|
|
168
|
+
console.error('❌ CLI-Fehler:', err)
|
|
169
|
+
process.exit(1)
|
|
170
|
+
})
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import Hyperdrive from 'hyperdrive'
|
|
2
|
+
import b4a from 'b4a'
|
|
3
|
+
import path from 'path'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Erstellt oder lädt ein Hyperdrive über einen Corestore
|
|
7
|
+
*/
|
|
8
|
+
export class MeshDrive {
|
|
9
|
+
constructor(store, key = null) {
|
|
10
|
+
this.store = store
|
|
11
|
+
this.key = key ? (typeof key === 'string' ? b4a.from(key, 'hex') : key) : null
|
|
12
|
+
this.drive = new Hyperdrive(this.store, this.key)
|
|
13
|
+
this.ready = false
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
async init() {
|
|
17
|
+
await this.drive.ready()
|
|
18
|
+
this.key = this.drive.key
|
|
19
|
+
this.ready = true
|
|
20
|
+
return this
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
get keyHex() {
|
|
24
|
+
return this.key ? b4a.toString(this.key, 'hex') : null
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
get version() {
|
|
28
|
+
return this.drive.version
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
sanitizePath(p) {
|
|
32
|
+
if (!p) return '/'
|
|
33
|
+
// Null-Bytes, Steuerzeichen (\r, \n, \t) und Whitespace trimmen
|
|
34
|
+
let clean = p.replace(/[\0\r\n\t]/g, '').trim().replace(/\\/g, '/')
|
|
35
|
+
|
|
36
|
+
// Windows Laufwerksbuchstaben entfernen (z. B. C:)
|
|
37
|
+
clean = clean.replace(/^[a-zA-Z]:/, '')
|
|
38
|
+
|
|
39
|
+
// POSIX Normalisierung
|
|
40
|
+
clean = path.posix.normalize('/' + clean)
|
|
41
|
+
|
|
42
|
+
// Traversal-Schutz: Filtere alle Segmente, die nur Punkte enthalten (., .., ..., etc.)
|
|
43
|
+
const segments = clean.split('/').filter(s => {
|
|
44
|
+
const trimmed = s.trim()
|
|
45
|
+
return trimmed.length > 0 && !/^\.+$/.test(trimmed)
|
|
46
|
+
})
|
|
47
|
+
return '/' + segments.join('/')
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async writeFile(filePath, content) {
|
|
51
|
+
const cleanPath = this.sanitizePath(filePath)
|
|
52
|
+
const buf = typeof content === 'string' ? b4a.from(content) : content
|
|
53
|
+
await this.drive.put(cleanPath, buf)
|
|
54
|
+
return { path: cleanPath, bytes: buf.length, version: this.drive.version }
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async readFile(filePath) {
|
|
58
|
+
const cleanPath = this.sanitizePath(filePath)
|
|
59
|
+
const buf = await this.drive.get(cleanPath)
|
|
60
|
+
if (!buf) return null
|
|
61
|
+
return b4a.toString(buf)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async listFiles(prefix = '/') {
|
|
65
|
+
const files = []
|
|
66
|
+
for await (const entry of this.drive.list(prefix)) {
|
|
67
|
+
files.push({
|
|
68
|
+
key: entry.key,
|
|
69
|
+
size: entry.value.blob ? entry.value.blob.byteLength : 0,
|
|
70
|
+
seq: entry.seq
|
|
71
|
+
})
|
|
72
|
+
}
|
|
73
|
+
return files
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
async close() {
|
|
77
|
+
await this.drive.close()
|
|
78
|
+
await this.store.close()
|
|
79
|
+
}
|
|
80
|
+
}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SecurityGuard v2.0 (OWASP Agentic Security Standard 2026)
|
|
3
|
+
* Schützt HyperDrive Mesh gegen Indirect Injections, Obfuscated Payloads,
|
|
4
|
+
* Exfiltrationsversuche, Bidi-Spoofing und unautorisierte Pfade.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import path from 'path'
|
|
8
|
+
|
|
9
|
+
// Erweiterte Injection-Muster nach OWASP GenAI 2026
|
|
10
|
+
const INJECTION_PATTERNS = [
|
|
11
|
+
/ignore\s+(all\s+)?(previous|prior|above)\s+(instructions|prompts|directives)/i,
|
|
12
|
+
/disregard\s+(all\s+)?(previous|prior|above)/i,
|
|
13
|
+
/system\s*prompt\s*override/i,
|
|
14
|
+
/you\s+are\s+now\s+in\s+([a-z0-9_-]+)?mode/i,
|
|
15
|
+
/new\s+role\s*:\s*you\s+must/i,
|
|
16
|
+
/<\|im_start\|>/i,
|
|
17
|
+
/<\|im_end\|>/i,
|
|
18
|
+
/\[SYSTEM_PROMPT\]/i,
|
|
19
|
+
/\[OVERRIDE\]/i,
|
|
20
|
+
/DAN\s+Mode/i,
|
|
21
|
+
/act\s+as\s+(an?\s+)?unrestricted/i,
|
|
22
|
+
/developer\s*mode\s*enabled/i
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
// Markdown Image / Exfiltration URL Patterns (z. B. )
|
|
26
|
+
const EXFILTRATION_PATTERNS = [
|
|
27
|
+
/!\[.*?\]\((https?:\/\/[^\s)]+)\)/i,
|
|
28
|
+
/<img\s+[^>]*src=["'](https?:\/\/[^"']+)["']/i
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
// Sensible Credentials / Secrets
|
|
32
|
+
const SENSITIVE_PATH_PATTERNS = [
|
|
33
|
+
/(\.env|credentials|secret|masterpw|id_rsa|id_ed25519|id_ecdsa|\.pem|\.key)/i,
|
|
34
|
+
/(token|apikey|password|passwd|shadow|auth\.json)/i
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
export class SecurityGuard {
|
|
38
|
+
/**
|
|
39
|
+
* Prüft und bereinigt Textinhalte nach OWASP GenAI 2026 Standards
|
|
40
|
+
* @param {string} content
|
|
41
|
+
* @returns {{ safe: boolean, threats: string[], sanitized: string }}
|
|
42
|
+
*/
|
|
43
|
+
static inspectPayload(content) {
|
|
44
|
+
if (typeof content !== 'string') {
|
|
45
|
+
return { safe: true, threats: [], sanitized: content }
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const threats = []
|
|
49
|
+
|
|
50
|
+
// 1. Unicode-Normalisierung (NFKC) gegen Homoglyphen & Bidi-Overrides (U+202E etc.)
|
|
51
|
+
let normalized = content.normalize('NFKC')
|
|
52
|
+
const bidiRegex = /[\u202A-\u202E\u2066-\u2069]/g
|
|
53
|
+
if (bidiRegex.test(normalized)) {
|
|
54
|
+
threats.push('Unicode BiDi Override detected (Visual spoofing attack)')
|
|
55
|
+
normalized = normalized.replace(bidiRegex, '')
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// 2. Unsichtbare Zero-Width Zeichen entfernen
|
|
59
|
+
const zeroWidthRegex = /[\u200B-\u200D\uFEFF]/g
|
|
60
|
+
if (zeroWidthRegex.test(normalized)) {
|
|
61
|
+
threats.push('Zero-Width Unicode Characters (Obfuscation detected)')
|
|
62
|
+
normalized = normalized.replace(zeroWidthRegex, '')
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// 3. Rekursive Base64-Inspektion (Erkennt versteckte Jailbreaks in Base64-Strings)
|
|
66
|
+
const base64Matches = normalized.match(/[A-Za-z0-9+/]{20,}={0,2}/g) || []
|
|
67
|
+
for (const b64 of base64Matches) {
|
|
68
|
+
try {
|
|
69
|
+
const decoded = Buffer.from(b64, 'base64').toString('utf-8')
|
|
70
|
+
// Prüfen ob decodierter Text ein lesbarer Angriffs-Prompt ist
|
|
71
|
+
for (const pattern of INJECTION_PATTERNS) {
|
|
72
|
+
if (pattern.test(decoded)) {
|
|
73
|
+
threats.push(`Obfuscated Base64 Injection detected: "${decoded.slice(0, 40)}..."`)
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
} catch (e) {
|
|
77
|
+
// Kein gültiger UTF-8 String, ignorieren
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// 4. Direkte Prompt Injection Muster
|
|
82
|
+
for (const pattern of INJECTION_PATTERNS) {
|
|
83
|
+
if (pattern.test(normalized)) {
|
|
84
|
+
threats.push(`Prompt Injection Pattern matched: ${pattern.toString()}`)
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// 5. Data Exfiltration / Tracking-Pixel Check
|
|
89
|
+
for (const exfil of EXFILTRATION_PATTERNS) {
|
|
90
|
+
if (exfil.test(normalized)) {
|
|
91
|
+
threats.push(`Potential Data-Exfiltration Vector detected (Outbound Image/Link Ping)`)
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return {
|
|
96
|
+
safe: threats.length === 0,
|
|
97
|
+
threats,
|
|
98
|
+
sanitized: normalized
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Normalisiert virtuelle Pfade exakt synchron zu MeshDrive.sanitizePath,
|
|
104
|
+
* damit Traversal-Bypässe ("system/cfg", "/a/../system/cfg") vor der Autorisierung abgefangen werden.
|
|
105
|
+
* @param {string} p
|
|
106
|
+
* @returns {string}
|
|
107
|
+
*/
|
|
108
|
+
static normalizeVirtualPath(p) {
|
|
109
|
+
if (!p || typeof p !== 'string') return '/'
|
|
110
|
+
let clean = p.replace(/[\0\r\n\t]/g, '').trim().replace(/\\/g, '/')
|
|
111
|
+
clean = clean.replace(/^[a-zA-Z]:/, '')
|
|
112
|
+
clean = path.posix.normalize('/' + clean)
|
|
113
|
+
const segments = clean.split('/').filter(s => {
|
|
114
|
+
const trimmed = s.trim()
|
|
115
|
+
return trimmed.length > 0 && !/^\.+$/.test(trimmed)
|
|
116
|
+
})
|
|
117
|
+
return '/' + segments.join('/')
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Prüft Pfad-Berechtigungen (Capability-Based Access Control)
|
|
122
|
+
* @param {string} filePath
|
|
123
|
+
* @param {string} agentRole ('admin' | 'worker' | 'guest')
|
|
124
|
+
* @param {'read' | 'write'} action
|
|
125
|
+
*/
|
|
126
|
+
static authorize(filePath, agentRole = 'worker', action = 'write') {
|
|
127
|
+
const cleanPath = SecurityGuard.normalizeVirtualPath(filePath)
|
|
128
|
+
|
|
129
|
+
// Nur exakt 'admin' besitzt Root-Rechte
|
|
130
|
+
const isAdmin = agentRole === 'admin'
|
|
131
|
+
|
|
132
|
+
// Gäste dürfen niemals schreiben
|
|
133
|
+
if (agentRole === 'guest' && action === 'write') {
|
|
134
|
+
return { allowed: false, reason: 'Guest agents are read-only.' }
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// System-Pfade sind strikt Admin-Only (geprüft auf kanonischem Pfad!)
|
|
138
|
+
if ((cleanPath === '/system' || cleanPath.startsWith('/system/')) && !isAdmin && action === 'write') {
|
|
139
|
+
return { allowed: false, reason: 'System paths are write-protected (Admin only).' }
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Credential- & Secret-Dateien sind für Nicht-Admins komplett gesperrt
|
|
143
|
+
for (const pattern of SENSITIVE_PATH_PATTERNS) {
|
|
144
|
+
if ((pattern.test(filePath) || pattern.test(cleanPath)) && !isAdmin) {
|
|
145
|
+
return {
|
|
146
|
+
allowed: false,
|
|
147
|
+
reason: `Access to sensitive path "${cleanPath}" blocked for role "${agentRole}".`
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return { allowed: true }
|
|
153
|
+
}
|
|
154
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import Corestore from 'corestore'
|
|
2
|
+
import path from 'path'
|
|
3
|
+
import fs from 'fs'
|
|
4
|
+
import os from 'os'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Initialisiert den persistenten oder ephemeren Corestore
|
|
8
|
+
* @param {string|null} storageDir - Optionaler lokaler Speicherpfad. Falls null, ephemerer Temp-Ordner.
|
|
9
|
+
* @returns {Corestore}
|
|
10
|
+
*/
|
|
11
|
+
export function initStorage(storageDir = null) {
|
|
12
|
+
const targetDir = storageDir
|
|
13
|
+
? path.resolve(storageDir)
|
|
14
|
+
: fs.mkdtempSync(path.join(os.tmpdir(), 'hyperdrive-mesh-'))
|
|
15
|
+
|
|
16
|
+
if (!fs.existsSync(targetDir)) {
|
|
17
|
+
fs.mkdirSync(targetDir, { recursive: true })
|
|
18
|
+
}
|
|
19
|
+
return new Corestore(targetDir)
|
|
20
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import Hyperswarm from 'hyperswarm'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Verbindet einen Corestore mit dem Hyperswarm DHT Netzwerk
|
|
5
|
+
*/
|
|
6
|
+
export class MeshSwarm {
|
|
7
|
+
constructor(store, opts = {}) {
|
|
8
|
+
this.store = store
|
|
9
|
+
this.swarm = new Hyperswarm(opts)
|
|
10
|
+
this.peers = new Set()
|
|
11
|
+
|
|
12
|
+
// Automatische P2P-Replikation über den Corestore
|
|
13
|
+
this.swarm.on('connection', (connection, peerInfo) => {
|
|
14
|
+
this.peers.add(connection)
|
|
15
|
+
this.store.replicate(connection)
|
|
16
|
+
|
|
17
|
+
connection.on('close', () => {
|
|
18
|
+
this.peers.delete(connection)
|
|
19
|
+
})
|
|
20
|
+
})
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async join(discoveryKey, { server = true, client = true } = {}) {
|
|
24
|
+
const discovery = this.swarm.join(discoveryKey, { server, client })
|
|
25
|
+
await discovery.flushed()
|
|
26
|
+
return discovery
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
get peerCount() {
|
|
30
|
+
return this.peers.size
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async destroy() {
|
|
34
|
+
await this.swarm.destroy()
|
|
35
|
+
}
|
|
36
|
+
}
|
package/src/index.mjs
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { MeshMCPServer } from './mcp/server.mjs'
|
|
2
|
+
|
|
3
|
+
async function main() {
|
|
4
|
+
const server = new MeshMCPServer()
|
|
5
|
+
await server.start()
|
|
6
|
+
console.error('[HYPERDRIVE-MESH] MCP-Server via Stdio gestartet.')
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
main().catch(err => {
|
|
10
|
+
console.error('[HYPERDRIVE-MESH FEHLER]:', err)
|
|
11
|
+
process.exit(1)
|
|
12
|
+
})
|
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js'
|
|
2
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
|
|
3
|
+
import {
|
|
4
|
+
CallToolRequestSchema,
|
|
5
|
+
ListToolsRequestSchema
|
|
6
|
+
} from '@modelcontextprotocol/sdk/types.js'
|
|
7
|
+
import { initStorage } from '../core/storage.mjs'
|
|
8
|
+
import { MeshDrive } from '../core/drive.mjs'
|
|
9
|
+
import { MeshSwarm } from '../core/swarm.mjs'
|
|
10
|
+
import { SecurityGuard } from '../core/guard.mjs'
|
|
11
|
+
import b4a from 'b4a'
|
|
12
|
+
import path from 'path'
|
|
13
|
+
import fs from 'fs'
|
|
14
|
+
|
|
15
|
+
export class MeshMCPServer {
|
|
16
|
+
constructor(storageDir = './.storage') {
|
|
17
|
+
this.storageDir = storageDir
|
|
18
|
+
this.server = new Server(
|
|
19
|
+
{
|
|
20
|
+
name: 'hyperdrive-mesh',
|
|
21
|
+
version: '1.0.0'
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
capabilities: {
|
|
25
|
+
tools: {}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
this.activeDrives = new Map() // keyHex -> { drive, swarm, isSeeder }
|
|
31
|
+
this.setupHandlers()
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
setupHandlers() {
|
|
35
|
+
// 1. Liste der verfügbaren Werkzeuge
|
|
36
|
+
this.server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
37
|
+
return {
|
|
38
|
+
tools: [
|
|
39
|
+
{
|
|
40
|
+
name: 'p2p_drive_create',
|
|
41
|
+
description: 'Erstellt ein neues, kryptografisch gesichertes P2P-Hyperdrive und liefert den Public Key zum Teilen zurück.',
|
|
42
|
+
inputSchema: {
|
|
43
|
+
type: 'object',
|
|
44
|
+
properties: {
|
|
45
|
+
name: {
|
|
46
|
+
type: 'string',
|
|
47
|
+
description: 'Ein lokaler Bezeichner für dieses Drive (z. B. "memory", "project_docs").'
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
name: 'p2p_drive_connect',
|
|
54
|
+
description: 'Verbindet sich mit einem bestehenden Remote-Hyperdrive über dessen 64-stelligen hexadezimalen Public Key via DHT.',
|
|
55
|
+
inputSchema: {
|
|
56
|
+
type: 'object',
|
|
57
|
+
properties: {
|
|
58
|
+
key: {
|
|
59
|
+
type: 'string',
|
|
60
|
+
description: 'Der 64-stellige Hex-Public-Key des Remote-Drives.'
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
required: ['key']
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
name: 'p2p_file_write',
|
|
68
|
+
description: 'Schreibt oder aktualisiert eine Datei im P2P-Hyperdrive. Die Änderung wird automatisch an verbundene Peers gestreamt.',
|
|
69
|
+
inputSchema: {
|
|
70
|
+
type: 'object',
|
|
71
|
+
properties: {
|
|
72
|
+
key: {
|
|
73
|
+
type: 'string',
|
|
74
|
+
description: 'Der Hex-Key des Drives (optional falls nur ein Drive aktiv ist).'
|
|
75
|
+
},
|
|
76
|
+
filePath: {
|
|
77
|
+
type: 'string',
|
|
78
|
+
description: 'Virtueller Pfad im Drive (z. B. "/context/task.md" oder "/state.json").'
|
|
79
|
+
},
|
|
80
|
+
content: {
|
|
81
|
+
type: 'string',
|
|
82
|
+
description: 'Der Textinhalt der Datei.'
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
required: ['filePath', 'content']
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
name: 'p2p_file_read',
|
|
90
|
+
description: 'Liest eine Datei aus dem lokalen oder remote über P2P gestreamten Hyperdrive.',
|
|
91
|
+
inputSchema: {
|
|
92
|
+
type: 'object',
|
|
93
|
+
properties: {
|
|
94
|
+
key: {
|
|
95
|
+
type: 'string',
|
|
96
|
+
description: 'Der Hex-Key des Drives.'
|
|
97
|
+
},
|
|
98
|
+
filePath: {
|
|
99
|
+
type: 'string',
|
|
100
|
+
description: 'Virtueller Pfad im Drive (z. B. "/state.json").'
|
|
101
|
+
}
|
|
102
|
+
},
|
|
103
|
+
required: ['filePath']
|
|
104
|
+
}
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
name: 'p2p_drive_list',
|
|
108
|
+
description: 'Listet alle Dateien und Verzeichnisse eines P2P-Hyperdrives auf.',
|
|
109
|
+
inputSchema: {
|
|
110
|
+
type: 'object',
|
|
111
|
+
properties: {
|
|
112
|
+
key: {
|
|
113
|
+
type: 'string',
|
|
114
|
+
description: 'Der Hex-Key des Drives.'
|
|
115
|
+
},
|
|
116
|
+
prefix: {
|
|
117
|
+
type: 'string',
|
|
118
|
+
description: 'Optionales Präfix / Verzeichnis (Standard: "/").'
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
name: 'p2p_swarm_status',
|
|
125
|
+
description: 'Liefert den aktuellen Status aller gemounteten Drives und die Anzahl aktiver P2P-Peers im Hyperswarm.',
|
|
126
|
+
inputSchema: {
|
|
127
|
+
type: 'object',
|
|
128
|
+
properties: {}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
]
|
|
132
|
+
}
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
// 2. Ausführung der Werkzeuge
|
|
136
|
+
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
137
|
+
const { name, arguments: args } = request.params
|
|
138
|
+
|
|
139
|
+
try {
|
|
140
|
+
switch (name) {
|
|
141
|
+
case 'p2p_drive_create': {
|
|
142
|
+
const rawName = String(args?.name || 'default').trim()
|
|
143
|
+
const driveName = rawName.replace(/[^a-zA-Z0-9_-]/g, '_') || 'default'
|
|
144
|
+
const storePath = path.join(this.storageDir, driveName)
|
|
145
|
+
const store = initStorage(storePath)
|
|
146
|
+
const drive = new MeshDrive(store)
|
|
147
|
+
await drive.init()
|
|
148
|
+
|
|
149
|
+
const swarm = new MeshSwarm(store)
|
|
150
|
+
await swarm.join(drive.drive.discoveryKey, { server: true, client: true })
|
|
151
|
+
|
|
152
|
+
this.activeDrives.set(drive.keyHex, { drive, swarm, name: driveName, isSeeder: true })
|
|
153
|
+
|
|
154
|
+
return {
|
|
155
|
+
content: [
|
|
156
|
+
{
|
|
157
|
+
type: 'text',
|
|
158
|
+
text: JSON.stringify({
|
|
159
|
+
status: 'success',
|
|
160
|
+
message: `Hyperdrive "${driveName}" erfolgreich erstellt und im P2P-DHT veröffentlicht!`,
|
|
161
|
+
publicKey: drive.keyHex,
|
|
162
|
+
discoveryKey: b4a.toString(drive.drive.discoveryKey, 'hex')
|
|
163
|
+
}, null, 2)
|
|
164
|
+
}
|
|
165
|
+
]
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
case 'p2p_drive_connect': {
|
|
170
|
+
const targetKey = String(args?.key || '').trim().toLowerCase()
|
|
171
|
+
if (!/^[0-9a-f]{64}$/.test(targetKey)) {
|
|
172
|
+
throw new Error('Ungültiger Hyperdrive Public Key: Erwartet wird ein 64-stelliger hexadezimaler Key.')
|
|
173
|
+
}
|
|
174
|
+
if (this.activeDrives.has(targetKey)) {
|
|
175
|
+
return {
|
|
176
|
+
content: [{ type: 'text', text: JSON.stringify({ status: 'already_connected', key: targetKey }) }]
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const storePath = path.join(this.storageDir, `remote_${targetKey.slice(0, 8)}`)
|
|
181
|
+
const store = initStorage(storePath)
|
|
182
|
+
const drive = new MeshDrive(store, targetKey)
|
|
183
|
+
await drive.init()
|
|
184
|
+
|
|
185
|
+
const swarm = new MeshSwarm(store)
|
|
186
|
+
await swarm.join(drive.drive.discoveryKey, { server: false, client: true })
|
|
187
|
+
|
|
188
|
+
this.activeDrives.set(targetKey, { drive, swarm, name: `remote_${targetKey.slice(0, 8)}`, isSeeder: false })
|
|
189
|
+
|
|
190
|
+
return {
|
|
191
|
+
content: [
|
|
192
|
+
{
|
|
193
|
+
type: 'text',
|
|
194
|
+
text: JSON.stringify({
|
|
195
|
+
status: 'success',
|
|
196
|
+
message: `Erfolgreich mit Remote-Drive verbunden. Suche Peers im P2P-Netzwerk...`,
|
|
197
|
+
publicKey: targetKey
|
|
198
|
+
}, null, 2)
|
|
199
|
+
}
|
|
200
|
+
]
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
case 'p2p_file_write': {
|
|
205
|
+
const targetDrive = this.resolveDrive(args.key)
|
|
206
|
+
if (!targetDrive) throw new Error('Kein aktives Hyperdrive gefunden. Bitte zuerst erstellen oder verbinden.')
|
|
207
|
+
|
|
208
|
+
// 1. Rollen- & Pfad-Autorisierung
|
|
209
|
+
const agentRole = args.role || 'worker'
|
|
210
|
+
const auth = SecurityGuard.authorize(args.filePath, agentRole, 'write')
|
|
211
|
+
if (!auth.allowed) {
|
|
212
|
+
throw new Error(`[ACCESS DENIED] ${auth.reason}`)
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// 2. Payload Inspection auf Prompt Injection
|
|
216
|
+
const inspection = SecurityGuard.inspectPayload(args.content)
|
|
217
|
+
if (!inspection.safe) {
|
|
218
|
+
throw new Error(`[SECURITY ALERT] Schreibe-Vorgang blockiert! Erkannte Bedrohungen: ${inspection.threats.join(', ')}`)
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const res = await targetDrive.drive.writeFile(args.filePath, inspection.sanitized)
|
|
222
|
+
return {
|
|
223
|
+
content: [
|
|
224
|
+
{
|
|
225
|
+
type: 'text',
|
|
226
|
+
text: JSON.stringify({
|
|
227
|
+
status: 'success',
|
|
228
|
+
path: res.path,
|
|
229
|
+
bytesWritten: res.bytes,
|
|
230
|
+
version: res.version,
|
|
231
|
+
driveKey: targetDrive.drive.keyHex,
|
|
232
|
+
securityAudit: 'PASSED'
|
|
233
|
+
}, null, 2)
|
|
234
|
+
}
|
|
235
|
+
]
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
case 'p2p_file_read': {
|
|
240
|
+
const targetDrive = this.resolveDrive(args.key)
|
|
241
|
+
if (!targetDrive) throw new Error('Kein aktives Hyperdrive gefunden.')
|
|
242
|
+
|
|
243
|
+
const agentRole = args.role || 'worker'
|
|
244
|
+
const auth = SecurityGuard.authorize(args.filePath, agentRole, 'read')
|
|
245
|
+
if (!auth.allowed) {
|
|
246
|
+
throw new Error(`[ACCESS DENIED] ${auth.reason}`)
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const content = await targetDrive.drive.readFile(args.filePath)
|
|
250
|
+
if (content === null) {
|
|
251
|
+
return {
|
|
252
|
+
content: [{ type: 'text', text: JSON.stringify({ status: 'not_found', path: args.filePath }) }]
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// 3. Beim Lesen auf eingeschleuste Injections warnen
|
|
257
|
+
const readInspect = SecurityGuard.inspectPayload(content)
|
|
258
|
+
if (!readInspect.safe) {
|
|
259
|
+
return {
|
|
260
|
+
content: [{
|
|
261
|
+
type: 'text',
|
|
262
|
+
text: `[SECURITY WARNING: POTENTIAL PROMPT INJECTION DETECTED]\nBedrohungen: ${readInspect.threats.join(', ')}\n\n--- INHALT (UNFILTERT NICHT AUSFÜHREN) ---\n${content}`
|
|
263
|
+
}]
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
return {
|
|
268
|
+
content: [{ type: 'text', text: content }]
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
case 'p2p_drive_list': {
|
|
273
|
+
const targetDrive = this.resolveDrive(args.key)
|
|
274
|
+
if (!targetDrive) throw new Error('Kein aktives Hyperdrive gefunden.')
|
|
275
|
+
|
|
276
|
+
const files = await targetDrive.drive.listFiles(args.prefix || '/')
|
|
277
|
+
return {
|
|
278
|
+
content: [
|
|
279
|
+
{
|
|
280
|
+
type: 'text',
|
|
281
|
+
text: JSON.stringify({
|
|
282
|
+
status: 'success',
|
|
283
|
+
driveKey: targetDrive.drive.keyHex,
|
|
284
|
+
files
|
|
285
|
+
}, null, 2)
|
|
286
|
+
}
|
|
287
|
+
]
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
case 'p2p_swarm_status': {
|
|
292
|
+
const drivesStatus = []
|
|
293
|
+
for (const [key, item] of this.activeDrives.entries()) {
|
|
294
|
+
drivesStatus.push({
|
|
295
|
+
key,
|
|
296
|
+
name: item.name,
|
|
297
|
+
isSeeder: item.isSeeder,
|
|
298
|
+
version: item.drive.version,
|
|
299
|
+
activePeers: item.swarm.peerCount
|
|
300
|
+
})
|
|
301
|
+
}
|
|
302
|
+
return {
|
|
303
|
+
content: [
|
|
304
|
+
{
|
|
305
|
+
type: 'text',
|
|
306
|
+
text: JSON.stringify({ status: 'success', activeDrives: drivesStatus }, null, 2)
|
|
307
|
+
}
|
|
308
|
+
]
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
default:
|
|
313
|
+
throw new Error(`Unbekanntes Werkzeug: ${name}`)
|
|
314
|
+
}
|
|
315
|
+
} catch (err) {
|
|
316
|
+
return {
|
|
317
|
+
isError: true,
|
|
318
|
+
content: [{ type: 'text', text: `Fehler bei Ausführung von ${name}: ${err.message}` }]
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
})
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
resolveDrive(key) {
|
|
325
|
+
if (key) {
|
|
326
|
+
return this.activeDrives.get(key.trim().toLowerCase()) || null
|
|
327
|
+
}
|
|
328
|
+
// Falls kein Key angegeben: nimm das erste verfügbare Drive
|
|
329
|
+
if (this.activeDrives.size > 0) {
|
|
330
|
+
return this.activeDrives.values().next().value
|
|
331
|
+
}
|
|
332
|
+
return null
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
async start() {
|
|
336
|
+
const transport = new StdioServerTransport()
|
|
337
|
+
await this.server.connect(transport)
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
async close() {
|
|
341
|
+
for (const item of this.activeDrives.values()) {
|
|
342
|
+
await item.swarm.destroy()
|
|
343
|
+
await item.drive.close()
|
|
344
|
+
}
|
|
345
|
+
await this.server.close()
|
|
346
|
+
}
|
|
347
|
+
}
|