@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 +66 -0
- package/bin/concord-mcp.js +183 -0
- package/package.json +40 -0
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
|
+
}
|