Stop Building Middlemen: Let AI Call Your APIs Directly with UTCP
If you have ever asked a voice assistant for the weather and waited three extra seconds for the answer, you have felt the pain of “wrapper servers.”
These invisible middlemen translate the assistant’s question into an API call, then translate the answer back again.
Universal Tool Calling Protocol (UTCP) removes that extra hop.
It gives large language models, chatbots, or any other client a plain-English instruction manual that says:
-
“Here is the tool.” -
“Here is its real endpoint.” -
“Here is how you call it directly.”
After the client reads the manual, UTCP steps out of the way.
The result is lower latency, zero extra infrastructure, and the freedom to keep your existing authentication, rate limiting, and billing exactly as they are.
This article walks you through everything you need to know—from the big idea to copy-paste code—so you can expose your own tools to AI without writing another wrapper again.
The Three Core Pieces
UTCP keeps its vocabulary small:
Everything else—authentication, retries, timeouts—lives inside these three pieces, so nothing is hidden behind extra abstractions.
Why UTCP Exists
Traditional integrations often look like this:
-
AI sends a text request to a “translation server.” -
Translation server maps the text to your API. -
Your API answers the translation server. -
Translation server answers the AI.
Each step adds latency, cost, and another moving part that can fail on a Saturday night.
UTCP inverts the model: publish a manual once, then let every client speak to the real API directly.
Benefits you can measure:
-
No wrapper tax—your existing endpoint is enough. -
No re-implementation—authentication, caching, and rate limits stay where they already work. -
Direct path—latency drops because traffic no longer hair-pins through a proxy.
A 10-Minute End-to-End Example
We will turn a plain weather API into a UTCP-ready service, then call it from a Python client.
No extra servers, no language tricks—just two small files.
Step 1: Write the Manual
Create app.py
with FastAPI:
from fastapi import FastAPI
from pydantic import BaseModel
from typing import List
app = FastAPI()
class UTCPManual(BaseModel):
version: str
tools: List[dict]
tools = [
{
"name": "get_weather",
"description": "Get current weather for a city",
"inputs": {
"type": "object",
"properties": {
"location": {"type": "string", "description": "City name"},
"unit": {"type": "string", "enum": ["celsius", "fahrenheit"]}
},
"required": ["location"]
},
"outputs": {
"type": "object",
"properties": {
"temperature": {"type": "number"},
"conditions": {"type": "string"}
}
},
"tool_provider": {
"provider_type": "http",
"url": "https://api.example.com/weather",
"http_method": "GET"
}
}
]
@app.get("/utcp")
def utcp_discovery():
return UTCPManual(version="1.0", tools=tools)
@app.get("/weather")
def get_weather(location: str, unit: str = "celsius"):
# In real life, call your weather provider here
return {"temperature": 22.5, "conditions": "Sunny"}
Start the service:
uvicorn app:app --reload
Visit http://localhost:8000/utcp
in your browser; the raw JSON you see is the entire manual.
Step 2: Use the Tool from Any Client
Install the official SDK:
pip install utcp
Create client.py
:
import asyncio
from utcp.client import UtcpClient
from utcp.shared.provider import HttpProvider
async def main():
client = await UtcpClient.create()
manual_provider = HttpProvider(
name="weather_demo",
provider_type="http",
url="http://localhost:8000/utcp",
http_method="GET"
)
await client.register_manual_provider(manual_provider)
result = await client.call_tool(
"weather_demo.get_weather",
arguments={"location": "San Francisco"}
)
print(f"Temperature: {result['temperature']}°C, Conditions: {result['conditions']}")
if __name__ == "__main__":
asyncio.run(main())
Run it:
python client.py
Output:
Temperature: 22.5°C, Conditions: Sunny
That is the entire integration.
The client read the manual, then contacted the weather endpoint directly.
No extra proxy, no new container, no extra latency.
Manual Deep Dive: JSON You Can Actually Read
A manual is nothing more than a JSON object sitting at a stable URL.
Below is the minimal required structure with inline comments.
{
"version": "1.0",
"tools": [
{
"name": "get_weather",
"description": "Get current weather for a city",
"inputs": {
"type": "object",
"properties": {
"location": {"type": "string"},
"unit": {"type": "string", "enum": ["celsius", "fahrenheit"]}
},
"required": ["location"]
},
"outputs": {
"type": "object",
"properties": {
"temperature": {"type": "number"},
"conditions": {"type": "string"}
}
},
"tags": ["weather", "api"],
"tool_provider": {
"provider_type": "http",
"url": "https://api.example.com/weather",
"http_method": "GET"
}
}
]
}
-
inputs
andoutputs
use standard JSON Schema. -
tool_provider
describes the actual call. -
tags
are optional but help AI clients group related tools.
Provider Types: One Format, Many Protocols
UTCP does not care how the tool speaks, only that you describe it.
Supported provider types today:
Each provider type has its own small set of extra keys, but they all live inside the same tool_provider
object, so switching from HTTP to WebSocket is a three-line change.
Variables and Secrets Without Hard-Coding
Real deployments need secrets.
UTCP supports ${VARIABLE}
substitution anywhere inside the provider object.
Example:
"url": "https://${DOMAIN}/api/v1",
"auth": {
"auth_type": "api_key",
"api_key": "$OPENWEATHER_KEY"
}
Resolution order:
-
Client-side configuration map -
Environment variables
If a variable is missing, the client raises a clear error before any network call is attempted.
Authentication Patterns That Already Work
UTCP reuses existing auth mechanisms; you do not invent new ones.
API Key in Header
"auth": {
"auth_type": "api_key",
"api_key": "$MY_API_KEY",
"var_name": "X-API-Key"
}
Basic Auth
"auth": {
"auth_type": "basic",
"username": "$USER",
"password": "$PASS"
}
OAuth 2.0 Client Credentials
"auth": {
"auth_type": "oauth2",
"client_id": "$CLIENT_ID",
"client_secret": "$CLIENT_SECRET",
"token_url": "https://auth.example.com/token"
}
The SDK exchanges the client secret for a bearer token and caches it until expiry.
Beyond HTTP: CLI Tools in One Snippet
Suppose you have a local Python script compress.py
:
import sys, gzip, os, json
path = sys.argv[1]
before = os.path.getsize(path)
with open(path, 'rb') as f_in, gzip.open(path + '.gz', 'wb') as f_out:
f_out.writelines(f_in)
after = os.path.getsize(path + '.gz')
print(json.dumps({"ratio": round(after / before, 2)}))
Manual entry:
{
"name": "compress_file",
"description": "Gzip a local file and return compression ratio",
"inputs": {
"type": "object",
"properties": {
"path": {"type": "string", "description": "Absolute file path"}
},
"required": ["path"]
},
"outputs": {
"type": "object",
"properties": {
"ratio": {"type": "number"}
}
},
"tool_provider": {
"provider_type": "cli",
"command": ["python", "compress.py", "${path}"]
}
}
An AI client will run exactly that command and read the JSON printed to stdout—no HTTP wrapper required.
WebSocket Streaming for Live Data
Some tools push updates instead of answering once.
Manual snippet:
"tool_provider": {
"provider_type": "websocket",
"url": "wss://logs.example.com/stream",
"auth": {
"auth_type": "api_key",
"api_key": "$LOG_TOKEN"
}
}
The client opens the socket, receives newline-delimited JSON, and decides when to close.
Best Practices That Save You Pain
-
Cache the manual
Fetch it once at startup, then reuse.
The SDK defaults to a 5-minute TTL. -
Validate inputs locally
Use the JSON Schema inside the manual to reject bad requests before they hit the wire. -
Set timeouts
Always pass a timeout tocall_tool
. The SDK default is 30 s; tune it. -
Log call IDs
Every call returns a unique ID—log it for tracing across services. -
Version your manual
Bump theversion
string when you add or remove tools so clients know when to refresh.
When to Use UTCP—and When Not To
Common Pitfalls and How to Dodge Them
Roadmap: Where UTCP Is Going
The maintainers keep the protocol minimal on purpose.
Planned additions include:
-
OAuth 2.0 authorization code flow for user-level tokens. -
Binary provider for raw protobuf over TCP. -
SDK generators for Go, Java, and Rust.
All changes remain backward-compatible; a v1.0 manual will always work.
Key Takeaways
-
UTCP is a description language, not a proxy. -
You publish a JSON manual once; any AI reads it and calls your real endpoint. -
No new servers, no new auth, no new latency. -
Supported protocols range from HTTP to CLI to WebSocket. -
Variables and existing auth keep secrets safe and deployments flexible.
Give your next API a ten-line manual, and let AI dial the number directly.
The wrapper days are over.