Command Palette

Search for a command to run...

UnylyUnyly
Весь каталог

Toolnexus

БесплатноНе проверен

Dynamic MCP servers + agent skills as uniform tools for any LLM (opencode-style).

GitHubEmbed

Описание

Dynamic MCP servers + agent skills as uniform tools for any LLM (opencode-style).

README

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

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

Point toolnexus at an mcp.json and a skills/ folder and you get a working agent: the tool-calling loop, skills injection, five unified tool sources, and conversation memory — all included. Vendor-neutral, byte-identical across JavaScript · Python · Go · Java · C# · Elixir.

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, 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.9"}                             # Elixir (mix.exs deps)
# Java (Maven): io.github.muthuishere:toolnexus:0.9.5

The insight (borrowed from opencode): MCP server tools, agent skills, your own functions, remote HTTP endpoints, and the built-in shell/file tools 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.

   SOURCES                          TOOLKIT                          ANY LLM
 ┌──────────────────┐
 │ MCP servers      │──┐
 │  (mcp.json)      │  │     ┌──────────────────────────┐      ┌──────────────┐
 ├──────────────────┤  │     │  uniform Tool[] registry  │─────▶│ OpenAI-style │
 │ Agent skills     │  │     │  • tools() / execute()    │      ├──────────────┤
 │  (SKILL.md)      │  ├────▶│  • skillsPrompt()         │─────▶│ Anthropic    │
 ├──────────────────┤  │     │  • toOpenAI/Anthropic/    │      ├──────────────┤
 │ Native fns       │  │     │    Gemini()               │─────▶│ Gemini       │
 │  (defineTool)    │  │     └────────────┬─────────────┘      └──────────────┘
 ├──────────────────┤  │                  ▼
 │ HTTP / OpenAPI   │  │     ┌───────────────────────────────────┐
 │  (url+headers)   │  ├────▶│ UNIFIED CLIENT (host loop):        │
 ├──────────────────┤  │     │ baseURL + style + model → run()    │
 │ Built-in tools   │──┘     │ inject skills → call → exec → …    │
 │  (10, on by dflt)│        │ + memory: ask() / ConversationStore│
 └──────────────────┘        └───────────────────────────────────┘
        + remote A2A agents (each skill → a tool) · or serve your toolkit as an A2A agent

From zero to agent in 3 steps

No framework, no glue. Two files and one call — and your LLM now has MCP tools, agent skills, and 10 built-in shell/file tools built in, something no other library hands you as a drop-in.

1. Add an MCP config filemcp.json:

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

2. Add a skills folderskills/process-refund/SKILL.md:

---
name: process-refund
description: Use when a customer asks for a refund. Walks the policy + steps.
---
# Refund workflow
1. Verify the order …

3. Call any LLM — MCP + skills + built-ins are already in it:

const tk = await createToolkit({ mcpConfig: "./mcp.json", skillsDir: "./skills" })

const agent = createClient({
  baseUrl: "https://openrouter.ai/api/v1",
  style: "openai",
  model: "openai/gpt-4o-mini",
})

const { text } = await agent.run("Refund order 1234 for the customer.", { toolkit: tk })
// The model sees every MCP server tool, a `skill` tool, and the built-in toolset — the skills
// catalog is injected into its system prompt. It loads `process-refund` and calls tools itself.

That's the whole thing. createToolkit() alone (no config) still gives you a working agent — the 10 built-in tools are on by default. Bring your own loop instead? Use tk.toOpenAI() / toAnthropic() / toGemini() for the schema and tk.execute(name, args) to run a call. The same three steps work in Python, Go, Java, C#, and Elixir.

Why toolnexus

The individual pieces — MCP, agent skills (SKILL.md), native tools, HTTP tools — each landed in the big frameworks during 2026: Spring AI, LangChain (Deep Agents) and Google ADK now do most of them. What none of them combine is every tool source behind one interface, byte-identical across six languages, vendor-neutral, in a small à-la-carte library:

  • Six languages, one behavior — JS · Python · Go · Java · C# · Elixir, pinned by a shared SPEC.md so they stay byte-compatible (the skill-loader output is byte-for-byte). First-class Go and C# — where Spring AI (Java-only) and LangChain (no Go/C#) don't reach.
  • Vendor-neutral — a plain base URL + openai/anthropic style; not tied to one provider (unlike Gemini-centric ADK).
  • A library, not a platform — à la carte: use just the MCP host, or add skills / native / HTTP / built-ins / A2A / the host loop as you like. No runtime, no orchestration server.
  • Everything unified — MCP servers, agent skills, native functions, HTTP/REST, built-in tools, and remote A2A agents as one Tool registry, for any model.

