UTCP for Python: Calling APIs Directly From an Agent, With No Middleware Layer
Official python implementation of UTCP. UTCP is an open standard that lets AI agents call any API directly, without extra middleware.
At a glance
- What is it?
- The official Python implementation of the Universal Tool Calling Protocol splits a core client from per-protocol plugins, so an agent can call HTTP, CLI, MCP or WebSocket tools through one interface. The design is sound for multi-protocol setups; the 1.0.0 migration is breaking and the tool-discovery story is thinner than the README suggests.
- Who is it for?
- Adopt python-utcp if you already need several transports behind one client and can pin the 1.0.0 API from the start. Do not adopt it if your entire tool surface is a single HTTP API, or if you depend on the 0.x provider model, since the README states that provider is now call_template and provider_type is now call_template_type.
- Can I use it commercially?
- Yes, with conditions. MPL-2.0 is a weak copyleft licence: you can use it inside commercial and closed-source software, but if you distribute changes to its own files, you must publish those changes under the same licence.
- Is it still maintained?
- Yes. The repository last received commits 9 days ago.
- What is it written in?
- Mainly Python, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 15, 2026, and from our analysis. They are not legal advice.
DEEP OPEN-SOURCE ANALYSIS
The middleware problem UTCP is trying to remove
Most agent stacks insert a server between the model and the API it wants to call. That server holds credentials, translates schemas, and exposes a tool list. The UTCP README frames the protocol as an alternative: an agent calls any API directly, and the protocol is the contract that makes the call possible without that extra hop. The stated emphasis is on scalability across large numbers of tools and providers, extensibility through plugins, and interoperability with services that already exist. The target reader is a Python developer wiring an LLM agent to more than one kind of backend. If you have exactly one REST endpoint, the protocol layer is overhead you will pay for and never use. The pitch becomes relevant when the same agent must reach an HTTP API, a local command-line tool, and an MCP server, and you do not want three unrelated client libraries in the same process.
Core client, protocol plugins, and where the Pydantic models live
The repository is split into a core package and a plugins tree. Under core/ sits the utcp package: Pydantic models for Tool, CallTemplate, UtcpManual and Auth, the UtcpClient class, plugin interfaces for protocols, repositories and search, plus default tool storage and search strategies. Under plugins/communication_protocols/ sit the transports, each published as its own distribution: utcp-http for HTTP, REST, SSE, streaming and OpenAPI, utcp-cli for command-line tools, utcp-mcp for the Model Context Protocol, utcp-text for file-based tools, and utcp-websocket. The socket and gql directories are present but marked in progress. The data flow implied by the README is: you construct a client with a configuration containing manual_call_templates, each template names a call_template_type and a location such as a url, the client resolves a tool name to a template, and the plugin for that type performs the call. Tool names are namespaced, as in my_api.get_data, so the template name is the prefix. The plugin boundary is the interesting design choice here. Adding a transport does not require touching the core library, which is what the README means by extensible. The cost is that the core alone is inert: without at least one plugin installed, there is nothing for the client to dispatch to.
Installing the core plus only the plugins you need
The README's quick start installs the core and the HTTP plugin together, which it calls the most common combination: pip install utcp utcp-http. Additional transports are separate installs, for example pip install utcp-cli utcp-mcp utcp-text. For working against a clone, the README gives pip install -e "core[dev]" for the core with development dependencies, and pip install -e plugins/communication_protocols/http for a specific plugin in editable mode. Client construction takes a dict, which the README shows with a single manual_call_templates entry: a name of my_api, a call_template_type of http, and a url pointing at an endpoint such as https://api.example.com/utcp. Calls are then made by qualified name, as in await client.call_tool("my_api.get_data", {"id": "123"}). The README also states that UtcpClient is initialized with a UtcpClientConfig object, a dict, or a path to a JSON file containing the configuration. That JSON-file option is the one worth noting for deployment, because it moves the template list out of source code. What the README does not show is the shape of that JSON file or the full set of keys on UtcpClientConfig; you would need core/README.md for that.
The 1.0.0 migration is breaking, and the README says so
Version 1.0.0 renamed things rather than adding alongside them. The README's migration list is explicit: provider becomes call_template, provider_type becomes call_template_type, and the http_stream call_template_type becomes streamable_http. The providers_file_path option is gone; instead of pointing at a file path, you pass a list of manual_call_templates inside the configuration. Imports move with the plugin split, and the README gives one concrete example: from utcp.client.transport_interfaces.http_transport import HttpProvider becomes from utcp_http.http_call_template import HttpCallTemplate. On search, the new default strategy is named TagAndDescriptionWordMatchStrategy, and the README says no changes are needed unless you implemented a custom strategy. The migration section then stops mid-sentence on tool naming, so the repository snapshot does not tell you how tool names are rewritten in 1.0.0. That is a real gap if you have persisted tool names in prompts or logs. Treat any 0.x code as needing a rename pass, not a version bump.
Where UTCP is the wrong tool
The plugin architecture is also its main constraint. Each transport is a separate package with its own release cadence, so your dependency list grows with every protocol you support, and a fix in the HTTP plugin does not ship with the core. The socket and GraphQL plugins are marked in progress in the README's own table, which means a GraphQL backend is not a supported target today; you would be writing a plugin rather than using one. Discovery is the other soft spot. The README describes default tool storage and search strategies in the core, and names TagAndDescriptionWordMatchStrategy as the default, but it does not document how a UtcpManual is fetched or refreshed at runtime, nor what happens when two templates expose a tool with the same name. For a small, fixed tool set that you can enumerate by hand, that ambiguity does not matter. For a system that discovers tools dynamically across many providers, it is the part you would have to read the core source to trust. And if your agent only ever talks to one HTTP API, the direct-call premise is already satisfied by an ordinary HTTP client; UTCP adds a schema layer and a plugin dependency for no gain.
How this differs from MCP, and why the plugin exists
The Model Context Protocol is the obvious comparison, and the repository makes it directly: it ships utcp-mcp as a stable plugin and tags the project with model-context-protocol. The difference in approach is architectural. MCP defines a server that exposes tools to a client, so the integration work sits in a process you run and maintain. UTCP's README instead describes calling APIs directly without extra middleware, with the protocol defining how tools are declared and invoked. The practical consequence is that UTCP can reach a plain HTTP endpoint, a local CLI binary, or a text-file tool through the same client interface, because those are plugins rather than servers. It can also reach an MCP server, which is why the MCP plugin exists: the two are not mutually exclusive in this design. If you already run MCP servers and they work, UTCP does not replace them; it gives you one client that can also speak to the things MCP does not cover. That is the honest framing, and it is narrower than a protocol war.
Licence, maintenance, and what to check before committing
The repository is licensed under MPL-2.0. That is a file-level copyleft licence: modifications to covered files stay under MPL-2.0, while larger works that combine the library with other code can be distributed under other terms. This is not legal advice; if you are embedding the client in a product, have counsel read the licence text in LICENSE rather than a summary. Maintenance signals visible in the snapshot: the default branch is main, the repository is not archived, and the last push is dated 2026-09-06. No releases were retrieved, so there is no changelog to read for upgrade cost; you would track breaking changes through the README's migration section and the per-plugin READMEs. The upgrade cost is structural rather than incidental. Because transports are separately versioned packages, a core upgrade can require coordinated plugin upgrades, and the 0.x to 1.0.0 rename shows that the project is willing to make breaking renames. Before you commit, read core/README.md for the UtcpClientConfig schema and the search strategy interface, and read the README for the one plugin you actually need. Verify how tool names are composed, since the top-level README cuts off there, and confirm whether the socket or GraphQL plugin has left in-progress status if either is on your list.
Editorial conclusion
Adopt python-utcp if you already need several transports behind one client and can pin the 1.0.0 API from the start. Do not adopt it if your entire tool surface is a single HTTP API, or if you depend on the 0.x provider model, since the README states that provider is now call_template and provider_type is now call_template_type. Before writing code, verify the current core/README.md and the plugin README for the transport you need, because the top-level README truncates mid-sentence on tool naming and the socket and GraphQL plugins are still marked in progress.
Community notes