toolnexus
v0.21.0
Published
Dynamic MCP servers + agent skills as uniform tools for any LLM (opencode-style).
Maintainers
Readme
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, advisors, runnables, config graphs), 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 JS/TypeScript port of toolnexus — the same
library, byte-identical, also in Python, Go, Java, C#, Elixir and Clojure. Built on
@modelcontextprotocol/sdk (the MCP SDK opencode uses).
Install
npm install toolnexusQuick start
Built-in tools are on by default, so an empty toolkit can already act:
import { createToolkit, createClient } from "toolnexus"
const tk = await createToolkit() // 10 built-in tools, on by default
const agent = createClient({
baseUrl: "https://openrouter.ai/api/v1", // any OpenAI- or Anthropic-style endpoint
style: "openai", // or "anthropic"
model: "openai/gpt-4o-mini",
})
const { text } = await agent.run("What files are in this folder?", { toolkit: tk })
console.log(text)Add real tool sources by pointing at them:
const tk = await createToolkit({ mcp: "mcp.json", skills: ["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.
Simple judgments
A thin layer over any Classifier (SPEC.md §8B); the wire request is byte-identical to
hand-written maps.
import { createClassifier, judge, State, ask, gate } from "toolnexus"
const c = createClassifier() // reads TYPESAFE_API_KEY by name at call time
const d = await ask(c, State("You are Donkey Kong, you want to win.", { message_received: "jump off the stage" }), [
judge.noul("is_appropriate", "Does `message_received` contain inappropriate language?"),
judge.noul("does_this_help", "Does `message_received` help donkey kong win?"),
])
d.is_appropriate.band // "yes" | "no" | "uncertain" (cut-points 0.30 / 0.70, exclusive)
d.does_this_help.value() // the one number
const out = await gate(c, state, questions, [
{ question: "fixable", below: 0.3, action: "fail" },
{ question: "component", is: "pricing", action: "skip_to", target: "fix-pricing" },
])
// unsure or missing answer -> { action: "needs_input", escalated: true, request: <§10 input Request> }- The role goes in the state (
State(role, data)), never into question text; each question names the state field it judges. decide(c, state, questions, { rules, default, bands, skipUncertain })— aPolicywith a declared fall-through (emptydefaultescalates "no rule fired").new Tape(live).classifier("plan")records;Tape.replay(entries).classifier("plan")replays offline.staticClassifier(recorded)— one-line hermetic classifier;c.evaluateBatch(states, questions)— same questions over many states, in order, fail-closed, 16 in flight.- JS naming: the named builders live under
judge.(barenoul/choice/scorestay the §8B wire builders); a choice answer keeps itschoicestring field, so the picked-option method ispick(). - Batteries (§8B Batteries):
ToolGuardClassifier,ToolRelevanceClassifier,SkillRelevanceClassifier,ToolResultFilterClassifier,IsCompleteClassifier,ContentGuardClassifier(all take a requiredonError: "open" | "closed"),AgentRouterClassifierand the opt-inModelRouterClassifier(c, [{ id, description }]). Methodscheck/select/filter/pick;asHook(next?)plugs the guard, relevance, filter, content and model batteries intohooks. AbeforeLLMhook may return{ model }to send another model for that turn only.
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 | JavaScript | | Cookbook | Zero to agent · MCP servers · Agent skills · Judge |
Contract across all seven ports: SPEC.md.
