66 "fmt"
77 "log/slog"
88 "strings"
9+ "time"
910
1011 "github.com/grafana/sobek"
1112 "github.com/mark3labs/mcp-go/mcp"
@@ -23,6 +24,7 @@ import (
2324 _ "github.com/shiroyk/ski/modules/fetch"
2425 _ "github.com/shiroyk/ski/modules/html"
2526 _ "github.com/shiroyk/ski/modules/http"
27+ httpmodule "github.com/shiroyk/ski/modules/http"
2628 _ "github.com/shiroyk/ski/modules/signal"
2729 _ "github.com/shiroyk/ski/modules/stream"
2830 _ "github.com/shiroyk/ski/modules/timers"
@@ -127,8 +129,68 @@ func (h *JSHandler) handleExecuteJS(
127129 ski .SetScheduler (scheduler )
128130 defer scheduler .Close ()
129131
130- // Execute the JavaScript code using ski
131- result , err := ski .RunString (ctx , code )
132+ // Create a VM with proper module initialization
133+ vm := js .NewVM ()
134+
135+ // Override the HTTP server module to make it non-blocking if enabled
136+ 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+ }))
186+ }
187+
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 )
191+ defer cancel ()
192+
193+ result , err := vm .RunString (execCtx , code )
132194
133195 if err != nil {
134196 return & mcp.CallToolResult {
@@ -167,21 +229,14 @@ func (h *JSHandler) getAvailableModules() []string {
167229 "encoding" , "ext" , "html" , "signal" , "stream" , "url" ,
168230 }
169231
170- if len (h .config .DisabledModules ) > 0 {
171- var enabled []string
172- for _ , module := range allModules {
173- if h .isModuleEnabled (module ) {
174- enabled = append (enabled , module )
175- }
232+ // Always filter through isModuleEnabled for consistency
233+ var enabled []string
234+ for _ , module := range allModules {
235+ if h .isModuleEnabled (module ) {
236+ enabled = append (enabled , module )
176237 }
177- return enabled
178- }
179-
180- if len (h .config .EnabledModules ) > 0 {
181- return h .config .EnabledModules
182238 }
183-
184- return allModules
239+ return enabled
185240}
186241
187242func NewJSServer () (* server.MCPServer , error ) {
@@ -218,7 +273,8 @@ func buildToolDescription(enabledModules []string) string {
218273 var description strings.Builder
219274
220275 description .WriteString ("Execute JavaScript code with Node.js-like APIs powered by ski runtime. " )
221- description .WriteString ("Supports ES modules, CommonJS, promises, and comprehensive JavaScript APIs.\n \n " )
276+ description .WriteString ("Supports modern JavaScript (ES2020+), CommonJS modules via require(), promises, and comprehensive JavaScript APIs. " )
277+ description .WriteString ("ES6 import statements are not supported in direct execution - use require() instead.\n \n " )
222278
223279 if len (enabledModules ) == 0 {
224280 description .WriteString ("No modules are currently enabled. Only basic JavaScript execution is available." )
@@ -227,18 +283,18 @@ func buildToolDescription(enabledModules []string) string {
227283
228284 description .WriteString ("Available modules:\n " )
229285
230- // Define module descriptions with ski's actual features and import paths
286+ // Define module descriptions with ski's actual features and require paths
231287 moduleDescriptions := map [string ]string {
232- "http" : "HTTP server creation and management (import serve from 'ski/http/server')" ,
288+ "http" : "HTTP server creation and management (const serve = require( 'ski/http/server') )" ,
233289 "fetch" : "Modern fetch API with Request, Response, Headers, FormData (available globally)" ,
234290 "timers" : "setTimeout, setInterval, clearTimeout, clearInterval (available globally)" ,
235291 "buffer" : "Buffer, Blob, File APIs for binary data handling (available globally)" ,
236- "cache" : "In-memory caching with TTL support (import cache from 'ski/cache')" ,
237- "crypto" : "Cryptographic functions (hashing, encryption, HMAC) (import crypto from 'ski/crypto')" ,
238- "dom" : "DOM Event and EventTarget APIs" ,
292+ "cache" : "In-memory caching with TTL support (const cache = require( 'ski/cache') )" ,
293+ "crypto" : "Cryptographic functions (hashing, encryption, HMAC) (const crypto = require( 'ski/crypto') )" ,
294+ "dom" : "DOM Event and EventTarget APIs (const dom = require('ski/dom')) " ,
239295 "encoding" : "TextEncoder, TextDecoder for text encoding/decoding (available globally)" ,
240- "ext" : "Extended context and utility functions" ,
241- "html" : "HTML parsing and manipulation" ,
296+ "ext" : "Extended context and utility functions (const ext = require('ski/ext')) " ,
297+ "html" : "HTML parsing and manipulation (const html = require('ski/html')) " ,
242298 "signal" : "AbortController and AbortSignal for cancellation (available globally)" ,
243299 "stream" : "ReadableStream and streaming APIs (available globally)" ,
244300 "url" : "URL and URLSearchParams APIs (available globally)" ,
@@ -252,33 +308,73 @@ func buildToolDescription(enabledModules []string) string {
252308 }
253309
254310 // Add usage examples
255- description .WriteString ("\n Example usage:\n " )
311+ description .WriteString ("\n Example usage (modern JavaScript with require()) :\n " )
256312 description .WriteString ("```javascript\n " )
257313 description .WriteString ("// Basic JavaScript execution\n " )
258314 description .WriteString ("const result = 2 + 3;\n " )
259315 description .WriteString ("console.log('Result:', result);\n \n " )
260- description .WriteString ("// Fetch API (available globally when enabled)\n " )
261- description .WriteString ("const response = await fetch('https://api.example.com/data');\n " )
262- description .WriteString ("const data = await response.json();\n \n " )
263- description .WriteString ("// HTTP server (import required)\n " )
264- description .WriteString ("import serve from 'ski/http/server';\n " )
265- description .WriteString ("serve(8000, async (req) => {\n " )
266- description .WriteString (" return new Response('Hello World');\n " )
267- description .WriteString ("});\n \n " )
268- description .WriteString ("// Cache operations (import required)\n " )
269- description .WriteString ("import cache from 'ski/cache';\n " )
270- description .WriteString ("cache.set('key', 'value');\n " )
271- description .WriteString ("console.log(cache.get('key'));\n \n " )
272- description .WriteString ("// Crypto operations (import required)\n " )
273- description .WriteString ("import crypto from 'ski/crypto';\n " )
274- description .WriteString ("const hash = crypto.md5('hello').hex();\n " )
275- description .WriteString ("console.log('MD5 hash:', hash);\n \n " )
276- description .WriteString ("// Timers (available globally)\n " )
277- description .WriteString ("setTimeout(() => console.log('Hello after 1 second'), 1000);\n \n " )
278- description .WriteString ("// Buffer operations (available globally)\n " )
279- description .WriteString ("const buffer = Buffer.from('hello', 'utf8');\n " )
280- description .WriteString ("console.log(buffer.toString('base64'));\n " )
316+
317+ // Create a set for faster lookup
318+ enabledSet := make (map [string ]bool )
319+ for _ , module := range enabledModules {
320+ enabledSet [module ] = true
321+ }
322+
323+ // Add examples only for enabled modules
324+ if enabledSet ["fetch" ] {
325+ description .WriteString ("// Fetch API (available globally when enabled)\n " )
326+ description .WriteString ("const response = await fetch('https://api.example.com/data');\n " )
327+ description .WriteString ("const data = await response.json();\n " )
328+ description .WriteString ("console.log(data);\n \n " )
329+ }
330+
331+ if enabledSet ["http" ] {
332+ description .WriteString ("// HTTP server (require import - NOT import statement)\n " )
333+ description .WriteString ("const serve = require('ski/http/server');\n " )
334+ description .WriteString ("const server = serve(8000, async (req) => {\n " )
335+ description .WriteString (" return new Response(`Hello ${req.method} ${req.url}!`);\n " )
336+ description .WriteString ("});\n " )
337+ description .WriteString ("console.log('Server running at:', server.url);\n \n " )
338+ }
339+
340+ if enabledSet ["cache" ] {
341+ description .WriteString ("// Cache operations (require import)\n " )
342+ description .WriteString ("const cache = require('ski/cache');\n " )
343+ description .WriteString ("cache.set('key', 'value');\n " )
344+ description .WriteString ("console.log(cache.get('key'));\n \n " )
345+ }
346+
347+ if enabledSet ["crypto" ] {
348+ description .WriteString ("// Crypto operations (require import)\n " )
349+ description .WriteString ("const crypto = require('ski/crypto');\n " )
350+ description .WriteString ("const hash = crypto.md5('hello').hex();\n " )
351+ description .WriteString ("console.log('MD5 hash:', hash);\n \n " )
352+ }
353+
354+ if enabledSet ["timers" ] {
355+ description .WriteString ("// Timers (available globally)\n " )
356+ description .WriteString ("setTimeout(() => {\n " )
357+ description .WriteString (" console.log('Hello after 1 second');\n " )
358+ description .WriteString ("}, 1000);\n \n " )
359+ }
360+
361+ if enabledSet ["buffer" ] {
362+ description .WriteString ("// Buffer operations (available globally)\n " )
363+ description .WriteString ("const buffer = Buffer.from('hello', 'utf8');\n " )
364+ description .WriteString ("console.log(buffer.toString('base64'));\n \n " )
365+ }
366+
281367 description .WriteString ("```\n " )
368+ description .WriteString ("\n Important notes:\n " )
369+ description .WriteString ("• Use require() for modules, NOT import statements\n " )
370+ description .WriteString ("• Modern JavaScript features supported (const/let, arrow functions, destructuring, etc.)\n " )
371+
372+ // Add HTTP-specific note only if HTTP is enabled
373+ if enabledSet ["http" ] {
374+ description .WriteString ("• HTTP servers automatically run in background and don't block execution\n " )
375+ }
376+
377+ description .WriteString ("• Async/await and Promises are fully supported\n " )
282378
283379 return description .String ()
284380}
0 commit comments