@vdoninja/mcp 0.4.0 → 0.4.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.
@@ -0,0 +1,251 @@
1
+ # VDO.Ninja MCP Client Config Examples
2
+
3
+ Ready-to-paste config for popular MCP clients.
4
+
5
+ ## Prereqs (No Clone)
6
+
7
+ Install in the project/folder where you want to use the MCP:
8
+
9
+ ```bash
10
+ npm i @vdoninja/mcp
11
+ ```
12
+
13
+ Server script path used below:
14
+
15
+ - Linux/macOS: `./node_modules/@vdoninja/mcp/scripts/vdo-mcp-server.js`
16
+ - Windows: `.\node_modules\@vdoninja\mcp\scripts\vdo-mcp-server.js`
17
+
18
+ Use an absolute path if your client launches from another working directory.
19
+
20
+ ## Onboarding Pattern (What Works Best)
21
+
22
+ Popular MCP servers with strong onboarding docs generally do these things:
23
+
24
+ 1. Give one copy/paste install path.
25
+ 2. Provide per-client config snippets.
26
+ 3. Tell users the first tool call to run.
27
+ 4. Show one deterministic "it works" workflow with expected output.
28
+
29
+ This file follows that same pattern.
30
+
31
+ ## Generic `stdio` MCP Template
32
+
33
+ Most MCP clients need the same three things:
34
+
35
+ 1. `command` (usually `node`)
36
+ 2. `args` (script path + optional flags)
37
+ 3. `env` (optional)
38
+
39
+ Example:
40
+
41
+ ```json
42
+ {
43
+ "command": "node",
44
+ "args": [
45
+ "/ABSOLUTE/PATH/node_modules/@vdoninja/mcp/scripts/vdo-mcp-server.js",
46
+ "--tool-profile",
47
+ "core"
48
+ ],
49
+ "env": {
50
+ "VDON_MCP_TOOL_PROFILE": "core"
51
+ }
52
+ }
53
+ ```
54
+
55
+ ## Cursor
56
+
57
+ Cursor reads MCP config from:
58
+
59
+ - Global: `~/.cursor/mcp.json`
60
+ - Project: `.cursor/mcp.json`
61
+
62
+ Example:
63
+
64
+ ```json
65
+ {
66
+ "mcpServers": {
67
+ "vdo-ninja-mcp": {
68
+ "command": "node",
69
+ "args": [
70
+ "/ABSOLUTE/PATH/node_modules/@vdoninja/mcp/scripts/vdo-mcp-server.js"
71
+ ],
72
+ "env": {
73
+ "VDON_MCP_TOOL_PROFILE": "core"
74
+ }
75
+ }
76
+ }
77
+ }
78
+ ```
79
+
80
+ ## Gemini CLI
81
+
82
+ Gemini CLI supports MCP via `mcpServers` in `settings.json`.
83
+
84
+ Common config file locations:
85
+
86
+ - Project: `.gemini/settings.json`
87
+ - Linux/macOS: `~/.gemini/settings.json`
88
+ - Windows: `%USERPROFILE%\\.gemini\\settings.json`
89
+
90
+ Example:
91
+
92
+ ```json
93
+ {
94
+ "mcpServers": {
95
+ "vdo-ninja-mcp": {
96
+ "command": "node",
97
+ "args": [
98
+ "/ABSOLUTE/PATH/node_modules/@vdoninja/mcp/scripts/vdo-mcp-server.js"
99
+ ],
100
+ "env": {
101
+ "VDON_MCP_TOOL_PROFILE": "core"
102
+ }
103
+ }
104
+ }
105
+ }
106
+ ```
107
+
108
+ After restarting Gemini CLI, use `/mcp` to confirm server status.
109
+
110
+ ## OpenCode
111
+
112
+ OpenCode MCP config lives under `mcp` in OpenCode config.
113
+
114
+ Common config file locations:
115
+
116
+ - Global: `~/.config/opencode/opencode.json`
117
+ - Project: `opencode.json` (project root)
118
+
119
+ Example:
120
+
121
+ ```json
122
+ {
123
+ "$schema": "https://opencode.ai/config.json",
124
+ "mcp": {
125
+ "vdo-ninja-mcp": {
126
+ "type": "local",
127
+ "command": [
128
+ "node",
129
+ "/ABSOLUTE/PATH/node_modules/@vdoninja/mcp/scripts/vdo-mcp-server.js",
130
+ "--tool-profile",
131
+ "core"
132
+ ],
133
+ "enabled": true,
134
+ "environment": {
135
+ "VDON_MCP_TOOL_PROFILE": "core"
136
+ }
137
+ }
138
+ }
139
+ }
140
+ ```
141
+
142
+ ## Codex CLI and Claude Code (CLI Automation)
143
+
144
+ Auto-register if either/both CLIs are installed:
145
+
146
+ ```bash
147
+ npx vdon-mcp-install
148
+ ```
149
+
150
+ If only one CLI is installed:
151
+
152
+ ```bash
153
+ npx vdon-mcp-install --codex-only
154
+ npx vdon-mcp-install --claude-only
155
+ ```
156
+
157
+ ## Sanity Check (All Clients)
158
+
159
+ First tool call should be:
160
+
161
+ ```json
162
+ { "name": "vdo_capabilities", "arguments": {} }
163
+ ```
164
+
165
+ Then connect:
166
+
167
+ ```json
168
+ { "name": "vdo_connect", "arguments": { "room": "lab_1" } }
169
+ ```
170
+
171
+ If `vdo_capabilities` works, your MCP wiring is correct.
172
+
173
+ ## Context Budget Note
174
+
175
+ Adding MCP servers increases tool/context footprint. Start with one MCP and least-privilege profile (`core`) first, then add more only as needed.
176
+
177
+ ## First Functional Workflows (Clear Result)
178
+
179
+ Use unique room names to avoid collisions.
180
+
181
+ ### Codex to Codex (Two Terminals)
182
+
183
+ 1. Install/register once:
184
+
185
+ ```bash
186
+ npm i @vdoninja/mcp
187
+ npx vdon-mcp-install --codex-only
188
+ ```
189
+
190
+ 2. Open two Codex sessions (`A` and `B`) with MCP enabled.
191
+ 3. In session `A`, call:
192
+
193
+ ```json
194
+ { "name": "vdo_connect", "arguments": { "room": "codex_lab_001", "stream_id": "codex_a" } }
195
+ ```
196
+
197
+ 4. In session `B`, call:
198
+
199
+ ```json
200
+ { "name": "vdo_connect", "arguments": { "room": "codex_lab_001", "stream_id": "codex_b", "target_stream_id": "codex_a" } }
201
+ ```
202
+
203
+ 5. In session `A`, send:
204
+
205
+ ```json
206
+ {
207
+ "name": "vdo_send",
208
+ "arguments": {
209
+ "session_id": "<codex_a_session_id>",
210
+ "data": { "type": "chat.message", "text": "hello from codex_a" }
211
+ }
212
+ }
213
+ ```
214
+
215
+ 6. In session `B`, receive:
216
+
217
+ ```json
218
+ {
219
+ "name": "vdo_receive",
220
+ "arguments": {
221
+ "session_id": "<codex_b_session_id>",
222
+ "wait_ms": 1000
223
+ }
224
+ }
225
+ ```
226
+
227
+ Expected result: a `data_received` event containing `"hello from codex_a"`.
228
+
229
+ ### Claude to Claude (Two Terminals)
230
+
231
+ 1. Install/register once:
232
+
233
+ ```bash
234
+ npm i @vdoninja/mcp
235
+ npx vdon-mcp-install --claude-only
236
+ ```
237
+
238
+ 2. Repeat the same tool flow as above with a new room name, e.g. `claude_lab_001`.
239
+
240
+ Expected result: the second Claude session receives the message in `vdo_receive` with `event.type = "data_received"`.
241
+
242
+ ### Fast Scripted Proof (No Manual Tool Calls)
243
+
244
+ If you just want a quick pass/fail:
245
+
246
+ ```bash
247
+ npx vdon-mcp-demo-message
248
+ npx vdon-mcp-demo-file
249
+ ```
250
+
251
+ Expected result: each script exits successfully and reports message/file workflow completion.
@@ -2,9 +2,9 @@
2
2
 
