https://github.com/universal-tool-calling-protocol/zig-utcp
zig implementation of UTCP
https://github.com/universal-tool-calling-protocol/zig-utcp
Last synced: 4 days ago
JSON representation
zig implementation of UTCP
- Host: GitHub
- URL: https://github.com/universal-tool-calling-protocol/zig-utcp
- Owner: universal-tool-calling-protocol
- Created: 2026-05-31T19:46:56.000Z (7 days ago)
- Default Branch: main
- Last Pushed: 2026-05-31T20:16:17.000Z (7 days ago)
- Last Synced: 2026-05-31T21:22:55.745Z (7 days ago)
- Language: Zig
- Size: 40 KB
- Stars: 0
- Watchers: 0
- Forks: 0
- Open Issues: 2
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
# zig-utcp
`zig-utcp` is a Zig implementation of the Universal Tool Calling Protocol (UTCP)
client model, based on the package boundaries in
[`universal-tool-calling-protocol/go-utcp`](https://github.com/universal-tool-calling-protocol/go-utcp).
A UTCP client discovers tools from *providers* (described by JSON config),
stores them in a repository, searches across them, and calls them — each
provider kind is handled by a matching *transport*.
Requires Zig `0.16.0`.
## Features
- Provider models and working transports for every UTCP provider kind:
`text`, `cli`, `http`, `sse`, `http_stream`, `websocket`, `grpc`, `graphql`,
`tcp`, `udp`, `webrtc`, and `mcp`
- UTCP manual parsing and an in-memory provider/tool repository
- Tag + description tool search
- `$VAR` / `${VAR}` substitution from inline variables, dotenv files, or an
optional `std.process.Environ`
- Text template rendering with `{{.name}}` and `{{.Inputs.name}}`
- Auth helpers: API key (header / query / cookie) and HTTP basic
## Quick start
```zig
const std = @import("std");
const utcp = @import("zig_utcp");
pub fn main() !void {
var client = try utcp.Client.init(std.heap.page_allocator, .{});
defer client.deinit();
// Register a provider; the matching transport discovers its tools.
try client.registerToolProvider(.{ .text = .{
.base = .{ .name = "greetings", .provider_type = .text },
.templates = &.{.{ .name = "hello", .value = "Hello, {{.name}}!" }},
} });
// Tools are addressed as ".".
const result = try client.callToolJson("greetings.hello", "{\"name\":\"Ada\"}");
std.debug.print("{s}\n", .{result.string}); // Hello, Ada!
}
```
`Client` owns its own arena, repository, and one instance of every transport.
`callTool` takes a parsed `std.json.Value`; `callToolJson` parses a JSON string
for you. `searchTools(query, limit)` ranks the combined catalog by tag and
description.
## Using it as a dependency
```sh
zig fetch --save https://github.com/universal-tool-calling-protocol/zig-utcp/archive/refs/tags/v0.1.1.tar.gz
```
```zig
// build.zig
const utcp = b.dependency("zig_utcp", .{ .target = target, .optimize = optimize });
exe.root_module.addImport("zig_utcp", utcp.module("zig_utcp"));
```
## Transports
Each provider kind maps to a transport under `utcp.transport`. The client
dispatches automatically, but a transport can also be driven directly
(`registerToolProvider` to discover tools, `callTool` to invoke one) — see the
per-transport examples.
| `provider_type` | Transport | Key config fields |
| --- | --- | --- |
| `text` | `TextTransport` | `templates` (offline; renders locally) |
| `cli` | `CliTransport` | `command_name`, `working_dir`, `env_vars` |
| `http` | `HttpTransport` | `url`, `http_method`, `headers`, `auth`, `body_field` |
| `sse` | `SseTransport` | `url`, `event_type`, `headers`, `auth` |
| `http_stream` | `HttpStreamTransport` | `url`, `http_method`, `chunk_size`, `timeout` |
| `websocket` | `WebSocketTransport` | `url` (`ws://`), `protocol`, `headers`, `auth` |
| `grpc` | `GrpcTransport` | `host`, `port`, `service_name`, `method_name` |
| `graphql` | `GraphQLTransport` | `url`, `operation_type`, `operation_name` |
| `tcp` | `TcpTransport` | `host`, `port`, `timeout` (newline-delimited JSON) |
| `udp` | `UdpTransport` | `host`, `port`, `timeout` |
| `webrtc` | `WebRTCTransport` | `signaling_server`, `peer_id`, `data_channel_name` |
| `mcp` | `McpTransport` | `command` + `args` (stdio) **or** `url` (HTTP) |
`text` runs fully offline. The rest need a matching server reachable at the
configured endpoint.
## Providers from JSON
Instead of building providers in code, load them from JSON — a single provider,
an array, or a `{ "providers": [...] }` wrapper. `$VAR` / `${VAR}` references are
substituted from `variables`, `dotenv_paths`, or `environ`.
```jsonc
// providers.json
{
"providers": [
{
"provider_type": "text",
"name": "greeter",
"templates": { "welcome": "$GREETING {{.name}}, welcome to zig-utcp" }
}
]
}
```
```zig
var client = try utcp.Client.init(std.heap.page_allocator, .{
.providers_file_path = "providers.json",
.variables = &.{.{ .name = "GREETING", .value = "Hello" }},
});
defer client.deinit();
const result = try client.callToolJson("greeter.welcome", "{\"name\":\"Ada\"}");
```
## Examples
The [`examples/`](examples/) directory has one program per transport plus client
demos. Build them all with `zig build examples`, or run one:
```sh
# High-level client API (offline, runnable as-is)
zig build run-client # register providers, search, call a tool
zig build run-client-file # load examples/providers.json with $VAR substitution
# Each transport example loads its endpoint/config from examples/*_provider.json.
# Server-backed examples start a tiny local fixture server before discovery.
zig build run-text
zig build run-cli
zig build run-http
zig build run-sse
zig build run-http-stream
zig build run-graphql
zig build run-websocket
zig build run-grpc
zig build run-tcp
zig build run-udp
zig build run-webrtc
zig build run-mcp
# Parse every bundled provider config and print its transport kind
zig build run-all
```
## Build and test
```sh
zig build test # run the unit tests
zig build examples # compile every example
```