Install
$ agentstack add mcp-zeke-john-codecall ✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.
Security review
✓ PassedNo 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.
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:
/exitor/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.
Write a review
Versions
- v0.1.0 Imported from the upstream source.