Universal Tool Calling Protocol (UTCP) 1.0.1
Introduction
The Universal Tool Calling Protocol (UTCP) is a modern, flexible, and scalable standard for defining and interacting with tools across a wide variety of communication protocols. UTCP 1.0.0 introduces a modular core with a plugin-based architecture, making it more extensible, testable, and easier to package.
In contrast to other protocols, UTCP places a strong emphasis on:
Scalability : UTCP is designed to handle a large number of tools and providers without compromising performance.
: UTCP is 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.
: 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, SSE, CLI, and more), UTCP can integrate with almost any existing service or infrastructure.
: With a growing ecosystem of protocol plugins (including HTTP, SSE, CLI, and more), UTCP can integrate with almost any existing service or infrastructure. Ease of Use: The protocol is built on simple, well-defined Pydantic models, making it easy for developers to implement and use.
New Architecture in 1.0.0
UTCP has been refactored into a core library and a set of optional plugins.
Core Package ( utcp )
The utcp package provides the central components and interfaces:
Data Models : Pydantic models for Tool , CallTemplate , UtcpManual , and Auth .
: Pydantic models for , , , and . Pluggable Interfaces : CommunicationProtocol : Defines the contract for protocol-specific communication (e.g., HTTP, CLI). ConcurrentToolRepository : An interface for storing and retrieving tools with thread-safe access. ToolSearchStrategy : An interface for implementing tool search algorithms. VariableSubstitutor : Handles variable substitution in configurations. ToolPostProcessor : Allows for modifying tool results before they are returned.
: Default Implementations : UtcpClient : The main client for interacting with the UTCP ecosystem. InMemToolRepository : An in-memory tool repository with asynchronous read-write locks. TagAndDescriptionWordMatchStrategy : An improved search strategy that matches on tags and description keywords.
:
Protocol Plugins
Communication protocols are now separate, installable packages. This keeps the core lean and allows users to install only the protocols they need.
utcp-http : Supports HTTP, SSE, and streamable HTTP, plus an OpenAPI converter.
: Supports HTTP, SSE, and streamable HTTP, plus an OpenAPI converter. utcp-cli : For wrapping local command-line tools.
: For wrapping local command-line tools. utcp-mcp : For interoperability with the Model Context Protocol (MCP).
: For interoperability with the Model Context Protocol (MCP). utcp-text : For reading text files.
: For reading text files. utcp-socket : Scaffolding for TCP and UDP protocols. (Work in progress, requires update)
: Scaffolding for TCP and UDP protocols. (Work in progress, requires update) utcp-gql : Scaffolding for GraphQL. (Work in progress, requires update)
Installation
Install the core library and any required protocol plugins.
# Install the core client and the HTTP plugin pip install utcp utcp-http # Install the CLI plugin as well pip install utcp-cli
For development, you can install the packages in editable mode from the cloned repository:
# Clone the repository git clone https://github.com/universal-tool-calling-protocol/python-utcp.git cd python-utcp # Install the core package in editable mode with dev dependencies pip install -e core[dev] # Install a specific protocol plugin in editable mode pip install -e plugins/communication_protocols/http
Migration Guide from 0.x to 1.0.0
Version 1.0.0 introduces several breaking changes. Follow these steps to migrate your project.
Update Dependencies: Install the new utcp core package and the specific protocol plugins you use (e.g., utcp-http , utcp-cli ). Configuration: Configuration Object : UtcpClient is initialized with a UtcpClientConfig object, dict or a path to a JSON file containing the configuration.
: is initialized with a object, dict or a path to a JSON file containing the configuration. Manual Call Templates : The providers_file_path option is removed. Instead of a file path, you now provide a list of manual_call_templates directly within the UtcpClientConfig .
: The option is removed. Instead of a file path, you now provide a list of directly within the . Terminology : The term provider has been replaced with call_template , and provider_type is now call_template_type .
: The term has been replaced with , and is now . Streamable HTTP: The call_template_type http_stream has been renamed to streamable_http . Update Imports: Change your imports to reflect the new modular structure. For example, from utcp.client.transport_interfaces.http_transport import HttpProvider becomes from utcp_http.http_call_template import HttpCallTemplate . Tool Search: If you were using the default search, the new strategy is TagAndDescriptionWordMatchStrategy . This is the new default and requires no changes unless you were implementing a custom strategy. Tool Naming: Tool names are now namespaced as manual_name.tool_name . The client handles this automatically. 6 Variable Substitution Namespacing: Variables that are subsituted in different call_templates , are first namespaced with the name of the manual with the _ duplicated. So a key in a tool call template called API_KEY from the manual manual_1 would be converted to manual__1_API_KEY .
Usage Examples
1. Using the UTCP Client
config.json (Optional)
You can define a comprehensive client configuration in a JSON file. All of these fields are optional.
{ "variables" : { "openlibrary_URL" : " https://openlibrary.org/static/openapi.json " }, "load_variables_from" : [ { "variable_loader_type" : " dotenv " , "env_file_path" : " .env " } ], "tool_repository" : { "tool_repository_type" : " in_memory " }, "tool_search_strategy" : { "tool_search_strategy_type" : " tag_and_description_word_match " }, "manual_call_templates" : [ { "name" : " openlibrary " , "call_template_type" : " http " , "http_method" : " GET " , "url" : " ${URL} " , "content_type" : " application/json " }, ], "post_processing" : [ { "tool_post_processor_type" : " filter_dict " , "only_include_keys" : [ " name " , " key " ], "only_include_tools" : [ " openlibrary.read_search_authors_json_search_authors_json_get " ] } ] }
client.py
import asyncio from utcp . utcp_client import UtcpClient from utcp . data . utcp_client_config import UtcpClientConfig async def main (): # The UtcpClient can be created with a config file path, a dict, or a UtcpClientConfig object. # Option 1: Initialize from a config file path # client_from_file = await UtcpClient.create(config="./config.json") # Option 2: Initialize from a dictionary client_from_dict = await UtcpClient . create ( config = { "variables" : { "openlibrary_URL" : "https://openlibrary.org/static/openapi.json" }, "load_variables_from" : [ { "variable_loader_type" : "dotenv" , "env_file_path" : ".env" } ], "tool_repository" : { "tool_repository_type" : "in_memory" }, "tool_search_strategy" : { "tool_search_strategy_type" : "tag_and_description_word_match" }, "manual_call_templates" : [ { "name" : "openlibrary" , "call_template_type" : "http" , "http_method" : "GET" , "url" : "${URL}" , "content_type" : "application/json" } ], "post_processing" : [ { "tool_post_processor_type" : "filter_dict" , "only_include_keys" : [ "name" , "key" ], "only_include_tools" : [ "openlibrary.read_search_authors_json_search_authors_json_get" ] } ] }) # Option 3: Initialize with a full-featured UtcpClientConfig object from utcp_http . http_call_template import HttpCallTemplate from utcp . data . variable_loader import VariableLoaderSerializer from utcp . interfaces . tool_post_processor import ToolPostProcessorConfigSerializer config_obj = UtcpClientConfig ( variables = { "openlibrary_URL" : "https://openlibrary.org/static/openapi.json" }, load_variables_from = [ VariableLoaderSerializer (). validate_dict ({ "variable_loader_type" : "dotenv" , "env_file_path" : ".env" }) ], manual_call_templates = [ HttpCallTemplate ( name = "openlibrary" , call_template_type = "http" , http_method = "GET" , url = "${URL}" , content_type = "application/json" ) ], post_processing = [ ToolPostProcessorConfigSerializer (). validate_dict ({ "tool_post_processor_type" : "filter_dict" , "only_include_keys" : [ "name" , "key" ], "only_include_tools" : [ "openlibrary.read_search_authors_json_search_authors_json_get" ] }) ] ) client = await UtcpClient . create ( config = config_obj ) # Call a tool. The name is namespaced: `manual_name.tool_name` result = await client . call_tool ( tool_name = "openlibrary.read_search_authors_json_search_authors_json_get" , tool_args = { "q" : "J. K. Rowling" } ) print ( result ) if __name__ == "__main__" : asyncio . run ( main ())
2. Providing a UTCP Manual
A UTCPManual describes the tools you offer. The key change is replacing tool_provider with call_template .
server.py
UTCP decorator version:
from fastapi import FastAPI from utcp_http . http_call_template import HttpCallTemplate from utcp . data . utcp_manual import UtcpManual from utcp . python_specific_tooling . tool_decorator import utcp_tool app = FastAPI () # The discovery endpoint returns the tool manual @ app . get ( "/utcp" ) def utcp_discovery (): return UtcpManual . create_from_decorators ( manual_version = "1.0.0" ) # The actual tool endpoint @ utcp_tool ( tool_call_template = HttpCallTemplate ( name = "get_weather" , url = f"https://example.com/api/weather" , http_method = "GET" ), tags = [ "weather" ]) @ app . get ( "/api/weather" ) def get_weather ( location : str ): return { "temperature" : 22.5 , "conditions" : "Sunny" }
No UTCP dependencies server version:
from fastapi import FastAPI app = FastAPI () # The discovery endpoint returns the tool manual @ app . get ( "/utcp" ) def utcp_discovery (): return { "manual_version" : "1.0.0" , "utcp_version" : "1.0.1" , "tools" : [ { "name" : "get_weather" , "description" : "Get current weather for a location" , "tags" : [ "weather" ], "inputs" : { "type" : "object" , "properties" : { "location" : { "type" : "string" } } }, "outputs" : { "type" : "object" , "properties" : { "temperature" : { "type" : "number" }, "conditions" : { "type" : "string" } } }, "call_template" : { "call_template_type" : "http" , "url" : "https://example.com/api/weather" , "http_method" : "GET" } } ] } # The actual tool endpoint @ app . get ( "/api/weather" ) def get_weather ( location : str ): return { "temperature" : 22.5 , "conditions" : "Sunny" }
3. Full examples
You can find full examples in the examples repository.
Protocol Specification
UtcpManual and Tool Models
The tool_provider object inside a Tool has been replaced by call_template .
{ "manual_version" : " string " , "utcp_version" : " string " , "tools" : [ { "name" : " string " , "description" : " string " , "inputs" : { ... }, "outputs" : { ... }, "tags" : [ " string " ], "call_template" : { "call_template_type" : " http " , "url" : " https://... " , "http_method" : " GET " } } ] }
Call Template Configuration Examples
Configuration examples for each protocol. Remember to replace provider_type with call_template_type .
HTTP Call Template
{ "name" : " my_rest_api " , "call_template_type" : " http " , // Required "url" : " https://api.example.com/users/{user_id} " , // Required "http_method" : " POST " , // Required, default: "GET" "content_type" : " application/json " , // Optional, default: "application/json" "auth" : { // Optional, example using ApiKeyAuth for a Bearer token. The client must prepend "Bearer " to the token. "auth_type" : " api_key " , "api_key" : " Bearer $API_KEY " , // Required "var_name" : " Authorization " , // Optional, default: "X-Api-Key" "location" : " header " // Optional, default: "header" }, "headers" : { // Optional "X-Custom-Header" : " value " }, "body_field" : " body " , // Optional, default: "body" "header_fields" : [ " user_id " ] // Optional }
SSE (Server-Sent Events) Call Template
{ "name" : " my_sse_stream " , "call_template_type" : " sse " , // Required "url" : " https://api.example.com/events " , // Required "event_type" : " message " , // Optional "reconnect" : true , // Optional, default: true "retry_timeout" : 30000 , // Optional, default: 30000 (ms) "auth" : { // Optional, example using BasicAuth "auth_type" : " basic " , "username" : " ${USERNAME} " , // Required "password" : " ${PASSWORD} " // Required }, "headers" : { // Optional "X-Client-ID" : " 12345 " }, "body_field" : null , // Optional "header_fields" : [] // Optional }
Streamable HTTP Call Template
Note the name change from http_stream to streamable_http .
{ "name" : " streaming_data_source " , "call_template_type" : " streamable_http " , // Required "url" : " https://api.example.com/stream " , // Required "http_method" : " POST " , // Optional, default: "GET" "content_type" : " application/octet-stream " , // Optional, default: "application/octet-stream" "chunk_size" : 4096 , // Optional, default: 4096 "timeout" : 60000 , // Optional, default: 60000 (ms) "auth" : null , // Optional "headers" : {}, // Optional "body_field" : " data " , // Optional "header_fields" : [] // Optional }
CLI Call Template
{ "name" : " my_cli_tool " , "call_template_type" : " cli " , // Required "command_name" : " my-command --utcp " , // Required "env_vars" : { // Optional "MY_VAR" : " my_value " }, "working_dir" : " /path/to/working/directory " , // Optional "auth" : null // Optional (always null for CLI) }
Text Call Template
{ "name" : " my_text_manual " , "call_template_type" : " text " , // Required "file_path" : " ./manuals/my_manual.json " , // Required "auth" : null // Optional (always null for Text) }
MCP (Model Context Protocol) Call Template
{ "name" : " my_mcp_server " , "call_template_type" : " mcp " , // Required "config" : { // Required "mcpServers" : { "server_name" : { "transport" : " stdio " , "command" : [ " python " , " -m " , " my_mcp_server " ] } } }, "auth" : { // Optional, example using OAuth2 "auth_type" : " oauth2 " , "token_url" : " https://auth.example.com/token " , // Required "client_id" : " ${CLIENT_ID} " , // Required "client_secret" : " ${CLIENT_SECRET} " , // Required "scope" : " read:tools " // Optional } }
Testing
The testing structure has been updated to reflect the new core/plugin split.
Running Tests
To run all tests for the core library and all plugins:
# Ensure you have installed all dev dependencies python -m pytest
To run tests for a specific package (e.g., the core library):
python -m pytest core/tests/
To run tests for a specific plugin (e.g., HTTP):
python -m pytest plugins/communication_protocols/http/tests/ -v
To run tests with coverage:
python -m pytest --cov=utcp --cov-report=xml
Build
The build process now involves building each package ( core and plugins ) separately if needed, though they are published to PyPI independently.