THE REPOSITORY
finestructure-ai/claude-pluginSponsored
Open finestructure-ai/claude-pluginThe whole repo is seven files
Clone it and the tree fits on one screen:
.claude-plugin/
plugin.json 686 B
marketplace.json 507 B
commands/
deploy.md 3.3 KB
fs-domain.md 905 B
fs-status.md 741 B
skills/
fine-structure/
SKILL.md 4.0 KB
LICENSE MIT
README.md 2.3 KBNo package.json, no lockfile, no CI, no dist. Nothing is compiled or bundled, and there is no version of this repo that can fail to build. If you have been putting off writing a plugin because you assumed there was a toolchain, that is the correction: the format is a directory convention plus two JSON files, and the rest is prose.
plugin.json is a pointer, not a program
The manifest carries the usual identity fields (name, version 0.1.0, description, author, homepage, repository, license, keywords) and then one block that does all the interesting work:
"mcpServers": {
"finestructure": {
"type": "http",
"url": "https://finestructure.ai/api/mcp"
}
}Two keys. No command, no args, no env. Compare that with the shape most MCP configs take: a local process (npx, uvx, a path to a binary), an argument list, and an env map holding an API key. Declaring a streamable HTTP endpoint instead moves the server off the user's machine, which is why 105 tools (the count the skill file gives) sit behind a 686 byte manifest. Nothing about the user's Node or Python version can break the install, and server-side changes ship without a plugin release. The trade is reproducibility: you cannot pin the tool surface from the client side.
marketplace.json makes the repo its own distribution channel
Claude Code installs plugins from marketplaces, not from a package registry, and a marketplace is a JSON file listing plugins with a source path. The second file in .claude-plugin does that, and its one plugin entry has "source": "./". The repo is simultaneously the plugin and the marketplace serving it, so publishing is a public git push:
/plugin marketplace add finestructure-ai/claude-plugin
/plugin install finestructure@finestructureThe two identical tokens in the second line are plugin name at marketplace name, colliding because both are called finestructure. If you copy this layout, name them differently. Install instructions get clearer, and you keep room to add a second plugin later.
Commands are prompt templates with frontmatter
Each file in commands/ is YAML frontmatter plus prose. deploy.md declares a description and an argument-hint, the two strings a user sees in the picker, then lays out a numbered decision tree: check the project root for a .finestructure.json holding an app_id, otherwise call list_apps and look for a name match, otherwise treat this as a first deploy and use $ARGUMENTS as the app name. fs-status.md is 741 bytes and does a CLI subcommand's job: resolve the app, call get_app_status, get_app_links and get_errors, summarize. fs-domain.md walks add_custom_domain, get_domain_verification, check_domain_verification, get_domain_ssl_status and set_primary_domain in order.
The design point: none of these files call anything. They name tools, fix an order, and describe what to do when a step fails. Execution happens when the model invokes MCP tools. A slash command is a checked-in prompt, so reviewing one is not asking whether the logic is correct, it is asking whether the instruction is ambiguous.
The best lines are the negative ones. deploy.md forbids deletion tools unless the user explicitly asked. fs-domain.md notes DNS propagation takes minutes to hours and says to suggest retrying later rather than loop on verification. That is retry policy and guard rails written as sentences, and it is what naive plugins leave out.
Skill versus command: pull versus push
skills/fine-structure/SKILL.md has two frontmatter fields, name and description, and the description reads as a trigger condition rather than a summary: use whenever the user wants to deploy, create or update an app, connect a domain, manage data or secrets. That phrasing is deliberate. A command fires because a human typed it. A skill fires because the model matched the description against what is happening.
The content split follows. SKILL.md is a mental model, not a task list: what an app is here (JSX pages, shared components, entity schemas, no arbitrary server code), what entities are, which operations spend credits. Then workflows as tool chains, get_app_files to read_app_file to write_app_file to validate_app to publish_app, with change sets for multi-file edits. Then error semantics, which carry the most weight per byte: auth errors mean reconnect, credit errors mean top up, neither is retryable. An agent without that knowledge retries an unretryable failure in a loop, and on a metered platform that loop has a price.
The rule for your own plugin falls out cleanly. If a command file is explaining what your product is, that paragraph belongs in the skill. Commands stay procedures.
OAuth is why the manifest holds no secrets
The endpoint authenticates over OAuth 2.1 with dynamic client registration, so the first tool call opens a browser consent page and the token lands in Claude Code's credential store, not in the repo. A fork is safe by construction, revocation is server side rather than a config edit that has to reach every machine, and nobody is asked to paste a long-lived key into a file that sits one careless commit from a public diff.
What to copy for your own plugin
Concrete pointers, roughly in build order:
- Start with .claude-plugin/plugin.json holding name, version and description. Everything else is optional.
- If you run a hosted MCP endpoint, declare "type": "http" with a url instead of a local command. That deletes the user's runtime from your support surface.
- Ship marketplace.json in the same repo with "source": "./" while you have one plugin, and give it a different name from the plugin.
- Give every command a frontmatter description and argument-hint, written as answers to what does this do and what do I type after it.
- Use $ARGUMENTS rather than inventing positional syntax. There is no parser, only substitution.
- Write command bodies as decision trees with tools named explicitly. "If the config file exists read app_id, otherwise call list_apps" holds up better than a paragraph of intent.
- Put failure semantics in writing: which errors are terminal, which are retryable, what to say instead of retrying.
- Name your destructive tools in the prose and forbid them unless asked.
- Keep the domain model in SKILL.md and the procedure in the command. Duplication between the two is a smell.
- Test with /plugin marketplace add against a local path or your fork before pointing anyone at main.
Rough edges
Three honest ones, none fatal. Everything is English, and the format offers no hook for fixing that: there is no locale key in the frontmatter, so localizing means duplicated command files or accepting the mismatch.
The no-API-keys story has an asterisk. SKILL.md admits media generation tools sit outside the OAuth connection and need a static MCP token with media scopes, created in the platform's Studio and sent as a bearer header. The plugin handles it well, telling the model to explain the absence rather than retry into a wall, but it is a second auth path bolted onto a design whose selling point is having one.
And it is version 0.1.0 with no changelog, tests or CI. For markdown that is defensible, though a JSON schema check in a pre-commit hook would catch the one error class that actually breaks installs, a malformed manifest. Directory submission is in review at the time of writing, so for now you install by repo reference.
Read the three command files first, then SKILL.md, then the two JSON files. In that order the format explains itself: markdown decides what should happen and in what order, the MCP server decides what actually runs, and the manifest is the thin seam between them.
QUESTIONS
Asked about this repository
- what is the minimum file structure for a claude code plugin?
- One file: .claude-plugin/plugin.json with a name, version and description. Commands, skills, agents and hooks are optional layers on top. finestructure-ai/claude-plugin is a useful size reference because it uses several of those layers and still totals seven files and under fourteen kilobytes, with no build step.
- how do slash commands work in a claude code plugin?
- A slash command is a markdown file in commands/ whose filename becomes the command name. Frontmatter supplies a description and an argument-hint for the picker, and the body is a prompt the model receives when the command runs, with $ARGUMENTS substituted for whatever followed it. The file executes nothing itself, so real work has to come from tools the model can call, which is why this plugin pairs commands with an MCP server.
- do I need an API key to use an MCP server in a claude code plugin?
- Not if the server speaks OAuth. This manifest declares only "type": "http" and a url, and the endpoint uses OAuth 2.1 with dynamic client registration, so authorization happens in the browser and the token stays in Claude Code's credential store. The alternative, an env block holding a key, works but puts a long-lived secret in a file users copy around. One caveat visible here: the media tools sit outside that flow and still need a static token.
- how do I turn a github repo into a claude code plugin marketplace?
- Add .claude-plugin/marketplace.json with a name, owner and plugins array. If it serves a plugin in the same repo, set that plugin's source to "./", which is what this repo does. Users then run /plugin marketplace add owner/repo followed by /plugin install plugin-name@marketplace-name. There is no registry and no publish step between a git push and an installable plugin.
THE SPONSOR
The platform behind the endpoint
Fine Structure runs the hosted MCP server this plugin points at, and sponsors this publication.
finestructure.ai