@@ -105,11 +105,76 @@ func (h *JSHandler) handleExecuteJS(
105105 ctx context.Context ,
106106 request mcp.CallToolRequest ,
107107) (* mcp.CallToolResult , error ) {
108- code , err := request .RequireString ("code " )
108+ code , err := request .RequireString ("scriptCode " )
109109 if err != nil {
110110 return nil , err
111111 }
112112
113+ // Check if this looks like HTTP server code
114+ isServerCode := strings .Contains (code , "serve(" ) || strings .Contains (code , "require('ski/http/server')" )
115+
116+ if isServerCode {
117+ // For server code, run in a goroutine and return immediately
118+ return h .handleServerCode (ctx , code )
119+ } else {
120+ // For regular code, run synchronously
121+ return h .handleRegularCode (ctx , code )
122+ }
123+ }
124+
125+ func (h * JSHandler ) handleServerCode (ctx context.Context , code string ) (* mcp.CallToolResult , error ) {
126+ // Capture console output
127+ var output bytes.Buffer
128+ captureHandler := & captureLogger {buffer : & output }
129+ logger := slog .New (captureHandler )
130+
131+ // Create context with custom logger
132+ ctx = js .WithLogger (ctx , logger )
133+
134+ // Run the server code in a goroutine
135+ go func () {
136+ // Create a custom scheduler for this server
137+ schedulerOpts := ski.SchedulerOptions {
138+ InitialVMs : 1 ,
139+ MaxVMs : 1 ,
140+ }
141+ scheduler := ski .NewScheduler (schedulerOpts )
142+ ski .SetScheduler (scheduler )
143+ defer scheduler .Close ()
144+
145+ // Create a VM with proper module initialization
146+ vm := js .NewVM ()
147+
148+ // Override the HTTP server module if enabled
149+ if h .isModuleEnabled ("http" ) {
150+ h .setupHTTPModule (vm )
151+ }
152+
153+ // Execute the JavaScript code - this will block until server stops
154+ _ , err := vm .RunString (context .Background (), code )
155+ if err != nil {
156+ // Log error but don't return it since we're in a goroutine
157+ logger .Error ("Server execution error" , "error" , err )
158+ }
159+
160+ // Keep the goroutine alive indefinitely
161+ select {}
162+ }()
163+
164+ // Give the server more time to start
165+ time .Sleep (500 * time .Millisecond )
166+
167+ return & mcp.CallToolResult {
168+ Content : []mcp.Content {
169+ mcp.TextContent {
170+ Type : "text" ,
171+ Text : fmt .Sprintf ("Server started in background. Check console output:\n %s" , output .String ()),
172+ },
173+ },
174+ }, nil
175+ }
176+
177+ func (h * JSHandler ) handleRegularCode (ctx context.Context , code string ) (* mcp.CallToolResult , error ) {
113178 // Capture console output
114179 var output bytes.Buffer
115180 captureHandler := & captureLogger {buffer : & output }
@@ -124,70 +189,20 @@ func (h *JSHandler) handleExecuteJS(
124189 MaxVMs : 1 ,
125190 }
126191
127- // Set up the scheduler with filtered modules
128192 scheduler := ski .NewScheduler (schedulerOpts )
129193 ski .SetScheduler (scheduler )
130194 defer scheduler .Close ()
131195
132196 // Create a VM with proper module initialization
133197 vm := js .NewVM ()
134198
135- // Override the HTTP server module to make it non-blocking if enabled
199+ // Override the HTTP server module if enabled
136200 if h .isModuleEnabled ("http" ) {
137- // Create a custom module loader that wraps the HTTP server
138- vm .Runtime ().Set ("__originalRequire" , vm .Runtime ().Get ("require" ))
139- vm .Runtime ().Set ("require" , vm .Runtime ().ToValue (func (call sobek.FunctionCall ) sobek.Value {
140- moduleName := call .Argument (0 ).String ()
141-
142- // If requesting the HTTP server module, return our wrapped version
143- if moduleName == "ski/http/server" {
144- httpServer := & httpmodule.Server {}
145- value , err := httpServer .Instantiate (vm .Runtime ())
146- if err != nil {
147- panic (vm .Runtime ().NewGoError (err ))
148- }
149-
150- // Wrap the serve function to automatically unref servers
151- wrappedServe := vm .Runtime ().ToValue (func (call sobek.FunctionCall ) sobek.Value {
152- // Call the original serve function
153- serveFunc , ok := sobek .AssertFunction (value )
154- if ! ok {
155- panic (vm .Runtime ().NewTypeError ("serve is not a function" ))
156- }
157-
158- result , err := serveFunc (sobek .Undefined (), call .Arguments ... )
159- if err != nil {
160- panic (vm .Runtime ().NewGoError (err ))
161- }
162-
163- // If the result is a server object, unref it to prevent blocking
164- if server , ok := result .(* sobek.Object ); ok {
165- if unref := server .Get ("unref" ); unref != nil {
166- if unrefFunc , ok := sobek .AssertFunction (unref ); ok {
167- _ , _ = unrefFunc (server )
168- }
169- }
170- }
171-
172- return result
173- })
174-
175- return wrappedServe
176- }
177-
178- // For all other modules, use the original require
179- originalRequire , _ := sobek .AssertFunction (vm .Runtime ().Get ("__originalRequire" ))
180- result , err := originalRequire (sobek .Undefined (), call .Arguments ... )
181- if err != nil {
182- panic (vm .Runtime ().NewGoError (err ))
183- }
184- return result
185- }))
201+ h .setupHTTPModule (vm )
186202 }
187203
188- // Execute the JavaScript code using the VM with a timeout context
189- // This allows servers to start but doesn't block indefinitely
190- execCtx , cancel := context .WithTimeout (ctx , time .Second * 5 )
204+ // Execute the JavaScript code with a timeout for regular code
205+ execCtx , cancel := context .WithTimeout (ctx , time .Second * 10 )
191206 defer cancel ()
192207
193208 result , err := vm .RunString (execCtx , code )
@@ -223,6 +238,34 @@ func (h *JSHandler) handleExecuteJS(
223238 }, nil
224239}
225240
241+ func (h * JSHandler ) setupHTTPModule (vm js.VM ) {
242+ // Create a custom module loader that wraps the HTTP server
243+ vm .Runtime ().Set ("__originalRequire" , vm .Runtime ().Get ("require" ))
244+ vm .Runtime ().Set ("require" , vm .Runtime ().ToValue (func (call sobek.FunctionCall ) sobek.Value {
245+ moduleName := call .Argument (0 ).String ()
246+
247+ // If requesting the HTTP server module, return our wrapped version
248+ if moduleName == "ski/http/server" {
249+ httpServer := & httpmodule.Server {}
250+ value , err := httpServer .Instantiate (vm .Runtime ())
251+ if err != nil {
252+ panic (vm .Runtime ().NewGoError (err ))
253+ }
254+
255+ // Don't wrap or unref - let the server run normally
256+ return value
257+ }
258+
259+ // For all other modules, use the original require
260+ originalRequire , _ := sobek .AssertFunction (vm .Runtime ().Get ("__originalRequire" ))
261+ result , err := originalRequire (sobek .Undefined (), call .Arguments ... )
262+ if err != nil {
263+ panic (vm .Runtime ().NewGoError (err ))
264+ }
265+ return result
266+ }))
267+ }
268+
226269func (h * JSHandler ) getAvailableModules () []string {
227270 allModules := []string {
228271 "http" , "fetch" , "timers" , "buffer" , "cache" , "crypto" , "dom" ,
@@ -260,8 +303,8 @@ func NewJSServerWithConfig(config ModuleConfig) (*server.MCPServer, error) {
260303 s .AddTool (mcp .NewTool (
261304 "executeJS" ,
262305 mcp .WithDescription (description ),
263- mcp .WithString ("code " ,
264- mcp .Description ("JavaScript code to execute with Node.js-like APIs " ),
306+ mcp .WithString ("scriptCode " ,
307+ mcp .Description ("Complete JavaScript source code to execute in the ski runtime environment. This parameter accepts a full JavaScript program including variable declarations, function definitions, control flow statements, async/await operations, and module imports via require(). The code will be executed in a sandboxed environment with access to enabled ski modules. Supports modern JavaScript syntax (ES2020+) including arrow functions, destructuring, template literals, and promises. Use require() for module imports (e.g., 'const serve = require( \" ski/http/server \" )') rather than ES6 import statements. The execution context includes a console object for output, and any returned values will be displayed along with console output. For HTTP servers, they will run in the background without blocking execution completion. " ),
265308 mcp .Required (),
266309 ),
267310 ), h .handleExecuteJS )
0 commit comments