Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions app/docs/_components/live.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,14 @@ export function LiveWorkflowToolCount({ workflow }: { workflow: string }) {

export function LiveWorkflowsTable() {
const manifest = useManifest()
const hasTargetPlatformData = manifest.workflows.some((w) => w.targetPlatforms.length > 0)

return (
<table className="docs-table">
<thead>
<tr>
<th>Workflow</th>
{hasTargetPlatformData ? <th>Recommended for</th> : null}
<th style={{ textAlign: "right" }}>Tools</th>
<th>Description</th>
</tr>
Expand All @@ -54,6 +57,21 @@ export function LiveWorkflowsTable() {
</span>
) : null}
</td>
{hasTargetPlatformData ? (
<td>
{w.targetPlatforms.length > 0 ? (
<span style={{ display: "inline-flex", flexWrap: "wrap", gap: 4 }}>
{w.targetPlatforms.map((platform) => (
<span key={platform} className="tc-badge">
{platform}
</span>
))}
</span>
) : (
<span style={{ opacity: 0.6 }}>not platform-specific</span>
)}
</td>
) : null}
<td style={{ textAlign: "right" }}>{w.tools.length}</td>
<td>{w.description}</td>
</tr>
Expand Down
17 changes: 16 additions & 1 deletion app/docs/_content/architecture-manifest-visibility.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ Keeping that metadata in YAML prevents each runtime from inventing its own catal
| Directory | Defines | Used by |
|-----------|---------|---------|
| `manifests/tools/` | Tool ID, implementation module, MCP and CLI names, description, annotations, availability, predicates, routing, next steps, output schema metadata. | MCP registration, CLI command generation, daemon catalog, generated docs, schema checks. |
| `manifests/workflows/` | Workflow ID and tool membership. | Workflow selection, sidebar grouping in generated references, MCP catalog trimming. |
| `manifests/workflows/` | Workflow ID, tool membership, and `targetPlatforms` setup guidance. | Workflow selection, sidebar grouping in generated references, MCP catalog trimming, setup wizard recommendations. |
| `manifests/resources/` | MCP resource metadata and module handler. | MCP resource registration. |

Tool modules are imported only after the manifest has been loaded and filtered. A tool module must export named `schema` and `handler` values. Resource modules have their own contract and require a handler, so avoid treating the tool-module contract as universal.
Expand Down Expand Up @@ -83,6 +83,21 @@ Workflow selection is the coarse catalog control. It keeps MCP sessions small an

That order matters for contributors. Put durable product grouping in workflow manifests. Put conditional exposure in predicates. Put public runtime support in the `availability` object.

## Workflow `targetPlatforms` is setup guidance, not exposure

Workflow manifests carry a `targetPlatforms` field listing the Apple platforms (`iOS`, `macOS`, `tvOS`, `watchOS`, `visionOS`) the workflow is recommended for. The setup wizard reads this to ask the user which platforms they target and to highlight matching workflows. The conservative default selection stays narrow: `simulator` for any simulator platform, `macos` when macOS is selected. Everything else is recommended-but-opt-in, and the user can still pick any workflow regardless of platform.

A few things `targetPlatforms` is deliberately not:

- Not an exposure check. It does not replace predicates, the `availability` object, or per-tool capability checks at runtime.
- Not unsupported when empty. `targetPlatforms: []` means the workflow is not specific to a target platform (typical for `session-management`, `workflow-discovery`, or `doctor`); it is still exposed normally.
- Not authoritative for individual tools. It describes the workflow as a unit; tool-level platform support stays in the implementation and predicates.

When authoring a new workflow:

- Set `targetPlatforms` to every platform the workflow is genuinely useful on. `swift-package` covers all five Apple platforms; `macos` is `macOS` only; `ui-automation` is `iOS` only today.
- Leave `targetPlatforms: []` for workflows that are not user-selected by platform (session management, workflow discovery, doctor).

## Authoring implications

When adding or changing a tool, update the manifest first enough to make the intended exposure explicit:
Expand Down
10 changes: 10 additions & 0 deletions app/docs/_content/setup.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,23 @@ xcodebuildmcp setup

The wizard walks you through:

- **Target platforms**: which Apple platforms you build for (macOS, iOS, tvOS, watchOS, visionOS). Selecting at least one platform shapes the rest of the wizard, including which workflows are recommended and whether you need to pick a simulator.
- **Workflows**: which tool groups to enable (simulator, device, macOS, debugging, UI automation, etc.)
- **Project defaults**: your workspace or project path, scheme, configuration
- **Simulator defaults**: preferred simulator by name or UDID
- **Debug options**: whether to enable debug logging and the doctor tool

Non-interactive mode is available for CI and scripts (`xcodebuildmcp setup --help`).

### Recommended vs default workflows

The wizard splits workflows into two groups based on your target platforms:

- **Recommended**: workflows whose `targetPlatforms` overlap your selection. The wizard pre-selects only the conservative defaults: `simulator` for any simulator platform (iOS, tvOS, watchOS, visionOS) and `macos` when macOS is selected. Other recommended workflows (for example `device`, `debugging`, `ui-automation`, `swift-package`) are listed alongside but left unchecked, so you opt in to what you need.
- **Additional**: every other workflow. These are still selectable, just not flagged as a fit for your platforms.

Target platforms are guidance for setup only. They do not gate tool exposure at runtime: any workflow you enable still advertises its tools through MCP regardless of platform metadata. Predicates and the `availability` field handle runtime gating, see [Workflows](/docs/workflows) and [MCP Server Mode → Workflow selection](/docs/mcp-mode#workflow-selection) for the full picture.

<Callout variant="info" title="No MCP client context? No problem.">
If your client doesn't read `.xcodebuildmcp/config.yaml` from the workspace (some don't), you can export an env-based config block with `xcodebuildmcp setup --format mcp-json` and paste it into your MCP client config.
</Callout>
Expand Down
11 changes: 11 additions & 0 deletions app/docs/_data/fetch-manifest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ import "server-only"
import { load as loadYaml } from "js-yaml"
import {
BUNDLED_MANIFEST,
WORKFLOW_TARGET_PLATFORMS,
type ManifestSnapshot,
type ToolEntry,
type WorkflowEntry,
type WorkflowTargetPlatform,
} from "./manifests"

const REPO = "getsentry/XcodeBuildMCP"
Expand Down Expand Up @@ -94,6 +96,14 @@ function normalizeTools(raw: Array<Record<string, unknown>>): ToolEntry[] {
.sort((a, b) => a.mcpName.localeCompare(b.mcpName))
}

function normalizeTargetPlatforms(value: unknown): WorkflowTargetPlatform[] {
if (!Array.isArray(value)) return []
return value.filter(
(p): p is WorkflowTargetPlatform =>
typeof p === "string" && (WORKFLOW_TARGET_PLATFORMS as readonly string[]).includes(p)
)
}

function normalizeWorkflows(raw: Array<Record<string, unknown>>): WorkflowEntry[] {
return raw
.map((w) => {
Expand All @@ -104,6 +114,7 @@ function normalizeWorkflows(raw: Array<Record<string, unknown>>): WorkflowEntry[
title: (w.title as string | undefined) ?? String(w.id ?? ""),
description: (w.description as string | undefined) ?? "",
defaultEnabled: Boolean(selection.mcp?.defaultEnabled),
targetPlatforms: normalizeTargetPlatforms(w.targetPlatforms),
tools: Array.isArray(w.tools) ? (w.tools as string[]) : [],
}
})
Expand Down
42 changes: 41 additions & 1 deletion app/docs/_data/manifests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,22 @@ export interface ToolEntry {
predicates: string[]
}

export const WORKFLOW_TARGET_PLATFORMS = [
"iOS",
"macOS",
"tvOS",
"watchOS",
"visionOS",
] as const

export type WorkflowTargetPlatform = (typeof WORKFLOW_TARGET_PLATFORMS)[number]

export interface WorkflowEntry {
id: string
title: string
description: string
defaultEnabled: boolean
targetPlatforms: WorkflowTargetPlatform[]
tools: string[]
}

Expand All @@ -30,11 +41,40 @@ export interface ManifestSnapshot {
tools: ToolEntry[]
}

function normalizeBundledWorkflow(w: Record<string, unknown>): WorkflowEntry {
const platforms = Array.isArray(w.targetPlatforms)
? (w.targetPlatforms as unknown[]).filter(
(p): p is WorkflowTargetPlatform =>
typeof p === "string" &&
(WORKFLOW_TARGET_PLATFORMS as readonly string[]).includes(p)
)
: []
return {
id: String(w.id ?? ""),
title: typeof w.title === "string" ? w.title : String(w.id ?? ""),
description: typeof w.description === "string" ? w.description : "",
defaultEnabled: Boolean(w.defaultEnabled),
targetPlatforms: platforms,
tools: Array.isArray(w.tools) ? (w.tools as string[]) : [],
}
}

/**
* Bundled snapshot from `pnpm run docs:sync`. Used as a fallback when the
* GitHub API is unreachable or rate-limited at request time.
*
* Snapshots produced before workflows gained `targetPlatforms` are tolerated
* by defaulting the field to an empty array.
*/
export const BUNDLED_MANIFEST: ManifestSnapshot = snapshot as ManifestSnapshot
export const BUNDLED_MANIFEST: ManifestSnapshot = (() => {
const raw = snapshot as Omit<ManifestSnapshot, "workflows"> & {
workflows: Array<Record<string, unknown>>
}
return {
...raw,
workflows: raw.workflows.map(normalizeBundledWorkflow),
} as ManifestSnapshot
})()

/**
* Default export for legacy imports that haven't migrated to the provider
Expand Down
8 changes: 8 additions & 0 deletions scripts/sync-xcodebuildmcp-manifests.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -130,13 +130,21 @@ function normalizeTools(raw) {
.sort((a, b) => a.mcpName.localeCompare(b.mcpName))
}

const WORKFLOW_TARGET_PLATFORMS = new Set(["iOS", "macOS", "tvOS", "watchOS", "visionOS"])

function normalizeTargetPlatforms(value) {
if (!Array.isArray(value)) return []
return value.filter((p) => typeof p === "string" && WORKFLOW_TARGET_PLATFORMS.has(p))
}

function normalizeWorkflows(raw) {
return raw
.map((w) => ({
id: w.id,
title: w.title ?? w.id,
description: w.description ?? "",
defaultEnabled: Boolean(w.selection?.mcp?.defaultEnabled),
targetPlatforms: normalizeTargetPlatforms(w.targetPlatforms),
tools: Array.isArray(w.tools) ? w.tools : [],
}))
.sort((a, b) => a.id.localeCompare(b.id))
Expand Down
Loading