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

APIDescription
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