# How the Claude Plugin Manifest Is Parsed and Validated in compound-engineering-plugin

> Discover how the Claude plugin manifest is parsed and validated in EveryInc/compound-engineering-plugin. Learn about JSON structure validation, path resolution, and security measures.

- Repository: [Every/compound-engineering-plugin](https://github.com/everyinc/compound-engineering-plugin)
- Tags: internals
- Published: 2026-02-16

---

**The Claude plugin manifest is parsed by `loadClaudePlugin` in [`src/parsers/claude.ts`](https://github.com/EveryInc/compound-engineering-plugin/blob/main/src/parsers/claude.ts), which validates the JSON structure, resolves component directories, and enforces path containment to prevent directory traversal attacks before returning a fully typed `ClaudePlugin` object.**

The compound-engineering-plugin repository by EveryInc provides a robust parser for Claude Code plugins. Understanding how the Claude plugin manifest is parsed and validated ensures developers can securely load custom agents, commands, and MCP servers while maintaining strict path containment.

## Locating and Reading the Manifest File

### Resolving the Plugin Root

The entry point `loadClaudePlugin` calls `resolveClaudeRoot` (lines 39-55 in [`src/parsers/claude.ts`](https://github.com/EveryInc/compound-engineering-plugin/blob/main/src/parsers/claude.ts)) to locate the manifest. This function accepts either a direct path to [`.claude-plugin/plugin.json`](https://github.com/EveryInc/compound-engineering-plugin/blob/main/.claude-plugin/plugin.json) or a directory containing it. If the file cannot be found, the function throws an error immediately.

### Parsing the JSON Manifest

Once located, the manifest is read using `readJson<ClaudeManifest>` and stored as a typed object. This occurs at line 19 in [`src/parsers/claude.ts`](https://github.com/EveryInc/compound-engineering-plugin/blob/main/src/parsers/claude.ts), where the raw JSON is deserialized into the `ClaudeManifest` interface defined in [`src/types/claude.ts`](https://github.com/EveryInc/compound-engineering-plugin/blob/main/src/types/claude.ts).

## Validating Component Paths and Security

### Preventing Directory Traversal Attacks

Security is enforced by `resolveWithinRoot` (lines 45-52 in [`src/parsers/claude.ts`](https://github.com/EveryInc/compound-engineering-plugin/blob/main/src/parsers/claude.ts)). This function checks that any resolved path is either exactly the plugin root or a subdirectory using `resolvedPath.startsWith(resolvedRoot + path.sep)`. If a custom path attempts to escape the root (e.g., `../outside`), the parser throws a clear error message preventing the directory traversal attack.

### Resolving Custom Component Directories

The manifest allows custom paths for agents, commands, skills, and hooks via `resolveComponentDirs` (lines 81-90 in [`src/parsers/claude.ts`](https://github.com/EveryInc/compound-engineering-plugin/blob/main/src/parsers/claude.ts)). This function builds an array starting with default directories (`agents/`, `commands/`, etc.) and appends validated custom paths. Each custom path undergoes the `resolveWithinRoot` check before inclusion.

## Loading Plugin Components

### Agents, Commands, and Skills

Component loading occurs in `loadAgents`, `loadCommands`, and `loadSkills` (lines 57-99 in [`src/parsers/claude.ts`](https://github.com/EveryInc/compound-engineering-plugin/blob/main/src/parsers/claude.ts)). These functions use `collectMarkdownFiles` to gather markdown files from the resolved directories, then parse each file's frontmatter using `parseFrontmatter` from [`src/utils/frontmatter.ts`](https://github.com/EveryInc/compound-engineering-plugin/blob/main/src/utils/frontmatter.ts). The frontmatter fields (e.g., `name`, `description`, `allowed-tools`) are extracted and typed according to [`src/types/claude.ts`](https://github.com/EveryInc/compound-engineering-plugin/blob/main/src/types/claude.ts).

### Hooks Configuration

Hooks are loaded via `loadHooks` (lines 21-44 in [`src/parsers/claude.ts`](https://github.com/EveryInc/compound-engineering-plugin/blob/main/src/parsers/claude.ts)). The parser first checks for a default [`hooks/hooks.json`](https://github.com/EveryInc/compound-engineering-plugin/blob/main/hooks/hooks.json) file, then processes any custom hook paths defined in the manifest. After loading multiple hook configurations, `mergeHooks` combines them into a single `ClaudeHooks` map, with later configurations overriding earlier ones.

### MCP Server Definitions

MCP servers are handled by `loadMcpServers` (lines 46-64 in [`src/parsers/claude.ts`](https://github.com/EveryInc/compound-engineering-plugin/blob/main/src/parsers/claude.ts)). Definitions may be inline in the manifest, referenced via file paths, or stored in a [`.mcp.json`](https://github.com/EveryInc/compound-engineering-plugin/blob/main/.mcp.json) file at the plugin root. All path references undergo the same `resolveWithinRoot` validation before being loaded and merged into a unified map of server configurations.

## Complete Plugin Loading Example

```typescript
import { loadClaudePlugin } from "./src/parsers/claude"

const pluginRoot = "/path/to/plugins/compound-engineering"

try {
  const plugin = await loadClaudePlugin(pluginRoot)
  console.log(`✅ Loaded ${plugin.manifest.name} v${plugin.manifest.version}`)
  console.log(`Agents:   ${plugin.agents.length}`)
  console.log(`Commands: ${plugin.commands.length}`)
  console.log(`MCP Servers: ${Object.keys(plugin.mcpServers).length}`)
} catch (err) {
  console.error("Failed to load plugin:", err.message)
}

```

## Summary

- The parser enforces strict **path containment** via `resolveWithinRoot` to prevent directory traversal attacks.
- **Custom component directories** are supported but must validate against the plugin root before loading.
- **Frontmatter parsing** extracts metadata from markdown files for agents, commands, and skills using `parseFrontmatter`.
- **Hooks and MCP servers** support configuration merging from multiple sources (inline, default files, and custom paths).
- The entire pipeline returns a **fully typed `ClaudePlugin`** object defined in [`src/types/claude.ts`](https://github.com/EveryInc/compound-engineering-plugin/blob/main/src/types/claude.ts).

## Frequently Asked Questions

### What happens if the plugin.json file is missing?

If the manifest file cannot be found at [`.claude-plugin/plugin.json`](https://github.com/EveryInc/compound-engineering-plugin/blob/main/.claude-plugin/plugin.json) or the specified path, `resolveClaudeRoot` throws an error immediately. This prevents the parser from attempting to load an invalid or incomplete plugin structure.

### How does the parser prevent path traversal attacks?

The `resolveWithinRoot` function (lines 45-52 in [`src/parsers/claude.ts`](https://github.com/EveryInc/compound-engineering-plugin/blob/main/src/parsers/claude.ts)) validates that all resolved paths start with the plugin root directory using `resolvedPath.startsWith(resolvedRoot + path.sep)`. Any path attempting to escape the root (e.g., containing `../`) triggers a clear error message.

### Can I use custom directories for agents and commands?

Yes. The manifest supports custom paths for agents, commands, skills, and hooks. The `resolveComponentDirs` function processes these paths, but each must pass the `resolveWithinRoot` validation to ensure it remains within the plugin directory.

### What is the difference between hooks and MCP servers?

Hooks are event-driven configurations loaded from [`hooks.json`](https://github.com/EveryInc/compound-engineering-plugin/blob/main/hooks.json) files that define callbacks or triggers within the Claude plugin lifecycle. MCP (Model Context Protocol) servers are external service definitions that may be inline, referenced by path, or stored in [`.mcp.json`](https://github.com/EveryInc/compound-engineering-plugin/blob/main/.mcp.json), enabling the plugin to connect to external tools and data sources.