3
3
  Implemented by:
4
4
 
5
- - `MCP/scripts/vdo-mcp-server.js`
6
- - Installer automation: `MCP/scripts/install-mcp.js`
7
- - Discovery manifest: `MCP/server.json`
5
+ - `scripts/vdo-mcp-server.js`
6
+ - Installer automation: `scripts/install-mcp.js`
7
+ - Discovery manifest: `server.json`
8
8
 
9
9
  ## Transport
10
10
 
@@ -23,7 +23,7 @@ Implemented by:
23
23
 
24
24
  ## Server CLI Flags
25
25
 
26
- Supported by `MCP/scripts/vdo-mcp-server.js`:
26
+ Supported by `scripts/vdo-mcp-server.js`:
27
27
 
28
28
  - `--tool-profile <core|file|state|full>`
29
29
  - `--max-message-bytes <bytes>`
@@ -140,7 +140,7 @@ Tools return `isError: true` with:
140
140
  Automated registration:
141
141
 
142
142
  ```bash
143
- npm i @vdoninja/mcp @roamhq/wrtc
143
+ npm i @vdoninja/mcp
144
144
  npx vdon-mcp-install
145
145
  ```
146
146
 
@@ -13,7 +13,7 @@ This is the fast path for users and AI agents.
13
13
  Install locally (no clone), including a Node WebRTC implementation:
