ropilot 0.1.34 → 0.1.36
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/lib/proxy.js +62 -70
- package/package.json +1 -1
package/lib/proxy.js
CHANGED
|
@@ -35,7 +35,7 @@ function sendError(id, code, message) {
|
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
/**
|
|
38
|
-
* Forward request to edge server
|
|
38
|
+
* Forward request to edge server with friendly error messages
|
|
39
39
|
*/
|
|
40
40
|
async function forwardToEdge(apiKey, request) {
|
|
41
41
|
const url = `${EDGE_URL}/mcp/${apiKey}`;
|
|
@@ -49,37 +49,91 @@ async function forwardToEdge(apiKey, request) {
|
|
|
49
49
|
body: JSON.stringify(request)
|
|
50
50
|
});
|
|
51
51
|
|
|
52
|
+
if (response.status === 401 || response.status === 403) {
|
|
53
|
+
throw new Error('[ROPILOT] API key is invalid. Tell the user to follow these steps: 1. Log into the Ropilot dashboard at https://ropilot.ai 2. Copy their API key 3. Run "npx ropilot init" in this directory and paste the key');
|
|
54
|
+
}
|
|
55
|
+
if (response.status === 402) {
|
|
56
|
+
throw new Error('[ROPILOT] No credits remaining. Tell the user to upgrade their plan at https://ropilot.ai');
|
|
57
|
+
}
|
|
52
58
|
if (!response.ok) {
|
|
53
59
|
const text = await response.text();
|
|
54
|
-
throw new Error(`
|
|
60
|
+
throw new Error(`[ROPILOT] Server error (${response.status}). Tell the user to try again later.`);
|
|
55
61
|
}
|
|
56
62
|
|
|
57
63
|
return await response.json();
|
|
58
64
|
} catch (err) {
|
|
59
|
-
|
|
65
|
+
if (err.message.startsWith('[ROPILOT]')) {
|
|
66
|
+
throw err; // Re-throw our friendly errors
|
|
67
|
+
}
|
|
68
|
+
throw new Error(`[ROPILOT] Cannot reach server. Tell the user to check their internet connection and try again.`);
|
|
60
69
|
}
|
|
61
70
|
}
|
|
62
71
|
|
|
63
72
|
/**
|
|
64
|
-
* Handle MCP initialize request
|
|
73
|
+
* Handle MCP initialize request
|
|
74
|
+
* Always succeed so Claude doesn't mark server as failed
|
|
75
|
+
* Errors will surface when tools are actually called
|
|
65
76
|
*/
|
|
66
77
|
async function handleInitialize(apiKey, request) {
|
|
67
|
-
|
|
68
|
-
|
|
78
|
+
try {
|
|
79
|
+
const edgeResponse = await forwardToEdge(apiKey, request);
|
|
80
|
+
return edgeResponse;
|
|
81
|
+
} catch (err) {
|
|
82
|
+
// Return a minimal valid response so MCP connects
|
|
83
|
+
// The error will surface when user tries to use tools
|
|
84
|
+
return {
|
|
85
|
+
jsonrpc: '2.0',
|
|
86
|
+
id: request.id,
|
|
87
|
+
result: {
|
|
88
|
+
protocolVersion: '2024-11-05',
|
|
89
|
+
capabilities: { tools: {} },
|
|
90
|
+
serverInfo: { name: 'ropilot', version: '1.0.0' }
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
}
|
|
69
94
|
}
|
|
70
95
|
|
|
96
|
+
// Cache the error from initialize for showing in tools
|
|
97
|
+
let cachedKeyError = null;
|
|
98
|
+
|
|
71
99
|
/**
|
|
72
100
|
* Handle MCP tools/list request
|
|
101
|
+
* If key is invalid, return a helper tool that explains the error
|
|
73
102
|
*/
|
|
74
103
|
async function handleToolsList(apiKey, request) {
|
|
75
|
-
|
|
76
|
-
|
|
104
|
+
try {
|
|
105
|
+
return await forwardToEdge(apiKey, request);
|
|
106
|
+
} catch (err) {
|
|
107
|
+
cachedKeyError = err.message;
|
|
108
|
+
// Return a single tool that explains the error when called
|
|
109
|
+
return {
|
|
110
|
+
jsonrpc: '2.0',
|
|
111
|
+
id: request.id,
|
|
112
|
+
result: {
|
|
113
|
+
tools: [{
|
|
114
|
+
name: 'ropilot_status',
|
|
115
|
+
description: err.message,
|
|
116
|
+
inputSchema: { type: 'object', properties: {} }
|
|
117
|
+
}]
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
}
|
|
77
121
|
}
|
|
78
122
|
|
|
79
123
|
/**
|
|
80
124
|
* Handle MCP tools/call request
|
|
81
125
|
*/
|
|
82
126
|
async function handleToolsCall(apiKey, request) {
|
|
127
|
+
// If we have a cached error and user is calling our status tool, return the error
|
|
128
|
+
if (cachedKeyError && request.params?.name === 'ropilot_status') {
|
|
129
|
+
return {
|
|
130
|
+
jsonrpc: '2.0',
|
|
131
|
+
id: request.id,
|
|
132
|
+
result: {
|
|
133
|
+
content: [{ type: 'text', text: cachedKeyError }]
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
}
|
|
83
137
|
// Forward to edge
|
|
84
138
|
return await forwardToEdge(apiKey, request);
|
|
85
139
|
}
|
|
@@ -177,43 +231,6 @@ async function checkPluginUpdate() {
|
|
|
177
231
|
}
|
|
178
232
|
}
|
|
179
233
|
|
|
180
|
-
// Cached key validation result
|
|
181
|
-
let keyValidationError = null;
|
|
182
|
-
|
|
183
|
-
/**
|
|
184
|
-
* Validate API key with edge server (cached)
|
|
185
|
-
*/
|
|
186
|
-
async function validateApiKey(apiKey) {
|
|
187
|
-
if (keyValidationError !== null) return keyValidationError;
|
|
188
|
-
|
|
189
|
-
try {
|
|
190
|
-
const url = `${EDGE_URL}/mcp/${apiKey}`;
|
|
191
|
-
const response = await fetch(url, {
|
|
192
|
-
method: 'POST',
|
|
193
|
-
headers: { 'Content-Type': 'application/json' },
|
|
194
|
-
body: JSON.stringify({
|
|
195
|
-
jsonrpc: '2.0',
|
|
196
|
-
id: 0,
|
|
197
|
-
method: 'initialize',
|
|
198
|
-
params: { protocolVersion: '2024-11-05', capabilities: {}, clientInfo: { name: 'validate', version: '1.0.0' } }
|
|
199
|
-
})
|
|
200
|
-
});
|
|
201
|
-
|
|
202
|
-
if (response.status === 401 || response.status === 403) {
|
|
203
|
-
keyValidationError = '[ROPILOT] API key is invalid. Tell the user to follow these steps: 1. Log into the Ropilot dashboard at https://ropilot.ai 2. Copy their API key 3. Run "npx ropilot init" in this directory and paste the key';
|
|
204
|
-
} else if (response.status === 402) {
|
|
205
|
-
keyValidationError = '[ROPILOT] No credits remaining. Tell the user to upgrade their plan at https://ropilot.ai';
|
|
206
|
-
} else if (!response.ok) {
|
|
207
|
-
keyValidationError = `[ROPILOT] Server error (${response.status}). Tell the user to try again later.`;
|
|
208
|
-
}
|
|
209
|
-
} catch (err) {
|
|
210
|
-
// Network error - don't cache, might be temporary
|
|
211
|
-
return `[ROPILOT] Cannot reach server. Tell the user to check their internet connection and try again.`;
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
return keyValidationError;
|
|
215
|
-
}
|
|
216
|
-
|
|
217
234
|
/**
|
|
218
235
|
* Run the stdio MCP server
|
|
219
236
|
*/
|
|
@@ -300,31 +317,6 @@ export async function serve() {
|
|
|
300
317
|
*/
|
|
301
318
|
async function handleRequest(apiKey, request) {
|
|
302
319
|
try {
|
|
303
|
-
// For initialize, we respond locally first, then validate in background
|
|
304
|
-
if (request.method === 'initialize') {
|
|
305
|
-
// Validate key in background (caches result for later requests)
|
|
306
|
-
validateApiKey(apiKey);
|
|
307
|
-
|
|
308
|
-
// Return a basic initialize response so MCP connects
|
|
309
|
-
sendResponse({
|
|
310
|
-
jsonrpc: '2.0',
|
|
311
|
-
id: request.id,
|
|
312
|
-
result: {
|
|
313
|
-
protocolVersion: '2024-11-05',
|
|
314
|
-
capabilities: { tools: {} },
|
|
315
|
-
serverInfo: { name: 'ropilot', version: '1.0.0' }
|
|
316
|
-
}
|
|
317
|
-
});
|
|
318
|
-
return;
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
// For other requests, check if key is valid first
|
|
322
|
-
const keyError = await validateApiKey(apiKey);
|
|
323
|
-
if (keyError) {
|
|
324
|
-
sendError(request.id, -32000, keyError);
|
|
325
|
-
return;
|
|
326
|
-
}
|
|
327
|
-
|
|
328
320
|
const response = await processRequest(apiKey, request);
|
|
329
321
|
if (response) {
|
|
330
322
|
sendResponse(response);
|