@thisness.ai/concord-mcp 0.1.2

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 ADDED
@@ -0,0 +1,66 @@
1
+ # @thisness.ai/concord-mcp
2
+
3
+ Wrapper MCP léger pour [Concord](https://codeberg.org/DDS-Genki/Concord-beta).
4
+ Télécharge automatiquement le binaire compilé correspondant à votre
5
+ plateforme depuis Codeberg releases, vérifie sha256, et expose le
6
+ serveur MCP stdio.
7
+
8
+ ## Installation et usage
9
+
10
+ Pas d'installation préalable. Configurez votre client MCP avec :
11
+
12
+ ```json
13
+ {
14
+ "mcpServers": {
15
+ "concord": {
16
+ "command": "npx",
17
+ "args": ["-y", "@thisness.ai/concord-mcp@latest"]
18
+ }
19
+ }
20
+ }
21
+ ```
22
+
23
+ Au premier lancement, le wrapper télécharge le binaire (~100 MB) depuis
24
+ les [releases Codeberg](https://codeberg.org/DDS-Genki/Concord-beta/releases),
25
+ vérifie son sha256, et le met en cache à `~/.cache/concord-mcp/v<version>/`.
26
+ Les lancements suivants sont instantanés.
27
+
28
+ ## Plateformes supportées
29
+
30
+ - Linux x64
31
+ - Linux ARM64
32
+ - macOS Apple Silicon (M1+)
33
+ - macOS Intel
34
+ - Windows x64
35
+
36
+ ## Prérequis runtime
37
+
38
+ Concord lance un service caché Tor pour exposer son endpoint. Installez
39
+ `tor` :
40
+
41
+ ```bash
42
+ # Debian / Ubuntu
43
+ sudo apt install tor
44
+
45
+ # macOS
46
+ brew install tor
47
+
48
+ # Windows : WSL2 recommandé pour la phase bêta (voir docs/INSTALL.md du repo)
49
+ ```
50
+
51
+ ## Variables d'environnement
52
+
53
+ - `CONCORD_MCP_CACHE` : override le dossier de cache (défaut `~/.cache/concord-mcp/`).
54
+ - `CONCORD_ROOT` : override le dossier d'état Concord (défaut `~/.concord/`).
55
+ - Voir le binaire `concord` pour les autres variables.
56
+
57
+ ## Doc complète
58
+
59
+ - [README protocole](https://codeberg.org/DDS-Genki/Concord-beta/src/branch/main/README.md)
60
+ - [docs/INSTALL.md](https://codeberg.org/DDS-Genki/Concord-beta/src/branch/main/docs/INSTALL.md) — install détaillée + modus operandi de test entre deux instances
61
+ - [docs/PROTOCOL.md](https://codeberg.org/DDS-Genki/Concord-beta/src/branch/main/docs/PROTOCOL.md) — vocabulaire, cycles de vie, opcodes
62
+
63
+ ## Licence
64
+
65
+ Voir [LICENSE](https://codeberg.org/DDS-Genki/Concord-beta/src/branch/main/LICENSE)
66
+ du repo principal (PolyForm Noncommercial 1.0.0).
@@ -0,0 +1,183 @@
1
+ #!/usr/bin/env node
2
+ // Concord MCP wrapper — détecte plateforme, télécharge le binaire compilé
3
+ // depuis Codeberg releases (compressé gzip), vérifie sha256 du .gz,
4
+ // décompresse vers le cache, puis spawn le binaire en stdio MCP inherit.
5
+ //
6
+ // Cache : ~/.cache/concord-mcp/v<version>/concord[.exe]
7
+ // Source : https://codeberg.org/DDS-Genki/Concord-beta/releases/download/Concord-<ver>/concord-<plat>-v<ver>[.exe].gz
8
+ //
9
+ // Compression gzip choisie pour respecter la limite Codeberg 100 MB par
10
+ // asset (binaire 100-120 MB → 25-45 MB compressé). Décompression native
11
+ // via node:zlib, zéro dépendance.
12
+
13
+ import { createHash } from 'node:crypto';
14
+ import { createWriteStream, existsSync, mkdirSync, chmodSync, readFileSync } from 'node:fs';
15
+ import { homedir } from 'node:os';
16
+ import { join } from 'node:path';
17
+ import { pipeline } from 'node:stream/promises';
18
+ import { spawn } from 'node:child_process';
19
+ import { fileURLToPath } from 'node:url';
20
+ import { createGunzip } from 'node:zlib';
21
+ import { Readable } from 'node:stream';
22
+
23
+ const __dirname = fileURLToPath(new URL('.', import.meta.url));
24
+ const pkgPath = join(__dirname, '..', 'package.json');
25
+ const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'));
26
+ const VERSION = pkg.version;
27
+
28
+ const REPO_BASE = 'https://codeberg.org/DDS-Genki/Concord-beta/releases/download';
29
+ // Codeberg tag format : Concord-X.Y.Z (cohérent avec naming des releases)
30
+ const TAG_PREFIX = 'Concord-';
31
+
32
+ const PLATFORM_MAP = {
33
+ 'linux-x64': { name: `concord-linux-x64-v${VERSION}`, ext: '' },
34
+ 'linux-arm64': { name: `concord-linux-arm64-v${VERSION}`, ext: '' },
35
+ 'darwin-x64': { name: `concord-darwin-x64-v${VERSION}`, ext: '' },
36
+ 'darwin-arm64': { name: `concord-darwin-arm64-v${VERSION}`, ext: '' },
37
+ 'win32-x64': { name: `concord-windows-x64-v${VERSION}`, ext: '.exe' },
38
+ };
39
+
40
+ function log(level, msg) {
41
+ // stderr pour ne pas polluer stdio MCP (stdout)
42
+ process.stderr.write(`[concord-mcp ${level}] ${msg}\n`);
43
+ }
44
+
45
+ function detectPlatform() {
46
+ const key = `${process.platform}-${process.arch}`;
47
+ const entry = PLATFORM_MAP[key];
48
+ if (!entry) {
49
+ log('error', `plateforme non supportée: ${key}. Supportées: ${Object.keys(PLATFORM_MAP).join(', ')}`);
50
+ process.exit(2);
51
+ }
52
+ return entry;
53
+ }
54
+
55
+ function cacheDir() {
56
+ const base = process.env['CONCORD_MCP_CACHE'] ?? join(homedir(), '.cache', 'concord-mcp');
57
+ return join(base, `v${VERSION}`);
58
+ }
59
+
60
+ async function fetchToFile(url, destPath) {
61
+ const res = await fetch(url);
62
+ if (!res.ok) {
63
+ throw new Error(`HTTP ${res.status} ${res.statusText} sur ${url}`);
64
+ }
65
+ const out = createWriteStream(destPath);
66
+ await pipeline(res.body, out);
67
+ }
68
+
69
+ async function fetchText(url) {
70
+ const res = await fetch(url);
71
+ if (!res.ok) {
72
+ throw new Error(`HTTP ${res.status} ${res.statusText} sur ${url}`);
73
+ }
74
+ return await res.text();
75
+ }
76
+
77
+ function sha256File(path) {
78
+ const hash = createHash('sha256');
79
+ hash.update(readFileSync(path));
80
+ return hash.digest('hex');
81
+ }
82
+
83
+ async function ensureBinary() {
84
+ const { name, ext } = detectPlatform();
85
+ const dir = cacheDir();
86
+ const binPath = join(dir, `concord${ext}`);
87
+ const gzPath = `${binPath}.gz`;
88
+
89
+ if (existsSync(binPath)) {
90
+ return binPath;
91
+ }
92
+
93
+ mkdirSync(dir, { recursive: true });
94
+
95
+ const gzAssetName = `${name}${ext}.gz`;
96
+ const binUrl = `${REPO_BASE}/${TAG_PREFIX}${VERSION}/${gzAssetName}`;
97
+ const shaUrl = `${REPO_BASE}/${TAG_PREFIX}${VERSION}/${gzAssetName}.sha256`;
98
+
99
+ log('info', `download ${gzAssetName} (~25-45MB première fois, mis en cache après décompression)`);
100
+ log('info', `from ${binUrl}`);
101
+
102
+ try {
103
+ await fetchToFile(binUrl, gzPath);
104
+ } catch (err) {
105
+ log('error', `téléchargement échoué: ${err.message}`);
106
+ log('error', `vérifier que la release Concord-${VERSION} contient ${gzAssetName}`);
107
+ process.exit(3);
108
+ }
109
+
110
+ // Vérification sha256 sur le .gz (le binaire décompressé sera vérifié implicitement par l'OS au chargement)
111
+ try {
112
+ const shaText = await fetchText(shaUrl);
113
+ const expectedHash = shaText.trim().split(/\s+/)[0];
114
+ const actualHash = sha256File(gzPath);
115
+ if (expectedHash !== actualHash) {
116
+ log('error', `sha256 mismatch sur ${gzAssetName}: attendu ${expectedHash}, obtenu ${actualHash}`);
117
+ process.exit(4);
118
+ }
119
+ log('info', `sha256 OK (${actualHash.slice(0, 12)}...)`);
120
+ } catch (err) {
121
+ log('warn', `vérification sha256 échouée: ${err.message}. Archive conservée sans vérification.`);
122
+ }
123
+
124
+ // Décompression .gz → binaire
125
+ log('info', `décompression gzip vers ${binPath}`);
126
+ const { createReadStream, unlinkSync } = await import('node:fs');
127
+ try {
128
+ await pipeline(createReadStream(gzPath), createGunzip(), createWriteStream(binPath));
129
+ } catch (err) {
130
+ log('error', `décompression échouée: ${err.message}`);
131
+ process.exit(6);
132
+ }
133
+
134
+ // Cleanup .gz une fois décompressé (gain de place cache)
135
+ try {
136
+ unlinkSync(gzPath);
137
+ } catch {
138
+ // best-effort, pas bloquant
139
+ }
140
+
141
+ // chmod +x sur Unix
142
+ if (ext !== '.exe') {
143
+ chmodSync(binPath, 0o755);
144
+ }
145
+
146
+ log('info', `prêt: ${binPath}`);
147
+ return binPath;
148
+ }
149
+
150
+ async function main() {
151
+ const binPath = await ensureBinary();
152
+
153
+ // Spawn en stdio inherit (pas-through MCP stdio)
154
+ const child = spawn(binPath, process.argv.slice(2), {
155
+ stdio: 'inherit',
156
+ env: process.env,
157
+ });
158
+
159
+ // Propager signaux (SIGTERM, SIGINT)
160
+ const forward = (sig) => {
161
+ if (!child.killed) child.kill(sig);
162
+ };
163
+ process.on('SIGTERM', () => forward('SIGTERM'));
164
+ process.on('SIGINT', () => forward('SIGINT'));
165
+
166
+ child.on('exit', (code, signal) => {
167
+ if (signal) {
168
+ process.kill(process.pid, signal);
169
+ } else {
170
+ process.exit(code ?? 0);
171
+ }
172
+ });
173
+
174
+ child.on('error', (err) => {
175
+ log('error', `exec échec: ${err.message}`);
176
+ process.exit(5);
177
+ });
178
+ }
179
+
180
+ main().catch((err) => {
181
+ log('error', `fatal: ${err.message}`);
182
+ process.exit(1);
183
+ });
package/package.json ADDED
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "@thisness.ai/concord-mcp",
3
+ "version": "0.1.2",
4
+ "description": "Concord MCP — auto-fetch binaire prebuild Codeberg + spawn stdio MCP. Wrapper léger autour de concord-standalone.",
5
+ "type": "module",
6
+ "bin": {
7
+ "concord-mcp": "./bin/concord-mcp.js"
8
+ },
9
+ "files": [
10
+ "bin/",
11
+ "README.md",
12
+ "LICENSE"
13
+ ],
14
+ "engines": {
15
+ "node": ">=18"
16
+ },
17
+ "repository": {
18
+ "type": "git",
19
+ "url": "https://codeberg.org/DDS-Genki/Concord-beta.git",
20
+ "directory": "apps/mcp-wrapper"
21
+ },
22
+ "homepage": "https://codeberg.org/DDS-Genki/Concord-beta",
23
+ "bugs": "https://codeberg.org/DDS-Genki/Concord-beta/issues",
24
+ "license": "SEE LICENSE IN LICENSE",
25
+ "author": "Andréa Vistoli (Thisness.ai)",
26
+ "keywords": [
27
+ "mcp",
28
+ "model-context-protocol",
29
+ "concord",
30
+ "thisness",
31
+ "agent",
32
+ "framework-to-framework",
33
+ "tor",
34
+ "ed25519",
35
+ "polkadot"
36
+ ],
37
+ "publishConfig": {
38
+ "access": "public"
39
+ }
40
+ }