polydev-ai 1.9.16 → 1.9.17
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 +284 -17
- package/package.json +1 -1
package/mcp/stdio-wrapper.js
CHANGED
|
@@ -299,11 +299,11 @@ class StdioMCPWrapper {
|
|
|
299
299
|
|
|
300
300
|
// Pending session file for surviving restarts
|
|
301
301
|
this.PENDING_SESSION_FILE = path.join(os.homedir(), '.polydev-pending-session');
|
|
302
|
-
|
|
302
|
+
|
|
303
303
|
// MCP client info (set during initialize handshake)
|
|
304
304
|
// Used to detect which IDE is running us and exclude its CLI to avoid recursive calls
|
|
305
305
|
this.clientInfo = null;
|
|
306
|
-
|
|
306
|
+
|
|
307
307
|
// Initialize CLI Manager for local CLI functionality
|
|
308
308
|
// Disable StatusReporter - it's redundant (updateCliStatusInDatabase handles DB updates via /api/cli-status-update)
|
|
309
309
|
// and causes 401 errors because /api/mcp uses different auth than /api/cli-status-update
|
|
@@ -1000,10 +1000,11 @@ Token will be saved automatically after login.`
|
|
|
1000
1000
|
perspectivesSection += ` (${perspectiveCount} active)`;
|
|
1001
1001
|
|
|
1002
1002
|
// Show providers in order with their selected models
|
|
1003
|
-
for (
|
|
1003
|
+
for (let i = 0; i < modelPrefs.allProviders.length; i++) {
|
|
1004
|
+
const p = modelPrefs.allProviders[i];
|
|
1004
1005
|
const providerName = p.provider.charAt(0).toUpperCase() + p.provider.slice(1);
|
|
1005
1006
|
const source = p.cliId ? 'CLI' : (p.tier ? `Credits/${p.tier}` : 'API');
|
|
1006
|
-
perspectivesSection += `\n ${providerName.padEnd(12)} ${p.model} [${source}]`;
|
|
1007
|
+
perspectivesSection += `\n ${i + 1}. ${providerName.padEnd(12)} ${p.model} [${source}]`;
|
|
1007
1008
|
}
|
|
1008
1009
|
} else {
|
|
1009
1010
|
perspectivesSection += '\n (none configured - using defaults)';
|
|
@@ -1421,6 +1422,273 @@ Error: ${error.message}`
|
|
|
1421
1422
|
</html>`;
|
|
1422
1423
|
}
|
|
1423
1424
|
|
|
1425
|
+
async forwardToRemoteServer(request) {
|
|
1426
|
+
console.error(`[Stdio Wrapper] Forwarding request to remote server`);
|
|
1427
|
+
|
|
1428
|
+
try {
|
|
1429
|
+
// Use AbortController for timeout if available, otherwise rely on fetch timeout
|
|
1430
|
+
const controller = typeof AbortController !== 'undefined' ? new AbortController() : null;
|
|
1431
|
+
const timeoutId = controller ? setTimeout(() => controller.abort(), 240000) : null; // 240s timeout
|
|
1432
|
+
|
|
1433
|
+
const response = await fetch('https://www.polydev.ai/api/mcp', {
|
|
1434
|
+
method: 'POST',
|
|
1435
|
+
headers: {
|
|
1436
|
+
'Content-Type': 'application/json',
|
|
1437
|
+
'Authorization': `Bearer ${this.userToken}`,
|
|
1438
|
+
'User-Agent': 'polydev-stdio-wrapper/1.0.0'
|
|
1439
|
+
},
|
|
1440
|
+
body: JSON.stringify(request),
|
|
1441
|
+
...(controller ? { signal: controller.signal } : {})
|
|
1442
|
+
});
|
|
1443
|
+
|
|
1444
|
+
if (timeoutId) clearTimeout(timeoutId);
|
|
1445
|
+
|
|
1446
|
+
if (!response.ok) {
|
|
1447
|
+
const errorText = await response.text();
|
|
1448
|
+
console.error(`[Stdio Wrapper] Remote server error: ${response.status} - ${errorText}`);
|
|
1449
|
+
|
|
1450
|
+
// Handle 401 specifically - token expired/invalid, trigger re-auth
|
|
1451
|
+
if (response.status === 401) {
|
|
1452
|
+
console.error('[Polydev] Remote API returned 401, auto-triggering re-authentication...');
|
|
1453
|
+
return await this.triggerReAuth(request.id, 'Authentication expired. Re-authenticating...');
|
|
1454
|
+
}
|
|
1455
|
+
|
|
1456
|
+
return {
|
|
1457
|
+
jsonrpc: '2.0',
|
|
1458
|
+
id: request.id,
|
|
1459
|
+
error: {
|
|
1460
|
+
code: -32603,
|
|
1461
|
+
message: `Remote server error: ${response.status} - ${errorText}`
|
|
1462
|
+
}
|
|
1463
|
+
};
|
|
1464
|
+
}
|
|
1465
|
+
|
|
1466
|
+
const result = await response.json();
|
|
1467
|
+
console.error(`[Stdio Wrapper] Got response from remote server`);
|
|
1468
|
+
return result;
|
|
1469
|
+
} catch (error) {
|
|
1470
|
+
console.error(`[Stdio Wrapper] Network error:`, error.message);
|
|
1471
|
+
return {
|
|
1472
|
+
jsonrpc: '2.0',
|
|
1473
|
+
id: request.id,
|
|
1474
|
+
error: {
|
|
1475
|
+
code: -32603,
|
|
1476
|
+
message: `Network error: ${error.message}`
|
|
1477
|
+
}
|
|
1478
|
+
};
|
|
1479
|
+
}
|
|
1480
|
+
}
|
|
1481
|
+
|
|
1482
|
+
/**
|
|
1483
|
+
* Check if a tool is a CLI tool that should be handled locally
|
|
1484
|
+
*/
|
|
1485
|
+
isCliTool(toolName) {
|
|
1486
|
+
const cliTools = [
|
|
1487
|
+
'force_cli_detection',
|
|
1488
|
+
'get_cli_status',
|
|
1489
|
+
'send_cli_prompt',
|
|
1490
|
+
'polydev.force_cli_detection',
|
|
1491
|
+
'polydev.get_cli_status',
|
|
1492
|
+
'polydev.send_cli_prompt'
|
|
1493
|
+
];
|
|
1494
|
+
return cliTools.includes(toolName);
|
|
1495
|
+
}
|
|
1496
|
+
|
|
1497
|
+
/**
|
|
1498
|
+
* Handle get_perspectives with local CLIs + remote perspectives
|
|
1499
|
+
*/
|
|
1500
|
+
async handleGetPerspectivesWithCLIs(params, id) {
|
|
1501
|
+
console.error(`[Stdio Wrapper] Handling get_perspectives with local CLIs + remote`);
|
|
1502
|
+
|
|
1503
|
+
try {
|
|
1504
|
+
// Use existing localSendCliPrompt logic which already:
|
|
1505
|
+
// 1. Checks all local CLIs
|
|
1506
|
+
// 2. Calls remote perspectives
|
|
1507
|
+
// 3. Combines results
|
|
1508
|
+
const result = await this.localSendCliPrompt(params.arguments);
|
|
1509
|
+
|
|
1510
|
+
// Check if result indicates auth failure (from forwardToRemoteServer 401 handling)
|
|
1511
|
+
const resultText = result.content || this.formatCliResponse(result);
|
|
1512
|
+
if (result.result?.content?.[0]?.text?.includes('RE-AUTHENTICATION REQUIRED')) {
|
|
1513
|
+
// Auth failure already handled with re-auth response - pass through
|
|
1514
|
+
return result;
|
|
1515
|
+
}
|
|
1516
|
+
|
|
1517
|
+
return {
|
|
1518
|
+
jsonrpc: '2.0',
|
|
1519
|
+
id,
|
|
1520
|
+
result: {
|
|
1521
|
+
content: [
|
|
1522
|
+
{
|
|
1523
|
+
type: 'text',
|
|
1524
|
+
text: resultText
|
|
1525
|
+
}
|
|
1526
|
+
]
|
|
1527
|
+
}
|
|
1528
|
+
};
|
|
1529
|
+
|
|
1530
|
+
} catch (error) {
|
|
1531
|
+
console.error(`[Stdio Wrapper] get_perspectives error:`, error);
|
|
1532
|
+
|
|
1533
|
+
// Check if error is auth-related
|
|
1534
|
+
const errMsg = (error.message || '').toLowerCase();
|
|
1535
|
+
if (errMsg.includes('401') || errMsg.includes('unauthorized') || errMsg.includes('auth') || errMsg.includes('token')) {
|
|
1536
|
+
console.error('[Polydev] Perspectives auth error, auto-triggering re-authentication...');
|
|
1537
|
+
return await this.triggerReAuth(id, 'Authentication expired. Re-authenticating...');
|
|
1538
|
+
}
|
|
1539
|
+
|
|
1540
|
+
return {
|
|
1541
|
+
jsonrpc: '2.0',
|
|
1542
|
+
id,
|
|
1543
|
+
error: {
|
|
1544
|
+
code: -32603,
|
|
1545
|
+
message: error.message
|
|
1546
|
+
}
|
|
1547
|
+
};
|
|
1548
|
+
}
|
|
1549
|
+
}
|
|
1550
|
+
|
|
1551
|
+
/**
|
|
1552
|
+
* Handle CLI tools locally without remote server calls
|
|
1553
|
+
*/
|
|
1554
|
+
async handleLocalCliTool(request) {
|
|
1555
|
+
const { method, params, id } = request;
|
|
1556
|
+
const { name: toolName, arguments: args } = params;
|
|
1557
|
+
|
|
1558
|
+
console.error(`[Stdio Wrapper] Handling local CLI tool: ${toolName}`);
|
|
1559
|
+
|
|
1560
|
+
try {
|
|
1561
|
+
let result;
|
|
1562
|
+
|
|
1563
|
+
switch (toolName) {
|
|
1564
|
+
case 'force_cli_detection':
|
|
1565
|
+
case 'polydev.force_cli_detection':
|
|
1566
|
+
result = await this.localForceCliDetection(args);
|
|
1567
|
+
break;
|
|
1568
|
+
|
|
1569
|
+
case 'get_cli_status':
|
|
1570
|
+
case 'polydev.get_cli_status':
|
|
1571
|
+
result = await this.localGetCliStatus(args);
|
|
1572
|
+
break;
|
|
1573
|
+
|
|
1574
|
+
case 'send_cli_prompt':
|
|
1575
|
+
case 'polydev.send_cli_prompt':
|
|
1576
|
+
result = await this.localSendCliPrompt(args);
|
|
1577
|
+
break;
|
|
1578
|
+
|
|
1579
|
+
default:
|
|
1580
|
+
throw new Error(`Unknown CLI tool: ${toolName}`);
|
|
1581
|
+
}
|
|
1582
|
+
|
|
1583
|
+
return {
|
|
1584
|
+
jsonrpc: '2.0',
|
|
1585
|
+
id,
|
|
1586
|
+
result: {
|
|
1587
|
+
content: [
|
|
1588
|
+
{
|
|
1589
|
+
type: 'text',
|
|
1590
|
+
text: this.formatCliResponse(result)
|
|
1591
|
+
}
|
|
1592
|
+
]
|
|
1593
|
+
}
|
|
1594
|
+
};
|
|
1595
|
+
|
|
1596
|
+
} catch (error) {
|
|
1597
|
+
console.error(`[Stdio Wrapper] CLI tool error:`, error);
|
|
1598
|
+
return {
|
|
1599
|
+
jsonrpc: '2.0',
|
|
1600
|
+
id,
|
|
1601
|
+
error: {
|
|
1602
|
+
code: -32603,
|
|
1603
|
+
message: error.message
|
|
1604
|
+
}
|
|
1605
|
+
};
|
|
1606
|
+
}
|
|
1607
|
+
}
|
|
1608
|
+
|
|
1609
|
+
/**
|
|
1610
|
+
* Local CLI detection implementation with database updates
|
|
1611
|
+
*/
|
|
1612
|
+
async localForceCliDetection(args) {
|
|
1613
|
+
console.error(`[Stdio Wrapper] Local CLI detection with model detection started`);
|
|
1614
|
+
|
|
1615
|
+
try {
|
|
1616
|
+
const providerId = args.provider_id; // Optional - detect specific provider
|
|
1617
|
+
|
|
1618
|
+
// Force detection using CLI Manager (no remote API calls)
|
|
1619
|
+
const results = await this.cliManager.forceCliDetection(providerId);
|
|
1620
|
+
console.error(`[Stdio Wrapper] CLI detection results:`, JSON.stringify(results, null, 2));
|
|
1621
|
+
|
|
1622
|
+
// Save status locally to file-based cache
|
|
1623
|
+
await this.saveLocalCliStatus(results);
|
|
1624
|
+
|
|
1625
|
+
// Update database with CLI status
|
|
1626
|
+
await this.updateCliStatusInDatabase(results);
|
|
1627
|
+
|
|
1628
|
+
return {
|
|
1629
|
+
success: true,
|
|
1630
|
+
results,
|
|
1631
|
+
message: `Local CLI detection completed for ${providerId || 'all providers'}`,
|
|
1632
|
+
timestamp: new Date().toISOString(),
|
|
1633
|
+
local_only: true
|
|
1634
|
+
};
|
|
1635
|
+
|
|
1636
|
+
} catch (error) {
|
|
1637
|
+
console.error('[Stdio Wrapper] Local CLI detection error:', error);
|
|
1638
|
+
return {
|
|
1639
|
+
success: false,
|
|
1640
|
+
error: error.message,
|
|
1641
|
+
timestamp: new Date().toISOString(),
|
|
1642
|
+
local_only: true
|
|
1643
|
+
};
|
|
1644
|
+
}
|
|
1645
|
+
}
|
|
1646
|
+
|
|
1647
|
+
/**
|
|
1648
|
+
* Local CLI status retrieval
|
|
1649
|
+
*/
|
|
1650
|
+
async localGetCliStatus(args) {
|
|
1651
|
+
console.error(`[Stdio Wrapper] Local CLI status retrieval`);
|
|
1652
|
+
|
|
1653
|
+
try {
|
|
1654
|
+
const providerId = args.provider_id;
|
|
1655
|
+
let results = {};
|
|
1656
|
+
|
|
1657
|
+
if (providerId) {
|
|
1658
|
+
// Get specific provider status
|
|
1659
|
+
const status = await this.cliManager.getCliStatus(providerId);
|
|
1660
|
+
results = status;
|
|
1661
|
+
} else {
|
|
1662
|
+
// Get all providers status
|
|
1663
|
+
const providers = this.cliManager.getProviders();
|
|
1664
|
+
for (const provider of providers) {
|
|
1665
|
+
const status = await this.cliManager.getCliStatus(provider.id);
|
|
1666
|
+
results[provider.id] = status;
|
|
1667
|
+
}
|
|
1668
|
+
}
|
|
1669
|
+
|
|
1670
|
+
// Update database with current status
|
|
1671
|
+
await this.updateCliStatusInDatabase(results);
|
|
1672
|
+
|
|
1673
|
+
return {
|
|
1674
|
+
success: true,
|
|
1675
|
+
results,
|
|
1676
|
+
message: 'Local CLI status retrieved successfully',
|
|
1677
|
+
timestamp: new Date().toISOString(),
|
|
1678
|
+
local_only: true
|
|
1679
|
+
};
|
|
1680
|
+
|
|
1681
|
+
} catch (error) {
|
|
1682
|
+
console.error('[Stdio Wrapper] Local CLI status error:', error);
|
|
1683
|
+
return {
|
|
1684
|
+
success: false,
|
|
1685
|
+
error: error.message,
|
|
1686
|
+
timestamp: new Date().toISOString(),
|
|
1687
|
+
local_only: true
|
|
1688
|
+
};
|
|
1689
|
+
}
|
|
1690
|
+
}
|
|
1691
|
+
|
|
1424
1692
|
/**
|
|
1425
1693
|
* Get the CLI ID to exclude based on the current IDE client.
|
|
1426
1694
|
* Prevents recursive calls (e.g., Claude Code calling claude_code CLI which spawns another Claude Code).
|
|
@@ -1428,9 +1696,9 @@ Error: ${error.message}`
|
|
|
1428
1696
|
*/
|
|
1429
1697
|
getExcludedCliForCurrentIDE() {
|
|
1430
1698
|
if (!this.clientInfo?.name) return null;
|
|
1431
|
-
|
|
1699
|
+
|
|
1432
1700
|
const clientName = this.clientInfo.name.toLowerCase();
|
|
1433
|
-
|
|
1701
|
+
|
|
1434
1702
|
// Map known IDE client names to their corresponding CLI IDs
|
|
1435
1703
|
// These are the clientInfo.name values sent during MCP initialize handshake
|
|
1436
1704
|
const ideToCliMap = {
|
|
@@ -1439,8 +1707,7 @@ Error: ${error.message}`
|
|
|
1439
1707
|
'claude_code': 'claude_code',
|
|
1440
1708
|
'claude code': 'claude_code',
|
|
1441
1709
|
'claudecode': 'claude_code',
|
|
1442
|
-
// Cursor
|
|
1443
|
-
// Don't exclude any CLI for Cursor since it's not calling itself
|
|
1710
|
+
// Cursor uses Claude under the hood but is a separate IDE — don't exclude
|
|
1444
1711
|
// Gemini CLI / Google IDX
|
|
1445
1712
|
'gemini-cli': 'gemini_cli',
|
|
1446
1713
|
'gemini_cli': 'gemini_cli',
|
|
@@ -1449,17 +1716,17 @@ Error: ${error.message}`
|
|
|
1449
1716
|
'codex_cli': 'codex_cli',
|
|
1450
1717
|
'codex': 'codex_cli',
|
|
1451
1718
|
};
|
|
1452
|
-
|
|
1719
|
+
|
|
1453
1720
|
// Direct match first
|
|
1454
1721
|
if (ideToCliMap[clientName]) {
|
|
1455
1722
|
return ideToCliMap[clientName];
|
|
1456
1723
|
}
|
|
1457
|
-
|
|
1724
|
+
|
|
1458
1725
|
// Fuzzy match: check if client name contains known patterns
|
|
1459
1726
|
if (clientName.includes('claude')) return 'claude_code';
|
|
1460
1727
|
if (clientName.includes('gemini')) return 'gemini_cli';
|
|
1461
1728
|
if (clientName.includes('codex')) return 'codex_cli';
|
|
1462
|
-
|
|
1729
|
+
|
|
1463
1730
|
return null;
|
|
1464
1731
|
}
|
|
1465
1732
|
|
|
@@ -1548,17 +1815,17 @@ Error: ${error.message}`
|
|
|
1548
1815
|
|
|
1549
1816
|
// CLI priority order: Claude Code first, then Gemini, then Codex
|
|
1550
1817
|
const cliPriorityOrder = ['claude_code', 'gemini_cli', 'codex_cli'];
|
|
1551
|
-
|
|
1818
|
+
|
|
1552
1819
|
// Detect if we should exclude the current IDE's CLI to avoid recursive calls
|
|
1553
1820
|
const excludedCli = this.getExcludedCliForCurrentIDE();
|
|
1554
1821
|
if (excludedCli) {
|
|
1555
1822
|
console.error(`[Stdio Wrapper] Excluding CLI '${excludedCli}' (current IDE: ${this.clientInfo?.name}) to avoid recursive calls`);
|
|
1556
1823
|
}
|
|
1557
|
-
|
|
1824
|
+
|
|
1558
1825
|
// Build merged provider list: CLIs first, then API-only
|
|
1559
1826
|
const finalProviders = [];
|
|
1560
1827
|
const usedProviderNames = new Set();
|
|
1561
|
-
|
|
1828
|
+
|
|
1562
1829
|
// STEP 1: Add ALL available CLIs (in priority order) - they're FREE
|
|
1563
1830
|
// Don't limit to maxPerspectives here — we run all CLIs in parallel
|
|
1564
1831
|
// and take the first maxPerspectives successes (fast-collect pattern)
|
|
@@ -1584,7 +1851,7 @@ Error: ${error.message}`
|
|
|
1584
1851
|
|
|
1585
1852
|
finalProviders.push({
|
|
1586
1853
|
provider: providerName,
|
|
1587
|
-
model: configuredProvider?.model || null, // null = use CLI default model
|
|
1854
|
+
model: configuredProvider?.model || null, // null = use CLI's default model
|
|
1588
1855
|
cliId: cliId,
|
|
1589
1856
|
source: 'cli',
|
|
1590
1857
|
tier: null // CLIs don't use credits tiers
|
|
@@ -1664,7 +1931,7 @@ Error: ${error.message}`
|
|
|
1664
1931
|
}
|
|
1665
1932
|
});
|
|
1666
1933
|
|
|
1667
|
-
// Fast-collect: resolve
|
|
1934
|
+
// Fast-collect: resolve once we have maxPerspectives successes OR all complete
|
|
1668
1935
|
localResults = await this.collectFirstNSuccesses(cliPromises, maxPerspectives);
|
|
1669
1936
|
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`);
|
|
1670
1937
|
}
|
|
@@ -2456,7 +2723,7 @@ Error: ${error.message}`
|
|
|
2456
2723
|
/**
|
|
2457
2724
|
* Fetch user's model preferences from API keys
|
|
2458
2725
|
* Returns a map of CLI provider -> default_model
|
|
2459
|
-
* Also fetches and caches
|
|
2726
|
+
* Also fetches and caches perspectivesPerMessage setting and allProviders list
|
|
2460
2727
|
*/
|
|
2461
2728
|
async fetchUserModelPreferences() {
|
|
2462
2729
|
// Check cache first
|