AgentStack
MCP verified MIT Self-run

Codecall

mcp-zeke-john-codecall · by zeke-john

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

No reviews yet
0 installs
13 views
0.0% view→install

Install

$ agentstack add mcp-zeke-john-codecall

✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.

Security review

✓ Passed

No issues found. Passed automated security review. · v0.1.0 How review works →

  • Prompt-injection patterns
  • Secret / credential exfiltration
  • Dangerous shell & filesystem operations
  • Untrusted network calls
  • Known-malicious package signatures

What it can access

  • Network access No
  • Filesystem access No
  • Shell / process execution No
  • Environment & secrets Used
  • Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

Are you the author of Codecall? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

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

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.

// 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
  • OpenRouter API Key (or any OpenRouter compatible provider)

Clone the repo

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

Environment Variables

Create a .env file in the project root:

OPENROUTER_API_KEY=your_openrouter_api_key

Optional variables:

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:

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:

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:

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:

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

Connect to HTTP-based MCP servers:

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

Multiple servers:

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:

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:

# 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:


# 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" }); ->

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

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

Success returns:

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

Error returns:

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

Reviews

No reviews yet — be the first.

Versions

  • v0.1.0 Imported from the upstream source.