Each language builds on the most popular MCP SDK for that ecosystem — nothing is reimplemented from scratch:

Lang Dir MCP SDK
JS/TS js/ @modelcontextprotocol/sdk (same as opencode)
Python python/ mcp (modelcontextprotocol/python-sdk)
Go golang/ github.com/mark3labs/mcp-go
Java java/ io.modelcontextprotocol.sdk:mcp (official)
C# csharp/ ModelContextProtocol (official)
Elixir elixir/ in-house MCP client (OTP-supervised, no SDK)

The language-independent behavior is pinned in SPEC.md so all six stay byte-compatible (especially the skill loader output).

Five tool sources, one interface

Everything below surfaces as the same uniform Tool — one registry, any model.

# Source Declare with What you get
1 MCP servers mcp.json Claude-desktop superset (mcpServers/servers/mcp); local stdio + remote streamable-HTTP/SSE; ${ENV} header auth; one bad server is isolated, never fatal.
2 Agent skills skills/**/SKILL.md One skill tool loads each on demand (progressive disclosure) + a system-prompt catalog. Same format as Claude/opencode.
3 Native functions defineTool A plain function → a tool; schema inferred from type hints / struct tags.
4 HTTP / REST httpTool Declare an endpoint; {ph} URL substitution, ${ENV} header expansion; OpenAPI import (best-effort).
5 Built-in tools on by default 10 opencode shell/file tools so an agent can act with zero wiring (see below).

Registering your own native + HTTP tools is one call:

const tk = await createToolkit({ mcpConfig: "./mcp.json", skillsDir: "./skills" })

tk.register(
  defineTool({
    name: "add",
    description: "Add two numbers",
    inputSchema: {
      type: "object",
      properties: { a: { type: "number" }, b: { type: "number" } },
      required: ["a", "b"],
    },
    run: ({ a, b }) => `${a + b}`,
  }),
  httpTool({
    name: "get_post",
    description: "Fetch a post",
    method: "GET",
    url: "https://jsonplaceholder.typicode.com/posts/{id}",
    inputSchema: {
      type: "object",
      properties: { id: { type: "number" } },
      required: ["id"],
    },
  }),
)

À la carte. Each source is usable on its own. Want only an MCP host — parse mcp.json, connect, get the tools, like the MCP-only libraries do? Use just loadMcp / load_mcp / LoadMcp (no skills, no loop). Everything else is opt-in on top.

Built-in tools (on by default)

toolnexus ships opencode's default toolset — 10 built-in tools (bash, read, write, edit, grep, glob, webfetch, question, apply_patch, todowrite, with names + input schemas matching opencode) so an agent can act with zero wiring. They surface in the tool schema (toOpenAI/toAnthropic/toGemini) like MCP tools — not injected into the system prompt.

The source is on by default with two levels of control:

  • Global togglecreateToolkit({ builtins: false }) / create_toolkit(builtins=False) / Options{ Builtins: false } / .builtins(false) turns the whole source off.
  • Per-tool mapbuiltins: { tools: { bash: false } } drops individual tools on the all-on baseline (other tools stay on, unknown names ignored; a whole-source-off still wins).

Because bash/write/edit/apply_patch run commands and mutate the filesystem, these switches are the off-switch for locked-down hosts.

A2A agents — call remote agents, or be one

Beyond the five local sources: agent-to-agent. Point the toolkit at a remote A2A agent and each of its skills becomes a tool (named <agent>_<skill>, source "a2a") — an agent is just another tool source. The same toolkit can serve itself as an A2A agent, so other agents (toolnexus or not) can call it. It's a genuine, minimal subset of real A2A (verified against a2a-python): JSON-RPC 2.0, the Agent Card at /.well-known/agent-card.json, SendMessage → poll GetTask. No streaming / push / auth in v1.

