Skip to content

Official typescript implementation of UTCP. UTCP is an open standard that lets AI agents call any API directly, without extra middleware.

License

Notifications You must be signed in to change notification settings

universal-tool-calling-protocol/typescript-utcp

Repository files navigation

Universal Tool Calling Protocol (UTCP) for TypeScript

Follow Org NPM version License CDTM S23

The Universal Tool Calling Protocol (UTCP) is a secure and scalable standard for defining and interacting with tools across a wide variety of communication protocols. This repository contains the official TypeScript implementation, structured as a monorepo with a lean core and pluggable communication protocols.

UTCP offers a unified framework for integrating disparate tools and services, making them accessible through a consistent and well-defined interface. This TypeScript SDK provides a comprehensive toolkit for developers to leverage the full power of the UTCP standard in their applications.

Key Features

  • Automatic Plugin Registration: The official plugins are automatically discovered and registered when you import the core client—no manual setup required. For other plugins, you will need to register them manually.
  • Scalability: Designed to handle a large number of tools and providers without compromising performance.
  • Extensibility: A pluggable architecture allows developers to easily add new communication protocols, tool storage mechanisms, and search strategies without modifying the core library.
  • Interoperability: With a growing ecosystem of protocol plugins—including HTTP, MCP, Text, File, and CLI—UTCP can integrate with almost any existing service or infrastructure.
  • Code Execution Mode: Execute TypeScript code with hierarchical access to tools, complete console output capture, and runtime interface introspection for powerful AI agent workflows.
  • Type Safety: Built on well-defined TypeScript interfaces and runtime validation powered by Zod, making it robust and developer-friendly.
  • Secure Variable Management: Namespace-isolated variables prevent leakage between manuals, with support for environment variables and .env files.

MCP vs. UTCP

Getting Started

Installation

Install UTCP packages from npm:

# Install core SDK and desired protocol plugins
npm install @utcp/sdk @utcp/http @utcp/mcp @utcp/text @utcp/file @utcp/code-mode

# Optional: Add dotenv variable loader for Node.js
npm install @utcp/dotenv-loader

# Or using bun
bun add @utcp/sdk @utcp/http @utcp/mcp @utcp/text @utcp/file @utcp/code-mode

For Development

To set up the monorepo for development:

# Clone the repository
git clone https://github.com/universal-tool-calling-protocol/typescript-utcp.git
cd typescript-utcp

# Install dependencies (requires bun)
bun install

# Build all packages
bun run build

Quick Start

Basic Usage

Plugins are automatically registered when you import the UtcpClient—no manual registration needed!

import { UtcpClient } from '@utcp/sdk';
import { HttpCallTemplateSerializer } from '@utcp/http';

async function main() {
  // Create client - plugins auto-register
  const serializer = new HttpCallTemplateSerializer();
  const githubTemplate = serializer.validateDict({
    name: 'github_api',
    call_template_type: 'http',
    http_method: 'GET',
    url: 'https://api.github.com/users/${username}',
  });

  const client = await UtcpClient.create(process.cwd(), {
    manual_call_templates: [githubTemplate],
    variables: {
      // Namespace format: manual_name_VARIABLE
      github__api_username: 'octocat'
    }
  });

  // Search for tools
  const tools = await client.searchTools('github user');
  console.log('Found tools:', tools.map(t => t.name));

  // Call a tool
  const result = await client.callTool('github_api.get_user', {});
  console.log('Result:', result);

  await client.close();
}

main().catch(console.error);

Working with Multiple Protocols

import { UtcpClient } from '@utcp/sdk';
import { HttpCallTemplateSerializer } from '@utcp/http';
import { McpCallTemplateSerializer } from '@utcp/mcp';
import { FileCallTemplateSerializer } from '@utcp/file';

