Skip to main content
loomcycle
§ release note

From Go-bundled to JSON-pluggable — and into Claude Code itself.

A week ago, adding a new MCP server to loomcycle's curated catalog meant a Go PR, a recompile, a binary release, and a Homebrew bump. Today it means dropping a JSON file in $LOOMCYCLE_MCP_RECIPES_ROOT and running loomcycle mcp-registry list. The catalog moved from code to data. Once it became data, three things became obvious that hadn't been before — and the third one ships today as a Claude Code plugin.

The reconsideration

The first version of loomcycle's MCP catalog was a Go slice. Thirteen curated recipes — Tavily, Brave Search, GitHub, GitLab, Notion, Slack, Discord, Telegram, email, arXiv, fetch, filesystem, an internal jobs API — each a struct with a command, args, env-var allowlist, and a metadata block. Operators could see what was on offer through loomcycle mcp list, and the typed struct meant every recipe was schema-correct by construction. It was the right first move; it shipped the catalog as a feature without inventing infrastructure.

It also meant the catalog grew at the speed of releases. An operator who needed a recipe loomcycle didn't yet ship had two options: open a PR, or write the MCP server entry by hand in their yaml. Neither matches how operators think about an MCP catalog. The catalog is their domain — which servers are trusted in this deployment, which env vars are gated, which credential keys are required. Hard-coding it in Go is a category error: that's data, not code.

PR #274 moved the catalog. The recipes now live as JSON files under internal/recipes/bundled/, embedded into the binary via //go:embed so operators get the curated thirteen for free. A filesystem overlay at $LOOMCYCLE_MCP_RECIPES_ROOT shadows the bundled set with complete-replace semantics — your overlay file beats the bundled file wholesale. A sibling <name>.disabled file hides a recipe operators don't want surfaced. Seven CLI verbs (list, show, append-to-config, add, remove, enable, disable) cover discover-and-install for the bundled set and full CRUD over the overlay. No recompile, ever.

The shape choice that mattered most: each recipe is Claude Code's .mcp.json per-server JSON shape, with a sibling _loomcycle metadata block. Top-level fields are byte-compatible with Claude Code's format — a bundled loomcycle recipe drops cleanly into a Claude Code .mcp.json if you strip the _loomcycle block, and a Claude Code .mcp.json entry lifts into the loomcycle library if you add one. One JSON schema, two consumers, no translation layer.

// internal/recipes/bundled/slack.json
{
  "command": "npx",
  "args": ["-y", "@modelcontextprotocol/server-slack"],
  "env": {
    "SLACK_BOT_TOKEN":  "${LOOMCYCLE_SLACK_BOT_TOKEN}",
    "SLACK_TEAM_ID":    "${LOOMCYCLE_SLACK_TEAM_ID}"
  },
  "_loomcycle": {
    "description":         "Slack via the official MCP server.",
    "transport":           "stdio",
    "pool_size":           4,
    "env_vars_required":   ["LOOMCYCLE_SLACK_BOT_TOKEN", "LOOMCYCLE_SLACK_TEAM_ID"],
    "credentials":         ["slack"],
    "schedule_compatible": true,
    "agent_prompt_hint":   "Slack publish + DM."
  }
}

What the format-share unlocked

As soon as the loomcycle catalog spoke Claude Code's JSON, an obvious primitive surfaced: walk a Claude Code repo's .claude/ directory and ingest it into loomcycle yaml. Most teams using Claude Code already have a real .claude/ by the time they hear about loomcycle — agents under .claude/agents/, skills under .claude/skills/, MCP servers in .claude/mcp.json or a project-root .mcp.json. Asking them to re-author all of that in loomcycle's shape was a non-starter. Asking the loomcycle binary to read it directly is a one-command move.

PR #275 shipped loomcycle import claude-code. The verb walks the .claude/ tree, maps each artifact to a loomcycle yaml fragment, and emits a typed ImportReport with what was mapped, what was skipped, what was unmapped, and what needs operator review. Six output modes (--dry-run, --write, --json, --emit-recipes, --force, --no-recipe-match) cover the spectrum from "show me what you'd do" to "rebuild loomcycle yaml from this Claude repo in CI."

Two design choices in the importer are worth naming:

