Python Execution (exec_code)
exec_code is an MCP tool that runs arbitrary Python code with access to Dinobase internals. Agents use it when SQL isn’t enough — calling external MCP tools, chaining multiple operations, or reshaping results in ways that are awkward to express in SQL.
It complements the SQL query tool rather than replacing it: SQL stays the primary read interface, and exec_code fills the gaps.
When to reach for it
Section titled “When to reach for it”| Situation | Use this |
|---|---|
| Filter/join/aggregate across synced tables | query (SQL) |
| Browse tools on a connected MCP server | dinobase mcp CLI or mcp call |
| Call one MCP tool with simple arguments | mcp call |
| Call multiple MCP tools and combine their output | exec_code |
| Reshape query results with Python logic (grouping, string parsing) | exec_code |
| Call an MCP tool that needs dynamic arguments built from a query | exec_code |
| Write-back mutations to upstream systems | query with UPDATE/INSERT (plus confirm) |
If the task is a read you could write as SQL, prefer SQL — it’s cheaper in tokens and faster. Use exec_code when the logic genuinely needs Python.
How the tool works
Section titled “How the tool works”The MCP server exposes a single tool:
exec_code(code: str) -> strThe Python code runs in an isolated namespace with full access to the Python standard library and every installed package. To return a value, assign it to result:
result = "hello"If result is set, Dinobase serializes it to JSON (falling back to str() for non-JSON values) and returns it. If result is unset, the tool returns {"status": "ok"}. If the code raises, you get {"error": "ExceptionType: message"}.
State does not persist between calls — each invocation starts with a fresh namespace.
Available APIs
Section titled “Available APIs”Import these inside your code string — nothing is pre-imported:
from dinobase.mcp import call, tools, servers, search, instructionsfrom dinobase.db import DinobaseDBfrom dinobase.query.engine import QueryEnginefrom dinobase.query.mutations import MutationEnginedinobase.mcp— sync wrappers around connected MCP servers. See the Python API reference.DinobaseDB/QueryEngine— same interfaces the CLI and MCP server use internally. See the Python API reference.MutationEngine— programmatic write-back with the same preview/confirm flow.
Examples
Section titled “Examples”Call an MCP tool with arguments
Section titled “Call an MCP tool with arguments”from dinobase.mcp import call
result = call("posthog_mcp.dashboard-get", id=1118504)Chain two MCP calls
Section titled “Chain two MCP calls”Fetch a list, then look up details for each item:
from dinobase.mcp import call
dashboards = call("posthog_mcp.dashboards-get-all")ids = [d["id"] for d in dashboards.get("structuredContent", {}).get("results", [])]
details = []for dashboard_id in ids[:5]: detail = call("posthog_mcp.dashboard-get", id=dashboard_id) details.append(detail.get("structuredContent"))
result = detailsCombine SQL and an MCP call
Section titled “Combine SQL and an MCP call”Query the synced database, then call an upstream tool for each row:
from dinobase.db import DinobaseDBfrom dinobase.query.engine import QueryEnginefrom dinobase.mcp import call
with DinobaseDB() as db: rows = QueryEngine(db).execute( "SELECT id FROM hubspot.companies WHERE industry = 'SaaS' LIMIT 10" )["rows"]
enriched = []for row in rows: detail = call("clearbit_mcp.company-lookup", domain=row["id"]) enriched.append(detail.get("structuredContent"))
result = enrichedReshape results with Python
Section titled “Reshape results with Python”Group query results by a computed key that SQL can’t easily express:
from collections import defaultdictfrom dinobase.db import DinobaseDBfrom dinobase.query.engine import QueryEngine
with DinobaseDB() as db: rows = QueryEngine(db).execute( "SELECT email, created FROM stripe.customers" )["rows"]
by_domain = defaultdict(list)for row in rows: domain = row["email"].split("@")[-1] if row["email"] else "(none)" by_domain[domain].append(row["created"])
result = {d: len(v) for d, v in sorted(by_domain.items(), key=lambda kv: -len(kv[1]))}Discover what’s available
Section titled “Discover what’s available”from dinobase.mcp import servers, search
result = { "servers": servers(), "dashboard_tools": search("dashboard"),}Returning values
Section titled “Returning values”The returned value becomes the tool’s JSON response. For agent-friendly output:
- Prefer plain dicts, lists, and primitives — they serialize cleanly.
- Strings become JSON strings. Integers, floats, and booleans pass through.
- Objects that aren’t JSON-serializable fall back to
str()viajson.dumps(..., default=str), then finally to a rawstr(output)if that fails too. - Setting
result = None(or not settingresult) returns{"status": "ok"}— use this for scripts whose side effects are the point (writes, refreshes).
Errors
Section titled “Errors”Exceptions come back as a single-field dict:
{"error": "ValueError: id is required"}The traceback isn’t included — if you’re debugging, print intermediate values and wrap risky sections in try/except yourself:
try: data = call("my_server.fetch", id=123) result = {"ok": True, "rows": len(data.get("structuredContent", {}).get("items", []))}except Exception as e: result = {"ok": False, "error": str(e)}Security considerations
Section titled “Security considerations”exec_code runs with the same privileges as the MCP server process. There’s no sandbox — the code can read any file, hit any network endpoint, or call any MCP server the Dinobase process has access to.
Only enable this tool on MCP servers you trust with the agents you trust. If you’re running Dinobase locally for yourself, this is the same trust model as letting the agent run shell commands. If you’re exposing Dinobase to a broader set of agents, consider disabling the MCP server’s exec_code tool in your deployment.
See also
Section titled “See also”- MCP Tools Reference → exec_code — the raw tool schema and response format
- Python API → MCP Client —
call,tools,servers,search,instructions - MCP Server Connectors — connecting MCP servers so their tools are callable from
exec_code - Querying Data — when SQL is the better choice