Skip to content

Repository files navigation

lua-utcp

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.

Requirements

  • Lua 5.3 or 5.4
  • lua-socket
  • lua-cjson (recommended) or dkjson
  • optional luarocks for installation

Supported transports

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.

Quick start

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)

Manual

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"
      }
    }
  }
})

Streaming

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.

CodeMode

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" })

OpenRouter + CodeMode

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-http

Then run the LLM-generated CodeMode example:

make example-openrouter-codemode

Or use lua-openai's chat-session API:

make example-openrouter-codemode-chat

The 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.

Design

  • 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.

Installation with LuaRocks

luarocks install lua-utcp-1.0-1.rockspec

Tests

The test suite is intentionally dependency-light. Run:

make test

For transport integration tests:

make integration

Reference implementations

License

MPL-2.0.

provider.json → UTCP → CodeMode

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.

Example servers

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.

About

Lua implementation of Universal Tool Calling Protocol

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages