# Codecall

> An Open Source Typescript implementation of Programmatic Tool Calling for AI Agents (Based on Code Mode)

- **Type:** MCP server
- **Install:** `agentstack add mcp-zeke-john-codecall`
- **Verified:** Yes — security-reviewed for prompt injection and unsafe behavior
- **Seller:** [zeke-john](https://agentstack.voostack.com/s/zeke-john)
- **Installs:** 0
- **Category:** [Integrations](https://agentstack.voostack.com/c/integrations)
- **Latest version:** 0.1.0
- **License:** MIT
- **Upstream author:** [zeke-john](https://github.com/zeke-john)
- **Source:** https://github.com/zeke-john/codecall

## Install

```sh
agentstack add mcp-zeke-john-codecall
```

Requires the [AgentStack CLI](https://agentstack.voostack.com/docs/cli). Works with Claude Code, Cursor, and any MCP-compatible agent.

## About

# Codecall

> An Open Source Typescript implementation of Programmatic Tool Calling for AI Agents.

Codecall changes how agents interact with tools by letting them **write and execute code** (in secured sandboxes) that orchestrates multiple tools programmatically (like an API) to do a task, rather than making individual tool calls that bloat the context and increase the token usage like in traditional agents. Codecall also has progressive tool discovery & error handling!

**Demo** →

Codecall vs a Traditional Agent performing the same task w/ the same tools

_74.7% fewer tokens · 92.3% fewer tool calls_

https://github.com/user-attachments/assets/6cfc44e4-784a-4258-97d6-6ae694ef4024

## Problems w/ Traditional Agents

Traditional tool calling in agents has many fundamental architectural issues that get worse at scale:

### 1. Context Bloat & Wasting Tokens

Traditional agents send EVERY tool definition with every request, so for 20 tools thats about 6k+ tokens of schema definitions in every inference call, even for questions like, "what can you do?" or "update the date for this" where they are not necessary. This issue scales with tool count and gets multiplied with every turn in a conversation.

### 2. N Inference Calls for N Tool Operations

Every tool operation requires a full inference round-trip, so "Delete all completed tasks" becomes: LLM calls `findTasks`, waits, calls `deleteTask` for task 1, waits, calls for task 2... Each call resends the entire conversation history including all previous tool results, so the tokens compound. For example those 20 steps = 20+ inference calls with exponentially growing context.

### 3. No Parallel Execution

Similar to #2, but traditional agents execute tools sequentially even when operations are independent. Ten API calls that could run simultaneously instead happen one at a time with the agent reasoning between each of them that wastes time, tokens, and unnecessarily sends info into the context window and through the API provider's infrastructure.

### 4. Models are not great at Lookup

Benchmarks show models have a **[10-50% failure rate](https://github.com/toon-format/toon?tab=readme-ov-file#per-model-accuracy)** when searching through large datasets in context. They hallucinate field names, miss entries, and get confused by similar data.

But doing this programmatically fixes this because it can just write code, as its deterministic so 0% failure rate

```typescript
users.filter((u) => u.role === "admin");
```

## Our Approach

Let models do what they're good at: **writing code**.

LLMs have enormous amounts of real-world TypeScript in their training data. They're significantly better at writing code to call APIs than they are at the arbitrary JSON matching that tool calling requires.

Codecall ALSO only has 2 tools: `readFile` and `executeCode`, along with our SDK file tree of your tools (that we generate ahead of time) in context, so the agent only reads and gets the context it needs as needed based on the task, so this makes a 30 tool setup effectively have the same base context as a 5 tool setup (only the file tree gets larger)

When code in the sandbox calls `tools.namespace.method()` a Proxy intercepts it, sends a JSON message via stdout to the host process, which routes it through ToolRegistry to execute the actual tool, then sends the result back via stdin where the sandbox resolves the waiting Promise, making IPC look like normal async function calls.

```typescript
// Instead of 20+ inference passes and 90k+ tokens:
const allUsers = await tools.users.listAllUsers();
const adminUsers = allUsers.filter((u) => u.role === "admin");
const resources = await tools.resources.getSensitiveResources();

progress({
  step: "Data loaded",
  admins: adminUsers.length,
  resources: resources.length,
});

const revokedAccesses = [];
const failedAccesses = [];

for (const admin of adminUsers) {
  for (const resource of resources) {
    try {
      const result = await tools.permissions.revokeAccess({
        userId: admin.id,
        resourceId: resource.id,
      });
      if (result.success) {
        revokedAccesses.push({ admin: admin.name, resource: resource.name });
      }
    } catch (err) {
      failedAccesses.push({
        admin: admin.name,
        resource: resource.name,
        error: err.message,
      });
    }
  }
}

return {
  totalAdmins: adminUsers.length,
  resourcesAffected: resources.length,
  accessesRevoked: revokedAccesses.length,
  accessesFailed: failedAccesses.length,
};
```

Three inference passes. The code runs in a sandbox calling all 20 updates programmatically with step by step updates, only pulling the relevant context when it is needed, and returning only what is necessary. Saving tens of thousands worth of tokens, and doing everything more efficiently.

## Getting Started

### Prerequisites

- **Node.js** v20+ and npm
- **Deno** v2+ (for sandbox execution) - [Install Deno](https://docs.deno.com/runtime/getting_started/installation/)
- **OpenRouter API Key** (or any OpenRouter compatible provider)

### Clone the repo

```bash
git clone https://github.com/zeke-john/codecall.git
cd codecall
npm install
```

### Environment Variables

Create a `.env` file in the project root:

```bash
OPENROUTER_API_KEY=your_openrouter_api_key
```

Optional variables:

```bash
TODOIST_API_KEY=your_todoist_api_key    # For Todoist the todoist MCP
MCP_PORT=4001                           # Custom port for demo MCP server
```

### Running the Demo MCP Server

Codecall includes a demo MCP server with user management tools for testing. Start it in a separate terminal:

```bash
npm run mcp
```

This starts an HTTP MCP server at `http://localhost:4001/mcp` with 18 user management tools (create, update, delete, search, etc.) that operate on `demoMCP/users.json`.

### Running the Codecall Agent

With the demo MCP server running:

```bash
npm run codecall
```

This starts an interactive chat session using the Codecall approach (2 internal tools + SDK file tree). The agent will read SDK files on-demand and write code to execute tasks in a sandbox.

### Running the Traditional Agent

For comparison, run the traditional agent that exposes all tools directly:

```bash
npm run traditional
```

### Chat Commands

Both agents support these commands:

- `/exit` or `/quit` - Exit the chat
- `/clear` - Clear conversation history
- `/tools` - List all available tools

### Connecting to Custom MCP Servers

#### Option 1: Command Line Arguments

Connect to stdio-based MCP servers:

```bash
npm run codecall -- --stdio namespace npx @some/mcp-server arg1 arg2
```

Connect to HTTP-based MCP servers:

```bash
npm run codecall -- --http namespace http://localhost:3000/mcp
```

Multiple servers:

```bash
npm run codecall -- \
  --stdio todoist npx @doist/todoist-ai \
  --http demo http://localhost:4001/mcp
```

#### Option 2: Modify Default Servers

Edit `scripts/chat.ts` and modify the `getDefaultServers()` function:

```typescript
function getDefaultServers(): MCPServerEntry[] {
  return [
    {
      namespace: "demo",
      config: {
        type: "http",
        url: "http://localhost:4001/mcp",
      },
    },
    {
      namespace: "github",
      config: {
        type: "stdio",
        command: "docker",
        args: ["run", "-i", "--rm", "ghcr.io/github/github-mcp-server"],
        env: { GITHUB_PERSONAL_ACCESS_TOKEN: process.env.GITHUB_TOKEN },
      },
    },
  ];
}
```

### Generating SDK Files from MCP Servers

Before using Codecall with a new MCP server, generate SDK files:

```bash
# From an HTTP MCP server
npm run test:mcp -- http http://localhost:4001/mcp

# From a stdio MCP server
npm run test:mcp -- stdio npx @doist/todoist-ai

# With environment variables
npm run test:mcp -- stdio npx @some/server --env API_KEY=xxx

# Custom output directory
npm run test:mcp -- http http://localhost:4001/mcp --output ./mysdks
```

SDK files are written to `generatedSdks/tools/{namespace}/`. see `docs/exampleSdkFile.ts` or any of the existing SDK files for the recommended format.

### Dev Scripts

For Observability & testing individual components:

```bash

# Debugs the demo MCP server using the MCP Inspector
npm run inspect

# Test the sandbox execution
npx tsx scripts/testSandbox.ts

# Test the tool registry
npx tsx scripts/testToolRegistry.ts
```

### Project Structure

```
codecall/
├── src/
│   ├── agents/           # Codecall and Traditional agent implementations
│   ├── core/             # Sandbox, tool registry, internal tools
│   ├── llm/              # OpenRouter client
│   ├── mcp/              # MCP client and loader
│   ├── sdk/              # SDK generator
│   └── types/            # TypeScript type definitions
├── scripts/              # CLI scripts for running agents
├── demoMCP/              # Demo MCP server with user tools
├── generatedSdks/        # Generated SDK files (tools/)
└── docs/                 # Example SDK file format (more coming soon)
```

## How Codecall Works

Codecall gives the model 2 tools + a file tree to work with so the model still controls the entire flow that decides what to read, what code to write, when to execute, and how to respond... so everything stays fully agentic.

Instead of exposing every tool directly to the LLM for it to call, Codecall:

- Converts your MCP definitions into TypeScript SDK files (types + function signatures)
- Shows the model a directory tree of available files
- Allows the model to selectively read SDK files to understand types and APIs
- Lets the model write code to accomplish the task
- Executes that code in a deno sandbox with access to your actual tools as functions
- Returns the execution result back (success/error)
- Lets the model produce a respond or continue

The system message by default has an SDK file tree showing all available tools as files, it just shows them the file tree and not the actual contents of each of the files, so it can progressively discover tools as it needs them for a certain task

Example:

```
tools/
├─ Database
│  ├─ checkEmailExists.ts
│  ├─ cloneUser.ts
│  ├─ createUser.ts
│  ├─ deactivateUsersByDomain.ts
│  ├─ deleteUser.ts
│  ├─ getUsersByFavoriteColor.ts
│  ├─ getUsersCreatedAfter.ts
│  ├─ getUserStats.ts
│  ├─ searchUsers.ts
│  ├─ setUserActiveStatus.ts
│  ├─ setUserFavoriteColor.ts
│  ├─ updateUser.ts
│  └─ validateEmailFormat.ts
└─ todoist
   ├─ addComments.ts
   ├─ addProjects.ts
   ├─ addSections.ts
   ├─ addTasks.ts
   ├─ manageAssignments.ts
   ├─ search.ts
   ├─ updateComments.ts
   ├─ updateProjects.ts
   ├─ updateSections.ts
   ├─ updateTasks.ts
   └─ userInfo.ts
```

### 1. `readFile(path: string)`

Returns the full contents of a specific SDK file, including type definitions, function signatures, and schemas.

Example:

`readFile({ path: "tools/users/listAllUsers.ts" });` ->

```typescript
/**
 * HOW TO CALL THIS TOOL:
 * await tools.users.listAllUsers({ limit: 100, offset: 0 })
 *
 * This is the ONLY way to invoke this tool in your code.
 */

export interface ListAllUsersInput {
  limit?: number;
  offset?: number;
}

export interface User {
  id: string;
  name: string;
  email: string;
  role: "admin" | "user" | "guest";
  department: string;
  createdAt: string;
}

export async function listAllUsers(input: ListAllUsersInput): Promise;
```

### 2. `executeCode(code: string)`

Executes TypeScript code in a Deno sandbox. Returns either the successful output or an error w/ the execution trace.

Example:

```typescript
executeCode(`
  const users = await tools.users.listAllUsers({ limit: 100 });
  return users.filter(u => u.role === "admin");
`);
```

Success returns:

```typescript
{
  status: "success",
  output: [
    { id: "1", name: "Alice", role: "admin", ... },
    { id: "2", name: "Bob", role: "admin", ... }
  ],
  progressLogs: [{ step: "Loading users..." }]
}
```

Error returns:

```typescript
{
  status: "error",
  error: `=== ERROR ===
Type: Error
Message: Undefined value at 'result[0]'. This usually means you accessed a property that doesn't exist.

=== STACK TRACE ===
Error: Undefined value at 'result[0]'...
    at validateResult (file:///.../sandbox.ts:68:11)
    at file:///.../sandbox.ts:99:5

progressLogs: [{ step: "Loading users..." }]
}
```

The error includes the full stack trace, giving the model maximum context to fix the issue and try again, then update the SDK file once fixed.

### Code Execution & Sandboxing

When the model calls `executeCode()`, Codecall runs that code inside a fresh, short-lived Deno sandbox. Each sandbox is spun up using Deno and runs the code in isolation. Deno’s security model blocks access to sensitive capabilities unless explicitly allowed.

By default, the sandboxed code has no access to the filesystem, network, environment variables, or system processes. The only way it can interact with the outside world is by calling the tool functions exposed through tools (which are forwarded by Codecall to the MCP server).

#### Sandbox Lifecycle

```
┌─────────────────────────────────────────────────────────────────────────────────────────┐
│   ┌─────────┐       ┌─────────┐     ┌─────────┐     ┌─────────┐     ┌─────────┐         │
│   │  SPAWN  │────--▶│  INJECT │────▶│ EXECUTE │────▶│ CAPTURE │────▶│ DESTROY │         │
│   └─────────┘       └─────────┘     └─────────┘     └─────────┘     └─────────┘         │
│        │                 │               │               │               │              │
│        ▼                 ▼               ▼               ▼               ▼              │
│   Fresh Deno       tools proxy     Run generated    Collect return   Terminate          │
│   process with     + progress()    TypeScript       value or error   process,           │
│   deny-by-default  injected        code             + progress logs  cleanup            │
│   (Deno 2)                                                                              │
│                                                                                         │
└─────────────────────────────────────────────────────────────────────────────────────────┘
```

#### Data Flow

```
┌─────────────────────────────────────────────────────────────────────────────────────────┐
│                                                                                         │
│    SANDBOX                        TOOL BRIDGE                         MCP SERVER        │
│       │                               │                                    │            │
│       │  tools.users.listAllUsers()   │                                    │            │
│       │ ─────────────────────────────▶│                                    │            │
│       │                               │                                    │            │
│       │                               │   tools/call: listAllUsers         │            │
│       │                               │ ──────────────────────────────────▶│            │
│       │                               │                                    │            │
│       │                               │          [{ id, name, role }, ...] │            │
│       │                               │ ◀──────────────────────────────────│            │
│       │                               │                                    │            │
│       │   Promise resolved    │                                    │            │
│       │ ◀─────────────────────────────│                                    │            │
│       │                               │                                    │            │
│       │  (code continues execution)   │                                    │            │
│       │                               │                                    │            │
│       │  progress({ step: "Done" })   │                                    │            │
│       │ ─────────────────────────────▶│                                    │            │
│       │                               │

…

## Source & license

This open-source MCP server is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [zeke-john](https://github.com/zeke-john)
- **Source:** [zeke-john/codecall](https://github.com/zeke-john/codecall)
- **License:** MIT

Install and usage instructions live in the source repository linked above.

## Pricing

- **Free** — Free

## Security capabilities

Automated source analysis of v0.1.0 — what this tool can access:

- **Network access:** no
- **Filesystem access:** no
- **Shell / process execution:** no
- **Environment & secrets:** yes
- **Dynamic code execution:** no

*"Yes" means the capability is present in the source — more access means more to trust, not that it is unsafe.*


## Versions

- **0.1.0** — security scan: passed — Imported from the upstream source.

## Links

- Listing page: https://agentstack.voostack.com/l/mcp-zeke-john-codecall
- Seller: https://agentstack.voostack.com/s/zeke-john
- Browse the marketplace: https://agentstack.voostack.com/browse

---
Listed on AgentStack — the marketplace for AI agent skills and MCP servers. Every listing is security-reviewed. Creators keep 70%.