async function main() {
  // Create serializers for each protocol
  const httpSerializer = new HttpCallTemplateSerializer();
  const mcpSerializer = new McpCallTemplateSerializer();
  const fileSerializer = new FileCallTemplateSerializer();

  // Validate and create call templates
  const httpTemplate = httpSerializer.validateDict({
    name: 'api_manual',
    call_template_type: 'http',
    http_method: 'GET',
    url: 'https://api.example.com/data',
    headers: {
      'Authorization': 'Bearer ${API_KEY}'
    }
  });

  const mcpTemplate = mcpSerializer.validateDict({
    name: 'mcp_tools',
    call_template_type: 'mcp',
    config: {
      mcpServers: {
        my_mcp_server: {
          transport: 'stdio',
          command: 'node',
          args: ['./mcp-server.js'],
          cwd: './servers'
        }
      }
    }
  });

  const fileTemplate = fileSerializer.validateDict({
    name: 'local_tools',
    call_template_type: 'file',
    file_path: './config/tools.json'
  });

  const client = await UtcpClient.create(process.cwd(), {
    variables: {
      // Namespaced variables for security
      api__manual_API_KEY: process.env.API_KEY || 'default-key',
    },
    load_variables_from: [
      {
        variable_loader_type: 'dotenv',
        env_file_path: './.env'
      }
    ],
    manual_call_templates: [
      httpTemplate,  // HTTP API
      mcpTemplate,   // MCP Server
      fileTemplate   // Local file-based tools
    ]
  });

  // Tools are namespaced: manual_name.tool_name
  // For MCP: manual_name.server_name.tool_name
  const allTools = await client.getTools();
  console.log('Registered tools:', allTools.map(t => t.name));
  
  // Examples:
  // - 'api_manual.get_data'
  // - 'mcp_tools.my_mcp_server.echo'
  // - 'local_tools.my_function'

  await client.close();
}

API Reference

UtcpClient.create()

static async create(
  root_dir: string,
  config: Partial<UtcpClientConfig>
): Promise<UtcpClient>

Parameters:

  • root_dir: Base directory for resolving relative paths (usually process.cwd())
  • config: Client configuration object

Configuration Options:

interface UtcpClientConfig {
  // Direct variable definitions (highest priority)
  variables?: Record<string, string>;
  
  // External variable loaders (e.g., .env files)
  load_variables_from?: Array<{
    variable_loader_type: 'dotenv';
    env_file_path: string;
  }>;
  
  // Manual call templates to register at startup
  manual_call_templates?: CallTemplate[];
  
  // Tool repository configuration (defaults to in-memory)
  tool_repository?: ConcurrentToolRepository;
  
  // Search strategy configuration (defaults to tag_and_description_word_match)
  tool_search_strategy?: ToolSearchStrategy;
  
  // Post-processing configurations
  post_processing?: ToolPostProcessor[];
}

Core Methods

Search Tools

async searchTools(
  query: string,
  limit?: number,
  anyOfTagsRequired?: string[]
): Promise<Tool[]>

Searches for tools matching the query. The search considers:

  • Tool names (highest priority)
  • Tool tags
  • Tool descriptions

Call Tool

async callTool(
  toolName: string,
  args: Record<string, any>
): Promise<any>

Executes a tool with the given arguments. Tool names follow these formats:

  • HTTP/Text/CLI: manual_name.tool_name
  • MCP: manual_name.server_name.tool_name

Get Tools

async getTools(): Promise<Tool[]>
async getTool(toolName: string): Promise<Tool | undefined>

Retrieve all registered tools or get a specific tool by name.

Register/Deregister Manuals

async registerManual(callTemplate: CallTemplate): Promise<void>
async deregisterManual(manualName: string): Promise<boolean>

Dynamically add or remove tool manuals at runtime.

Variable Management

Variable Namespacing (Security Feature)

Variables are namespace-isolated by manual name to prevent variable leakage between manuals:

// For a manual named "github_api", variables are accessed as:
// ${VARIABLE} -> resolved from "github__api_VARIABLE"

import { HttpCallTemplateSerializer } from '@utcp/http';

const serializer = new HttpCallTemplateSerializer();
const githubTemplate = serializer.validateDict({
  name: 'github_api',
  call_template_type: 'http',
  http_method: 'GET',
  url: 'https://api.github.com/users',
  headers: {
    // Resolves to 'github__api_TOKEN' only
    'Authorization': 'Bearer ${TOKEN}'
  }
});

const client = await UtcpClient.create(process.cwd(), {
  variables: {
    'github__api_TOKEN': 'github-token-123',
    'slack__api_TOKEN': 'slack-token-456',
  },
  manual_call_templates: [githubTemplate]
});

Namespace transformation: Manual name underscores become double underscores:

  • github_apigithub__api_
  • my-servicemy_service_ (hyphens to underscores)

Variable Resolution Order

  1. Client config variables (highest priority)
  2. Variable loaders (e.g., .env files, in order)
  3. Environment variables (lowest priority)

