Skip to main content

toolnexus

PyPI license

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.json is the standard Claude-desktop-style config (mcpServers / servers / mcp keys all accepted).
  • skills/ is a folder of **/SKILL.md files, loaded on demand through one skill tool.
  • Remote MCP headers values 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-only list_mcp_resources / read_mcp_resource.
  • create_client(wait_for_ctx=fn(cancel, request)) — wait_for that ends with the turn.
  • before_tool events carry hints (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} adds bash_output / kill_shell, websearch, notebook_edit (call BackgroundShells.close_all() on shutdown). grep takes context / before / after; read and grep report truncation in metadata.
  • compactor(..., on_compact=fn) gets a CompactInfo after each compaction.
  • agent.as_tool_with(lambda ctx: {"hooks": ..., "wait_for": ...}) runs a sub-agent under the parent's gates; ctx.call_id tells 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).

Media generation

Generate images, video and speech (plus transcription, voice cloning and talking-head video) from a JSON style instead of provider code. Async jobs are started and polled for you. The same verbs become agent tools (generate_image, generate_video, generate_speech, …), and whatever they produce lands in RunResult.files. examples/media-styles/a2e.json ships first and reads A2E_API_KEY at call time. Its result URLs expire in 3–30 days, so save what you keep. SPEC §7G; guide: https://muthuishere.github.io/toolnexus/media/.

from toolnexus import load_media_style, create_media, create_toolkit

media = create_media(load_media_style("examples/media-styles/a2e.json"),
                     on_progress=lambda p: print(p["verb"], p["status"]))
img = await media.image("a red fox in fresh snow", width=1024, height=768)
vid = await media.video("the fox blinks", image=img.files[0].url, seconds=5)

tk = await create_toolkit(media=media)               # the agent gets generate_image, …
res = await agent.run("Draw a fox, then animate it.", tk)
for f in res.files:
    print(f["from"], f["mimeType"], f["url"])

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

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for toolnexus 0.24.0
File Size Uploaded Provenance Actions
toolnexus-0.24.0.tar.gz 372.4 kB With provenance Details for toolnexus-0.24.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for toolnexus 0.24.0
File Interpreter ABI Platform Actions
toolnexus-0.24.0-py3-none-any.whl Python 3 none any Details for toolnexus-0.24.0-py3-none-any.whl

Total release size: 570.8 kB

Release files / toolnexus-0.24.0.tar.gz

Download URL toolnexus-0.24.0.tar.gz
Size 372.4 kB
Tags Source
SHA-256 checksum
How to use checksums
0f54a3223b6f69635dfbdf73067e34ae2268e44c1b35a84ff7b9bfc7eab32073
BLAKE2b-256 checksum
How to use checksums
506bf727462a12cce7ae94e550ce67699f8ff714646ca2ed27ec218ec7649dfe
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 3, 2026.

Transparency log

Release files / toolnexus-0.24.0-py3-none-any.whl

Download URL toolnexus-0.24.0-py3-none-any.whl
Size 198.4 kB
Tags Python 3
SHA-256 checksum
How to use checksums
c23863851f120892252c77c16bfd6c865c2954ffdc5fe1ae8c4ff2c64737cb99
BLAKE2b-256 checksum
How to use checksums
a30fdaa7c9932759e853e7d00c123e5af13c65214fa714969e7108fbed9a3843
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 3, 2026.

Transparency log

Security for toolnexus 0.24.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.

Select from 2 files

Attestation for toolnexus-0.24.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 3, 2026.

Transparency log

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

Release history Release notifications | RSS feed

This release

0.24.0 This release

2 release files Full provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page