14
14
 
15
15
  ```bash
16
- npm i @vdoninja/mcp @roamhq/wrtc
16
+ npm i @vdoninja/mcp
17
17
  ```
18
18
 
19
19
  Register MCP for Codex/Claude:
@@ -28,6 +28,9 @@ Start the local MCP server:
28
28
  npx vdon-mcp-server
29
29
  ```
30
30
 
31
+ If started directly in a terminal, it prints a short `Server running (stdio)` banner and a first-call hint.
32
+ If started by an MCP client, no interactive banner may appear, which is normal.
33
+
31
34
  Optional least-privilege profile:
32
35
 
33
36
  ```bash
@@ -38,7 +41,7 @@ Repo-local equivalents still work when developing in this repo:
38
41
 
39
42
  ```bash
40
43
  npm run mcp:install
41
- node MCP/scripts/vdo-mcp-server.js
44
+ node scripts/vdo-mcp-server.js
42
45
  ```
43
46
 
44
47
  AI-first sanity check (recommended first call):
@@ -186,13 +189,19 @@ npm run test:mcp:all
186
189
 
187
190
  ## Compatibility Notes
188
191
 
189
- - Codex and Claude Code both work via command-based local MCP registration.
190
- - Registry metadata is in `MCP/server.json`.
191
- - Tool contracts are in `MCP/references/mcp-tool-contract.md`.
192
+ - Works with any MCP client that supports local `stdio` transport (Codex CLI, Claude Code, Cursor, and other MCP-compatible CLIs).
193
+ - Codex and Claude can be auto-registered via `npx vdon-mcp-install`.
194
+ - Cursor and other GUI tools typically need manual MCP JSON config using:
195
+ - `command`: your Node executable
196
+ - `args`: path to `.../node_modules/@vdoninja/mcp/scripts/vdo-mcp-server.js` plus optional flags
197
+ - Ready-to-paste per-client examples are in `references/client-config-examples.md`.
198
+ - Registry metadata is in `server.json`.
199
+ - Tool contracts are in `references/mcp-tool-contract.md`.
192
200
  - `@vdoninja/mcp` depends on `@vdoninja/sdk`, so SDK is installed transitively.
201
+ - License model matches SDK packaging: AGPL-3.0-only with an additional unmodified-distribution exception (`LICENSE-SDK-EXCEPTION`).
193
202
 
194
203
  ## Optional Advanced Diagnostics
195
204
 
196
205
  For soak/matrix/preset-matrix/sweep tuning and all reliability knobs, use:
197
206
 
198
- - `MCP/references/advanced-diagnostics.md`
207
+ - `references/advanced-diagnostics.md`
@@ -71,7 +71,7 @@ This file captures operational constraints from this repository's SDK docs.
71
71
 
72
72
  ## Bridge Runtime Knobs
73
73
 
