# Validation Rules for Plugin Manifests in the Compound Engineering Plugin

> Learn the essential validation rules for Compound Engineering Plugin manifests. Ensure secure loading with correct JSON syntax, name, version fields, and valid paths.

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

---

**The Compound Engineering Plugin enforces strict validation rules on [`plugin.json`](https://github.com/EveryInc/compound-engineering-plugin/blob/main/plugin.json) manifests during loading, requiring valid JSON syntax, mandatory `name` and `version` string fields, and path entries that must resolve within the plugin root directory to prevent directory traversal attacks.**

When loading a Claude plugin, the `loadClaudePlugin()` function in the EveryInc/compound-engineering-plugin repository performs comprehensive validation on the manifest file located at [`.claude-plugin/plugin.json`](https://github.com/EveryInc/compound-engineering-plugin/blob/main/.claude-plugin/plugin.json). These validation rules for plugin manifests ensure security, structural integrity, and compatibility without relying on external schema validation libraries.

## Required and Optional Manifest Fields

The `ClaudeManifest` type defined in [`src/types/claude.ts`](https://github.com/EveryInc/compound-engineering-plugin/blob/main/src/types/claude.ts) establishes the contract for valid manifests.

### Mandatory Fields

Every manifest must include two required top-level fields:

- **name**: A string identifying the plugin
- **version**: A string specifying the semantic version

These fields are non-optional in the TypeScript interface (lines 10-13), and downstream code assumes their presence.

### Optional Fields

The manifest may optionally include:

- `description`, `author`, `keywords` for metadata
- `agents`, `commands`, `skills` for component paths
- `hooks` for lifecycle hooks
- `mcpServers` for Model Context Protocol server configurations

## Path Validation and Security Rules

The most critical validation rules for plugin manifests involve path resolution security. The system prevents directory traversal attacks through strict path validation.

### The resolveWithinRoot Guard

In [`src/parsers/claude.ts`](https://github.com/EveryInc/compound-engineering-plugin/blob/main/src/parsers/claude.ts) (lines 45-52), the `resolveWithinRoot()` function enforces that all custom paths must remain within the plugin root directory:

```typescript
function resolveWithinRoot(root: string, entry: string, label: string): string {
  const resolvedRoot = path.resolve(root)
  const resolvedPath = path.resolve(root, entry)
  if (resolvedPath === resolvedRoot || resolvedPath.startsWith(resolvedRoot + path.sep)) {
    return resolvedPath
  }
  throw new Error(`Invalid ${label}: ${entry}. Paths must stay within the plugin root.`)
}

```

### Component Path Validation

For `agents`, `commands`, and `skills` fields, the `resolveComponentDirs()` function (lines 81-90) normalizes entries using `toPathList()` and validates each through `resolveWithinRoot()`. Entries must be either strings or arrays of strings representing paths relative to the plugin root.

### Hooks and MCP Server Path Validation

The `loadHooks()` function (lines 21-44) and `loadMcpServers()` function (lines 151-164) apply the same path validation. For `hooks`, values can be strings, string arrays, or full `ClaudeHooks` objects. For `mcpServers`, values can be objects, strings, or string arrays, with paths resolved through `loadMcpPaths()` → `resolveWithinRoot()`.

## MCP Server Configuration Fallback

When the manifest omits the `mcpServers` field, the loader implements a fallback mechanism. In [`src/parsers/claude.ts`](https://github.com/EveryInc/compound-engineering-plugin/blob/main/src/parsers/claude.ts) (lines 59-64), `loadMcpServers()` checks for the presence of a [`.mcp.json`](https://github.com/EveryInc/compound-engineering-plugin/blob/main/.mcp.json) file in the plugin root directory and reads it if the manifest does not provide explicit MCP configuration.

## JSON Syntax and Error Handling

### Valid JSON Requirement

The `readJson()` utility (referenced in [`src/parsers/claude.ts`](https://github.com/EveryInc/compound-engineering-plugin/blob/main/src/parsers/claude.ts) line 19 and implemented in [`src/utils/files.ts`](https://github.com/EveryInc/compound-engineering-plugin/blob/main/src/utils/files.ts)) parses the manifest file and throws immediately if the JSON is malformed.

### Explicit Error Messages

Validation failures produce specific error messages that identify the problematic field and the offending path. The test suite in [`tests/claude-parser.test.ts`](https://github.com/EveryInc/compound-engineering-plugin/blob/main/tests/claude-parser.test.ts) (lines 94-108) verifies these messages for invalid component paths, hook paths, and MCP paths, ensuring developers receive actionable feedback when validation rules for plugin manifests are violated.

## Code Examples

### Loading a Well-Formed Plugin

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

const pluginRoot = path.join(import.meta.dir, "fixtures", "sample-plugin")
const plugin = await loadClaudePlugin(pluginRoot)

console.log(plugin.manifest.name)        // "compound-engineering"
console.log(plugin.manifest.version)     // "1.0.0"

```

The manifest must contain at least `name` and `version`; the rest is optional.

### Invalid Custom Path – Throws

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

await loadClaudePlugin("fixtures/invalid-command-path")
  // → Error: Invalid commands path: ../outside-commands. Paths must stay within the plugin root.

```

The same check applies to `hooks` and `mcpServers` paths.

### Falling Back to [`.mcp.json`](https://github.com/EveryInc/compound-engineering-plugin/blob/main/.mcp.json)

```typescript
const plugin = await loadClaudePlugin("fixtures/mcp-file")
console.log(plugin.mcpServers?.remote?.url) // "https://example.com/stream"

```

When the manifest does not define `mcpServers`, the loader reads [`.mcp.json`](https://github.com/EveryInc/compound-engineering-plugin/blob/main/.mcp.json).

## Summary

- **Mandatory fields**: Every manifest must include `name` and `version` as strings, defined in [`src/types/claude.ts`](https://github.com/EveryInc/compound-engineering-plugin/blob/main/src/types/claude.ts).
- **Path security**: All custom paths for components, hooks, and MCP servers must resolve within the plugin root via `resolveWithinRoot()` in [`src/parsers/claude.ts`](https://github.com/EveryInc/compound-engineering-plugin/blob/main/src/parsers/claude.ts).
- **Flexible configuration**: Optional fields support multiple formats (strings, arrays, or objects) with normalization via `toPathList()`.
- **MCP fallback**: If `mcpServers` is omitted, the loader falls back to [`.mcp.json`](https://github.com/EveryInc/compound-engineering-plugin/blob/main/.mcp.json) in the plugin root.
- **Explicit errors**: Validation failures throw descriptive messages identifying the specific field and path violation.

## Frequently Asked Questions

### What happens if the plugin.json file contains invalid JSON?

The `readJson()` function throws an error immediately during the loading process, preventing the plugin from initializing. This check occurs in [`src/parsers/claude.ts`](https://github.com/EveryInc/compound-engineering-plugin/blob/main/src/parsers/claude.ts) before any field validation begins.

### Can I use absolute paths for agents, commands, or skills in the manifest?

No. The `resolveWithinRoot()` function explicitly rejects any path that resolves outside the plugin root directory. All paths must be relative to the plugin root and remain within its boundaries to prevent directory traversal attacks.

### What is the difference between hooks and skills in the manifest validation?

While both support string or array-of-string path specifications, `skills` are validated through `resolveComponentDirs()` alongside `agents` and `commands`, whereas `hooks` are processed by `loadHooks()` which additionally accepts a full `ClaudeHooks` object format. Both undergo the same `resolveWithinRoot()` security check.

### Is the mcpServers field required in every plugin manifest?

No. The `mcpServers` field is optional. When omitted, the loader automatically checks for a [`.mcp.json`](https://github.com/EveryInc/compound-engineering-plugin/blob/main/.mcp.json) file in the plugin root directory and uses that as the MCP configuration source instead.