Skip to content

Latest commit

 

History

History
161 lines (119 loc) · 4.46 KB

File metadata and controls

161 lines (119 loc) · 4.46 KB

Code Mode

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.

Why Code Mode

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.

Generating SDKs

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/

Generated TypeScript

// 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>

Generated Python

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"""
    ...

What gets generated

  • Typed interfaces for all action parameters
  • JSDoc/docstrings from parameter descriptions
  • Enum types from values fields (e.g., "open" | "closed" | "all")
  • Required vs optional parameters properly marked
  • Auth env vars documented as comments

MCP Code Mode

Enable code mode on the MCP server with --code-mode:

clictl mcp-serve github stripe --code-mode

This 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.

MCP config

{
  "mcpServers": {
    "clictl": {
      "command": "clictl",
      "args": ["mcp-serve", "github", "stripe", "--code-mode"]
    }
  }
}

How it works

  1. Agent receives execute_code tool with type definitions in the description
  2. Agent writes JavaScript code that calls API methods (e.g., github.user(...))
  3. Code executes in a sandboxed JavaScript runtime (goja)
  4. Each API method call routes through the Go HTTP executor
  5. console.log() output is captured and returned to the agent

Hybrid mode

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.

Sandbox Security

The code execution sandbox blocks:

  • Network access - fetch, XMLHttpRequest, WebSocket are all undefined
  • Dynamic execution - eval, Function constructor blocked
  • Module loading - require, import blocked
  • Timers - setTimeout, setInterval blocked
  • I/O - process, window, document blocked

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.

Multi-API Code

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