Lua implementation of the Universal Tool Calling Protocol (UTCP), modeled after the official Go/Rust implementations and the UTCP 1.x data model.
UTCP is a native tool-calling protocol: a client discovers a tool manual and then calls the tool through its native transport rather than requiring a wrapper server.
- Lua 5.3 or 5.4
lua-socketlua-cjson(recommended) ordkjson- optional
luarocksfor installation
| 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 / WebRTC / WebSocket | extension points; require ecosystem-specific Lua runtimes |
The core registry and client are transport independent. New transports implement call() and can be registered in lua/utcp/transports/init.lua.
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)A UTCP manual can be registered directly:
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"
}
}
}
})client:call_tool_stream("events", {}, function(event)
print(event.event, event.data)
end)SSE parsing handles event, id, and multi-line data fields and decodes JSON payloads when possible.
The CodeMode adapter exposes only canonical registry operations, avoiding invented tool names:
local tools = utcp.codemode.new(client)
local result = tools.call("echo", { message = "hello" })lua-openai provides an OpenRouter compatibility client for OpenAI-compatible chat completions. OpenRouter uses the OpenAI-compatible base URL https://openrouter.ai/api/v1, so the model can be selected with an OpenRouter model slug.
Install the optional dependency:
luarocks install lua-openai
export OPENROUTER_API_KEY=sk-or-...Start the local calculator server used by the examples:
make server-httpThen run the LLM-generated CodeMode example:
make example-openrouter-codemodeOr use lua-openai's chat-session API:
make example-openrouter-codemode-chatThe important architecture is:
OpenRouter / lua-openai
|
| generates Lua source
v
UTCP CodeMode sandbox
|
| codemode.call_tool(name, args)
v
canonical UTCP registry
|
v
native transport -> tool server
The model is given the discovered UTCP tool catalog and is instructed to emit only Lua CodeMode. The generated program cannot access the UTCP client or transport objects directly; it can invoke registered tools through codemode.call_tool(...). This keeps tool names canonical and prevents the model from inventing transport calls.
See examples/openrouter_codemode.lua and examples/openrouter_codemode_chat.lua for complete examples.
utcp.client— discovery, canonical registry and tool invocation.utcp.registry— provider/tool index and tag/name search.utcp.transports.*— native transport implementations.utcp.json— JSON backend abstraction.utcp.codemode— small execution API for Lua-based orchestration.utcp.errors— structured errors.
luarocks install lua-utcp-1.0-1.rockspecThe test suite is intentionally dependency-light. Run:
make testFor transport integration tests:
make integration- Go: https://github.com/universal-tool-calling-protocol/go-utcp
- Rust: https://github.com/universal-tool-calling-protocol/rs-utcp
- Specification: https://github.com/universal-tool-calling-protocol/utcp-specification
MPL-2.0.
A provider can be declared as JSON and loaded directly into the canonical UTCP 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})
]]))See provider.json, examples/provider_flow.lua, and examples/provider_codemode.lua for the complete flow.
Network transport examples include local Python servers under examples/servers/. Run make servers to start all demo servers, or use the individual make server-* targets.