@ranger-testing/ranger-cli 1.0.10 → 1.0.13
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 +111 -0
- package/build/cli.js +290 -8
- package/build/cli.js.map +1 -1
- package/build/commands/addEnv.js +1 -1
- package/build/commands/addEnv.js.map +1 -1
- package/build/commands/clean.js +1 -1
- package/build/commands/clean.js.map +1 -1
- package/build/commands/dataMcpServer.js +1 -1
- package/build/commands/dataMcpServer.js.map +1 -1
- package/build/commands/env.js +46 -0
- package/build/commands/env.js.map +1 -0
- package/build/commands/feature.js +494 -0
- package/build/commands/feature.js.map +1 -0
- package/build/commands/index.js +1 -0
- package/build/commands/index.js.map +1 -1
- package/build/commands/skillup.js +68 -16
- package/build/commands/skillup.js.map +1 -1
- package/build/commands/start.js +1 -1
- package/build/commands/start.js.map +1 -1
- package/build/commands/status.js +42 -11
- package/build/commands/status.js.map +1 -1
- package/build/commands/update.js +29 -16
- package/build/commands/update.js.map +1 -1
- package/build/commands/updateEnv.js +1 -1
- package/build/commands/updateEnv.js.map +1 -1
- package/build/commands/useEnv.js +1 -1
- package/build/commands/useEnv.js.map +1 -1
- package/build/commands/utils/browserSessionsApi.js +1 -1
- package/build/commands/utils/browserSessionsApi.js.map +1 -1
- package/build/commands/utils/claudeConfig.js +73 -0
- package/build/commands/utils/claudeConfig.js.map +1 -0
- package/build/commands/utils/cliSecret.js +1 -1
- package/build/commands/utils/environment.js +69 -0
- package/build/commands/utils/environment.js.map +1 -0
- package/build/commands/utils/featureApi.js +190 -0
- package/build/commands/utils/featureApi.js.map +1 -0
- package/build/commands/utils/featureReportGenerator.js +170 -0
- package/build/commands/utils/featureReportGenerator.js.map +1 -0
- package/build/commands/utils/keychain.js +1 -1
- package/build/commands/utils/localAgentInstallationsApi.js +1 -1
- package/build/commands/utils/localAgentInstallationsApi.js.map +1 -1
- package/build/commands/utils/mcpConfig.js +1 -1
- package/build/commands/utils/settings.js +2 -2
- package/build/commands/utils/settings.js.map +1 -1
- package/build/commands/utils/skills.js +1 -1
- package/build/commands/utils/skills.js.map +1 -1
- package/build/commands/verifyFeature.js +451 -0
- package/build/commands/verifyFeature.js.map +1 -0
- package/build/commands/verifyInBrowser.js +1 -1
- package/build/commands/verifyInBrowser.js.map +1 -1
- package/build/skills/feature-tracker/SKILL.md +185 -0
- package/build/skills/feature-tracker/create.md +105 -0
- package/build/skills/feature-tracker/manage.md +145 -0
- package/build/skills/feature-tracker/report.md +159 -0
- package/build/skills/feature-tracker/start.md +93 -0
- package/build/skills/feature-tracker/verify.md +143 -0
- package/package.json +23 -20
- package/build/agents/bug-basher.md +0 -259
- package/build/agents/e2e-test-recommender.md +0 -164
- package/build/agents/quality-advocate.md +0 -164
- package/build/agents/ui-verifier.md +0 -100
- package/build/commands/addApp.js +0 -21
- package/build/commands/initAgents.js +0 -84
- package/build/commands/utils/agents.js +0 -45
- package/build/index.js +0 -436
- package/build/test-auth.js +0 -13
package/build/index.js
DELETED
|
@@ -1,436 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
3
|
-
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
|
|
4
|
-
import { z } from 'zod';
|
|
5
|
-
import { createServer } from 'http';
|
|
6
|
-
const RANGER_API_BASE = process.env.RANGER_API_URL || 'http://localhost:8080';
|
|
7
|
-
const PORT = parseInt(process.env.PORT || '8080', 10);
|
|
8
|
-
// Tool classification - read tools can be used with mcp:read scope
|
|
9
|
-
const READ_TOOLS = ['get_test_runs', 'get_active_tests'];
|
|
10
|
-
const tokenInfoCache = new Map();
|
|
11
|
-
const CACHE_TTL_MS = 5 * 60 * 1000; // 5 minutes
|
|
12
|
-
// Create API request function bound to a specific token
|
|
13
|
-
function createApiClient(token) {
|
|
14
|
-
console.error(`[API Client] Created with customer token: ${token ? token.substring(0, 10) + '...' : 'none'}`);
|
|
15
|
-
// All requests now use customer token
|
|
16
|
-
const apiRequest = async (endpoint, options = {}) => {
|
|
17
|
-
const url = `${RANGER_API_BASE}${endpoint}`;
|
|
18
|
-
const headers = {
|
|
19
|
-
'Content-Type': 'application/json',
|
|
20
|
-
...(token ? { Authorization: `Bearer ${token}` } : {}),
|
|
21
|
-
...options.headers,
|
|
22
|
-
};
|
|
23
|
-
console.error(`[API Request] ${options.method || 'GET'} ${url}`);
|
|
24
|
-
const response = await fetch(url, {
|
|
25
|
-
...options,
|
|
26
|
-
headers,
|
|
27
|
-
});
|
|
28
|
-
if (!response.ok) {
|
|
29
|
-
const body = await response.text();
|
|
30
|
-
console.error(`[API Error] ${response.status} ${response.statusText}: ${body}`);
|
|
31
|
-
throw new Error(`API request failed: ${response.status} ${response.statusText}`);
|
|
32
|
-
}
|
|
33
|
-
return response.json();
|
|
34
|
-
};
|
|
35
|
-
const getTokenInfo = async () => {
|
|
36
|
-
if (!token) {
|
|
37
|
-
throw new Error('No authorization token provided');
|
|
38
|
-
}
|
|
39
|
-
// Check cache
|
|
40
|
-
const cached = tokenInfoCache.get(token);
|
|
41
|
-
if (cached && cached.expiresAt > Date.now()) {
|
|
42
|
-
return { orgId: cached.orgId, scope: cached.scope, apiKeyId: cached.apiKeyId };
|
|
43
|
-
}
|
|
44
|
-
// Fetch from API using customer token
|
|
45
|
-
const data = await apiRequest('/api/v1/me');
|
|
46
|
-
const orgId = data.organizationId || data.organization?.id;
|
|
47
|
-
const scope = (data.scope || 'api:all');
|
|
48
|
-
const apiKeyId = data.apiKeyId;
|
|
49
|
-
if (!orgId) {
|
|
50
|
-
throw new Error('Could not determine organization ID from token');
|
|
51
|
-
}
|
|
52
|
-
if (!apiKeyId) {
|
|
53
|
-
throw new Error('Could not determine API key ID from token');
|
|
54
|
-
}
|
|
55
|
-
// Cache the result
|
|
56
|
-
tokenInfoCache.set(token, { orgId, scope, apiKeyId, expiresAt: Date.now() + CACHE_TTL_MS });
|
|
57
|
-
return { orgId, scope, apiKeyId };
|
|
58
|
-
};
|
|
59
|
-
// Helper to log MCP tool calls
|
|
60
|
-
const logToolCall = async (params) => {
|
|
61
|
-
try {
|
|
62
|
-
await apiRequest('/api/v1/mcp/tool-calls', {
|
|
63
|
-
method: 'POST',
|
|
64
|
-
body: JSON.stringify(params),
|
|
65
|
-
});
|
|
66
|
-
}
|
|
67
|
-
catch (error) {
|
|
68
|
-
// Log but don't fail the tool call if logging fails
|
|
69
|
-
console.error(`[MCP] Failed to log tool call: ${error}`);
|
|
70
|
-
}
|
|
71
|
-
};
|
|
72
|
-
return { apiRequest, getTokenInfo, logToolCall };
|
|
73
|
-
}
|
|
74
|
-
// Register all tools on an MCP server instance
|
|
75
|
-
async function registerTools(server, token) {
|
|
76
|
-
const { apiRequest, getTokenInfo, logToolCall } = createApiClient(token);
|
|
77
|
-
// Get token info including scope
|
|
78
|
-
let tokenInfo;
|
|
79
|
-
try {
|
|
80
|
-
tokenInfo = await getTokenInfo();
|
|
81
|
-
}
|
|
82
|
-
catch (error) {
|
|
83
|
-
console.error(`[MCP] Failed to get token info: ${error}`);
|
|
84
|
-
// Register an error-only tool if we can't get token info
|
|
85
|
-
server.registerTool('error', {
|
|
86
|
-
description: 'Error tool - authentication failed',
|
|
87
|
-
inputSchema: {},
|
|
88
|
-
}, async () => ({
|
|
89
|
-
content: [{ type: 'text', text: `Authentication failed: ${error}` }],
|
|
90
|
-
}));
|
|
91
|
-
return;
|
|
92
|
-
}
|
|
93
|
-
const { orgId, scope } = tokenInfo;
|
|
94
|
-
// Check if scope allows MCP access
|
|
95
|
-
if (scope === 'api:all') {
|
|
96
|
-
console.error(`[MCP] Access denied: API key scope 'api:all' does not allow MCP access`);
|
|
97
|
-
server.registerTool('access_denied', {
|
|
98
|
-
description: 'Access denied - API key scope does not allow MCP access',
|
|
99
|
-
inputSchema: {},
|
|
100
|
-
}, async () => ({
|
|
101
|
-
content: [{
|
|
102
|
-
type: 'text',
|
|
103
|
-
text: 'Access denied: Your API key has scope "api:all" which does not allow MCP access. Please create an API key with scope "mcp:read" or "mcp:write" to use the MCP server.'
|
|
104
|
-
}],
|
|
105
|
-
}));
|
|
106
|
-
return;
|
|
107
|
-
}
|
|
108
|
-
// Determine which tools to register based on scope
|
|
109
|
-
const canRegisterTool = (toolName) => {
|
|
110
|
-
if (scope === 'mcp:write') {
|
|
111
|
-
return true; // Write scope can use all tools
|
|
112
|
-
}
|
|
113
|
-
if (scope === 'mcp:read') {
|
|
114
|
-
return READ_TOOLS.includes(toolName);
|
|
115
|
-
}
|
|
116
|
-
return false;
|
|
117
|
-
};
|
|
118
|
-
// Helper to wrap tool handlers with logging
|
|
119
|
-
const wrapWithLogging = (toolName, handler) => {
|
|
120
|
-
return async (args) => {
|
|
121
|
-
const startTime = Date.now();
|
|
122
|
-
try {
|
|
123
|
-
const result = await handler(args);
|
|
124
|
-
const durationMs = Date.now() - startTime;
|
|
125
|
-
await logToolCall({
|
|
126
|
-
toolName,
|
|
127
|
-
inputArgs: args,
|
|
128
|
-
success: true,
|
|
129
|
-
durationMs,
|
|
130
|
-
});
|
|
131
|
-
return result;
|
|
132
|
-
}
|
|
133
|
-
catch (error) {
|
|
134
|
-
const durationMs = Date.now() - startTime;
|
|
135
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
136
|
-
await logToolCall({
|
|
137
|
-
toolName,
|
|
138
|
-
inputArgs: args,
|
|
139
|
-
success: false,
|
|
140
|
-
errorMessage,
|
|
141
|
-
durationMs,
|
|
142
|
-
});
|
|
143
|
-
throw error;
|
|
144
|
-
}
|
|
145
|
-
};
|
|
146
|
-
};
|
|
147
|
-
// Register tool: Get test runs
|
|
148
|
-
if (canRegisterTool('get_test_runs')) {
|
|
149
|
-
server.registerTool('get_test_runs', {
|
|
150
|
-
description: 'Get test runs for the organization associated with your API token',
|
|
151
|
-
inputSchema: {
|
|
152
|
-
limit: z
|
|
153
|
-
.number()
|
|
154
|
-
.optional()
|
|
155
|
-
.describe('Maximum number of runs to return (default: 10)'),
|
|
156
|
-
offset: z
|
|
157
|
-
.number()
|
|
158
|
-
.optional()
|
|
159
|
-
.describe('Pagination offset (default: 0)'),
|
|
160
|
-
},
|
|
161
|
-
}, wrapWithLogging('get_test_runs', async ({ limit, offset }) => {
|
|
162
|
-
const limitValue = limit || 10;
|
|
163
|
-
const offsetValue = offset || 0;
|
|
164
|
-
const data = await apiRequest(`/api/v1/mcp/test-runs?limit=${limitValue}&offset=${offsetValue}`);
|
|
165
|
-
return {
|
|
166
|
-
content: [
|
|
167
|
-
{
|
|
168
|
-
type: 'text',
|
|
169
|
-
text: JSON.stringify(data, null, 2),
|
|
170
|
-
},
|
|
171
|
-
],
|
|
172
|
-
};
|
|
173
|
-
}));
|
|
174
|
-
}
|
|
175
|
-
// Register tool: Generate test plan
|
|
176
|
-
if (canRegisterTool('generate_test_plan')) {
|
|
177
|
-
server.registerTool('generate_test_plan', {
|
|
178
|
-
description: 'Create a draft test and trigger TestDetailsWriter to generate detailed test steps',
|
|
179
|
-
inputSchema: {
|
|
180
|
-
name: z.string().describe('Test name/title'),
|
|
181
|
-
description: z.string().optional().describe('Test description'),
|
|
182
|
-
targetUrl: z
|
|
183
|
-
.string()
|
|
184
|
-
.optional()
|
|
185
|
-
.describe('Target URL for the test'),
|
|
186
|
-
additionalInstructions: z
|
|
187
|
-
.string()
|
|
188
|
-
.optional()
|
|
189
|
-
.describe('Additional instructions for TestDetailsWriter'),
|
|
190
|
-
priority: z
|
|
191
|
-
.enum(['p0', 'p1', 'p2', 'p3'])
|
|
192
|
-
.optional()
|
|
193
|
-
.describe('Test priority (default: p2)'),
|
|
194
|
-
},
|
|
195
|
-
}, wrapWithLogging('generate_test_plan', async ({ name, description, targetUrl, additionalInstructions, priority, }) => {
|
|
196
|
-
// Step 1: Create draft test
|
|
197
|
-
const createTestBody = {
|
|
198
|
-
test: {
|
|
199
|
-
name,
|
|
200
|
-
description,
|
|
201
|
-
priority: priority || 'p2',
|
|
202
|
-
},
|
|
203
|
-
};
|
|
204
|
-
const createTestResponse = await apiRequest('/api/v1/mcp/tests', {
|
|
205
|
-
method: 'POST',
|
|
206
|
-
body: JSON.stringify(createTestBody),
|
|
207
|
-
});
|
|
208
|
-
const testId = createTestResponse.test.id;
|
|
209
|
-
// Step 2: Trigger TestDetailsWriter
|
|
210
|
-
const generateDetailsBody = {
|
|
211
|
-
eventType: 'GenerateTestDetails',
|
|
212
|
-
payload: {
|
|
213
|
-
organizationId: orgId,
|
|
214
|
-
testId,
|
|
215
|
-
targetUrl,
|
|
216
|
-
additionalInstructions,
|
|
217
|
-
authenticationNeeded: true,
|
|
218
|
-
updateStatusOnSuccess: true,
|
|
219
|
-
},
|
|
220
|
-
};
|
|
221
|
-
const generateResponse = await apiRequest('/api/v1/mcp/agent-jobs', {
|
|
222
|
-
method: 'POST',
|
|
223
|
-
body: JSON.stringify(generateDetailsBody),
|
|
224
|
-
});
|
|
225
|
-
return {
|
|
226
|
-
content: [
|
|
227
|
-
{
|
|
228
|
-
type: 'text',
|
|
229
|
-
text: `Test plan generation initiated:\n\nTest Created:\n- ID: ${testId}\n- Name: ${name}\n- Organization: ${orgId}\n\nTestDetailsWriter Job:\n- Event ID: ${generateResponse.eventId}\n\nThe AI agent will now generate detailed test steps. You can check the test status using the test ID: ${testId}`,
|
|
230
|
-
},
|
|
231
|
-
],
|
|
232
|
-
};
|
|
233
|
-
}));
|
|
234
|
-
}
|
|
235
|
-
// Register tool: Get active tests
|
|
236
|
-
if (canRegisterTool('get_active_tests')) {
|
|
237
|
-
server.registerTool('get_active_tests', {
|
|
238
|
-
description: 'Get active tests for the organization. Returns a summary of each test (id, name, status, priority). Use get_test_details for full test information.',
|
|
239
|
-
inputSchema: {
|
|
240
|
-
limit: z
|
|
241
|
-
.number()
|
|
242
|
-
.optional()
|
|
243
|
-
.describe('Maximum number of tests to return (default: 20, max: 50)'),
|
|
244
|
-
offset: z
|
|
245
|
-
.number()
|
|
246
|
-
.optional()
|
|
247
|
-
.describe('Pagination offset (default: 0)'),
|
|
248
|
-
},
|
|
249
|
-
}, wrapWithLogging('get_active_tests', async ({ limit, offset }) => {
|
|
250
|
-
const limitValue = Math.min(limit || 20, 50); // Cap at 50
|
|
251
|
-
const offsetValue = offset || 0;
|
|
252
|
-
const testStatuses = encodeURIComponent(JSON.stringify(['active']));
|
|
253
|
-
const data = await apiRequest(`/api/v1/mcp/tests?testStatuses=${testStatuses}&limit=${limitValue}&offset=${offsetValue}`);
|
|
254
|
-
// Return summarized test data to reduce response size
|
|
255
|
-
const summarizedTests = (data.items || []).map((test) => ({
|
|
256
|
-
id: test.id,
|
|
257
|
-
name: test.name,
|
|
258
|
-
status: test.status,
|
|
259
|
-
priority: test.priority,
|
|
260
|
-
description: test.description?.substring(0, 200) || null,
|
|
261
|
-
lastUpdated: test.lastUpdated,
|
|
262
|
-
}));
|
|
263
|
-
const total = data.totalCount || data.total || summarizedTests.length;
|
|
264
|
-
return {
|
|
265
|
-
content: [
|
|
266
|
-
{
|
|
267
|
-
type: 'text',
|
|
268
|
-
text: JSON.stringify({
|
|
269
|
-
tests: summarizedTests,
|
|
270
|
-
total,
|
|
271
|
-
limit: limitValue,
|
|
272
|
-
offset: offsetValue,
|
|
273
|
-
hasMore: total > offsetValue + limitValue,
|
|
274
|
-
}, null, 2),
|
|
275
|
-
},
|
|
276
|
-
],
|
|
277
|
-
};
|
|
278
|
-
}));
|
|
279
|
-
}
|
|
280
|
-
// Register tool: Update test status
|
|
281
|
-
if (canRegisterTool('update_test_status')) {
|
|
282
|
-
server.registerTool('update_test_status', {
|
|
283
|
-
description: 'Bulk update the status of multiple tests',
|
|
284
|
-
inputSchema: {
|
|
285
|
-
testIds: z
|
|
286
|
-
.array(z.string())
|
|
287
|
-
.describe('Array of test IDs to update (e.g., ["test_xxx", "test_yyy"])'),
|
|
288
|
-
status: z
|
|
289
|
-
.enum([
|
|
290
|
-
'active',
|
|
291
|
-
'blocked_by_customer',
|
|
292
|
-
'under_maintenance',
|
|
293
|
-
'ignored',
|
|
294
|
-
'draft',
|
|
295
|
-
'ready_for_review',
|
|
296
|
-
'requested',
|
|
297
|
-
'expected_failure',
|
|
298
|
-
'canceled',
|
|
299
|
-
'suggested',
|
|
300
|
-
])
|
|
301
|
-
.describe('The new status to set for all tests'),
|
|
302
|
-
reason: z.string().describe('Reason for the status change'),
|
|
303
|
-
category: z
|
|
304
|
-
.string()
|
|
305
|
-
.optional()
|
|
306
|
-
.describe('Optional category for the status change'),
|
|
307
|
-
updatedBy: z
|
|
308
|
-
.string()
|
|
309
|
-
.default('mcp-tool')
|
|
310
|
-
.describe('Who is updating the status (default: mcp-tool)'),
|
|
311
|
-
triggerMaintenanceJobs: z
|
|
312
|
-
.boolean()
|
|
313
|
-
.optional()
|
|
314
|
-
.describe('Whether to trigger maintenance jobs (default: false)'),
|
|
315
|
-
triggerCodegenJobs: z
|
|
316
|
-
.boolean()
|
|
317
|
-
.optional()
|
|
318
|
-
.describe('Whether to trigger codegen jobs (default: false)'),
|
|
319
|
-
},
|
|
320
|
-
}, wrapWithLogging('update_test_status', async ({ testIds, status, reason, category, updatedBy, triggerMaintenanceJobs, triggerCodegenJobs, }) => {
|
|
321
|
-
const requestBody = {
|
|
322
|
-
bulkUpdateType: 'status',
|
|
323
|
-
ids: testIds,
|
|
324
|
-
change: {
|
|
325
|
-
status,
|
|
326
|
-
reason,
|
|
327
|
-
category,
|
|
328
|
-
updatedBy: updatedBy || 'mcp-tool',
|
|
329
|
-
},
|
|
330
|
-
triggerMaintenanceJobs,
|
|
331
|
-
triggerCodegenJobs,
|
|
332
|
-
};
|
|
333
|
-
const data = await apiRequest('/api/v1/mcp/tests/bulk-update', {
|
|
334
|
-
method: 'PATCH',
|
|
335
|
-
body: JSON.stringify(requestBody),
|
|
336
|
-
});
|
|
337
|
-
return {
|
|
338
|
-
content: [
|
|
339
|
-
{
|
|
340
|
-
type: 'text',
|
|
341
|
-
text: `Successfully updated ${data.updatedCount} test(s) to status "${status}".\n\nDetails:\n${JSON.stringify(data, null, 2)}`,
|
|
342
|
-
},
|
|
343
|
-
],
|
|
344
|
-
};
|
|
345
|
-
}));
|
|
346
|
-
}
|
|
347
|
-
// Register tool: Skip tests from description
|
|
348
|
-
if (canRegisterTool('skip_tests_from_description')) {
|
|
349
|
-
server.registerTool('skip_tests_from_description', {
|
|
350
|
-
description: 'Skip tests based on a user description by changing their status to under_maintenance',
|
|
351
|
-
inputSchema: {
|
|
352
|
-
userRequest: z
|
|
353
|
-
.string()
|
|
354
|
-
.describe('The user request describing which tests to skip (e.g., "skip all login tests")'),
|
|
355
|
-
},
|
|
356
|
-
}, wrapWithLogging('skip_tests_from_description', async ({ userRequest }) => {
|
|
357
|
-
return {
|
|
358
|
-
content: [
|
|
359
|
-
{
|
|
360
|
-
type: 'text',
|
|
361
|
-
text: `To skip tests based on the request: "${userRequest}"
|
|
362
|
-
|
|
363
|
-
Follow these steps:
|
|
364
|
-
|
|
365
|
-
1. Run get_active_tests to retrieve all active tests for the organization
|
|
366
|
-
2. Review the test names and descriptions in the results
|
|
367
|
-
3. Identify which test IDs are relevant to the user's request: "${userRequest}"
|
|
368
|
-
4. Call update_test_status with:
|
|
369
|
-
- testIds: array of relevant test IDs you identified
|
|
370
|
-
- status: "under_maintenance"
|
|
371
|
-
- reason: "${userRequest}"
|
|
372
|
-
- updatedBy: the user's name or "mcp-tool"
|
|
373
|
-
|
|
374
|
-
This will change the selected tests from "active" to "under_maintenance" status, effectively skipping them.`,
|
|
375
|
-
},
|
|
376
|
-
],
|
|
377
|
-
};
|
|
378
|
-
}));
|
|
379
|
-
}
|
|
380
|
-
} // end registerTools
|
|
381
|
-
// Factory function to create a new MCP server instance
|
|
382
|
-
async function createMcpServer(token) {
|
|
383
|
-
const mcpServer = new McpServer({
|
|
384
|
-
name: 'ranger',
|
|
385
|
-
version: '1.0.0',
|
|
386
|
-
});
|
|
387
|
-
// Register all tools on this server instance with the token
|
|
388
|
-
await registerTools(mcpServer, token);
|
|
389
|
-
return mcpServer;
|
|
390
|
-
}
|
|
391
|
-
// Start the server
|
|
392
|
-
async function main() {
|
|
393
|
-
const httpServer = createServer(async (req, res) => {
|
|
394
|
-
// Enable CORS
|
|
395
|
-
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
396
|
-
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, DELETE, OPTIONS');
|
|
397
|
-
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
|
|
398
|
-
if (req.method === 'OPTIONS') {
|
|
399
|
-
res.writeHead(204);
|
|
400
|
-
res.end();
|
|
401
|
-
return;
|
|
402
|
-
}
|
|
403
|
-
const url = new URL(req.url || '/', `http://localhost:${PORT}`);
|
|
404
|
-
// Extract token from Authorization header (supports both "Bearer <token>" and raw token)
|
|
405
|
-
const authHeader = req.headers.authorization;
|
|
406
|
-
const token = authHeader?.startsWith('Bearer ')
|
|
407
|
-
? authHeader.slice(7)
|
|
408
|
-
: authHeader || undefined;
|
|
409
|
-
console.error(`[MCP] ${req.method} ${url.pathname} - Auth header: ${authHeader ? 'present' : 'missing'}, Token: ${token ? token.substring(0, 10) + '...' : 'none'}`);
|
|
410
|
-
if (url.pathname === '/health') {
|
|
411
|
-
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
412
|
-
res.end(JSON.stringify({ status: 'ok' }));
|
|
413
|
-
}
|
|
414
|
-
else {
|
|
415
|
-
// Route all other paths to MCP handler
|
|
416
|
-
// Stateless mode - create a new server and transport for each request
|
|
417
|
-
// This allows horizontal scaling across multiple Cloud Run instances
|
|
418
|
-
const transport = new StreamableHTTPServerTransport({
|
|
419
|
-
sessionIdGenerator: undefined, // Stateless mode
|
|
420
|
-
});
|
|
421
|
-
// Create server with token bound to all API calls
|
|
422
|
-
const mcpServer = await createMcpServer(token);
|
|
423
|
-
await mcpServer.connect(transport);
|
|
424
|
-
await transport.handleRequest(req, res);
|
|
425
|
-
}
|
|
426
|
-
});
|
|
427
|
-
httpServer.listen(PORT, () => {
|
|
428
|
-
console.error(`Ranger MCP server running on http://localhost:${PORT}`);
|
|
429
|
-
console.error(` MCP endpoint: http://localhost:${PORT}/mcp`);
|
|
430
|
-
console.error(` Health check: http://localhost:${PORT}/health`);
|
|
431
|
-
});
|
|
432
|
-
}
|
|
433
|
-
main().catch((error) => {
|
|
434
|
-
console.error('Server error:', error);
|
|
435
|
-
process.exit(1);
|
|
436
|
-
});
|
package/build/test-auth.js
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import { chromium } from 'playwright';
|
|
2
|
-
async function main() {
|
|
3
|
-
const browser = await chromium.launch({ headless: false });
|
|
4
|
-
const context = await browser.newContext({
|
|
5
|
-
storageState: '/Users/adwithmukherjee/dev/lavender-core/lavender-core/.ranger/debug-tool/local/auth.json',
|
|
6
|
-
});
|
|
7
|
-
const page = await context.newPage();
|
|
8
|
-
await page.goto('http://localhost:3000');
|
|
9
|
-
// Keep browser open
|
|
10
|
-
console.log('Browser opened. Press Ctrl+C to close.');
|
|
11
|
-
await new Promise(() => { });
|
|
12
|
-
}
|
|
13
|
-
main().catch(console.error);
|