Skip to main content

JavaScript Code Execution - Troubleshooting

Common issues, error messages, and solutions for the code_execution tool.

Table of Contents​

  1. Configuration Issues
  2. Syntax Errors
  3. Runtime Errors
  4. Timeout Issues
  5. Tool Call Errors
  6. Stored Script Errors
  7. Serialization Errors
  8. Performance Issues
  9. Debugging Tips

Configuration Issues​

Error: "code_execution is disabled in configuration"​

Symptom:

Error: code_execution is disabled in configuration. Set 'enable_code_execution: true' in config file.

Cause: The code_execution feature is switched off in this configuration ("enable_code_execution": false — it ships on by default since v0.66.0, but a config file written by an earlier release carries an explicit false). While it is disabled the tool is not listed in tools/list, so an MCP client will normally not see it at all; this error appears when a call reaches the handler by name anyway (the REST API or the CLI). An MCP client that calls the name from a stale tool list gets an unknown-tool error instead.

Solution:

  1. Edit your configuration file (~/.mcpproxy/mcp_config.json)
  2. Add or update: "enable_code_execution": true
  3. The flag is hot-reloaded: the running proxy picks up the change, advertises the tool, and sends notifications/tools/list_changed to connected sessions. No restart is needed; clients that do not honor list_changed need to reconnect to see the tool.

Example Configuration:

{
"enable_code_execution": true,
"code_execution_timeout_ms": 120000,
"code_execution_max_tool_calls": 0,
"code_execution_pool_size": 10,
"code_execution_max_parallel": 8,
"mcpServers": [...]
}

Error: "code_execution tool not found in tools list"​

Symptom: LLM agent lists available tools but code_execution is missing.

Cause: Feature is not enabled — a disabled code_execution is deliberately absent from tools/list — or the client is holding a tool list from before the flag was enabled.

Solution:

  1. Verify configuration has "enable_code_execution": true
  2. The change is hot-reloaded and announced with notifications/tools/list_changed; if the client does not honor that notification, reconnect it
  3. List tools again

Verification:

# Check if code_execution tool is registered
mcpproxy call tool --tool-name=retrieve_tools --json_args='{"query":"code execution"}'

Error: "timeout must be between 1 and 600000 milliseconds"​

Symptom:

{
"ok": false,
"error": {
"message": "timeout_ms must be between 1 and 600000 milliseconds"
}
}

Cause: Invalid timeout_ms value in request options.

Solution: Use a timeout between 1ms and 600000ms (10 minutes):

{
"code": "...",
"options": {
"timeout_ms": 120000 // 2 minutes (valid)
}
}

Error: "code_execution_max_parallel: must be between 1 and 32"​

Symptom: mcpproxy refuses to load the configuration file:

config validation failed: code_execution_max_parallel: must be between 1 and 32 (or 0 for default)

Cause: code_execution_max_parallel is outside the supported range.

Solution: Use a value between 1 and 32 (or omit the key / set 0 for the default of 8):

{
"code_execution_max_parallel": 8
}

The same range applies to the per-batch override — call_tools(requests, {max_parallel: 40}) returns a single INVALID_ARGS envelope instead of dispatching.


Syntax Errors​

Error: "SyntaxError: Unexpected token"​

Symptom:

{
"ok": false,
"error": {
"code": "SYNTAX_ERROR",
"message": "SyntaxError: Unexpected token '{'",
"stack": ""
}
}

Common Causes:

  1. Missing closing brackets/braces
  2. Invalid JavaScript syntax

Solutions:

Problem: Missing brackets