74
- `MCP/scripts/vdo-data-bridge.js` supports:
74
+ `scripts/vdo-data-bridge.js` supports:
75
75
 
76
76
  - `VDON_ROOM` (required in real deployments)
77
77
  - `VDON_STREAM_ID` (optional; random if omitted)
@@ -90,10 +90,22 @@ async function main() {
90
90
  serverA.callTool('vdo_status', { session_id: sessionA }),
91
91
  serverB.callTool('vdo_status', { session_id: sessionB })
92
92
  ]);
93
- return statusA.connected && statusB.connected;
93
+ return (
94
+ statusA.connected &&
95
+ statusB.connected &&
96
+ statusA.peer_count > 0 &&
97
+ statusB.peer_count > 0
98
+ );
94
99
  }, connectTimeoutMs, 200);
95
100
  if (!connected) {
96
- throw new Error('demo-file connect timeout');
101
+ const [statusA, statusB] = await Promise.all([
102
+ serverA.callTool('vdo_status', { session_id: sessionA }),
103
+ serverB.callTool('vdo_status', { session_id: sessionB })
104
+ ]);
105
+ throw new Error(
106
+ `demo-file connect timeout (peer_count a=${statusA.peer_count} b=${statusB.peer_count} ` +
107
+ `connected a=${statusA.connected} b=${statusB.connected})`
108
+ );
97
109
  }
98
110
 
99
111
  const payload = Buffer.from(fileText, 'utf8').toString('base64');
@@ -162,7 +174,11 @@ async function main() {
162
174
  }
163
175
  }
164
176
 
165
- main().catch((error) => {
166
- console.error(error.message);
167
- process.exit(1);
168
- });
177
+ main()
178
+ .then(() => {
179
+ process.exit(0);
180
+ })
181
+ .catch((error) => {
182
+ console.error(error.message);
183
+ process.exit(1);
184
+ });
@@ -94,10 +94,22 @@ async function main() {
94
94
  serverA.callTool('vdo_status', { session_id: sessionA }),
95
95
  serverB.callTool('vdo_status', { session_id: sessionB })
96
96
  ]);
97
- return statusA.connected && statusB.connected;
97
+ return (
98
+ statusA.connected &&
99
+ statusB.connected &&
100
+ statusA.peer_count > 0 &&
101
+ statusB.peer_count > 0
102
+ );
98
103
  }, connectTimeoutMs, 200);
99
104
  if (!connected) {
100
- throw new Error('demo-message connect timeout');
105
+ const [statusA, statusB] = await Promise.all([
106
+ serverA.callTool('vdo_status', { session_id: sessionA }),
107
+ serverB.callTool('vdo_status', { session_id: sessionB })
108
+ ]);
109
+ throw new Error(
110
+ `demo-message connect timeout (peer_count a=${statusA.peer_count} b=${statusB.peer_count} ` +
111
+ `connected a=${statusA.connected} b=${statusB.connected})`
112
+ );
101
113
  }
102
114
 
103
115
  const id = `demo_msg_${Date.now()}`;
@@ -161,7 +173,11 @@ async function main() {
161
173
  }
162
174
  }
163
175
 
164
- main().catch((error) => {
165
- console.error(error.message);
166
- process.exit(1);
167
- });
176
+ main()
177
+ .then(() => {
178
+ process.exit(0);
179
+ })
180
+ .catch((error) => {
181
+ console.error(error.message);
182
+ process.exit(1);
183
+ });
@@ -69,6 +69,7 @@ function parseArgs(argv) {
69
69
  serverArgs: [],
70
70
  codex: true,
71
71
  claude: true,
72
+ targetSelectionExplicit: false,
72
73
  claudeScope: 'local',
73
74
  dryRun: false
74
75
  };
