UTCP Explained: A Universal Tool Calling Protocol for APIs, LLMs, and Beyond

7月15日 Published inCommunication Tools

The Universal Tool Calling Protocol (UTCP) establishes a standardized framework for communication between disparate tools. It operates across multiple transport layers, including HTTP, WebSocket, gRPC, and CLI. By utilizing a streamlined Pydantic core, the protocol ensures robust interoperability without unnecessary overhead. UTCP includes native support for common authentication methods (API Key, Basic Auth, OAuth2), automatic OpenAPI v3 conversion, and a design optimized for Large Language Models (LLMs). This allows LLMs to discover tools dynamically, identify the most relevant functions, and execute multi-turn, context-aware interactions.

Compared to MCP and other similar protocols, UTCP distinguishes itself through three primary advantages:

  • Scalability: It manages extensive collections of tools and providers without degrading system performance.
  • Interoperability: UTCP integrates with existing stacks by supporting HTTP, WebSockets, gRPC, and CLI tools.
  • Simplicity: Because it is built on straightforward Pydantic models, implementation is efficient and the learning curve is minimal.

Using UTCP: Core Examples

The following snippets demonstrate the basic implementation. For comprehensive, end-to-end code, refer to the examples/ directory in the repository.

1. The UTCP Client

The client is initialized by referencing a providers.json file, which automates the orchestration of different tools.

The providers.json file specifies where the client should fetch one or more UTCP manuals—the endpoints that define available tools.

[
  {
    "name": "cool_public_apis",
    "provider_type": "http",
    "url": "http://utcp.io/public-apis-manual",
    "http_method": "GET"
  }
]

The client.py script initializes the client and invokes a specific tool from a provider.

import asyncio
from utcp.client import UtcpClient

async def main():
    # Initialize the client by loading providers from the specified config file.
    client = await UtcpClient.create(
        config={"providers_file_path": "./providers.json"}
    )

    # Invoke a tool. Tool names follow the "provider_name.tool_name" convention.
    result = await client.call_tool(
        tool_name="cool_public_apis.example_tool", 
        arguments={}
    )

    print(result)

if __name__ == "__main__":
    asyncio.run(main())

2. Serving a UTCP Manual

Any service can function as a UTCP tool by exposing a UTCPManual. This manual can be served directly by the tool or managed by a third-party registry—a powerful approach for wrapping existing APIs that do not natively support the UTCP specification.

Below is a minimal FastAPI implementation:

server.py

from fastapi import FastAPI

app = FastAPI()

# The discovery endpoint provides the tool manual.
@app.get("/utcp")
def utcp_discovery():
    return {
        "version": "1.0",
        "tools": [
            {
                "name": "get_weather",
                "description": "Get current weather for a location",
                "inputs": {
                    "type": "object",
                    "properties": {
                        "location": {"type": "string"}
                    }
                },
                "outputs": {
                    "type": "object",
                    "properties": {
                        "temperature": {"type": "number"}
                    }
                },
                "tool_provider": {
                    "provider_type": "http",
                    "url": "https://example.com/api/weather",
                    "http_method": "GET"
                }
            }
        ]
    }

# The functional tool endpoint
@app.get("/api/weather")
def get_weather(location: str):
    return {"temperature": 22.5, "conditions": "sunny"}

3. LLM Integration

For a full demonstration connecting UTCP to OpenAI, see example/src/full_llm_example/openai_utcp_example.py.

That implementation demonstrates:

  • Dynamic Discovery: The client loads tools directly from providers.json rather than using hardcoded names.
  • Intelligent Search: For every user prompt, utcp_client.search_tools() identifies the most appropriate tool.
  • LLM-Driven Execution: The OpenAI model generates a custom JSON response to trigger specific tool calls.
  • Reliable Execution: The system parses the LLM's response, executes utcp_client.call_tool(), and returns the result to the model to generate a natural language answer.
  • Conversation Persistence: The client maintains full chat history to support multi-turn, context-sensitive dialogue.

Running the Example:

  1. Navigate to the directory: cd example/src/full_llm_example/
  2. Rename example.env to .env and insert your OpenAI API key.
  3. Execute the script: python openai_utcp_example.py

Protocol Specification

UTCP is defined by several core data models: tools, providers, and authentication mechanisms.

Tool Discovery

Clients require a UtcpManual object, which lists every tool offered by a provider. Depending on the provider type, this manual is either fetched from an HTTP discovery endpoint or read from a local file (common for CLI tools).

UtcpManual model:

{
  "version": "string",
  "tools": [
    {
      "name": "string",
      "description": "string",
      "inputs": { ... },
      "outputs": { ... },
      "tags": ["string"],
      "tool_provider": { ... }
    }
  ]
}
  • version: The current UTCP protocol version.
  • tools: An array of Tool objects.

Tool Definition

Every tool is defined using the Tool model.

Tool model:

{
  "name": "string",
  "description": "string",
  "inputs": {
    "type": "object",
    "properties": { ... },
    "required": ["string"],
    "description": "string",
    "title": "string"
  },
  "outputs": { ... },
  "tags": ["string"],
  "tool_provider": { ... }
}
  • name: A unique identifier for the tool.
  • description: A clear summary of the tool's function.
  • inputs: A JSON Schema defining required input parameters.
  • outputs: A JSON Schema defining the tool’s output format.
  • tags: Used for categorization and search filtering.
  • tool_provider: A ToolProvider object specifying the connection and invocation details.

Authentication

