@vdoninja/mcp 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/SKILL.md +90 -0
- package/assets/cloudflare/worker-proxy.js +89 -0
- package/assets/cloudflare/wrangler.toml.example +10 -0
- package/package.json +66 -0
- package/references/advanced-diagnostics.md +45 -0
- package/references/mcp-tool-contract.md +184 -0
- package/references/quickstart-and-compat.md +198 -0
- package/references/vdo-ninja-p2p-data-notes.md +156 -0
- package/scripts/demo-file-workflow.js +168 -0
- package/scripts/demo-message-workflow.js +167 -0
- package/scripts/install-mcp.js +302 -0
- package/scripts/lib/bridge-session.js +2331 -0
- package/scripts/lib/bridge-utils.js +185 -0
- package/scripts/lib/fake-network-sdk.js +293 -0
- package/scripts/lib/load-vdo-sdk.js +36 -0
- package/scripts/lib/mcp-stdio-parser.js +137 -0
- package/scripts/lib/tool-profiles.js +122 -0
- package/scripts/vdo-data-bridge.js +157 -0
- package/scripts/vdo-mcp-server.js +1425 -0
- package/scripts/vdo-mcp-streamable-http.js +270 -0
- package/server.json +134 -0
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const fs = require('node:fs');
|
|
5
|
+
const path = require('node:path');
|
|
6
|
+
const { spawnSync } = require('node:child_process');
|
|
7
|
+
|
|
8
|
+
const PRESETS = Object.freeze({
|
|
9
|
+
full: Object.freeze({
|
|
10
|
+
serverArgs: [],
|
|
11
|
+
description: 'Full tool surface (core + file + state).'
|
|
12
|
+
}),
|
|
13
|
+
core: Object.freeze({
|
|
14
|
+
serverArgs: ['--tool-profile', 'core'],
|
|
15
|
+
description: 'Core-only tool surface for messaging and sync.'
|
|
16
|
+
}),
|
|
17
|
+
file: Object.freeze({
|
|
18
|
+
serverArgs: ['--tool-profile', 'file'],
|
|
19
|
+
description: 'Core + file transfer tools.'
|
|
20
|
+
}),
|
|
21
|
+
state: Object.freeze({
|
|
22
|
+
serverArgs: ['--tool-profile', 'state'],
|
|
23
|
+
description: 'Core + shared state tools.'
|
|
24
|
+
}),
|
|
25
|
+
'secure-core': Object.freeze({
|
|
26
|
+
serverArgs: ['--tool-profile', 'core', '--enforce-join-token', 'true', '--require-session-mac', 'true'],
|
|
27
|
+
description: 'Core profile plus join-token enforcement and session MAC requirement.'
|
|
28
|
+
}),
|
|
29
|
+
'secure-full': Object.freeze({
|
|
30
|
+
serverArgs: ['--tool-profile', 'full', '--enforce-join-token', 'true', '--require-session-mac', 'true'],
|
|
31
|
+
description: 'Full profile plus join-token enforcement and session MAC requirement.'
|
|
32
|
+
})
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
function usage() {
|
|
36
|
+
const presetList = Object.keys(PRESETS)
|
|
37
|
+
.map((name) => ` - ${name}: ${PRESETS[name].description}`)
|
|
38
|
+
.join('\n');
|
|
39
|
+
const text = `
|
|
40
|
+
Usage:
|
|
41
|
+
vdon-mcp-install [options]
|
|
42
|
+
|
|
43
|
+
Options:
|
|
44
|
+
--uninstall Remove MCP server entries instead of installing.
|
|
45
|
+
--name <name> Server name. Default: vdo-ninja-mcp
|
|
46
|
+
--server-script <path> Path to MCP server script. Default: bundled scripts/vdo-mcp-server.js
|
|
47
|
+
--node-cmd <path_or_name> Node executable used in registered command. Default: current node
|
|
48
|
+
--preset <name> Registration preset. Default: full
|
|
49
|
+
--server-arg <value> Extra server argument (repeatable).
|
|
50
|
+
--codex-only Only apply Codex MCP configuration.
|
|
51
|
+
--claude-only Only apply Claude MCP configuration.
|
|
52
|
+
--claude-scope <scope> Claude scope: local|user|project. Default: local
|
|
53
|
+
--dry-run Print actions without executing.
|
|
54
|
+
-h, --help Show this help.
|
|
55
|
+
|
|
56
|
+
Presets:
|
|
57
|
+
${presetList}
|
|
58
|
+
`;
|
|
59
|
+
process.stdout.write(text.trim() + '\n');
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function parseArgs(argv) {
|
|
63
|
+
const out = {
|
|
64
|
+
action: 'install',
|
|
65
|
+
name: 'vdo-ninja-mcp',
|
|
66
|
+
serverScript: path.resolve(__dirname, 'vdo-mcp-server.js'),
|
|
67
|
+
nodeCmd: process.execPath,
|
|
68
|
+
preset: 'full',
|
|
69
|
+
serverArgs: [],
|
|
70
|
+
codex: true,
|
|
71
|
+
claude: true,
|
|
72
|
+
claudeScope: 'local',
|
|
73
|
+
dryRun: false
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
77
|
+
const arg = argv[i];
|
|
78
|
+
if (arg === '--uninstall') {
|
|
79
|
+
out.action = 'uninstall';
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
if (arg === '--name') {
|
|
83
|
+
out.name = argv[++i];
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
if (arg === '--server-script') {
|
|
87
|
+
out.serverScript = path.resolve(argv[++i]);
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
if (arg === '--node-cmd') {
|
|
91
|
+
out.nodeCmd = argv[++i];
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
if (arg === '--preset') {
|
|
95
|
+
out.preset = String(argv[++i] || '').trim().toLowerCase();
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
if (arg === '--server-arg') {
|
|
99
|
+
const value = argv[++i];
|
|
100
|
+
if (value === undefined) {
|
|
101
|
+
throw new Error('--server-arg requires a value');
|
|
102
|
+
}
|
|
103
|
+
out.serverArgs.push(String(value));
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
if (arg === '--codex-only') {
|
|
107
|
+
out.claude = false;
|
|
108
|
+
out.codex = true;
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
if (arg === '--claude-only') {
|
|
112
|
+
out.codex = false;
|
|
113
|
+
out.claude = true;
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
if (arg === '--claude-scope') {
|
|
117
|
+
out.claudeScope = String(argv[++i] || '').trim().toLowerCase();
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
if (arg === '--dry-run') {
|
|
121
|
+
out.dryRun = true;
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
if (arg === '--help' || arg === '-h') {
|
|
125
|
+
usage();
|
|
126
|
+
process.exit(0);
|
|
127
|
+
}
|
|
128
|
+
throw new Error(`Unknown argument: ${arg}`);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (!out.codex && !out.claude) {
|
|
132
|
+
throw new Error('No target selected (Codex/Claude).');
|
|
133
|
+
}
|
|
134
|
+
if (!out.name || !String(out.name).trim()) {
|
|
135
|
+
throw new Error('Server name is required.');
|
|
136
|
+
}
|
|
137
|
+
if (!['local', 'user', 'project'].includes(out.claudeScope)) {
|
|
138
|
+
throw new Error(`Invalid --claude-scope: ${out.claudeScope}`);
|
|
139
|
+
}
|
|
140
|
+
if (!PRESETS[out.preset]) {
|
|
141
|
+
throw new Error(`Invalid --preset: ${out.preset}`);
|
|
142
|
+
}
|
|
143
|
+
return out;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function runCommand(command, args, options = {}) {
|
|
147
|
+
const cmdLabel = `${command} ${args.join(' ')}`;
|
|
148
|
+
if (options.dryRun) {
|
|
149
|
+
process.stdout.write(`[dry-run] ${cmdLabel}\n`);
|
|
150
|
+
return { ok: true, code: 0, stdout: '', stderr: '' };
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const result = spawnSync(command, args, {
|
|
154
|
+
encoding: 'utf8',
|
|
155
|
+
env: options.env || process.env,
|
|
156
|
+
cwd: options.cwd || process.cwd()
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
const code = Number.isInteger(result.status) ? result.status : 1;
|
|
160
|
+
const stdout = result.stdout || '';
|
|
161
|
+
const stderr = result.stderr || '';
|
|
162
|
+
if (!options.quiet && stdout.trim()) process.stdout.write(stdout);
|
|
163
|
+
if (!options.quiet && stderr.trim()) process.stderr.write(stderr);
|
|
164
|
+
return {
|
|
165
|
+
ok: code === 0,
|
|
166
|
+
code,
|
|
167
|
+
stdout,
|
|
168
|
+
stderr
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function assertBinaryExists(name, options) {
|
|
173
|
+
const check = runCommand(name, ['--version'], {
|
|
174
|
+
dryRun: options.dryRun,
|
|
175
|
+
quiet: true,
|
|
176
|
+
env: options.env,
|
|
177
|
+
cwd: options.cwd
|
|
178
|
+
});
|
|
179
|
+
if (!check.ok) {
|
|
180
|
+
throw new Error(`Required command not found or not working: ${name}`);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function codexServerExists(name, options) {
|
|
185
|
+
if (options.dryRun) return false;
|
|
186
|
+
const result = runCommand('codex', ['mcp', 'get', name, '--json'], {
|
|
187
|
+
dryRun: options.dryRun,
|
|
188
|
+
quiet: true,
|
|
189
|
+
env: options.env,
|
|
190
|
+
cwd: options.cwd
|
|
191
|
+
});
|
|
192
|
+
return result.ok;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function claudeServerExists(name, options) {
|
|
196
|
+
if (options.dryRun) return false;
|
|
197
|
+
const result = runCommand('claude', ['mcp', 'get', name], {
|
|
198
|
+
dryRun: options.dryRun,
|
|
199
|
+
quiet: true,
|
|
200
|
+
env: options.env,
|
|
201
|
+
cwd: options.cwd
|
|
202
|
+
});
|
|
203
|
+
return result.ok;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function installCodex(config, options) {
|
|
207
|
+
process.stdout.write(`Installing Codex MCP server '${config.name}'...\n`);
|
|
208
|
+
if (codexServerExists(config.name, options)) {
|
|
209
|
+
runCommand('codex', ['mcp', 'remove', config.name], options);
|
|
210
|
+
}
|
|
211
|
+
const add = runCommand('codex', ['mcp', 'add', config.name, '--', config.nodeCmd, config.serverScript, ...config.serverArgs], options);
|
|
212
|
+
if (!add.ok) throw new Error(`Failed to add Codex MCP server '${config.name}'.`);
|
|
213
|
+
runCommand('codex', ['mcp', 'get', config.name, '--json'], options);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function uninstallCodex(config, options) {
|
|
217
|
+
process.stdout.write(`Removing Codex MCP server '${config.name}'...\n`);
|
|
218
|
+
if (!codexServerExists(config.name, options)) {
|
|
219
|
+
process.stdout.write('Codex MCP server not found; skipping.\n');
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
const remove = runCommand('codex', ['mcp', 'remove', config.name], options);
|
|
223
|
+
if (!remove.ok) throw new Error(`Failed to remove Codex MCP server '${config.name}'.`);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function installClaude(config, options) {
|
|
227
|
+
process.stdout.write(`Installing Claude MCP server '${config.name}' (scope=${config.claudeScope})...\n`);
|
|
228
|
+
if (claudeServerExists(config.name, options)) {
|
|
229
|
+
runCommand('claude', ['mcp', 'remove', config.name], options);
|
|
230
|
+
}
|
|
231
|
+
const add = runCommand(
|
|
232
|
+
'claude',
|
|
233
|
+
['mcp', 'add', '-s', config.claudeScope, config.name, '--', config.nodeCmd, config.serverScript, ...config.serverArgs],
|
|
234
|
+
options
|
|
235
|
+
);
|
|
236
|
+
if (!add.ok) throw new Error(`Failed to add Claude MCP server '${config.name}'.`);
|
|
237
|
+
runCommand('claude', ['mcp', 'get', config.name], options);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function uninstallClaude(config, options) {
|
|
241
|
+
process.stdout.write(`Removing Claude MCP server '${config.name}'...\n`);
|
|
242
|
+
if (!claudeServerExists(config.name, options)) {
|
|
243
|
+
process.stdout.write('Claude MCP server not found; skipping.\n');
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
const remove = runCommand('claude', ['mcp', 'remove', config.name], options);
|
|
247
|
+
if (!remove.ok) throw new Error(`Failed to remove Claude MCP server '${config.name}'.`);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function ensureInputs(config) {
|
|
251
|
+
if (!fs.existsSync(config.serverScript)) {
|
|
252
|
+
throw new Error(`Server script not found: ${config.serverScript}`);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function applyPreset(config) {
|
|
257
|
+
const preset = PRESETS[config.preset];
|
|
258
|
+
const mergedServerArgs = [...preset.serverArgs, ...config.serverArgs];
|
|
259
|
+
return {
|
|
260
|
+
...config,
|
|
261
|
+
serverArgs: mergedServerArgs
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
function main() {
|
|
266
|
+
let config = parseArgs(process.argv.slice(2));
|
|
267
|
+
config = applyPreset(config);
|
|
268
|
+
ensureInputs(config);
|
|
269
|
+
|
|
270
|
+
const options = {
|
|
271
|
+
dryRun: config.dryRun,
|
|
272
|
+
env: process.env,
|
|
273
|
+
cwd: process.cwd()
|
|
274
|
+
};
|
|
275
|
+
|
|
276
|
+
process.stdout.write(`Using preset '${config.preset}' with server args: ${config.serverArgs.join(' ') || '(none)'}\n`);
|
|
277
|
+
if (config.preset.startsWith('secure') && !process.env.VDON_MCP_JOIN_TOKEN_SECRET) {
|
|
278
|
+
process.stdout.write('Warning: secure preset selected but VDON_MCP_JOIN_TOKEN_SECRET is not set in this shell.\n');
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
if (config.codex) assertBinaryExists('codex', options);
|
|
282
|
+
if (config.claude) assertBinaryExists('claude', options);
|
|
283
|
+
|
|
284
|
+
if (config.action === 'install') {
|
|
285
|
+
if (config.codex) installCodex(config, options);
|
|
286
|
+
if (config.claude) installClaude(config, options);
|
|
287
|
+
} else {
|
|
288
|
+
if (config.codex) uninstallCodex(config, options);
|
|
289
|
+
if (config.claude) uninstallClaude(config, options);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
process.stdout.write('\nDone.\n');
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
if (require.main === module) {
|
|
296
|
+
try {
|
|
297
|
+
main();
|
|
298
|
+
} catch (error) {
|
|
299
|
+
process.stderr.write(`${error.message}\n`);
|
|
300
|
+
process.exit(1);
|
|
301
|
+
}
|
|
302
|
+
}
|