Skip to content

Commit 8d44575

Browse files
committed
cleanup
1 parent f5cd4c2 commit 8d44575

File tree

2 files changed

+143
-47
lines changed

2 files changed

+143
-47
lines changed

jsserver/description_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ func TestBuildToolDescription(t *testing.T) {
2525
"• timers: setTimeout, setInterval, clearTimeout, clearInterval",
2626
"• buffer: Buffer, Blob, File APIs for binary data handling",
2727
"• crypto: Cryptographic functions (hashing, encryption, HMAC)",
28-
"Example usage:",
28+
"Example usage (modern JavaScript with require()):",
2929
},
3030
},
3131
{
@@ -36,7 +36,7 @@ func TestBuildToolDescription(t *testing.T) {
3636
"Available modules:",
3737
"• http: HTTP server creation and management",
3838
"• fetch: Modern fetch API with Request, Response, Headers, FormData",
39-
"Example usage:",
39+
"Example usage (modern JavaScript with require()):",
4040
},
4141
notExpected: []string{
4242
"• timers:",

jsserver/server.go

Lines changed: 141 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
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

187242
func 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("\nExample usage:\n")
311+
description.WriteString("\nExample 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("\nImportant 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

Comments
 (0)