Bundling — Plugin Bundle Discovery & Packaging

Manifest parsing, command discovery, and MCP config for Codex/Claude/Cursor bundles

The bundling sub-module handles plugin bundles — pre-packaged plugin directories that follow Codex, Claude, or Cursor conventions. It reads bundle manifest files (.codex-plugin/plugin.json, .claude-plugin/plugin.json, .cursor-plugin/plugin.json), extracts skills, hooks, commands, and MCP server configurations. This enables OpenClaw to discover and activate plugins that were originally built for other AI coding assistants.

8
Core Files
3
Bundle Formats
~1,200
LOC
TypeScript
Language

Multi-Format Manifest-First Command Discovery MCP Config

📚 What is this & why does it exist?

The bundling sub-module handles plugin bundles — pre-packaged plugin directories that follow Codex, Claude, or Cursor conventions. It reads bundle manifest files (.codex-plugin/plugin.json, .claude-plugin/plugin.json, .cursor-plugin/plugin.json), extracts skills, hooks, commands, and MCP server configurations. This enables OpenClaw to discover and activate plugins that were originally built for other AI coding assistants.

💡 Real-world analogy

Imagine a universal power adapter for international travel. Different countries (Codex, Claude, Cursor) have different plug shapes (manifest formats). The bundling module is the adapter — it reads each format’s manifest, normalizes the metadata into OpenClaw’s standard BundlePluginManifest, and exposes the capabilities (skills, hooks, commands, MCP servers) through a common interface.

Contents

Architecture

flowchart TD
  subgraph Formats["Bundle Formats"]
    Codex[".codex-plugin/plugin.json"]
    Claude[".claude-plugin/plugin.json"]
    Cursor[".cursor-plugin/plugin.json"]
  end
  subgraph Manifest["Manifest Processing"]
    Detect["detectBundleManifestFormat()"]
    Load["loadBundleManifest()"]
    Normalize["Normalize to BundlePluginManifest"]
  end
  subgraph Commands["Command Discovery"]
    CmdRoots["resolveClaudeCommandRootDirs()"]
    MDScan["listMarkdownFilesRecursive()"]
    Parse["parseFrontmatterBlock()"]
    CmdSpec["ClaudeBundleCommandSpec[]"]
  end
  subgraph MCP["MCP Configuration"]
    MCPPaths["resolveBundleMcpConfigPaths()"]
    Extract["extractMcpServerMap()"]
    Merge["applyMergePatch()"]
    MCPConfig["BundleMcpConfig"]
  end
  Codex --> Detect
  Claude --> Detect
  Cursor --> Detect
  Detect --> Load
  Load --> Normalize
  Normalize --> CmdRoots
  CmdRoots --> MDScan
  MDScan --> Parse
  Parse --> CmdSpec
  Normalize --> MCPPaths
  MCPPaths --> Extract
  Extract --> Merge
  Merge --> MCPConfig

Key Types

NameFileTypeDescription
BundlePluginManifestbundle-manifest.tsTypeNormalized bundle metadata: id, skills, hooks, capabilities, bundleFormat
BundleManifestLoadResultbundle-manifest.tsUnionResult type for manifest loading (ok with manifest or error)
ClaudeBundleCommandSpecbundle-commands.tsTypeParsed command: pluginId, rawName, description, promptTemplate, sourceFilePath
BundleMcpServerConfigbundle-mcp.tsTypeMCP server configuration (arbitrary key-value)
BundleMcpConfigbundle-mcp.tsTypeCollection of MCP server configurations keyed by server name
BundleMcpRuntimeSupportbundle-mcp.tsTypeRuntime MCP support: stdio servers, supported/unsupported names, diagnostics
PluginBundleFormattypes.tsUnionBundle format discriminator: 'claude' | 'codex' | 'cursor'

Data Flow

sequenceDiagram
  participant Disc as Plugin Discovery
  participant BM as bundle-manifest.ts
  participant BC as bundle-commands.ts
  participant BMCP as bundle-mcp.ts
  participant Reg as Plugin Registry
  Disc->>BM: detectBundleManifestFormat(rootDir)
  BM-->>Disc: format ('claude'|'codex'|'cursor')
  Disc->>BM: loadBundleManifest(rootDir, format)
  BM->>BM: Read format-specific manifest file
  BM->>BM: normalizeBundlePathList(skills, hooks, capabilities)
  BM-->>Disc: BundleManifestLoadResult
  Disc->>BC: discoverBundleCommands(rootDir, config)
  BC->>BC: resolveClaudeCommandRootDirs()
  BC->>BC: listMarkdownFilesRecursive()
  BC->>BC: parseFrontmatterBlock() per .md
  BC-->>Disc: ClaudeBundleCommandSpec[]
  Disc->>BMCP: loadEnabledBundleMcpConfig(rootDir)
  BMCP->>BMCP: resolveBundleMcpConfigPaths()
  BMCP->>BMCP: extractMcpServerMap()
  BMCP-->>Disc: EnabledBundleMcpConfigResult
  Disc->>Reg: Register capabilities

Deep-Dive Highlights

Core bundling mechanisms have been extracted into dedicated focus pages for in-depth exploration. Each page covers one aspect of the bundling pipeline with diagrams, code walks, and configuration details.

Deep-Dive Pages

📦 Bundle Manifest Focus

Multi-format manifest detection, loading, and normalization for Codex/Claude/Cursor plugins.

Extracted topic page

⚡ Bundle Commands Focus

Markdown-based command discovery with frontmatter parsing and prompt templates.

Extracted topic page

🎯 Bundled Capabilities Focus

Runtime capability registration for bundled plugins with Vitest compatibility.

Extracted topic page

Directory Map

Source tree for bundling
📁 src/plugins/ (bundling-related files)
├── bundle-manifest.ts           # Multi-format manifest loading & normalization
├── bundle-commands.ts           # Markdown command discovery & frontmatter parsing
├── bundle-mcp.ts                # MCP server config extraction & merging
├── bundle-lsp.ts                # LSP integration for bundled plugins
├── bundled-sources.ts           # Bundled plugin source resolution
├── bundled-dir.ts               # Bundled directory management
├── bundled-compat.ts            # Compatibility layer for bundled plugins
├── bundled-capability-runtime.ts # Runtime config for bundled capabilities
├── bundled-plugin-metadata.ts   # Plugin metadata extraction
├── bundled-capability-metadata.ts # Capability metadata
└── bundled-web-search*.ts       # Bundled web search providers

Code Examples

Example: Loading a Bundle Manifest

// Loading a bundle manifest
import { detectBundleManifestFormat, loadBundleManifest } from "./bundle-manifest.js";

const format = detectBundleManifestFormat(pluginRootDir);
if (format) {
  const result = loadBundleManifest(pluginRootDir, format);
  if (result.ok) {
    console.log("Plugin:", result.manifest.id);
    console.log("Skills:", result.manifest.skills);
    console.log("MCP Servers:", result.manifest.capabilities);
  }
}