UTCP supports various authentication protocols. The auth object within a provider configuration determines the method used.

  • API Key (ApiKeyAuth): A static key, usually passed in the header.
{
  "auth_type": "api_key",
  "api_key": "YOUR_SECRET_API_KEY",
  "var_name": "X-API-Key"
}
  • Basic Auth (BasicAuth): Standard username and password credentials.
{
  "auth_type": "basic",
  "username": "your_username",
  "password": "your_password"
}
  • OAuth2 (OAuth2Auth): Client credentials flow. The UTCP client automatically retrieves a bearer token from the specified token_url.
{
  "auth_type": "oauth2",
  "token_url": "https://auth.example.com/token",
  "client_id": "your_client_id",
  "client_secret": "your_client_secret",
  "scope": "read write"
}

Providers

Providers give UTCP its flexibility by defining the communication protocol for specific tools.

Supported Provider Types:

  • http: RESTful HTTP/HTTPS APIs
  • sse: Server-Sent Events
  • http_stream: HTTP chunked transfer encoding
  • cli: Command-line interfaces
  • websocket: WebSocket (in development)
  • grpc: gRPC (in development)
  • graphql: GraphQL (in development)
  • tcp: Raw TCP sockets (in development)
  • udp: UDP (in development)
  • webrtc: WebRTC (in development)
  • mcp: Model Context Protocol (for interoperability)
  • text: Local text files

Each provider type requires specific configuration. For example, an HttpProvider requires a url and http_method. By default, the discovery endpoint is located at /utcp.

Provider Configuration Examples

  • HTTP Provider (Standard REST API)
{
  "name": "my_rest_api",
  "provider_type": "http",
  "url": "https://api.example.com/utcp",
  "http_method": "POST",
  "content_type": "application/json",
  "auth": {
    "auth_type": "oauth2",
    "token_url": "https://api.example.com/oauth/token",
    "client_id": "your_client_id",
    "client_secret": "your_client_secret"
  }
}
  • Automatic OpenAPI Conversion: Instead of a UtcpManual, an HTTP provider can link to an OpenAPI v3 specification. The OpenApiConverter automatically maps these to UTCP, providing immediate access to thousands of existing APIs.
{
  "name": "open_library_api",
  "provider_type": "http",
  "url": "https://openlibrary.org/dev/docs/api/openapi.json"
}
  • SSE Provider (Streaming Data)
{
  "name": "live_updates_service",
  "provider_type": "sse",
  "url": "https://api.example.com/utcp",
  "event_type": "message"
}
  • HTTP Stream Provider (Chunked Transfer)
{
  "name": "streaming_data_source",
  "provider_type": "http_stream",
  "url": "https://api.example.com/utcp",
  "http_method": "GET"
}
  • CLI Provider (Local Command Wrapper)
{
  "name": "my_cli_tool",
  "provider_type": "cli",
  "command_name": "my-command -utcp"
}
  • MCP Provider (Interoperability with Model Context Protocol via stdio or http)
{
  "name": "my_mcp_service",
  "provider_type": "mcp",
  "config": {
    "mcpServers": {
      "my-server": {
        "transport": "http",
        "url": "http://localhost:8000/mcp"
      }
    }
  },
  "auth": {
    "auth_type": "oauth2",
    "token_url": "http://localhost:8000/token",
    "client_id": "test-client",
    "client_secret": "test-secret"
  }
}
  • Text Provider (Local JSON Tool Definitions)
{
  "name": "my_local_tools",
  "provider_type": "text",
  "file_path": "/path/to/my/tools.json"
}

UTCP Client Architecture

The Python-based UTCP client serves as a modular framework for managing and invoking tools. It relies on several integrated components.

Core Components

  • UtcpClient: The primary entry point. It manages provider registration, tool execution, and search functionality.
  • UtcpClientConfig: A Pydantic model that defines the providers_file_path and manages variable loading (e.g., from .env files).
  • ClientTransportInterface: An abstract base class for all transports (such as HttpClientTransport or CliTransport). Each transport implements the specifics of its respective protocol.
  • ToolRepository: An interface for tool and provider storage. The default implementation is InMemToolRepository.
  • ToolSearchStrategy: An interface for search logic. The default TagSearchStrategy ranks tools based on tag matches and description keywords.

Initialization and Configuration

The UtcpClient is instantiated via the asynchronous UtcpClient.create() method.

import asyncio
from utcp.client import UtcpClient

async def main():
    client = await UtcpClient.create(
        config={
            "providers_file_path": "/path/to/your/providers.json",
            "load_variables_from": [{
                "type": "dotenv",
                "env_file_path": ".env"
            }]
        }
    )
    # The client is now ready for use.

asyncio.run(main())

During initialization, the client parses providers.json, resolves variables (such as ${API_KEY}), and registers each defined provider.

Tool Management and Execution

  • Registration: The register_tool_provider method uses the relevant transport to fetch definitions and populate the repository.
  • Execution: The call_tool method identifies the tool, retrieves its provider, and executes it with the provided arguments. Tools are namespaced by their provider (e.g., my_api.get_weather).
  • Deregistration: Providers can be removed to clean up the repository dynamically.

Tool Search

The search_tools method locates tools via query strings.

tools = client.search_tools(query="current weather in London")
for tool in tools:
    print(tool.name, tool.description)

Build Steps

  1. Create and activate a virtual environment: conda create --name utcp python=3.10 conda activate utcp
  2. Install the necessary dependencies: pip install -r requirements.txt
  3. Ensure pip is up to date: python -m pip install --upgrade pip
  4. Build the package: python -m build
  5. Install the generated package: pip install dist/utcp-<version>.tar.gz (e.g., pip install dist/utcp-1.0.0.tar.gz)