polydev-ai 1.9.15 → 1.9.16
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/stdio-wrapper.js +60 -265
- package/package.json +1 -1
package/mcp/stdio-wrapper.js
CHANGED
|
@@ -300,6 +300,10 @@ class StdioMCPWrapper {
|
|
|
300
300
|
// Pending session file for surviving restarts
|
|
301
301
|
this.PENDING_SESSION_FILE = path.join(os.homedir(), '.polydev-pending-session');
|
|
302
302
|
|
|
303
|
+
// MCP client info (set during initialize handshake)
|
|
304
|
+
// Used to detect which IDE is running us and exclude its CLI to avoid recursive calls
|
|
305
|
+
this.clientInfo = null;
|
|
306
|
+
|
|
303
307
|
// Initialize CLI Manager for local CLI functionality
|
|
304
308
|
// Disable StatusReporter - it's redundant (updateCliStatusInDatabase handles DB updates via /api/cli-status-update)
|
|
305
309
|
// and causes 401 errors because /api/mcp uses different auth than /api/cli-status-update
|
|
@@ -363,6 +367,12 @@ class StdioMCPWrapper {
|
|
|
363
367
|
try {
|
|
364
368
|
switch (method) {
|
|
365
369
|
case 'initialize':
|
|
370
|
+
// Capture client info to detect which IDE is running us
|
|
371
|
+
// This lets us exclude its CLI to avoid recursive calls (e.g., Claude Code → claude_code CLI)
|
|
372
|
+
this.clientInfo = params?.clientInfo || null;
|
|
373
|
+
if (this.clientInfo) {
|
|
374
|
+
console.error(`[Stdio Wrapper] IDE detected: ${this.clientInfo.name} v${this.clientInfo.version || 'unknown'}`);
|
|
375
|
+
}
|
|
366
376
|
return {
|
|
367
377
|
jsonrpc: '2.0',
|
|
368
378
|
id,
|
|
@@ -990,11 +1000,10 @@ Token will be saved automatically after login.`
|
|
|
990
1000
|
perspectivesSection += ` (${perspectiveCount} active)`;
|
|
991
1001
|
|
|
992
1002
|
// Show providers in order with their selected models
|
|
993
|
-
for (
|
|
994
|
-
const p = modelPrefs.allProviders[i];
|
|
1003
|
+
for (const p of modelPrefs.allProviders) {
|
|
995
1004
|
const providerName = p.provider.charAt(0).toUpperCase() + p.provider.slice(1);
|
|
996
1005
|
const source = p.cliId ? 'CLI' : (p.tier ? `Credits/${p.tier}` : 'API');
|
|
997
|
-
perspectivesSection += `\n ${
|
|
1006
|
+
perspectivesSection += `\n ${providerName.padEnd(12)} ${p.model} [${source}]`;
|
|
998
1007
|
}
|
|
999
1008
|
} else {
|
|
1000
1009
|
perspectivesSection += '\n (none configured - using defaults)';
|
|
@@ -1412,271 +1421,46 @@ Error: ${error.message}`
|
|
|
1412
1421
|
</html>`;
|
|
1413
1422
|
}
|
|
1414
1423
|
|
|
1415
|
-
async forwardToRemoteServer(request) {
|
|
1416
|
-
console.error(`[Stdio Wrapper] Forwarding request to remote server`);
|
|
1417
|
-
|
|
1418
|
-
try {
|
|
1419
|
-
// Use AbortController for timeout if available, otherwise rely on fetch timeout
|
|
1420
|
-
const controller = typeof AbortController !== 'undefined' ? new AbortController() : null;
|
|
1421
|
-
const timeoutId = controller ? setTimeout(() => controller.abort(), 240000) : null; // 240s timeout
|
|
1422
|
-
|
|
1423
|
-
const response = await fetch('https://www.polydev.ai/api/mcp', {
|
|
1424
|
-
method: 'POST',
|
|
1425
|
-
headers: {
|
|
1426
|
-
'Content-Type': 'application/json',
|
|
1427
|
-
'Authorization': `Bearer ${this.userToken}`,
|
|
1428
|
-
'User-Agent': 'polydev-stdio-wrapper/1.0.0'
|
|
1429
|
-
},
|
|
1430
|
-
body: JSON.stringify(request),
|
|
1431
|
-
...(controller ? { signal: controller.signal } : {})
|
|
1432
|
-
});
|
|
1433
|
-
|
|
1434
|
-
if (timeoutId) clearTimeout(timeoutId);
|
|
1435
|
-
|
|
1436
|
-
if (!response.ok) {
|
|
1437
|
-
const errorText = await response.text();
|
|
1438
|
-
console.error(`[Stdio Wrapper] Remote server error: ${response.status} - ${errorText}`);
|
|
1439
|
-
|
|
1440
|
-
// Handle 401 specifically - token expired/invalid, trigger re-auth
|
|
1441
|
-
if (response.status === 401) {
|
|
1442
|
-
console.error('[Polydev] Remote API returned 401, auto-triggering re-authentication...');
|
|
1443
|
-
return await this.triggerReAuth(request.id, 'Authentication expired. Re-authenticating...');
|
|
1444
|
-
}
|
|
1445
|
-
|
|
1446
|
-
return {
|
|
1447
|
-
jsonrpc: '2.0',
|
|
1448
|
-
id: request.id,
|
|
1449
|
-
error: {
|
|
1450
|
-
code: -32603,
|
|
1451
|
-
message: `Remote server error: ${response.status} - ${errorText}`
|
|
1452
|
-
}
|
|
1453
|
-
};
|
|
1454
|
-
}
|
|
1455
|
-
|
|
1456
|
-
const result = await response.json();
|
|
1457
|
-
console.error(`[Stdio Wrapper] Got response from remote server`);
|
|
1458
|
-
return result;
|
|
1459
|
-
} catch (error) {
|
|
1460
|
-
console.error(`[Stdio Wrapper] Network error:`, error.message);
|
|
1461
|
-
return {
|
|
1462
|
-
jsonrpc: '2.0',
|
|
1463
|
-
id: request.id,
|
|
1464
|
-
error: {
|
|
1465
|
-
code: -32603,
|
|
1466
|
-
message: `Network error: ${error.message}`
|
|
1467
|
-
}
|
|
1468
|
-
};
|
|
1469
|
-
}
|
|
1470
|
-
}
|
|
1471
|
-
|
|
1472
|
-
/**
|
|
1473
|
-
* Check if a tool is a CLI tool that should be handled locally
|
|
1474
|
-
*/
|
|
1475
|
-
isCliTool(toolName) {
|
|
1476
|
-
const cliTools = [
|
|
1477
|
-
'force_cli_detection',
|
|
1478
|
-
'get_cli_status',
|
|
1479
|
-
'send_cli_prompt',
|
|
1480
|
-
'polydev.force_cli_detection',
|
|
1481
|
-
'polydev.get_cli_status',
|
|
1482
|
-
'polydev.send_cli_prompt'
|
|
1483
|
-
];
|
|
1484
|
-
return cliTools.includes(toolName);
|
|
1485
|
-
}
|
|
1486
|
-
|
|
1487
1424
|
/**
|
|
1488
|
-
*
|
|
1425
|
+
* Get the CLI ID to exclude based on the current IDE client.
|
|
1426
|
+
* Prevents recursive calls (e.g., Claude Code calling claude_code CLI which spawns another Claude Code).
|
|
1427
|
+
* Returns null if no CLI should be excluded.
|
|
1489
1428
|
*/
|
|
1490
|
-
|
|
1491
|
-
|
|
1429
|
+
getExcludedCliForCurrentIDE() {
|
|
1430
|
+
if (!this.clientInfo?.name) return null;
|
|
1492
1431
|
|
|
1493
|
-
|
|
1494
|
-
// Use existing localSendCliPrompt logic which already:
|
|
1495
|
-
// 1. Checks all local CLIs
|
|
1496
|
-
// 2. Calls remote perspectives
|
|
1497
|
-
// 3. Combines results
|
|
1498
|
-
const result = await this.localSendCliPrompt(params.arguments);
|
|
1499
|
-
|
|
1500
|
-
// Check if result indicates auth failure (from forwardToRemoteServer 401 handling)
|
|
1501
|
-
const resultText = result.content || this.formatCliResponse(result);
|
|
1502
|
-
if (result.result?.content?.[0]?.text?.includes('RE-AUTHENTICATION REQUIRED')) {
|
|
1503
|
-
// Auth failure already handled with re-auth response - pass through
|
|
1504
|
-
return result;
|
|
1505
|
-
}
|
|
1506
|
-
|
|
1507
|
-
return {
|
|
1508
|
-
jsonrpc: '2.0',
|
|
1509
|
-
id,
|
|
1510
|
-
result: {
|
|
1511
|
-
content: [
|
|
1512
|
-
{
|
|
1513
|
-
type: 'text',
|
|
1514
|
-
text: resultText
|
|
1515
|
-
}
|
|
1516
|
-
]
|
|
1517
|
-
}
|
|
1518
|
-
};
|
|
1519
|
-
|
|
1520
|
-
} catch (error) {
|
|
1521
|
-
console.error(`[Stdio Wrapper] get_perspectives error:`, error);
|
|
1522
|
-
|
|
1523
|
-
// Check if error is auth-related
|
|
1524
|
-
const errMsg = (error.message || '').toLowerCase();
|
|
1525
|
-
if (errMsg.includes('401') || errMsg.includes('unauthorized') || errMsg.includes('auth') || errMsg.includes('token')) {
|
|
1526
|
-
console.error('[Polydev] Perspectives auth error, auto-triggering re-authentication...');
|
|
1527
|
-
return await this.triggerReAuth(id, 'Authentication expired. Re-authenticating...');
|
|
1528
|
-
}
|
|
1529
|
-
|
|
1530
|
-
return {
|
|
1531
|
-
jsonrpc: '2.0',
|
|
1532
|
-
id,
|
|
1533
|
-
error: {
|
|
1534
|
-
code: -32603,
|
|
1535
|
-
message: error.message
|
|
1536
|
-
}
|
|
1537
|
-
};
|
|
1538
|
-
}
|
|
1539
|
-
}
|
|
1540
|
-
|
|
1541
|
-
/**
|
|
1542
|
-
* Handle CLI tools locally without remote server calls
|
|
1543
|
-
*/
|
|
1544
|
-
async handleLocalCliTool(request) {
|
|
1545
|
-
const { method, params, id } = request;
|
|
1546
|
-
const { name: toolName, arguments: args } = params;
|
|
1547
|
-
|
|
1548
|
-
console.error(`[Stdio Wrapper] Handling local CLI tool: ${toolName}`);
|
|
1549
|
-
|
|
1550
|
-
try {
|
|
1551
|
-
let result;
|
|
1552
|
-
|
|
1553
|
-
switch (toolName) {
|
|
1554
|
-
case 'force_cli_detection':
|
|
1555
|
-
case 'polydev.force_cli_detection':
|
|
1556
|
-
result = await this.localForceCliDetection(args);
|
|
1557
|
-
break;
|
|
1558
|
-
|
|
1559
|
-
case 'get_cli_status':
|
|
1560
|
-
case 'polydev.get_cli_status':
|
|
1561
|
-
result = await this.localGetCliStatus(args);
|
|
1562
|
-
break;
|
|
1563
|
-
|
|
1564
|
-
case 'send_cli_prompt':
|
|
1565
|
-
case 'polydev.send_cli_prompt':
|
|
1566
|
-
result = await this.localSendCliPrompt(args);
|
|
1567
|
-
break;
|
|
1568
|
-
|
|
1569
|
-
default:
|
|
1570
|
-
throw new Error(`Unknown CLI tool: ${toolName}`);
|
|
1571
|
-
}
|
|
1572
|
-
|
|
1573
|
-
return {
|
|
1574
|
-
jsonrpc: '2.0',
|
|
1575
|
-
id,
|
|
1576
|
-
result: {
|
|
1577
|
-
content: [
|
|
1578
|
-
{
|
|
1579
|
-
type: 'text',
|
|
1580
|
-
text: this.formatCliResponse(result)
|
|
1581
|
-
}
|
|
1582
|
-
]
|
|
1583
|
-
}
|
|
1584
|
-
};
|
|
1585
|
-
|
|
1586
|
-
} catch (error) {
|
|
1587
|
-
console.error(`[Stdio Wrapper] CLI tool error:`, error);
|
|
1588
|
-
return {
|
|
1589
|
-
jsonrpc: '2.0',
|
|
1590
|
-
id,
|
|
1591
|
-
error: {
|
|
1592
|
-
code: -32603,
|
|
1593
|
-
message: error.message
|
|
1594
|
-
}
|
|
1595
|
-
};
|
|
1596
|
-
}
|
|
1597
|
-
}
|
|
1598
|
-
|
|
1599
|
-
/**
|
|
1600
|
-
* Local CLI detection implementation with database updates
|
|
1601
|
-
*/
|
|
1602
|
-
async localForceCliDetection(args) {
|
|
1603
|
-
console.error(`[Stdio Wrapper] Local CLI detection with model detection started`);
|
|
1432
|
+
const clientName = this.clientInfo.name.toLowerCase();
|
|
1604
1433
|
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
//
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
//
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
local_only: true
|
|
1624
|
-
};
|
|
1625
|
-
|
|
1626
|
-
} catch (error) {
|
|
1627
|
-
console.error('[Stdio Wrapper] Local CLI detection error:', error);
|
|
1628
|
-
return {
|
|
1629
|
-
success: false,
|
|
1630
|
-
error: error.message,
|
|
1631
|
-
timestamp: new Date().toISOString(),
|
|
1632
|
-
local_only: true
|
|
1633
|
-
};
|
|
1634
|
-
}
|
|
1635
|
-
}
|
|
1636
|
-
|
|
1637
|
-
/**
|
|
1638
|
-
* Local CLI status retrieval
|
|
1639
|
-
*/
|
|
1640
|
-
async localGetCliStatus(args) {
|
|
1641
|
-
console.error(`[Stdio Wrapper] Local CLI status retrieval`);
|
|
1434
|
+
// Map known IDE client names to their corresponding CLI IDs
|
|
1435
|
+
// These are the clientInfo.name values sent during MCP initialize handshake
|
|
1436
|
+
const ideToCliMap = {
|
|
1437
|
+
// Claude Code variants
|
|
1438
|
+
'claude-code': 'claude_code',
|
|
1439
|
+
'claude_code': 'claude_code',
|
|
1440
|
+
'claude code': 'claude_code',
|
|
1441
|
+
'claudecode': 'claude_code',
|
|
1442
|
+
// Cursor (uses Claude under the hood but is a separate IDE)
|
|
1443
|
+
// Don't exclude any CLI for Cursor since it's not calling itself
|
|
1444
|
+
// Gemini CLI / Google IDX
|
|
1445
|
+
'gemini-cli': 'gemini_cli',
|
|
1446
|
+
'gemini_cli': 'gemini_cli',
|
|
1447
|
+
// Codex CLI / OpenAI
|
|
1448
|
+
'codex-cli': 'codex_cli',
|
|
1449
|
+
'codex_cli': 'codex_cli',
|
|
1450
|
+
'codex': 'codex_cli',
|
|
1451
|
+
};
|
|
1642
1452
|
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
if (providerId) {
|
|
1648
|
-
// Get specific provider status
|
|
1649
|
-
const status = await this.cliManager.getCliStatus(providerId);
|
|
1650
|
-
results = status;
|
|
1651
|
-
} else {
|
|
1652
|
-
// Get all providers status
|
|
1653
|
-
const providers = this.cliManager.getProviders();
|
|
1654
|
-
for (const provider of providers) {
|
|
1655
|
-
const status = await this.cliManager.getCliStatus(provider.id);
|
|
1656
|
-
results[provider.id] = status;
|
|
1657
|
-
}
|
|
1658
|
-
}
|
|
1659
|
-
|
|
1660
|
-
// Update database with current status
|
|
1661
|
-
await this.updateCliStatusInDatabase(results);
|
|
1662
|
-
|
|
1663
|
-
return {
|
|
1664
|
-
success: true,
|
|
1665
|
-
results,
|
|
1666
|
-
message: 'Local CLI status retrieved successfully',
|
|
1667
|
-
timestamp: new Date().toISOString(),
|
|
1668
|
-
local_only: true
|
|
1669
|
-
};
|
|
1670
|
-
|
|
1671
|
-
} catch (error) {
|
|
1672
|
-
console.error('[Stdio Wrapper] Local CLI status error:', error);
|
|
1673
|
-
return {
|
|
1674
|
-
success: false,
|
|
1675
|
-
error: error.message,
|
|
1676
|
-
timestamp: new Date().toISOString(),
|
|
1677
|
-
local_only: true
|
|
1678
|
-
};
|
|
1453
|
+
// Direct match first
|
|
1454
|
+
if (ideToCliMap[clientName]) {
|
|
1455
|
+
return ideToCliMap[clientName];
|
|
1679
1456
|
}
|
|
1457
|
+
|
|
1458
|
+
// Fuzzy match: check if client name contains known patterns
|
|
1459
|
+
if (clientName.includes('claude')) return 'claude_code';
|
|
1460
|
+
if (clientName.includes('gemini')) return 'gemini_cli';
|
|
1461
|
+
if (clientName.includes('codex')) return 'codex_cli';
|
|
1462
|
+
|
|
1463
|
+
return null;
|
|
1680
1464
|
}
|
|
1681
1465
|
|
|
1682
1466
|
/**
|
|
@@ -1765,6 +1549,12 @@ Error: ${error.message}`
|
|
|
1765
1549
|
// CLI priority order: Claude Code first, then Gemini, then Codex
|
|
1766
1550
|
const cliPriorityOrder = ['claude_code', 'gemini_cli', 'codex_cli'];
|
|
1767
1551
|
|
|
1552
|
+
// Detect if we should exclude the current IDE's CLI to avoid recursive calls
|
|
1553
|
+
const excludedCli = this.getExcludedCliForCurrentIDE();
|
|
1554
|
+
if (excludedCli) {
|
|
1555
|
+
console.error(`[Stdio Wrapper] Excluding CLI '${excludedCli}' (current IDE: ${this.clientInfo?.name}) to avoid recursive calls`);
|
|
1556
|
+
}
|
|
1557
|
+
|
|
1768
1558
|
// Build merged provider list: CLIs first, then API-only
|
|
1769
1559
|
const finalProviders = [];
|
|
1770
1560
|
const usedProviderNames = new Set();
|
|
@@ -1772,8 +1562,13 @@ Error: ${error.message}`
|
|
|
1772
1562
|
// STEP 1: Add ALL available CLIs (in priority order) - they're FREE
|
|
1773
1563
|
// Don't limit to maxPerspectives here — we run all CLIs in parallel
|
|
1774
1564
|
// and take the first maxPerspectives successes (fast-collect pattern)
|
|
1565
|
+
// Skip the CLI that matches the current IDE to avoid recursive calls
|
|
1775
1566
|
for (const cliId of cliPriorityOrder) {
|
|
1776
1567
|
if (!availableClis.includes(cliId)) continue;
|
|
1568
|
+
if (cliId === excludedCli) {
|
|
1569
|
+
console.error(`[Stdio Wrapper] [CLI-FIRST] Skipping ${cliId} (same as current IDE — would cause recursive call)`);
|
|
1570
|
+
continue;
|
|
1571
|
+
}
|
|
1777
1572
|
|
|
1778
1573
|
const providerName = cliToProviderMap[cliId];
|
|
1779
1574
|
usedProviderNames.add(providerName);
|
|
@@ -1789,7 +1584,7 @@ Error: ${error.message}`
|
|
|
1789
1584
|
|
|
1790
1585
|
finalProviders.push({
|
|
1791
1586
|
provider: providerName,
|
|
1792
|
-
model: configuredProvider?.model || null, // null = use CLI
|
|
1587
|
+
model: configuredProvider?.model || null, // null = use CLI default model
|
|
1793
1588
|
cliId: cliId,
|
|
1794
1589
|
source: 'cli',
|
|
1795
1590
|
tier: null // CLIs don't use credits tiers
|
|
@@ -1869,7 +1664,7 @@ Error: ${error.message}`
|
|
|
1869
1664
|
}
|
|
1870
1665
|
});
|
|
1871
1666
|
|
|
1872
|
-
// Fast-collect: resolve once we have maxPerspectives successes OR all complete
|
|
1667
|
+
// Fast-collect: resolve early once we have maxPerspectives successes OR all complete
|
|
1873
1668
|
localResults = await this.collectFirstNSuccesses(cliPromises, maxPerspectives);
|
|
1874
1669
|
console.error(`[Stdio Wrapper] Fast-collect: got ${localResults.filter(r => r.success).length} successful, ${localResults.filter(r => !r.success).length} failed out of ${cliPromises.length} CLIs`);
|
|
1875
1670
|
}
|
|
@@ -2661,7 +2456,7 @@ Error: ${error.message}`
|
|
|
2661
2456
|
/**
|
|
2662
2457
|
* Fetch user's model preferences from API keys
|
|
2663
2458
|
* Returns a map of CLI provider -> default_model
|
|
2664
|
-
* Also fetches and caches
|
|
2459
|
+
* Also fetches and caches perspectives_per_message setting and allProviders list
|
|
2665
2460
|
*/
|
|
2666
2461
|
async fetchUserModelPreferences() {
|
|
2667
2462
|
// Check cache first
|