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.
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.
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 manifests define individual tools and their metadata.
# 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)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: trueid: 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 toolid: 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:
- experimentalWorkflowDiscoveryEnabledWorkflow manifests define groups of related tools.
# 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: [])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 toolsid: 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:
- doctorid: 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 | 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 |
| 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) |
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:
- Named exports (preferred):
{ schema, handler } - Legacy default export:
export default { schema, handler }
Note: name, description, and annotations are defined in the YAML manifest, not the module.
Example module structure:
// 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
}- Use
snake_case:build_sim,list_devices - Must match the YAML filename (without
.yaml) - Must be unique across all tools
- Use
snake_caseorkebab-caseconsistently - Must be globally unique across all tools
- This is what LLMs see and call
- Optional; if omitted, derived from MCP name
- Derivation:
snake_case→kebab-case(build_sim→build-sim) - Use
kebab-casefor explicit names
- Use
kebab-case:simulator,swift-package,ui-automation - Must match the YAML filename (without
.yaml)
Predicates control visibility based on runtime context. All predicates in the array must pass (AND logic) for the tool/workflow to be visible.
| Predicate | Description |
|---|---|
debugEnabled |
Show only when config.debug is true |
experimentalWorkflowDiscoveryEnabled |
Show only when experimental workflow discovery is enabled |
mcpRuntimeOnly |
Show only in MCP runtime (hide in CLI/daemon catalogs) |
runningUnderXcodeAgent |
Show only when running under Xcode's coding agent |
hideWhenXcodeAgentMode |
Hide when running inside Xcode's coding agent (tools conflict with Xcode's native equivalents) |
xcodeAutoSyncDisabled |
Show only when running under Xcode and config.disableXcodeAutoSync is true |
always |
Always visible (explicit documentation) |
never |
Never visible (temporarily disable) |
Notes:
- Bridge availability/connection is handled at tool call time, not as a visibility predicate.
- Prefer runtime/config predicates for deterministic tool exposure.
Predicates receive a context object:
interface PredicateContext {
runtime: 'cli' | 'mcp' | 'daemon';
config: ResolvedRuntimeConfig;
runningUnderXcode: boolean;
}To add a new predicate, edit src/visibility/predicate-registry.ts:
export const PREDICATES: Record<string, PredicateFn> = {
// Existing predicates...
myNewPredicate: (ctx: PredicateContext): boolean => {
return ctx.config.someFlag === true;
},
};For MCP runtime, workflows are selected based on these rules (in order):
- Auto-include workflows (
autoInclude: true) when their predicates pass - Explicitly requested workflows from
config.enabledWorkflows - Default workflows (
defaultEnabled: true) whenconfig.enabledWorkflowsis empty - All selected workflows are filtered by availability + predicates
# Always included (autoInclude with no predicates = always passes)
selection:
mcp:
autoInclude: true
# Enabled by default when no workflows configured
selection:
mcp:
defaultEnabled: true
# MCP-only workflow/tool visibility
predicates:
- mcpRuntimeOnly
# Auto-included only when predicates pass (e.g., debug mode)
selection:
mcp:
autoInclude: true
predicates:
- debugEnabled
# Show only when manual Xcode sync is needed
predicates:
- xcodeAutoSyncDisabledA single tool can belong to multiple workflows. This is useful for shared utilities:
# 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_deviceThe tool is defined once in manifests/tools/clean.yaml but referenced by both workflows.
Daemon routing is intentionally simple:
routing.stateful: true: CLI routes this tool through the daemon.routingomitted orstateful: false: CLI runs the tool directly.- Special-case: dynamic
xcode-idebridge tools use daemon-backed routing for bridge session persistence.
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.
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 cachingsrc/core/manifest/import-tool-module.ts- Dynamic module importssrc/utils/tool-registry.ts- MCP server tool registrationsrc/runtime/tool-catalog.ts- CLI/daemon tool catalog buildingsrc/visibility/exposure.ts- Workflow/tool visibility filtering
- Create the tool module in
src/mcp/tools/<category>/<tool_name>.ts - Create the manifest in
manifests/tools/<tool_name>.yaml - Add to workflow(s) in
manifests/workflows/<workflow>.yaml - 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
- Create the manifest in
manifests/workflows/<workflow_id>.yaml - Add tool references (tools must already exist)
- Configure selection rules for MCP behavior
- Run tests to validate
Example checklist:
- Workflow ID matches filename
- All referenced tool IDs exist
- Selection rules are appropriate
- Predicates reference valid predicate names