Discover and call tools over the protocols they already use.
go-utcp is a Go client for the Universal Tool Calling Protocol (UTCP). A provider describes its tools and how to reach them; the client discovers that metadata, indexes it locally, and dispatches calls over the provider's native transport.
The result is one API for tools exposed through HTTP, command-line programs, WebSockets, gRPC, GraphQL, MCP, and other transports.
- One client API: discover, search, call, and stream tools without transport-specific application code.
- Native connectivity: use existing APIs and services without moving them behind a single gateway.
- Local tool catalog: keep discovered metadata in memory by default, or supply your own repository and search strategy.
- Flexible configuration: load providers from JSON, register them at runtime, and inject secrets from explicit variables,
.envfiles, or the environment. - Broad transport support: HTTP, CLI, SSE, streamable HTTP, WebSocket, gRPC/gNMI, GraphQL, TCP, UDP, WebRTC, MCP, and local text templates.
- Tool composition: use CodeMode to orchestrate multi-step workflows with small Go-like programs.
- Go 1.25 or newer
Install the module:
go get github.com/universal-tool-calling-protocol/go-utcp@latestCreate a providers.json file. This example uses the local text transport, so it needs no server or credentials:
{
"providers": [
{
"provider_type": "text",
"name": "greetings",
"templates": {
"hello": "Hello, {{.name}}!"
}
}
]
}Create main.go:
package main
import (
"context"
"fmt"
"log"
utcp "github.com/universal-tool-calling-protocol/go-utcp"
)
func main() {
ctx := context.Background()
client, err := utcp.NewUTCPClient(ctx, &utcp.UtcpClientConfig{
ProvidersFilePath: "providers.json",
}, nil, nil)
if err != nil {
log.Fatal(err)
}
discovered, err := client.SearchTools("", 10)
if err != nil {
log.Fatal(err)
}
for _, tool := range discovered {
fmt.Printf("%s: %s\n", tool.Name, tool.Description)
}
result, err := client.CallTool(ctx, "greetings.hello", map[string]any{
"name": "UTCP",
})
if err != nil {
log.Fatal(err)
}
fmt.Println(result)
}Run it:
go run .The client loads the provider during construction and qualifies every discovered tool as <provider>.<tool>:
Successfully registered provider greetings (1 tools)
greetings.hello: Text template tool
Hello, UTCP!
- Configure providers. Each provider specifies a transport, a name, and the connection details for discovery and invocation.
- Discover tools.
NewUTCPClientloads configured providers;RegisterToolProvidercan add more at runtime. - Search locally. Discovered schemas are stored in the tool repository and searched without contacting every provider again.
- Call by qualified name.
CallToolandCallToolStreamresolve the provider and dispatch through the matching transport.
ProvidersFilePath accepts any of these JSON root shapes:
- An array of provider objects
- A single provider object
- An object whose
providersfield contains an array or one provider object
Every provider requires a provider_type (the type alias is also accepted). A name is strongly recommended because it becomes the prefix for all of that provider's tools.
provider_type |
Connects to |
|---|---|
http |
UTCP manuals or OpenAPI documents over HTTP/HTTPS |
cli |
Local command-line processes |
sse |
Server-Sent Events endpoints |
http_stream |
Streamable HTTP endpoints |
websocket |
WebSocket services |
grpc |
gRPC services and gNMI telemetry |
graphql |
GraphQL queries and subscriptions |
tcp |
Raw TCP services |
udp |
UDP services |
webrtc |
WebRTC data channels |
mcp |
MCP servers over stdio or HTTP |
text |
Local Go text templates |
Provider-specific fields and runnable client/transport pairs are documented in the examples guide.
Any provider string can reference $NAME or ${NAME}. Values are looked up in this order:
UtcpClientConfig.Variables- Loaders in
UtcpClientConfig.LoadVariablesFrom - Process environment variables
cfg := &utcp.UtcpClientConfig{
ProvidersFilePath: "providers.json",
Variables: map[string]string{
"API_HOST": "api.example.com",
},
LoadVariablesFrom: []utcp.UtcpVariablesConfig{
utcp.NewDotEnv(".env"),
},
}The corresponding provider can keep credentials out of committed JSON:
{
"provider_type": "http",
"name": "catalog",
"http_method": "POST",
"url": "https://${API_HOST}/tools",
"headers": {
"Authorization": "Bearer ${API_TOKEN}"
}
}SearchTools(query, limit) has three useful modes:
| Query | Behavior |
|---|---|
"" |
Return tools from the entire local catalog |
| Exact provider name | Return only that provider's tools |
| Any other text | Rank tools using the configured search strategy |
The default strategy matches the query against tool tags and description words. A non-positive limit returns all matching tools.
The core client interface is deliberately small:
| Method | Purpose |
|---|---|
RegisterToolProvider |
Discover and store tools from a provider |
DeregisterToolProvider |
Remove a provider and its tools |
SearchTools |
List, filter, or rank locally indexed tools |
CallTool |
Invoke a tool and return its result |
CallToolStream |
Invoke a streaming tool and return a StreamResult |
GetTransports |
Access the registered client transports |
NewUTCPClient accepts optional repository and search-strategy arguments. Pass nil for the built-in in-memory repository and tag/description search:
client, err := utcp.NewUTCPClient(ctx, cfg, nil, nil)For transports and tools that support streaming, read from the returned StreamResult until io.EOF and always close it:
stream, err := client.CallToolStream(ctx, "events.watch", args)
if err != nil {
log.Fatal(err)
}
defer stream.Close()
for {
item, err := stream.Next()
if errors.Is(err, io.EOF) {
break
}
if err != nil {
log.Fatal(err)
}
fmt.Println(item)
}Streaming availability depends on the provider transport.
CodeMode lets an LLM or application compose registered tools with interpreted Go-like snippets. Snippets can branch, loop, process intermediate values, and use these helpers:
codemode.CallToolcodemode.CallToolStreamcodemode.SearchTools
Use NewCodeModeUTCP for model-driven orchestration or Execute to run a snippet directly. See the CodeMode guide for the API, execution rules, and examples.
The examples directory contains standalone modules for every supported client and transport. To run the zero-dependency text client:
cd examples/text_client
GOWORK=off go run -mod=mod .Some network examples start their own demo service; others are designed to run alongside the corresponding transport example. Check each example's source for its setup.
git clone https://github.com/universal-tool-calling-protocol/go-utcp.git
cd go-utcp
go test ./...See onboarding.md for the contributor workflow.
go-utcp is licensed under the Mozilla Public License 2.0.
