@rvry/mcp 0.1.3 → 0.1.5

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/dist/index.js CHANGED
@@ -13,6 +13,9 @@ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
13
13
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
14
14
  import { CallToolRequestSchema, ListToolsRequestSchema, ListPromptsRequestSchema, GetPromptRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
15
15
  import { callTool } from './client.js';
16
+ import { createRequire } from 'module';
17
+ const require = createRequire(import.meta.url);
18
+ const PKG_VERSION = require('../package.json').version;
16
19
  /** Cache original question per session for response enrichment */
17
20
  const questionCache = new Map();
18
21
  /**
@@ -198,7 +201,7 @@ async function main() {
198
201
  console.error('[rvry-mcp] Generate one at https://rvry.ai/dashboard');
199
202
  process.exit(1);
200
203
  }
201
- const server = new Server({ name: 'RVRY', version: '0.1.0' }, { capabilities: { tools: {}, prompts: {} } });
204
+ const server = new Server({ name: 'RVRY', version: PKG_VERSION }, { capabilities: { tools: {}, prompts: {} } });
202
205
  server.setRequestHandler(ListToolsRequestSchema, async () => ({
203
206
  tools: TOOL_DEFS,
204
207
  }));
package/dist/setup.js CHANGED
@@ -15,6 +15,9 @@ import { homedir } from 'os';
15
15
  import { createInterface } from 'readline';
16
16
  import { execSync } from 'child_process';
17
17
  import { platform } from 'os';
18
+ import { createRequire } from 'module';
19
+ const require = createRequire(import.meta.url);
20
+ const PKG_VERSION = require('../package.json').version;
18
21
  const ENGINE_URL = 'https://engine.rvry.ai';
19
22
  const COMMAND_FILES = [
20
23
  'deepthink.md',
@@ -177,9 +180,6 @@ function openBrowser(url) {
177
180
  return false;
178
181
  }
179
182
  }
180
- /**
181
- * Check if the `claude` CLI is available in PATH.
182
- */
183
183
  function isClaudeCLIAvailable() {
184
184
  try {
185
185
  const cmd = platform() === 'win32' ? 'where claude' : 'which claude';
@@ -190,9 +190,6 @@ function isClaudeCLIAvailable() {
190
190
  return false;
191
191
  }
192
192
  }
193
- /**
194
- * Get the platform-specific Claude Desktop config file path.
195
- */
196
193
  function getDesktopConfigPath() {
197
194
  const plat = platform();
198
195
  if (plat === 'darwin') {
@@ -203,19 +200,10 @@ function getDesktopConfigPath() {
203
200
  }
204
201
  return join(homedir(), '.config', 'Claude', 'claude_desktop_config.json');
205
202
  }
206
- /**
207
- * Check if Claude Desktop config directory exists (app is installed).
208
- */
209
203
  function isClaudeDesktopInstalled() {
210
204
  const configPath = getDesktopConfigPath();
211
- const configDir = dirname(configPath);
212
- return existsSync(configDir);
205
+ return existsSync(dirname(configPath));
213
206
  }
214
- /**
215
- * Configure Claude Desktop by merging RVRY into the existing config.
216
- * Creates the config file if it doesn't exist. Preserves other MCP servers.
217
- * Returns 'created' | 'updated' | 'unchanged' | 'error'.
218
- */
219
207
  function configureDesktop(token) {
220
208
  const configPath = getDesktopConfigPath();
221
209
  try {
@@ -232,29 +220,23 @@ function configureDesktop(token) {
232
220
  config = {};
233
221
  }
234
222
  }
235
- // Ensure mcpServers object exists
236
223
  if (!config.mcpServers || typeof config.mcpServers !== 'object') {
237
224
  config.mcpServers = {};
238
225
  }
239
226
  const servers = config.mcpServers;
240
- const newEntry = RVRY_SERVER_ENTRY(token);
241
- // Check if RVRY is already configured with the same token
242
227
  const existing = servers.RVRY;
243
228
  if (existing) {
244
229
  const existingEnv = existing.env;
245
- if (existingEnv?.RVRY_TOKEN === token) {
230
+ if (existingEnv?.RVRY_TOKEN === token)
246
231
  return 'unchanged';
247
- }
248
232
  }
249
233
  const wasNew = !existing;
250
- servers.RVRY = newEntry;
251
- // Write config with clean formatting
234
+ servers.RVRY = RVRY_SERVER_ENTRY(token);
252
235
  const configDir = dirname(configPath);
253
- if (!existsSync(configDir)) {
236
+ if (!existsSync(configDir))
254
237
  mkdirSync(configDir, { recursive: true });
255
- }
256
238
  writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n', 'utf-8');
257
- return wasNew ? 'created' : 'updated';
239
+ return wasNew ? 'ok' : 'updated';
258
240
  }
259
241
  catch (err) {
260
242
  const msg = err instanceof Error ? err.message : String(err);
@@ -262,27 +244,221 @@ function configureDesktop(token) {
262
244
  return 'error';
263
245
  }
264
246
  }
265
- /**
266
- * Register RVRY as an MCP server in Claude Code.
267
- * Removes existing registration first for idempotency.
268
- */
269
247
  function registerClaudeCode(token) {
270
248
  try {
271
- // Remove existing (ignore error if not registered)
272
249
  try {
273
250
  execSync('claude mcp remove rvry', { stdio: 'pipe' });
274
251
  }
275
- catch {
276
- // Not registered yet, that's fine
277
- }
278
- // Add with token
252
+ catch { /* not registered */ }
279
253
  execSync(`claude mcp add -e RVRY_TOKEN="${token}" -s user rvry -- npx @rvry/mcp`, { stdio: 'inherit' });
280
- return true;
254
+ return 'ok';
281
255
  }
282
256
  catch {
283
- return false;
257
+ return 'error';
258
+ }
259
+ }
260
+ /**
261
+ * Write RVRY server entry into a generic MCP JSON config file.
262
+ * Used by clients that follow the Desktop-style config format (Codex, Gemini, etc.)
263
+ */
264
+ function configureJsonMcp(configPath, token) {
265
+ try {
266
+ let config = {};
267
+ if (existsSync(configPath)) {
268
+ const raw = readFileSync(configPath, 'utf-8');
269
+ try {
270
+ config = JSON.parse(raw);
271
+ }
272
+ catch {
273
+ writeFileSync(configPath + '.backup', raw, 'utf-8');
274
+ config = {};
275
+ }
276
+ }
277
+ if (!config.mcpServers || typeof config.mcpServers !== 'object') {
278
+ config.mcpServers = {};
279
+ }
280
+ const servers = config.mcpServers;
281
+ const existing = servers.RVRY;
282
+ if (existing) {
283
+ const existingEnv = existing.env;
284
+ if (existingEnv?.RVRY_TOKEN === token)
285
+ return 'unchanged';
286
+ }
287
+ const wasNew = !existing;
288
+ servers.RVRY = RVRY_SERVER_ENTRY(token);
289
+ const dir = dirname(configPath);
290
+ if (!existsSync(dir))
291
+ mkdirSync(dir, { recursive: true });
292
+ writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n', 'utf-8');
293
+ return wasNew ? 'ok' : 'updated';
294
+ }
295
+ catch (err) {
296
+ const msg = err instanceof Error ? err.message : String(err);
297
+ console.log(` Error writing config: ${msg}`);
298
+ return 'error';
284
299
  }
285
300
  }
301
+ /** All supported clients. Add new clients here. */
302
+ const CLIENT_REGISTRY = [
303
+ {
304
+ name: 'Claude Code',
305
+ id: 'code',
306
+ detect: isClaudeCLIAvailable,
307
+ configure: registerClaudeCode,
308
+ notInstalledHint: 'CLI not found (https://docs.anthropic.com/en/docs/claude-code)',
309
+ },
310
+ {
311
+ name: 'Claude Desktop / Claude Co-Work',
312
+ id: 'desktop',
313
+ detect: isClaudeDesktopInstalled,
314
+ configure: (token) => configureDesktop(token),
315
+ notInstalledHint: 'Not installed',
316
+ },
317
+ {
318
+ name: 'Anti-Gravity',
319
+ id: 'antigravity',
320
+ detect: () => {
321
+ // OR-logic: binary (agy or antigravity), config dir, or macOS app bundle
322
+ try {
323
+ const cmd = platform() === 'win32' ? 'where agy' : 'which agy';
324
+ execSync(cmd, { stdio: 'pipe' });
325
+ return true;
326
+ }
327
+ catch { /* not on PATH */ }
328
+ if (platform() !== 'win32') {
329
+ try {
330
+ execSync('which antigravity', { stdio: 'pipe' });
331
+ return true;
332
+ }
333
+ catch { /* not on PATH */ }
334
+ }
335
+ if (existsSync(join(homedir(), '.gemini', 'antigravity')))
336
+ return true;
337
+ if (platform() === 'darwin' && existsSync('/Applications/Google Antigravity.app'))
338
+ return true;
339
+ return false;
340
+ },
341
+ configure: (token) => configureJsonMcp(join(homedir(), '.gemini', 'antigravity', 'mcp_config.json'), token),
342
+ notInstalledHint: 'Not installed (https://antigravity.google)',
343
+ },
344
+ ];
345
+ /**
346
+ * Interactive multi-select picker with arrow keys + space + enter.
347
+ * Returns indices of selected items.
348
+ */
349
+ function multiSelect(items) {
350
+ return new Promise((resolve) => {
351
+ const stdin = process.stdin;
352
+ const wasRaw = stdin.isRaw;
353
+ if (typeof stdin.setRawMode === 'function')
354
+ stdin.setRawMode(true);
355
+ stdin.resume();
356
+ stdin.setEncoding('utf8');
357
+ let cursor = items.findIndex((i) => i.available);
358
+ if (cursor === -1)
359
+ cursor = 0;
360
+ const render = () => {
361
+ // Move cursor up to overwrite previous render (except first render)
362
+ for (let i = 0; i < items.length; i++) {
363
+ process.stdout.write('\x1b[2K'); // clear line
364
+ if (i < items.length - 1)
365
+ process.stdout.write('\x1b[1A'); // move up
366
+ }
367
+ process.stdout.write('\r');
368
+ for (let i = 0; i < items.length; i++) {
369
+ const item = items[i];
370
+ const pointer = i === cursor ? '›' : ' ';
371
+ const checkbox = !item.available
372
+ ? '\x1b[2m[ ]\x1b[0m'
373
+ : item.selected
374
+ ? '\x1b[32m[x]\x1b[0m'
375
+ : '[ ]';
376
+ const label = !item.available
377
+ ? `\x1b[2m${item.label}\x1b[0m`
378
+ : item.label;
379
+ const hint = !item.available && item.hint
380
+ ? ` \x1b[2m${item.hint}\x1b[0m`
381
+ : '';
382
+ process.stdout.write(` ${pointer} ${checkbox} ${label}${hint}`);
383
+ if (i < items.length - 1)
384
+ process.stdout.write('\n');
385
+ }
386
+ };
387
+ // Initial render: print blank lines then render
388
+ for (let i = 0; i < items.length; i++) {
389
+ process.stdout.write(i < items.length - 1 ? '\n' : '');
390
+ }
391
+ render();
392
+ const cleanup = () => {
393
+ if (typeof stdin.setRawMode === 'function')
394
+ stdin.setRawMode(wasRaw ?? false);
395
+ stdin.removeListener('data', onData);
396
+ stdin.pause();
397
+ };
398
+ const onData = (key) => {
399
+ // Ctrl+C
400
+ if (key === '\x03') {
401
+ cleanup();
402
+ process.stdout.write('\n');
403
+ process.exit(1);
404
+ }
405
+ // Enter — confirm selection
406
+ if (key === '\r' || key === '\n') {
407
+ cleanup();
408
+ process.stdout.write('\n');
409
+ const selected = [];
410
+ for (let i = 0; i < items.length; i++) {
411
+ if (items[i].selected && items[i].available)
412
+ selected.push(i);
413
+ }
414
+ resolve(selected);
415
+ return;
416
+ }
417
+ // Space — toggle current item
418
+ if (key === ' ') {
419
+ if (items[cursor].available) {
420
+ items[cursor].selected = !items[cursor].selected;
421
+ render();
422
+ }
423
+ return;
424
+ }
425
+ // Arrow keys (escape sequences)
426
+ if (key === '\x1b[A' || key === 'k') {
427
+ // Up
428
+ let next = cursor - 1;
429
+ while (next >= 0 && !items[next].available)
430
+ next--;
431
+ if (next >= 0) {
432
+ cursor = next;
433
+ render();
434
+ }
435
+ return;
436
+ }
437
+ if (key === '\x1b[B' || key === 'j') {
438
+ // Down
439
+ let next = cursor + 1;
440
+ while (next < items.length && !items[next].available)
441
+ next++;
442
+ if (next < items.length) {
443
+ cursor = next;
444
+ render();
445
+ }
446
+ return;
447
+ }
448
+ // 'a' — select all available
449
+ if (key === 'a') {
450
+ const allSelected = items.filter((i) => i.available).every((i) => i.selected);
451
+ for (const item of items) {
452
+ if (item.available)
453
+ item.selected = !allSelected;
454
+ }
455
+ render();
456
+ return;
457
+ }
458
+ };
459
+ stdin.on('data', onData);
460
+ });
461
+ }
286
462
  /**
287
463
  * Verify token by hitting the engine /api/usage endpoint.
288
464
  * Returns tier info on success, null on failure.
@@ -454,16 +630,18 @@ async function installCommands() {
454
630
  }
455
631
  // ── Main setup flow ────────────────────────────────────────────────
456
632
  const BANNER = `
457
- _____ __ __ _____ __ __
458
- | __ \\\\ \\\\ / /| __ \\\\ \\\\ / /
459
- | |__) |\\\\ \\\\ / / | |__) |\\\\ \\\\_/ /
460
- | _ / \\\\ \\\\/ / | _ / \\\\ /
461
- | | \\\\ \\\\ \\\\ / | | \\\\ \\\\ | |
462
- |_| \\\\_\\\\ \\\\/ |_| \\\\_\\\\ |_|
633
+ 8888888b. 888 888 8888888b. Y88b d88P
634
+ 888 Y88b 888 888 888 Y88b Y88b d88P
635
+ 888 888 888 888 888 888 Y88o88P
636
+ 888 d88P Y88b d88P 888 d88P Y888P
637
+ 8888888P" Y88b d88P 8888888P" 888
638
+ 888 T88b Y88o88P 888 T88b 888
639
+ 888 T88b Y888P 888 T88b 888
640
+ 888 T88b Y8P 888 T88b 888
463
641
  `;
464
642
  export async function runSetup() {
465
643
  console.log(BANNER);
466
- console.log('Setup');
644
+ console.log(`Setup v${PKG_VERSION}`);
467
645
  console.log('');
468
646
  // Parse flags
469
647
  const tokenFlagIndex = process.argv.indexOf('--token');
@@ -517,93 +695,68 @@ export async function runSetup() {
517
695
  }
518
696
  console.log('');
519
697
  // ── Step 2: Detect clients ──────────────────────────────────────
520
- console.log('[2/4] Detecting Claude clients');
521
- const hasClaudeCode = isClaudeCLIAvailable();
522
- const hasDesktop = isClaudeDesktopInstalled();
523
- const desktopConfigPath = getDesktopConfigPath();
524
- const wantCode = !clientFilter || clientFilter === 'code';
525
- const wantDesktop = !clientFilter || clientFilter === 'desktop';
526
- if (hasClaudeCode && wantCode) {
527
- console.log(' Found: Claude Code (claude CLI)');
528
- }
529
- if (hasDesktop && wantDesktop) {
530
- console.log(` Found: Claude Desktop (${desktopConfigPath})`);
698
+ console.log('[2/4] Detecting clients');
699
+ // Filter registry if --client was passed
700
+ const clients = clientFilter
701
+ ? CLIENT_REGISTRY.filter((c) => c.id === clientFilter)
702
+ : CLIENT_REGISTRY;
703
+ const detected = clients.map((c) => ({
704
+ client: c,
705
+ available: c.detect(),
706
+ }));
707
+ const anyDetected = detected.some((d) => d.available);
708
+ for (const d of detected) {
709
+ if (d.available) {
710
+ console.log(` Found: ${d.client.name}`);
711
+ }
531
712
  }
532
- if (!hasClaudeCode && !hasDesktop) {
533
- console.log(' No Claude clients detected.');
713
+ if (!anyDetected) {
714
+ console.log(' No supported clients detected.');
534
715
  }
535
716
  console.log('');
536
717
  // ── Step 3: Configure clients ───────────────────────────────────
537
- console.log('[3/4] Client Configuration');
538
- let codeConfigured = false;
539
- let desktopConfigured = 'skipped';
540
- let neitherConfigured = true;
541
- // Claude Code
542
- if (hasClaudeCode && wantCode) {
543
- const rl = createRL();
544
- const answer = await ask(rl, ' Install for Claude Code? [Y/n] ');
545
- rl.close();
546
- if (answer.toLowerCase() !== 'n') {
547
- const success = registerClaudeCode(token);
548
- if (success) {
549
- codeConfigured = true;
550
- neitherConfigured = false;
551
- console.log(' Claude Code: Registered (user scope -- works in all projects)');
552
- }
553
- else {
554
- console.log(' Claude Code: Failed to register via CLI');
555
- }
556
- }
557
- else {
558
- console.log(' Claude Code: Skipped');
559
- }
560
- }
561
- else if (wantCode && !hasClaudeCode) {
562
- console.log(' Claude Code: CLI not found (install: https://docs.anthropic.com/en/docs/claude-code)');
563
- }
564
- // Claude Desktop / Co-Work
565
- if (hasDesktop && wantDesktop) {
566
- const rl = createRL();
567
- const answer = await ask(rl, ' Install for Claude Desktop / Co-Work? [Y/n] ');
568
- rl.close();
569
- if (answer.toLowerCase() !== 'n') {
570
- desktopConfigured = configureDesktop(token);
571
- switch (desktopConfigured) {
572
- case 'created':
573
- neitherConfigured = false;
574
- console.log(' Claude Desktop: Added RVRY to config');
575
- break;
576
- case 'updated':
577
- neitherConfigured = false;
578
- console.log(' Claude Desktop: Updated RVRY token in config');
579
- break;
580
- case 'unchanged':
581
- neitherConfigured = false;
582
- console.log(' Claude Desktop: Already configured with this token');
583
- break;
584
- case 'error':
585
- console.log(' Claude Desktop: Failed to write config (see error above)');
586
- break;
587
- }
588
- }
589
- else {
590
- console.log(' Claude Desktop: Skipped');
591
- }
718
+ console.log('[3/4] Select apps to add RVRY MCP configuration');
719
+ console.log(' Use \x1b[1m↑↓\x1b[0m to navigate, \x1b[1mspace\x1b[0m to toggle, \x1b[1ma\x1b[0m to toggle all, \x1b[1menter\x1b[0m to confirm');
720
+ console.log('');
721
+ const pickerItems = detected.map((d) => ({
722
+ label: d.client.name,
723
+ selected: d.available, // pre-select detected clients
724
+ available: d.available,
725
+ hint: d.available ? undefined : d.client.notInstalledHint,
726
+ }));
727
+ const selectedIndices = await multiSelect(pickerItems);
728
+ const configuredClients = [];
729
+ let anyConfigured = false;
730
+ console.log('');
731
+ for (const idx of selectedIndices) {
732
+ const { client } = detected[idx];
733
+ const result = client.configure(token);
734
+ const statusMap = {
735
+ ok: 'Configured',
736
+ updated: 'Updated token',
737
+ unchanged: 'Already configured',
738
+ error: 'Failed',
739
+ };
740
+ const status = statusMap[result] ?? result;
741
+ const icon = result === 'error' ? '✗' : '✓';
742
+ console.log(` ${icon} ${client.name}: ${status}`);
743
+ configuredClients.push({ name: client.name, status });
744
+ if (result !== 'error')
745
+ anyConfigured = true;
592
746
  }
593
- else if (wantDesktop && !hasDesktop) {
594
- console.log(' Claude Desktop: Not installed');
747
+ if (selectedIndices.length === 0) {
748
+ console.log(' No clients selected.');
595
749
  }
596
750
  // Fallback: print manual config if nothing was configured
597
- if (neitherConfigured) {
751
+ if (!anyConfigured) {
598
752
  console.log('');
599
753
  console.log(' Manual configuration:');
600
754
  console.log('');
601
755
  console.log(' Option A — Claude Code (if you install it later):');
602
756
  console.log(` claude mcp add -e RVRY_TOKEN="${token}" -s user rvry -- npx @rvry/mcp`);
603
757
  console.log('');
604
- console.log(' Option B — Claude Desktop config JSON:');
758
+ console.log(' Option B — JSON config (Claude Desktop, Codex, etc.):');
605
759
  const manualConfig = { mcpServers: { RVRY: RVRY_SERVER_ENTRY(token) } };
606
- console.log(` File: ${desktopConfigPath}`);
607
760
  console.log('');
608
761
  for (const line of JSON.stringify(manualConfig, null, 2).split('\n')) {
609
762
  console.log(` ${line}`);
@@ -626,35 +779,36 @@ export async function runSetup() {
626
779
  console.log('RVRY Setup Complete');
627
780
  console.log('');
628
781
  console.log(` Token: ${maskToken(token)}${usage ? ` (${usage.tier})` : ''}`);
629
- if (codeConfigured) {
630
- console.log(' Claude Code: Configured');
631
- }
632
- if (desktopConfigured !== 'skipped' && desktopConfigured !== 'error') {
633
- console.log(' Claude Desktop: Configured');
782
+ for (const c of configuredClients) {
783
+ const pad = ' '.repeat(Math.max(1, 16 - c.name.length));
784
+ console.log(` ${c.name}:${pad}${c.status}`);
634
785
  }
635
786
  console.log(` Commands: ${commandCount} installed`);
636
787
  console.log('');
637
788
  // Client-specific next steps
789
+ const configuredNames = new Set(configuredClients.map((c) => c.name));
790
+ const hasDesktopStyle = configuredNames.has('Claude Desktop / Claude Co-Work')
791
+ || configuredNames.has('Anti-Gravity');
792
+ const hasCodeStyle = configuredNames.has('Claude Code');
638
793
  console.log('Next steps:');
639
- const desktopOk = desktopConfigured !== 'skipped' && desktopConfigured !== 'error';
640
794
  let step = 1;
641
- if (desktopOk) {
642
- console.log(` ${step}. Restart Claude Desktop for the new MCP server to load`);
795
+ if (hasDesktopStyle) {
796
+ console.log(` ${step}. Restart desktop apps for the new MCP server to load`);
643
797
  step++;
644
798
  }
645
- if (codeConfigured) {
799
+ if (hasCodeStyle) {
646
800
  console.log(` ${step}. In Claude Code, try:`);
647
801
  console.log(' /deepthink "your question"');
648
802
  console.log(' /problem-solve "your decision"');
649
803
  step++;
650
804
  }
651
- if (desktopOk) {
652
- console.log(` ${step}. In Claude Desktop, use natural language:`);
805
+ if (hasDesktopStyle) {
806
+ console.log(` ${step}. In desktop clients, use natural language:`);
653
807
  console.log(' "Use deepthink to analyze..." or "Use problem-solve for..."');
654
808
  step++;
655
809
  }
656
- if (!codeConfigured && !desktopOk) {
657
- console.log(' 1. Configure a Claude client using the manual instructions above');
810
+ if (!anyConfigured) {
811
+ console.log(' 1. Configure a client using the manual instructions above');
658
812
  console.log(' 2. Then try: /deepthink "your question"');
659
813
  }
660
814
  console.log('');
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "@rvry/mcp",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
4
4
  "description": "RVRY reasoning depth enforcement (RDE) engine client.",
5
5
  "type": "module",
6
6
  "bin": {
7
- "rvry-mcp": "./dist/index.js"
7
+ "rvry-mcp": "dist/index.js"
8
8
  },
9
9
  "main": "./dist/index.js",
10
10
  "files": [