@@ -14,7 +14,7 @@ const STATE_DIR = join(homedir(), ".opera-browser-cli");
1414const PID_FILE = join ( STATE_DIR , "bridge.pid" ) ;
1515const CONFIG_FILE = join ( STATE_DIR , "config" ) ;
1616const LOG_FILE = join ( STATE_DIR , "bridge.log" ) ;
17- const DEFAULT_PORT = 9224 ;
17+ const DEFAULT_PORT = 9225 ;
1818
1919export function getLogFile ( ) : string {
2020 return LOG_FILE ;
@@ -202,16 +202,34 @@ function httpPost(
202202 } ) ;
203203}
204204
205- async function checkBridgeHealth ( port : number ) : Promise < boolean > {
205+ async function isBridgeHealthy ( port : number ) : Promise < boolean > {
206206 try {
207- const resp = await httpGet ( port , "/health" ) ;
207+ const resp = await httpGet ( port , "/health" , 2000 ) ;
208208 const data = JSON . parse ( resp ) ;
209209 return data . status === "ok" ;
210210 } catch {
211211 return false ;
212212 }
213213}
214214
215+ /**
216+ * Check what is listening on a port.
217+ * Returns "ok" if it is our bridge, "conflict" if something else responded,
218+ * or "free" if nothing is listening.
219+ */
220+ async function checkPortStatus ( port : number ) : Promise < "ok" | "conflict" | "free" > {
221+ try {
222+ const resp = await httpGet ( port , "/health" , 2000 ) ;
223+ const data = JSON . parse ( resp ) ;
224+ if ( data . server === "opera-browser-cli" ) {
225+ return data . status === "ok" ? "ok" : "free" ;
226+ }
227+ return "conflict" ;
228+ } catch {
229+ return "free" ;
230+ }
231+ }
232+
215233function sleep ( ms : number ) : Promise < void > {
216234 return new Promise ( ( r ) => setTimeout ( r , ms ) ) ;
217235}
@@ -225,10 +243,10 @@ export async function ensureBridge(): Promise<number> {
225243 10 ,
226244 ) ;
227245
228- // Check existing bridge via PID file
246+ // Check existing bridge via PID file (lenient: we trust our own PID file).
229247 const pidInfo = readPidFile ( ) ;
230248 if ( pidInfo && isProcessAlive ( pidInfo . pid ) ) {
231- if ( await checkBridgeHealth ( pidInfo . port ) ) {
249+ if ( await isBridgeHealthy ( pidInfo . port ) ) {
232250 return pidInfo . port ;
233251 }
234252 try {
@@ -238,6 +256,22 @@ export async function ensureBridge(): Promise<number> {
238256 }
239257 }
240258
259+ // Check for a foreign server already occupying the target port before spawning.
260+ const portStatus = await checkPortStatus ( port ) ;
261+ if ( portStatus === "ok" ) {
262+ // A healthy bridge is already running (no PID file or stale PID).
263+ return port ;
264+ }
265+ if ( portStatus === "conflict" ) {
266+ throw new CdpError (
267+ `Port ${ port } is in use by a different server (not opera-devtools-mcp). Stop it or choose a different port.` ,
268+ "BRIDGE_NOT_READY" ,
269+ [
270+ `Stop the process on port ${ port } and try again, or set OPERA_CLI_PORT to a different port number` ,
271+ ] ,
272+ ) ;
273+ }
274+
241275 // Start a new bridge
242276
243277 const bridgeScript = resolveBridgeScript ( import . meta. dirname ) ;
@@ -272,7 +306,7 @@ export async function ensureBridge(): Promise<number> {
272306 // Poll for health (max 30s — Chrome launch can be slow)
273307 const deadline = Date . now ( ) + 30_000 ;
274308 while ( Date . now ( ) < deadline ) {
275- if ( await checkBridgeHealth ( port ) ) {
309+ if ( await isBridgeHealthy ( port ) ) {
276310 return port ;
277311 }
278312 await sleep ( 500 ) ;
@@ -395,7 +429,7 @@ export async function getBridgeStatus(): Promise<BridgeStatus> {
395429 } ;
396430 }
397431 const alive = isProcessAlive ( pidInfo . pid ) ;
398- const healthy = alive ? await checkBridgeHealth ( pidInfo . port ) : false ;
432+ const healthy = alive ? await isBridgeHealthy ( pidInfo . port ) : false ;
399433 return {
400434 pidFileExists : true ,
401435 processAlive : alive ,
@@ -414,7 +448,7 @@ export async function getSessionSnapshotIfRunning(): Promise<string | null> {
414448 if ( ! pidInfo || ! isProcessAlive ( pidInfo . pid ) ) {
415449 return null ;
416450 }
417- if ( ! ( await checkBridgeHealth ( pidInfo . port ) ) ) {
451+ if ( ! ( await isBridgeHealthy ( pidInfo . port ) ) ) {
418452 return null ;
419453 }
420454 try {
0 commit comments