// outbound: a remote agent's skills become tools
const tk = await createToolkit({
  agents: [agent({ card: "https://peer.example.com/.well-known/agent-card.json" })],
})
await tk.addAgent("https://other.example.com/.well-known/agent-card.json") // or at runtime

// inbound: serve this toolkit as an agent — the card is built from your SKILL.md skills, not raw tools
const llm = createClient({
  baseUrl: "https://openrouter.ai/api/v1",
  style: "openai",
  model: "openai/gpt-4o-mini",
})
const handle = await tk.serve("127.0.0.1:0", {
  client: llm,
  a2a: { name: "my-agent", store: "memory" },
})

Both directions exist in all six ports (agent(...) / Agent{...}, an agents config block, and serve / ServeAsync). Served tasks persist through a pluggable TaskStore (in-memory default, "file:<dir>", or your own). See each port's README for the full option set.

Serve as an MCP server — be a gateway

The other inbound edge: expose your whole toolkit as an MCP server, so any MCP client (Claude Desktop, an IDE, another agent) can call its tools. Aggregate N MCP servers + skills + your own functions behind one toolkit, then re-expose the union as one MCP server — a universal MCP gateway. Unlike A2A (which advertises skills and runs the client loop), the MCP client is the LLM host, so each tools/call dispatches straight to Tool.execute — no client, no tasks, no store.

// streamable-HTTP — an embeddable MCP server at POST /mcp, beside any A2A routes:
const srv = await tk.serve("127.0.0.1:0", { mcp: { name: "my-gateway" } })   // connect at srv.url + "/mcp"

All six ports ship the streamable-HTTP MCP server (the /mcp endpoint on serve), built on each port's existing MCP SDK in server mode. tools/list advertises every tool (name verbatim, inputSchema = the tool's parameters). A stdio transport (for local clients like Claude Desktop) is a planned follow-up. See SPEC.md §7C.

Conversations & memory

The host loop remembers a thread for you. ask(prompt, { toolkit, id }) loads that id's transcript from a ConversationStore, runs the loop with it as history, and saves the updated transcript back — so the next ask with the same id continues the conversation. No id ⇒ a stateless one-shot (identical to run).

const agent = createClient({ baseUrl, style: "openai", model })   // in-memory store by default
await agent.ask("Book me a flight to Berlin.", { toolkit: tk, id: "user-42" })
await agent.ask("Actually, make it Munich.",   { toolkit: tk, id: "user-42" })  // same thread — remembered
await agent.ask("What is 21 + 21?",            { toolkit: tk })                 // no id → one-shot
  • Pluggable store, two methodsget(id) → messages and save(id, messages). The default is in-memory (per-client, process lifetime); pass createClient({ ..., store }) with your own file / db / redis implementation to persist across processes.
  • Served A2A agents remember too — inbound serve fulfils each SendMessage via ask(text, { id: contextId }), so a peer's turns are remembered by A2A contextId through the same store; a message with no contextId is a one-shot.
  • The low-level run(prompt, { toolkit, history }) primitive and a stateful client.conversation({ toolkit }) wrapper are still there when you'd rather own the transcript.
  • Streaming with memory — the same id works on the streaming paths. Pass on_text to ask to stream assistant text deltas while ask still returns the final result, or use stream(prompt, { toolkit, id }) to iterate events (text / tool_call / tool_result / usage / done); with an id the thread is loaded before and saved on the done event.

Available in all six ports (a ConversationStore interface + in-memory default + ask).

Observability — metric events + built-in Prometheus

Zero-dependency, two outputs from one internal instrumentation — both opt-in, no cost when unused.

  • on_metric event feedcreateClient({ ..., on_metric }) (idiomatic name per port) receives a readable semantic record at each significant point: one { event: "llm" } per model call, one { event: "tool" } per tool call, one terminal { event: "run" } per run/ask (with tokens, turns, timings, error). Forward it to statsd, logs, or OpenTelemetry — the library holds no opinion.
  • client.metrics() → Prometheus text — the same events feed a tiny in-memory registry that renders the Prometheus text exposition format (no third-party dep). Mount it at GET /metrics: toolnexus_llm_requests_total, toolnexus_llm_tokens_total, toolnexus_tool_calls_total, plus the toolnexus_llm_request_duration_seconds / toolnexus_tool_duration_seconds histograms. The rendered text is byte-identical across all six ports; OTLP push is a planned future companion.

