(
name: string,
info: MCPServerInfo,
runtime: Runtime,
_projectPath: string,
workspacePath: string,
projectSecrets: Record<string, string> | undefined,
onActivity: () => void,
signal: AbortSignal,
onAbortCleanup?: (cleanupPromise: Promise<void>) => void
)
| 1631 | } |
| 1632 | |
| 1633 | private async startSingleServerImpl( |
| 1634 | name: string, |
| 1635 | info: MCPServerInfo, |
| 1636 | runtime: Runtime, |
| 1637 | _projectPath: string, |
| 1638 | workspacePath: string, |
| 1639 | projectSecrets: Record<string, string> | undefined, |
| 1640 | onActivity: () => void, |
| 1641 | signal: AbortSignal, |
| 1642 | onAbortCleanup?: (cleanupPromise: Promise<void>) => void |
| 1643 | ): Promise<MCPServerInstance | null> { |
| 1644 | if (signal.aborted) { |
| 1645 | return null; |
| 1646 | } |
| 1647 | |
| 1648 | if (info.transport === "stdio") { |
| 1649 | log.debug("[MCP] Spawning stdio server", { name }); |
| 1650 | const execStream = await runtime.exec(info.command, { |
| 1651 | cwd: workspacePath, |
| 1652 | timeout: 60 * 60 * 24, // 24 hours — process lifetime, not startup |
| 1653 | abortSignal: signal, |
| 1654 | }); |
| 1655 | |
| 1656 | const cleanupSpawnedExecStream = async () => { |
| 1657 | try { |
| 1658 | await execStream.stdin.close(); |
| 1659 | } catch (error) { |
| 1660 | log.debug("[MCP] Error closing stdin during startup abort cleanup", { name, error }); |
| 1661 | } |
| 1662 | |
| 1663 | try { |
| 1664 | await execStream.stdout.cancel(); |
| 1665 | } catch (error) { |
| 1666 | log.debug("[MCP] Error canceling stdout during startup abort cleanup", { name, error }); |
| 1667 | } |
| 1668 | |
| 1669 | try { |
| 1670 | await execStream.stderr.cancel(); |
| 1671 | } catch (error) { |
| 1672 | log.debug("[MCP] Error canceling stderr during startup abort cleanup", { name, error }); |
| 1673 | } |
| 1674 | }; |
| 1675 | |
| 1676 | if (signal.aborted) { |
| 1677 | // runtime.exec() can return after abort when the process was already spawned. |
| 1678 | // Explicitly close/cancel stdio so the spawned process is not left running. |
| 1679 | await cleanupSpawnedExecStream(); |
| 1680 | return null; |
| 1681 | } |
| 1682 | |
| 1683 | const transport = new MCPStdioTransport(execStream); |
| 1684 | |
| 1685 | const instanceRef: { current: MCPServerInstance | null } = { current: null }; |
| 1686 | let transportClosed = false; |
| 1687 | const markClosed = () => { |
| 1688 | if (transportClosed) { |
| 1689 | return; |
| 1690 | } |
no test coverage detected