toolnexus
Build an agent in a few lines. Point at an mcp.json and a skills/ folder, call run(),
and you have a working agent — MCP servers, agent skills, your own functions, and HTTP endpoints
unified as one tool set, driving any LLM.
Right-sized. Not a framework (no builders, no config to wade through), not a toy that falls over the moment you need streaming or a retry. Everything a real agent needs — the loop, hooks, streaming, retries, memory — and nothing it doesn't.
The Python port of toolnexus — the same library,
byte-identical, also in JavaScript, Go, Java, C#, Elixir, Clojure and Rust. Built on the official
MCP Python SDK (the mcp package). Python ≥ 3.11.
Install
pip install toolnexus
Quick start
The 10 built-in tools (bash, read, grep, webfetch, …) are on by default, so the model
can act with no mcp.json and no skills folder:
import asyncio
from toolnexus import create_toolkit, create_client
async def main():
tk = await create_toolkit() # built-in tools, on by default
agent = create_client(
base_url="https://openrouter.ai/api/v1", style="openai",
model="deepseek/deepseek-chat", # any OpenRouter/OpenAI/Anthropic model
)
res = await agent.run("List the files here, then count them.", tk)
print(res.text)
await tk.close()
asyncio.run(main())
Add real tool sources by pointing at them:
tk = await create_toolkit(mcp_config="mcp.json", skills_dir=["skills"])
mcp.jsonis the standard Claude-desktop-style config (mcpServers/servers/mcpkeys all accepted).skills/is a folder of**/SKILL.mdfiles, loaded on demand through oneskilltool.- Remote MCP
headersvalues expand${ENV_VAR}at call time and are never logged.
API key by host (breaking in 0.22.0)
With no api_key, the client reads only the env var that belongs to the base_url host, over
https: api.openai.com → OPENAI_API_KEY, api.anthropic.com → ANTHROPIC_API_KEY,
openrouter.ai → OPENROUTER_API_KEY. Any other host — a local server, a proxy, plain http://
— needs api_key or api_key_env (the name of the env var to read); otherwise the first
request raises No API key. There is no cross-provider fallback any more, so a key is never sent
to a host it does not belong to.
local = create_client(base_url="http://localhost:11434/v1", style="openai",
model="llama3", api_key="YOUR_KEY_HERE")
proxy = create_client(base_url="https://llm-proxy.internal/v1", style="openai",
model="gpt-4o-mini", api_key_env="MY_PROXY_KEY")
list_models(base_url=..., api_key_env=...) follows the same rule and returns ModelInfo(id, name, context_length) sorted by id.
Prompt caching and tool tiers
from toolnexus import PromptCacheOptions, ToolTierOptions
client = create_client(
base_url="https://api.anthropic.com", style="anthropic", model="claude-sonnet-4-5",
prompt_cache=PromptCacheOptions(long_ttl=True), # openai: PromptCacheOptions(key="session-1")
tool_tiers=ToolTierOptions(core=["read", "grep"], deferred=["deploy"]),
)
res = await client.run("…", tk)
res.usage.get("cache_read_tokens"), res.usage.get("cache_write_tokens")
client.tool_tier_plan(tk) # {"read": "core", "bash": "listed", "deploy": "deferred", …}
client.reset_tool_tiers(tk) # rebuild the frozen plan at a session boundary
prompt_cache marks the stable prefix (anthropic cache_control breakpoints on the last tool,
the system prompt and the latest message; openai prompt_cache_key). tool_tiers sends only
the core schemas plus tool_search and call_tool; the model loads the rest on demand, and the
tools array (so the cached prefix) never changes. Under anthropic the real context size is
prompt_tokens + cache_read_tokens + cache_write_tokens. Runnable, hermetic:
examples/prompt_cache_tool_tiers.py.
Host hooks (0.22.0)
load_mcp(cfg, on_server=fn)reports each MCP server the moment it connects, fails or is disabled;source.resource_tools()adds read-onlylist_mcp_resources/read_mcp_resource.create_client(wait_for_ctx=fn(cancel, request))—wait_forthat ends with the turn.before_toolevents carryhints(ToolHints(read_only, destructive, open_world)), so a permission gate can allow read-only tools and deny or ask (pending(...)) for the rest.- Opt-in builtins:
builtins={"background": BackgroundShells(), "web_search": fn, "notebook_edit": True}addsbash_output/kill_shell,websearch,notebook_edit(callBackgroundShells.close_all()on shutdown).greptakescontext/before/after;readandgrepreport truncation inmetadata. compactor(..., on_compact=fn)gets aCompactInfoafter each compaction.agent.as_tool_with(lambda ctx: {"hooks": ..., "wait_for": ...})runs a sub-agent under the parent's gates;ctx.call_idtells concurrent tool calls apart.
Simple judgments
A thin layer over any Classifier (SPEC §8B); the wire request is unchanged.
from toolnexus import ask, gate, noul, choice, state, Rule, Policy
st = state("You are Donkey Kong, you want to win.", {"message_received": msg})
a = await ask(classifier, st, [
noul("is_appropriate", "Does message_received contain inappropriate language?"),
noul("does_this_help", "Does message_received help donkey kong win?"),
])
a["is_appropriate"].band # "yes" | "no" | "uncertain" (Bands(0.30, 0.70), exclusive)
a["does_this_help"].value() # the probability
out = await gate(classifier, st, qs, Policy([Rule("fixable", "fail", below=0.3),
Rule("component", "skip_to", is_="pricing", target="fix-pricing")]))
out.escalated, out.request # unsure / missing -> a §10 Request(kind="input")
Also: context(ctx, message, extra), score(...), Tape (record live, replay offline by call
name), static_classifier((state, questions, response), ...), questions_wire(...), and
classifier.evaluate_batch(states, questions) (state order, fail-closed, 16 in flight).
Documentation
Everything else — the full surface, with runnable examples — lives on the docs site:
| Start here | Quickstart · Concepts · Install |
| Tool sources | MCP · Skills · Native · HTTP · Built-ins · A2A |
| The loop | Streaming · Memory · Suspension · Resilience · Observability |
| Agents | Sub-agents & teams · Personas · Typed decisions |
| API reference | Python |
| Cookbook | Zero to agent · MCP servers · Agent skills · Judge |
Contract across all eight ports: SPEC.md.
Metadata
Release files for toolnexus 0.23.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| toolnexus-0.23.0.tar.gz | 354.6 kB | With provenance | Details for toolnexus-0.23.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| toolnexus-0.23.0-py3-none-any.whl | Python 3 | none | any | Details for toolnexus-0.23.0-py3-none-any.whl |
Total release size: 542.3 kB
Release files / toolnexus-0.23.0.tar.gz
| Download URL | toolnexus-0.23.0.tar.gz |
|---|---|
| Size | 354.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
2ee8598a78f8065a0caddb0898530e610c7a136265208d0c603eff7192a87785
|
|
BLAKE2b-256 checksum How to use checksums |
bfa6beb3c5c382415c36e3d51a0f51da7dadebe62363eeb2367bee2b625795be
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 2, 2026.
Transparency logRelease files / toolnexus-0.23.0-py3-none-any.whl
| Download URL | toolnexus-0.23.0-py3-none-any.whl |
|---|---|
| Size | 187.7 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
92ccc628c95071457eea5f9c0f85303a6b116a53baaa847edbe3d6d9a177054a
|
|
BLAKE2b-256 checksum How to use checksums |
da0dfa4ef3e00563263a9285c24b305e0bfeaefd6a49769eb7e2cc76e86cb187
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 2, 2026.
Transparency logSecurity for toolnexus 0.23.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
Both files in this release have been uploaded with provenance metadata.
Attestation for toolnexus-0.23.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 2, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.