// Bad
const x = { value: 1, missing: 2

// Good
const x = { value: 1, missing: 2 };

Problem: Incomplete expressions

// Bad
const msg = `Hello, ${

// Good
const msg = `Hello, ${name}!`;

Error: "SyntaxError: Unexpected end of input"​

Symptom: Code execution fails with "end of input" error.

Cause: Incomplete JavaScript expression or statement.

Solution: Ensure all blocks are properly closed:

// Bad
var x = function() {
return 42;
// Missing closing brace

// Good
var x = function() {
return 42;
};

Runtime Errors​

Error: "TypeError: Cannot read property 'X' of undefined/null"​

Symptom:

{
"ok": false,
"error": {
"code": "RUNTIME_ERROR",
"message": "TypeError: Cannot read property 'name' of undefined",
"stack": "..."
}
}

Cause: Accessing properties on undefined or null values.

Solution: Always check values before accessing properties:

// Bad
var name = input.user.name; // Crashes if input.user is undefined

// Good
var name = input.user ? input.user.name : 'Unknown';

// Better
var name = 'Unknown';
if (input && input.user && input.user.name) {
name = input.user.name;
}

For Tool Calls:

// Bad
var user = call_tool('github', 'get_user', {username: input.username});
var name = user.result.name; // Crashes if user.ok is false

// Good
var user = call_tool('github', 'get_user', {username: input.username});
if (!user.ok) {
return {error: user.error.message};
}
var name = user.result.name;

Error: "ReferenceError: X is not defined"​

Symptom:

{
"ok": false,
"error": {
"code": "RUNTIME_ERROR",
"message": "ReferenceError: myVariable is not defined",
"stack": "..."
}
}

Cause: Using variables or functions that don't exist.

Common Mistakes:

Problem: Undefined variables

// Bad
return result; // 'result' was never declared

// Good
var result = 42;
return result;

Problem: Using unavailable functions

// Bad
var data = require('fs').readFileSync('file.txt'); // require() not available

// Bad
setTimeout(function() { doWork(); }, 1000); // setTimeout() not available

// Good
var data = call_tool('filesystem', 'read_file', {path: 'file.txt'});

Error: "RangeError: Maximum call stack size exceeded"​

Symptom: Execution fails with stack overflow.

Cause: Infinite recursion or very deep recursion.

Solution: Check for infinite loops or recursive calls:

// Bad
function factorial(n) {
return n * factorial(n - 1); // No base case!
}

// Good
function factorial(n) {
if (n <= 1) return 1; // Base case
return n * factorial(n - 1);
}

// Better (iterative)
function factorial(n) {
var result = 1;
for (var i = 2; i <= n; i++) {
result *= i;
}
return result;
}

Timeout Issues​

Error: "JavaScript execution timed out"​

Symptom:

{
"ok": false,
"error": {
"code": "TIMEOUT",
"message": "JavaScript execution timed out",
"stack": ""
}
}

Common Causes:

  1. Infinite loops
  2. Too many tool calls
  3. Slow upstream servers
  4. Heavy computation

Solutions:

Problem: Infinite loop

// Bad
while (true) {
// This will timeout
}

// Bad
for (var i = 0; i < 1000000000; i++) {
// This takes too long
}

// Good
for (var i = 0; i < input.items.length; i++) {
// Bounded loop
}

Problem: Insufficient timeout for workload

// Bad: Default 2-minute timeout for 100 tool calls
{
"code": "for(var i=0;i<100;i++){call_tool(...)}",
"options": {"timeout_ms": 120000} // Might not be enough
}

// Good: Increase timeout for heavy workloads
{
"code": "for(var i=0;i<100;i++){call_tool(...)}",
"options": {"timeout_ms": 300000} // 5 minutes
}

Problem: Slow upstream tools

// Solution: Reduce number of calls or increase timeout
{
"code": "...",
"options": {
"timeout_ms": 300000, // Increase timeout
"max_tool_calls": 50 // Limit calls to prevent runaway
}
}

Tool Call Errors​

Error: "Exceeded maximum tool calls limit"​

Symptom:

{
"ok": false,
"error": {
"code": "MAX_TOOL_CALLS_EXCEEDED",
"message": "Exceeded maximum tool calls limit (10)",
"stack": ""
}
}

Cause: Code called call_tool() more times than max_tool_calls allows. Every element of a call_tools() batch counts as one call too, checked in input order — tail elements over the limit come back as per-slot MAX_TOOL_CALLS_EXCEEDED errors and are never dispatched.

Solution:

Option 1: Increase limit

{
"code": "for(var i=0;i<20;i++){call_tool(...)}",
"options": {"max_tool_calls": 25} // Set higher than needed
}

Option 2: Reduce tool calls

// Bad: Call tool for every item
for (var i = 0; i < 1000; i++) {
call_tool('api', 'process', {id: items[i]});
}

// Good: Batch items if tool supports it
var batchSize = 100;
for (var i = 0; i < items.length; i += batchSize) {
var batch = items.slice(i, i + batchSize);
call_tool('api', 'process_batch', {items: batch});
}

Error: "Server 'X' is not in the allowed servers list"​

Symptom:

{
"ok": false,
"error": {
"code": "SERVER_NOT_ALLOWED",
"message": "Server 'gitlab' is not in the allowed servers list",
"stack": ""
}
}

Cause: Attempted to call a server not in allowed_servers option.

Solution:

Option 1: Add server to allowed list

{
"code": "call_tool('gitlab', 'get_user', {username: 'test'})",
"options": {
"allowed_servers": ["github", "gitlab"] // Add gitlab
}
}

Option 2: Remove restriction (allow all servers)

{
"code": "call_tool('gitlab', 'get_user', {username: 'test'})",
"options": {
"allowed_servers": [] // Empty array = all servers allowed
}
}

Error: "server not found: X"​

Symptom: call_tool() returns error saying server doesn't exist.

Cause: Server name is incorrect or server is not configured.

Solution:

  1. Check server name: Verify spelling and case
// Bad
call_tool('GitHub', 'get_user', {}); // Wrong case

// Good
call_tool('github', 'get_user', {}); // Correct name
  1. List available servers:
mcpproxy call tool --tool-name=upstream_servers --json_args='{"operation":"list"}'
  1. Add server if missing:
mcpproxy call tool --tool-name=upstream_servers \
--json_args='{"operation":"add","name":"github","url":"https://api.github.com/mcp","protocol":"http","enabled":true}'

Error: "call_tools: element N: ..." (INVALID_ARGS)​

Symptom: call_tools() returns a single envelope instead of an array of slots:

{
"ok": false,
"error": {
"code": "INVALID_ARGS",
"message": "call_tools: element 3: must be an object with server and tool"
}
}

Cause: The batch itself is malformed, so nothing was dispatched and no budget was consumed. Triggers: requests is not an array, an element is not an object with non-empty server/tool strings, a supplied args is not an object, the array has a sparse hole, options is not an object, max_parallel is not an integer in 1-32, or the batch exceeds 100 elements.

Solution: Fix the element the message names, then check the result shape before mapping over it:

var slots = call_tools(requests, {max_parallel: 8});
if (!Array.isArray(slots)) {
// whole batch rejected — slots is {ok:false, error:{...}}
({error: slots.error.message});
} else {
({results: slots});
}

For more than 100 items, chunk the array and issue one call_tools() per chunk.


Error: "upstream server X is busy: its concurrency limit (N) is saturated (queue_full)"​

Symptom: A call_tools() batch returns one success and many failed slots mentioning a concurrency limit:

[
{"ok": true, "result": {...}},
{"ok": false, "error": {
"code": "UPSTREAM_ERROR",
"message": "upstream server \"fragile-db\" is busy: its concurrency limit (1) is saturated (queue_full) — please retry shortly"
}}
]

Cause: The target server has a per-server concurrency limit (max_concurrent_requests) with no queue_size. Batching never bypasses those limits: calls over the cap are shed immediately, which is the server's configured policy — one shed error per overflow element.

Solution: Give the server queue headroom, or lower the batch concurrency to match its cap:

{
"mcpServers": [
{ "name": "fragile-db", "command": "db-mcp", "max_concurrent_requests": 1, "queue_size": 20 }
]
}
// Or match the cap from the script
call_tools(requests, {max_parallel: 1});

Queued calls wait up to queue_timeout, and that wall-clock wait happens inside the script's timeout_ms budget — size queue_size and timeout_ms together.


Stored Script Errors​

These apply to invocations that name a stored script (script: "<name>", --script <name>) instead of sending code inline. One command answers most of them:

mcpproxy code scripts list # names, paths, statuses, and the directory that was read

Error: "Provide exactly one of 'code' or 'script'"​

Symptom:

Provide exactly one of 'code' (inline source) or 'script' (the name of a script stored in the 'scripts' directory next to mcpproxy's config file) — not both, not neither.

Over REST the same rule is an HTTP 400 before dispatch:

{"ok": false, "error": {"code": "INVALID_REQUEST", "message": "Provide exactly one of 'code' (inline source) or 'script' (the name of a stored script)"}}

Cause: The request carried both code and script, or neither. JSON Schema cannot express "exactly one of", so neither field is schema-required and the rule is enforced by the tool.

Solution: Send one source. On the CLI, --code, --file and --script are mutually exclusive (--code, --file and --script are mutually exclusive, exit code 2).


Error: "stored script X not found"​

Symptom:

Cannot execute stored script: stored script "fetch-pr" not found in /Users/me/.mcpproxy/scripts. Available scripts (3): daily-report, fetch-prs, triage

Or, with an empty/absent directory:

Cannot execute stored script: stored script "fetch-pr" not found: no stored scripts in /Users/me/.mcpproxy/scripts (create fetch-pr.js or fetch-pr.ts there)

Or, when the caller is an agent token rather than an administrator — the listing, the count and the directory are withheld, and the message is the same whether the directory is empty or full:

Cannot execute stored script: stored script "fetch-pr" not found (the stored-script listing is available to administrators only; an agent-token caller must already know the script name)

Cause: No <name>.js / <name>.ts in the scripts directory. Usually a typo (names are case-sensitive), a file that is not a script (uppercase or other extension: .JS, .mjs, .jsx are ignored), or the wrong directory — the scripts directory follows the active config file, not --data-dir.

Solution: For an administrator this error is the discovery mechanism — it lists the first 20 available names alphabetically plus the total, so the name set is recovered from the failed call. An agent token gets no listing: give the agent the script names out of band (or in its custom instructions) and check them against the administrator's view. For the full picture, including where the daemon looked:

mcpproxy code scripts list
mcpproxy code scripts list --config /etc/mcpproxy/mcp_config.json # a non-default config

If the directory in the message is not the one you authored in, start the daemon with the config file you meant (mcpproxy serve --config …) — with ~/.mcpproxy/mcp_config.json the scripts live in ~/.mcpproxy/scripts/.

Case-insensitive filesystems (the default macOS and Windows volumes; on Linux a Docker Desktop bind mount from a macOS or Windows host, vfat, an ext4 casefold directory): the on-disk spelling still decides, for every caller. FETCH-PR.JS or Fetch-pr.js is not the script fetch-pr even where the filesystem would open it under that name — the daemon verifies the stored spelling before running anything, so the administrator's listing, the administrator's call and an agent-token call all agree. Every platform — Linux, the BSDs, macOS/darwin and Windows — answers an agent-token call ONLY from an exact-name index of the directory that matches its CURRENT state — built at daemon start, validated once per call, refreshed in the background when the directory changes — so no call lists the directory, whatever name is asked for; a differently-cased name and one that is not stored at all cost exactly the same, and the refusal body is unchanged. Every step of one call's own check — the stat, the candidate probe, the open and the re-check after it — is bound to a single directory descriptor (or, on Windows, handle) retained for that call, never a fresh resolution of the path per step, so a symlink, bind mount or reparse point retargeted mid-call cannot make two of those steps disagree about which directory they are looking at. A call landing while that refresh is scheduled or in flight is refused exactly as one against a directory never seen before, never served from what the index held a moment ago — a rename cannot have a scoped caller's own probe fold onto whatever now occupies the old name. Even once refreshed, the index only authorizes a hit once its directory timestamp is provably SETTLED (old enough — about two seconds — that a write could not still be landing on the same coarse tick): a script you have just added or renamed is callable by agent tokens only after the index has both refreshed AND settled — retry a call refused in that window, up to about two seconds — while administrators see the change at once; mcpproxy never creates the directory itself, mkdir -p it. macOS adds one extra, belt-and-suspenders check on top: after the open, it re-reads the opened descriptor's own stored spelling (F_GETPATH) and refuses on any mismatch. Windows performs the probe, the open and the background listing all relative to the SAME retained directory handle (NtCreateFile), so the post-open check only needs to confirm the opened handle's own name (GetFinalPathNameByHandle) rather than re-walking a path that a retargeted reparse point could have redirected.


Error: "invalid script name"​

Symptom:

Cannot execute stored script: invalid script name "../../etc/passwd": character "." is not allowed (names are 1-64 characters of A-Z, a-z, 0-9, '-' or '_' — a name, never a path)

Cause: script is a name, never a path. Separators, .., dots, extensions, non-ASCII characters and names longer than 64 characters are rejected before the filesystem is touched at all.

Solution: Pass the base name only — fetch-prs, not fetch-prs.js, ./fetch-prs.js, or /abs/path/fetch-prs.js.


Error: "stored script X is ambiguous"​

Symptom:

Cannot execute stored script: stored script "triage" is ambiguous: /Users/me/.mcpproxy/scripts/triage.js and /Users/me/.mcpproxy/scripts/triage.ts both exist — remove one

Cause: Both extensions exist for one name — often a leftover after converting a script from JavaScript to TypeScript. Ambiguity is never resolved silently.

Solution: Delete (or rename) one of the two files. mcpproxy code scripts list flags such names with status ambiguous before you hit them at runtime.


Error: "stored script X is oversized / empty / unreadable / non-regular"​

Symptom:

Cannot execute stored script: stored script "big-report" (/Users/me/.mcpproxy/scripts/big-report.js) is oversized: scripts are limited to 262144 bytes

Cause:

ReasonMeaning
oversizedThe file exceeds the 256 KB stored-script bound (inline code has no such bound; this one exists purely to bound the daemon-side read)
emptyZero bytes — commonly a half-finished redirect (> script.js)
unreadablePermissions or an I/O error; the detail carries the OS error
non-regularThe path is a symlink, directory, or device — scripts must be regular files

Solution: Split an oversized workflow into several scripts (or move bulk data into input), finish the write, fix permissions, or replace the symlink with the real file. Copy, do not link:

cp /shared/workflows/report.js ~/.mcpproxy/scripts/report.js

The scripts directory itself may be a symlink — it is operator-controlled; only the script file may not be.


Error: "stored script X is a .ts file but language Y was requested"​

Symptom:

Cannot execute stored script: stored script "daily-report" is a .ts file (typescript) but language "javascript" was requested — omit 'language' or set it to "typescript"

Cause: The extension is authoritative for a stored script, and the explicit language contradicted it.

Solution: Omit language entirely — the extension decides. (The CLI already forwards --language only when you set it explicitly, so its javascript default cannot trigger this.)


Issue: An edited script still runs the old content​

Cause: Almost always the file was not replaced where the daemon looks, or the edit went to a different scripts directory. There is no cache and no watcher: every invocation opens and reads the file once, so a completed replacement is visible to the very next call — no restart, nothing to flush.

Solution: Confirm the path with mcpproxy code scripts list (it always prints the directory it read), then edit by atomic replace so no invocation can observe a half-written file:

tmp=$(mktemp ~/.mcpproxy/scripts/.report.XXXXXX)
cp new-report.js "$tmp" && mv "$tmp" ~/.mcpproxy/scripts/report.js

Editing in place while an invocation is reading is the one unsupported case: that run gets whatever the read returned (validated, but unspecified).


Issue: No way to upload or edit a script through the API​

Cause: Working as designed. v1 has no write path for stored scripts — no MCP tool, no REST endpoint, no CLI verb creates, updates, or deletes them. Code running in the sandbox has no filesystem access either.

Solution: Author scripts with your normal filesystem tooling (editor, scp, configuration management). GET /api/v1/code/scripts and mcpproxy code scripts list are read-only, administrator-only views of the result (an agent token is refused with 403).


Serialization Errors​

Error: "Result contains non-JSON-serializable values"​

Symptom:

{
"ok": false,
"error": {
"code": "SERIALIZATION_ERROR",
"message": "Result contains non-JSON-serializable values (functions, circular references, etc.)",
"stack": ""
}
}

Cause: JavaScript return value contains functions, circular references, or other non-JSON types.

Solutions:

Problem: Returning functions

// Bad
return {
calculate: function() { return 42; }
};

// Good
return {
result: 42
};

Problem: Returning undefined

// Bad
return undefined; // Not JSON-serializable

// Good
return null; // JSON-serializable

Problem: Circular references

// Bad
var obj = {value: 42};
obj.self = obj; // Circular reference
return obj;

// Good
return {value: 42};

Problem: Special objects (Date, RegExp)

// Bad
return {
created: new Date() // Date object not JSON-serializable
};

// Good
return {
created: new Date().toISOString() // String is JSON-serializable
};

Performance Issues​

Issue: Slow execution times​

Symptom: Code takes longer than expected to execute.

Diagnosis:

  1. Check number of tool calls
  2. Measure upstream tool latency
  3. Look for inefficient loops
  4. Check for excessive data processing

Solutions:

Reduce tool calls:

// Bad: N tool calls
for (var i = 0; i < items.length; i++) {
call_tool('api', 'get', {id: items[i]});
}

// Good: 1 batch tool call
call_tool('api', 'get_batch', {ids: items});

Run independent calls in parallel (when the tool has no bulk variant):

// Bad: N sequential calls — latency is the sum
for (var i = 0; i < items.length; i++) {
call_tool('api', 'get', {id: items[i]});
}

// Good: one batch — latency is about the slowest call
var slots = call_tools(items.map(function (id) {
return {server: 'api', tool: 'get', args: {id: id}};
}), {max_parallel: 8});

Only batch calls that do not depend on each other. Raise code_execution_max_parallel (or options.max_parallel, max 32) if upstreams can take the pressure — and check the target server's max_concurrent_requests / queue_size first (see the queue_full entry above).

Cache repeated calls:

// Bad: Call same tool multiple times
for (var i = 0; i < items.length; i++) {
var config = call_tool('api', 'get_config', {}); // Repeated call
process(items[i], config);
}

// Good: Call once, reuse result
var config = call_tool('api', 'get_config', {});
if (!config.ok) return config;

for (var i = 0; i < items.length; i++) {
process(items[i], config.result);
}

Optimize loops:

// Bad: Inefficient nested loops
for (var i = 0; i < arr1.length; i++) {
for (var j = 0; j < arr2.length; j++) {
if (arr1[i] === arr2[j]) {
results.push(arr1[i]);
}
}
}

// Good: Use object for O(1) lookup
var set = {};
for (var i = 0; i < arr2.length; i++) {
set[arr2[i]] = true;
}
for (var i = 0; i < arr1.length; i++) {
if (set[arr1[i]]) {
results.push(arr1[i]);
}
}

Issue: Pool exhaustion (all VMs busy)​

Symptom: Requests take longer to start, or you see "waiting for VM" logs.

Cause: More concurrent executions than pool size allows.

Solution: Increase pool size in configuration:

{
"code_execution_pool_size": 20 // Increase from default 10
}

Trade-offs:

  • Larger pool = more concurrency, more memory usage
  • Smaller pool = less concurrency, less memory usage

Recommendation: Monitor pool usage and adjust based on load.


Debugging Tips​

Enable Debug Logging​

CLI:

mcpproxy code exec --code="..." --log-level=debug

Server: Edit config to set log level:

{
"log_level": "debug"
}

What you'll see:

  • Tool call details (server, tool name, arguments)
  • Execution timing
  • Pool acquisition/release
  • Detailed error messages

Use console.log() for Debugging​

var user = call_tool('github', 'get_user', {username: input.username});
console.log('User response:', JSON.stringify(user)); // Logs to server logs

if (!user.ok) {
console.log('Error occurred:', user.error);
return {error: user.error.message};
}

console.log('User name:', user.result.name);
return {name: user.result.name};

Where to find logs: ~/.mcpproxy/logs/main.log (or platform-specific log directory)


Test Code Incrementally​

Start simple and build up:

// Step 1: Verify input access
return input;

// Step 2: Test tool call
var res = call_tool('github', 'get_user', {username: 'octocat'});
return res;

// Step 3: Add error handling
var res = call_tool('github', 'get_user', {username: 'octocat'});
if (!res.ok) return {error: res.error.message};
return res.result;

// Step 4: Add data transformation
var res = call_tool('github', 'get_user', {username: 'octocat'});
if (!res.ok) return {error: res.error.message};
return {
name: res.result.name,
repos: res.result.public_repos
};

Validate JSON Serialization​

Test that your return value is JSON-serializable:

var result = {/* your data */};

// This will throw if result is not serializable
var json = JSON.stringify(result);

// If it succeeds, return it
return result;

Use Error Boundaries​

Wrap risky operations in try-catch or use checks:

// Check before accessing
if (res && res.ok && res.result && res.result.name) {
return {name: res.result.name};
} else {
return {error: 'Invalid response structure'};
}

// For loops, check each iteration
for (var i = 0; i < items.length; i++) {
if (!items[i]) {
console.log('Skipping null item at index', i);
continue;
}
// Process items[i]
}

Reproduce Issues Locally​

Use the CLI to reproduce issues quickly:

# Save problematic code to file
cat > /tmp/debug.js << 'EOF'
var user = call_tool('github', 'get_user', {username: input.username});
console.log('Response:', JSON.stringify(user));
return user;
EOF

# Run with debug logging
mcpproxy code exec \
--file=/tmp/debug.js \
--input='{"username":"octocat"}' \
--log-level=debug

Getting Help​

If you're still stuck after trying these solutions:

  1. Check the examples: examples.md has 10+ working patterns
  2. Review API reference: api-reference.md has complete schema
  3. Read the overview: overview.md explains architecture
  4. Check server logs: ~/.mcpproxy/logs/main.log for detailed error messages
  5. File an issue: GitHub Issues

What to Include in Bug Reports​

**Environment**:
- MCPProxy version: (run `mcpproxy --version`)
- OS: (macOS/Linux/Windows)
- Configuration: (relevant config fields)

**Code**:
```javascript
// Your JavaScript code here

Input:

{
"input": {...}
}

Expected: What you expected to happen

Actual: What actually happened (include full error message)

Logs: Relevant lines from ~/.mcpproxy/logs/main.log


---

## Next Steps

- **Examples**: See [examples.md](examples.md) for working code samples
- **API Reference**: See [api-reference.md](api-reference.md) for complete schema
- **Overview**: See [overview.md](overview.md) for architecture and best practices