| execute | Run async Python in a sandboxed interpreter. The whole script runs as one
turn; use return to produce the tool result. Available in scope: await call_tool(name: str, params: dict) -> Any — calls any backend tool.
Failures raise RuntimeError(<envelope_json>) where the message body
is the full v1 cross-server error envelope JSON. Inside the sandbox:
try:
await call_tool(name, params)
except RuntimeError as e:
env = json.loads(str(e))
# env['error_kind'], env['hints'], env['error_code'],
# env['retryable'], env.get('_meta', {}) all available.
Non-envelope failures fall back to RuntimeError("<OriginalType>: <message>").
Both forms are catchable with try/except RuntimeError:. The format matches
Amazon SP MCP for cross-server symmetry.
Sandbox guardrails (Monty interpreter): No network: urllib, requests, httpx, socket not importable. Use call_tool. No filesystem writes: open() is missing from builtins; os and pathlib import successfully but most side-effecting methods are gated. Tool results larger than ~1 MB may be auto-stashed by the host client. Stdlib (verified against pydantic_monty==0.0.11; allowlist is hardcoded in Monty's compiled extension and not configurable from Python — may differ on other CodeMode hosts running a different pydantic_monty): Available: json, re, math, datetime, sys, typing, asyncio, os, pathlib. Blocked: collections, itertools, functools, statistics, decimal, dataclasses, random, string, time, base64, hashlib, urllib.parse. Use built-ins and comprehensions for aggregation; for hashing/encoding/URL work, request a server-side tool via await call_tool(...).
Builtins (verified missing / behavior differences): hasattr(o, k) is unavailable. Use a unique sentinel with getattr:
_MISSING = [] (or {}), then getattr(o, k, _MISSING) is not _MISSING.
object() is unavailable in this sandbox.
callable(x) is unavailable and getattr(x, '__call__', None) is not None is not reliable for built-in instances or functions in this sandbox.
Prefer known-callable inputs, or guarded invocation with try: x(...); except TypeError: when safe.
No reliable capability-probing on objects. The _MISSING sentinel pattern above works for module attributes (e.g. getattr(math, 'pi', _MISSING)) but not for methods on built-in instances or attributes on user-defined functions. In this sandbox, built-in methods are invokable via d.keys() syntax but are not reachable as attributes via d.keys (which itself raises AttributeError). Same for function attributes — fn.__name__ raises AttributeError. getattr(o, k, default) returns default for these cases not because getattr is misbehaving but because the attribute isn't there. Write code that knows the shape of its inputs rather than probing for capabilities. setattr, dir, vars, globals, locals, open are all absent.
Blocked-import error semantics: inside the sandbox, from collections import Counter raises a stock Python ImportError (NOT a RuntimeError-wrapped envelope). Catch with except ImportError: (or except Exception:). The v1 sandbox_runtime / SANDBOX_MODULE_BLOCKED envelope only fires when the import error is uncaught and propagates out of execute — at which point the calling tool layer sees the typed envelope. Unsupported-syntax error semantics: class definitions, match statements, and other parser-not-yet-supported constructs raise a NotImplementedError that DOES surface as a v1 envelope with error_kind=sandbox_runtime / error_code=SANDBOX_RUNTIME_ERROR. Asymmetric with imports — known limitation. print() output may be discarded depending on the client path; return data via the script's final expression instead.
asyncio.sleep is unavailable by design in this sandbox path. Don't sleep — chain await call_tool calls (e.g. poll a report-status tool) instead. For long-running reports (typically 1-20 minutes), do NOT rapid-poll inside a single execute block; return after one status check and let the user decide when to re-check.
try/except/finally work normally. To probe many candidates in one
block, wrap each await call_tool(...) in its own try/except RuntimeError.
with works for pure-Python context managers (e.g. decimal.localcontext()).
It does NOT work for open(...) because file I/O is blocked.
json.dumps(default=...) may trip on Pydantic models; call model_dump() first.
Auth, region, and active profile are managed by the server. Do not pass
Amazon-Ads-AccountId, Amazon-Advertising-API-Scope, or bearer tokens in
params — set them once via set_active_identity / set_region /
set_active_profile and they ride every subsequent call_tool. Session-scope contract: Call get_session_state first; if state_scope == 'request', re-run set_active_identity / set_region / set_active_profile in each execute block. If state_scope == 'session', set them once via the corresponding tools and they ride subsequent call_tool calls in that session. To detect the transport's scope, call get_session_state at the start
of a block. It is a read-only probe with no side effects. Rule: Re-establish context before the next tool call iff state_scope == 'request' or state_reason is not null. Within a block the scope cannot change; one probe per block is sufficient. state_reason enumerates: "no_mcp_session" (request-scoped transport),
"token_swapped" (a different bearer/refresh token arrived mid-session
and the previous tenant's state was cleared — state_scope stays
'session' but you must re-establish context for the new tenant), and
"bridge_unavailable" (reserved; treat as 'request').
|