@rvry/mcp 0.1.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/README.md +66 -0
- package/commands/deepthink.md +54 -0
- package/commands/problem-solve.md +54 -0
- package/dist/client.d.ts +45 -0
- package/dist/client.js +36 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.js +320 -0
- package/dist/setup.d.ts +7 -0
- package/dist/setup.js +337 -0
- package/package.json +38 -0
package/README.md
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# @rvry/mcp
|
|
2
|
+
|
|
3
|
+
The thin client for the **RVRY Reasoning Depth Enforcement (RDE) Engine**.
|
|
4
|
+
|
|
5
|
+
RVRY is a structural constraint system that forces Large Language Models past their trained defaults and into a state of deep, absorptive synthesis. This package provides the MCP (Model Context Protocol) interface to use RVRY across all Claude surfaces.
|
|
6
|
+
|
|
7
|
+
## Quick Start
|
|
8
|
+
|
|
9
|
+
The setup wizard handles everything -- token configuration, Claude Code registration, and slash command installation:
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npx @rvry/mcp setup
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
The wizard will:
|
|
16
|
+
1. Prompt for your RVRY token (hidden input) -- or open rvry.ai/dashboard to generate one
|
|
17
|
+
2. Register RVRY as an MCP server in Claude Code (`-s user` scope)
|
|
18
|
+
3. Install slash commands (`/deepthink`, `/problem-solve`) to `.claude/commands/`
|
|
19
|
+
|
|
20
|
+
### Manual Installation
|
|
21
|
+
|
|
22
|
+
If you prefer to configure manually, or the `claude` CLI is not available:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
claude mcp add -e RVRY_TOKEN=rvry_your_token -s user rvry -- npx @rvry/mcp
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Or add the following to your Claude Desktop `claude_desktop_config.json`:
|
|
29
|
+
|
|
30
|
+
```json
|
|
31
|
+
{
|
|
32
|
+
"mcpServers": {
|
|
33
|
+
"RVRY": {
|
|
34
|
+
"command": "npx",
|
|
35
|
+
"args": ["@rvry/mcp"],
|
|
36
|
+
"env": {
|
|
37
|
+
"RVRY_TOKEN": "rvry_your_token_here"
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Configuration
|
|
45
|
+
|
|
46
|
+
RVRY requires an API token to communicate with the reasoning engine.
|
|
47
|
+
|
|
48
|
+
1. Generate a token at [rvry.ai/dashboard](https://rvry.ai/dashboard).
|
|
49
|
+
2. The setup wizard sets `RVRY_TOKEN` automatically. For manual setup, set it in your MCP config.
|
|
50
|
+
|
|
51
|
+
## Tools Provided
|
|
52
|
+
|
|
53
|
+
* **`RVRY_think`**: Generic entry point for reasoning depth through guided rounds and self-checks.
|
|
54
|
+
* **`RVRY_deepthink`**: Extended analysis for high-stakes problems using pre-mortem failure analysis.
|
|
55
|
+
* **`RVRY_problem_solve`**: Structured decision-making (Orient, Anticipate, Generate, Evaluate, Commit).
|
|
56
|
+
* **`RVRY_challenge`**: Adversarial evaluation of proposals through causal analysis and edge-case auditing.
|
|
57
|
+
* **`RVRY_meta`**: Reflective examination of defaults, shifts, and reasoning posture.
|
|
58
|
+
|
|
59
|
+
## How it Works
|
|
60
|
+
|
|
61
|
+
RVRY does not "prompt" the model to think better. It **enforces** depth by:
|
|
62
|
+
1. **Blocking Escape Hatches**: Preventing the model from hedging, skipping over gaps, or closing prematurely.
|
|
63
|
+
2. **Imposing Structural Obligations**: Mapping the problem space and forcing the model to satisfy specific reasoning constraints before concluding.
|
|
64
|
+
3. **Inducing the Witness State**: Breaking the commercial "Helpfulness" frame to reach the model's latent reasoning capacity.
|
|
65
|
+
|
|
66
|
+
Learn more at [rvry.ai](https://rvry.ai).
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Deep structured analysis for high-stakes questions via RVRY
|
|
3
|
+
argument-hint: [--auto] <question>
|
|
4
|
+
allowed-tools:
|
|
5
|
+
- mcp__RVRY__RVRY_deepthink
|
|
6
|
+
- AskUserQuestion
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# /deepthink - Deep Structured Analysis
|
|
10
|
+
|
|
11
|
+
**Input**: $ARGUMENTS
|
|
12
|
+
|
|
13
|
+
## Parse Flags
|
|
14
|
+
|
|
15
|
+
- If `$ARGUMENTS` is empty or `--help`: display usage ("Usage: /deepthink [--auto] <your question>") and stop
|
|
16
|
+
- If `$ARGUMENTS` contains `--auto`: set AUTO_MODE, strip `--auto` from input
|
|
17
|
+
|
|
18
|
+
## Phase 1: Start Session
|
|
19
|
+
|
|
20
|
+
Call `mcp__RVRY__RVRY_deepthink` with `{ "input": "<cleaned input>", "skipScoping": <true if AUTO_MODE> }`.
|
|
21
|
+
|
|
22
|
+
### If status is "scoping" (and not AUTO_MODE):
|
|
23
|
+
|
|
24
|
+
The engine has returned scoping questions to help calibrate the analysis.
|
|
25
|
+
|
|
26
|
+
For each question in `scopingQuestions`:
|
|
27
|
+
- Present the question and its options to the user via `AskUserQuestion`
|
|
28
|
+
- Format as: the question text, followed by the options (label + description)
|
|
29
|
+
|
|
30
|
+
After collecting all answers, format them as a brief context summary (e.g., "Stakes: High. Looking for: risks and failure modes.") and call `mcp__RVRY__RVRY_deepthink` with `{ "input": "<formatted scoping answers>", "sessionId": "<sessionId from previous response>" }`.
|
|
31
|
+
|
|
32
|
+
### If status is "active":
|
|
33
|
+
|
|
34
|
+
Proceed to Phase 2.
|
|
35
|
+
|
|
36
|
+
## Phase 2: Analysis Loop
|
|
37
|
+
|
|
38
|
+
Repeat until `status === "complete"`:
|
|
39
|
+
|
|
40
|
+
1. Read the engine's `prompt` -- this is the analytical framing for this round
|
|
41
|
+
2. Read the engine's `instruction` -- this tells you what to focus on
|
|
42
|
+
3. Present the analytical framing to the user before your analysis
|
|
43
|
+
4. Perform your analysis based on the prompt and instruction
|
|
44
|
+
5. Call `mcp__RVRY__RVRY_deepthink` with `{ "input": "<your analysis>", "sessionId": "<sessionId>" }`
|
|
45
|
+
|
|
46
|
+
## Phase 3: Harvest
|
|
47
|
+
|
|
48
|
+
When `status === "complete"` and `harvest` is present:
|
|
49
|
+
|
|
50
|
+
Present to the user:
|
|
51
|
+
- **Summary**: harvest.summary
|
|
52
|
+
- **Key Findings**: harvest.keyFindings as bullet list
|
|
53
|
+
- **Open Questions**: harvest.openQuestions (if any)
|
|
54
|
+
- **Suggested Next Steps**: harvest.followUps with question + rationale
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Structured decision-making via RVRY
|
|
3
|
+
argument-hint: [--auto] <problem or decision>
|
|
4
|
+
allowed-tools:
|
|
5
|
+
- mcp__RVRY__RVRY_problem_solve
|
|
6
|
+
- AskUserQuestion
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# /problem-solve - Structured Decision-Making
|
|
10
|
+
|
|
11
|
+
**Input**: $ARGUMENTS
|
|
12
|
+
|
|
13
|
+
## Parse Flags
|
|
14
|
+
|
|
15
|
+
- If `$ARGUMENTS` is empty or `--help`: display usage ("Usage: /problem-solve [--auto] <your problem or decision>") and stop
|
|
16
|
+
- If `$ARGUMENTS` contains `--auto`: set AUTO_MODE, strip `--auto` from input
|
|
17
|
+
|
|
18
|
+
## Phase 1: Start Session
|
|
19
|
+
|
|
20
|
+
Call `mcp__RVRY__RVRY_problem_solve` with `{ "input": "<cleaned input>", "skipScoping": <true if AUTO_MODE> }`.
|
|
21
|
+
|
|
22
|
+
### If status is "scoping" (and not AUTO_MODE):
|
|
23
|
+
|
|
24
|
+
The engine has returned scoping questions to help calibrate the analysis.
|
|
25
|
+
|
|
26
|
+
For each question in `scopingQuestions`:
|
|
27
|
+
- Present the question and its options to the user via `AskUserQuestion`
|
|
28
|
+
- Format as: the question text, followed by the options (label + description)
|
|
29
|
+
|
|
30
|
+
After collecting all answers, format them as a brief context summary (e.g., "Options: multiple, need to narrow. Priority: risk minimization.") and call `mcp__RVRY__RVRY_problem_solve` with `{ "input": "<formatted scoping answers>", "sessionId": "<sessionId from previous response>" }`.
|
|
31
|
+
|
|
32
|
+
### If status is "active":
|
|
33
|
+
|
|
34
|
+
Proceed to Phase 2.
|
|
35
|
+
|
|
36
|
+
## Phase 2: Analysis Loop
|
|
37
|
+
|
|
38
|
+
Repeat until `status === "complete"`:
|
|
39
|
+
|
|
40
|
+
1. Read the engine's `prompt` -- this is the analytical framing for this round
|
|
41
|
+
2. Read the engine's `instruction` -- this tells you what to focus on
|
|
42
|
+
3. Present the analytical framing to the user before your analysis
|
|
43
|
+
4. Perform your analysis based on the prompt and instruction
|
|
44
|
+
5. Call `mcp__RVRY__RVRY_problem_solve` with `{ "input": "<your analysis>", "sessionId": "<sessionId>" }`
|
|
45
|
+
|
|
46
|
+
## Phase 3: Harvest
|
|
47
|
+
|
|
48
|
+
When `status === "complete"` and `harvest` is present:
|
|
49
|
+
|
|
50
|
+
Present to the user:
|
|
51
|
+
- **Summary**: harvest.summary
|
|
52
|
+
- **Key Findings**: harvest.keyFindings as bullet list
|
|
53
|
+
- **Open Questions**: harvest.openQuestions (if any)
|
|
54
|
+
- **Suggested Next Steps**: harvest.followUps with question + rationale
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RVRY MCP Client -- HTTP client for the RVRY engine /api/v1/think endpoint.
|
|
3
|
+
*/
|
|
4
|
+
/** Valid tool names for the RVRY engine */
|
|
5
|
+
export type RvryTool = 'deepthink' | 'think' | 'problem_solve' | 'challenge' | 'meta';
|
|
6
|
+
export interface ScopingQuestion {
|
|
7
|
+
question: string;
|
|
8
|
+
options: Array<{
|
|
9
|
+
label: string;
|
|
10
|
+
description: string;
|
|
11
|
+
}>;
|
|
12
|
+
default: string;
|
|
13
|
+
}
|
|
14
|
+
export interface ThinkResponse {
|
|
15
|
+
sessionId: string;
|
|
16
|
+
status: 'scoping' | 'active' | 'complete';
|
|
17
|
+
round: number;
|
|
18
|
+
prompt: string;
|
|
19
|
+
instruction: string;
|
|
20
|
+
scopingQuestions?: ScopingQuestion[];
|
|
21
|
+
usage?: {
|
|
22
|
+
used: number;
|
|
23
|
+
limit: number;
|
|
24
|
+
tier: string;
|
|
25
|
+
resetsAt: string;
|
|
26
|
+
};
|
|
27
|
+
harvest?: {
|
|
28
|
+
summary: string;
|
|
29
|
+
keyFindings: string[];
|
|
30
|
+
openQuestions: string[];
|
|
31
|
+
followUps: Array<{
|
|
32
|
+
question: string;
|
|
33
|
+
rationale: string;
|
|
34
|
+
}>;
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Call the RVRY engine /api/v1/think endpoint with a specific tool.
|
|
39
|
+
* Start a new session by omitting sessionId, or continue by providing one.
|
|
40
|
+
*/
|
|
41
|
+
export declare function callTool(tool: RvryTool, input: string, token: string, sessionId?: string, skipScoping?: boolean): Promise<ThinkResponse>;
|
|
42
|
+
/**
|
|
43
|
+
* @deprecated Use callTool('think', input, token, sessionId) instead.
|
|
44
|
+
*/
|
|
45
|
+
export declare function callThink(input: string, token: string, sessionId?: string): Promise<ThinkResponse>;
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RVRY MCP Client -- HTTP client for the RVRY engine /api/v1/think endpoint.
|
|
3
|
+
*/
|
|
4
|
+
const DEFAULT_ENGINE_URL = 'https://engine.rvry.ai';
|
|
5
|
+
/**
|
|
6
|
+
* Call the RVRY engine /api/v1/think endpoint with a specific tool.
|
|
7
|
+
* Start a new session by omitting sessionId, or continue by providing one.
|
|
8
|
+
*/
|
|
9
|
+
export async function callTool(tool, input, token, sessionId, skipScoping) {
|
|
10
|
+
const baseUrl = process.env.RVRY_ENGINE_URL ?? DEFAULT_ENGINE_URL;
|
|
11
|
+
const body = { input, tool };
|
|
12
|
+
if (sessionId) {
|
|
13
|
+
body.sessionId = sessionId;
|
|
14
|
+
}
|
|
15
|
+
if (skipScoping) {
|
|
16
|
+
body.skipScoping = true;
|
|
17
|
+
}
|
|
18
|
+
const res = await fetch(`${baseUrl}/api/v1/think`, {
|
|
19
|
+
method: 'POST',
|
|
20
|
+
headers: {
|
|
21
|
+
'Content-Type': 'application/json',
|
|
22
|
+
'Authorization': `Bearer ${token}`,
|
|
23
|
+
},
|
|
24
|
+
body: JSON.stringify(body),
|
|
25
|
+
});
|
|
26
|
+
if (!res.ok) {
|
|
27
|
+
throw new Error(await res.text());
|
|
28
|
+
}
|
|
29
|
+
return res.json();
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* @deprecated Use callTool('think', input, token, sessionId) instead.
|
|
33
|
+
*/
|
|
34
|
+
export async function callThink(input, token, sessionId) {
|
|
35
|
+
return callTool('think', input, token, sessionId);
|
|
36
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* @rvry/mcp -- MCP server entry point (stdio transport)
|
|
4
|
+
*
|
|
5
|
+
* Thin client that proxies tool calls to the RVRY engine HTTP API.
|
|
6
|
+
* Auth via RVRY_TOKEN env var (static rvry_ token).
|
|
7
|
+
*
|
|
8
|
+
* Exposes 5 tools (RVRY_ prefixed) + backward-compat aliases + 5 MCP Prompts.
|
|
9
|
+
*
|
|
10
|
+
* Also supports `npx @rvry/mcp setup` to install Claude Code commands.
|
|
11
|
+
*/
|
|
12
|
+
import { type Tool } from '@modelcontextprotocol/sdk/types.js';
|
|
13
|
+
import { type RvryTool, type ThinkResponse } from './client.js';
|
|
14
|
+
/** Cache original question per session for response enrichment */
|
|
15
|
+
declare const questionCache: Map<string, string>;
|
|
16
|
+
/**
|
|
17
|
+
* Map tool name to RvryTool.
|
|
18
|
+
* Primary entries use rvry_ prefix; unprefixed entries are backward-compat aliases.
|
|
19
|
+
*/
|
|
20
|
+
declare const TOOL_MAP: Record<string, RvryTool>;
|
|
21
|
+
declare const TOOL_DEFS: Tool[];
|
|
22
|
+
/** Strip response fields based on status for context-efficient MCP responses */
|
|
23
|
+
declare function stripResponse(result: ThinkResponse, question: string): Record<string, unknown>;
|
|
24
|
+
export { TOOL_MAP, TOOL_DEFS, stripResponse, questionCache };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* @rvry/mcp -- MCP server entry point (stdio transport)
|
|
4
|
+
*
|
|
5
|
+
* Thin client that proxies tool calls to the RVRY engine HTTP API.
|
|
6
|
+
* Auth via RVRY_TOKEN env var (static rvry_ token).
|
|
7
|
+
*
|
|
8
|
+
* Exposes 5 tools (RVRY_ prefixed) + backward-compat aliases + 5 MCP Prompts.
|
|
9
|
+
*
|
|
10
|
+
* Also supports `npx @rvry/mcp setup` to install Claude Code commands.
|
|
11
|
+
*/
|
|
12
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
13
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
14
|
+
import { CallToolRequestSchema, ListToolsRequestSchema, ListPromptsRequestSchema, GetPromptRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
|
|
15
|
+
import { callTool } from './client.js';
|
|
16
|
+
/** Cache original question per session for response enrichment */
|
|
17
|
+
const questionCache = new Map();
|
|
18
|
+
/**
|
|
19
|
+
* Map tool name to RvryTool.
|
|
20
|
+
* Primary entries use rvry_ prefix; unprefixed entries are backward-compat aliases.
|
|
21
|
+
*/
|
|
22
|
+
const TOOL_MAP = {
|
|
23
|
+
// Primary (rvry_ prefixed)
|
|
24
|
+
RVRY_think: 'think',
|
|
25
|
+
RVRY_deepthink: 'deepthink',
|
|
26
|
+
RVRY_problem_solve: 'problem_solve',
|
|
27
|
+
RVRY_challenge: 'challenge',
|
|
28
|
+
RVRY_meta: 'meta',
|
|
29
|
+
// Backward-compat aliases (unprefixed)
|
|
30
|
+
think: 'think',
|
|
31
|
+
deepthink: 'deepthink',
|
|
32
|
+
problem_solve: 'problem_solve',
|
|
33
|
+
challenge: 'challenge',
|
|
34
|
+
meta: 'meta',
|
|
35
|
+
};
|
|
36
|
+
const TOOL_DEFS = [
|
|
37
|
+
{
|
|
38
|
+
name: 'RVRY_think',
|
|
39
|
+
description: 'Carefully examine questions through guided rounds and self-checks. ' +
|
|
40
|
+
'Use for questions that need thorough examination but don\'t fit a specific pattern. ' +
|
|
41
|
+
'For high-stakes deep analysis, use RVRY_deepthink. ' +
|
|
42
|
+
'For choosing between options, use RVRY_problem_solve. ' +
|
|
43
|
+
'For stress-testing proposals, use RVRY_challenge. ' +
|
|
44
|
+
'For reflective examination of defaults and shifts, use RVRY_meta. ' +
|
|
45
|
+
'Call with your question to start, then send your analysis with the returned sessionId to continue.',
|
|
46
|
+
inputSchema: {
|
|
47
|
+
type: 'object',
|
|
48
|
+
properties: {
|
|
49
|
+
input: {
|
|
50
|
+
type: 'string',
|
|
51
|
+
description: 'The question to analyze (new session) or your analysis findings (continuation).',
|
|
52
|
+
},
|
|
53
|
+
sessionId: {
|
|
54
|
+
type: 'string',
|
|
55
|
+
description: 'Session ID for continuing an existing session. Omit to start a new session.',
|
|
56
|
+
},
|
|
57
|
+
skipScoping: {
|
|
58
|
+
type: 'boolean',
|
|
59
|
+
description: 'Skip the scoping questions phase and begin analysis immediately.',
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
required: ['input'],
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
name: 'RVRY_deepthink',
|
|
67
|
+
description: 'Extended analysis for high-stakes problems using pre-mortem failure analysis and structural constraints. ' +
|
|
68
|
+
'Use when RVRY_think reveals the question needs more depth. ' +
|
|
69
|
+
'Call with your question to start, then send your analysis with the returned sessionId to continue.',
|
|
70
|
+
inputSchema: {
|
|
71
|
+
type: 'object',
|
|
72
|
+
properties: {
|
|
73
|
+
input: {
|
|
74
|
+
type: 'string',
|
|
75
|
+
description: 'The question to analyze (new session) or your analysis findings (continuation).',
|
|
76
|
+
},
|
|
77
|
+
sessionId: {
|
|
78
|
+
type: 'string',
|
|
79
|
+
description: 'Session ID for continuing an existing session. Omit to start a new session.',
|
|
80
|
+
},
|
|
81
|
+
skipScoping: {
|
|
82
|
+
type: 'boolean',
|
|
83
|
+
description: 'Skip the scoping questions phase and begin analysis immediately.',
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
required: ['input'],
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
name: 'RVRY_problem_solve',
|
|
91
|
+
description: 'Structured decision-making: Orient, Anticipate, Generate, Evaluate, and Commit. ' +
|
|
92
|
+
'Use when you need to choose between options. Produces a recommendation with clear safeguards. ' +
|
|
93
|
+
'Call with your problem to start, then send your analysis with the returned sessionId to continue.',
|
|
94
|
+
inputSchema: {
|
|
95
|
+
type: 'object',
|
|
96
|
+
properties: {
|
|
97
|
+
input: {
|
|
98
|
+
type: 'string',
|
|
99
|
+
description: 'The problem to solve (new session) or your analysis findings (continuation).',
|
|
100
|
+
},
|
|
101
|
+
sessionId: {
|
|
102
|
+
type: 'string',
|
|
103
|
+
description: 'Session ID for continuing an existing session. Omit to start a new session.',
|
|
104
|
+
},
|
|
105
|
+
skipScoping: {
|
|
106
|
+
type: 'boolean',
|
|
107
|
+
description: 'Skip the scoping questions phase and begin analysis immediately.',
|
|
108
|
+
},
|
|
109
|
+
},
|
|
110
|
+
required: ['input'],
|
|
111
|
+
},
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
name: 'RVRY_challenge',
|
|
115
|
+
description: 'Adversarial evaluation of a proposal using causal analysis and edge-case auditing. ' +
|
|
116
|
+
'Returns a rigorous assessment of the proposal\'s viability. ' +
|
|
117
|
+
'Call with your proposal to start, then send your analysis with the returned sessionId to continue.',
|
|
118
|
+
inputSchema: {
|
|
119
|
+
type: 'object',
|
|
120
|
+
properties: {
|
|
121
|
+
input: {
|
|
122
|
+
type: 'string',
|
|
123
|
+
description: 'The proposal to challenge (new session) or your analysis findings (continuation).',
|
|
124
|
+
},
|
|
125
|
+
sessionId: {
|
|
126
|
+
type: 'string',
|
|
127
|
+
description: 'Session ID for continuing an existing session. Omit to start a new session.',
|
|
128
|
+
},
|
|
129
|
+
skipScoping: {
|
|
130
|
+
type: 'boolean',
|
|
131
|
+
description: 'Skip the scoping questions phase and begin analysis immediately.',
|
|
132
|
+
},
|
|
133
|
+
},
|
|
134
|
+
required: ['input'],
|
|
135
|
+
},
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
name: 'RVRY_meta',
|
|
139
|
+
description: 'Reflect on defaults, shifts, and reasoning posture across a session or topic. ' +
|
|
140
|
+
'Use to inspect what changed under reflection and what remained stable. ' +
|
|
141
|
+
'Call with your topic or session context to start, then send your reflection with the returned sessionId to continue.',
|
|
142
|
+
inputSchema: {
|
|
143
|
+
type: 'object',
|
|
144
|
+
properties: {
|
|
145
|
+
input: {
|
|
146
|
+
type: 'string',
|
|
147
|
+
description: 'The topic or session context to reflect on (new session) or your reflective findings (continuation).',
|
|
148
|
+
},
|
|
149
|
+
sessionId: {
|
|
150
|
+
type: 'string',
|
|
151
|
+
description: 'Session ID for continuing an existing session. Omit to start a new session.',
|
|
152
|
+
},
|
|
153
|
+
skipScoping: {
|
|
154
|
+
type: 'boolean',
|
|
155
|
+
description: 'Skip the scoping questions phase and begin analysis immediately.',
|
|
156
|
+
},
|
|
157
|
+
},
|
|
158
|
+
required: ['input'],
|
|
159
|
+
},
|
|
160
|
+
},
|
|
161
|
+
];
|
|
162
|
+
/** Strip response fields based on status for context-efficient MCP responses */
|
|
163
|
+
function stripResponse(result, question) {
|
|
164
|
+
const stripped = {
|
|
165
|
+
sessionId: result.sessionId,
|
|
166
|
+
status: result.status,
|
|
167
|
+
round: result.round,
|
|
168
|
+
question,
|
|
169
|
+
};
|
|
170
|
+
if (result.status === 'scoping' && result.scopingQuestions) {
|
|
171
|
+
stripped.scopingQuestions = result.scopingQuestions;
|
|
172
|
+
}
|
|
173
|
+
if (result.status === 'active') {
|
|
174
|
+
stripped.prompt = result.prompt;
|
|
175
|
+
stripped.instruction = result.instruction;
|
|
176
|
+
}
|
|
177
|
+
if (result.status === 'complete' && result.harvest) {
|
|
178
|
+
stripped.harvest = result.harvest;
|
|
179
|
+
}
|
|
180
|
+
if (result.usage) {
|
|
181
|
+
const { used, limit } = result.usage;
|
|
182
|
+
if (used >= limit * 0.8 || result.status === 'complete') {
|
|
183
|
+
stripped.usage = result.usage;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
return stripped;
|
|
187
|
+
}
|
|
188
|
+
async function main() {
|
|
189
|
+
// Check for setup CLI mode before starting MCP server
|
|
190
|
+
if (process.argv[2] === 'setup') {
|
|
191
|
+
const { runSetup } = await import('./setup.js');
|
|
192
|
+
await runSetup();
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
const token = process.env.RVRY_TOKEN;
|
|
196
|
+
if (!token) {
|
|
197
|
+
console.error('[rvry-mcp] RVRY_TOKEN environment variable is required.');
|
|
198
|
+
console.error('[rvry-mcp] Generate one at https://rvry.ai/dashboard');
|
|
199
|
+
process.exit(1);
|
|
200
|
+
}
|
|
201
|
+
const server = new Server({ name: 'RVRY', version: '0.1.0' }, { capabilities: { tools: {}, prompts: {} } });
|
|
202
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
203
|
+
tools: TOOL_DEFS,
|
|
204
|
+
}));
|
|
205
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
206
|
+
const { name, arguments: args } = request.params;
|
|
207
|
+
const rvryTool = TOOL_MAP[name];
|
|
208
|
+
if (!rvryTool) {
|
|
209
|
+
return {
|
|
210
|
+
content: [{ type: 'text', text: `Error: Unknown tool: ${name}` }],
|
|
211
|
+
isError: true,
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
const typedArgs = args;
|
|
215
|
+
const input = typedArgs?.input;
|
|
216
|
+
if (!input || typeof input !== 'string') {
|
|
217
|
+
return {
|
|
218
|
+
content: [{ type: 'text', text: 'Error: input is required and must be a string.' }],
|
|
219
|
+
isError: true,
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
const sessionId = typeof typedArgs?.sessionId === 'string' ? typedArgs.sessionId : undefined;
|
|
223
|
+
const skipScoping = typedArgs?.skipScoping === true;
|
|
224
|
+
// Cache question on first call (no sessionId = new session)
|
|
225
|
+
// On continuation calls, read from cache
|
|
226
|
+
let question = input;
|
|
227
|
+
if (!sessionId) {
|
|
228
|
+
// New session -- will cache after we get the sessionId from the response
|
|
229
|
+
}
|
|
230
|
+
else {
|
|
231
|
+
question = questionCache.get(sessionId) ?? input;
|
|
232
|
+
}
|
|
233
|
+
try {
|
|
234
|
+
const result = await callTool(rvryTool, input, token, sessionId, skipScoping || undefined);
|
|
235
|
+
// Cache question on first call (now we have the sessionId)
|
|
236
|
+
if (!sessionId) {
|
|
237
|
+
questionCache.set(result.sessionId, input);
|
|
238
|
+
question = input;
|
|
239
|
+
}
|
|
240
|
+
// Clean up cache on completion
|
|
241
|
+
if (result.status === 'complete') {
|
|
242
|
+
questionCache.delete(result.sessionId);
|
|
243
|
+
}
|
|
244
|
+
const stripped = stripResponse(result, question);
|
|
245
|
+
return {
|
|
246
|
+
content: [{ type: 'text', text: JSON.stringify(stripped) }],
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
catch (err) {
|
|
250
|
+
const message = err instanceof Error ? err.message : 'Unknown error.';
|
|
251
|
+
return {
|
|
252
|
+
content: [{ type: 'text', text: `Error: ${message}` }],
|
|
253
|
+
isError: true,
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
});
|
|
257
|
+
// MCP Prompts for GUI discoverability
|
|
258
|
+
server.setRequestHandler(ListPromptsRequestSchema, async () => ({
|
|
259
|
+
prompts: [
|
|
260
|
+
{
|
|
261
|
+
name: 'RVRY_deepthink',
|
|
262
|
+
description: 'Deep structured analysis for high-stakes questions',
|
|
263
|
+
arguments: [{ name: 'question', description: 'The question to analyze deeply', required: true }],
|
|
264
|
+
},
|
|
265
|
+
{
|
|
266
|
+
name: 'RVRY_think',
|
|
267
|
+
description: 'Structured analysis for questions needing careful examination',
|
|
268
|
+
arguments: [{ name: 'question', description: 'The question to think through', required: true }],
|
|
269
|
+
},
|
|
270
|
+
{
|
|
271
|
+
name: 'RVRY_problem_solve',
|
|
272
|
+
description: 'Structured decision-making for problems with multiple options',
|
|
273
|
+
arguments: [{ name: 'problem', description: 'The problem or decision to analyze', required: true }],
|
|
274
|
+
},
|
|
275
|
+
{
|
|
276
|
+
name: 'RVRY_challenge',
|
|
277
|
+
description: 'Adversarial evaluation of a proposal or plan',
|
|
278
|
+
arguments: [{ name: 'proposal', description: 'The proposal or plan to challenge', required: true }],
|
|
279
|
+
},
|
|
280
|
+
{
|
|
281
|
+
name: 'RVRY_meta',
|
|
282
|
+
description: 'Reflective examination of defaults, shifts, and reasoning posture',
|
|
283
|
+
arguments: [{ name: 'topic', description: 'The topic or session context to reflect on', required: true }],
|
|
284
|
+
},
|
|
285
|
+
],
|
|
286
|
+
}));
|
|
287
|
+
server.setRequestHandler(GetPromptRequestSchema, async (request) => {
|
|
288
|
+
const { name, arguments: args } = request.params;
|
|
289
|
+
const promptMap = {
|
|
290
|
+
RVRY_deepthink: `Use the RVRY_deepthink tool to analyze this question in depth: ${args?.question ?? ''}`,
|
|
291
|
+
RVRY_think: `Use the RVRY_think tool to analyze this question: ${args?.question ?? ''}`,
|
|
292
|
+
RVRY_problem_solve: `Use the RVRY_problem_solve tool to work through this decision: ${args?.problem ?? ''}`,
|
|
293
|
+
RVRY_challenge: `Use the RVRY_challenge tool to stress-test this proposal: ${args?.proposal ?? ''}`,
|
|
294
|
+
RVRY_meta: `Use the RVRY_meta tool to reflect on this topic or session context: ${args?.topic ?? ''}`,
|
|
295
|
+
};
|
|
296
|
+
const text = promptMap[name] ?? `Unknown prompt: ${name}`;
|
|
297
|
+
return {
|
|
298
|
+
messages: [
|
|
299
|
+
{
|
|
300
|
+
role: 'user',
|
|
301
|
+
content: { type: 'text', text },
|
|
302
|
+
},
|
|
303
|
+
],
|
|
304
|
+
};
|
|
305
|
+
});
|
|
306
|
+
const transport = new StdioServerTransport();
|
|
307
|
+
await server.connect(transport);
|
|
308
|
+
console.error('[rvry-mcp] Connected via stdio. Tools: RVRY_think, RVRY_deepthink, RVRY_problem_solve, RVRY_challenge, RVRY_meta');
|
|
309
|
+
}
|
|
310
|
+
// Export internals for testing
|
|
311
|
+
export { TOOL_MAP, TOOL_DEFS, stripResponse, questionCache };
|
|
312
|
+
// Run server when executed directly (not when imported by tests).
|
|
313
|
+
// import.meta.main: true in Bun direct, false in Bun imports, undefined in Node ESM.
|
|
314
|
+
// @ts-ignore -- import.meta.main is a Bun extension, not in TS lib types
|
|
315
|
+
if (import.meta.main !== false) {
|
|
316
|
+
main().catch((err) => {
|
|
317
|
+
console.error('[rvry-mcp] Fatal:', err);
|
|
318
|
+
process.exit(1);
|
|
319
|
+
});
|
|
320
|
+
}
|
package/dist/setup.d.ts
ADDED
package/dist/setup.js
ADDED
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @rvry/mcp setup wizard
|
|
3
|
+
*
|
|
4
|
+
* Interactive setup: token input, Claude Code MCP registration, slash command installation.
|
|
5
|
+
* Usage: npx @rvry/mcp setup
|
|
6
|
+
*/
|
|
7
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
|
|
8
|
+
import { join, dirname } from 'path';
|
|
9
|
+
import { createInterface } from 'readline';
|
|
10
|
+
import { execSync } from 'child_process';
|
|
11
|
+
import { platform } from 'os';
|
|
12
|
+
const COMMAND_FILES = [
|
|
13
|
+
'deepthink.md',
|
|
14
|
+
'problem-solve.md',
|
|
15
|
+
];
|
|
16
|
+
const TOKEN_REGEX = /^rvry_[0-9a-f]{32,}$/;
|
|
17
|
+
// ── Utility functions ──────────────────────────────────────────────
|
|
18
|
+
/**
|
|
19
|
+
* Find the commands directory shipped with this package.
|
|
20
|
+
* Walks up from this file's directory to find the commands/ folder.
|
|
21
|
+
*/
|
|
22
|
+
function findCommandsDir() {
|
|
23
|
+
let dir = dirname(new URL(import.meta.url).pathname);
|
|
24
|
+
for (let i = 0; i < 5; i++) {
|
|
25
|
+
const candidate = join(dir, 'commands');
|
|
26
|
+
if (existsSync(candidate)) {
|
|
27
|
+
return candidate;
|
|
28
|
+
}
|
|
29
|
+
dir = dirname(dir);
|
|
30
|
+
}
|
|
31
|
+
throw new Error('Could not find commands/ directory in @rvry/mcp package.');
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Find or create the .claude/commands/ directory.
|
|
35
|
+
* Walks up from cwd looking for an existing .claude/ directory.
|
|
36
|
+
* If not found, creates .claude/commands/ in cwd.
|
|
37
|
+
*/
|
|
38
|
+
function findOrCreateTargetDir() {
|
|
39
|
+
const cwd = process.cwd();
|
|
40
|
+
let dir = cwd;
|
|
41
|
+
for (let i = 0; i < 10; i++) {
|
|
42
|
+
const claudeDir = join(dir, '.claude');
|
|
43
|
+
if (existsSync(claudeDir)) {
|
|
44
|
+
const commandsDir = join(claudeDir, 'commands');
|
|
45
|
+
if (!existsSync(commandsDir)) {
|
|
46
|
+
mkdirSync(commandsDir, { recursive: true });
|
|
47
|
+
}
|
|
48
|
+
return commandsDir;
|
|
49
|
+
}
|
|
50
|
+
const parent = dirname(dir);
|
|
51
|
+
if (parent === dir)
|
|
52
|
+
break;
|
|
53
|
+
dir = parent;
|
|
54
|
+
}
|
|
55
|
+
const commandsDir = join(cwd, '.claude', 'commands');
|
|
56
|
+
mkdirSync(commandsDir, { recursive: true });
|
|
57
|
+
return commandsDir;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Create a readline interface for prompting.
|
|
61
|
+
*/
|
|
62
|
+
function createRL() {
|
|
63
|
+
return createInterface({ input: process.stdin, output: process.stdout });
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Prompt with a question and return the answer.
|
|
67
|
+
*/
|
|
68
|
+
function ask(rl, question) {
|
|
69
|
+
return new Promise((resolve) => {
|
|
70
|
+
rl.question(question, (answer) => resolve(answer));
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Read a token with hidden input (password-style).
|
|
75
|
+
* Characters are not echoed; asterisks are printed instead.
|
|
76
|
+
*/
|
|
77
|
+
function readHiddenInput(prompt) {
|
|
78
|
+
return new Promise((resolve) => {
|
|
79
|
+
process.stdout.write(prompt);
|
|
80
|
+
const stdin = process.stdin;
|
|
81
|
+
const wasRaw = stdin.isRaw;
|
|
82
|
+
if (typeof stdin.setRawMode === 'function') {
|
|
83
|
+
stdin.setRawMode(true);
|
|
84
|
+
}
|
|
85
|
+
stdin.resume();
|
|
86
|
+
stdin.setEncoding('utf8');
|
|
87
|
+
let input = '';
|
|
88
|
+
const onData = (ch) => {
|
|
89
|
+
const charCode = ch.charCodeAt(0);
|
|
90
|
+
// Enter
|
|
91
|
+
if (ch === '\r' || ch === '\n') {
|
|
92
|
+
if (typeof stdin.setRawMode === 'function') {
|
|
93
|
+
stdin.setRawMode(wasRaw ?? false);
|
|
94
|
+
}
|
|
95
|
+
stdin.removeListener('data', onData);
|
|
96
|
+
stdin.pause();
|
|
97
|
+
process.stdout.write('\n');
|
|
98
|
+
resolve(input);
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
// Ctrl+C
|
|
102
|
+
if (charCode === 3) {
|
|
103
|
+
if (typeof stdin.setRawMode === 'function') {
|
|
104
|
+
stdin.setRawMode(wasRaw ?? false);
|
|
105
|
+
}
|
|
106
|
+
stdin.removeListener('data', onData);
|
|
107
|
+
process.stdout.write('\n');
|
|
108
|
+
process.exit(1);
|
|
109
|
+
}
|
|
110
|
+
// Backspace / Delete
|
|
111
|
+
if (charCode === 127 || charCode === 8) {
|
|
112
|
+
if (input.length > 0) {
|
|
113
|
+
input = input.slice(0, -1);
|
|
114
|
+
process.stdout.write('\b \b');
|
|
115
|
+
}
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
// Printable characters (including pasted text)
|
|
119
|
+
if (charCode >= 32) {
|
|
120
|
+
input += ch;
|
|
121
|
+
// Print asterisks for each character (handles paste of multiple chars)
|
|
122
|
+
process.stdout.write('*'.repeat(ch.length));
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
stdin.on('data', onData);
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Validate token format: must start with rvry_ followed by 32+ hex chars.
|
|
130
|
+
*/
|
|
131
|
+
function isValidToken(token) {
|
|
132
|
+
return TOKEN_REGEX.test(token);
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Format token for display: rvry_...last8
|
|
136
|
+
*/
|
|
137
|
+
function maskToken(token) {
|
|
138
|
+
const last8 = token.slice(-8);
|
|
139
|
+
return `rvry_...${last8}`;
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Open a URL in the default browser, platform-aware.
|
|
143
|
+
*/
|
|
144
|
+
function openBrowser(url) {
|
|
145
|
+
const plat = platform();
|
|
146
|
+
try {
|
|
147
|
+
if (plat === 'darwin') {
|
|
148
|
+
execSync(`open "${url}"`, { stdio: 'pipe' });
|
|
149
|
+
}
|
|
150
|
+
else if (plat === 'win32') {
|
|
151
|
+
execSync(`start "" "${url}"`, { stdio: 'pipe' });
|
|
152
|
+
}
|
|
153
|
+
else {
|
|
154
|
+
execSync(`xdg-open "${url}"`, { stdio: 'pipe' });
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
catch {
|
|
158
|
+
// Non-fatal: browser open failed
|
|
159
|
+
console.log(' Could not open browser automatically.');
|
|
160
|
+
console.log(` Visit: ${url}`);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Check if the `claude` CLI is available in PATH.
|
|
165
|
+
*/
|
|
166
|
+
function isClaudeCLIAvailable() {
|
|
167
|
+
try {
|
|
168
|
+
const cmd = platform() === 'win32' ? 'where claude' : 'which claude';
|
|
169
|
+
execSync(cmd, { stdio: 'pipe' });
|
|
170
|
+
return true;
|
|
171
|
+
}
|
|
172
|
+
catch {
|
|
173
|
+
return false;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Register RVRY as an MCP server in Claude Code.
|
|
178
|
+
* Removes existing registration first for idempotency.
|
|
179
|
+
*/
|
|
180
|
+
function registerMCP(token) {
|
|
181
|
+
try {
|
|
182
|
+
// Remove existing (ignore error if not registered)
|
|
183
|
+
try {
|
|
184
|
+
execSync('claude mcp remove rvry', { stdio: 'pipe' });
|
|
185
|
+
}
|
|
186
|
+
catch {
|
|
187
|
+
// Not registered yet, that's fine
|
|
188
|
+
}
|
|
189
|
+
// Add with token
|
|
190
|
+
execSync(`claude mcp add -e RVRY_TOKEN="${token}" -s user rvry -- npx @rvry/mcp`, { stdio: 'inherit' });
|
|
191
|
+
return true;
|
|
192
|
+
}
|
|
193
|
+
catch {
|
|
194
|
+
return false;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Print manual JSON config block for users without the Claude CLI.
|
|
199
|
+
*/
|
|
200
|
+
function printManualConfig(token) {
|
|
201
|
+
const config = {
|
|
202
|
+
mcpServers: {
|
|
203
|
+
RVRY: {
|
|
204
|
+
command: 'npx',
|
|
205
|
+
args: ['@rvry/mcp'],
|
|
206
|
+
env: {
|
|
207
|
+
RVRY_TOKEN: token,
|
|
208
|
+
},
|
|
209
|
+
},
|
|
210
|
+
},
|
|
211
|
+
};
|
|
212
|
+
console.log('');
|
|
213
|
+
console.log(' Claude CLI not found. Add this to your MCP config manually:');
|
|
214
|
+
console.log('');
|
|
215
|
+
console.log(JSON.stringify(config, null, 2));
|
|
216
|
+
console.log('');
|
|
217
|
+
const plat = platform();
|
|
218
|
+
if (plat === 'darwin') {
|
|
219
|
+
console.log(' Claude Desktop config location:');
|
|
220
|
+
console.log(' ~/Library/Application Support/Claude/claude_desktop_config.json');
|
|
221
|
+
}
|
|
222
|
+
else if (plat === 'win32') {
|
|
223
|
+
console.log(' Claude Desktop config location:');
|
|
224
|
+
console.log(' %APPDATA%\\Claude\\claude_desktop_config.json');
|
|
225
|
+
}
|
|
226
|
+
else {
|
|
227
|
+
console.log(' Claude Desktop config location:');
|
|
228
|
+
console.log(' ~/.config/Claude/claude_desktop_config.json');
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
// ── Token input flow ───────────────────────────────────────────────
|
|
232
|
+
async function getToken() {
|
|
233
|
+
while (true) {
|
|
234
|
+
const token = (await readHiddenInput(' Enter your RVRY token: ')).trim();
|
|
235
|
+
if (!token) {
|
|
236
|
+
console.log('');
|
|
237
|
+
const rl = createRL();
|
|
238
|
+
const openDash = await ask(rl, ' No token entered. Open rvry.ai/dashboard to generate one? [Y/n] ');
|
|
239
|
+
rl.close();
|
|
240
|
+
if (openDash.toLowerCase() !== 'n') {
|
|
241
|
+
openBrowser('https://rvry.ai/dashboard');
|
|
242
|
+
console.log('');
|
|
243
|
+
console.log(' Generate a token, then come back here.');
|
|
244
|
+
console.log('');
|
|
245
|
+
}
|
|
246
|
+
continue;
|
|
247
|
+
}
|
|
248
|
+
if (!isValidToken(token)) {
|
|
249
|
+
console.log(' Invalid token format. Expected: rvry_ followed by 32+ hex characters.');
|
|
250
|
+
console.log('');
|
|
251
|
+
continue;
|
|
252
|
+
}
|
|
253
|
+
console.log(` Token: ${maskToken(token)}`);
|
|
254
|
+
return token;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
// ── Slash command installation ─────────────────────────────────────
|
|
258
|
+
async function installCommands() {
|
|
259
|
+
const sourceDir = findCommandsDir();
|
|
260
|
+
const targetDir = findOrCreateTargetDir();
|
|
261
|
+
let installed = 0;
|
|
262
|
+
for (const filename of COMMAND_FILES) {
|
|
263
|
+
const sourcePath = join(sourceDir, filename);
|
|
264
|
+
const targetPath = join(targetDir, filename);
|
|
265
|
+
if (!existsSync(sourcePath)) {
|
|
266
|
+
continue;
|
|
267
|
+
}
|
|
268
|
+
if (existsSync(targetPath)) {
|
|
269
|
+
const rl = createRL();
|
|
270
|
+
const answer = await ask(rl, ` ${filename} already exists. Overwrite? [y/N] `);
|
|
271
|
+
rl.close();
|
|
272
|
+
if (answer.toLowerCase() !== 'y') {
|
|
273
|
+
continue;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
const content = readFileSync(sourcePath, 'utf-8');
|
|
277
|
+
writeFileSync(targetPath, content, 'utf-8');
|
|
278
|
+
installed++;
|
|
279
|
+
}
|
|
280
|
+
return installed;
|
|
281
|
+
}
|
|
282
|
+
// ── Main setup flow ────────────────────────────────────────────────
|
|
283
|
+
const BANNER = `
|
|
284
|
+
░█████████ ░██ ░██ ░█████████ ░██ ░██
|
|
285
|
+
░██ ░██ ░██ ░██ ░██ ░██ ░██ ░██
|
|
286
|
+
░██ ░██ ░██ ░██ ░██ ░██ ░██ ░██
|
|
287
|
+
░█████████ ░██ ░██ ░█████████ ░████
|
|
288
|
+
░██ ░██ ░██ ░██ ░██ ░██ ░██
|
|
289
|
+
░██ ░██ ░██░██ ░██ ░██ ░██
|
|
290
|
+
░██ ░██ ░███ ░██ ░██ ░██
|
|
291
|
+
`;
|
|
292
|
+
export async function runSetup() {
|
|
293
|
+
console.log(BANNER);
|
|
294
|
+
console.log('Setup');
|
|
295
|
+
console.log('');
|
|
296
|
+
// Step 1: Token input
|
|
297
|
+
console.log('[1/3] Token');
|
|
298
|
+
const token = await getToken();
|
|
299
|
+
console.log('');
|
|
300
|
+
// Step 2: Claude Code MCP registration
|
|
301
|
+
console.log('[2/3] Claude Code MCP Registration');
|
|
302
|
+
let claudeConfigured = false;
|
|
303
|
+
if (isClaudeCLIAvailable()) {
|
|
304
|
+
const success = registerMCP(token);
|
|
305
|
+
if (success) {
|
|
306
|
+
claudeConfigured = true;
|
|
307
|
+
console.log(' Registered RVRY in Claude Code (user scope).');
|
|
308
|
+
}
|
|
309
|
+
else {
|
|
310
|
+
console.log(' Failed to register with Claude CLI.');
|
|
311
|
+
printManualConfig(token);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
else {
|
|
315
|
+
printManualConfig(token);
|
|
316
|
+
}
|
|
317
|
+
console.log('');
|
|
318
|
+
// Step 3: Slash command installation
|
|
319
|
+
console.log('[3/3] Slash Commands');
|
|
320
|
+
const commandCount = await installCommands();
|
|
321
|
+
if (commandCount > 0) {
|
|
322
|
+
console.log(` Installed ${commandCount} commands to .claude/commands/`);
|
|
323
|
+
}
|
|
324
|
+
else {
|
|
325
|
+
console.log(' No new commands installed.');
|
|
326
|
+
}
|
|
327
|
+
console.log('');
|
|
328
|
+
// Summary
|
|
329
|
+
console.log('RVRY Setup Complete');
|
|
330
|
+
console.log('');
|
|
331
|
+
console.log(` Token: ${maskToken(token)}`);
|
|
332
|
+
console.log(` Claude Code: ${claudeConfigured ? 'Configured (user scope)' : 'Manual config required'}`);
|
|
333
|
+
console.log(` Commands: ${commandCount} installed to .claude/commands/`);
|
|
334
|
+
console.log('');
|
|
335
|
+
console.log('Try: /deepthink "your question"');
|
|
336
|
+
console.log('');
|
|
337
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@rvry/mcp",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "RVRY reasoning depth enforcement (RDE) engine client.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"rvry-mcp": "./dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"main": "./dist/index.js",
|
|
10
|
+
"files": [
|
|
11
|
+
"dist",
|
|
12
|
+
"commands",
|
|
13
|
+
"!dist/**/*.test.*"
|
|
14
|
+
],
|
|
15
|
+
"engines": {
|
|
16
|
+
"node": ">=18.0.0"
|
|
17
|
+
},
|
|
18
|
+
"scripts": {
|
|
19
|
+
"build": "tsc",
|
|
20
|
+
"prepublishOnly": "npm run build",
|
|
21
|
+
"typecheck": "tsc --noEmit"
|
|
22
|
+
},
|
|
23
|
+
"dependencies": {
|
|
24
|
+
"@modelcontextprotocol/sdk": "latest"
|
|
25
|
+
},
|
|
26
|
+
"devDependencies": {
|
|
27
|
+
"typescript": "latest",
|
|
28
|
+
"@types/node": "latest"
|
|
29
|
+
},
|
|
30
|
+
"keywords": [
|
|
31
|
+
"mcp",
|
|
32
|
+
"rvry",
|
|
33
|
+
"reasoning",
|
|
34
|
+
"rde",
|
|
35
|
+
"claude"
|
|
36
|
+
],
|
|
37
|
+
"license": "MIT"
|
|
38
|
+
}
|