All lookups use the namespaced key: {namespace}_VARIABLE_NAME.

Loading from .env Files

const client = await UtcpClient.create(process.cwd(), {
  load_variables_from: [
    {
      variable_loader_type: 'dotenv',
      env_file_path: './.env'
    }
  ]
});

Communication Protocols

HTTP Protocol

Supports RESTful APIs with automatic OpenAPI specification conversion:

import { HttpCallTemplateSerializer } from '@utcp/http';

const serializer = new HttpCallTemplateSerializer();
const weatherTemplate = serializer.validateDict({
  name: 'weather_api',
  call_template_type: 'http',
  http_method: 'GET',
  url: 'https://api.weather.com/v1/forecast',
  headers: {
    'X-API-Key': '${API_KEY}'
  },
  // Optional: Basic, API Key, or OAuth2 authentication
  auth: {
    auth_type: 'api_key',
    var_name: 'X-API-Key',
    api_key_value: '${API_KEY}',
    in: 'header'
  }
});

Features:

  • Path parameter substitution
  • Header and body templates
  • Multiple authentication methods
  • Automatic OpenAPI to UTCP conversion

MCP Protocol

Connect to Model Context Protocol servers:

import { McpCallTemplateSerializer } from '@utcp/mcp';

const serializer = new McpCallTemplateSerializer();
const mcpTemplate = serializer.validateDict({
  name: 'mcp_manual',
  call_template_type: 'mcp',
  config: {
    mcpServers: {
      server_name: {
        transport: 'stdio', // or 'http'
        command: 'bun',
        args: ['run', './mcp-server.ts'],
        cwd: './servers',
        env: { DEBUG: 'true' }
      }
    }
  }
});

Tool Naming: manual_name.server_name.tool_name

Features:

  • Stdio and HTTP transports
  • Persistent session management
  • Automatic retry on connection errors
  • Multiple servers per manual

Text Protocol

Handle direct text/string content (browser-compatible):

import { TextCallTemplateSerializer } from '@utcp/text';

const serializer = new TextCallTemplateSerializer();
const textTemplate = serializer.validateDict({
  name: 'inline_tools',
  call_template_type: 'text',
  content: JSON.stringify({
    tools: [
      // UTCP manual or OpenAPI spec as string
    ]
  })
});

File Protocol

Load tools from local JSON/YAML files or OpenAPI specs (Node.js only):

import { FileCallTemplateSerializer } from '@utcp/file';

const serializer = new FileCallTemplateSerializer();
const fileTemplate = serializer.validateDict({
  name: 'local_tools',
  call_template_type: 'file',
  file_path: './config/tools.json'
  // Supports: .json, .yaml, .yml, OpenAPI specs
});

CLI Protocol

Execute command-line tools:

import { CliCallTemplateSerializer } from '@utcp/cli';

const serializer = new CliCallTemplateSerializer();
const cliTemplate = serializer.validateDict({
  name: 'system_commands',
  call_template_type: 'cli',
  commands: [
    {
      command: 'git status'
    }
  ],
  working_dir: './my-repo'
});

Code Execution Mode

The @utcp/code-mode package provides a powerful extension that allows executing TypeScript code with direct access to registered tools, perfect for AI agents and complex workflows:

import { CodeModeUtcpClient } from '@utcp/code-mode';

async function main() {
  const client = await CodeModeUtcpClient.create();
  
  // Register your tools (same as regular UTCP)
  await client.registerManual({
    name: 'math_tools',
    call_template_type: 'text',
    content: '...' // Your tool definitions
  });

  // Execute TypeScript code with hierarchical tool access
  const { result, logs } = await client.callToolChain(`
    console.log('Starting calculation...');
    
    // Tools are organized by namespace: manual.tool
    const sum = await math_tools.add({ a: 10, b: 20 });
    console.log('Sum result:', sum.result);
    
    // Access TypeScript interfaces at runtime for introspection
    const addInterface = __getToolInterface('math_tools.add');
    console.log('Tool interface:', addInterface);
    
    // Chain multiple tool calls
    const result = await math_tools.multiply({ 
      a: sum.result, 
      b: 2 
    });
    
    return result;
  `);
  
  console.log('Execution result:', result);
  console.log('Console output:', logs);
  // logs: ['Starting calculation...', 'Sum result: 30', 'Tool interface: ...']
  
  await client.close();
}