@@ -106,11 +107,13 @@ function parseArgs(argv) {
106
107
  if (arg === '--codex-only') {
107
108
  out.claude = false;
108
109
  out.codex = true;
110
+ out.targetSelectionExplicit = true;
109
111
  continue;
110
112
  }
111
113
  if (arg === '--claude-only') {
112
114
  out.codex = false;
113
115
  out.claude = true;
116
+ out.targetSelectionExplicit = true;
114
117
  continue;
115
118
  }
116
119
  if (arg === '--claude-scope') {
@@ -181,6 +184,16 @@ function assertBinaryExists(name, options) {
181
184
  }
182
185
  }
183
186
 
187
+ function binaryExists(name, options) {
188
+ const check = runCommand(name, ['--version'], {
189
+ dryRun: options.dryRun,
190
+ quiet: true,
191
+ env: options.env,
192
+ cwd: options.cwd
193
+ });
194
+ return check.ok;
195
+ }
196
+
184
197
  function codexServerExists(name, options) {
185
198
  if (options.dryRun) return false;
186
199
  const result = runCommand('codex', ['mcp', 'get', name, '--json'], {
@@ -278,8 +291,33 @@ function main() {
278
291
  process.stdout.write('Warning: secure preset selected but VDON_MCP_JOIN_TOKEN_SECRET is not set in this shell.\n');
279
292
  }
280
293
 
281
- if (config.codex) assertBinaryExists('codex', options);
282
- if (config.claude) assertBinaryExists('claude', options);
294
+ if (config.codex && !binaryExists('codex', options)) {
295
+ if (config.targetSelectionExplicit && !config.claude) {
296
+ assertBinaryExists('codex', options);
297
+ }
298
+ process.stdout.write('Warning: Codex CLI not found in PATH. Skipping Codex install target.\n');
299
+ process.stdout.write('Tip: install Codex CLI, or rerun with --claude-only.\n');
300
+ config.codex = false;
301
+ }
302
+ if (config.claude && !binaryExists('claude', options)) {
303
+ if (config.targetSelectionExplicit && !config.codex) {
304
+ assertBinaryExists('claude', options);
305
+ }
306
+ process.stdout.write('Warning: Claude Code CLI not found in PATH. Skipping Claude install target.\n');
307
+ process.stdout.write('Tip: install Claude Code CLI, or rerun with --codex-only.\n');
308
+ config.claude = false;
309
+ }
310
+
311
+ if (!config.codex && !config.claude) {
312
+ if (config.action === 'uninstall') {
313
+ process.stdout.write('No supported CLI targets found in PATH; nothing to uninstall.\n\nDone.\n');
314
+ return;
315
+ }
316
+ throw new Error(
317
+ 'No supported CLI targets found in PATH. Install Codex CLI or Claude Code CLI, ' +
318
+ 'or configure GUI clients manually (e.g. Cursor via mcp.json).'
319
+ );
320
+ }
283
321
 
284
322
  if (config.action === 'install') {
285
323
  if (config.codex) installCodex(config, options);
@@ -290,6 +328,16 @@ function main() {
290
328
  }
291
329
 
292
330
  process.stdout.write('\nDone.\n');
331
+ if (config.action === 'install') {
332
+ process.stdout.write('\nUsage quickstart:\n');
333
+ process.stdout.write(' 1) Start server: npx vdon-mcp-server\n');
334
+ process.stdout.write(' 2) In your MCP client, call: {"name":"vdo_capabilities","arguments":{}}\n');
335
+ process.stdout.write(' 3) Generic stdio MCP config:\n');
336
+ process.stdout.write(` command: ${config.nodeCmd}\n`);
337
+ process.stdout.write(` args: [${JSON.stringify(config.serverScript)}${config.serverArgs.map((arg) => `, ${JSON.stringify(arg)}`).join('')}]\n`);
338
+ process.stdout.write(' 4) Client-specific examples (Cursor/Gemini/OpenCode):\n');
339
+ process.stdout.write(' https://github.com/steveseguin/ninjamcp/blob/main/references/client-config-examples.md\n');
340
+ }
293
341
  }
294
342
 
295
343
  if (require.main === module) {
@@ -24,7 +24,7 @@ const {
24
24
  const SUPPORTED_PROTOCOL_VERSIONS = ['2025-06-18', '2024-11-05'];
25
25
  const SERVER_INFO = {
26
26
  name: 'vdo-ninja-mcp',
27
- version: '0.4.0'
27
+ version: '0.4.1'
28
28
  };
29
29
  const DEFAULT_MAX_MESSAGE_BYTES = 1024 * 1024;
30
30
 
@@ -500,6 +500,38 @@ Options:
500
500
  `.trim();
501
501
  }
502
502
 
503
+ function isInteractiveTerminal(stream) {
504
+ return !!(stream && stream.isTTY);
505
+ }
506
+
507
+ function buildStartupBanner(server) {
508
+ const profile = server && server.toolProfile && server.toolProfile.name
509
+ ? server.toolProfile.name
510
+ : DEFAULT_TOOL_PROFILE;
511
+ return [
512
+ '[vdon-mcp] Server running (stdio) and waiting for MCP client input.',
513
+ `[vdon-mcp] Active tool profile: ${profile}`,
514
+ '[vdon-mcp] First tool call: {"name":"vdo_capabilities","arguments":{}}',
515
+ '[vdon-mcp] Stop with Ctrl+C'
516
+ ];
517
+ }
518
+
519
+ function maybeWriteStartupBanner(options, server) {
520
+ const env = options.env || process.env;
521
+ const disabled = toBool(env.VDON_MCP_NO_STARTUP_BANNER, false);
522
+ if (disabled) return false;
523
+
524
+ const input = options.input || process.stdin;
525
+ const output = options.output || process.stdout;
526
+ const errorOutput = options.errorOutput || process.stderr;
527
+
528
+ const interactive = isInteractiveTerminal(input) && isInteractiveTerminal(output) && isInteractiveTerminal(errorOutput);
529
+ if (!interactive) return false;
530
+
531
+ errorOutput.write(`${buildStartupBanner(server).join('\n')}\n`);
532
+ return true;
533
+ }
534
+
503
535
  class VdoMcpServer {
504
536
  constructor(options = {}) {
505
537
  this.protocolVersion = SUPPORTED_PROTOCOL_VERSIONS[0];
@@ -1328,6 +1360,8 @@ async function runMcpStdioLoop(options = {}) {
1328
1360
  onExit
1329
1361
  });
1330
1362
 
1363
+ maybeWriteStartupBanner(options, server);
1364
+
1331
1365
  const parser = new McpStdioParser({ maxMessageBytes });
1332
1366
  let chain = Promise.resolve();
1333
1367
  let closed = false;
@@ -1421,5 +1455,7 @@ module.exports = {
1421
1455
  buildTools,
1422
1456
  makeToolResult,
1423
1457
  classifyToolError,
1424
- parseCliArgs
1458
+ parseCliArgs,
1459
+ buildStartupBanner,
1460
+ maybeWriteStartupBanner
1425
1461
  };
package/server.json CHANGED
@@ -3,19 +3,17 @@
3
3
  "name": "io.github.steveseguin/vdo-ninja-mcp",
4
4
  "title": "VDO.Ninja MCP Bridge",
5
5
  "description": "WebRTC data-channel MCP bridge for VDO.Ninja with reliable messages, files, and room sync.",
6
- "version": "0.4.0",
7
- "websiteUrl": "https://github.com/steveseguin/ninjasdk/tree/main/MCP",
6
+ "version": "0.4.1",
7
+ "websiteUrl": "https://github.com/steveseguin/ninjamcp",
8
8
  "repository": {
9
- "url": "https://github.com/steveseguin/ninjasdk",
10
- "source": "github",
11
- "id": "1006762093",
12
- "subfolder": "MCP"
9
+ "url": "https://github.com/steveseguin/ninjamcp",
10
+ "source": "github"
13
11
  },
14
12
  "packages": [
15
13
  {
16
14
  "registryType": "npm",
17
15
  "identifier": "@vdoninja/mcp",
18
- "version": "0.4.0",
16
+ "version": "0.4.1",
19
17
  "runtimeHint": "node",
20
18
  "runtimeArguments": [
21
19
  {
@@ -81,7 +79,7 @@
81
79
  "url": "http://127.0.0.1:{http_port}/mcp",
82
80
  "variables": {
83
81
  "http_port": {
84
- "description": "Port used by MCP/scripts/vdo-mcp-streamable-http.js.",
82
+ "description": "Port used by scripts/vdo-mcp-streamable-http.js.",
85
83
  "format": "number",
86
84
  "default": "8787",
87
85
  "isRequired": false