A plug-and-play library that lets agents call MCP and UTCP tools through TypeScript code - in just 3 lines.
LLMs are far better at writing code than managing complex tool calls. Instead of exposing hundreds of tools directly to the model, Code Mode UTCP allows for targeted searching of tools, and then calls the entire tool chain via code execution. The model writes JavaScript that calls your MCP or UTCP tools, enabling scalable, context-efficient orchestration.
- TypeScript Code Execution β Run TypeScript with full access to registered MCP/UTCP tools
- Hierarchical Tool Access β Tools organized by namespace (e.g.
math_tools.add()) - Auto-Generated Type Definitions β Type-safe interfaces for tool inputs and outputs
- Runtime Interface Access β Introspect TypeScript interfaces at runtime
- Secure Execution β Node.js VM sandbox with timeout and resource limits
- Composable Calls β Chain multiple tool calls within a single TypeScript code block
Direct tool calling doesn't scale:
- Each tool definition consumes context tokens
- Every intermediate result passes through the model
Code Mode flips this model:
- The LLM gets a single tool:
execute_code - It writes JS/TS that calls your MCP or UTCP endpoints
- A lightweight HTTP proxy forwards requests
- Results flow back through the execution environment
This leverages what LLMs excel at - writing code - while keeping tool orchestration efficient and stateless.
Tools can be explored like files on a filesystem β loaded only when needed. Agents can also search for tools dynamically () to keep context lean.search_tools
Large datasets can be filtered, joined, or aggregated in code before returning results, saving thousands of tokens.
Loops, conditionals, and error handling happen naturally in code - not through multiple tool calls.
Intermediate results stay within the sandbox; sensitive data can be tokenized automatically before reaching the model.
Agents can persist data or reusable functions (), gradually building their own "skills" over time../skills/*.ts
npm install @utcp/code-modeimport { CodeModeUtcpClient } from '@utcp/code-mode';
const client = await CodeModeUtcpClient.create();
// Register some tools first (example)
await client.registerManual({
name: 'github',
call_template_type: 'mcp',
config: {
"mcpServers": {
"github": {
"command": "docker",
"args": [
"run",
"-i",
"--rm",
"-e",
"GITHUB_PERSONAL_ACCESS_TOKEN",
"mcp/github"
],
"env": {
"GITHUB_PERSONAL_ACCESS_TOKEN": "<YOUR_TOKEN>"
}
}
}
}
});
// Now execute TypeScript code that uses the tools
const { result, logs } = await client.callToolChain(`
// Get pull request details
const prDetails = await github.get_pull_request({
owner: 'microsoft',
repo: 'vscode',
pull_number: 1234
});
console.log('PR Title:', prDetails.title);
console.log('PR State:', prDetails.state);
// Get pull request comments
const prComments = await github.get_pull_request_comments({
owner: 'microsoft',
repo: 'vscode',
pull_number: 1234
});
console.log('Found', prComments.length, 'review comments');
// Get pull request reviews
const prReviews = await github.get_pull_request_reviews({
owner: 'microsoft',
repo: 'vscode',
pull_number: 1234
});
console.log('Found', prReviews.length, 'reviews');
// Get files changed in the PR
const prFiles = await github.get_pull_request_files({
owner: 'microsoft',
repo: 'vscode',
pull_number: 1234
});
console.log('Files changed:', prFiles.length);
// Summarize the discussion
const discussionSummary = {
title: prDetails.title,
description: prDetails.body || 'No description provided',
state: prDetails.state,
author: prDetails.user.login,
filesChanged: prFiles.length,
totalComments: prComments.length,
totalReviews: prReviews.length,
reviewSummary: prReviews.map(review => ({
reviewer: review.user.login,
state: review.state,
commentCount: review.body ? 1 : 0
})),
keyDiscussionPoints: prComments.slice(0, 3).map(comment => ({
author: comment.user.login,
snippet: comment.body.substring(0, 100) + '...'
}))
};
console.log('Discussion Summary Generated');
return discussionSummary;
`);
console.log('PR Discussion Summary:', result);
console.log('Console output:', logs);All console output is automatically captured and returned alongside execution results:
const { result, logs } = await client.callToolChain(`
console.log('Starting PR analysis...');
console.warn('Analyzing large PR with many changes');
const prDetails = await github.get_pull_request({
owner: 'facebook',
repo: 'react',
pull_number: 5678
});
console.log('PR Title:', prDetails.title);
const prStatus = await github.get_pull_request_status({
owner: 'facebook',
repo: 'react',
pull_number: 5678
});
console.log('Status checks passed:', prStatus.state === 'success');
return { title: prDetails.title, checksPass: prStatus.state === 'success' };
`);
console.log('Result:', result); // { title: "Fix memory leak in hooks", checksPass: true }
console.log('Captured logs:');
logs.forEach((log, i) => console.log(`${i + 1}: ${log}`));
// Output:
// 1: Starting PR analysis...
// 2: [WARN] Analyzing large PR with many changes
// 3: PR Title: Fix memory leak in hooks
// 4: Status checks passed: trueYou can generate TypeScript interfaces for all your tools to get better IDE support:
const interfaces = await client.getAllToolsTypeScriptInterfaces();
console.log(interfaces);This will output something like:
// Auto-generated TypeScript interfaces for UTCP tools
namespace math_tools {
interface addInput {
/** First number */
a: number;
/** Second number */
b: number;
}
interface addOutput {
/** The sum result */
result: number;
}
}
/**
* Adds two numbers
* Tags: math, arithmetic
* Access as: math_tools.add(args)
*/Execute complex logic with multiple tools using hierarchical access:
const result = await client.callToolChain(`
// Get user data (assuming 'user_service' manual)
const user = await user_service.getUserData({ userId: "123" });
// Process the data (assuming 'data_processing' manual)
const processedData = await data_processing.processUserData({
userData: user,
options: { normalize: true, validate: true }
});
// Generate report (assuming 'reporting' manual)
const report = await reporting.generateReport({
data: processedData,
format: "json",
includeMetrics: true
});
// Send notification (assuming 'notifications' manual)
await notifications.sendNotification({
recipient: user.email,
subject: "Your report is ready",
body: \`Report generated with \${report.metrics.totalItems} items\`
});
return {
reportId: report.id,
itemCount: report.metrics.totalItems,
notificationSent: true
};
`);The code execution includes proper error handling:
try {
const result = await client.callToolChain(`
const result = await someToolThatMightFail({ input: "test" });
return result;
`);
} catch (error) {
console.error('Code execution failed:', error.message);
}You can set custom timeouts for code execution:
const result = await client.callToolChain(`
// Long running operation
const result = await processLargeDataset({ data: largeArray });
return result;
`, 60000); // 60 second timeoutThe code execution context provides access to TypeScript interfaces at runtime:
const result = await client.callToolChain(`
// Access all interfaces
console.log('All interfaces:', __interfaces);
// Get interface for a specific tool
const addInterface = __getToolInterface('math_tools.add');
console.log('Add tool interface:', addInterface);
// Parse interface information
const hasNamespaces = __interfaces.includes('namespace math_tools');
const availableNamespaces = __interfaces.match(/namespace \\w+/g) || [];
// Use this for dynamic validation, documentation, or debugging
return {
hasInterfaces: typeof __interfaces === 'string',
namespaceCount: availableNamespaces.length,
canIntrospect: typeof __getToolInterface === 'function',
specificToolInterface: !!addInterface
};
`);__interfaces: String containing all TypeScript interface definitions__getToolInterface(toolName: string): Function to get interface for a specific tool
For AI agents that will use CodeModeUtcpClient, include the built-in prompt template in your system prompt:
import { CodeModeUtcpClient } from '@utcp/code-mode';
// Add this to your AI agent's system prompt
const systemPrompt = `
You are an AI assistant with access to tools via UTCP CodeMode.
${CodeModeUtcpClient.AGENT_PROMPT_TEMPLATE}
Additional instructions...
`;This template provides essential guidance on:
- Tool Discovery Workflow: How to explore available tools before coding
- Hierarchical Access Patterns: Using
manual.tool()syntax correctly - Interface Introspection: Leveraging
__interfacesand__getToolInterface() - Best Practices: Error handling, data flow, and proper code structure
- Runtime Context: Available variables and functions in the execution environment
Extends UtcpClient with additional code execution capabilities.
Executes TypeScript code with access to all registered tools and captures console output.
- code: TypeScript code to execute
- timeout: Optional timeout in milliseconds (default: 30000)
- Returns: Object containing both the execution result and captured console logs (
console.log,console.error,console.warn,console.info)
Converts a single tool to its TypeScript interface definition.
- tool: The Tool object to convert
- Returns: TypeScript interface as a string
Generates TypeScript interfaces for all registered tools.
- Returns: Complete TypeScript interface definitions
A comprehensive prompt template designed for AI agents using CodeModeUtcpClient. Contains detailed guidance on tool discovery, hierarchical access patterns, interface introspection, and best practices for code execution.
CodeModeUtcpClient.create(root_dir?: string, config?: UtcpClientConfig): Promise<CodeModeUtcpClient>
Creates a new CodeModeUtcpClient instance.
- root_dir: Root directory for relative path resolution
- config: UTCP client configuration
- Returns: New CodeModeUtcpClient instance
- Code execution happens in a secure Node.js VM context
- No access to Node.js modules or filesystem by default
- Timeout protection prevents infinite loops
- Only registered tools are accessible in the execution context
The code mode client generates hierarchical TypeScript interfaces for all tools, providing:
- Namespace Organization: Tools grouped by manual (e.g.,
namespace math_tools) - Hierarchical Access: Clean dot notation (
math_tools.add()) prevents naming conflicts - Compile-time Type Checking: Full type safety for tool parameters and return values
- IntelliSense Support: Enhanced IDE autocompletion with organized namespaces
- Runtime Introspection: Access interface definitions during code execution
- Self-Documenting Code: Generated interfaces include descriptions and access patterns
For the best development experience:
- Generate TypeScript interfaces for your tools
- Save them to a
.d.tsfile in your project - Reference the file in your TypeScript configuration
- Enjoy full IntelliSense support for tool functions
// Generate and save interfaces
const interfaces = await client.getAllToolsTypeScriptInterfaces();
await fs.writeFile('tools.d.ts', interfaces);Actual up to date implementation in the typescript repository
MPL-2.0