DEV Community

Flavio Sacca
Flavio Sacca

Posted on

Building an enterprise Claude Code marketplace

If you're rolling out Claude Code across a company, at some point you’ll eventually hit the same question I did: how do you distribute internally built artifacts (skills, commands, subagents and MCPs) to every developer, without asking each person to hand-configure things one by one and, maybe, forcing them to use only some tool?

The answer turned out to be simpler than expected: Claude Code plugin marketplaces, hosted on a regular Git repo (GitLab, GitHub…). Here's what I learned setting one up, including where I initially got the mental model wrong.

The building block: a plugin marketplace

A marketplace is just a Git repo with one manifest file:

my-marketplace/
└── .claude-plugin/
    └── marketplace.json
Enter fullscreen mode Exit fullscreen mode
{
  "name": "acme-internal-marketplace",
  "owner": { "name": "ACME Platform Team", "email": "platform-team@acme.example.com" },
  "plugins": [
    {
      "name": "acme-sentry-http",
      "source": "./plugins/acme-sentry-http",
      "description": "Sentry integration via remote MCP server",
      "version": "1.0.0"
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

Anyone on the team runs:

/plugin marketplace add <your-repo-url>
/plugin install acme-sentry-http
Enter fullscreen mode Exit fullscreen mode

and that's it: one command installs everything the plugin bundles, whether that's an MCP server, a skill, a command or a subagent. There's no separate "install the skill" vs "install the agent" step.

A quick note: your own marketplace doesn't have to contain only internally-built artifacts. A plugin entry's source can point anywhere: a subfolder of your repo or straight at someone else's GitHub repo, so you can re-list a good third-party plugin under your own governance umbrella instead of telling people to go find it themselves.

Say your team likes “ponytail”, an unofficial but well-maintained plugin: you'd vet it once, pin it to a commit and add it to your marketplace.json.

{
  "name": "acme-internal-marketplace",
  "owner": { "name": "ACME Platform Team", "email": "platform-team@acme.example.com" },
  "plugins": [
    { "name": "acme-sentry-http", "source": "./plugins/acme-sentry-http" },
    { "name": "acme-internal-crm", "source": "./plugins/acme-internal-crm" },
    {
      "name": "ponytail",
      "source": {
        "source": "github",
        "repo": "dietrichgebert/ponytail"
      },
      "description": "Third-party plugin, vetted and pinned by the Platform Team — not maintained by ACME."
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

The four things a plugin can bundle

This was the part that took me a minute to get straight, because they all superficially "teach Claude how to do something", but the trigger mechanism and execution context are
genuinely different for each.

Trigger Runs where Best for
MCP server Always connected once plugin is enabled External process/API Giving Claude access to a system (CRM, monitoring, fitness data...)
Command User types /name explicitly Main conversation A prompt you'd otherwise retype constantly
Skill Claude decides it's relevant, based on the task Main conversation Domain knowledge / house conventions Claude should apply without being asked
Agent User or Claude invokes it explicitly or automatically Isolated context, returns only the final result Heavy, multi-step work you don't want cluttering the main thread

Focus on - Agents

The one that surprised me most is the agent, because it's the only one of the four
that runs in a separate context window. If a task needs to crawl through dozens of files
or call a tool a hundred times, doing it as an agent keeps that noise out of your main
conversation, so you can keep only a clean summary instead of the full trace.

A minimal agent looks like this:

---
name: garmin-analyst
description: Analyzes training load, HRV and sleep trends from Garmin data. Use PROACTIVELY when the user asks about recovery or overtraining.
tools: mcp__garmin
model: sonnet
---

You are a sports physiology specialist. When given Garmin data (HRV, training load, sleep), interpret trends cautiously, flag overtraining patterns, and never give direct medical advice.
Enter fullscreen mode Exit fullscreen mode

Focus on - MCP servers

I ended up needing three distinct
patterns:

1. Remote HTTP, SaaS-hosted (e.g. Sentry)

{
  "mcpServers": {
    "sentry": { "type": "http", "url": "https://mcp.sentry.dev/mcp" }
  }
}
Enter fullscreen mode Exit fullscreen mode

No secrets in the file — either the remote server handles OAuth itself, or you
interpolate an env var for headers ("Authorization": "Bearer ${SENTRY_MCP_TOKEN}"). You should never hardcode a token in something that lives in a shared repo.

2. Stdio, published npm/pip package

{
  "mcpServers": {
    "filesystem": {
      "type": "stdio",
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-filesystem@1.2.3", "${WORKSPACE_DIR:-.}"]
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Remember to always pin the version. npx -y package-name with no version means every user auto-pulls whatever gets published next, including a compromised release if that
ever happens upstream.

3. Stdio, internally built binary

{
  "mcpServers": {
    "acme-crm": {
      "type": "stdio",
      "command": "${CLAUDE_PLUGIN_ROOT}/bin/acme-crm-mcp",
      "args": ["--config", "${CLAUDE_PLUGIN_ROOT}/config/crm-config.json"],
      "env": { "ACME_CRM_API_URL": "https://crm-internal.acme.example.com/api" }
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

${CLAUDE_PLUGIN_ROOT} resolves to wherever the plugin got installed, so the path
works on any machine. Ship the compiled binary inside the plugin for zero-setup onboarding, or point to a pre-installed binary on PATH if it's already distributed some other way (apt/brew/choco/provisioning script): the trade-off is repo size and zero-config vs. syncing two release pipelines.

Enterprise governance

Everything above is opt-in (you make things available, users choose) and doesn’t lock out other sources (users can bypass you repo and install an artifact from any repo).

If you need to actually force the issue instead there are two separate knobs, and it's worth knowing them in order because they lock down two different layers.

1. Restricting MCP servers. allowedMcpServers + allowManagedMcpServersOnly: true in managed settings restricts which MCP servers are allowed to run at all, regardless of where they'd otherwise come from (plugin, project .mcp.json, or a user's own config). This is the narrower lock: it doesn't touch what plugins, skills or commands someone installs, it only decides whether the MCP connections inside them are permitted to actually start.

{
  "allowedMcpServers": ["sentry", "acme-crm", "filesystem"],
  "allowManagedMcpServersOnly": true
}
Enter fullscreen mode Exit fullscreen mode

2. Restricting plugins/marketplaces. strictKnownMarketplaces in managed settings restricts which marketplaces Claude Code will even talk to. Set this and any /plugin marketplace add pointing somewhere not on your list (including Anthropic's own default marketplace if not specified) is rejected outright.

Nothing outside your approved marketplace(s) is even visible to /plugin, let alone installable.

{
  "extraKnownMarketplaces": {
    "acme-internal": {
      "source": { "source": "git", "url": "https://gitlab.acme.example.com/platform/claude-marketplace.git" }
    }
  },
  "strictKnownMarketplaces": true,
  "enabledPlugins": {
    "acme-sentry-http@acme-internal": true,
    "acme-internal-crm@acme-internal": true
  }
}
Enter fullscreen mode Exit fullscreen mode

Stack the two and you get the pairing you actually want for governance: MCP servers restricted (only connections coming from your approved plugins are allowed to run) and plugins restricted (only your marketplace is even reachable) while still leaving you room to fold in vetted external tools under your own review, instead of forcing everything to be built in-house.

Users get /plugin, see your curated list, and that's the whole universe: nothing else shows up, and nothing else installs even if they try.

Takeaway

If you're standardizing Claude Code across a team, a single Git repo with a marketplace.json gets you a long way: internal + curated external skills, commands, subagents and MCP connections, all installable with the same two commands, versioned and reviewable like any other code.

The main things worth getting right up front are version-pinning anything that comes from outside your org, and keeping credentials out of the manifests entirely.

Top comments (0)