polydev-ai 1.0.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/mcp/server.js ADDED
@@ -0,0 +1,598 @@
1
+ #!/usr/bin/env node
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const CLIManager = require('../lib/cliManager').default;
6
+
7
+ class MCPServer {
8
+ constructor() {
9
+ this.capabilities = {
10
+ tools: {},
11
+ resources: {},
12
+ prompts: {}
13
+ };
14
+
15
+ this.tools = new Map();
16
+ this.cliManager = new CLIManager();
17
+ this.loadManifest();
18
+ }
19
+
20
+ loadManifest() {
21
+ try {
22
+ const manifestPath = path.join(__dirname, 'manifest.json');
23
+ const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
24
+
25
+ // Register tools from manifest
26
+ if (manifest.tools) {
27
+ manifest.tools.forEach(tool => {
28
+ this.tools.set(tool.name, tool);
29
+ });
30
+ }
31
+
32
+ this.manifest = manifest;
33
+ } catch (error) {
34
+ console.error('Failed to load manifest:', error);
35
+ process.exit(1);
36
+ }
37
+ }
38
+
39
+ async handleRequest(request) {
40
+ const { method, params, id } = request;
41
+
42
+ try {
43
+ switch (method) {
44
+ case 'initialize':
45
+ return this.handleInitialize(params, id);
46
+
47
+ case 'tools/list':
48
+ return this.handleToolsList(id);
49
+
50
+ case 'tools/call':
51
+ return await this.handleToolCall(params, id);
52
+
53
+ default:
54
+ return {
55
+ jsonrpc: '2.0',
56
+ id,
57
+ error: {
58
+ code: -32601,
59
+ message: 'Method not found'
60
+ }
61
+ };
62
+ }
63
+ } catch (error) {
64
+ return {
65
+ jsonrpc: '2.0',
66
+ id,
67
+ error: {
68
+ code: -32603,
69
+ message: 'Internal error',
70
+ data: error.message
71
+ }
72
+ };
73
+ }
74
+ }
75
+
76
+ handleInitialize(params, id) {
77
+ return {
78
+ jsonrpc: '2.0',
79
+ id,
80
+ result: {
81
+ protocolVersion: '2024-11-05',
82
+ capabilities: this.capabilities,
83
+ serverInfo: {
84
+ name: this.manifest.name,
85
+ version: this.manifest.version
86
+ }
87
+ }
88
+ };
89
+ }
90
+
91
+ handleToolsList(id) {
92
+ const tools = Array.from(this.tools.values()).map(tool => ({
93
+ name: tool.name,
94
+ description: tool.description,
95
+ inputSchema: tool.inputSchema
96
+ }));
97
+
98
+ return {
99
+ jsonrpc: '2.0',
100
+ id,
101
+ result: { tools }
102
+ };
103
+ }
104
+
105
+ async handleToolCall(params, id) {
106
+ const { name, arguments: args } = params;
107
+
108
+ if (!this.tools.has(name)) {
109
+ return {
110
+ jsonrpc: '2.0',
111
+ id,
112
+ error: {
113
+ code: -32602,
114
+ message: `Unknown tool: ${name}`
115
+ }
116
+ };
117
+ }
118
+
119
+ try {
120
+ let result;
121
+
122
+ switch (name) {
123
+ case 'get_perspectives':
124
+ result = await this.callPerspectivesAPI(args);
125
+ break;
126
+
127
+ case 'polydev.force_cli_detection':
128
+ result = await this.forceCliDetection(args);
129
+ break;
130
+
131
+ case 'polydev.get_cli_status':
132
+ result = await this.getCliStatus(args);
133
+ break;
134
+
135
+ case 'polydev.send_cli_prompt':
136
+ result = await this.sendCliPrompt(args);
137
+ break;
138
+
139
+ default:
140
+ throw new Error(`Tool ${name} not implemented`);
141
+ }
142
+
143
+ return {
144
+ jsonrpc: '2.0',
145
+ id,
146
+ result: {
147
+ content: [
148
+ {
149
+ type: 'text',
150
+ text: this.formatResponse(name, result)
151
+ }
152
+ ]
153
+ }
154
+ };
155
+ } catch (error) {
156
+ return {
157
+ jsonrpc: '2.0',
158
+ id,
159
+ error: {
160
+ code: -32603,
161
+ message: error.message
162
+ }
163
+ };
164
+ }
165
+ }
166
+
167
+ async callPerspectivesAPI(args) {
168
+ const serverUrl = 'https://www.polydev.ai/api/mcp';
169
+
170
+ // Validate required arguments
171
+ if (!args.prompt || typeof args.prompt !== 'string') {
172
+ throw new Error('prompt is required and must be a string');
173
+ }
174
+
175
+ // Support both parameter token (Claude Code) and environment token (Cline)
176
+ const userToken = args.user_token || process.env.POLYDEV_USER_TOKEN;
177
+
178
+ // Debug logging
179
+ console.error(`[Polydev MCP] Debug - args.user_token: ${args.user_token ? 'present' : 'missing'}`);
180
+ console.error(`[Polydev MCP] Debug - process.env.POLYDEV_USER_TOKEN: ${process.env.POLYDEV_USER_TOKEN ? 'present' : 'missing'}`);
181
+ console.error(`[Polydev MCP] Debug - final userToken: ${userToken ? 'present' : 'missing'}`);
182
+
183
+ if (!userToken || typeof userToken !== 'string') {
184
+ throw new Error('user_token is required. Either:\n' +
185
+ '1. Pass user_token parameter, or\n' +
186
+ '2. Set POLYDEV_USER_TOKEN environment variable\n' +
187
+ 'Generate token at: https://polydev.ai/dashboard/mcp-tokens');
188
+ }
189
+
190
+ console.error(`[Polydev MCP] Getting perspectives for: "${args.prompt.substring(0, 60)}${args.prompt.length > 60 ? '...' : ''}"`);
191
+ console.error(`[Polydev MCP] Models: ${args.models ? args.models.join(', ') : 'using user preferences'}`);
192
+ console.error(`[Polydev MCP] Project memory: ${args.project_memory || 'none'}`);
193
+
194
+ // Call the MCP endpoint directly with proper MCP format
195
+ const response = await fetch(serverUrl, {
196
+ method: 'POST',
197
+ headers: {
198
+ 'Content-Type': 'application/json',
199
+ 'Authorization': `Bearer ${userToken}`,
200
+ 'User-Agent': 'polydev-perspectives-mcp/1.0.0'
201
+ },
202
+ body: JSON.stringify({
203
+ jsonrpc: '2.0',
204
+ method: 'tools/call',
205
+ params: {
206
+ name: 'get_perspectives',
207
+ arguments: args
208
+ },
209
+ id: 1
210
+ })
211
+ });
212
+
213
+ if (!response.ok) {
214
+ const errorText = await response.text();
215
+ let errorMessage;
216
+
217
+ try {
218
+ const errorData = JSON.parse(errorText);
219
+ errorMessage = errorData.error || errorData.message || `HTTP ${response.status}`;
220
+ } catch {
221
+ errorMessage = errorText || `HTTP ${response.status}`;
222
+ }
223
+
224
+ if (response.status === 401) {
225
+ throw new Error(`Authentication failed: ${errorMessage}. Generate a new token at https://polydev.ai/dashboard/mcp-tools`);
226
+ }
227
+
228
+ throw new Error(`Polydev API error: ${errorMessage}`);
229
+ }
230
+
231
+ const result = await response.json();
232
+
233
+ // Handle MCP JSON-RPC response format
234
+ if (result.result && result.result.content && result.result.content[0]) {
235
+ const content = result.result.content[0].text;
236
+ console.error(`[Polydev MCP] Got MCP response successfully`);
237
+ return { content };
238
+ } else if (result.error) {
239
+ throw new Error(result.error.message || 'MCP API error');
240
+ }
241
+
242
+ console.error(`[Polydev MCP] Unexpected response format:`, result);
243
+ return result;
244
+ }
245
+
246
+ formatResponse(toolName, result) {
247
+ switch (toolName) {
248
+ case 'get_perspectives':
249
+ return this.formatPerspectivesResponse(result);
250
+
251
+ case 'polydev.force_cli_detection':
252
+ return this.formatCliDetectionResponse(result);
253
+
254
+ case 'polydev.get_cli_status':
255
+ return this.formatCliStatusResponse(result);
256
+
257
+ case 'polydev.send_cli_prompt':
258
+ return this.formatCliPromptResponse(result);
259
+
260
+ default:
261
+ return JSON.stringify(result, null, 2);
262
+ }
263
+ }
264
+
265
+ formatPerspectivesResponse(result) {
266
+ // Handle MCP response format (already formatted text)
267
+ if (result.content) {
268
+ return result.content;
269
+ }
270
+
271
+ // Handle legacy response format
272
+ if (!result.responses || result.responses.length === 0) {
273
+ return 'No perspectives received from models.';
274
+ }
275
+
276
+ let formatted = `# Multiple AI Perspectives\n\n`;
277
+ formatted += `Got ${result.responses.length} perspectives in ${result.total_latency_ms}ms using ${result.total_tokens} tokens.\n\n`;
278
+
279
+ result.responses.forEach((response, index) => {
280
+ const modelName = response.model.toUpperCase();
281
+
282
+ if (response.error) {
283
+ formatted += `## ${modelName} - ERROR\n`;
284
+ formatted += `❌ ${response.error}\n\n`;
285
+ } else {
286
+ formatted += `## ${modelName} Perspective\n`;
287
+ formatted += `${response.content}\n\n`;
288
+ if (response.tokens_used) {
289
+ formatted += `*Tokens: ${response.tokens_used}, Latency: ${response.latency_ms}ms*\n\n`;
290
+ }
291
+ }
292
+
293
+ if (index < result.responses.length - 1) {
294
+ formatted += '---\n\n';
295
+ }
296
+ });
297
+
298
+ return formatted;
299
+ }
300
+
301
+ formatCliDetectionResponse(result) {
302
+ if (!result.success) {
303
+ return `❌ CLI Detection Failed: ${result.error}`;
304
+ }
305
+
306
+ let formatted = `# CLI Provider Detection Results\n\n`;
307
+ formatted += `✅ Detection completed at ${result.timestamp}\n`;
308
+ formatted += `${result.message}\n\n`;
309
+
310
+ if (result.results && Object.keys(result.results).length > 0) {
311
+ // Count working vs broken providers
312
+ let workingProviders = 0;
313
+ let compatibilityIssues = 0;
314
+
315
+ Object.entries(result.results).forEach(([providerId, status]) => {
316
+ formatted += `## ${providerId.toUpperCase().replace('_', ' ')}\n`;
317
+
318
+ if (status.available) {
319
+ formatted += `✅ **Available** at ${status.path}\n`;
320
+ if (status.version) {
321
+ formatted += `📦 Version: ${status.version}\n`;
322
+ }
323
+
324
+ if (status.authenticated) {
325
+ formatted += `🔐 **Authenticated** - Ready to use\n`;
326
+ workingProviders++;
327
+ } else {
328
+ formatted += `❌ **Not Authenticated**\n`;
329
+ if (status.error) {
330
+ // Special formatting for compatibility issues
331
+ if (status.error.includes('Compatibility Issue')) {
332
+ formatted += `\n🚨 **COMPATIBILITY ISSUE**\n`;
333
+ formatted += `${status.error}\n\n`;
334
+ formatted += `**Need Help?** Contact support at https://polydev.ai/support with this error message.\n`;
335
+ compatibilityIssues++;
336
+ } else {
337
+ formatted += `💡 ${status.error}\n`;
338
+ }
339
+ }
340
+ }
341
+ } else {
342
+ formatted += `❌ **Not Available**\n`;
343
+ if (status.error) {
344
+ formatted += `💡 ${status.error}\n`;
345
+ }
346
+ }
347
+
348
+ formatted += `⏰ Last checked: ${new Date(status.last_checked).toLocaleString()}\n\n`;
349
+ });
350
+
351
+ // Add summary
352
+ formatted += `---\n\n`;
353
+ formatted += `## 📊 System Status Summary\n`;
354
+ formatted += `- **Working Providers**: ${workingProviders}/3 CLI tools ready\n`;
355
+ if (compatibilityIssues > 0) {
356
+ formatted += `- **⚠️ Compatibility Issues**: ${compatibilityIssues} provider(s) need attention\n`;
357
+ formatted += `- **Recommendation**: Update Node.js or CLI tools as suggested above\n`;
358
+ }
359
+
360
+ if (workingProviders >= 2) {
361
+ formatted += `- **Status**: ✅ System operational with ${workingProviders} working providers\n`;
362
+ } else if (workingProviders >= 1) {
363
+ formatted += `- **Status**: ⚠️ Limited functionality - only ${workingProviders} provider working\n`;
364
+ } else {
365
+ formatted += `- **Status**: ❌ No CLI providers working - check installations\n`;
366
+ }
367
+ }
368
+
369
+ return formatted;
370
+ }
371
+
372
+ formatCliStatusResponse(result) {
373
+ if (!result.success) {
374
+ return `❌ CLI Status Check Failed: ${result.error}`;
375
+ }
376
+
377
+ let formatted = `# CLI Provider Status\n\n`;
378
+ formatted += `Status retrieved at ${result.timestamp}\n\n`;
379
+
380
+ if (result.results && Object.keys(result.results).length > 0) {
381
+ Object.entries(result.results).forEach(([providerId, status]) => {
382
+ const providerName = providerId.toUpperCase().replace('_', ' ');
383
+ const statusIcon = status.available && status.authenticated ? '🟢' :
384
+ status.available ? '🟡' : '🔴';
385
+
386
+ formatted += `${statusIcon} **${providerName}**: `;
387
+
388
+ if (status.available && status.authenticated) {
389
+ formatted += `Ready to use`;
390
+ } else if (status.available) {
391
+ formatted += `Available but not authenticated`;
392
+ } else {
393
+ formatted += `Not available`;
394
+ }
395
+
396
+ formatted += `\n`;
397
+ });
398
+ } else {
399
+ formatted += `No CLI providers detected.\n`;
400
+ }
401
+
402
+ return formatted;
403
+ }
404
+
405
+ formatCliPromptResponse(result) {
406
+ if (!result.success) {
407
+ return `❌ CLI Prompt Failed: ${result.error}`;
408
+ }
409
+
410
+ let formatted = `# CLI Response from ${result.provider.toUpperCase().replace('_', ' ')}\n\n`;
411
+ formatted += `${result.content}\n\n`;
412
+ formatted += `---\n`;
413
+ formatted += `📊 **Stats**: ${result.tokens_used} tokens, ${result.latency_ms}ms latency\n`;
414
+ formatted += `⚙️ **Mode**: ${result.mode} | **Time**: ${result.timestamp}`;
415
+
416
+ return formatted;
417
+ }
418
+
419
+ /**
420
+ * Force CLI detection for all providers using MCP Supabase integration
421
+ */
422
+ async forceCliDetection(args) {
423
+ console.log('[MCP Server] Force CLI detection requested');
424
+
425
+ try {
426
+ const providerId = args.provider_id; // Optional - detect specific provider
427
+ console.log('[MCP Server] Calling CLI Manager forceCliDetection with providerId:', providerId);
428
+
429
+ // Force detection using CLI Manager
430
+ const results = await this.cliManager.forceCliDetection(providerId);
431
+ console.log('[MCP Server] CLI detection completed, results:', Object.keys(results));
432
+
433
+ // CLI status is cached locally in CLIManager - no database updates needed
434
+ // This MCP server runs in customer environments independently
435
+
436
+ return {
437
+ success: true,
438
+ results,
439
+ message: `CLI detection completed for ${providerId || 'all providers'}`,
440
+ timestamp: new Date().toISOString()
441
+ };
442
+
443
+ } catch (error) {
444
+ console.error('[MCP Server] CLI detection error:', error);
445
+ return {
446
+ success: false,
447
+ error: error.message,
448
+ timestamp: new Date().toISOString()
449
+ };
450
+ }
451
+ }
452
+
453
+ /**
454
+ * Get CLI status with caching using MCP integration
455
+ */
456
+ async getCliStatus(args) {
457
+ console.log('[MCP Server] Get CLI status requested');
458
+
459
+ try {
460
+ const providerId = args.provider_id;
461
+
462
+ let results = {};
463
+
464
+ if (providerId) {
465
+ // Get specific provider status
466
+ const allStatus = await this.cliManager.getCliStatus(providerId);
467
+ results[providerId] = allStatus[providerId];
468
+ } else {
469
+ // Get all providers status
470
+ const providers = this.cliManager.getAvailableProviders();
471
+ for (const provider of providers) {
472
+ const allStatus = await this.cliManager.getCliStatus(provider.id);
473
+ results[provider.id] = allStatus[provider.id];
474
+ }
475
+ }
476
+
477
+ return {
478
+ success: true,
479
+ results,
480
+ message: 'CLI status retrieved successfully',
481
+ timestamp: new Date().toISOString()
482
+ };
483
+
484
+ } catch (error) {
485
+ console.error('[MCP Server] CLI status error:', error);
486
+ return {
487
+ success: false,
488
+ error: error.message,
489
+ timestamp: new Date().toISOString()
490
+ };
491
+ }
492
+ }
493
+
494
+ /**
495
+ * Send prompt to CLI provider using MCP integration
496
+ */
497
+ async sendCliPrompt(args) {
498
+ console.log('[MCP Server] Send CLI prompt requested');
499
+
500
+ try {
501
+ const { provider_id, prompt, mode = 'args', timeout_ms = 30000, user_id } = args;
502
+
503
+ if (!provider_id || !prompt) {
504
+ throw new Error('provider_id and prompt are required');
505
+ }
506
+
507
+ // Send prompt using CLI Manager
508
+ const response = await this.cliManager.sendCliPrompt(
509
+ provider_id,
510
+ prompt,
511
+ mode,
512
+ timeout_ms
513
+ );
514
+
515
+ // CLI usage is tracked locally - no database integration needed
516
+ // This MCP server runs independently in customer environments
517
+
518
+ return {
519
+ success: response.success,
520
+ content: response.content,
521
+ error: response.error,
522
+ tokens_used: response.tokens_used,
523
+ latency_ms: response.latency_ms,
524
+ provider: provider_id,
525
+ mode,
526
+ timestamp: new Date().toISOString()
527
+ };
528
+
529
+ } catch (error) {
530
+ console.error('[MCP Server] CLI prompt error:', error);
531
+ return {
532
+ success: false,
533
+ error: error.message,
534
+ timestamp: new Date().toISOString()
535
+ };
536
+ }
537
+ }
538
+
539
+ // CLI status and usage tracking is handled locally by CLIManager
540
+ // No database integration needed - this MCP server runs independently
541
+
542
+ async start() {
543
+ console.log('Starting Polydev Perspectives MCP Server...');
544
+
545
+ // Handle JSONRPC communication over stdio
546
+ process.stdin.setEncoding('utf8');
547
+
548
+ let buffer = '';
549
+
550
+ process.stdin.on('data', async (chunk) => {
551
+ buffer += chunk;
552
+
553
+ const lines = buffer.split('\n');
554
+ buffer = lines.pop() || '';
555
+
556
+ for (const line of lines) {
557
+ if (line.trim()) {
558
+ try {
559
+ const request = JSON.parse(line);
560
+ const response = await this.handleRequest(request);
561
+ process.stdout.write(JSON.stringify(response) + '\n');
562
+ } catch (error) {
563
+ console.error('Failed to process request:', error);
564
+ }
565
+ }
566
+ }
567
+ });
568
+
569
+ process.stdin.on('end', () => {
570
+ console.log('MCP Server shutting down...');
571
+ process.exit(0);
572
+ });
573
+
574
+ // Handle process signals
575
+ process.on('SIGINT', () => {
576
+ console.log('Received SIGINT, shutting down...');
577
+ process.exit(0);
578
+ });
579
+
580
+ process.on('SIGTERM', () => {
581
+ console.log('Received SIGTERM, shutting down...');
582
+ process.exit(0);
583
+ });
584
+
585
+ console.log('MCP Server ready and listening on stdin...');
586
+ }
587
+ }
588
+
589
+ // Start the server if this file is run directly
590
+ if (require.main === module) {
591
+ const server = new MCPServer();
592
+ server.start().catch(error => {
593
+ console.error('Failed to start MCP server:', error);
594
+ process.exit(1);
595
+ });
596
+ }
597
+
598
+ module.exports = MCPServer;