66 * SPDX-License-Identifier: Apache-2.0
77 */
88
9- import fs from 'node:fs/promises ' ;
9+ import fs from 'node:fs' ;
1010import { createServer , type Server } from 'node:net' ;
11+ import path from 'node:path' ;
1112import process from 'node:process' ;
1213
1314import { Client } from '@modelcontextprotocol/sdk/client/index.js' ;
@@ -17,14 +18,33 @@ import {logger} from '../logger.js';
1718import { PipeTransport } from '../third_party/index.js' ;
1819import { VERSION } from '../version.js' ;
1920
21+ import type { DaemonMessage } from './types.js' ;
2022import {
23+ getDaemonPid ,
24+ getPidFilePath ,
2125 getSocketPath ,
22- handlePidFile ,
2326 INDEX_SCRIPT_PATH ,
2427 IS_WINDOWS ,
28+ isDaemonRunning ,
2529} from './utils.js' ;
2630
27- const pidFile = handlePidFile ( ) ;
31+ const pid = getDaemonPid ( ) ;
32+ if ( isDaemonRunning ( pid ) ) {
33+ try {
34+ process . kill ( pid , 0 ) ; // Throws if process doesn't exist
35+ logger ( 'Another daemon process is running' ) ;
36+ process . exit ( 1 ) ;
37+ } catch {
38+ // Process is dead, stale PID file. Proceed with startup.
39+ }
40+ }
41+ const pidFilePath = getPidFilePath ( ) ;
42+ fs . mkdirSync ( path . dirname ( pidFilePath ) , {
43+ recursive : true ,
44+ } ) ;
45+ fs . writeFileSync ( pidFilePath , process . pid . toString ( ) ) ;
46+ logger ( `Writing ${ process . pid . toString ( ) } to ${ pidFilePath } ` ) ;
47+
2848const socketPath = getSocketPath ( ) ;
2949
3050let mcpClient : Client | null = null ;
@@ -64,17 +84,6 @@ interface McpResult {
6484 content ?: McpContent [ ] | string ;
6585 text ?: string ;
6686}
67-
68- type DaemonMessage =
69- | {
70- method : 'stop' ;
71- }
72- | {
73- method : 'invoke_tool' ;
74- tool: string ;
75- args ? : Record < string , unknown > ;
76- } ;
77-
7887async function handleRequest ( msg : DaemonMessage ) {
7988 try {
8089 if ( msg . method === 'invoke_tool' ) {
@@ -93,7 +102,9 @@ async function handleRequest(msg: DaemonMessage) {
93102 result : JSON . stringify ( result ) ,
94103 } ;
95104 } else if ( msg . method === 'stop' ) {
96- // Trigger cleanup asynchronously
105+ // Ensure we are not interrupting in-progress starting.
106+ await started ;
107+ // Trigger cleanup asynchronously.
97108 setImmediate ( ( ) => {
98109 void cleanup ( ) ;
99110 } ) ;
@@ -120,7 +131,7 @@ async function startSocketServer() {
120131 // Remove existing socket file if it exists (only on non-Windows)
121132 if ( ! IS_WINDOWS ) {
122133 try {
123- await fs . unlink ( socketPath ) ;
134+ fs . unlinkSync ( socketPath ) ;
124135 } catch {
125136 // ignore errors.
126137 }
@@ -179,12 +190,22 @@ async function cleanup() {
179190 } catch ( error ) {
180191 logger ( 'Error closing MCP transport:' , error ) ;
181192 }
182- server ?. close ( ( ) => {
183- if ( ! IS_WINDOWS ) {
184- void fs . unlink ( socketPath ) . catch ( ( ) => undefined ) ;
193+ if ( server ) {
194+ await new Promise < void > ( resolve => {
195+ server ! . close ( ( ) => resolve ( ) ) ;
196+ } ) ;
197+ }
198+ if ( ! IS_WINDOWS ) {
199+ try {
200+ fs . unlinkSync ( socketPath ) ;
201+ } catch {
202+ // ignore errors
185203 }
186- } ) ;
187- await fs . unlink ( pidFile ) . catch ( ( ) => undefined ) ;
204+ }
205+ logger ( `unlinking ${ pidFilePath } ` ) ;
206+ if ( fs . existsSync ( pidFilePath ) ) {
207+ fs . unlinkSync ( pidFilePath ) ;
208+ }
188209 process . exit ( 0 ) ;
189210}
190211
@@ -208,7 +229,7 @@ process.on('unhandledRejection', error => {
208229} ) ;
209230
210231// Start the server
211- startSocketServer ( ) . catch ( error => {
232+ const started = startSocketServer ( ) . catch ( error => {
212233 logger ( 'Failed to start daemon server:' , error ) ;
213234 process . exit ( 1 ) ;
214235} ) ;
0 commit comments