@@ -100,13 +100,72 @@ import {
100100import { MiddlewareHandler } from "hono/types" ;
101101import { getToolsByCategory , MANAGEMENT_TOOLS } from "../tools/registry" ;
102102import { Env } from "./env" ;
103+ import { dangerouslyCreateSuperUserMCPProxy } from "./routes/proxy" ;
103104import { resetStdioConnectionPool } from "../stdio/stable-transport" ;
104105import { devLogger } from "./utils/dev-logger" ;
105106
106107const getHandleOAuthProtectedResourceMetadata = ( ) =>
107108 oAuthProtectedResourceMetadata ( auth ) ;
108109const getHandleOAuthDiscoveryMetadata = ( ) => oAuthDiscoveryMetadata ( auth ) ;
109110
111+ /**
112+ * Auto-start STDIO connections by title
113+ * Used with AUTO_START_CONNECTIONS env var
114+ *
115+ * Uses dangerouslyCreateSuperUserMCPProxy to create a system-level proxy
116+ * that spawns the STDIO process with proper credentials.
117+ */
118+ async function autoStartConnectionsByTitle (
119+ database : MeshDatabase ,
120+ titles : string [ ] ,
121+ ) {
122+ const db = database . db ;
123+
124+ // Query all STDIO connections matching the titles
125+ const connections = await db
126+ . selectFrom ( "connections" )
127+ . selectAll ( )
128+ . where ( "connection_type" , "=" , "STDIO" )
129+ . where ( "title" , "in" , titles )
130+ . execute ( ) ;
131+
132+ if ( connections . length === 0 ) {
133+ console . log ( `[AutoStart] No matching STDIO connections found` ) ;
134+ return ;
135+ }
136+
137+ console . log (
138+ `[AutoStart] Found ${ connections . length } connections to start: ${ connections . map ( ( c ) => c . title ) . join ( ", " ) } ` ,
139+ ) ;
140+
141+ for ( const conn of connections ) {
142+ try {
143+ console . log ( `[AutoStart] Starting: ${ conn . title } (${ conn . id } )` ) ;
144+
145+ // Create system context and use the superuser proxy
146+ const ctx = await ContextFactory . create ( ) ;
147+
148+ const proxy = await dangerouslyCreateSuperUserMCPProxy ( conn . id , {
149+ ...ctx ,
150+ auth : { ...ctx . auth , user : { id : "auto-start" } } ,
151+ } ) ;
152+
153+ // listTools() uses cached DB data, doesn't spawn STDIO
154+ // listPrompts() forces actual client connection, triggering spawn
155+ // Ignore "Method not found" - some MCPs don't implement prompts
156+ try {
157+ await proxy . client . listPrompts ( ) ;
158+ } catch {
159+ // Ignore - the spawn happened, that's what matters
160+ }
161+
162+ console . log ( `[AutoStart] ✓ ${ conn . title } started` ) ;
163+ } catch ( error ) {
164+ console . error ( `[AutoStart] ✗ ${ conn . title } failed:` , error ) ;
165+ }
166+ }
167+ }
168+
110169/**
111170 * Resource server metadata type
112171 */
@@ -136,7 +195,8 @@ export function createApp(options: CreateAppOptions = {}) {
136195
137196 // Kill and respawn STDIO connections on restart/HMR
138197 // Old processes have stale credentials, need fresh spawn with new tokens
139- resetStdioConnectionPool ( ) . catch ( ( err ) => {
198+ // IMPORTANT: Track this promise so autoStart waits for it to complete
199+ const poolResetPromise = resetStdioConnectionPool ( ) . catch ( ( err ) => {
140200 console . error ( "[StableStdio] Error resetting pool:" , err ) ;
141201 } ) ;
142202
@@ -494,6 +554,35 @@ export function createApp(options: CreateAppOptions = {}) {
494554 console . log ( "[EventBus] Worker started" ) ;
495555 } ) ;
496556
557+ // Auto-start connections specified in AUTO_START_CONNECTIONS env var
558+ // Format: comma-separated connection titles, e.g. "Bridge,Pilot"
559+ const autoStartConnections = process . env . AUTO_START_CONNECTIONS ;
560+ if ( autoStartConnections ) {
561+ const connectionTitles = autoStartConnections
562+ . split ( "," )
563+ . map ( ( s ) => s . trim ( ) )
564+ . filter ( Boolean ) ;
565+ if ( connectionTitles . length > 0 ) {
566+ console . log (
567+ `[AutoStart] Will start connections: ${ connectionTitles . join ( ", " ) } ` ,
568+ ) ;
569+ // Wait for pool reset to complete before starting new connections
570+ // This prevents race conditions where old processes haven't been killed yet
571+ // Also add a small delay to let the app fully initialize
572+ ( async ( ) => {
573+ try {
574+ // Wait for pool reset to finish (kills old STDIO processes)
575+ await poolResetPromise ;
576+ // Small delay to ensure ports are released
577+ await new Promise ( ( resolve ) => setTimeout ( resolve , 500 ) ) ;
578+ await autoStartConnectionsByTitle ( database , connectionTitles ) ;
579+ } catch ( error ) {
580+ console . error ( "[AutoStart] Failed:" , error ) ;
581+ }
582+ } ) ( ) ;
583+ }
584+ }
585+
497586 // Inject MeshContext into requests
498587 // Skip auth routes, static files, health check, and metrics - they don't need MeshContext
499588 app . use ( "*" , async ( c , next ) => {
0 commit comments