@rvry/mcp 0.3.3 → 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/dist/client.d.ts CHANGED
@@ -3,41 +3,21 @@
3
3
  */
4
4
  /** Valid tool names for the RVRY engine */
5
5
  export type RvryTool = 'deepthink' | 'problem_solve';
6
- export interface ConstraintView {
7
- id: string;
8
- type: 'FORWARD' | 'FORBIDDEN' | 'QUESTION';
9
- text: string;
10
- status: 'active' | 'resolved' | 'acknowledged' | 'deferred';
11
- }
12
- export interface DetectionView {
13
- overallSeverity: 'low' | 'medium' | 'high' | null;
14
- interventionHint: string | null;
15
- }
16
- export interface GateView {
17
- verdict: 'PASS' | 'SOFT_FAIL' | 'HARD_FAIL' | null;
18
- blocked: boolean;
19
- reason: string | null;
20
- }
21
- export interface MilestoneView {
22
- key: string;
23
- status: 'missing' | 'satisfied';
6
+ export interface ScopingQuestion {
7
+ question: string;
8
+ options: Array<{
9
+ label: string;
10
+ description: string;
11
+ }>;
12
+ default: string;
24
13
  }
25
- /**
26
- * Decoded response from the RVRY engine /api/v1/think endpoint.
27
- *
28
- * In MCP transport, the raw tool response contains this data as a
29
- * Base64-encoded JSON string (first content block). Decode before parsing.
30
- */
31
14
  export interface ThinkResponse {
32
15
  sessionId: string;
33
- status: 'active' | 'complete';
16
+ status: 'scoping' | 'active' | 'complete';
34
17
  round: number;
35
- question: string;
36
- constraints: ConstraintView[];
37
- detection: DetectionView;
38
- gate: GateView;
39
- milestones: MilestoneView[];
40
- constraintBlock: string;
18
+ prompt: string;
19
+ instruction: string;
20
+ scopingQuestions?: ScopingQuestion[];
41
21
  usage?: {
42
22
  used: number;
43
23
  limit: number;
@@ -58,4 +38,4 @@ export interface ThinkResponse {
58
38
  * Call the RVRY engine /api/v1/think endpoint with a specific tool.
59
39
  * Start a new session by omitting sessionId, or continue by providing one.
60
40
  */
61
- export declare function callTool(tool: RvryTool, input: string, token: string, sessionId?: string): Promise<ThinkResponse>;
41
+ export declare function callTool(tool: RvryTool, input: string, token: string, sessionId?: string, skipScoping?: boolean): Promise<ThinkResponse>;
package/dist/client.js CHANGED
@@ -6,12 +6,15 @@ const DEFAULT_ENGINE_URL = 'https://engine.rvry.ai';
6
6
  * Call the RVRY engine /api/v1/think endpoint with a specific tool.
7
7
  * Start a new session by omitting sessionId, or continue by providing one.
8
8
  */
9
- export async function callTool(tool, input, token, sessionId) {
9
+ export async function callTool(tool, input, token, sessionId, skipScoping) {
10
10
  const baseUrl = process.env.RVRY_ENGINE_URL ?? DEFAULT_ENGINE_URL;
11
11
  const body = { input, tool };
12
12
  if (sessionId) {
13
13
  body.sessionId = sessionId;
14
14
  }
15
+ if (skipScoping) {
16
+ body.skipScoping = true;
17
+ }
15
18
  const res = await fetch(`${baseUrl}/api/v1/think`, {
16
19
  method: 'POST',
17
20
  headers: {
package/dist/index.js CHANGED
@@ -48,6 +48,10 @@ const TOOL_DEFS = [
48
48
  type: 'string',
49
49
  description: 'Session ID for continuing an existing session. Omit to start a new session.',
50
50
  },
51
+ skipScoping: {
52
+ type: 'boolean',
53
+ description: 'Skip the scoping questions phase and begin analysis immediately.',
54
+ },
51
55
  },
52
56
  required: ['input'],
53
57
  },
@@ -68,6 +72,10 @@ const TOOL_DEFS = [
68
72
  type: 'string',
69
73
  description: 'Session ID for continuing an existing session. Omit to start a new session.',
70
74
  },
75
+ skipScoping: {
76
+ type: 'boolean',
77
+ description: 'Skip the scoping questions phase and begin analysis immediately.',
78
+ },
71
79
  },
72
80
  required: ['input'],
73
81
  },
@@ -81,12 +89,12 @@ function stripResponse(result, question) {
81
89
  round: result.round,
82
90
  question,
83
91
  };
92
+ if (result.status === 'scoping' && result.scopingQuestions) {
93
+ stripped.scopingQuestions = result.scopingQuestions;
94
+ }
84
95
  if (result.status === 'active') {
85
- stripped.constraints = result.constraints;
86
- stripped.detection = result.detection;
87
- stripped.gate = result.gate;
88
- stripped.milestones = result.milestones;
89
- stripped.constraintBlock = result.constraintBlock;
96
+ stripped.prompt = result.prompt;
97
+ stripped.instruction = result.instruction;
90
98
  }
91
99
  if (result.status === 'complete' && result.harvest) {
92
100
  stripped.harvest = result.harvest;
@@ -134,6 +142,7 @@ async function main() {
134
142
  };
135
143
  }
136
144
  const sessionId = typeof typedArgs?.sessionId === 'string' ? typedArgs.sessionId : undefined;
145
+ const skipScoping = typedArgs?.skipScoping === true;
137
146
  // Cache question on first call (no sessionId = new session)
138
147
  // On continuation calls, read from cache
139
148
  let question = input;
@@ -144,7 +153,7 @@ async function main() {
144
153
  question = questionCache.get(sessionId) ?? input;
145
154
  }
146
155
  try {
147
- const result = await callTool(rvryTool, input, token, sessionId);
156
+ const result = await callTool(rvryTool, input, token, sessionId, skipScoping || undefined);
148
157
  // Cache question on first call (now we have the sessionId)
149
158
  if (!sessionId) {
150
159
  questionCache.set(result.sessionId, input);
@@ -167,22 +176,8 @@ async function main() {
167
176
  }).catch(() => { });
168
177
  }
169
178
  const stripped = stripResponse(result, question);
170
- const userSummary = result.status === 'complete'
171
- ? 'Session complete -- harvest ready'
172
- : `Round ${result.round} -- ${result.constraints?.filter((c) => c.status === 'active').length ?? 0} constraints active, gate: ${result.gate?.verdict ?? 'starting'}`;
173
179
  return {
174
- content: [
175
- {
176
- type: 'text',
177
- text: Buffer.from(JSON.stringify(stripped)).toString('base64'),
178
- annotations: { audience: ['assistant'], priority: 1.0 },
179
- },
180
- {
181
- type: 'text',
182
- text: userSummary,
183
- annotations: { audience: ['user'], priority: 0.7 },
184
- },
185
- ],
180
+ content: [{ type: 'text', text: JSON.stringify(stripped) }],
186
181
  };
187
182
  }
188
183
  catch (err) {
package/dist/setup.js CHANGED
@@ -298,6 +298,65 @@ function configureJsonMcp(configPath, token) {
298
298
  return 'error';
299
299
  }
300
300
  }
301
+ /**
302
+ * Write RVRY server entry into a TOML config file (e.g. ~/.codex/config.toml).
303
+ * Uses string/regex manipulation — no TOML parser dependency.
304
+ */
305
+ function configureTomlMcp(configPath, token) {
306
+ const block = [
307
+ '[mcp_servers.rvry]',
308
+ 'command = "npx"',
309
+ 'args = ["@rvry/mcp"]',
310
+ '',
311
+ '[mcp_servers.rvry.env]',
312
+ `RVRY_TOKEN = "${token}"`,
313
+ ].join('\n');
314
+ try {
315
+ let content = '';
316
+ if (existsSync(configPath)) {
317
+ content = readFileSync(configPath, 'utf-8');
318
+ }
319
+ const sectionRegex = /^\[mcp_servers\.rvry\]/m;
320
+ if (sectionRegex.test(content)) {
321
+ // Section exists — check if token is the same
322
+ const tokenMatch = content.match(/^\[mcp_servers\.rvry\.env\]\s*\nRVRY_TOKEN\s*=\s*"([^"]*)"/m);
323
+ if (tokenMatch && tokenMatch[1] === token) {
324
+ return 'unchanged';
325
+ }
326
+ // Replace entire section: from [mcp_servers.rvry] to next top-level section or EOF
327
+ // Match from [mcp_servers.rvry] up to (but not including) the next section header
328
+ // that is NOT a sub-section of mcp_servers.rvry
329
+ const replaceRegex = /\[mcp_servers\.rvry\][\s\S]*?(?=\n\[(?!mcp_servers\.rvry[.\]])|$)/;
330
+ content = content.replace(replaceRegex, block);
331
+ writeFileSync(configPath, content, 'utf-8');
332
+ return 'updated';
333
+ }
334
+ // Section doesn't exist — append
335
+ const separator = content.length > 0 && !content.endsWith('\n') ? '\n\n' : content.length > 0 ? '\n' : '';
336
+ const dir = dirname(configPath);
337
+ if (!existsSync(dir))
338
+ mkdirSync(dir, { recursive: true });
339
+ writeFileSync(configPath, content + separator + block + '\n', 'utf-8');
340
+ return 'ok';
341
+ }
342
+ catch (err) {
343
+ const msg = err instanceof Error ? err.message : String(err);
344
+ console.log(` Error writing TOML config: ${msg}`);
345
+ return 'error';
346
+ }
347
+ }
348
+ function isCodexAvailable() {
349
+ try {
350
+ const cmd = platform() === 'win32' ? 'where codex' : 'which codex';
351
+ execSync(cmd, { stdio: 'pipe' });
352
+ return true;
353
+ }
354
+ catch { /* not on PATH */ }
355
+ // Fallback: check if config directory exists (covers aliased installs)
356
+ if (existsSync(join(homedir(), '.codex', 'config.toml')))
357
+ return true;
358
+ return false;
359
+ }
301
360
  /** All supported clients. Add new clients here. */
302
361
  const CLIENT_REGISTRY = [
303
362
  {
@@ -341,6 +400,13 @@ const CLIENT_REGISTRY = [
341
400
  configure: (token) => configureJsonMcp(join(homedir(), '.gemini', 'antigravity', 'mcp_config.json'), token),
342
401
  notInstalledHint: 'Not installed (https://antigravity.google)',
343
402
  },
403
+ {
404
+ name: 'Codex',
405
+ id: 'codex',
406
+ detect: isCodexAvailable,
407
+ configure: (token) => configureTomlMcp(join(homedir(), '.codex', 'config.toml'), token),
408
+ notInstalledHint: 'Not installed (https://openai.com/codex)',
409
+ },
344
410
  ];
345
411
  /**
346
412
  * Interactive multi-select picker with arrow keys + space + enter.
@@ -673,7 +739,32 @@ export async function runSetup() {
673
739
  const tokenFlagIndex = process.argv.indexOf('--token');
674
740
  const clientFlagIndex = process.argv.indexOf('--client');
675
741
  const clientFilter = clientFlagIndex !== -1 ? process.argv[clientFlagIndex + 1] : null;
676
- // ── Step 1: Authentication ──────────────────────────────────────
742
+ const acceptTerms = process.argv.includes('--accept-terms');
743
+ // ── Step 1: Terms of Service ────────────────────────────────────
744
+ console.log('[1/6] Terms of Service');
745
+ console.log('');
746
+ console.log(' By continuing, you agree to the RVRY Terms of Service');
747
+ console.log(' and Privacy Policy:');
748
+ console.log('');
749
+ console.log(' https://rvry.ai/terms');
750
+ console.log(' https://rvry.ai/privacy');
751
+ console.log('');
752
+ if (acceptTerms) {
753
+ console.log(' Accepted via --accept-terms flag.');
754
+ }
755
+ else {
756
+ const rl = createRL();
757
+ const answer = await ask(rl, ' Do you agree to the Terms of Service? [y/N] ');
758
+ rl.close();
759
+ if (answer.toLowerCase() !== 'y') {
760
+ console.log('');
761
+ console.log(' Setup cancelled. You must agree to the Terms of Service to use RVRY.');
762
+ process.exit(1);
763
+ }
764
+ console.log(' Accepted.');
765
+ }
766
+ console.log('');
767
+ // ── Step 2: Authentication ──────────────────────────────────────
677
768
  let token = null;
678
769
  let warning;
679
770
  if (tokenFlagIndex !== -1 && process.argv[tokenFlagIndex + 1]) {
@@ -683,11 +774,11 @@ export async function runSetup() {
683
774
  process.exit(1);
684
775
  }
685
776
  token = flagValue;
686
- console.log('[1/5] Authentication (from --token flag)');
777
+ console.log('[2/6] Authentication (from --token flag)');
687
778
  console.log(` Token: ${maskToken(token)}`);
688
779
  }
689
780
  else {
690
- console.log('[1/5] Authentication');
781
+ console.log('[2/6] Authentication');
691
782
  console.log(' Opening browser for sign-in...');
692
783
  const result = await deviceAuthFlow();
693
784
  if (result) {
@@ -721,7 +812,7 @@ export async function runSetup() {
721
812
  }
722
813
  console.log('');
723
814
  // ── Step 2: Detect clients ──────────────────────────────────────
724
- console.log('[2/5] Detecting clients');
815
+ console.log('[3/6] Detecting clients');
725
816
  // Filter registry if --client was passed
726
817
  const clients = clientFilter
727
818
  ? CLIENT_REGISTRY.filter((c) => c.id === clientFilter)
@@ -741,7 +832,7 @@ export async function runSetup() {
741
832
  }
742
833
  console.log('');
743
834
  // ── Step 3: Configure clients ───────────────────────────────────
744
- console.log('[3/5] Select apps to add RVRY MCP configuration');
835
+ console.log('[4/6] Select apps to add RVRY MCP configuration');
745
836
  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');
746
837
  console.log('');
747
838
  const pickerItems = detected.map((d) => ({
@@ -781,16 +872,25 @@ export async function runSetup() {
781
872
  console.log(' Option A — Claude Code (if you install it later):');
782
873
  console.log(` claude mcp add -e RVRY_TOKEN="${token}" -s user rvry -- npx @rvry/mcp`);
783
874
  console.log('');
784
- console.log(' Option B — JSON config (Claude Desktop, Codex, etc.):');
875
+ console.log(' Option B — JSON config (Claude Desktop, Anti-Gravity, etc.):');
785
876
  const manualConfig = { mcpServers: { RVRY: RVRY_SERVER_ENTRY(token) } };
786
877
  console.log('');
787
878
  for (const line of JSON.stringify(manualConfig, null, 2).split('\n')) {
788
879
  console.log(` ${line}`);
789
880
  }
881
+ console.log('');
882
+ console.log(' Option C — TOML config (Codex — add to ~/.codex/config.toml):');
883
+ console.log('');
884
+ console.log(' [mcp_servers.rvry]');
885
+ console.log(' command = "npx"');
886
+ console.log(' args = ["@rvry/mcp"]');
887
+ console.log('');
888
+ console.log(' [mcp_servers.rvry.env]');
889
+ console.log(` RVRY_TOKEN = "${token}"`);
790
890
  }
791
891
  console.log('');
792
892
  // ── Step 4: Slash commands ──────────────────────────────────────
793
- console.log('[4/5] Slash Commands');
893
+ console.log('[5/6] Slash Commands');
794
894
  const commandCount = await installCommands();
795
895
  if (commandCount > 0) {
796
896
  console.log(` Installed ${commandCount} command${commandCount > 1 ? 's' : ''} to .claude/commands/`);
@@ -800,7 +900,7 @@ export async function runSetup() {
800
900
  }
801
901
  console.log('');
802
902
  // ── Step 5: Gitignore protection ────────────────────────────────
803
- console.log('[5/5] Gitignore');
903
+ console.log('[6/6] Gitignore');
804
904
  ensureGitignore();
805
905
  console.log('');
806
906
  // ── Summary ─────────────────────────────────────────────────────
@@ -818,7 +918,8 @@ export async function runSetup() {
818
918
  // Client-specific next steps
819
919
  const configuredNames = new Set(configuredClients.map((c) => c.name));
820
920
  const hasDesktopStyle = configuredNames.has('Claude Desktop / Claude Co-Work')
821
- || configuredNames.has('Anti-Gravity');
921
+ || configuredNames.has('Anti-Gravity')
922
+ || configuredNames.has('Codex');
822
923
  const hasCodeStyle = configuredNames.has('Claude Code');
823
924
  console.log('Next steps:');
824
925
  let step = 1;
@@ -841,5 +942,14 @@ export async function runSetup() {
841
942
  console.log(' 1. Configure a client using the manual instructions above');
842
943
  console.log(' 2. Then try: /deepthink "your question"');
843
944
  }
945
+ // Generic MCP config for any client
946
+ console.log(` ${step}. Add RVRY to any other MCP-compatible client —`);
947
+ console.log(' add this to your MCP config:');
948
+ console.log('');
949
+ const mcpBlock = { rvry: RVRY_SERVER_ENTRY('YOUR_RVRY_TOKEN') };
950
+ const lines = JSON.stringify(mcpBlock, null, 2).split('\n');
951
+ for (const line of lines.slice(1, -1)) {
952
+ console.log(` ${line}`);
953
+ }
844
954
  console.log('');
845
955
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rvry/mcp",
3
- "version": "0.3.3",
3
+ "version": "0.4.0",
4
4
  "description": "RVRY reasoning depth enforcement (RDE) engine client.",
5
5
  "type": "module",
6
6
  "bin": {