Code Mode Features

  • Hierarchical Tool Access: Tools organized by manual namespace (manual.tool()) preventing naming conflicts
  • Console Output Capture: All console output automatically captured and returned
  • Runtime Interface Introspection: Access TypeScript interface definitions during execution
  • Type Safety: Generated TypeScript interfaces for all tools with hierarchical namespaces
  • Secure Execution: VM-based sandboxed execution with timeout protection
  • AI Agent Ready: Built-in prompt template to guide AI agents on proper usage

Perfect for AI Agents

The CodeModeUtcpClient.AGENT_PROMPT_TEMPLATE provides comprehensive guidance for AI agents:

// Add to your AI system prompt
const systemPrompt = `
${CodeModeUtcpClient.AGENT_PROMPT_TEMPLATE}

Your additional instructions...
`;

The template includes:

  • Tool discovery workflow
  • Interface introspection patterns
  • Best practices for hierarchical tool access
  • Error handling guidelines
  • Runtime context documentation

Monorepo Structure

typescript-utcp/
├── packages/
│   ├── core/          # Core SDK with UtcpClient and interfaces
│   ├── http/          # HTTP protocol plugin
│   ├── mcp/           # MCP protocol plugin
│   ├── text/          # Text/string content protocol plugin (browser-compatible)
│   ├── file/          # File system protocol plugin (Node.js only)
│   ├── code-mode/     # TypeScript code execution with hierarchical tool access
│   ├── dotenv-loader/ # DotEnv variable loader plugin (Node.js only)
│   ├── direct-call/   # Direct call protocol plugin
│   └── cli/           # CLI protocol plugin
├── tests/             # End-to-end integration tests
└── README.md

Each package is independently published to npm:

  • @utcp/sdk - Core SDK library (required)
  • @utcp/http - HTTP protocol support
  • @utcp/mcp - MCP protocol support
  • @utcp/text - Direct text/string content (browser-compatible)
  • @utcp/file - File system operations (Node.js only)
  • @utcp/code-mode - TypeScript code execution with hierarchical tool access
  • @utcp/dotenv-loader - DotEnv variable loader (Node.js only)
  • @utcp/direct-call - Direct function call protocol
  • @utcp/cli - Command-line tools

Development & Testing

Build

# Build all packages
bun run build

# Clean and rebuild
bun run rebuild

Testing

# Run all tests
bun test

# Run specific test file
bun test tests/utcp_client.test.ts

Publishing

# Publish all packages
bun run publish:all

# Or publish individually
bun run publish:core
bun run publish:http
bun run publish:mcp

Advanced Features

Custom Tool Repositories

Implement custom storage backends:

import { ConcurrentToolRepository } from '@utcp/sdk';

class RedisToolRepository implements ConcurrentToolRepository {
  // Implement required methods
  async getTools(): Promise<Tool[]> { /* ... */ }
  async getTool(name: string): Promise<Tool | undefined> { /* ... */ }
  // ... other methods
}

Custom Search Strategies

Implement custom tool search algorithms:

import { ToolSearchStrategy } from '@utcp/sdk';

class SemanticSearchStrategy implements ToolSearchStrategy {
  async searchTools(
    repository: ConcurrentToolRepository,
    query: string,
    limit?: number
  ): Promise<Tool[]> {
    // Custom semantic search implementation
  }
}

Post-Processors

Transform tool results:

const client = await UtcpClient.create(process.cwd(), {
  post_processing: [
    {
      tool_post_processor_type: 'filter_dict',
      allowed_keys: ['id', 'name', 'email']
    },
    {
      tool_post_processor_type: 'limit_strings',
      max_length: 1000
    }
  ]
});

Best Practices

  1. Always call client.close() to properly clean up resources
  2. Use namespaced variables for security and isolation
  3. Leverage automatic plugin registration - no manual setup needed
  4. Use TypeScript types from protocol packages for call templates
  5. Handle tool call errors appropriately in production
  6. Test with integration tests using the test patterns in /tests

License

This project is licensed under the Mozilla Public License Version 2.0. See the LICENSE file for details.

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

Code of Conduct

This project has adopted the Contributor Covenant Code of Conduct. For more information, see the Code of Conduct.

About

Official typescript implementation of UTCP. UTCP is an open standard that lets AI agents call any API directly, without extra middleware.

Topics

Resources

License

Stars

Watchers

Forks

Contributors 3

  •  
  •  
  •