UTCP for Lua — native tool calling, multiple transports, and LLM-ready CodeMode.
lua-utcp is a Lua implementation of the Universal Tool Calling Protocol (UTCP). It enables Lua applications to discover tools from providers, maintain a canonical registry, and invoke them directly via their native transport, eliminating the need for wrapper servers or provider-specific adapters.
UTCP manual / provider
│
▼
┌─────────────┐
│ Registry │
└──────┬──────┘
│
canonical tool
│
▼
┌─────────────┐
│ Client │
└──────┬──────┘
│
native transport
│
▼
Tool server
- Native Tool Calling: Invoke tools through their native transport without introducing a wrapper protocol server.
- Canonical Registry: Tools have a stable, unified name and schema across all transports.
- Transport Independence: Supports HTTP, SSE, Streamable HTTP, TCP, UDP, CLI, Text, GraphQL, and MCP.
- CodeMode Ready: Enables LLMs to generate Lua code that exclusively calls registered UTCP tools.
- LLM Friendly: Compatible with OpenAI-compatible APIs, including OpenRouter via
lua-openai. - Minimal Lua API: Designed for embedding within applications and agents.
- Structured Errors: Provides programmatic handling for tool and transport failures.
- Lua 5.3 or 5.4
lua-socketlua-cjson(recommended) ordkjson- LuaRocks (optional, but recommended for installation)
luarocks install lua-utcp-1.2-1.rockspecgit clone https://github.com/universal-tool-calling-protocol/lua-utcp.git
cd lua-utcp
make testThis example demonstrates creating a client with an HTTP provider, discovering its manual, and calling a tool:
local utcp = require("utcp")
local client = utcp.new({
providers = {
{
name = "demo",
provider_type = "http",
url = "http://127.0.0.1:8080",
tools_url = "http://127.0.0.1:8080/manual"
}
}
})
assert(client:discover())
local result, err = client:call_tool("echo", {
message = "hello"
})
assert(result, err)
print(type(result) == "table" and result.message or result)The key benefit is that the application invokes the echo tool via the canonical UTCP registry, abstracting away the underlying transport mechanism.
| Transport | Status |
|---|---|
| HTTP | ✅ Implemented |
| SSE | ✅ Implemented |
| Streamable HTTP | ✅ Implemented |
| TCP | ✅ Implemented |
| UDP | ✅ Implemented |
| CLI | ✅ Implemented |
| Text | ✅ Implemented |
| GraphQL | ✅ Implemented |
| MCP JSON-RPC over HTTP | ✅ Implemented |
| gRPC | Extension point |
| WebRTC | Extension point |
| WebSocket | Extension point |
The core client and registry are transport-agnostic. New transports can be implemented and registered via lua/utcp/transports/init.lua.
You can register a manual without relying on remote discovery:
client:add_manual({
manual_version = "1.0",
utcp_version = "1.0",
tools = {
{
name = "echo",
description = "Echo a message",
inputs = {
type = "object",
properties = {
message = { type = "string" }
},
required = { "message" }
},
tool_call_template = {
call_template_type = "http",
url = "http://127.0.0.1:8080/echo",
http_method = "POST"
}
}
}
})This makes the tool accessible through the same canonical registry used for discovered providers.
Streaming tools can be consumed incrementally:
client:call_tool_stream("events", {}, function(event)
print(event.event, event.data)
end)The SSE parser handles event, id, and multi-line data fields, decoding JSON payloads when possible.
CodeMode is the LLM-focused execution layer of lua-utcp. It provides a constrained Lua environment where models generate code that interacts with the canonical UTCP registry, rather than directly producing transport-specific calls.
local codemode = utcp.codemode.new(client)
local result = codemode.call_tool("echo", {
message = "hello"
})The CodeMode API exposes only canonical tool operations, preventing the LLM from generating invalid transport calls or accessing undefined tool endpoints.
Generated Lua programs can orchestrate multiple registered tools:
local execution = assert(codemode:call_tool_chain([[
local a = codemode.call_tool("calculator.add", { a = 10, b = 20 })
return a
]]))The execution flow is as follows:
LLM
│
│ generates Lua
▼
CodeMode
│
│ call_tool(name, args)
▼
Canonical UTCP registry
│
▼
Native transport
│
▼
Tool server
This separation is particularly beneficial for agent runtimes, allowing the LLM to focus on expressing computation while UTCP manages tool discovery and invocation.
lua-utcp includes examples demonstrating the integration of CodeMode with OpenAI-compatible LLM APIs via lua-openai.
Install the optional dependency and configure your API key:
luarocks install lua-openai
export OPENROUTER_API_KEY=sk-or-...Start the example HTTP tool server:
make server-httpRun the generated-CodeMode example:
make example-openrouter-codemodeOr execute the chat-session variant:
make example-openrouter-codemode-chatThe complete architecture for this integration is:
OpenRouter / lua-openai
│
│ generate Lua
▼
CodeMode sandbox
│
│ canonical tool call
▼
UTCP registry
│
▼
Native transport
│
▼
Tool server
The LLM receives the discovered UTCP tool catalog and is prompted to generate Lua CodeMode. The generated code invokes registered tools using codemode.call_tool(...) without direct access to transport objects.
Refer to the following examples:
examples/openrouter_codemode.luaexamples/openrouter_codemode_chat.lua
Providers can also be defined in JSON and loaded into the canonical registry:
local utcp = require("utcp")
local provider = assert(utcp.load_provider("provider.json"))
local client = utcp.Client.new()
assert(client:add_provider(provider))
local codemode = utcp.codemode.new(client)
local execution = assert(codemode:call_tool_chain([[
return codemode.call_tool("calculator.add", {
a = 10,
b = 20
})
]]))Related examples:
provider.jsonexamples/provider_flow.luaexamples/provider_codemode.lua
The implementation is structured into modular layers:
utcp
├── client # Discovery and invocation logic
├── registry # Canonical provider/tool index
├── transports # Native transport implementations
├── codemode # Constrained Lua execution API
├── json # JSON backend abstraction
└── errors # Structured error handling
utcp.client: Handles provider discovery, manual registration, and tool invocation.utcp.registry: Manages the indexing and lookup of providers and tools by name and tag.utcp.transports.*: Contains implementations for various native transports.utcp.codemode: Provides the Lua execution environment and canonical tool access for LLMs.utcp.json: Abstracts the underlying JSON library.utcp.errors: Defines the structure for error handling.
Network-related examples utilize local servers located in examples/servers/.
To start all demo servers:
make serversAlternatively, start individual servers using make server-* targets.
Run the unit and core test suite:
make testExecute transport integration tests:
make integrationlua-utcp/
├── lua/ # Library implementation
├── tests/ # Unit and transport tests
├── examples/ # Usage and CodeMode examples
├── examples/servers/ # Local demo tool servers
├── provider.json # Example provider definition file
├── Makefile
└── lua-utcp-*.rockspec
MPL-2.0.