toolnexus
CLAUDE.md6 starsChanged 5 months agoMITView on GitHub
toolnexus — a small, vendor-neutral library that gives any LLM the dynamic capabilities opencode has, ported byte-identically across eight languages (js/, python/, golang/, java/, csharp/, elixir/, clojure/, rust/): 1. Dynamic MCP servers — read an mcp.json, connect to every server (local stdio + remote streamable-HTTP), expose each server tool as a uniform Tool. 2. Dynamic agent skills — glob a skills/ folder (**/SKILL.md) and expose one skill tool that loads a skill's instructions + resources on demand (progressive disclosure). The…
- Reads credentials
- Installs packages
In this repository8 other files it gives its agents.
- site/AGENTS.mdAGENTS.md
- marketingSkill
- openspec-apply-changeSkill
- openspec-archive-changeSkill
- openspec-exploreSkill
- openspec-proposeSkill
- openspec-sync-specsSkill
- releaseSkill
The file
We split this CLAUDE.md into the rules it gives an agent
8 rules. About 0 are common, 2 are distinctive, and 6 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈2
Distinctive · estimate
This author's own choices. Worth a look.
6
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 90
Default to a change for: a new tool source, a change to MCP/skill discovery or naming, the skill tool output, adapter …
- Very specific: line 111
Every requirement needs ≥ 1 scenario.
- Distinctive: line 151
Never bake a registry token into code, config, a test or CI.
- Distinctive: line 174
Do not tag or publish unless the owner asked.
- Very specific: line 176
Do not bump one port "to unblock" — that is exactly what preflight refuses.
- Very specific: line 177
Do not hand-write a GitHub Release body that disagrees with CHANGELOG.md.
- Very specific: line 210
Say what is NOT done: Every entry that ships a partial capability names the gap, and names where it is tracked (openspec/changes/<name>).
- Very specific: line 237
Run /opsx:propose to start any non-trivial change.
≈ “Common” is an estimate: at least 1 in 5 of the 26,980 CLAUDE.md files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
What we're trying to build
toolnexus — a small, vendor-neutral library that gives any LLM the dynamic
capabilities opencode has, ported byte-identically
across eight languages (js/, python/, golang/, java/, csharp/, elixir/, clojure/, rust/):
- Dynamic MCP servers — read an
mcp.json, connect to every server (local stdio + remote streamable-HTTP), expose each server tool as a uniformTool. - Dynamic agent skills — glob a
skills/folder (**/SKILL.md) and expose oneskilltool that loads a skill's instructions + resources on demand (progressive disclosure).
The insight: MCP tools, agent skills, your own functions (defineTool), remote HTTP endpoints, the
built-in shell/file tools, and remote A2A agents are the same thing to an LLM — a named,
described, schema'd callable. toolnexus unifies all these tool sources behind one Tool
interface, emits the schema in OpenAI / Anthropic / Gemini formats, and ships a unified
client with a built-in tool-calling loop (system prompt, skills injection, parallel + chained
tool calls, hooks, streaming, retries, conversation memory, observability metrics).
It is also an inbound endpoint: toolkit.serve(addr, …) exposes the toolkit to the outside
world — today as a remote A2A agent (Agent Card + JSON-RPC over the client loop; SPEC.md §7B).
The whole point of the ports is parity: the same examples/ fixtures must produce the
same behavior in every language. That is the product. Protecting it is the prime directive
below.
Repo layout
| Path | What |
SPEC.md | The shared contract. The one-page conformance spec (§0) every port must satisfy, plus reference detail (§1–§9). Source of truth for behavior. |
js/ | TypeScript port — toolnexus (npm). MCP SDK: @modelcontextprotocol/sdk (same as opencode). |
python/ | Python port — toolnexus (PyPI), Python ≥ 3.11. MCP SDK: mcp. |
golang/ | Go port — github.com/muthuishere/toolnexus/golang, Go 1.23. MCP SDK: mark3labs/mcp-go. Also ships the toolnexus CLI under cmd/. |
java/ | Java port — io.github.muthuishere:toolnexus, Java 21. MCP: in-house stdio client for local servers (StdioMcpClient, Mcp2026); official io.modelcontextprotocol.sdk:mcp for remote HTTP/SSE servers and the serve side. |
csharp/ | C# port — Toolnexus (NuGet), .NET. MCP SDK: ModelContextProtocol. |
elixir/ | Elixir port — toolnexus (Hex), Elixir ≥ 1.16/OTP 26. MCP client: in-house (Toolnexus.Mcp.* — no mature Elixir SDK exists); coverage gate ≥ 95%. |
clojure/ | Clojure port — net.clojars.muthuishere/toolnexus (Clojars). One .cljc source tree for the JVM and cljgo; koine is its only dependency. |
rust/ | Rust port — toolnexus (crates.io), edition 2024, tokio. MCP: in-house stdio client; official rmcp only for remote HTTP/SSE servers and the serve side. Held to tier full (conformance/options_manifest.json); remaining gaps (no Gemini client style, Windows unrun) are in CHANGELOG.md and openspec/changes/add-rust-port. |
examples/ | Shared cross-language fixtures — mcp.json + skills/hello-world/. Every port runs against these; outputs must match. |
openspec/ | Spec-driven change workflow (OpenSpec). changes/ = active proposals (proposal.md + spec deltas + tasks.md), specs/ = canonical capability specs, changes/archive/ = shipped changes. Run the openspec CLI from the repo root. |
.github/workflows/ci.yml | Runs all eight suites (JS/Python/Go/Java/C#/Elixir/Clojure/Rust), hermetically (no network, no live LLM). |
The prime directive: spec-driven, eight-language parity
SPEC.md is the contract; the eight ports are implementations of it. Two rules govern every change:
- Behavior is defined in the spec before it is written in code.
SPEC.md §0is the conformance contract — if a change alters observable behavior (tool naming, config parsing, theskilltool's byte-exact output, adapter mapping, the client loop), it changesSPEC.mdfirst, then the code. Any change of real substance goes through an OpenSpec change first (below) — the proposal's spec deltas are where intended behavior is pinned before code. - A behavior change lands in all eight ports, or it is not done. Do not ship a capability
in
js/and leavepython//golang//java//csharp/behind without explicitly saying so and tracking it in an in-progress spec. The ports are meant to be substitutable; silent drift is the one bug this repo exists to prevent.
"Correct" is defined by §0: run a port against the shared
examples/(samemcp.json+ skill) and its outputs must match the others. When in doubt, make the spec authoritative and bring code to it — not the reverse.
Required Workflow — OpenSpec
Every change of substance runs through OpenSpec.
Slash commands (Claude Code): /opsx:propose, /opsx:apply, /opsx:archive (plus
/opsx:explore, /opsx:sync). CLI: openspec — use npx @fission-ai/openspec@latest … until
it is installed globally.
- Propose —
/opsx:propose "<idea>"scaffolds a change atopenspec/changes/<name>/:proposal.md(why + what),design.md(how),tasks.md(checklist), and spec deltas atspecs/<capability>/spec.md. This is where intended behavior is pinned, before code. If the change moves the cross-language contract, editSPEC.mdin the same change. - Validate —
openspec validate <name>(catches malformed deltas / missing scenarios). - Apply across the ports —
/opsx:apply. Implement the tasks in every affected language and tick them off. Each behavior change carries a per-language parity checklist (js / python / golang / java / csharp) intasks.md; if a pass covers only a subset, the rest stay as unchecked tasks — never let parity drift silently. - Verify — run the narrowest useful suite per touched port (see Commands) and check parity
against the shared
examples/. Call out anything unverified. - Open the PR with the change folder + code in one diff, so reviewers see intent and implementation together.
- Archive on merge — once the PR merges, run
/opsx:archive(oropenspec archive <name>). This folds the spec deltas intoopenspec/specs/and moves the change toopenspec/changes/archive/. Archive only after merge — never when the PR is merely opened, soopenspec/specs/always describes shipped behavior, not work under review.
Default to a change for: a new tool source, a change to MCP/skill discovery or naming, the
skill tool output, adapter (OpenAI/Anthropic/Gemini) mapping, the client loop (hooks,
streaming, resilience, memory), the Go CLI surface, publishing, or a new language port. Keep
ceremony minimal for typo-only fixes; tightly scoped bug fixes should still update the capability
spec that owns the behavior.
How the two spec layers relate
SPEC.md— the hand-maintained cross-language conformance contract, the byte-level porting obligation (§0is the one-page contract). Source of truth for "are the four ports identical."openspec/specs/— the capability specs that accrue from archived changes: the growing, validated record of what each capability does, in requirement/scenario form.- A behavior change updates both — the spec delta in the OpenSpec change, and the relevant
SPEC.mdsection when the cross-language contract itself moves.
Spec delta format (the part that bites)
- Requirement:
### Requirement: <name>, SHALL/MUST wording. - Scenario: exactly four hashes —
#### Scenario: <name>— with- **WHEN** …/- **THEN** …. Three hashes or bullets fail silently at archive time. - Every requirement needs ≥ 1 scenario. A
MODIFIEDrequirement must paste the full updated block, not a fragment.
Common Commands
Each port is self-contained and hermetic — cd into its directory first. CI runs exactly these.
| Port | Build | Test |
js/ | npm install && npm run build | npm test (builds, then node --test) · run a demo: npm run example |
python/ | pip install -e ".[test]" | python -m pytest -q |
golang/ | go build ./... && go vet ./... | go test -race ./... |
java/ | ./gradlew build --no-daemon | ./gradlew test --no-daemon |
csharp/ | dotnet build | dotnet test |
elixir/ | mix deps.get && mix compile | mix test · coverage gate: mix coveralls (min 95%) |
clojure/ | clojure -P | clojure -M -m toolnexus.test-main (JVM; CI also runs cljgo) |
rust/ | cargo build | cargo fmt --check && cargo clippy --all-targets -- -D warnings && cargo test && cargo test --no-default-features |
Releasing — the runbook, so nothing is missed
All ports are versioned and released together. One tag, one set of versions, every registry. A partial bump is the failure this process exists to prevent.
The one-time setup an owner controls
- Each registry publishes only if its repo variable is
true:ENABLE_NPM,ENABLE_PYPI,ENABLE_GO,ENABLE_NUGET,ENABLE_JAVA,ENABLE_ELIXIR,ENABLE_CLOJARS,ENABLE_CRATES. A leg that is off is skipped silently — check the variables before concluding a release "worked". - Secrets live in the
prodGitHub environment, never in the repo, never in code.
| registry | mechanism | credentials |
| npm / PyPI / NuGet | OIDC Trusted Publishing | none stored; the trusted publisher is scoped to the prod environment |
| Go | tag push golang/vX.Y.Z | none — contents: write; idempotent, skips if the tag exists |
| Maven Central | Gradle publishAndReleaseToMavenCentral | CENTRAL_USERNAME, CENTRAL_PASSWORD, GPG_PRIVATE_KEY, GPG_PASSPHRASE |
| Hex | mix hex.publish --yes | HEX_API_KEY |
| Clojars | clojure -T:build deploy | CLOJARS_USERNAME, CLOJARS_PASSWORD — a Clojars deploy token, not the account password |
| crates.io | cargo publish --locked (skips if the version exists) | CARGO_REGISTRY_TOKEN — a token only because a crate's first publish cannot use Trusted Publishing; switch to it once the crate exists |
Maven Central, Hex and Clojars use stored secrets only because none of the three offers OIDC. Never bake a registry token into code, config, a test or CI; they are use-only env vars read at the point of use.
Cutting a release
- Bump every manifest to the same version — js, python, csharp, java, elixir,
clojure/build.cljandrust/Cargo.toml. Go has no manifest; it releases as a tag. Thepreflightjob fails the run if any manifest disagrees with the tag, which is the guard against a partial bump. - Move the changelog:
## Unreleasedbecomes## X.Y.Z — YYYY-MM-DD, with a fresh empty## Unreleasedabove it. - Cut a GitHub Release
vX.Y.Z. That is the trigger.workflow_dispatchalso works for a re-run. The release body IS the changelog section — not a second, divergent account. - Watch the run and read the job list. A green workflow with three skipped legs is not a release; confirm each enabled registry actually published.
- Verify from the outside, as a consumer would — install the published artifact from the registry in a clean directory and run something. ADR-0012 exists because NuGet and Maven publishes have reported success without landing, so a green CI job is not evidence that a package is installable.
- Marketing — run the
marketingskill (.claude/skills/marketing/) for the version. It writes drafts only, in~/.marketing/toolnexus/<version>/; the owner posts.
Do not
- Do not tag or publish unless the owner asked. Cutting a release is outward-facing and irreversible: a version number can never be reused on any of these registries.
- Do not bump one port "to unblock" — that is exactly what
preflightrefuses. - Do not hand-write a GitHub Release body that disagrees with
CHANGELOG.md.
Coding conventions
- Match the local style of each port. Idiomatic TS, idiomatic Python, idiomatic Go, idiomatic Java, idiomatic C# — not a transliteration of one language into another. Same behavior, native shape.
- The
examples/fixtures are shared and authoritative — don't fork per-language copies; if a fixture must change, change it once and re-verify every port. - Secrets are use-only. Remote MCP
headersvalues expand${ENV_VAR}from the environment at call time and are never logged. Never write a real key into a spec, test, example, or comment — read it from the environment; use an obvious fake (YOUR_KEY_HERE) for placeholders. - Conventional commits:
feat:,fix:,docs:,test:,chore:,ci:— scope by port where it helps, e.g.feat(go client): …,test(js): …. Do not addCo-authored-by:. - Touch only what the task needs; note unrelated issues rather than fixing them inline.
CHANGELOG.md — required, and it is not the git log
CHANGELOG.md at the repo root is the one place a user learns what changed. Keeping it is
part of the change, not a release chore: the person who made the behaviour is the only one who
still knows why, and a changelog written later from git log reliably records what was
touched rather than what a user gets.
Every change of substance adds to ## Unreleased in the same PR that makes it. A change is
of substance if a user could notice it: new capability, new option, changed default, changed
output, a fixed bug they could have hit, a new port, a moved contract. Typo and internal-only
refactors do not qualify.
Write what a user gets, not what a file did. feat(clojure): add :on-error is a commit
subject; the entry says what the option is for, what happens without it, and what it now makes
possible. Follow the existing entries — they lead with the user-visible change, then the
reasoning, then the detail.
Say what is NOT done. Every entry that ships a partial capability names the gap, and names
where it is tracked (openspec/changes/<name>). A port at core tier lists its debt. This is
the same rule as the option gate printing permitted absences by name: an omission that stops
being mentioned is indistinguishable from something that was finished.
On release, ## Unreleased becomes ## X.Y.Z — YYYY-MM-DD and a fresh empty ## Unreleased
goes above it. The version must match the GitHub Release tag and every port manifest — the
preflight job in release.yml fails the run on drift. The GitHub Release body should be the
changelog section, not a second, divergent account of the same work.
Per-port notes belong in the shared entry, named inline (e.g. "golang only"), never in a per-port changelog — six parallel changelogs is exactly the drift this repo exists to prevent.
Documentation map
| Document | Purpose |
CLAUDE.md | Project guidance for Claude Code (this file) |
SPEC.md | The shared cross-language contract — start here for any behavior question |
README.md | End-user pitch + "zero to agent in 3 steps" |
js/README.md, python/README.md, golang/README.md + golang/GUIDE.md, java/README.md, csharp/README.md | Per-language end-user docs |
openspec/changes/ | Active OpenSpec change proposals — feature/change work starts here (/opsx:propose) |
openspec/specs/ | Canonical capability specs (accrue from archived changes) |
CHANGELOG.md | What changed, for users — updated in the SAME PR as the change (see above) |
PUBLISHING.md | How each port is published |
Consult SPEC.md, openspec/, and the relevant port's README before making behavior changes.
Run /opsx:propose to start any non-trivial change.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