Lossy import is loud, not silent. Unmapped frontmatter (Claude Code's hooks, output_style, temperature, top_p, color, custom keys) surfaces in the report's Unmapped slice with a per-field hint. Malformed individual files become warnings, not aborts. Operators see what was ingested cleanly, what needs review, and what loomcycle deliberately doesn't model — and they can act on each separately.

Closing the loop — claude-code-plugin-loomcycle

loomcycle logo connected to the Claude Code mark by two woven threads in purple and green
loomcycle + Claude Code — the plugin's social preview, on the same loom-thread motif as the rest of the brand.

Both the catalog change and the import command were data movement. The third consequence is runtime: once loomcycle can be authored from Claude Code (via import) and exposed as MCP (via loomcycle mcp install, shipped in v0.8.15), the missing piece is the UX layer that makes loomcycle feel like a Claude Code citizen rather than a separately-managed sidecar. That's claude-code-plugin-loomcycle, released today.

It's a separate-repo plugin (denn-gubsky/claude-code-plugin-loomcycle), distributed via Claude Code's plugin marketplace — markdown + JSON in a git repo, no npm, no build step. Install with /plugin marketplace add denn-gubsky/claude-code-plugin-loomcycle then /plugin install loomcycle. Claude Code prompts you for four bits of config at install — bin_path, config_path, auth_token (keychain-stored as sensitive), base_url — and wires the MCP server entry automatically.

What you get once it's installed:

Notably the plugin ships zero loomcycle-side code changes. It consumes the existing loomcycle mcp stdio server plus the 20+ meta-tools (spawn_run, cancel_run, memory, channel, agentdef, evaluation, context, pause/snapshot ops) verbatim. The plugin's job is wiring + UX, not new capabilities. The only loomcycle-side change was a one-page docs/CLAUDE-CODE.md at denn-gubsky/loomcycle/docs/CLAUDE-CODE.md covering both paths (plugin-recommended, manual loomcycle mcp install still supported).

What the three together unlock

Until this week, "use loomcycle from Claude Code" required three uncomfortable choices about where authoring lived, where runtime lived, and how the two synced. From today the answer is the same loop in three steps:

The three steps share one JSON schema (the .mcp.json shape), one CLI tool (loomcycle), one auth surface (the bearer token), one observability stack (loomcycle's OTEL profiles). Nothing is duplicated, nothing is translated.

The lesson is the architectural choice that comes earlier than the artifact. Moving the MCP catalog from Go-bundled to JSON-pluggable wasn't a feature; it was a decision about who owns the catalog (operator) and what shape that ownership takes (a file format both ends speak). Once that was settled, the import command and the plugin both became obvious next moves rather than ambitious ones. "Catalog as data, in a shape your neighbor already uses" is the small idea the whole week hinges on.

What you can do with it today

If you're on a loomcycle build past PR #274:

$ loomcycle mcp-registry list
13 bundled recipes (loomcycle internal).
Overlay root: $LOOMCYCLE_MCP_RECIPES_ROOT (none set).

  arxiv          ArXiv search via mcp-arxiv.
  brave-search   Brave Search API.
  discord        Discord via the official MCP server.
  email          Email via mcp-email.
  fetch          HTTP fetch via the official MCP server.
  filesystem     Local filesystem (operator-restricted roots).
  github         GitHub via the official MCP server.
  gitlab         GitLab via the official MCP server.
  jobs           Internal jobs API.
  notion         Notion via the official MCP server.
  slack          Slack via the official MCP server.
  tavily         Tavily search.
  telegram       Telegram bot via mcp-telegram.

$ loomcycle import claude-code ~/work/my-claude-repo --dry-run
Found 6 agents, 3 skills, 4 mcp servers under .claude/.
Recipe-matched 3 of 4 mcp entries (github, slack, notion).
Unmapped frontmatter: 2 fields across 1 agent (output_style, color).
Run with --write to apply.

Then in Claude Code:

/plugin marketplace add denn-gubsky/claude-code-plugin-loomcycle
/plugin install loomcycle
/loomcycle:connect [email protected] --persist
/loomcycle:run job-search-batch "find FE roles in Berlin"

Companion reading: Three MCP tokens in one run (the per-run credentials story the recipes' credentials field plumbs into), Scheduled runs at 30,000 fires (where the recipes' schedule_compatible flag becomes load-bearing), and the n8n integration writeup (the same UX-layer pattern in a different IDE).