@@ -27,6 +27,7 @@ interface PersistedSession {
2727 worktreePath : string ;
2828 createdAt : number ;
2929 sessionUuid : string ;
30+ mcpConfigPath ?: string ;
3031}
3132
3233let mainWindow : BrowserWindow ;
@@ -49,14 +50,61 @@ function getNextSessionNumber(): number {
4950 return Math . max ( ...sessions . map ( s => s . number ) ) + 1 ;
5051}
5152
53+ // Extract MCP config for a project from ~/.claude.json
54+ function extractProjectMcpConfig ( projectDir : string ) : any {
55+ try {
56+ const claudeConfigPath = path . join ( os . homedir ( ) , ".claude.json" ) ;
57+
58+ if ( ! fs . existsSync ( claudeConfigPath ) ) {
59+ return { } ;
60+ }
61+
62+ const claudeConfig = JSON . parse ( fs . readFileSync ( claudeConfigPath , "utf8" ) ) ;
63+
64+ if ( ! claudeConfig . projects || ! claudeConfig . projects [ projectDir ] ) {
65+ return { } ;
66+ }
67+
68+ return claudeConfig . projects [ projectDir ] . mcpServers || { } ;
69+ } catch ( error ) {
70+ console . error ( "Error extracting MCP config:" , error ) ;
71+ return { } ;
72+ }
73+ }
74+
75+ // Write MCP config file for a project (shared across all sessions)
76+ function writeMcpConfigFile ( projectDir : string , mcpServers : any ) : string | null {
77+ try {
78+ // Create a hash of the project directory for unique filename
79+ const crypto = require ( "crypto" ) ;
80+ const hash = crypto . createHash ( "md5" ) . update ( projectDir ) . digest ( "hex" ) . substring ( 0 , 8 ) ;
81+
82+ const fleetcodeDir = path . join ( projectDir , ".fleetcode" ) ;
83+ if ( ! fs . existsSync ( fleetcodeDir ) ) {
84+ fs . mkdirSync ( fleetcodeDir , { recursive : true } ) ;
85+ }
86+
87+ const configFilePath = path . join ( fleetcodeDir , `mcp-config-${ hash } .json` ) ;
88+ const configContent = JSON . stringify ( { mcpServers } , null , 2 ) ;
89+
90+ fs . writeFileSync ( configFilePath , configContent , "utf8" ) ;
91+
92+ return configFilePath ;
93+ } catch ( error ) {
94+ console . error ( "Error writing MCP config file:" , error ) ;
95+ return null ;
96+ }
97+ }
98+
5299// Spawn headless PTY for MCP polling
53- function spawnMcpPoller ( sessionId : string , worktreePath : string ) {
100+ function spawnMcpPoller ( sessionId : string , projectDir : string ) {
101+ console . log ( `[MCP Poller] Spawning for session ${ sessionId } , project dir: ${ projectDir } ` ) ;
54102 const shell = os . platform ( ) === "darwin" ? "zsh" : "bash" ;
55103 const ptyProcess = pty . spawn ( shell , [ "-l" ] , {
56104 name : "xterm-color" ,
57105 cols : 80 ,
58106 rows : 30 ,
59- cwd : worktreePath ,
107+ cwd : projectDir ,
60108 env : process . env ,
61109 } ) ;
62110
@@ -68,49 +116,61 @@ function spawnMcpPoller(sessionId: string, worktreePath: string) {
68116 ptyProcess . onData ( ( data ) => {
69117 // Accumulate output without displaying it
70118 outputBuffer += data ;
119+ console . log ( `[MCP Poller ${ sessionId } ] Data:` , data . substring ( 0 , 100 ) ) ;
71120
72121 // Parse output whenever we have MCP server entries
73122 // Match lines like: "servername: url (type) - ✓ Connected" or "servername: command (stdio) - ✓ Connected"
74123 // Pattern handles both SSE (with URLs) and stdio (with commands/paths)
75124 const mcpServerLineRegex = / ^ [ \w - ] + : .+ \( (?: S S E | s t d i o ) \) \s + - \s + [ ✓ ⚠ ] / m;
76125
77126 if ( mcpServerLineRegex . test ( data ) || data . includes ( "No MCP servers configured" ) ) {
127+ console . log ( `[MCP Poller ${ sessionId } ] MCP output detected, parsing...` ) ;
78128 try {
79129 const servers = parseMcpOutput ( outputBuffer ) ;
130+ console . log ( `[MCP Poller ${ sessionId } ] Parsed servers:` , servers ) ;
80131
81132 // Merge servers into the map (upsert by name)
82133 servers . forEach ( server => {
83134 serverMap . set ( server . name , server ) ;
84135 } ) ;
85136
86137 const allServers = Array . from ( serverMap . values ( ) ) ;
138+ console . log ( `[MCP Poller ${ sessionId } ] Total servers:` , allServers ) ;
87139
88140 if ( mainWindow && ! mainWindow . isDestroyed ( ) ) {
89141 mainWindow . webContents . send ( "mcp-servers-updated" , sessionId , allServers ) ;
142+ console . log ( `[MCP Poller ${ sessionId } ] Sent mcp-servers-updated event` ) ;
90143 }
91144 } catch ( error ) {
92- console . error ( "Error parsing MCP output:" , error ) ;
145+ console . error ( `[MCP Poller ${ sessionId } ] Error parsing:` , error ) ;
93146 }
94147 }
95148
96149 // Clear buffer when we see the shell prompt (command finished)
97150 if ( ( data . includes ( "% " ) || data . includes ( "$ " ) || data . includes ( "➜ " ) ) &&
98151 outputBuffer . includes ( "claude mcp list" ) ) {
152+ console . log ( `[MCP Poller ${ sessionId } ] Command complete, clearing buffer` ) ;
99153 outputBuffer = "" ;
100154 }
101155 } ) ;
102156
103- // Wait for shell to be ready, then start polling loop
157+ // Start polling immediately and then every 60 seconds
158+ console . log ( `[MCP Poller ${ sessionId } ] Starting polling loop` ) ;
159+ const pollMcp = ( ) => {
160+ if ( mcpPollerPtyProcesses . has ( sessionId ) ) {
161+ const command = `claude mcp list` ;
162+ console . log ( `[MCP Poller ${ sessionId } ] Running command: ${ command } ` ) ;
163+ ptyProcess . write ( command + "\r" ) ;
164+ setTimeout ( pollMcp , 60000 ) ;
165+ } else {
166+ console . log ( `[MCP Poller ${ sessionId } ] No longer active, stopping` ) ;
167+ }
168+ } ;
169+
170+ // Wait briefly for shell to be ready before first poll
104171 setTimeout ( ( ) => {
105- // Run `claude mcp list` every 60 seconds
106- const pollMcp = ( ) => {
107- if ( mcpPollerPtyProcesses . has ( sessionId ) ) {
108- ptyProcess . write ( "claude mcp list\r" ) ;
109- setTimeout ( pollMcp , 60000 ) ;
110- }
111- } ;
112172 pollMcp ( ) ;
113- } , 2000 ) ; // Wait 2s for shell to initialize
173+ } , 500 ) ;
114174}
115175
116176// Parse MCP server list output
@@ -157,7 +217,9 @@ function spawnSessionPty(
157217 worktreePath : string ,
158218 config : SessionConfig ,
159219 sessionUuid : string ,
160- isNewSession : boolean
220+ isNewSession : boolean ,
221+ mcpConfigPath ?: string ,
222+ projectDir ?: string
161223) {
162224 const shell = os . platform ( ) === "darwin" ? "zsh" : "bash" ;
163225 const ptyProcess = pty . spawn ( shell , [ "-l" ] , {
@@ -211,7 +273,8 @@ function spawnSessionPty(
211273 ? `--session-id ${ sessionUuid } `
212274 : `--resume ${ sessionUuid } ` ;
213275 const skipPermissionsFlag = config . skipPermissions ? "--dangerously-skip-permissions" : "" ;
214- const flags = [ sessionFlag , skipPermissionsFlag ] . filter ( f => f ) . join ( " " ) ;
276+ const mcpConfigFlag = mcpConfigPath ? `--mcp-config ${ mcpConfigPath } ` : "" ;
277+ const flags = [ sessionFlag , skipPermissionsFlag , mcpConfigFlag ] . filter ( f => f ) . join ( " " ) ;
215278 const claudeCmd = `claude ${ flags } \r` ;
216279 ptyProcess . write ( claudeCmd ) ;
217280 } else if ( config . codingAgent === "codex" ) {
@@ -226,8 +289,8 @@ function spawnSessionPty(
226289 claudeReady = true ;
227290 // Spawn MCP poller now that Claude is authenticated and ready
228291 // Check if poller doesn't already exist to prevent duplicates
229- if ( ! mcpPollerPtyProcesses . has ( sessionId ) ) {
230- spawnMcpPoller ( sessionId , worktreePath ) ;
292+ if ( ! mcpPollerPtyProcesses . has ( sessionId ) && projectDir ) {
293+ spawnMcpPoller ( sessionId , projectDir ) ;
231294 }
232295 }
233296 }
@@ -377,6 +440,10 @@ ipcMain.on("create-session", async (event, config: SessionConfig) => {
377440 // Create git worktree with unique branch name
378441 const worktreePath = await createWorktree ( config . projectDir , config . parentBranch , sessionNumber , sessionUuid ) ;
379442
443+ // Extract and write MCP config
444+ const mcpServers = extractProjectMcpConfig ( config . projectDir ) ;
445+ const mcpConfigPath = writeMcpConfigFile ( config . projectDir , mcpServers ) ;
446+
380447 // Create persisted session metadata
381448 const persistedSession : PersistedSession = {
382449 id : sessionId ,
@@ -386,6 +453,7 @@ ipcMain.on("create-session", async (event, config: SessionConfig) => {
386453 worktreePath,
387454 createdAt : Date . now ( ) ,
388455 sessionUuid,
456+ mcpConfigPath : mcpConfigPath || undefined ,
389457 } ;
390458
391459 // Save to store
@@ -394,7 +462,7 @@ ipcMain.on("create-session", async (event, config: SessionConfig) => {
394462 savePersistedSessions ( sessions ) ;
395463
396464 // Spawn PTY in worktree directory
397- spawnSessionPty ( sessionId , worktreePath , config , sessionUuid , true ) ;
465+ spawnSessionPty ( sessionId , worktreePath , config , sessionUuid , true , mcpConfigPath || undefined , config . projectDir ) ;
398466
399467 event . reply ( "session-created" , sessionId , persistedSession ) ;
400468 } catch ( error ) {
@@ -438,7 +506,7 @@ ipcMain.on("reopen-session", (event, sessionId: string) => {
438506 }
439507
440508 // Spawn new PTY in worktree directory
441- spawnSessionPty ( sessionId , session . worktreePath , session . config , session . sessionUuid , false ) ;
509+ spawnSessionPty ( sessionId , session . worktreePath , session . config , session . sessionUuid , false , session . mcpConfigPath , session . config . projectDir ) ;
442510
443511 event . reply ( "session-reopened" , sessionId ) ;
444512} ) ;
0 commit comments