JavaScript Scripts
Write custom MCP tools in JavaScript. Scripts run in a sandboxed Sobek runtime — no Node.js, no npm, no filesystem access by default.
Configuration
upstreams:
- name: custom
type: script
tool_prefix: ops
tools:
- name: health_check
description: Check service health across regions
source: scripts/health_check.js
args:
service:
type: string
description: Service name
required: true
region:
type: string
description: AWS region
default: us-east-1
timeout: 10s Script context
Scripts receive a ctx object and an args object containing the typed arguments declared in the config.
// scripts/health_check.js
const url = `https://${args.service}.internal/${args.region}/health`;
const resp = await ctx.fetch(url, {
method: 'GET',
headers: { 'X-Service': args.service },
});
if (!resp.ok) {
return { status: 'unhealthy', code: resp.status };
}
const body = await resp.json();
return { status: 'healthy', ...body }; Available APIs
| API | Description |
|---|---|
ctx.fetch(url, options) | HTTP client — same signature as the Web Fetch API. Returns a Response. |
ctx.env(name) | Read an environment variable by name. Returns string or null. |
ctx.log(message, ...args) | Write to the proxy's structured logger (slog). Appears in proxy logs. |
Return value
Return any JSON-serializable value. Objects, arrays, strings, and numbers are all valid. The return value becomes the tool result sent to the MCP client.
Throw an Error to signal a tool failure. The error message is forwarded to the client.
// Return an object
return { price: 42.0, currency: 'USD' };
// Return an array
return items.map(i => ({ id: i.id, name: i.name }));
// Signal an error
throw new Error('Service unavailable: ' + resp.status); Async/await
Scripts are executed as async functions. await is supported for ctx.fetch() and any Promise-returning expression.
Timeout
The timeout field limits total script execution time including any ctx.fetch() calls. Scripts that exceed the timeout are interrupted and an error is returned.
Security sandbox
The Sobek runtime does not expose Node.js globals, filesystem access, or process manipulation. Scripts can only:
- Read arguments passed in via
args - Make HTTP calls via
ctx.fetch() - Read env vars explicitly via
ctx.env() - Log via
ctx.log() - Use standard ECMAScript built-ins (JSON, Date, Math, etc.)
Example: multi-service aggregation
// Fetch from multiple services in parallel and aggregate
const [prices, inventory] = await Promise.all([
ctx.fetch('https://prices.internal/api/current').then(r => r.json()),
ctx.fetch('https://inventory.internal/api/stock').then(r => r.json()),
]);
return prices.items.map(item => ({
sku: item.sku,
price: item.price,
inStock: inventory[item.sku] > 0,
})); See also
- Shell Commands — simpler option for wrapping CLI tools
- Authentication — pass auth tokens to
ctx.fetch()viactx.env()