bifrost-code-mode · diff
git:20261007.de193df to git:20261019.5f24f36
28 added, 3 removed. Audit A to A.
---
name: bifrost-code-mode
description: "Call Bifrost gateway MCP servers that are NOT exposed as flat tools, through the executeToolCode meta-tool. Triggers on 'executeToolCode', 'code mode', 'code-mode', 'listToolFiles', 'readToolFile', 'getToolDocs', 'starlark', 'tool not found on bifrost', 'no such tool mcp__bifrost__', 'no such tool mcp__plugin_bifrost-plugin_bifrost__', 'no such tool mcp__claude_ai_luca_Bifrost__', 'how do I call gitlab/jira/grafana/sentry through the gateway', 'gateway tool missing', 'server keys'."
---
# Bifrost code mode
Most servers on a Bifrost gateway are **not** flat tools. They do not appear in your
tool list and `<prefix><server>-<tool>` does not exist for them (`mcp__plugin_bifrost-plugin_bifrost__` from the plugin, `mcp__claude_ai_luca_Bifrost__` from the claude.ai connector, `mcp__bifrost__` from a hand-added server). They are reached
by writing a short Starlark snippet and running it with `executeToolCode`.
If a capability seems missing, it is almost certainly here. Do not conclude the gateway
lacks it, and do not fall back to guessing from training data, until you have checked.
## The four meta-tools
Exact schemas, as the gateway advertises them:
| Tool | Parameters | Required |
|---|---|---|
| `listToolFiles` | *(none)* | — |
| `readToolFile` | `fileName`, `startLine`, `endLine` | `fileName` |
| `getToolDocs` | `server`, `tool` | both |
| `executeToolCode` | `code` | `code` |
The parameter is `fileName`, **not** `path` or `file`. Passing `path` fails with
`fileName parameter is required and must be a string`.
`fileName` is the full catalog path: `servers/<server>/<tool>.pyi`.
## Workflow
**1. Discover.** `listToolFiles()` returns the catalog, grouped by server:
```
servers/
jira/
search_jira_tickets.pyi
create_jira_ticket.pyi
...
```
**2. Confirm the signature.** Never call a tool whose parameters you have not read.
```
readToolFile(fileName="servers/jira/search_jira_tickets.pyi")
```
returns a typed stub with the exact callable name and an inline description:
```python
# Total lines: 10 (this is the complete file, no need to paginate)
# jira.search_jira_tickets tool
# Usage: jira.tool_name(param=value)
def search_jira_tickets(query: str, max_results: float = None, service: str = None, status: str = None) -> dict: # Search JIRA before filing a new ticket to avoid duplicates.
```
The `def` name is the exact callable. Descriptions in `.pyi` files may be truncated —
use `getToolDocs(server="jira", tool="search_jira_tickets")` when a parameter's meaning
or return shape is unclear.
A wrong tool name is cheap to recover from: the error lists every valid tool on that
server.
**3. Execute.**
```
executeToolCode(code='result = jira.search_jira_tickets(query="payment webhook", max_results=5)')
```
## Starlark rules
The runtime is Starlark, a Python subset.
**Assign to `result`.** Nothing comes back unless you do. This is the single most
common mistake, and it fails quietly — the call reports
- `Execution completed but produced no data`.
+ `Execution completed successfully.` with no return value.
```python
result = gitlab.create_issue(project_id="123", title="Fix webhook") # correct
gitlab.create_issue(project_id="123", title="Fix webhook") # returns nothing
```
**Call tools with keyword arguments**: `server.tool(param=value)`, as the `.pyi` stub
shows.
**Top-level `for` and `if` are fine** — verified on a live gateway. Older Bifrost
documentation says they must be wrapped in a `def`; that is not true of this runtime,
and neither form is wrong:
```python
keys = []
for t in jira.search_jira_tickets(query="webhook")["issues"]:
if t["status"] == "Open":
keys.append(t["key"])
result = keys
```
**`print()` works** and its output is returned alongside the result, which makes it
the practical way to debug a snippet.
Supported: `def`, `for`, `if`, list comprehensions, dicts, lists, string ops,
- arithmetic. Not available: imports, file I/O, direct network calls. Reach the outside
- world only through the server functions.
+ arithmetic. Not available: imports, file I/O, direct network calls, `try`/`except`,
+ f-strings (use `"%s" % x`). Reach the outside world only through the server functions.
+ **Built-in modules** (no import needed): `json.encode(obj)`, `json.decode(text)` and
+ `math` (`math.sqrt`, `math.floor`, ...).
+
+ **Failed calls do not abort the script.** A tool error comes back as a value with an
+ `error` field, so check it before indexing:
+
+ ```python
+ p = gitlab.get_project(project_id="123")
+ if hasattr(p, "error"):
+ result = "lookup failed: %s" % p.error
+ else:
+ result = p["web_url"]
+ ```
+
+ The reply then ends with `Execution completed, but 1 nested tool call failed.`
+
+ **Numbers keep their type.** Integer ids from tool results stay `int`, so `str(p["id"])`
+ gives `"87265108"`, not a float.
+
+ **Limits.** One script may make at most 64 tool calls. Batch or narrow the query
+ instead of looping over hundreds of items. Only `print()` output and `result` come
+ back; raw tool responses are not echoed, so print what you need while debugging.
+
## Why this beats one flat tool per call
The snippet runs server-side, so a multi-step task costs **one** round trip and only
the final `result` enters your context. Filter and aggregate inside the snippet rather
than pulling everything back:
```python
def open_bugs():
tickets = jira.search_jira_tickets(query="checkout", max_results=50)
return [t["key"] for t in tickets["issues"] if t["status"] == "Open"]
result = open_bugs()
```
That returns a handful of keys instead of 50 full ticket bodies.
## Checking what is available
`executeToolCode` reports the live server keys in its `Environment` block on every
run, so a trivial snippet tells you what this key can reach:
```
executeToolCode(code="result = 1")
→ Available server keys: gitlab, grafana, jira, sentry, hubspot, metabase,
clarity, context7, exa, markitdown, perplexity, spinach, sequentialthinking
```
The roster is scoped to your virtual key: servers you may not call are absent, so
anything listed is genuinely callable.
## Troubleshooting
| Symptom | Cause |
|---|---|
| `fileName parameter is required` | Used `path=` instead of `fileName=` |
| `Tool 'x' not found in server 'y'` | Wrong tool name; the error lists the valid ones |
| Execution succeeds, no value | Missing `result =` assignment |
| Syntax error on `for`/`if` | Top-level control flow; move it into a `def` |
| Server key undefined | Not in your key's scope — check the `Environment` block |
+ | `nested tool call failed` | A call returned an error value; check `hasattr(r, "error")` |
+ | Script stops after many calls | 64 tool calls per script cap; split or batch |
## Relationship to flat tools
A gateway mixes both modes. Flat tools (`<prefix><server>-<tool>`) are already in
your tool list and should be called directly. Code mode is for everything else. A
server never appears in both, so if you can see it as a flat tool, do not route it
through `executeToolCode`.