> ## Documentation Index
> Fetch the complete documentation index at: https://docs.runlayer.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Plugins

> Bundle multiple MCP connectors into a single, reusable toolset.

## What is a Plugin?

A **Plugin** is a curated bundle of tools from one or more MCP servers ("connectors"). It gives you a **single MCP endpoint** you can add to your AI client, while still enforcing all the underlying Runlayer policies and access controls.

Plugins are useful when you want:

* **A purpose-built toolset** for a workflow (e.g. "Release Management", "Customer Support", "On-call")
* **One connection** in your MCP client instead of many
* **A safe, shareable setup** (private to you or public within your workspace)

<Note>
  Plugins expose **tools and prompts**, but no resources. Linked skill files are
  also available through the `get_skill` and `get_skill_file` tools described below.
</Note>

Plugins installed outside Runlayer are discovered by [Shadow AI Detect](/shadow-ai/detect#shadow-plugins-discovery), and with [Enforce](/shadow-ai/enforce#can-users-bypass-approved-plugins-and-skills) hooks installed, their embedded MCP servers are blocked unless they point at Runlayer.

***

## Creating or extending a Plugin

### From the Plugins page

<Steps>
  <Step title="Open Plugins">
    Navigate to <strong>Plugins</strong> in the sidebar.
  </Step>

  <Step title="Create a new Plugin">
    Click <strong>Create new</strong> and provide a name, optional description, and optional **namespace**.

    A namespace (e.g. `acme/review`) groups related Plugins and ensures a unique `(namespace, path)` pair. It is optional — leave it blank for one-off Plugins.
  </Step>

  <Step title="Choose privacy">
    <ul>
      <li>
        <strong>Private</strong>: only you can access it
      </li>

      <li>
        <strong>Public</strong>: anyone in your workspace can access it
      </li>
    </ul>
  </Step>

  <Step title="Add connectors and pick tools">
    Select the MCP servers you want, then choose which tools from each server should be included in the Plugin.
  </Step>
</Steps>

### From connectors or skills

You can start with the items you want instead of creating an empty Plugin first.

<Steps>
  <Step title="Select connectors or skills">
    Open <strong>My connectors</strong> or <strong>Skills</strong> and select one or more cards. Only active hosted connectors can be selected; local, draft, and disabled connectors cannot be bundled into a Plugin.
  </Step>

  <Step title="Choose Add to Client">
    Use the selection bar at the bottom of the page and click <strong>Add to Client</strong>.
  </Step>

  <Step title="Choose a Plugin path">
    Select one of the available actions:

    <ul>
      <li><strong>Use the Runlayer Plugin</strong> — connect the organization-wide Plugin that includes your accessible connectors and skills</li>
      <li><strong>Add to plugin</strong> — open an existing Plugin you can edit with its missing selected items preselected</li>
      <li><strong>Create new plugin</strong> — start a new Plugin with the selected items preselected</li>
      <li><strong>View all plugins</strong> — browse existing Plugins before choosing</li>
    </ul>
  </Step>
</Steps>

***

## Using a Plugin in an MCP client

Open the Plugin and click <strong>Add to Client</strong>. Runlayer will generate the correct connection details for your selected client.

## Deploy an Agent from a Plugin

From any Plugin detail page, click **Deploy as Agent** to create a new [Agent](/platform-agents) that automatically inherits the plugin's connectors and skills. This is the fastest path from a curated toolset to a running agent.

***

## Managing connectors and tools

* **Add connectors**: open a Plugin and click <strong>Add Connectors</strong>
* **Remove connectors**: open the connector menu and choose <strong>Remove connector</strong>
* **Review tools**: expand a connector to see the tools included in the Plugin

***

## Dynamic tools

Dynamic tools are an optimization for Plugins with many tools.

### How it works

When **Dynamic tools** is enabled, the Plugin does **not** expose every underlying tool definition directly. Instead, it exposes two meta-tools:

* **`search_tools`**: search for the most relevant tools by describing what you want to do (returns tool names + descriptions + input schemas)
* **`execute_tool`**: execute a specific tool by name with arguments (typically chosen from `search_tools` results)

This creates a two-step flow: **search → execute**.

### Why it's useful

Sending hundreds of tool definitions (names, descriptions, and JSON schemas) to the LLM can push requests onto a "context route" that:

* **Degrades model performance** (more noise in the prompt)
* **Increases latency** (more tokens processed)
* **Increases cost** (larger prompts and tool schemas)

Dynamic tools keeps the prompt/tooling surface small until the model actually needs a specific tool.

### When to use it

* **Enable** Dynamic tools for Plugins with many connectors/tools, or when tool schemas are large.
* **Disable** it for small Plugins where you want the model to see all tools immediately without an extra search step.

***

## Skill tools in Plugins

When a Plugin has linked skills, those skills are automatically exposed through two shared read-only search tools alongside the connector tools:

| Tool             | Description                                                                                                                |
| ---------------- | -------------------------------------------------------------------------------------------------------------------------- |
| `get_skill`      | Full-text search across skill names, descriptions, and file content. Returns up to 5 ranked matches with their file lists. |
| `get_skill_file` | Reads one file by exact skill name and file title (use values returned by `get_skill`).                                    |

The legacy `getSkill` and `getSkillFile` names are still accepted on tool calls for clients with cached tool lists, but only the snake\_case names are advertised.

This keeps the tool surface small for Plugins with large skill libraries while still giving the model access to every skill through search.

***

## Authoring and syncing with CLI

Runlayer can also publish Claude-format plugins from disk with `uvx runlayer plugins push`.

### Directory structure

```text theme={null}
my-plugin/
  .claude-plugin/
    plugin.json
  .mcp.json
  skills/
    ticket-triage/
      SKILL.md
      helper.py
    code-review/
      SKILL.md
      prompts.md
  commands/
    review.md
  agents/
    code-reviewer.md
  hooks/
    hooks.json
    validate.sh
  scripts/
    deploy.sh
```

### Manifest

Runlayer uses `.claude-plugin/plugin.json` as the plugin manifest and now reads `mcpServers` from two places: inline in the manifest (preferred) or as a legacy `.mcp.json`.

```json theme={null}
{
  "name": "my-plugin",
  "version": "1.0.0",
  "description": "Review and triage code"
}
```

| Field         | Required | Notes                                                                                                                         |
| ------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------- |
| `name`        | Yes      | Used as the plugin display name                                                                                               |
| `version`     | No       | Stored for discovery                                                                                                          |
| `description` | No       | Truncated to 1024 characters on push to match the Runlayer API                                                                |
| `mcpServers`  | No       | Inline MCP definitions take precedence over `.mcp.json` and are parsed the same way Runlayer already handled the old sidecar. |

### How files map to Runlayer

* `skills/*/` become individual Runlayer skills
* supported non-skill files are bundled into one generated root skill named after the plugin

Supported root-skill file types:

* `.md`
* `.txt`
* `.sh`
* `.py`
* `.js`
* `.ts`
* `.json`

Excluded from the root skill:

* `.claude-plugin/`
* `skills/`
* `.mcp.json`
* `.lsp.json`
* `settings.json`
* root-level `README.md`

Nested files like `agents/README.md` are included.

### MCP connector extraction

Runlayer first inspects `mcpServers` inside `.claude-plugin/plugin.json` and, if none are present, falls back to `.mcp.json`. Regardless of where the servers come from, only Runlayer proxy URLs are accepted:

```json theme={null}
{
  "mcpServers": {
    "crm": {
      "url": "https://app.runlayer.com/api/v1/proxy/<server-uuid>/mcp"
    }
  }
}
```

Attached:

* URLs matching `/api/v1/proxy/<server-uuid>/mcp`

Skipped:

* non-Runlayer MCP URLs
* stdio MCP entries
* plugin proxy URLs like `/api/v1/proxy/plugins/<plugin-uuid>/mcp`
  When you `push` a plugin that still defines non-Runlayer URLs, the CLI warns that those connectors were skipped; only the Runlayer proxies are recorded in the backend.

During `plugins add`, Runlayer regenerates the manifest `mcpServers` section with the proxy URL for the linked server(s), so the installed `.claude-plugin/plugin.json` and `.mcp.json` always point at the Runlayer endpoint even if the original source defined other entries.

### Publishing

```bash theme={null}
uvx runlayer plugins push [PATH] --namespace <namespace> [OPTIONS]
```

| Flag              | Description                                                                             |
| ----------------- | --------------------------------------------------------------------------------------- |
| `--namespace, -N` | Required. Groups plugins by repo or source namespace                                    |
| `--host, -H`      | Runlayer host URL. Also reads `RUNLAYER_HOST`                                           |
| `--secret, -s`    | API key override (optional; prefer `uvx runlayer login`). Also reads `RUNLAYER_API_KEY` |
| `--public`        | Publish the plugin as public                                                            |
| `--dynamic-tools` | Enable dynamic tools on the plugin                                                      |
| `--dry-run, -n`   | Show what would change without making writes                                            |
| `--prune`         | Delete remote plugins missing locally                                                   |
| `PATH`            | Root directory to scan. Defaults to `.`                                                 |

Examples:

```bash theme={null}
# Preview plugin changes
uvx runlayer plugins push --namespace myorg/repo -n

# Push plugins
uvx runlayer plugins push --namespace myorg/repo

# Push and remove remote plugins missing locally
uvx runlayer plugins push --namespace myorg/repo --prune

# Push a single plugin directory
uvx runlayer plugins push ./engineering --namespace myorg/repo
```

### GitHub Actions

A common approach is to sync plugins automatically on every push to `main`. This keeps your Runlayer workspace in lockstep with your repo without any manual steps. Here's a starter workflow you can adapt:

```yaml theme={null}
name: Push Plugins
on:
  push:
    branches: [main]

jobs:
  sync:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: astral-sh/setup-uv@v4

      - run: uvx runlayer plugins push --namespace ${{ github.repository }} --prune
        env:
          RUNLAYER_HOST: ${{ secrets.RUNLAYER_HOST }}
          RUNLAYER_API_KEY: ${{ secrets.RUNLAYER_API_KEY }}
```

Set `RUNLAYER_HOST` and `RUNLAYER_API_KEY` as repository secrets.

The `--prune` flag removes plugins from Runlayer when their directory is deleted from the repo.

### Declarative sync semantics

Plugin skill membership is replaced from local disk.

* If a local plugin stops including one of its linked skills, that linked skill is removed from the remote plugin
* standalone skills in the same namespace are **not** deleted just because their path shares the plugin prefix
* only skills actually linked to the plugin are considered plugin-managed for deletion

Plugin connector membership is replaced from local `.mcp.json`.

* On plugin create, each imported Runlayer connector enables all tools currently visible on that server
* On plugin update, existing connectors keep their current selected tools
* On plugin update, newly added connectors enable all tools currently visible on that server
* If a connector is present locally and resolves to an accessible Runlayer server, it is attached
* if a connector is missing locally, it is removed remotely on update
* `.mcp.json` controls connector membership, not per-tool curation for connectors that already exist remotely

The CLI warns when a push will remove remote connectors not present in the validated local plugin state.

`--dry-run` is a remote-aware preview:

* it reads remote plugin and skill state
* it prints `created`, `updated`, `unchanged`, and `deleted` previews
* it does not write changes

With `--prune`, remote plugins not found locally are deleted, along with their linked skills. It does **not** delete unrelated standalone skills in the same namespace.

Warnings you may see:

* `truncated plugin description to 1024 characters`
* `skipped MCP server <name> with non-Runlayer URL <url>`
* `missing server <uuid> skipped`
* `push will remove remote connectors not present in local plugin state: <uuid>`

***

## Installing

Add published plugins from the Runlayer API to your local project or global config.

```bash theme={null}
uvx runlayer plugins add [SOURCE] [OPTIONS]
```

| Flag            | Description                                                                             |
| --------------- | --------------------------------------------------------------------------------------- |
| `source`        | Optional when `--all` is used. Plugin UUID or namespace (e.g. `Org/Repo`)               |
| `--all`         | Install all accessible plugins across namespaces                                        |
| `--plugin`      | Filter by plugin name within a namespace                                                |
| `--client, -c`  | Target editor client (default: `claude_code`)                                           |
| `--global, -g`  | Install globally instead of project-level                                               |
| `--dry-run, -n` | Show what would be installed without writing files                                      |
| `--secret, -s`  | API key override (optional; prefer `uvx runlayer login`). Also reads `RUNLAYER_API_KEY` |
| `--host, -H`    | Runlayer host URL. Also reads `RUNLAYER_HOST` env var                                   |

### Supported clients

| Client                  | Install mode |
| ----------------------- | ------------ |
| `claude_code` (default) | Native       |
| `cursor`                | Native       |
| `vscode`                | Native       |
| `codex`                 | Native       |
| `windsurf`              | MCP fallback |
| `goose`                 | MCP fallback |
| `zed`                   | MCP fallback |
| `opencode`              | MCP fallback |

**Native mode** writes a plugin manifest and `.mcp.json` config file into the project or global directory. Codex uses a `marketplace.json` registry instead of symlinks. **MCP fallback** adds a server entry to the client's MCP configuration file instead.

### Examples

```bash theme={null}
# Install all plugins from a namespace
uvx runlayer plugins add myorg/my-repo

# Install all accessible plugins across namespaces
uvx runlayer plugins add --all

# Install a single plugin by name
uvx runlayer plugins add myorg/my-repo --plugin release-tools

# Install globally
uvx runlayer plugins add myorg/my-repo --global

# Install for Cursor
uvx runlayer plugins add myorg/my-repo --client cursor

# Install for Codex
uvx runlayer plugins add myorg/my-repo --client codex

# Preview without writing files
uvx runlayer plugins add myorg/my-repo --dry-run
```

`plugins add --all` installs all accessible remote plugins. It does not affect how `plugins list` works.

### File layout (native mode)

| Scope   | Claude Code          | Cursor               | VS Code              | Codex                |
| ------- | -------------------- | -------------------- | -------------------- | -------------------- |
| Project | `.claude/plugins/`   | `.cursor/plugins/`   | `.vscode/plugins/`   | `.agents/plugins/`   |
| Global  | `~/.claude/plugins/` | `~/.cursor/plugins/` | `~/.vscode/plugins/` | `~/.agents/plugins/` |

Manifest directories per client: `.claude-plugin/`, `.cursor-plugin/`, `.vscode-plugin/`, `.codex-plugin/`. Codex also writes a `marketplace.json` in the canonical plugins directory.

A lockfile at `.runlayer/plugin-lock.yml` (or `~/.runlayer/plugin-lock.yml` for global) tracks installed plugins, install mode, and versions per client.

***

## Interactive find

Browse and install plugins interactively from the terminal.

```bash theme={null}
uvx runlayer plugins find
```

The command loads all available plugins, presents a searchable multi-select list, then prompts you to choose target clients and install scope (project or global). No flags are required — the wizard walks you through each step.

| Flag           | Description                                     |
| -------------- | ----------------------------------------------- |
| `--secret, -s` | API key override. Also reads `RUNLAYER_API_KEY` |
| `--host, -H`   | Runlayer host URL. Also reads `RUNLAYER_HOST`   |

***

## Managing installed plugins

### List

```bash theme={null}
uvx runlayer plugins list [OPTIONS]
```

| Flag           | Description                                    |
| -------------- | ---------------------------------------------- |
| `--client, -c` | Filter to one client                           |
| `--global, -g` | List global plugins instead of project plugins |

By default, `uvx runlayer plugins list` shows installed plugins in the current project's scope across all clients. Use `--global` to switch to global scope, or `--client` to filter the selected scope down to one client.

`list` is scope-first. `update` and `remove` stay client-scoped.

### List examples

```bash theme={null}
# List project plugins across all clients
uvx runlayer plugins list

# List global plugins across all clients
uvx runlayer plugins list --global

# List project plugins for one client
uvx runlayer plugins list --client cursor

# List global plugins for one client
uvx runlayer plugins list --global --client cursor
```

### Update

Pull the latest versions of installed plugins from the API.

```bash theme={null}
uvx runlayer plugins update [OPTIONS]
```

| Flag            | Description                                                                             |
| --------------- | --------------------------------------------------------------------------------------- |
| `--plugin`      | Update a specific plugin only                                                           |
| `--client, -c`  | Target editor client (default: `claude_code`)                                           |
| `--global, -g`  | Update global plugins                                                                   |
| `--dry-run, -n` | Show what would change without writing files                                            |
| `--secret, -s`  | API key override (optional; prefer `uvx runlayer login`). Also reads `RUNLAYER_API_KEY` |
| `--host, -H`    | Runlayer host URL. Also reads `RUNLAYER_HOST` env var                                   |

<Note>
  **Do installed plugins auto-update?** No. Plugins installed with `plugins add` are pinned in the lockfile (`.runlayer/plugin-lock.yml`) and do not change on their own. Run `plugins update` to pull the latest versions, or re-run `runlayer setup sync` (which your MDM can schedule) to refresh managed clients. This is different from the [Runlayer Plugin](/runlayer-plugin) org entrypoint, which is built dynamically per request — policy, tool, and skill changes there take effect automatically without reinstalling.
</Note>

### Remove

```bash theme={null}
uvx runlayer plugins remove [PLUGIN_REF] [OPTIONS]
```

| Flag           | Description                                                  |
| -------------- | ------------------------------------------------------------ |
| `plugin_ref`   | Optional when `--all` is used. Plugin name or UUID to remove |
| `--all`        | Remove all installed plugins in the selected scope           |
| `--yes`        | Skip confirmation prompt for `--all`                         |
| `--client, -c` | Target editor client (default: `claude_code`)                |
| `--global, -g` | Remove from global plugins                                   |

### Remove examples

```bash theme={null}
# Remove one plugin
uvx runlayer plugins remove release-tools

# Remove one plugin by UUID
uvx runlayer plugins remove a9d3ab20-4f5f-4d7b-a7b2-7b2b6f1d0d44

# Remove all project plugins (prompts for confirmation)
uvx runlayer plugins remove --all

# Remove all global plugins without prompt
uvx runlayer plugins remove --all --global --yes
```
