Skip to content

Commit c3f46aa

Browse files
committed
fix http.serve
1 parent 8d44575 commit c3f46aa

File tree

1 file changed

+100
-57
lines changed

1 file changed

+100
-57
lines changed

jsserver/server.go

Lines changed: 100 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
226269
func (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

Comments
 (0)