Go CLI — an instant agent from the terminal

cd golang && go build -o toolnexus ./cmd/toolnexus
./toolnexus run --config ../examples/mcp.json --skills ../examples/skills \
  --base-url https://openrouter.ai/api/v1 --style openai --model openai/gpt-4o-mini
# > you: ...     (continuous REPL agent loop)
./toolnexus tools --config ../examples/mcp.json --skills ../examples/skills   # list resolved tools

Per-language docs

Full docs site (all six languages): https://muthuishere.github.io/toolnexus/. Per port: js/ · python/ · golang/ · java/ · csharp/ · elixir/ — quickstarts and API. Embedding in a Go app? See golang/GUIDE.md. examples/ holds the shared mcp.json + sample skill used by every implementation's examples and tests. The cross-language contract lives in SPEC.md.

Status

  • ✅ MCP servers (stdio + streamable-HTTP / SSE)
  • ✅ Agent skills (SKILL.md discovery + progressive-disclosure skill tool)
  • ✅ Native/decorator tools + HTTP/REST tools
  • ✅ Built-in tools (10 opencode tools; on by default, whole-source toggle + per-tool map)
  • ✅ A2A agents — outbound (call remote agents) + inbound (serve your toolkit as an agent); all six ports
  • ✅ MCP server (inbound) — expose the toolkit as a streamable-HTTP MCP server (/mcp on serve); all six ports
  • ✅ Conversation memory (ask + pluggable ConversationStore; A2A serve remembers by contextId)
  • ✅ Streaming with memory (stream/ask take an id; ask gains an on_text delta callback)
  • ✅ Observability — on_metric event feed + zero-dep client.metrics() Prometheus text
  • ✅ Unified LLM client (OpenAI- and Anthropic-style endpoints) + Go CLI
  • ✅ OpenAI / Anthropic / Gemini schema adapters
  • ✅ Verified with live OpenRouter tool-calling round trips (every port)
  • ✅ Published on all six registries: npm · PyPI · Go module · NuGet · Maven Central · Hex
  • ⏳ OpenAPI bulk import + MCP OAuth — follow-ups (pass a bearer token via headers for now)

Tests

Each port has a hermetic suite (no network, no LLM — local HTTP servers for the HTTP tool, the shared examples/ fixtures for skills) covering config parsing, ${ENV} header expansion, the byte-exact skill block, native + HTTP + built-in tools, A2A, the provider adapters, and toolkit routing.

cd js     && npm test                 # node:test
cd python && uv run pytest -q         # pytest
cd golang && go test ./...            # go test
cd java   && ./gradlew test           # JUnit 5
cd csharp && dotnet test              # xUnit
cd elixir && mix test                 # ExUnit

The end-to-end agent loop (MCP + skills + native + HTTP through the host loop) is additionally verified live against OpenRouter per language via the examples/agent.* runners (need OPENROUTER_API_KEY).

License

MIT

from github.com/muthuishere/toolnexus

Установить Toolnexus в Claude Desktop, Claude Code, Cursor

Рекомендуется · одна команда, все IDE
unyly install toolnexus

Ставит в Claude Desktop, Claude Code, Cursor и VS Code — сам разбирается с npx, uvx и сборкой из исходников.

Впервые? Поставь CLI: curl -fsSL https://unyly.org/install | sh

Или настроить вручную

Выполни в терминале:

claude mcp add toolnexus -- npx -y toolnexus

FAQ

Toolnexus MCP бесплатный?

Да, Toolnexus MCP бесплатный — установка в пару кликов через Unyly без оплаты.

Нужен ли API-ключ для Toolnexus?

Нет, Toolnexus работает без API-ключей и переменных окружения.

Toolnexus — hosted или self-hosted?

Self-hosted: сервер запускается локально на твоей машине командой из раздела установки.

Как установить Toolnexus в Claude Desktop, Claude Code или Cursor?

Открой Toolnexus на unyly.org, выбери вкладку своего клиента (Claude Desktop, Claude Code, Cursor) и нажми Install — конфиг сгенерируется автоматически, без правки JSON.

Похожие MCP

Compare Toolnexus with

Не уверен что выбрать?

Найди свой стек за 60 секунд

Автор?

Embed-бейдж для README

Похожее

Все в категории ai