agentleFS
muthuisheremuthuishere/toolnexus/CLAUDE.md

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

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
  1. Very specific:

    Default to a change for: a new tool source, a change to MCP/skill discovery or naming, the skill tool output, adapter …

    line 90
  2. Very specific:

    Every requirement needs ≥ 1 scenario.

    line 111
  3. Distinctive:

    Never bake a registry token into code, config, a test or CI.

    line 151
  4. Distinctive:

    Do not tag or publish unless the owner asked.

    line 174
  5. Very specific:

    Do not bump one port "to unblock" — that is exactly what preflight refuses.

    line 176
  6. Very specific:

    Do not hand-write a GitHub Release body that disagrees with CHANGELOG.md.

    line 177
  7. Very specific:

    Say what is NOT done: Every entry that ships a partial capability names the gap, and names where it is tracked (openspec/changes/<name>).

    line 210
  8. Very specific:

    Run /opsx:propose to start any non-trivial change.

    line 237

≈ “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.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
CLAUDE.md

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/):

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

PathWhat
SPEC.mdThe 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.ymlRuns 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:

  1. Behavior is defined in the spec before it is written in code. SPEC.md §0 is the conformance contract — if a change alters observable behavior (tool naming, config parsing, the skill tool's byte-exact output, adapter mapping, the client loop), it changes SPEC.md first, 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.
  2. A behavior change lands in all eight ports, or it is not done. Do not ship a capability in js/ and leave python//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/ (same mcp.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.

  1. Propose — /opsx:propose "<idea>" scaffolds a change at openspec/changes/<name>/: proposal.md (why + what), design.md (how), tasks.md (checklist), and spec deltas at specs/<capability>/spec.md. This is where intended behavior is pinned, before code. If the change moves the cross-language contract, edit SPEC.md in the same change.
  2. Validate — openspec validate <name> (catches malformed deltas / missing scenarios).
  3. 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) in tasks.md; if a pass covers only a subset, the rest stay as unchecked tasks — never let parity drift silently.
  4. Verify — run the narrowest useful suite per touched port (see Commands) and check parity against the shared examples/. Call out anything unverified.
  5. Open the PR with the change folder + code in one diff, so reviewers see intent and implementation together.
  6. Archive on merge — once the PR merges, run /opsx:archive (or openspec archive <name>). This folds the spec deltas into openspec/specs/ and moves the change to openspec/changes/archive/. Archive only after merge — never when the PR is merely opened, so openspec/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 (§0 is 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.md section 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 MODIFIED requirement 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.

PortBuildTest
js/npm install && npm run buildnpm 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 builddotnet test
elixir/mix deps.get && mix compilemix test · coverage gate: mix coveralls (min 95%)
clojure/clojure -Pclojure -M -m toolnexus.test-main (JVM; CI also runs cljgo)
rust/cargo buildcargo 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 prod GitHub environment, never in the repo, never in code.
registrymechanismcredentials
npm / PyPI / NuGetOIDC Trusted Publishingnone stored; the trusted publisher is scoped to the prod environment
Gotag push golang/vX.Y.Znone — contents: write; idempotent, skips if the tag exists
Maven CentralGradle publishAndReleaseToMavenCentralCENTRAL_USERNAME, CENTRAL_PASSWORD, GPG_PRIVATE_KEY, GPG_PASSPHRASE
Hexmix hex.publish --yesHEX_API_KEY
Clojarsclojure -T:build deployCLOJARS_USERNAME, CLOJARS_PASSWORD — a Clojars deploy token, not the account password
crates.iocargo 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

  1. Bump every manifest to the same version — js, python, csharp, java, elixir, clojure/build.clj and rust/Cargo.toml. Go has no manifest; it releases as a tag. The preflight job fails the run if any manifest disagrees with the tag, which is the guard against a partial bump.
  2. Move the changelog: ## Unreleased becomes ## X.Y.Z — YYYY-MM-DD, with a fresh empty ## Unreleased above it.
  3. Cut a GitHub Release vX.Y.Z. That is the trigger. workflow_dispatch also works for a re-run. The release body IS the changelog section — not a second, divergent account.
  4. 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.
  5. 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.
  6. Marketing — run the marketing skill (.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 preflight refuses.
  • 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 headers values 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 add Co-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

DocumentPurpose
CLAUDE.mdProject guidance for Claude Code (this file)
SPEC.mdThe shared cross-language contract — start here for any behavior question
README.mdEnd-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.mdPer-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.mdWhat changed, for users — updated in the SAME PR as the change (see above)
PUBLISHING.mdHow 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.

Download now (ZIP)

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
What did you use it for? e.g. Reviewing pull requests in a Rails app

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.