# Manifest Format Reference
This document describes the YAML manifest format used to define tools and workflows in XcodeBuildMCP. Manifests are the single source of truth for tool/workflow metadata, visibility rules, and runtime behavior.
## Overview
Manifests are stored in the `manifests/` directory:
```
manifests/
├── tools/ # Tool manifest files
│ ├── build_sim.yaml
│ ├── list_sims.yaml
│ └── ...
└── workflows/ # Workflow manifest files
├── simulator.yaml
├── device.yaml
└── ...
```
Each tool and workflow has its own YAML file. The manifest loader reads all files at startup and validates them against the schema.
## Directory Structure
Tool implementations live in `src/mcp/tools/<category>/`:
```
src/mcp/tools/
├── simulator/
│ ├── build_sim.ts # Tool implementation
│ ├── build_run_sim.ts
│ ├── list_sims.ts
│ └── ...
├── device/
│ ├── build_device.ts
│ └── ...
└── ...
```
## Tool Manifest Format
Tool manifests define individual tools and their metadata.
### Schema
```yaml
# Required fields
id: string # Unique tool identifier (must match filename without .yaml)
module: string # Module path (see Module Path section)
names:
mcp: string # MCP tool name (globally unique, used in MCP protocol)
cli: string # CLI command name (optional, derived from mcp if omitted)
# Optional fields
description: string # Tool description (shown in tool listings)
availability: # Per-runtime availability flags
mcp: boolean # Available via MCP server (default: true)
cli: boolean # Available via CLI (default: true)
predicates: string[] # Predicate names for visibility filtering (default: [])
routing: # CLI daemon routing
stateful: boolean # Tool maintains state (default: false)
annotations: # MCP tool annotations (hints for clients)
title: string # Human-readable title (optional)
readOnlyHint: boolean # Tool only reads data (optional)
destructiveHint: boolean # Tool may modify/delete data (optional)
idempotentHint: boolean # Safe to retry (optional)
openWorldHint: boolean # May access external resources (optional)
```
### Example: Basic Tool
```yaml
id: list_sims
module: mcp/tools/simulator/list_sims
names:
mcp: list_sims
description: "List available iOS simulators."
availability:
mcp: true
cli: true
predicates: []
annotations:
title: "List Simulators"
readOnlyHint: true
```
### Example: Tool with Predicates
```yaml
id: build_sim
module: mcp/tools/simulator/build_sim
names:
mcp: build_sim
description: "Build for iOS sim."
availability:
mcp: true
cli: true
predicates:
- hideWhenXcodeAgentMode # Hidden when Xcode provides equivalent tool
```
### Example: MCP-Only Tool
```yaml
id: manage_workflows
module: mcp/tools/workflow-discovery/manage_workflows
names:
mcp: manage-workflows # Note: MCP name uses hyphens
description: "Manage enabled workflows at runtime."
availability:
mcp: true
cli: false # Not available in CLI
predicates:
- experimentalWorkflowDiscoveryEnabled
```
## Workflow Manifest Format
Workflow manifests define groups of related tools.
### Schema
```yaml
# Required fields
id: string # Unique workflow identifier (must match filename without .yaml)
title: string # Display title
description: string # Workflow description
tools: string[] # Array of tool IDs belonging to this workflow
# Optional fields
availability: # Per-runtime availability flags
mcp: boolean # Available via MCP server (default: true)
cli: boolean # Available via CLI (default: true)
selection: # MCP selection rules
mcp:
defaultEnabled: boolean # Enabled when config.enabledWorkflows is empty (default: false)
autoInclude: boolean # Include when predicates pass, even if not requested (default: false)
predicates: string[] # Predicate names for visibility filtering (default: [])
```
### Example: Default-Enabled Workflow
```yaml
id: simulator
title: "iOS Simulator Development"
description: "Complete iOS development workflow for simulators."
availability:
mcp: true
cli: true
selection:
mcp:
defaultEnabled: true # Enabled by default
autoInclude: false
predicates: []
tools:
- list_sims
- boot_sim
- build_sim
- build_run_sim
- test_sim
# ... more tools
```
### Example: Auto-Include Workflow
```yaml
id: doctor
title: "MCP Doctor"
description: "Diagnostic tool for the MCP server environment."
availability:
mcp: true
cli: true
selection:
mcp:
defaultEnabled: false
autoInclude: true # Auto-included when predicates pass
predicates:
- debugEnabled # Only shown in debug mode
tools:
- doctor
```
### Example: Conditional Workflow
```yaml
id: workflow-discovery
title: "Workflow Discovery"
description: "Manage enabled workflows at runtime."
availability:
mcp: true
cli: false
selection:
mcp:
defaultEnabled: false
autoInclude: true
predicates:
- experimentalWorkflowDiscoveryEnabled # Feature flag
tools:
- manage_workflows
```
## Field Reference
### Tool Fields
| Field | Type | Required | Default | Description |
|-------|------|----------|---------|-------------|
| `id` | string | Yes | - | Unique identifier, must match filename |
| `module` | string | Yes | - | Module path relative to `src/` (extensionless) |
| `names.mcp` | string | Yes | - | MCP protocol tool name |
| `names.cli` | string | No | Derived from MCP name | CLI command name |
| `description` | string | No | - | Tool description |
| `availability.mcp` | boolean | No | `true` | Available via MCP |
| `availability.cli` | boolean | No | `true` | Available via CLI |
| `predicates` | string[] | No | `[]` | Visibility predicates (all must pass) |
| `routing.stateful` | boolean | No | `false` | Tool maintains state |
| `annotations.title` | string | No | - | Human-readable title |
| `annotations.readOnlyHint` | boolean | No | - | Tool only reads data |
| `annotations.destructiveHint` | boolean | No | - | Tool may modify/delete data |
| `annotations.idempotentHint` | boolean | No | - | Safe to retry |
| `annotations.openWorldHint` | boolean | No | - | May access external resources |
### Workflow Fields
| Field | Type | Required | Default | Description |
|-------|------|----------|---------|-------------|
| `id` | string | Yes | - | Unique identifier, must match filename |
| `title` | string | Yes | - | Display title |
| `description` | string | Yes | - | Workflow description |
| `tools` | string[] | Yes | - | Tool IDs in this workflow |
| `availability.mcp` | boolean | No | `true` | Available via MCP |
| `availability.cli` | boolean | No | `true` | Available via CLI |
| `selection.mcp.defaultEnabled` | boolean | No | `false` | Enabled when no workflows configured |
| `selection.mcp.autoInclude` | boolean | No | `false` | Auto-include when predicates pass |
| `predicates` | string[] | No | `[]` | Visibility predicates (all must pass) |
## Module Path
The `module` field specifies where to find the tool implementation. It uses a package-relative path without file extension:
```
mcp/tools/<category>/<tool_name>
```
At runtime, this resolves to:
```
build/mcp/tools/<category>/<tool_name>.js
```
The module must export either:
1. **Named exports** (preferred): `{ schema, handler }`
2. **Legacy default export**: `export default { schema, handler }`
Note: `name`, `description`, and `annotations` are defined in the YAML manifest, not the module.
Example module structure:
```typescript
// src/mcp/tools/simulator/build_sim.ts
import { z } from 'zod';
export const schema = z.object({
projectPath: z.string().describe('Path to project'),
// ...
});
export async function handler(params: z.infer<typeof schema>) {
// Implementation
}
```
## Naming Conventions
### Tool ID
- Use `snake_case`: `build_sim`, `list_devices`
- Must match the YAML filename (without `.yaml`)
- Must be unique across all tools
### MCP Name (`names.mcp`)
- Use `snake_case` or `kebab-case` consistently
- Must be globally unique across all tools
- This is what LLMs see and call
### CLI Name (`names.cli`)
- Optional; if omitted, derived from MCP name
- Derivation: `snake_case` → `kebab-case` (`build_sim` → `build-sim`)
- Use `kebab-case` for explicit names
### Workflow ID
- Use `kebab-case`: `simulator`, `swift-package`, `ui-automation`
- Must match the YAML filename (without `.yaml`)
## Predicates
Predicates control visibility based on runtime context. All predicates in the array must pass (AND logic) for the tool/workflow to be visible.
### Available Predicates
| Predicate | Description |
|-----------|-------------|
| `debugEnabled` | Show only when `config.debug` is `true` |
| `experimentalWorkflowDiscoveryEnabled` | Show only when experimental workflow discovery is enabled |
| `runningUnderXcodeAgent` | Show only when running under Xcode's coding agent |
| `requiresXcodeTools` | Show only when Xcode Tools bridge is active |
| `hideWhenXcodeAgentMode` | Hide when running inside Xcode's coding agent (tools conflict with Xcode's native equivalents) |
| `always` | Always visible (explicit documentation) |
| `never` | Never visible (temporarily disable) |
### Predicate Context
Predicates receive a context object:
```typescript
interface PredicateContext {
runtime: 'cli' | 'mcp' | 'daemon';
config: ResolvedRuntimeConfig;
runningUnderXcode: boolean;
xcodeToolsActive: boolean;
}
```
### Adding New Predicates
To add a new predicate, edit `src/visibility/predicate-registry.ts`:
```typescript
export const PREDICATES: Record<string, PredicateFn> = {
// Existing predicates...
myNewPredicate: (ctx: PredicateContext): boolean => {
return ctx.config.someFlag === true;
},
};
```
## Workflow Selection Rules
For MCP runtime, workflows are selected based on these rules (in order):
1. **Auto-include workflows** (`autoInclude: true`) when their predicates pass
2. **Explicitly requested workflows** from `config.enabledWorkflows`
3. **Default workflows** (`defaultEnabled: true`) when `config.enabledWorkflows` is empty
4. All selected workflows are filtered by availability + predicates
### Selection Examples
```yaml
# Always included (autoInclude with no predicates = always passes)
selection:
mcp:
autoInclude: true
# Enabled by default when no workflows configured
selection:
mcp:
defaultEnabled: true
# Auto-included only when predicates pass (e.g., debug mode)
selection:
mcp:
autoInclude: true
predicates:
- debugEnabled
```
## Tool Re-export
A single tool can belong to multiple workflows. This is useful for shared utilities:
```yaml
# manifests/workflows/simulator.yaml
tools:
- clean # Shared tool
- discover_projs # Shared tool
- build_sim
# manifests/workflows/device.yaml
tools:
- clean # Same tool, different workflow
- discover_projs # Same tool, different workflow
- build_device
```
The tool is defined once in `manifests/tools/clean.yaml` but referenced by both workflows.
## Daemon Routing
Daemon routing is intentionally simple:
- **`routing.stateful: true`**: CLI routes this tool through the daemon.
- **`routing` omitted or `stateful: false`**: CLI runs the tool directly.
- **Special-case**: dynamic `xcode-ide` bridge tools use daemon-backed routing for bridge session persistence.
## Validation
Manifests are validated at load time against Zod schemas. Invalid manifests cause startup failures with descriptive error messages.
The schema definitions are in `src/core/manifest/schema.ts`.
## Runtime Tool Registration
At startup, tools are registered dynamically from manifests:
```
1. loadManifest()
└── Reads all YAML files from manifests/tools/ and manifests/workflows/
└── Validates against Zod schemas
└── Returns { tools: Map, workflows: Map }
2. selectWorkflowsForMcp(workflows, requestedWorkflows, ctx)
└── Filters workflows by availability (mcp: true)
└── Applies selection rules (defaultEnabled, autoInclude)
└── Evaluates predicates against context
3. For each selected workflow:
└── For each tool ID in workflow.tools:
└── Look up tool manifest by ID
└── Check tool availability and predicates
└── importToolModule(module) → { schema, handler, annotations }
└── server.registerTool(mcpName, schema, handler)
```
Key files:
- `src/core/manifest/load-manifest.ts` - Manifest loading and caching
- `src/core/manifest/import-tool-module.ts` - Dynamic module imports
- `src/utils/tool-registry.ts` - MCP server tool registration
- `src/runtime/tool-catalog.ts` - CLI/daemon tool catalog building
- `src/visibility/exposure.ts` - Workflow/tool visibility filtering
## Creating a New Tool
1. **Create the tool module** in `src/mcp/tools/<category>/<tool_name>.ts`
2. **Create the manifest** in `manifests/tools/<tool_name>.yaml`
3. **Add to workflow(s)** in `manifests/workflows/<workflow>.yaml`
4. **Run tests** to validate
Example checklist:
- [ ] Tool ID matches filename
- [ ] Module path is correct
- [ ] MCP name is unique
- [ ] Tool is added to at least one workflow
- [ ] Predicates reference valid predicate names
- [ ] Availability flags match intended runtimes
## Creating a New Workflow
1. **Create the manifest** in `manifests/workflows/<workflow_id>.yaml`
2. **Add tool references** (tools must already exist)
3. **Configure selection rules** for MCP behavior
4. **Run tests** to validate
Example checklist:
- [ ] Workflow ID matches filename
- [ ] All referenced tool IDs exist
- [ ] Selection rules are appropriate
- [ ] Predicates reference valid predicate names