Skip to content

Commit 5dc59e1

Browse files
committed
http.serve keeps running until MCP is closed
1 parent c3f46aa commit 5dc59e1

File tree

1 file changed

+77
-6
lines changed

1 file changed

+77
-6
lines changed

jsserver/server.go

Lines changed: 77 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,9 @@ func (h *JSHandler) handleServerCode(ctx context.Context, code string) (*mcp.Cal
131131
// Create context with custom logger
132132
ctx = js.WithLogger(ctx, logger)
133133

134+
// Channel to signal if a server was actually started
135+
serverStarted := make(chan bool, 1)
136+
134137
// Run the server code in a goroutine
135138
go func() {
136139
// Create a custom scheduler for this server
@@ -147,33 +150,101 @@ func (h *JSHandler) handleServerCode(ctx context.Context, code string) (*mcp.Cal
147150

148151
// Override the HTTP server module if enabled
149152
if h.isModuleEnabled("http") {
150-
h.setupHTTPModule(vm)
153+
h.setupHTTPModuleWithCallback(vm, serverStarted)
151154
}
152155

153-
// Execute the JavaScript code - this will block until server stops
156+
// Execute the JavaScript code
154157
_, err := vm.RunString(context.Background(), code)
155158
if err != nil {
156159
// Log error but don't return it since we're in a goroutine
157160
logger.Error("Server execution error", "error", err)
161+
serverStarted <- false
162+
return
163+
}
164+
165+
// If no server was started, signal false and let goroutine exit
166+
select {
167+
case serverStarted <- false:
168+
default:
169+
// Channel already has a value, meaning a server was started
158170
}
159171

160-
// Keep the goroutine alive indefinitely
161-
select {}
172+
// Check if we should keep the goroutine alive
173+
select {
174+
case started := <-serverStarted:
175+
if started {
176+
// Keep the goroutine alive indefinitely for HTTP servers
177+
select {}
178+
}
179+
// Otherwise, let the goroutine exit naturally
180+
default:
181+
// No signal received, let goroutine exit
182+
}
162183
}()
163184

164-
// Give the server more time to start
185+
// Give the server time to start
165186
time.Sleep(500 * time.Millisecond)
166187

167188
return &mcp.CallToolResult{
168189
Content: []mcp.Content{
169190
mcp.TextContent{
170191
Type: "text",
171-
Text: fmt.Sprintf("Server started in background. Check console output:\n%s", output.String()),
192+
Text: fmt.Sprintf("Server code executed in background. Check console output:\n%s", output.String()),
172193
},
173194
},
174195
}, nil
175196
}
176197

198+
func (h *JSHandler) setupHTTPModuleWithCallback(vm js.VM, serverStarted chan bool) {
199+
// Create a custom module loader that wraps the HTTP server
200+
vm.Runtime().Set("__originalRequire", vm.Runtime().Get("require"))
201+
vm.Runtime().Set("require", vm.Runtime().ToValue(func(call sobek.FunctionCall) sobek.Value {
202+
moduleName := call.Argument(0).String()
203+
204+
// If requesting the HTTP server module, return our wrapped version
205+
if moduleName == "ski/http/server" {
206+
httpServer := &httpmodule.Server{}
207+
value, err := httpServer.Instantiate(vm.Runtime())
208+
if err != nil {
209+
panic(vm.Runtime().NewGoError(err))
210+
}
211+
212+
// Wrap the serve function to detect when a server is actually started
213+
wrappedServe := vm.Runtime().ToValue(func(call sobek.FunctionCall) sobek.Value {
214+
// Call the original serve function
215+
serveFunc, ok := sobek.AssertFunction(value)
216+
if !ok {
217+
panic(vm.Runtime().NewTypeError("serve is not a function"))
218+
}
219+
220+
result, err := serveFunc(sobek.Undefined(), call.Arguments...)
221+
if err != nil {
222+
panic(vm.Runtime().NewGoError(err))
223+
}
224+
225+
// Signal that a server was started
226+
select {
227+
case serverStarted <- true:
228+
default:
229+
// Channel already has a value
230+
}
231+
232+
return result
233+
})
234+
235+
return wrappedServe
236+
}
237+
238+
// For all other modules, use the original require
239+
originalRequire, _ := sobek.AssertFunction(vm.Runtime().Get("__originalRequire"))
240+
result, err := originalRequire(sobek.Undefined(), call.Arguments...)
241+
if err != nil {
242+
panic(vm.Runtime().NewGoError(err))
243+
}
244+
return result
245+
}))
246+
}
247+
177248
func (h *JSHandler) handleRegularCode(ctx context.Context, code string) (*mcp.CallToolResult, error) {
178249
// Capture console output
179250
var output bytes.Buffer

0 commit comments

Comments
 (0)