Code mode lets agents write code against typed API bindings instead of making individual tool calls. LLMs have seen billions of lines of code but almost no tool-calling examples. Code mode plays to their strength.
Before (tool calls):
Agent -> tool_call: github_user {username: "octocat"}
Agent <- result (waits, processes, decides next)
Agent -> tool_call: github_repo-issues {owner: "octocat", repo: "hello-world"}
Agent <- result (waits, processes, decides next)
Three LLM turns. Three round trips.
After (code mode):
var user = github.user({username: "octocat"})
var issues = github.repoIssues({owner: "octocat", repo: "hello-world", state: "open"})
console.log("Open issues: " + issues.length)One turn. All calls in one shot.
Generate typed TypeScript or Python code from any spec:
# TypeScript to stdout
clictl codegen github --lang typescript
# Python to a file
clictl codegen stripe --lang python --out stripe_sdk.py
# All installed tools to a directory
clictl codegen --all --lang typescript --out ./sdk/// Auto-generated by clictl codegen
// Tool: github v1.0
export interface UserParams {
/** GitHub username */
username: string
}
export interface RepoIssuesParams {
/** Repository owner */
owner: string
/** Repository name */
repo: string
/** Filter by state */
state?: "open" | "closed" | "all"
}
/** Get a GitHub user's public profile */
export declare function user(params: UserParams): Promise<any>
/** List issues for a repository */
export declare function repoIssues(params: RepoIssuesParams): Promise<any>from dataclasses import dataclass
from typing import Any, Optional, Literal
@dataclass
class RepoIssuesParams:
owner: str
"""Repository owner"""
repo: str
"""Repository name"""
state: Optional[Literal["open", "closed", "all"]] = None
async def repo_issues(params: RepoIssuesParams) -> Any:
"""List issues for a repository"""
...- Typed interfaces for all action parameters
- JSDoc/docstrings from parameter descriptions
- Enum types from
valuesfields (e.g.,"open" | "closed" | "all") - Required vs optional parameters properly marked
- Auth env vars documented as comments
Enable code mode on the MCP server with --code-mode:
clictl mcp-serve github stripe --code-modeThis adds an execute_code tool alongside the regular individual tools. The tool description includes TypeScript type definitions for all loaded specs. The agent writes JavaScript code that calls the typed methods.
{
"mcpServers": {
"clictl": {
"command": "clictl",
"args": ["mcp-serve", "github", "stripe", "--code-mode"]
}
}
}- Agent receives
execute_codetool with type definitions in the description - Agent writes JavaScript code that calls API methods (e.g.,
github.user(...)) - Code executes in a sandboxed JavaScript runtime (goja)
- Each API method call routes through the Go HTTP executor
console.log()output is captured and returned to the agent
Code mode is additive. Individual tools remain available alongside execute_code. The agent can use simple tool calls for one-off actions and code mode for multi-step workflows.
The code execution sandbox blocks:
- Network access -
fetch,XMLHttpRequest,WebSocketare all undefined - Dynamic execution -
eval,Functionconstructor blocked - Module loading -
require,importblocked - Timers -
setTimeout,setIntervalblocked - I/O -
process,window,documentblocked
API calls go through the Go executor which handles SSRF protection, auth injection, and rate limiting. Credentials are never exposed to the JavaScript runtime.
Execution has a 30-second timeout.
With multi-API specs, each action's method knows its own URL and auth:
// Different APIs, different auth - handled automatically
var users = acmePlatform.listUsers({page: 1})
var invoices = acmePlatform.listInvoices({status: "open"})
var health = acmePlatform.health()
console.log(JSON.stringify({
userCount: users.length,
openInvoices: invoices.length,
healthy: health.status === "ok"
}))See also: Spec Format | CLI Reference | Security