Liking cljdoc? Tell your friends :D

toolnexus

We wrote one agent SDK and ported it, by hand, to eight languages — JS, Python, Go, Java, C#, Elixir, Clojure, Rust — until the same conformance suite passes byte-identical output in every one of them.

CI npm PyPI NuGet Maven Central Hex.pm Go Reference license Discord

Your LLM, with MCP tools and agent skills built in — in 3 lines, in 8 languages.

Point toolnexus at an mcp.json and a skills/ folder and you get a working agent: the tool-calling loop, skills injection, six unified tool sources, and conversation memory — all included. Vendor-neutral, byte-identical across JavaScript · Python · Go · Java · C# · Elixir · Clojure — the Clojure port is one .cljc tree that runs on both the JVM and cljgo, held to the same full tier as the other six and published to Clojars like the rest.

Right-sized. Not a framework — no builders, advisors, runnables, or config to wade through. Not a toy that falls over the moment you need streaming or a retry. Exactly what a real agent needs — MCP, skills, native + HTTP + built-in tools, remote A2A agents, in-process sub-agents, the loop, hooks, streaming, retries, memory — and nothing it doesn't.

npm i toolnexus                                   # JS / TypeScript
pip install toolnexus                             # Python
go get github.com/muthuishere/toolnexus/golang    # Go
dotnet add package Toolnexus                       # C#
{:toolnexus, "~> 0.23"}                             # Elixir (mix.exs deps)
# Java (Maven): io.github.muthuishere:toolnexus:0.23.0
# Clojure (deps.edn): net.clojars.muthuishere/toolnexus {:mvn/version "0.23.0"} — JVM and cljgo
cargo add toolnexus                                # Rust (crates.io)

The insight (borrowed from opencode): MCP server tools, agent skills, your own functions, remote HTTP endpoints, the built-in shell/file tools, remote A2A agents, and in-process sub-agents are all the same thing to an LLM — a named, described, schema'd callable. toolnexus unifies every tool source behind one Tool interface and drives any model with them.

From zero to agent in 3 steps

1. Point at an mcp.json — the standard Claude-desktop-style config:

{
  "mcpServers": {
    "fs":   { "command": ["npx", "-y", "@modelcontextprotocol/server-filesystem", "/data"] },
    "acme": { "type": "remote", "url": "https://api.acme.com/mcp",
              "headers": { "Authorization": "Bearer ${ACME_TOKEN}" } }
  }
}

2. Point at a skills/ folder — **/SKILL.md files with YAML frontmatter, loaded on demand through one skill tool (progressive disclosure).

3. Run.

import { createToolkit, createClient } from "toolnexus"

const tk = await createToolkit({ mcp: "mcp.json", skills: ["skills"] })
const agent = createClient({ baseUrl: "https://openrouter.ai/api/v1", style: "openai", model: "openai/gpt-4o-mini" })

const { text } = await agent.run("Refund order 4021 using the policy.", { toolkit: tk })

The key above comes from OPENROUTER_API_KEY, because the host is openrouter.ai. Since 0.22.0 a key is only ever sent to the host it belongs to: with no apiKey, the client reads only the env var matching the baseUrl host (openrouter.ai → OPENROUTER_API_KEY, api.openai.com → OPENAI_API_KEY, api.anthropic.com → ANTHROPIC_API_KEY, https only). A local server or a proxy needs apiKey or apiKeyEnv (the name of the env var to read):

createClient({ baseUrl: "http://localhost:11434/v1", style: "openai", model: "llama3", apiKey: "unused" })
createClient({ baseUrl: "https://llm.internal/v1", style: "openai", model: "m", apiKeyEnv: "INTERNAL_LLM_KEY" })

The same three steps, in the same shape, in every port — see your language's API reference below.

New in 0.22

  • Prompt caching and tool tiers. promptCache marks the stable prefix for the provider (Anthropic cache breakpoints, OpenAI prompt_cache_key) and usage reports cacheReadTokens / cacheWriteTokens. toolTiers sends only your core tool schemas and lets the model load the rest through tool_search / call_tool, so the tools array — and the cached prefix — never changes mid-session. Recipe.
  • API key by host (breaking for local/custom hosts, above). Recipe.
  • Host hooks: a cancellable waitFor, per-server MCP readiness (onServer), MCP resource tools, tool hints (readOnly / destructive / openWorld) on beforeTool for permission gates, opt-in builtins (background shells, websearch, notebook_edit), grep context, compaction notices, asToolWith for subagents under the parent's gates, callId on the tool context, and listModels. Recipe.
  • Go and Rust only, for now: ACP host hooks (OnPermission, OnUpdate, GenerateCtx, GenerateStream). Recipe.

Per-port spellings are in CHANGELOG.md and each port's API reference.

The eight ports

languagepackageAPI reference
JavaScript / TypeScripttoolnexus (npm)api/javascript
Pythontoolnexus (PyPI)api/python
Gogithub.com/muthuishere/toolnexus/golangapi/go
Javaio.github.muthuishere:toolnexusapi/java
C# / .NETToolnexus (NuGet)api/csharp
Elixirtoolnexus (Hex)api/elixir
Clojurenet.clojars.muthuishere/toolnexusapi/clojure
Rusttoolnexus (crates.io) · rust/api/rust

Each port's own README covers install and a quick start; everything beyond that is on the docs site.

Documentation

Start hereQuickstart · Concepts · Install
Tool sourcesMCP · Skills · Native · HTTP · Built-ins · A2A
The loopStreaming · Memory · Suspension · Resilience · Observability
AgentsSub-agents & teams · Personas · Typed decisions (Classifier)
CookbookZero to agent · MCP servers · Agent skills · Prompt cache & tool tiers · API key by host · Host hooks · Judge
NumbersPerformance · Comparison · Judge, live

The contract

SPEC.md is the cross-language contract — §0 is the one-page conformance suite every port must satisfy. A behaviour change lands in all eight ports or it is not done; the shared fixtures in examples/ are what "identical" is measured against.

Changes go through OpenSpec before code. See CLAUDE.md for the workflow, CHANGELOG.md for what shipped, and PUBLISHING.md for how each port is released.

Community

Join the AgentNexus Discord.

License

MIT — see LICENSE.

Can you improve this documentation?Edit on GitHub

cljdoc builds & hosts documentation for Clojure/Script libraries

Keyboard shortcuts
Ctrl+kJump to recent docs
←Move to previous article
→Move to next article
Ctrl+/Jump to the search field
× close