Skip to content

Commit 98e3344

Browse files
committed
WIP
1 parent 6509330 commit 98e3344

30 files changed

Lines changed: 1872 additions & 209 deletions

.smithery/index.cjs

Lines changed: 199 additions & 177 deletions
Large diffs are not rendered by default.

docs/ARCHITECTURE.md

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -223,14 +223,14 @@ export async function someToolLogic(
223223
executor: CommandExecutor,
224224
): Promise<ToolResponse> {
225225
log('info', `Executing some_tool with param: ${params.requiredParam}`);
226-
226+
227227
try {
228228
const result = await executor(['some', 'command'], 'Some Tool Operation');
229-
229+
230230
if (!result.success) {
231231
return createErrorResponse('Operation failed', result.error);
232232
}
233-
233+
234234
return createTextResponse(`✅ Success: ${result.output}`);
235235
} catch (error) {
236236
const errorMessage = error instanceof Error ? error.message : String(error);
@@ -243,7 +243,7 @@ export default {
243243
name: 'some_tool',
244244
description: 'Tool description for AI agents. Example: some_tool({ requiredParam: "value" })',
245245
schema: someToolSchema.shape, // Expose shape for MCP SDK
246-
246+
247247
// 5. Create the handler using the type-safe factory
248248
handler: createTypedTool(
249249
someToolSchema,
@@ -260,6 +260,15 @@ This pattern ensures that:
260260
- Import paths use focused facades for clear dependency management
261261
```
262262
263+
### Debugger Subsystem
264+
265+
The debugging workflow relies on a long-lived, interactive LLDB subprocess. A `DebuggerManager` owns the session lifecycle and routes tool calls to a backend implementation. The default backend is the LLDB CLI (`xcrun lldb --no-lldbinit`) and configures a unique prompt sentinel to safely read command results. A stub DAP backend exists for future expansion.
266+
267+
Key elements:
268+
- **Interactive execution**: Uses a dedicated interactive spawner with `stdin: 'pipe'` so LLDB commands can be streamed across multiple tool calls.
269+
- **Session manager**: Tracks debug session metadata (session id, simulator id, pid, timestamps) and maintains a “current” session.
270+
- **Backend abstraction**: `DebuggerBackend` keeps the tool contract stable while allowing future DAP support.
271+
263272
### MCP Resources System
264273
265274
XcodeBuildMCP provides dual interfaces: traditional MCP tools and efficient MCP resources for supported clients. Resources are located in `src/mcp/resources/` and are automatically discovered **at build time**. The build process generates `src/core/generated-resources.ts`, which contains dynamic loaders for each resource, improving startup performance. For more details on creating resources, see the [Plugin Development Guide](docs/PLUGIN_DEVELOPMENT.md).
@@ -432,7 +441,7 @@ describe('Tool Name', () => {
432441

433442
// 2. Call the tool's logic function, injecting the mock executor
434443
const result = await someToolLogic({ requiredParam: 'value' }, mockExecutor);
435-
444+
436445
// 3. Assert the final result
437446
expect(result).toEqual({
438447
content: [{ type: 'text', text: 'Expected output' }],

docs/DEBUGGING_ARCHITECTURE.md

Lines changed: 277 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,277 @@
1+
# Debugging Architecture
2+
3+
This document describes how the simulator debugging tools are wired, how sessions are managed,
4+
and how external tools (simctl, Simulator, LLDB, xcodebuild) are invoked.
5+
6+
## Scope
7+
8+
- Tools: `src/mcp/tools/debugging/*`
9+
- Debugger subsystem: `src/utils/debugger/*`
10+
- Execution and tool wiring: `src/utils/typed-tool-factory.ts`, `src/utils/execution/*`
11+
- External invocation: `xcrun simctl`, `xcrun lldb`, `xcodebuild`
12+
13+
## Registration and Wiring
14+
15+
- Workflow discovery is automatic: `src/core/plugin-registry.ts` loads debugging tools via the
16+
generated workflow loaders (`src/core/generated-plugins.ts`).
17+
- Tool handlers are created with the typed tool factory:
18+
- `createTypedToolWithContext` for standard tools (Zod validation + dependency injection).
19+
- `createSessionAwareToolWithContext` for session-aware tools (merges session defaults and
20+
validates requirements).
21+
- Debugging tools inject a `DebuggerToolContext` that provides:
22+
- `executor`: a `CommandExecutor` used for simctl and other command execution.
23+
- `debugger`: a shared `DebuggerManager` instance.
24+
25+
## Session Defaults and Validation
26+
27+
- Session defaults live in `src/utils/session-store.ts` and are merged with user args by the
28+
session-aware tool factory.
29+
- `debug_attach_sim` is session-aware; it can omit `simulatorId`/`simulatorName` in the public
30+
schema and rely on session defaults.
31+
- The `XCODEBUILDMCP_DISABLE_SESSION_DEFAULTS` env flag exposes legacy schemas that include all
32+
parameters (no session default hiding).
33+
34+
## Debug Session Lifecycle
35+
36+
`DebuggerManager` owns lifecycle, state, and backend routing:
37+
38+
Backend selection happens inside `DebuggerManager.createSession`:
39+
40+
- Selection order: explicit `backend` argument -> `XCODEBUILDMCP_DEBUGGER_BACKEND` -> default `lldb-cli`.
41+
- Env values: `lldb-cli`/`lldb` -> `lldb-cli`, `dap` -> `dap`, anything else throws.
42+
- Backend factory: `defaultBackendFactory` maps `lldb-cli` to `createLldbCliBackend` and `dap` to
43+
`createDapBackend`. A custom factory can be injected for tests or extensions.
44+
45+
1. `debug_attach_sim` resolves simulator UUID and PID, then calls
46+
`DebuggerManager.createSession`.
47+
2. `DebuggerManager` creates a backend (default `lldb-cli`), attaches to the process, and stores
48+
session metadata (id, simulatorId, pid, timestamps).
49+
3. Debugging tools (`debug_lldb_command`, `debug_stack`, `debug_variables`,
50+
`debug_breakpoint_add/remove`) look up the session (explicit id or current) and route commands
51+
to the backend.
52+
4. `debug_detach` calls `DebuggerManager.detachSession` to detach and dispose the backend.
53+
54+
## Debug Session + Command Execution Flow
55+
56+
Session lifecycle flow (text):
57+
58+
1. Client calls `debug_attach_sim`.
59+
2. `debug_attach_sim` resolves simulator UUID and PID, then calls `DebuggerManager.createSession`.
60+
3. `DebuggerManager.createSession` resolves backend kind (explicit/env/default), instantiates the
61+
backend, and calls `backend.attach`.
62+
4. Command tools (`debug_lldb_command`, `debug_stack`, `debug_variables`) call
63+
`DebuggerManager.runCommand`/`getStack`/`getVariables`, which route to the backend.
64+
5. `debug_detach` calls `DebuggerManager.detachSession`, which invokes `backend.detach` and
65+
`backend.dispose`.
66+
67+
`LldbCliBackend.runCommand()` flow (text):
68+
69+
1. Enqueue the command to serialize LLDB access.
70+
2. Await backend readiness (`initialize` completed).
71+
3. Write the command to the interactive process.
72+
4. Write `script print("__XCODEBUILDMCP_DONE__")` to emit the sentinel marker.
73+
5. Buffer stdout/stderr until the sentinel is detected.
74+
6. Trim the buffer to the next prompt, sanitize output, and return the result.
75+
76+
<details>
77+
<summary>Sequence diagrams (Mermaid)</summary>
78+
79+
```mermaid
80+
sequenceDiagram
81+
participant U as User/Client
82+
participant A as debug_attach_sim
83+
participant M as DebuggerManager
84+
participant F as backendFactory
85+
participant B as DebuggerBackend (lldb-cli|dap)
86+
participant L as LldbCliBackend
87+
participant P as InteractiveProcess (xcrun lldb)
88+
89+
U->>A: debug_attach_sim(simulator*, bundleId|pid)
90+
A->>A: determineSimulatorUuid(...)
91+
A->>A: resolveSimulatorAppPid(...) (if bundleId)
92+
A->>M: createSession({simulatorId, pid, waitFor})
93+
M->>M: resolveBackendKind(explicit/env/default)
94+
M->>F: create backend(kind)
95+
F-->>M: backend instance
96+
M->>B: attach({pid, simulatorId, waitFor})
97+
alt kind == lldb-cli
98+
B-->>L: (is LldbCliBackend)
99+
L->>P: spawn xcrun lldb + initialize prompt/sentinel
100+
else kind == dap
101+
B-->>M: throws DAP_ERROR_MESSAGE
102+
end
103+
M-->>A: DebugSessionInfo {id, backend, ...}
104+
A->>M: setCurrentSession(id) (optional)
105+
U->>M: runCommand(id?, "thread backtrace")
106+
M->>B: runCommand(...)
107+
U->>M: detachSession(id?)
108+
M->>B: detach()
109+
M->>B: dispose()
110+
```
111+
112+
```mermaid
113+
sequenceDiagram
114+
participant T as debug_lldb_command
115+
participant M as DebuggerManager
116+
participant L as LldbCliBackend
117+
participant P as InteractiveProcess
118+
participant S as stdout/stderr buffer
119+
120+
T->>M: runCommand(sessionId?, command, {timeoutMs?})
121+
M->>L: runCommand(command)
122+
L->>L: enqueue(work)
123+
L->>L: await ready (initialize())
124+
L->>P: write(command + "\n")
125+
L->>P: write('script print("__XCODEBUILDMCP_DONE__")\n')
126+
P-->>S: stdout/stderr chunks
127+
S-->>L: handleData() appends to buffer
128+
L->>L: checkPending() finds sentinel
129+
L->>L: slice output up to sentinel
130+
L->>L: trim buffer to next prompt (if present)
131+
L->>L: sanitizeOutput() + trimEnd()
132+
L-->>M: output string
133+
M-->>T: output string
134+
```
135+
136+
</details>
137+
138+
## LLDB CLI Backend (Default)
139+
140+
- Backend implementation: `src/utils/debugger/backends/lldb-cli-backend.ts`.
141+
- Uses `InteractiveSpawner` from `src/utils/execution/interactive-process.ts` to keep a single
142+
long-lived `xcrun lldb` process alive.
143+
- Keeps LLDB state (breakpoints, selected frames, target) across tool calls without reattaching.
144+
145+
### Internals: interactive process model
146+
147+
- The backend spawns `xcrun lldb --no-lldbinit -o "settings set prompt <prompt>"`.
148+
- `InteractiveProcess.write()` is used to send commands; stdout and stderr are merged into a single
149+
parse buffer.
150+
- `InteractiveProcess.dispose()` closes stdin, removes listeners, and kills the process.
151+
152+
### Prompt and sentinel protocol
153+
154+
The backend uses a prompt + sentinel protocol to detect command completion reliably:
155+
156+
- `LLDB_PROMPT = "XCODEBUILDMCP_LLDB> "`
157+
- `COMMAND_SENTINEL = "__XCODEBUILDMCP_DONE__"`
158+
159+
Definitions:
160+
161+
- Prompt: the LLDB REPL prompt string that indicates LLDB is ready to accept the next command.
162+
- Sentinel: a unique marker explicitly printed after each command to mark the end of that
163+
command's output.
164+
165+
Protocol flow:
166+
167+
1. Startup: write `script print("__XCODEBUILDMCP_DONE__")` to prime the prompt parser.
168+
2. For each command:
169+
- Write the command.
170+
- Write `script print("__XCODEBUILDMCP_DONE__")`.
171+
- Read until the sentinel is observed, then trim up to the next prompt.
172+
173+
The sentinel marks command completion, while the prompt indicates the REPL is ready for the next
174+
command.
175+
176+
Why both a prompt and a sentinel?
177+
178+
- The sentinel is the explicit end-of-output marker; LLDB does not provide a reliable boundary for
179+
arbitrary command output otherwise.
180+
- The prompt is used to cleanly align the buffer for the next command after the sentinel is seen.
181+
182+
Annotated example (simplified):
183+
184+
1. Backend writes:
185+
- `thread backtrace`
186+
- `script print("__XCODEBUILDMCP_DONE__")`
187+
2. LLDB emits (illustrative):
188+
- `... thread backtrace output ...`
189+
- `__XCODEBUILDMCP_DONE__`
190+
- `XCODEBUILDMCP_LLDB> `
191+
3. Parser behavior:
192+
- Sentinel marks the end of the command output payload.
193+
- Prompt is used to trim the buffer so the next command starts cleanly.
194+
195+
### Output parsing and sanitization
196+
197+
- `handleData()` appends to an internal buffer, and `checkPending()` scans for the sentinel regex
198+
`/(^|\\r?\\n)__XCODEBUILDMCP_DONE__(\\r?\\n)/`.
199+
- Output is the buffer up to the sentinel. The remainder is trimmed to the next prompt if present.
200+
- `sanitizeOutput()` removes prompt echoes, sentinel lines, the `script print(...)` lines, and empty
201+
lines, then `runCommand()` returns `trimEnd()` output.
202+
203+
### Concurrency model (queueing)
204+
205+
- Commands are serialized through a promise queue to avoid interleaved output.
206+
- `waitForSentinel()` rejects if a pending command exists, acting as a safety check.
207+
208+
### Timeouts, errors, and disposal
209+
210+
- Startup timeout: `DEFAULT_STARTUP_TIMEOUT_MS = 10_000`.
211+
- Per-command timeout: `DEFAULT_COMMAND_TIMEOUT_MS = 30_000` (override via `runCommand` opts).
212+
- Timeout failure clears the pending command and rejects the promise.
213+
- `assertNoLldbError()` throws if `/error:/i` appears in output (simple heuristic).
214+
- Process exit triggers `failPending(new Error(...))` so in-flight calls fail promptly.
215+
- `runCommand()` rejects immediately if the backend is already disposed.
216+
217+
### Testing and injection
218+
219+
`getDefaultInteractiveSpawner()` throws in test environments to prevent spawning real interactive
220+
processes. Tests should inject a mock `InteractiveSpawner` into `createLldbCliBackend()` or a custom
221+
`DebuggerManager` backend factory.
222+
223+
## DAP Backend (Stub / Not Implemented)
224+
225+
- Implementation: `src/utils/debugger/backends/dap-backend.ts`.
226+
- Selected via backend selection (explicit `backend`, `XCODEBUILDMCP_DEBUGGER_BACKEND=dap`).
227+
- Current status: not implemented. All `DebuggerBackend` methods throw `DAP_ERROR_MESSAGE`,
228+
including `dispose()`.
229+
230+
Practical effect:
231+
232+
- Setting `XCODEBUILDMCP_DEBUGGER_BACKEND=dap` causes `debug_attach_sim` to fail during
233+
session creation because `backend.attach()` throws.
234+
- `DebuggerManager.createSession` attempts to call `dispose()` on failure, but the stub `dispose()`
235+
also throws (same message). This is expected until a real DAP backend exists.
236+
237+
Use `lldb-cli` (default) for actual debugging.
238+
239+
## External Tool Invocation
240+
241+
### simctl and Simulator
242+
243+
- Simulator UUID resolution uses `xcrun simctl list devices available -j`
244+
(`determineSimulatorUuid` in `src/utils/simulator-utils.ts`).
245+
- PID lookup uses `xcrun simctl spawn <simulatorId> launchctl list`
246+
(`resolveSimulatorAppPid` in `src/utils/debugger/simctl.ts`).
247+
248+
### LLDB
249+
250+
- Attachment uses `xcrun lldb --no-lldbinit` in the interactive backend.
251+
- Breakpoint conditions are applied by issuing an additional LLDB command:
252+
`breakpoint modify -c "<condition>" <id>`.
253+
254+
### xcodebuild (Build/Launch Context)
255+
256+
- Debugging assumes a running simulator app.
257+
- The typical flow is build and launch via simulator tools (for example `build_sim`),
258+
which uses `executeXcodeBuildCommand` to invoke `xcodebuild` (or `xcodemake` when enabled).
259+
- After launch, `debug_attach_sim` attaches LLDB to the simulator process.
260+
261+
## Typical Tool Flow
262+
263+
1. Build and launch the app on a simulator (`build_sim`, `launch_app_sim`).
264+
2. Attach LLDB (`debug_attach_sim`) using session defaults or explicit simulator + bundle ID.
265+
3. Set breakpoints (`debug_breakpoint_add`), inspect stack/variables (`debug_stack`,
266+
`debug_variables`), and issue arbitrary LLDB commands (`debug_lldb_command`).
267+
4. Detach when done (`debug_detach`).
268+
269+
## Integration Points Summary
270+
271+
- Tool entrypoints: `src/mcp/tools/debugging/*`
272+
- Session defaults: `src/utils/session-store.ts`
273+
- Debug session manager: `src/utils/debugger/debugger-manager.ts`
274+
- Backends: `src/utils/debugger/backends/lldb-cli-backend.ts` (default),
275+
`src/utils/debugger/backends/dap-backend.ts` (stub)
276+
- Interactive execution: `src/utils/execution/interactive-process.ts` (used by LLDB CLI backend)
277+
- External commands: `xcrun simctl`, `xcrun lldb`, `xcodebuild`

docs/TOOLS.md

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# XcodeBuildMCP Tools Reference
22

3-
XcodeBuildMCP provides 63 tools organized into 12 workflow groups for comprehensive Apple development workflows.
3+
XcodeBuildMCP provides 70 tools organized into 13 workflow groups for comprehensive Apple development workflows.
44

55
## Workflow Groups
66

@@ -68,6 +68,16 @@ XcodeBuildMCP provides 63 tools organized into 12 workflow groups for comprehens
6868
- `session_clear_defaults` - Clear selected or all session defaults.
6969
- `session_set_defaults` - Set the session defaults needed by many tools. Most tools require one or more session defaults to be set before they can be used. Agents should set all relevant defaults up front in a single call (e.g., project/workspace, scheme, simulator or device ID, useLatestOS) to avoid iterative prompts; only set the keys your workflow needs.
7070
- `session_show_defaults` - Show current session defaults.
71+
### Simulator Debugging (`debugging`)
72+
**Purpose**: Interactive iOS Simulator debugging tools: attach LLDB, manage breakpoints, inspect stack/variables, and run LLDB commands. (7 tools)
73+
74+
- `debug_attach_sim` - Attach LLDB to a running iOS simulator app. Provide bundleId or pid, plus simulator defaults.
75+
- `debug_breakpoint_add` - Add a breakpoint by file/line or function name for the active debug session.
76+
- `debug_breakpoint_remove` - Remove a breakpoint by id for the active debug session.
77+
- `debug_detach` - Detach the current debugger session or a specific debugSessionId.
78+
- `debug_lldb_command` - Run an arbitrary LLDB command within the active debug session.
79+
- `debug_stack` - Return a thread backtrace from the active debug session.
80+
- `debug_variables` - Return variables for a selected frame in the active debug session.
7181
### Simulator Management (`simulator-management`)
7282
**Purpose**: Tools for managing simulators from booting, opening simulators, listing simulators, stopping simulators, erasing simulator content and settings, and setting simulator environment options like location, network, statusbar and appearance. (5 tools)
7383

@@ -93,7 +103,7 @@ XcodeBuildMCP provides 63 tools organized into 12 workflow groups for comprehens
93103
**Purpose**: UI automation and accessibility testing tools for iOS simulators. Perform gestures, interactions, screenshots, and UI analysis for automated testing workflows. (11 tools)
94104

95105
- `button` - Press hardware button on iOS simulator. Supported buttons: apple-pay, home, lock, side-button, siri
96-
- `describe_ui` - Gets entire view hierarchy with precise frame coordinates (x, y, width, height) for all visible elements. Use this before UI interactions or after layout changes - do NOT guess coordinates from screenshots. Returns JSON tree with frame data for accurate automation.
106+
- `describe_ui` - Gets entire view hierarchy with precise frame coordinates (x, y, width, height) for all visible elements. Use this before UI interactions or after layout changes - do NOT guess coordinates from screenshots. Returns JSON tree with frame data for accurate automation. Requires the target process to be running; paused debugger/breakpoints can yield an empty tree.
97107
- `gesture` - Perform gesture on iOS simulator using preset gestures: scroll-up, scroll-down, scroll-left, scroll-right, swipe-from-left-edge, swipe-from-right-edge, swipe-from-top-edge, swipe-from-bottom-edge
98108
- `key_press` - Press a single key by keycode on the simulator. Common keycodes: 40=Return, 42=Backspace, 43=Tab, 44=Space, 58-67=F1-F10.
99109
- `key_sequence` - Press key sequence using HID keycodes on iOS simulator with configurable delay
@@ -106,9 +116,9 @@ XcodeBuildMCP provides 63 tools organized into 12 workflow groups for comprehens
106116

107117
## Summary Statistics
108118

109-
- **Total Tools**: 63 canonical tools + 22 re-exports = 85 total
110-
- **Workflow Groups**: 12
119+
- **Total Tools**: 70 canonical tools + 22 re-exports = 92 total
120+
- **Workflow Groups**: 13
111121

112122
---
113123

114-
*This documentation is automatically generated by `scripts/update-tools-docs.ts` using static analysis. Last updated: 2025-12-30*
124+
*This documentation is automatically generated by `scripts/update-tools-docs.ts` using static analysis. Last updated: 2026-01-02*

0 commit comments

Comments
 (0)