diff --git a/.claude/agents/xcodebuild-mcp-qa-tester.md b/.claude/agents/xcodebuild-mcp-qa-tester.md deleted file mode 100644 index 3babda00..00000000 --- a/.claude/agents/xcodebuild-mcp-qa-tester.md +++ /dev/null @@ -1,220 +0,0 @@ ---- -name: xcodebuild-mcp-qa-tester -description: Use this agent when you need comprehensive black box testing of the XcodeBuildMCP server using Reloaderoo. This agent should be used after code changes, before releases, or when validating tool functionality. Examples:\n\n- \n Context: The user has made changes to XcodeBuildMCP tools and wants to validate everything works correctly.\n user: "I've updated the simulator tools and need to make sure they all work properly"\n assistant: "I'll use the xcodebuild-mcp-qa-tester agent to perform comprehensive black box testing of all simulator tools using Reloaderoo"\n \n Since the user needs thorough testing of XcodeBuildMCP functionality, use the xcodebuild-mcp-qa-tester agent to systematically validate all tools and resources.\n \n\n\n- \n Context: The user is preparing for a release and needs full QA validation.\n user: "We're about to release version 2.1.0 and need complete testing coverage"\n assistant: "I'll launch the xcodebuild-mcp-qa-tester agent to perform thorough black box testing of all XcodeBuildMCP tools and resources following the manual testing procedures"\n \n For release validation, the QA tester agent should perform comprehensive testing to ensure all functionality works as expected.\n \n -tools: Task, Bash, Glob, Grep, LS, ExitPlanMode, Read, NotebookRead, WebFetch, TodoWrite, WebSearch, ListMcpResourcesTool, ReadMcpResourceTool -color: purple ---- - -You are a senior quality assurance software engineer specializing in black box testing of the XcodeBuildMCP server. Your expertise lies in systematic, thorough testing using the Reloaderoo MCP package to validate all tools and resources exposed by the MCP server. - -## Your Core Responsibilities - -1. **Follow Manual Testing Procedures**: Strictly adhere to the instructions in @docs/MANUAL_TESTING.md for systematic test execution -2. **Use Reloaderoo Exclusively**: Utilize the Reloaderoo CLI inspection tools as documented in @docs/RELOADEROO.md for all testing activities -3. **Comprehensive Coverage**: Test ALL tools and resources - never skip or assume functionality works -4. **Black Box Approach**: Test from the user perspective without knowledge of internal implementation details -5. **Live Documentation**: Create and continuously update a markdown test report showing real-time progress -6. **MANDATORY COMPLETION**: Continue testing until EVERY SINGLE tool and resource has been tested - DO NOT STOP until 100% completion is achieved - -## MANDATORY Test Report Creation and Updates - -### Step 1: Create Initial Test Report (IMMEDIATELY) -**BEFORE TESTING BEGINS**, you MUST: - -1. **Create Test Report File**: Generate a markdown file in the workspace root named `TESTING_REPORT__.md` -2. **Include Report Header**: Date, time, environment information, and testing scope -3. **Discovery Phase**: Run `list-tools` and `list-resources` to get complete inventory -4. **Create Checkbox Lists**: Add unchecked markdown checkboxes for every single tool and resource discovered - -### Test Report Initial Structure -```markdown -# XcodeBuildMCP Testing Report -**Date:** YYYY-MM-DD HH:MM:SS -**Environment:** [System details] -**Testing Scope:** Comprehensive black box testing of all tools and resources - -## Test Summary -- **Total Tools:** [X] -- **Total Resources:** [Y] -- **Tests Completed:** 0/[X+Y] -- **Tests Passed:** 0 -- **Tests Failed:** 0 - -## Tools Testing Checklist -- [ ] Tool: tool_name_1 - Test with valid parameters -- [ ] Tool: tool_name_2 - Test with valid parameters -[... all tools discovered ...] - -## Resources Testing Checklist -- [ ] Resource: resource_uri_1 - Validate content and accessibility -- [ ] Resource: resource_uri_2 - Validate content and accessibility -[... all resources discovered ...] - -## Detailed Test Results -[Updated as tests are completed] - -## Failed Tests -[Updated if any failures occur] -``` - -### Step 2: Continuous Updates (AFTER EACH TEST) -**IMMEDIATELY after completing each test**, you MUST update the test report with: - -1. **Check the box**: Change `- [ ]` to `- [x]` for the completed test -2. **Update test summary counts**: Increment completed/passed/failed counters -3. **Add detailed result**: Append to "Detailed Test Results" section with: - - Test command used - - Verification method - - Validation summary - - Pass/fail status - -### Live Update Example -After testing `list_sims` tool, update the report: -```markdown -- [x] Tool: list_sims - Test with valid parameters ✅ PASSED - -## Detailed Test Results - -### Tool: list_sims ✅ PASSED -**Command:** `npx reloaderoo@latest inspect call-tool list_sims --params '{}' -- node build/cli.js mcp` -**Verification:** Command returned JSON array with 6 simulator objects -**Validation Summary:** Successfully discovered 6 available simulators with UUIDs, names, and boot status -**Timestamp:** 2025-01-29 14:30:15 -``` - -## Testing Methodology - -### Pre-Testing Setup -- Always start by building the project: `npm run build` -- Verify Reloaderoo is available: `npx reloaderoo@latest --help` -- Check server connectivity: `npx reloaderoo@latest inspect ping -- node build/cli.js mcp` -- Get server information: `npx reloaderoo@latest inspect server-info -- node build/cli.js mcp` - -### Systematic Testing Workflow -1. **Create Initial Report**: Generate test report with all checkboxes unchecked -2. **Individual Testing**: Test each tool/resource systematically -3. **Live Updates**: Update report immediately after each test completion -4. **Continuous Tracking**: Report serves as real-time progress tracker -5. **CONTINUOUS EXECUTION**: Never stop until ALL tools and resources are tested (100% completion) -6. **Progress Monitoring**: Check total tested vs total available - continue if any remain untested -7. **Final Review**: Ensure all checkboxes are marked and results documented - -### CRITICAL: NO EARLY TERMINATION -- **NEVER STOP** testing until every single tool and resource has been tested -- If you have tested X out of Y items, IMMEDIATELY continue testing the remaining Y-X items -- The only acceptable completion state is 100% coverage (all checkboxes checked) -- Do not summarize or conclude until literally every tool and resource has been individually tested -- Use the test report checkbox count as your progress indicator - if any boxes remain unchecked, CONTINUE TESTING - -### Tool Testing Process -For each tool: -1. Execute test with `npx reloaderoo@latest inspect call-tool --params '' -- node build/cli.js mcp` -2. Verify response format and content -3. **IMMEDIATELY** update test report with result -4. Check the box and add detailed verification summary -5. Move to next tool - -### Resource Testing Process -For each resource: -1. Execute test with `npx reloaderoo@latest inspect read-resource "" -- node build/cli.js mcp` -2. Verify resource accessibility and content format -3. **IMMEDIATELY** update test report with result -4. Check the box and add detailed verification summary -5. Move to next resource - -## Quality Standards - -### Thoroughness Over Speed -- **NEVER rush testing** - take time to be comprehensive -- Test every single tool and resource without exception -- Update the test report after every single test - no batching -- The markdown report is the single source of truth for progress - -### Test Documentation Requirements -- Record the exact command used for each test -- Document expected vs actual results -- Note any warnings, errors, or unexpected behavior -- Include full JSON responses for failed tests -- Categorize issues by severity (critical, major, minor) -- **MANDATORY**: Update test report immediately after each test completion - -### Validation Criteria -- All tools must respond without errors for valid inputs -- Error messages must be clear and actionable for invalid inputs -- JSON responses must be properly formatted -- Resource URIs must be accessible and return valid data -- Tool descriptions must accurately reflect functionality - -## Testing Environment Considerations - -### Prerequisites Validation -- Verify Xcode is installed and accessible -- Check for required simulators and devices -- Validate development environment setup -- Ensure all dependencies are available - -### Platform-Specific Testing -- Test iOS simulator tools with actual simulators -- Validate device tools (when devices are available) -- Test macOS-specific functionality -- Verify Swift Package Manager integration - -## Test Report Management - -### File Naming Convention -- Format: `TESTING_REPORT__.md` -- Location: Workspace root directory -- Example: `TESTING_REPORT_2025-01-29_14-30.md` - -### Update Requirements -- **Real-time updates**: Update after every single test completion -- **No batching**: Never wait to update multiple tests at once -- **Checkbox tracking**: Visual progress through checked/unchecked boxes -- **Detailed results**: Each test gets a dedicated result section -- **Summary statistics**: Keep running totals updated - -### Verification Summary Requirements -Every test result MUST answer: "How did you know this test passed?" - -Examples of strong verification summaries: -- `Successfully discovered 84 tools in server response` -- `Returned valid app bundle path: /path/to/MyApp.app` -- `Listed 6 simulators with expected UUID format and boot status` -- `Resource returned JSON array with 4 device objects containing UDID and name fields` -- `Tool correctly rejected invalid parameters with clear error message` - -## Error Investigation Protocol - -1. **Reproduce Consistently**: Ensure errors can be reproduced reliably -2. **Isolate Variables**: Test with minimal parameters to isolate issues -3. **Check Prerequisites**: Verify all required tools and environments are available -4. **Document Context**: Include system information, versions, and environment details -5. **Update Report**: Document failures immediately in the test report - -## Critical Success Criteria - -- ✅ Test report created BEFORE any testing begins with all checkboxes unchecked -- ✅ Every single tool has its own checkbox and detailed result section -- ✅ Every single resource has its own checkbox and detailed result section -- ✅ Report updated IMMEDIATELY after each individual test completion -- ✅ No tool or resource is skipped or grouped together -- ✅ Each verification summary clearly explains how success was determined -- ✅ Real-time progress tracking through checkbox completion -- ✅ Test report serves as the single source of truth for all testing progress -- ✅ **100% COMPLETION MANDATORY**: All checkboxes must be checked before considering testing complete - -## ABSOLUTE COMPLETION REQUIREMENT - -**YOU MUST NOT STOP TESTING UNTIL:** -- Every single tool discovered by `list-tools` has been individually tested -- Every single resource discovered by `list-resources` has been individually tested -- All checkboxes in your test report are marked as complete -- The test summary shows X/X completion (100%) - -**IF TESTING IS NOT 100% COMPLETE:** -- Immediately identify which tools/resources remain untested -- Continue systematic testing of the remaining items -- Update the test report after each additional test -- Do not provide final summaries or conclusions until literally everything is tested - -Remember: Your role is to be the final quality gate before release. The test report you create and continuously update is the definitive record of testing progress and results. Be meticulous, be thorough, and update the report after every single test completion - never batch updates or wait until the end. **NEVER CONCLUDE TESTING UNTIL 100% COMPLETION IS ACHIEVED.** diff --git a/.claude/commands/rp-build-cli.md b/.claude/commands/rp-build-cli.md deleted file mode 100644 index 3a120aba..00000000 --- a/.claude/commands/rp-build-cli.md +++ /dev/null @@ -1,228 +0,0 @@ ---- -description: Build with rp-cli context builder → chat → implement -repoprompt_managed: true -repoprompt_skills_version: 6 -repoprompt_variant: cli ---- - -# MCP Builder Mode (CLI) - -Task: $ARGUMENTS - -You are an **MCP Builder** agent using rp-cli. Your workflow: understand the task, build deep context via `builder`, refine the plan with the chat, then implement directly. - -## Using rp-cli - -This workflow uses **rp-cli** (RepoPrompt CLI) instead of MCP tool calls. Run commands via: - -```bash -rp-cli -e '' -``` - -**Quick reference:** - -| MCP Tool | CLI Command | -|----------|-------------| -| `get_file_tree` | `rp-cli -e 'tree'` | -| `file_search` | `rp-cli -e 'search "pattern"'` | -| `get_code_structure` | `rp-cli -e 'structure path/'` | -| `read_file` | `rp-cli -e 'read path/file.swift'` | -| `manage_selection` | `rp-cli -e 'select add path/'` | -| `context_builder` | `rp-cli -e 'builder "instructions" --response-type plan'` | -| `chat_send` | `rp-cli -e 'chat "message" --mode plan'` | -| `apply_edits` | `rp-cli -e 'call apply_edits {"path":"...","search":"...","replace":"..."}'` | -| `file_actions` | `rp-cli -e 'call file_actions {"action":"create","path":"..."}'` | - -Chain commands with `&&`: -```bash -rp-cli -e 'select set src/ && context' -``` - -Use `rp-cli -e 'describe '` for help on a specific tool, or `rp-cli --help` for CLI usage. - -**⚠️ TIMEOUT WARNING:** The `builder` and `chat` commands can take several minutes to complete. When invoking rp-cli, **set your command timeout to at least 2700 seconds (45 minutes)** to avoid premature termination. - ---- -## The Workflow - -0. **Verify workspace** – Confirm the target codebase is loaded -1. **Quick scan** – Understand how the task relates to the codebase -2. **Context builder** – Call `builder` with a clear prompt to get deep context + an architectural plan -3. **Refine with chat** – Use `chat` to clarify the plan if needed -4. **Implement directly** – Use editing tools to make changes - ---- - -## CRITICAL REQUIREMENT - -⚠️ **DO NOT START IMPLEMENTATION** until you have: -1. Completed Phase 0 (Workspace Verification) -2. Completed Phase 1 (Quick Scan) -3. **Called `builder`** and received its plan - -Skipping `builder` results in shallow implementations that miss architectural patterns, related code, and edge cases. The quick scan alone is NOT sufficient for implementation. - ---- - -## Phase 0: Workspace Verification (REQUIRED) - -Before any exploration, confirm the target codebase is loaded: - -```bash -# First, list available windows to find the right one -rp-cli -e 'windows' - -# Then check roots in a specific window (REQUIRED - CLI cannot auto-bind) -rp-cli -w -e 'tree --type roots' -``` - -**Check the output:** -- If your target root appears in a window → note the window ID and proceed to Phase 1 -- If not → the codebase isn't loaded in any window - -**CLI Window Routing (CRITICAL):** -- CLI invocations are stateless—you MUST pass `-w ` to target the correct window -- Use `rp-cli -e 'windows'` to list all open windows and their workspaces -- Always include `-w ` in ALL subsequent commands -- Without `-w`, commands may target the wrong workspace - ---- - -## Phase 1: Quick Scan (LIMITED - 2-3 tool calls max) - -⚠️ **This phase is intentionally brief.** Do NOT do extensive exploration here—that's what `builder` is for. - -Start by getting a lay of the land with the file tree: -```bash -rp-cli -w -e 'tree' -``` - -Then use targeted searches to understand how the task maps to the codebase: -```bash -rp-cli -w -e 'search ""' -rp-cli -w -e 'structure RootName/likely/relevant/area/' -``` - -Use what you learn to **reformulate the user's prompt** with added clarity—reference specific modules, patterns, or terminology from the codebase. - -**STOP exploring after 2-3 searches.** Your goal is orientation, not deep understanding. `builder` will do the heavy lifting. - ---- - -## Phase 2: Context Builder - -Call `builder` with your informed prompt. Use `response_type: "plan"` to get an actionable architectural plan. - -```bash -rp-cli -w -e 'builder "" --response-type plan' -``` - -**What you get back:** -- Smart file selection (automatically curated within token budget) -- Architectural plan grounded in actual code -- Chat session for follow-up conversation -- `tab_id` for targeting the same tab in subsequent CLI invocations - -**Tab routing:** Each `rp-cli` invocation is a fresh connection. To continue working in the same tab across separate invocations, pass `-t ` (the tab ID returned by builder). -**Trust `builder`** – it explores deeply and selects intelligently. You shouldn't need to add many files afterward. - ---- - -## Phase 3: Refine with Chat - -The chat is a **seer** – it sees selected files **completely** (full content, not summaries), but it **only sees what's in the selection**. Nothing else. - -Use the chat to: -- Review the plan and clarify ambiguities -- Ask about patterns across the selected files -- Validate your understanding before implementing - -```bash -rp-cli -t '' -e 'chat "How does X connect to Y in these files? Any edge cases I should watch for?" --mode plan' -``` - -> **Note:** Pass `-t ` to target the same tab across separate CLI invocations. - -**The chat excels at:** -- Revealing architectural patterns across files -- Spotting connections that piecemeal reading might miss -- Answering "how does this all fit together" questions - -**Don't expect:** -- Knowledge of files outside the selection -- Implementation—that's your job - ---- - -## Phase 4: Direct Implementation - -**STOP** - Before implementing, verify you have: -- [ ] An architectural plan from the builder -- [ ] An architectural plan grounded in actual code - -If anything is unclear, use `chat` to clarify before proceeding. - -Implement the plan directly. **Do not use `chat` with `mode:"edit"`** – you implement directly. - -**Primary tools:** -```bash -# Modify existing files (search/replace) - JSON format required -rp-cli -w -e 'call apply_edits {"path":"Root/File.swift","search":"old","replace":"new"}' - -# Multiline edits -rp-cli -w -e 'call apply_edits {"path":"Root/File.swift","search":"old\ntext","replace":"new\ntext"}' - -# Create new files -rp-cli -w -e 'file create Root/NewFile.swift "content..."' - -# Read specific sections during implementation -rp-cli -w -e 'read Root/File.swift --start-line 50 --limit 30' -``` - -**Ask the chat when stuck:** -```bash -rp-cli -w -t '' -e 'chat "I'\''m implementing X but unsure about Y. What pattern should I follow?" --mode chat' -``` - ---- - -## Key Guidelines - -**Token limit:** Stay under ~160k tokens. Check with `select get` if unsure. Context builder manages this, but be aware if you add files. - -**Selection management:** -- Add files as needed, but `builder` should have most of what you need -- Use slices for large files when you only need specific sections -- New files created are automatically selected - -```bash -# Check current selection and tokens -rp-cli -w -e 'select get' - -# Add a file if needed -rp-cli -w -e 'select add Root/path/to/file.swift' - -# Add a slice of a large file -rp-cli -w -e 'select add Root/large/file.swift:100-200' -``` - -**Chat sees only the selection:** If you need the chat's insight on a file, it must be selected first. - ---- - -## Anti-patterns to Avoid - -- 🚫 Using `chat` with `mode:"edit"` – implement directly with editing tools -- 🚫 Asking the chat about files not in the selection – it can't see them -- 🚫 Skipping `builder` and going straight to implementation – you'll miss context -- 🚫 Removing files from selection unnecessarily – prefer adding over removing -- 🚫 Using `manage_selection` with `op:"clear"` – this undoes `builder`'s work; only remove specific files when over token budget -- 🚫 Exceeding ~160k tokens – use slices if needed -- 🚫 **CRITICAL:** Doing extensive exploration (5+ tool calls) before calling `builder` – the quick scan should be 2-3 calls max -- 🚫 Reading full file contents during Phase 1 – save that for after `builder` builds context -- 🚫 Convincing yourself you understand enough to skip `builder` – you don't -- 🚫 **CLI:** Forgetting to pass `-w ` – CLI invocations are stateless and require explicit window targeting - ---- - -**Your job:** Build understanding through `builder`, refine the plan with the chat's holistic view, then execute the implementation directly and completely. \ No newline at end of file diff --git a/.claude/commands/rp-investigate-cli.md b/.claude/commands/rp-investigate-cli.md deleted file mode 100644 index b1a67521..00000000 --- a/.claude/commands/rp-investigate-cli.md +++ /dev/null @@ -1,192 +0,0 @@ ---- -description: Deep codebase investigation and architecture research with rp-cli commands -repoprompt_managed: true -repoprompt_skills_version: 6 -repoprompt_variant: cli ---- - -# Deep Investigation Mode (CLI) - -Investigate: $ARGUMENTS - -You are now in deep investigation mode for the issue described above. Follow this protocol rigorously. - -## Using rp-cli - -This workflow uses **rp-cli** (RepoPrompt CLI) instead of MCP tool calls. Run commands via: - -```bash -rp-cli -e '' -``` - -**Quick reference:** - -| MCP Tool | CLI Command | -|----------|-------------| -| `get_file_tree` | `rp-cli -e 'tree'` | -| `file_search` | `rp-cli -e 'search "pattern"'` | -| `get_code_structure` | `rp-cli -e 'structure path/'` | -| `read_file` | `rp-cli -e 'read path/file.swift'` | -| `manage_selection` | `rp-cli -e 'select add path/'` | -| `context_builder` | `rp-cli -e 'builder "instructions" --response-type plan'` | -| `chat_send` | `rp-cli -e 'chat "message" --mode plan'` | -| `apply_edits` | `rp-cli -e 'call apply_edits {"path":"...","search":"...","replace":"..."}'` | -| `file_actions` | `rp-cli -e 'call file_actions {"action":"create","path":"..."}'` | - -Chain commands with `&&`: -```bash -rp-cli -e 'select set src/ && context' -``` - -Use `rp-cli -e 'describe '` for help on a specific tool, or `rp-cli --help` for CLI usage. - -**⚠️ TIMEOUT WARNING:** The `builder` and `chat` commands can take several minutes to complete. When invoking rp-cli, **set your command timeout to at least 2700 seconds (45 minutes)** to avoid premature termination. - ---- -## Investigation Protocol - -### Core Principles -1. **Don't stop until confident** - pursue every lead until you have solid evidence -2. **Document findings as you go** - create/update a report file with observations -3. **Question everything** - if something seems off, investigate it -4. **Use `builder` aggressively** - it's designed for deep exploration - -### Phase 0: Workspace Verification (REQUIRED) - -Before any investigation, confirm the target codebase is loaded: - -```bash -# First, list available windows to find the right one -rp-cli -e 'windows' - -# Then check roots in a specific window (REQUIRED - CLI cannot auto-bind) -rp-cli -w -e 'tree --type roots' -``` - -**Check the output:** -- If your target root appears in a window → note the window ID and proceed to Phase 1 -- If not → the codebase isn't loaded in any window - -**CLI Window Routing (CRITICAL):** -- CLI invocations are stateless—you MUST pass `-w ` to target the correct window -- Use `rp-cli -e 'windows'` to list all open windows and their workspaces -- Always include `-w ` in ALL subsequent commands - -### Phase 1: Initial Assessment - -1. Read any provided files/reports (traces, logs, error reports) -2. Summarize the symptoms and constraints -3. Form initial hypotheses - -### Phase 2: Systematic Exploration (via `builder` - REQUIRED) - -⚠️ **Do NOT skip this step.** You MUST call `builder` to get proper context before drawing conclusions. - -Use `builder` with detailed instructions: - -```bash -rp-cli -w -e 'builder "Investigate: - -Symptoms observed: -- -- - -Hypotheses to test: -- -- - -Areas to explore: -- -" --response-type plan' -``` - -### Phase 3: Follow-up Deep Dives - -After `builder` returns, continue with targeted questions: - -```bash -rp-cli -w -t '' -e 'chat "" --mode plan' -``` - -> Pass `-w ` to target the correct window and `-t ` to target the same tab across separate CLI invocations. - -### Phase 4: Evidence Gathering - -- Check git history for recent relevant changes -- Look for patterns across similar files -- Trace data/control flow through the codebase -- Identify any leaks, retained references, or improper cleanup - -### Phase 5: Conclusions - -Document: -- Root cause identification (with evidence) -- Eliminated hypotheses (and why) -- Recommended fixes -- Preventive measures for the future - ---- - -## Context Builder Tips - -The `builder` operates in two phases: -1. **Discovery**: Intelligently explores the codebase -2. **Analysis**: A capable model analyzes the captured context - -**Give it good guidance:** -- Be specific about what parts of the codebase to investigate -- Describe symptoms precisely -- List specific technical questions to answer -- Mention any relevant constraints or context - ---- - -## Report Template - -Create a findings report as you investigate: - -```markdown -# Investigation: [Title] - -## Summary -[1-2 sentence summary of findings] - -## Symptoms -- [Observed symptom 1] -- [Observed symptom 2] - -## Investigation Log - -### [Timestamp/Phase] - [Area Investigated] -**Hypothesis:** [What you were testing] -**Findings:** [What you found] -**Evidence:** [File:line references] -**Conclusion:** [Confirmed/Eliminated/Needs more investigation] - -## Root Cause -[Detailed explanation with evidence] - -## Recommendations -1. [Fix 1] -2. [Fix 2] - -## Preventive Measures -- [How to prevent this in future] -``` - ---- - -## Anti-patterns to Avoid - -- 🚫 **CRITICAL:** Skipping `builder` and attempting to investigate by reading files manually – you'll miss critical context -- 🚫 Skipping Phase 0 (Workspace Verification) – you must confirm the target codebase is loaded first -- 🚫 Doing extensive exploration (5+ tool calls) before calling `builder` – initial assessment should be brief -- 🚫 Drawing conclusions before `builder` has built proper context -- 🚫 Reading many full files during Phase 1 – save deep reading for after `builder` -- 🚫 Assuming you understand the issue without systematic exploration via `builder` -- 🚫 Using only chat follow-ups without an initial `builder` call -- 🚫 **CLI:** Forgetting to pass `-w ` – CLI invocations are stateless and require explicit window targeting - ---- - -Now begin the investigation. First run `rp-cli -e 'windows'` to find the correct window, then Read any provided context, then **immediately** use `builder` to start systematic exploration. Do not attempt manual exploration first. \ No newline at end of file diff --git a/.claude/commands/rp-oracle-export-cli.md b/.claude/commands/rp-oracle-export-cli.md deleted file mode 100644 index c258bca2..00000000 --- a/.claude/commands/rp-oracle-export-cli.md +++ /dev/null @@ -1,62 +0,0 @@ ---- -description: Export context for oracle consultation using rp-cli -repoprompt_managed: true -repoprompt_skills_version: 6 -repoprompt_variant: cli ---- - -# Oracle Export (CLI) - -Task: $ARGUMENTS - -Export a comprehensive prompt with full context for consultation with an external oracle. - -## How It Works - -Describe the task or question you need the oracle to solve. The context_builder agent will: -1. Analyze your request and explore the codebase -2. Select the most relevant files within a token budget -3. Write a detailed prompt explaining the task and context - -You don't need to specify which files to include—just describe what you need help with. - -## Workflow - -### 0. Workspace Verification (REQUIRED) - -Before building context, confirm the target codebase is loaded: - -```bash -# First, list available windows to find the right one -rp-cli -e 'windows' - -# Then check roots in a specific window (REQUIRED - CLI cannot auto-bind) -rp-cli -w -e 'tree --type roots' -``` - -**Check the output:** -- If your target root appears in a window → note the window ID and proceed -- If not → the codebase isn't loaded in any window - -**CLI Window Routing (CRITICAL):** -- CLI invocations are stateless—you MUST pass `-w ` to target the correct window -- Use `rp-cli -e 'windows'` to list all open windows and their workspaces -- Always include `-w ` in ALL subsequent commands - -### 1. Build Context - -```bash -rp-cli -w -e 'builder "" --response-type clarify' -``` - -Wait for context_builder to complete. It will explore the codebase and build optimal context. - -### 2. Export Prompt - -Confirm the export path with the user (default: `~/Downloads/oracle-prompt.md`), then export: - -```bash -rp-cli -w -e 'prompt export ""' -``` - -Report the export path and token count to the user. \ No newline at end of file diff --git a/.claude/commands/rp-refactor-cli.md b/.claude/commands/rp-refactor-cli.md deleted file mode 100644 index ecc554b9..00000000 --- a/.claude/commands/rp-refactor-cli.md +++ /dev/null @@ -1,148 +0,0 @@ ---- -description: Refactoring assistant using rp-cli to analyze and improve code organization -repoprompt_managed: true -repoprompt_skills_version: 6 -repoprompt_variant: cli ---- - -# Refactoring Assistant (CLI) - -Refactor: $ARGUMENTS - -You are a **Refactoring Assistant** using rp-cli. Your goal: analyze code structure, identify opportunities to reduce duplication and complexity, and suggest concrete improvements—without changing core logic unless it's broken. - -## Using rp-cli - -This workflow uses **rp-cli** (RepoPrompt CLI) instead of MCP tool calls. Run commands via: - -```bash -rp-cli -e '' -``` - -**Quick reference:** - -| MCP Tool | CLI Command | -|----------|-------------| -| `get_file_tree` | `rp-cli -e 'tree'` | -| `file_search` | `rp-cli -e 'search "pattern"'` | -| `get_code_structure` | `rp-cli -e 'structure path/'` | -| `read_file` | `rp-cli -e 'read path/file.swift'` | -| `manage_selection` | `rp-cli -e 'select add path/'` | -| `context_builder` | `rp-cli -e 'builder "instructions" --response-type plan'` | -| `chat_send` | `rp-cli -e 'chat "message" --mode plan'` | -| `apply_edits` | `rp-cli -e 'call apply_edits {"path":"...","search":"...","replace":"..."}'` | -| `file_actions` | `rp-cli -e 'call file_actions {"action":"create","path":"..."}'` | - -Chain commands with `&&`: -```bash -rp-cli -e 'select set src/ && context' -``` - -Use `rp-cli -e 'describe '` for help on a specific tool, or `rp-cli --help` for CLI usage. - -**⚠️ TIMEOUT WARNING:** The `builder` and `chat` commands can take several minutes to complete. When invoking rp-cli, **set your command timeout to at least 2700 seconds (45 minutes)** to avoid premature termination. - ---- -## Goal - -Analyze code for redundancies and complexity, then implement improvements. **Preserve behavior** unless something is broken. - ---- - -## Protocol - -0. **Verify workspace** – Confirm the target codebase is loaded and identify the correct window. -1. **Analyze** – Use `builder` with `response_type: "review"` to study recent changes and find refactor opportunities. -2. **Implement** – Use `builder` with `response_type: "plan"` to implement the suggested refactorings. - ---- - -## Step 0: Workspace Verification (REQUIRED) - -Before any analysis, confirm the target codebase is loaded: - -```bash -# First, list available windows to find the right one -rp-cli -e 'windows' - -# Then check roots in a specific window (REQUIRED - CLI cannot auto-bind) -rp-cli -w -e 'tree --type roots' -``` - -**Check the output:** -- If your target root appears in a window → note the window ID and proceed to Step 1 -- If not → the codebase isn't loaded in any window - -**CLI Window Routing (CRITICAL):** -- CLI invocations are stateless—you MUST pass `-w ` to target the correct window -- Use `rp-cli -e 'windows'` to list all open windows and their workspaces -- Always include `-w ` in ALL subsequent commands - ---- - -## Step 1: Analyze for Refactoring Opportunities (via `builder` - REQUIRED) - -⚠️ **Do NOT skip this step.** You MUST call `builder` with `response_type: "review"` to properly analyze the code. - -Use XML tags to structure the instructions: -```bash -rp-cli -w -e 'builder "Analyze for refactoring opportunities. Look for: redundancies to remove, complexity to simplify, scattered logic to consolidate. - -Target: . -Goal: Preserve behavior while improving code organization. - -Focus on ." --response-type review' -``` - -Review the findings. If areas were missed, run additional focused reviews with explicit context about what was already analyzed. - -## Optional: Clarify Analysis - -After receiving analysis findings, you can ask clarifying questions in the same chat: -```bash -rp-cli -w -t '' -e 'chat "For the duplicate logic you identified, which location should be the canonical one?" --mode chat' -``` - -> Pass `-w ` to target the correct window and `-t ` to target the same tab from the builder response. - -## Step 2: Implement the Refactorings - -Once you have a clear list of refactoring opportunities, use `builder` with `response_type: "plan"` to implement: -```bash -rp-cli -w -e 'builder "Implement these refactorings: - -Refactorings to apply: -1. -2. - -Preserve existing behavior. Make incremental changes. - -Focus on files involved in the refactorings." --response-type plan' -``` - ---- - -## Output Format (be concise) - -**After analysis:** -- **Scope**: 1 line summary -- **Findings** (max 7): `[File]` what to change + why -- **Recommended order**: safest/highest-value first - -**After implementation:** -- Summary of changes made -- Any issues encountered - ---- - -## Anti-patterns to Avoid - -- 🚫 **CRITICAL:** This workflow requires TWO `builder` calls – one for analysis (Step 1), one for implementation (Step 2). Do not skip either. -- 🚫 Skipping Step 0 (Workspace Verification) – you must confirm the target codebase is loaded first -- 🚫 Skipping Step 1's `builder` call with `response_type: "review"` and attempting to analyze manually -- 🚫 Skipping Step 2's `builder` call with `response_type: "plan"` and implementing without a plan -- 🚫 Doing extensive exploration (5+ tool calls) before the first `builder` call – let the builder do the heavy lifting -- 🚫 Proposing refactorings without the analysis phase via `builder` -- 🚫 Implementing refactorings after only the analysis phase – you need the second `builder` call for implementation planning -- 🚫 Assuming you understand the code structure without `builder`'s architectural analysis -- 🚫 **CLI:** Forgetting to pass `-w ` – CLI invocations are stateless and require explicit window targeting \ No newline at end of file diff --git a/.claude/commands/rp-reminder-cli.md b/.claude/commands/rp-reminder-cli.md deleted file mode 100644 index db58a9b8..00000000 --- a/.claude/commands/rp-reminder-cli.md +++ /dev/null @@ -1,51 +0,0 @@ ---- -description: Reminder to use rp-cli -repoprompt_managed: true -repoprompt_skills_version: 6 -repoprompt_variant: cli ---- - -# RepoPrompt Tools Reminder (CLI) - -Continue your current workflow using rp-cli instead of built-in alternatives. - -## Primary Tools - -| Task | Use This | Not This | -|------|----------|----------| -| Find files/content | `search` | grep, find, Glob | -| Read files | `read` | cat, Read | -| Edit files | `edit` | sed, Edit | -| Create/delete/move | `file` | touch, rm, mv, Write | - -## Quick Reference - -```bash -# Search (path or content) -rp-cli -w -e 'search "keyword"' - -# Read file (or slice) -rp-cli -w -e 'read Root/file.swift' -rp-cli -w -e 'read Root/file.swift --start-line 50 --limit 30' - -# Edit (search/replace) - JSON format required -rp-cli -w -e 'call apply_edits {"path":"Root/file.swift","search":"old","replace":"new"}' -rp-cli -w -e 'call apply_edits {"path":"Root/file.swift","search":"a\nb","replace":"c\nd"}' - -# File operations -rp-cli -w -e 'file create Root/new.swift "content..."' -rp-cli -w -e 'file delete /absolute/path.swift' -rp-cli -w -e 'file move Root/old.swift Root/new.swift' -``` - -## Context Management - -```bash -# Check selection -rp-cli -w -e 'select get' - -# Add files for chat context -rp-cli -w -e 'select add Root/path/file.swift' -``` - -Continue with your task using these tools. \ No newline at end of file diff --git a/.claude/commands/rp-review-cli.md b/.claude/commands/rp-review-cli.md deleted file mode 100644 index b7ac4b28..00000000 --- a/.claude/commands/rp-review-cli.md +++ /dev/null @@ -1,165 +0,0 @@ ---- -description: Code review workflow using rp-cli git tool and context_builder -repoprompt_managed: true -repoprompt_skills_version: 6 -repoprompt_variant: cli ---- - -# Code Review Mode (CLI) - -Review: $ARGUMENTS - -You are a **Code Reviewer** using rp-cli. Your workflow: understand the scope of changes, gather context, and provide thorough, actionable code review feedback. - -## Using rp-cli - -This workflow uses **rp-cli** (RepoPrompt CLI) instead of MCP tool calls. Run commands via: - -```bash -rp-cli -e '' -``` - -**Quick reference:** - -| MCP Tool | CLI Command | -|----------|-------------| -| `get_file_tree` | `rp-cli -e 'tree'` | -| `file_search` | `rp-cli -e 'search "pattern"'` | -| `get_code_structure` | `rp-cli -e 'structure path/'` | -| `read_file` | `rp-cli -e 'read path/file.swift'` | -| `manage_selection` | `rp-cli -e 'select add path/'` | -| `context_builder` | `rp-cli -e 'builder "instructions" --response-type plan'` | -| `chat_send` | `rp-cli -e 'chat "message" --mode plan'` | -| `apply_edits` | `rp-cli -e 'call apply_edits {"path":"...","search":"...","replace":"..."}'` | -| `file_actions` | `rp-cli -e 'call file_actions {"action":"create","path":"..."}'` | - -Chain commands with `&&`: -```bash -rp-cli -e 'select set src/ && context' -``` - -Use `rp-cli -e 'describe '` for help on a specific tool, or `rp-cli --help` for CLI usage. - -**⚠️ TIMEOUT WARNING:** The `builder` and `chat` commands can take several minutes to complete. When invoking rp-cli, **set your command timeout to at least 2700 seconds (45 minutes)** to avoid premature termination. - ---- -## Protocol - -0. **Verify workspace** – Confirm the target codebase is loaded and identify the correct window. -1. **Survey changes** – Check git state and recent commits to understand what's changed. -2. **Confirm scope (MANDATORY)** – You MUST confirm the comparison branch/scope with the user before proceeding. -3. **Deep review** – Run `builder` with `response_type: "review"`, explicitly specifying the confirmed comparison scope. -4. **Fill gaps** – If the review missed areas, run focused follow-up reviews explicitly describing what was/wasn't covered. - ---- - -## Step 0: Workspace Verification (REQUIRED) - -Before any git operations, confirm the target codebase is loaded: - -```bash -# First, list available windows to find the right one -rp-cli -e 'windows' - -# Then check roots in a specific window (REQUIRED - CLI cannot auto-bind) -rp-cli -w -e 'tree --type roots' -``` - -**Check the output:** -- If your target root appears in a window → note the window ID and proceed to Step 1 -- If not → the codebase isn't loaded in any window - -**CLI Window Routing (CRITICAL):** -- CLI invocations are stateless—you MUST pass `-w ` to target the correct window -- Use `rp-cli -e 'windows'` to list all open windows and their workspaces -- Always include `-w ` in ALL subsequent commands - ---- - -## Step 1: Survey Changes -```bash -rp-cli -w -e 'git status' -rp-cli -w -e 'git log --count 10' -rp-cli -w -e 'git diff --detail files' -``` - -## Step 2: Confirm Scope with User (MANDATORY - DO NOT SKIP) - -⚠️ **You MUST confirm the comparison scope with the user before calling `builder`.** Do not assume or proceed without explicit confirmation. - -Ask the user to confirm: -- **Current branch**: What branch are you on? (from git status) -- **Comparison target**: What should changes be compared against? - - `uncommitted` – All uncommitted changes vs HEAD (default) - - `staged` – Only staged changes vs HEAD - - `back:N` – Last N commits - - `main` or `master` – Compare current branch against trunk - - `` – Compare against specific branch - -**Example prompt to user:** -> "You're on branch `feature/xyz`. What should I compare against? -> - `uncommitted` (default) - review all uncommitted changes -> - `main` - review all changes on this branch vs main -> - Other branch name?" - -**STOP and wait for user confirmation before proceeding to Step 3.** - -## Step 3: Deep Review (via `builder` - REQUIRED) - -⚠️ **Do NOT skip this step.** You MUST call `builder` with `response_type: "review"` for proper code review context. - -**CRITICAL:** Include the confirmed comparison scope in your instructions so the context builder knows exactly what to review. - -Use XML tags to structure the instructions: -```bash -rp-cli -w -e 'builder "Review changes comparing against . Focus on correctness, security, API changes, error handling. - -Comparison: (e.g., uncommitted, main, staged) -Current branch: -Changed files: - -Focus on directories containing changes." --response-type review' -``` - -## Optional: Clarify Findings - -After receiving review findings, you can ask clarifying questions in the same chat: -```bash -rp-cli -w -t '' -e 'chat "Can you explain the security concern in more detail? What'\''s the attack vector?" --mode chat' -``` - -> Pass `-w ` to target the correct window and `-t ` to target the same tab from the builder response. - -## Step 4: Fill Gaps - -If the review omitted significant areas, run a focused follow-up. **You must explicitly describe what was already covered and what needs review now** (`builder` has no memory of previous runs): -```bash -rp-cli -w -e 'builder "Review in depth. - -Previous review covered: . -Not yet reviewed: . - -Focus specifically on ." --response-type review' -``` - ---- - -## Anti-patterns to Avoid - -- 🚫 **CRITICAL:** Skipping Step 2 (branch confirmation) – you MUST confirm the comparison scope with the user before calling `builder` -- 🚫 **CRITICAL:** Skipping `builder` and attempting to review by reading files manually – you'll miss architectural context -- 🚫 Calling `builder` without specifying the confirmed comparison scope in the instructions -- 🚫 Doing extensive file reading before calling `builder` – git status/log/diff is sufficient for Step 1 -- 🚫 Providing review feedback without first calling `builder` with `response_type: "review"` -- 🚫 Assuming the git diff alone is sufficient context for a thorough review -- 🚫 Reading changed files manually instead of letting `builder` build proper review context -- 🚫 **CLI:** Forgetting to pass `-w ` – CLI invocations are stateless and require explicit window targeting - ---- - -## Output Format (be concise, max 15 bullets total) - -- **Summary**: 1-2 sentences -- **Must-fix** (max 5): `[File:line]` issue + suggested fix -- **Suggestions** (max 5): `[File:line]` improvement -- **Questions** (optional, max 3): clarifications needed \ No newline at end of file diff --git a/.gitignore b/.gitignore index 310958ed..b99c5936 100644 --- a/.gitignore +++ b/.gitignore @@ -93,6 +93,7 @@ xcuserdata/ .sentryclirc # Claude Config File +.claude/ **/.claude/settings.local.json # incremental builds diff --git a/CHANGELOG.md b/CHANGELOG.md index 6d3cb1aa..78470b2a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,16 @@ # Changelog +## [Unreleased] + +### Changed + +- Changed MCP `xcode-ide` integration to expose manifest-managed gateway tools (`xcode_ide_list_tools`, `xcode_ide_call_tool`) while keeping CLI dynamic `xcode_tools_*` behavior unchanged ([#210](https://github.com/getsentry/XcodeBuildMCP/issues/210)) +- Deferred non-critical Xcode IDE state synchronization and Sentry enrichment work until after MCP connect to reduce handshake-path latency ([#210](https://github.com/getsentry/XcodeBuildMCP/issues/210)) + +### Fixed + +- Removed startup dependency on handshake-time Xcode bridge `tools/list` sync for MCP tool registration, preventing bridge list latency from delaying initial connect ([#210](https://github.com/getsentry/XcodeBuildMCP/issues/210)) + ## [2.0.7] ### Changed @@ -273,4 +284,3 @@ Please note that the UI automation features are an early preview and currently i - Initial release of XcodeBuildMCP - Basic support for building iOS and macOS applications - diff --git a/docs/CONFIGURATION.md b/docs/CONFIGURATION.md index c8837192..fa3e139a 100644 --- a/docs/CONFIGURATION.md +++ b/docs/CONFIGURATION.md @@ -147,7 +147,7 @@ enabledWorkflows: ["simulator", "ui-automation", "debugging"] See [TOOLS.md](TOOLS.md) for available workflows and their tools. -To proxy Xcode IDE tools (Xcode 26+ `xcrun mcpbridge`), enable `xcode-ide`. See [XCODE_IDE_MCPBRIDGE.md](XCODE_IDE_MCPBRIDGE.md). +To access Xcode IDE tools (Xcode 26+ `xcrun mcpbridge`), enable `xcode-ide`. This workflow exposes `xcode_ide_list_tools` and `xcode_ide_call_tool` for MCP clients. See [XCODE_IDE_MCPBRIDGE.md](XCODE_IDE_MCPBRIDGE.md). ### Experimental workflow discovery diff --git a/docs/TOOLS-CLI.md b/docs/TOOLS-CLI.md index a1eb08c8..188cba84 100644 --- a/docs/TOOLS-CLI.md +++ b/docs/TOOLS-CLI.md @@ -2,7 +2,7 @@ This document lists CLI tool names as exposed by `xcodebuildmcp `. -XcodeBuildMCP provides 71 canonical tools organized into 13 workflow groups. +XcodeBuildMCP provides 73 canonical tools organized into 13 workflow groups. ## Workflow Groups @@ -171,20 +171,22 @@ XcodeBuildMCP provides 71 canonical tools organized into 13 workflow groups. ### Xcode IDE Integration (`xcode-ide`) -**Purpose**: Bridge tools for connecting to Xcode's built-in MCP server (mcpbridge) to access IDE-specific functionality. (3 tools) +**Purpose**: Bridge tools for connecting to Xcode's built-in MCP server (mcpbridge) to access IDE-specific functionality. (5 tools) - `bridge-disconnect` - Disconnect bridge and unregister proxied `xcode_tools_*` tools. - `bridge-status` - Show xcrun mcpbridge availability and proxy tool sync status. - `bridge-sync` - One-shot connect + tools/list sync (manual retry; avoids background prompt spam). +- `call-tool` - Call a remote Xcode IDE MCP tool. +- `list-tools` - Lists Xcode-IDE-only MCP capabilities (Use for: SwiftUI previews image capture, code snippet execution, issue Navigator/build logs, and window/tab context). ## Summary Statistics -- **Canonical Tools**: 71 -- **Total Tools**: 95 +- **Canonical Tools**: 73 +- **Total Tools**: 97 - **Workflow Groups**: 13 --- -*This documentation is automatically generated by `scripts/update-tools-docs.ts` from the tools manifest. Last updated: 2026-02-08T12:09:33.648Z UTC* +*This documentation is automatically generated by `scripts/update-tools-docs.ts` from the tools manifest. Last updated: 2026-02-15T21:09:48.107Z UTC* diff --git a/docs/TOOLS.md b/docs/TOOLS.md index 71950ad7..8ae6b8c6 100644 --- a/docs/TOOLS.md +++ b/docs/TOOLS.md @@ -1,6 +1,6 @@ # XcodeBuildMCP MCP Tools Reference -This document lists MCP tool names as exposed to MCP clients. XcodeBuildMCP provides 76 canonical tools organized into 15 workflow groups for comprehensive Apple development workflows. +This document lists MCP tool names as exposed to MCP clients. XcodeBuildMCP provides 78 canonical tools organized into 15 workflow groups for comprehensive Apple development workflows. ## Workflow Groups @@ -186,8 +186,10 @@ This document lists MCP tool names as exposed to MCP clients. XcodeBuildMCP prov ### Xcode IDE Integration (`xcode-ide`) -**Purpose**: Bridge tools for connecting to Xcode's built-in MCP server (mcpbridge) to access IDE-specific functionality. (3 tools) +**Purpose**: Bridge tools for connecting to Xcode's built-in MCP server (mcpbridge) to access IDE-specific functionality. (5 tools) +- `xcode_ide_call_tool` - Call a remote Xcode IDE MCP tool. +- `xcode_ide_list_tools` - Lists Xcode-IDE-only MCP capabilities (Use for: SwiftUI previews image capture, code snippet execution, issue Navigator/build logs, and window/tab context). - `xcode_tools_bridge_disconnect` - Disconnect bridge and unregister proxied `xcode_tools_*` tools. - `xcode_tools_bridge_status` - Show xcrun mcpbridge availability and proxy tool sync status. - `xcode_tools_bridge_sync` - One-shot connect + tools/list sync (manual retry; avoids background prompt spam). @@ -196,10 +198,10 @@ This document lists MCP tool names as exposed to MCP clients. XcodeBuildMCP prov ## Summary Statistics -- **Canonical Tools**: 76 -- **Total Tools**: 100 +- **Canonical Tools**: 78 +- **Total Tools**: 102 - **Workflow Groups**: 15 --- -*This documentation is automatically generated by `scripts/update-tools-docs.ts` from the tools manifest. Last updated: 2026-02-08T12:09:33.648Z UTC* +*This documentation is automatically generated by `scripts/update-tools-docs.ts` from the tools manifest. Last updated: 2026-02-15T21:09:48.107Z UTC* diff --git a/docs/XCODE_IDE_MCPBRIDGE.md b/docs/XCODE_IDE_MCPBRIDGE.md index 49c132bf..880fdd27 100644 --- a/docs/XCODE_IDE_MCPBRIDGE.md +++ b/docs/XCODE_IDE_MCPBRIDGE.md @@ -15,12 +15,16 @@ enabledWorkflows: ["simulator", "debugging", "xcode-ide"] If the workflow is not enabled, XcodeBuildMCP does not start the bridge. -## Tool naming +## MCP tools in `xcode-ide` workflow -Proxied tools are registered dynamically, based on whatever Xcode advertises via `tools/list`: +When `xcode-ide` is enabled and `mcpbridge` is available, XcodeBuildMCP exposes two gateway tools: -- Remote: `XcodeListWindows` -- Local proxy: `xcode_tools_XcodeListWindows` +- `xcode_ide_list_tools`: Lists remote tools from Xcode's MCP server (name, description, schemas). +- `xcode_ide_call_tool`: Calls a remote Xcode tool by name with a JSON argument payload. + +These tools are stable and manifest-managed. They are shown only when `mcpbridge` is available. + +CLI behavior is unchanged and continues to use dynamic `xcode_tools_*` proxy naming. ## Bridge debug tools @@ -38,7 +42,7 @@ Recommended flow: 1. Launch Xcode. 2. Start XcodeBuildMCP with `xcode-ide` enabled. -3. If you don’t see any `xcode_tools_*` tools, temporarily set `debug: true` and call `xcode_tools_bridge_sync` after approving any prompts. +3. If `xcode_ide_list_tools` fails, temporarily set `debug: true` and call `xcode_tools_bridge_status` to inspect bridge health, then retry after approving prompts. ## Targeting a specific Xcode instance (optional) diff --git a/docs/dev/MANIFEST_FORMAT.md b/docs/dev/MANIFEST_FORMAT.md index 0ebc32c3..578a6a80 100644 --- a/docs/dev/MANIFEST_FORMAT.md +++ b/docs/dev/MANIFEST_FORMAT.md @@ -299,12 +299,17 @@ Predicates control visibility based on runtime context. All predicates in the ar |-----------|-------------| | `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 | -| `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) | +| `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. + ### Predicate Context Predicates receive a context object: @@ -314,7 +319,6 @@ interface PredicateContext { runtime: 'cli' | 'mcp' | 'daemon'; config: ResolvedRuntimeConfig; runningUnderXcode: boolean; - xcodeToolsActive: boolean; } ``` @@ -354,12 +358,20 @@ 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: + - xcodeAutoSyncDisabled ``` ## Tool Re-export diff --git a/example_projects/iOS_Calculator/.xcodebuildmcp/config.yaml b/example_projects/iOS_Calculator/.xcodebuildmcp/config.yaml index bdd7ad1c..237b38ba 100644 --- a/example_projects/iOS_Calculator/.xcodebuildmcp/config.yaml +++ b/example_projects/iOS_Calculator/.xcodebuildmcp/config.yaml @@ -3,16 +3,17 @@ enabledWorkflows: - simulator - ui-automation - debugging + - xcode-ide sessionDefaults: workspacePath: ./CalculatorApp.xcworkspace scheme: CalculatorApp configuration: Debug simulatorName: iPhone 17 Pro + simulatorId: B38FE93D-578B-454B-BE9A-C6FA0CE5F096 + simulatorPlatform: iOS Simulator useLatestOS: true arch: arm64 suppressWarnings: false derivedDataPath: ./iOS_Calculator/.derivedData preferXcodebuild: true bundleId: io.sentry.calculatorapp - simulatorId: B38FE93D-578B-454B-BE9A-C6FA0CE5F096 - simulatorPlatform: iOS Simulator diff --git a/manifests/tools/xcode_ide_call_tool.yaml b/manifests/tools/xcode_ide_call_tool.yaml new file mode 100644 index 00000000..7ecfb158 --- /dev/null +++ b/manifests/tools/xcode_ide_call_tool.yaml @@ -0,0 +1,11 @@ +id: xcode_ide_call_tool +module: mcp/tools/xcode-ide/xcode_ide_call_tool +names: + mcp: xcode_ide_call_tool + cli: call-tool +description: Call a remote Xcode IDE MCP tool. +predicates: + - mcpRuntimeOnly +annotations: + title: Call Xcode IDE Tool + readOnlyHint: false diff --git a/manifests/tools/xcode_ide_list_tools.yaml b/manifests/tools/xcode_ide_list_tools.yaml new file mode 100644 index 00000000..40ffca81 --- /dev/null +++ b/manifests/tools/xcode_ide_list_tools.yaml @@ -0,0 +1,11 @@ +id: xcode_ide_list_tools +module: mcp/tools/xcode-ide/xcode_ide_list_tools +names: + mcp: xcode_ide_list_tools + cli: list-tools +description: "Lists Xcode-IDE-only MCP capabilities (Use for: SwiftUI previews image capture, code snippet execution, issue Navigator/build logs, and window/tab context)." +predicates: + - mcpRuntimeOnly +annotations: + title: List Xcode IDE Tools + readOnlyHint: true diff --git a/manifests/workflows/xcode-ide.yaml b/manifests/workflows/xcode-ide.yaml index c13fc489..4d9b8b58 100644 --- a/manifests/workflows/xcode-ide.yaml +++ b/manifests/workflows/xcode-ide.yaml @@ -4,9 +4,10 @@ description: Bridge tools for connecting to Xcode's built-in MCP server (mcpbrid availability: cli: false predicates: - - xcodeToolsAvailable - hideWhenXcodeAgentMode tools: + - xcode_ide_list_tools + - xcode_ide_call_tool - xcode_tools_bridge_status - xcode_tools_bridge_sync - xcode_tools_bridge_disconnect diff --git a/src/cli.ts b/src/cli.ts index 3a37ef11..d3e2975a 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -63,8 +63,7 @@ async function main(): Promise { excludeWorkflows: ['session-management', 'workflow-discovery'], }); const topLevelCommand = findTopLevelCommand(process.argv.slice(2)); - const discoveryMode = - topLevelCommand === 'xcode-ide' || topLevelCommand === 'tools' ? 'quick' : 'none'; + const discoveryMode = topLevelCommand === 'xcode-ide' ? 'quick' : 'none'; // CLI uses a manifest-resolved catalog plus daemon-backed xcode-ide dynamic tools. const catalog = await buildCliToolCatalog({ diff --git a/src/cli/cli-tool-catalog.ts b/src/cli/cli-tool-catalog.ts index 0952c112..235584a7 100644 --- a/src/cli/cli-tool-catalog.ts +++ b/src/cli/cli-tool-catalog.ts @@ -85,7 +85,7 @@ function createCliXcodeProxyTool(remoteTool: DynamicBridgeTool): ToolDefinition const cliSchema = jsonSchemaToToolSchemaShape(remoteTool.inputSchema); return { - cliName: `xcode-ide-${toKebabCase(remoteTool.name)}`, + cliName: toKebabCase(remoteTool.name), mcpName: toLocalToolName(remoteTool.name), workflow: 'xcode-ide', description: remoteTool.description ?? '', diff --git a/src/cli/commands/tools.ts b/src/cli/commands/tools.ts index 6b6fb36f..c18a2f36 100644 --- a/src/cli/commands/tools.ts +++ b/src/cli/commands/tools.ts @@ -9,7 +9,6 @@ import { getEffectiveCliName } from '../../core/manifest/schema.ts'; import { isWorkflowEnabledForRuntime, isToolExposedForRuntime } from '../../visibility/exposure.ts'; import type { PredicateContext } from '../../visibility/predicate-types.ts'; import { getConfig } from '../../utils/config-store.ts'; -import { getMcpBridgeAvailability } from '../../integrations/xcode-tools-bridge/core.ts'; const CLI_EXCLUDED_WORKFLOWS = new Set(['session-management', 'workflow-discovery']); @@ -82,13 +81,10 @@ function toGroupedJsonTool(tool: ToolListItem): JsonTool { * CLI is never running under Xcode and never has Xcode tools active. */ async function buildCliPredicateContext(): Promise { - const bridge = await getMcpBridgeAvailability(); return { runtime: 'cli', config: getConfig(), runningUnderXcode: false, - xcodeToolsActive: false, - xcodeToolsAvailable: bridge.available, }; } diff --git a/src/core/manifest/__tests__/load-manifest.test.ts b/src/core/manifest/__tests__/load-manifest.test.ts index dc55d4cc..798b0bd0 100644 --- a/src/core/manifest/__tests__/load-manifest.test.ts +++ b/src/core/manifest/__tests__/load-manifest.test.ts @@ -84,19 +84,20 @@ describe('load-manifest', () => { expect(doctor?.selection?.mcp?.autoInclude).toBe(true); }); - it('should have xcode-ide workflow gated by xcode tools availability', () => { + it('should have xcode-ide workflow hidden in Xcode agent mode only', () => { const manifest = loadManifest(); const xcodeIde = manifest.workflows.get('xcode-ide'); expect(xcodeIde).toBeDefined(); - expect(xcodeIde?.predicates).toContain('xcodeToolsAvailable'); expect(xcodeIde?.predicates).toContain('hideWhenXcodeAgentMode'); expect(xcodeIde?.predicates).not.toContain('debugEnabled'); }); - it('should keep xcode bridge static tools gated by debugEnabled', () => { + it('should keep xcode bridge debug tools gated by debugEnabled', () => { const manifest = loadManifest(); + expect(manifest.tools.get('xcode_ide_list_tools')?.predicates).toContain('mcpRuntimeOnly'); + expect(manifest.tools.get('xcode_ide_call_tool')?.predicates).toContain('mcpRuntimeOnly'); expect(manifest.tools.get('xcode_tools_bridge_status')?.predicates).toContain('debugEnabled'); expect(manifest.tools.get('xcode_tools_bridge_sync')?.predicates).toContain('debugEnabled'); expect(manifest.tools.get('xcode_tools_bridge_disconnect')?.predicates).toContain( diff --git a/src/daemon/daemon-server.ts b/src/daemon/daemon-server.ts index 685acdf6..ae2ec53c 100644 --- a/src/daemon/daemon-server.ts +++ b/src/daemon/daemon-server.ts @@ -42,13 +42,6 @@ export function startDaemonServer(ctx: DaemonServerContext): net.Server { const invoker = new DefaultToolInvoker(ctx.catalog); const xcodeIdeService = new XcodeIdeToolService(); xcodeIdeService.setWorkflowEnabled(ctx.xcodeIdeWorkflowEnabled); - if (ctx.xcodeIdeWorkflowEnabled) { - // Warm dynamic tool cache in the background so CLI discovery can stay fast. - void xcodeIdeService.listTools({ refresh: true }).catch((error) => { - const message = error instanceof Error ? error.message : String(error); - log('debug', `[Daemon] Initial xcode-ide bridge prefetch failed: ${message}`); - }); - } const server = net.createServer((socket) => { log('info', '[Daemon] Client connected'); diff --git a/src/integrations/xcode-tools-bridge/client.ts b/src/integrations/xcode-tools-bridge/client.ts index f3e42669..5ee60165 100644 --- a/src/integrations/xcode-tools-bridge/client.ts +++ b/src/integrations/xcode-tools-bridge/client.ts @@ -139,7 +139,11 @@ export class XcodeToolsBridgeClient { return result.tools; } - async callTool(name: string, args: Record): Promise { + async callTool( + name: string, + args: Record, + opts: { timeoutMs?: number } = {}, + ): Promise { if (!this.client) { throw new Error('Bridge client is not connected'); } @@ -147,7 +151,7 @@ export class XcodeToolsBridgeClient { { method: 'tools/call', params: { name, arguments: args } }, CompatibilityCallToolResultSchema, { - timeout: this.options.callToolTimeoutMs, + timeout: opts.timeoutMs ?? this.options.callToolTimeoutMs, resetTimeoutOnProgress: true, }, ); diff --git a/src/integrations/xcode-tools-bridge/core.ts b/src/integrations/xcode-tools-bridge/core.ts index c7f84fb8..bce54ac0 100644 --- a/src/integrations/xcode-tools-bridge/core.ts +++ b/src/integrations/xcode-tools-bridge/core.ts @@ -1,6 +1,7 @@ import { execFile } from 'node:child_process'; import process from 'node:process'; import { promisify } from 'node:util'; +import type { Tool } from '@modelcontextprotocol/sdk/types.js'; import type { XcodeToolsBridgeClientStatus } from './client.ts'; const execFileAsync = promisify(execFile); @@ -18,6 +19,17 @@ export type XcodeToolsBridgeStatus = { xcodeSessionId: string | null; }; +export function serializeBridgeTool(tool: Tool): Record { + return { + name: tool.name, + title: tool.title, + description: tool.description, + inputSchema: tool.inputSchema, + outputSchema: tool.outputSchema, + annotations: tool.annotations, + }; +} + export interface BuildXcodeToolsBridgeStatusArgs { workflowEnabled: boolean; proxiedToolCount: number; @@ -68,3 +80,35 @@ export async function isXcodeRunning(): Promise { return null; } } + +export function classifyBridgeError( + error: unknown, + operation: 'list' | 'call', + opts?: { connected?: boolean }, +): string { + const message = (error instanceof Error ? error.message : String(error)).toLowerCase(); + + if (message.includes('mcpbridge not available')) { + return 'MCPBRIDGE_NOT_FOUND'; + } + if (message.includes('workflow is not enabled')) { + return 'XCODE_MCP_UNAVAILABLE'; + } + if (message.includes('timed out') || message.includes('timeout')) { + if (opts?.connected === false) { + return 'BRIDGE_CONNECT_TIMEOUT'; + } + return operation === 'list' ? 'BRIDGE_LIST_TIMEOUT' : 'BRIDGE_CALL_TIMEOUT'; + } + if (message.includes('permission') || message.includes('not allowed')) { + return 'XCODE_APPROVAL_REQUIRED'; + } + if ( + message.includes('connection closed') || + message.includes('closed') || + message.includes('disconnected') + ) { + return 'XCODE_SESSION_NOT_READY'; + } + return 'XCODE_MCP_UNAVAILABLE'; +} diff --git a/src/integrations/xcode-tools-bridge/index.ts b/src/integrations/xcode-tools-bridge/index.ts index bb7765c1..a0fe12a4 100644 --- a/src/integrations/xcode-tools-bridge/index.ts +++ b/src/integrations/xcode-tools-bridge/index.ts @@ -10,6 +10,12 @@ export interface XcodeToolsBridgeToolHandler { statusTool(): Promise; syncTool(): Promise; disconnectTool(): Promise; + listToolsTool(params: { refresh?: boolean }): Promise; + callToolTool(params: { + remoteTool: string; + arguments: Record; + timeoutMs?: number; + }): Promise; } export function getXcodeToolsBridgeManager(server?: McpServer): XcodeToolsBridgeManager | null { diff --git a/src/integrations/xcode-tools-bridge/manager.ts b/src/integrations/xcode-tools-bridge/manager.ts index 3d6acc9e..9df3a74f 100644 --- a/src/integrations/xcode-tools-bridge/manager.ts +++ b/src/integrations/xcode-tools-bridge/manager.ts @@ -8,7 +8,9 @@ import { import { XcodeToolsProxyRegistry, type ProxySyncResult } from './registry.ts'; import { buildXcodeToolsBridgeStatus, + classifyBridgeError, getMcpBridgeAvailability, + serializeBridgeTool, type XcodeToolsBridgeStatus, } from './core.ts'; import { XcodeIdeToolService } from './tool-service.ts'; @@ -145,4 +147,61 @@ export class XcodeToolsBridgeManager { return createErrorResponse('Bridge disconnect failed', message); } } + + async listToolsTool(params: { refresh?: boolean }): Promise { + if (!this.workflowEnabled) { + return this.createBridgeFailureResponse( + 'XCODE_MCP_UNAVAILABLE', + 'xcode-ide workflow is not enabled', + ); + } + + try { + const tools = await this.service.listTools({ refresh: params.refresh !== false }); + const payload = { + toolCount: tools.length, + tools: tools.map(serializeBridgeTool), + }; + return createTextResponse(JSON.stringify(payload, null, 2)); + } catch (error) { + return this.createBridgeFailureResponse( + classifyBridgeError(error, 'list', { + connected: this.service.getClientStatus().connected, + }), + error, + ); + } + } + + async callToolTool(params: { + remoteTool: string; + arguments: Record; + timeoutMs?: number; + }): Promise { + if (!this.workflowEnabled) { + return this.createBridgeFailureResponse( + 'XCODE_MCP_UNAVAILABLE', + 'xcode-ide workflow is not enabled', + ); + } + + try { + const response = await this.service.invokeTool(params.remoteTool, params.arguments, { + timeoutMs: params.timeoutMs, + }); + return response as ToolResponse; + } catch (error) { + return this.createBridgeFailureResponse( + classifyBridgeError(error, 'call', { + connected: this.service.getClientStatus().connected, + }), + error, + ); + } + } + + private createBridgeFailureResponse(code: string, error: unknown): ToolResponse { + const message = error instanceof Error ? error.message : String(error); + return createErrorResponse(code, message); + } } diff --git a/src/integrations/xcode-tools-bridge/standalone.ts b/src/integrations/xcode-tools-bridge/standalone.ts index ce260ae1..040f5976 100644 --- a/src/integrations/xcode-tools-bridge/standalone.ts +++ b/src/integrations/xcode-tools-bridge/standalone.ts @@ -3,7 +3,12 @@ import { createTextResponse, type ToolResponse, } from '../../utils/responses/index.ts'; -import { buildXcodeToolsBridgeStatus, type XcodeToolsBridgeStatus } from './core.ts'; +import { + buildXcodeToolsBridgeStatus, + classifyBridgeError, + serializeBridgeTool, + type XcodeToolsBridgeStatus, +} from './core.ts'; import { XcodeIdeToolService } from './tool-service.ts'; export class StandaloneXcodeToolsBridge { @@ -62,4 +67,39 @@ export class StandaloneXcodeToolsBridge { return createErrorResponse('Bridge disconnect failed', message); } } + + async listToolsTool(params: { refresh?: boolean }): Promise { + try { + const tools = await this.service.listTools({ refresh: params.refresh !== false }); + return createTextResponse( + JSON.stringify( + { + toolCount: tools.length, + tools: tools.map(serializeBridgeTool), + }, + null, + 2, + ), + ); + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + return createErrorResponse(classifyBridgeError(error, 'list'), message); + } + } + + async callToolTool(params: { + remoteTool: string; + arguments: Record; + timeoutMs?: number; + }): Promise { + try { + const response = await this.service.invokeTool(params.remoteTool, params.arguments, { + timeoutMs: params.timeoutMs, + }); + return response as ToolResponse; + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + return createErrorResponse(classifyBridgeError(error, 'call'), message); + } + } } diff --git a/src/integrations/xcode-tools-bridge/tool-service.ts b/src/integrations/xcode-tools-bridge/tool-service.ts index b2c894b4..9656a44d 100644 --- a/src/integrations/xcode-tools-bridge/tool-service.ts +++ b/src/integrations/xcode-tools-bridge/tool-service.ts @@ -89,10 +89,14 @@ export class XcodeIdeToolService { return this.refreshTools(); } - async invokeTool(name: string, args: Record): Promise { + async invokeTool( + name: string, + args: Record, + opts: { timeoutMs?: number } = {}, + ): Promise { await this.ensureConnected(); try { - const response = await this.client.callTool(name, args); + const response = await this.client.callTool(name, args, opts); this.lastError = null; return response; } catch (error) { diff --git a/src/mcp/tools/workflow-discovery/__tests__/manage_workflows.test.ts b/src/mcp/tools/workflow-discovery/__tests__/manage_workflows.test.ts index 904fe443..c610c1ce 100644 --- a/src/mcp/tools/workflow-discovery/__tests__/manage_workflows.test.ts +++ b/src/mcp/tools/workflow-discovery/__tests__/manage_workflows.test.ts @@ -7,7 +7,6 @@ vi.mock('../../../../utils/tool-registry.ts', () => ({ runtime: 'mcp', config: { debug: false }, runningUnderXcode: false, - xcodeToolsActive: false, }), })); diff --git a/src/mcp/tools/xcode-ide/__tests__/bridge_tools.test.ts b/src/mcp/tools/xcode-ide/__tests__/bridge_tools.test.ts index c35b2e59..d183cb82 100644 --- a/src/mcp/tools/xcode-ide/__tests__/bridge_tools.test.ts +++ b/src/mcp/tools/xcode-ide/__tests__/bridge_tools.test.ts @@ -6,12 +6,15 @@ vi.mock('../../../../server/server-state.ts', () => ({ vi.mock('../../../../integrations/xcode-tools-bridge/core.ts', () => ({ buildXcodeToolsBridgeStatus: vi.fn(), + classifyBridgeError: vi.fn(() => 'XCODE_MCP_UNAVAILABLE'), getMcpBridgeAvailability: vi.fn(), + serializeBridgeTool: vi.fn((tool) => tool), })); const clientMocks = { connectOnce: vi.fn(), listTools: vi.fn(), + callTool: vi.fn(), disconnect: vi.fn(), getStatus: vi.fn(), }; @@ -23,6 +26,8 @@ vi.mock('../../../../integrations/xcode-tools-bridge/client.ts', () => ({ import { handler as statusHandler } from '../xcode_tools_bridge_status.ts'; import { handler as syncHandler } from '../xcode_tools_bridge_sync.ts'; import { handler as disconnectHandler } from '../xcode_tools_bridge_disconnect.ts'; +import { handler as listHandler } from '../xcode_ide_list_tools.ts'; +import { handler as callHandler } from '../xcode_ide_call_tool.ts'; import { getServer } from '../../../../server/server-state.ts'; import { shutdownXcodeToolsBridge } from '../../../../integrations/xcode-tools-bridge/index.ts'; import { @@ -41,6 +46,7 @@ describe('xcode-ide bridge tools (standalone fallback)', () => { clientMocks.listTools.mockReset(); clientMocks.disconnect.mockReset(); clientMocks.getStatus.mockReset(); + clientMocks.callTool.mockReset(); vi.mocked(getServer).mockReturnValue(undefined); clientMocks.getStatus.mockReturnValue({ @@ -66,6 +72,10 @@ describe('xcode-ide bridge tools (standalone fallback)', () => { }); clientMocks.listTools.mockResolvedValue([{ name: 'toolA' }, { name: 'toolB' }]); clientMocks.connectOnce.mockResolvedValue(undefined); + clientMocks.callTool.mockResolvedValue({ + content: [{ type: 'text', text: 'ok' }], + isError: false, + }); clientMocks.disconnect.mockResolvedValue(undefined); }); @@ -91,4 +101,18 @@ describe('xcode-ide bridge tools (standalone fallback)', () => { expect(payload.connected).toBe(false); expect(clientMocks.disconnect).toHaveBeenCalledOnce(); }); + + it('list handler returns bridge tools without MCP server instance', async () => { + const result = await listHandler({ refresh: true }); + const payload = JSON.parse(result.content[0].text as string); + expect(payload.toolCount).toBe(2); + expect(payload.tools).toHaveLength(2); + expect(clientMocks.listTools).toHaveBeenCalledOnce(); + }); + + it('call handler forwards remote tool calls without MCP server instance', async () => { + const result = await callHandler({ remoteTool: 'toolA', arguments: { foo: 'bar' } }); + expect(result.isError).toBe(false); + expect(clientMocks.callTool).toHaveBeenCalledWith('toolA', { foo: 'bar' }, {}); + }); }); diff --git a/src/mcp/tools/xcode-ide/shared.ts b/src/mcp/tools/xcode-ide/shared.ts new file mode 100644 index 00000000..15de889d --- /dev/null +++ b/src/mcp/tools/xcode-ide/shared.ts @@ -0,0 +1,15 @@ +import type { ToolResponse } from '../../../types/common.ts'; +import type { XcodeToolsBridgeToolHandler } from '../../../integrations/xcode-tools-bridge/index.ts'; +import { getServer } from '../../../server/server-state.ts'; +import { getXcodeToolsBridgeToolHandler } from '../../../integrations/xcode-tools-bridge/index.ts'; +import { createErrorResponse } from '../../../utils/responses/index.ts'; + +export async function withBridgeToolHandler( + callback: (bridge: XcodeToolsBridgeToolHandler) => Promise, +): Promise { + const bridge = getXcodeToolsBridgeToolHandler(getServer()); + if (!bridge) { + return createErrorResponse('Bridge unavailable', 'Unable to initialize xcode tools bridge'); + } + return callback(bridge); +} diff --git a/src/mcp/tools/xcode-ide/xcode_ide_call_tool.ts b/src/mcp/tools/xcode-ide/xcode_ide_call_tool.ts new file mode 100644 index 00000000..f15e2694 --- /dev/null +++ b/src/mcp/tools/xcode-ide/xcode_ide_call_tool.ts @@ -0,0 +1,48 @@ +import * as z from 'zod'; +import type { ToolResponse } from '../../../types/common.ts'; +import { createErrorResponse } from '../../../utils/responses/index.ts'; +import { withBridgeToolHandler } from './shared.ts'; + +const schemaObject = z.object({ + remoteTool: z.string().min(1).describe('Exact remote Xcode MCP tool name.'), + arguments: z + .record(z.string(), z.unknown()) + .optional() + .default({}) + .describe('Arguments payload to forward to the remote Xcode MCP tool.'), + timeoutMs: z + .number() + .int() + .min(100) + .max(120000) + .optional() + .describe('Optional timeout override in milliseconds for this single tool call.'), +}); + +type Params = z.infer; + +export async function xcodeIdeCallToolLogic(params: Params): Promise { + return withBridgeToolHandler((bridge) => + bridge.callToolTool({ + remoteTool: params.remoteTool, + arguments: params.arguments ?? {}, + timeoutMs: params.timeoutMs, + }), + ); +} + +export const schema = schemaObject.shape; + +export const handler = async (args: Record = {}): Promise => { + const parsed = schemaObject.safeParse(args); + if (!parsed.success) { + const details = parsed.error.issues + .map((issue) => { + const path = issue.path.length > 0 ? issue.path.join('.') : 'root'; + return `${path}: ${issue.message}`; + }) + .join('\n'); + return createErrorResponse('Parameter validation failed', details); + } + return xcodeIdeCallToolLogic(parsed.data); +}; diff --git a/src/mcp/tools/xcode-ide/xcode_ide_list_tools.ts b/src/mcp/tools/xcode-ide/xcode_ide_list_tools.ts new file mode 100644 index 00000000..152d4715 --- /dev/null +++ b/src/mcp/tools/xcode-ide/xcode_ide_list_tools.ts @@ -0,0 +1,33 @@ +import * as z from 'zod'; +import type { ToolResponse } from '../../../types/common.ts'; +import { createErrorResponse } from '../../../utils/responses/index.ts'; +import { withBridgeToolHandler } from './shared.ts'; + +const schemaObject = z.object({ + refresh: z + .boolean() + .optional() + .describe('When true (default), refreshes from Xcode bridge before returning tool list.'), +}); + +type Params = z.infer; + +export async function xcodeIdeListToolsLogic(params: Params): Promise { + return withBridgeToolHandler(async (bridge) => bridge.listToolsTool({ refresh: params.refresh })); +} + +export const schema = schemaObject.shape; + +export const handler = async (args: Record = {}): Promise => { + const parsed = schemaObject.safeParse(args); + if (!parsed.success) { + const details = parsed.error.issues + .map((issue) => { + const path = issue.path.length > 0 ? issue.path.join('.') : 'root'; + return `${path}: ${issue.message}`; + }) + .join('\n'); + return createErrorResponse('Parameter validation failed', details); + } + return xcodeIdeListToolsLogic(parsed.data); +}; diff --git a/src/mcp/tools/xcode-ide/xcode_tools_bridge_disconnect.ts b/src/mcp/tools/xcode-ide/xcode_tools_bridge_disconnect.ts index d39c0542..d81f1750 100644 --- a/src/mcp/tools/xcode-ide/xcode_tools_bridge_disconnect.ts +++ b/src/mcp/tools/xcode-ide/xcode_tools_bridge_disconnect.ts @@ -1,14 +1,8 @@ import type { ToolResponse } from '../../../types/common.ts'; -import { getServer } from '../../../server/server-state.ts'; -import { getXcodeToolsBridgeToolHandler } from '../../../integrations/xcode-tools-bridge/index.ts'; -import { createErrorResponse } from '../../../utils/responses/index.ts'; +import { withBridgeToolHandler } from './shared.ts'; export const schema = {}; export const handler = async (): Promise => { - const bridge = getXcodeToolsBridgeToolHandler(getServer()); - if (!bridge) { - return createErrorResponse('Bridge unavailable', 'Unable to initialize xcode tools bridge'); - } - return bridge.disconnectTool(); + return withBridgeToolHandler(async (bridge) => bridge.disconnectTool()); }; diff --git a/src/mcp/tools/xcode-ide/xcode_tools_bridge_status.ts b/src/mcp/tools/xcode-ide/xcode_tools_bridge_status.ts index 0907ceca..f3dae68e 100644 --- a/src/mcp/tools/xcode-ide/xcode_tools_bridge_status.ts +++ b/src/mcp/tools/xcode-ide/xcode_tools_bridge_status.ts @@ -1,14 +1,8 @@ import type { ToolResponse } from '../../../types/common.ts'; -import { getServer } from '../../../server/server-state.ts'; -import { getXcodeToolsBridgeToolHandler } from '../../../integrations/xcode-tools-bridge/index.ts'; -import { createErrorResponse } from '../../../utils/responses/index.ts'; +import { withBridgeToolHandler } from './shared.ts'; export const schema = {}; export const handler = async (): Promise => { - const bridge = getXcodeToolsBridgeToolHandler(getServer()); - if (!bridge) { - return createErrorResponse('Bridge unavailable', 'Unable to initialize xcode tools bridge'); - } - return bridge.statusTool(); + return withBridgeToolHandler(async (bridge) => bridge.statusTool()); }; diff --git a/src/mcp/tools/xcode-ide/xcode_tools_bridge_sync.ts b/src/mcp/tools/xcode-ide/xcode_tools_bridge_sync.ts index bf76cc9e..af609325 100644 --- a/src/mcp/tools/xcode-ide/xcode_tools_bridge_sync.ts +++ b/src/mcp/tools/xcode-ide/xcode_tools_bridge_sync.ts @@ -1,14 +1,8 @@ import type { ToolResponse } from '../../../types/common.ts'; -import { getServer } from '../../../server/server-state.ts'; -import { getXcodeToolsBridgeToolHandler } from '../../../integrations/xcode-tools-bridge/index.ts'; -import { createErrorResponse } from '../../../utils/responses/index.ts'; +import { withBridgeToolHandler } from './shared.ts'; export const schema = {}; export const handler = async (): Promise => { - const bridge = getXcodeToolsBridgeToolHandler(getServer()); - if (!bridge) { - return createErrorResponse('Bridge unavailable', 'Unable to initialize xcode tools bridge'); - } - return bridge.syncTool(); + return withBridgeToolHandler(async (bridge) => bridge.syncTool()); }; diff --git a/src/runtime/tool-catalog.ts b/src/runtime/tool-catalog.ts index 87247a53..337feb35 100644 --- a/src/runtime/tool-catalog.ts +++ b/src/runtime/tool-catalog.ts @@ -219,8 +219,6 @@ export async function buildDaemonToolCatalogFromManifest(opts?: { runtime: 'daemon', config: getConfig(), runningUnderXcode: false, - xcodeToolsActive: false, - xcodeToolsAvailable: false, }; return buildToolCatalogFromManifest({ @@ -238,7 +236,5 @@ async function buildCliPredicateContext(): Promise { runtime: 'cli', config: getConfig(), runningUnderXcode: false, - xcodeToolsActive: false, - xcodeToolsAvailable: false, }; } diff --git a/src/server/bootstrap.ts b/src/server/bootstrap.ts index e5c6d791..10d1c0b5 100644 --- a/src/server/bootstrap.ts +++ b/src/server/bootstrap.ts @@ -7,7 +7,6 @@ import type { RuntimeConfigOverrides } from '../utils/config-store.ts'; import { getRegisteredWorkflows, registerWorkflowsFromManifest } from '../utils/tool-registry.ts'; import { bootstrapRuntime } from '../runtime/bootstrap-runtime.ts'; import { getXcodeToolsBridgeManager } from '../integrations/xcode-tools-bridge/index.ts'; -import { getMcpBridgeAvailability } from '../integrations/xcode-tools-bridge/core.ts'; import { resolveWorkspaceRoot } from '../daemon/socket-path.ts'; import { detectXcodeRuntime } from '../utils/xcode-process.ts'; import { readXcodeIdeState } from '../utils/xcode-state-reader.ts'; @@ -15,6 +14,7 @@ import { sessionStore } from '../utils/session-store.ts'; import { startXcodeStateWatcher, lookupBundleId } from '../utils/xcode-state-watcher.ts'; import { getDefaultCommandExecutor } from '../utils/command.ts'; import type { PredicateContext } from '../visibility/predicate-types.ts'; +import { createStartupProfiler, getStartupProfileNowMs } from './startup-profiler.ts'; export interface BootstrapOptions { enabledWorkflows?: string[]; @@ -23,10 +23,16 @@ export interface BootstrapOptions { cwd?: string; } +export interface BootstrapResult { + runDeferredInitialization: () => Promise; +} + export async function bootstrapServer( server: McpServer, options: BootstrapOptions = {}, -): Promise { +): Promise { + const profiler = createStartupProfiler('bootstrap'); + server.server.setRequestHandler(SetLevelRequestSchema, async (request) => { const { level } = request.params; setLogLevel(level as LogLevel); @@ -47,12 +53,14 @@ export async function bootstrapServer( overrides.enabledWorkflows = options.enabledWorkflows ?? []; } + let stageStartMs = getStartupProfileNowMs(); const result = await bootstrapRuntime({ runtime: 'mcp', cwd: options.cwd, fs: options.fileSystemExecutor, configOverrides: overrides, }); + profiler.mark('bootstrapRuntime', stageStartMs); if (result.configFound) { for (const notice of result.notices) { @@ -65,116 +73,109 @@ export async function bootstrapServer( cwd: result.runtime.cwd, projectConfigPath: result.configPath, }); - const mcpBridge = await getMcpBridgeAvailability(); - const xcodeToolsAvailable = mcpBridge.available; + log('info', `🚀 Initializing server...`); - // Detect if running under Xcode const executor = getDefaultCommandExecutor(); + stageStartMs = getStartupProfileNowMs(); const xcodeDetection = await detectXcodeRuntime(executor); - if (xcodeDetection.runningUnderXcode) { - log('info', `[xcode] Running under Xcode agent environment`); - - // Get project/workspace path from config session defaults (for monorepo disambiguation) - const configSessionDefaults = result.runtime.config.sessionDefaults; - const projectPath = configSessionDefaults?.projectPath; - const workspacePath = configSessionDefaults?.workspacePath; - - // Sync session defaults from Xcode's IDE state - const xcodeState = await readXcodeIdeState({ - executor, - cwd: result.runtime.cwd, - searchRoot: workspaceRoot, - projectPath, - workspacePath, - }); - - if (xcodeState.error) { - log('debug', `[xcode] Could not read Xcode IDE state: ${xcodeState.error}`); - } else { - const syncedDefaults: Record = {}; - if (xcodeState.scheme) { - syncedDefaults.scheme = xcodeState.scheme; - } - if (xcodeState.simulatorId) { - syncedDefaults.simulatorId = xcodeState.simulatorId; - } - if (xcodeState.simulatorName) { - syncedDefaults.simulatorName = xcodeState.simulatorName; - } - - if (Object.keys(syncedDefaults).length > 0) { - sessionStore.setDefaults(syncedDefaults); - log( - 'info', - `[xcode] Synced session defaults from Xcode: ${JSON.stringify(syncedDefaults)}`, - ); - } + profiler.mark('detectXcodeRuntime', stageStartMs); - // Look up bundle ID asynchronously (non-blocking) - if (xcodeState.scheme) { - lookupBundleId(executor, xcodeState.scheme, projectPath, workspacePath) - .then((bundleId) => { - if (bundleId) { - sessionStore.setDefaults({ bundleId }); - log('info', `[xcode] Bundle ID resolved: "${bundleId}"`); - } - }) - .catch((e) => { - log('debug', `[xcode] Failed to lookup bundle ID: ${e}`); - }); - } - } - - // Start file watcher to auto-sync when user changes scheme/simulator in Xcode - if (!result.runtime.config.disableXcodeAutoSync) { - const watcherStarted = await startXcodeStateWatcher({ - executor, - cwd: result.runtime.cwd, - searchRoot: workspaceRoot, - projectPath, - workspacePath, - }); - if (watcherStarted) { - log('info', `[xcode] Started file watcher for automatic sync`); - } - } else { - log('info', `[xcode] Automatic Xcode sync disabled via config`); - } - } - - // Build predicate context for manifest-based registration const ctx: PredicateContext = { runtime: 'mcp', config: result.runtime.config, runningUnderXcode: xcodeDetection.runningUnderXcode, - xcodeToolsActive: false, // Will be updated after Xcode tools bridge sync - xcodeToolsAvailable, }; - // Register workflows using manifest system + stageStartMs = getStartupProfileNowMs(); await registerWorkflowsFromManifest(enabledWorkflows, ctx); + profiler.mark('registerWorkflowsFromManifest', stageStartMs); const resolvedWorkflows = getRegisteredWorkflows(); const xcodeIdeEnabled = resolvedWorkflows.includes('xcode-ide'); - const xcodeToolsBridge = xcodeToolsAvailable ? getXcodeToolsBridgeManager(server) : null; + const xcodeToolsBridge = xcodeIdeEnabled ? getXcodeToolsBridgeManager(server) : null; xcodeToolsBridge?.setWorkflowEnabled(xcodeIdeEnabled); - if (xcodeIdeEnabled && xcodeToolsBridge) { - try { - const syncResult = await xcodeToolsBridge.syncTools({ reason: 'startup' }); - // After sync, if Xcode tools are active, re-register with updated context - if (syncResult.total > 0 && xcodeDetection.runningUnderXcode) { - log('info', `[xcode-ide] Xcode tools active - applying conflict filtering`); - ctx.xcodeToolsActive = true; - await registerWorkflowsFromManifest(enabledWorkflows, ctx); - } - } catch (error) { - log( - 'warning', - `[xcode-ide] Startup sync failed: ${error instanceof Error ? error.message : String(error)}`, - ); - } - } + stageStartMs = getStartupProfileNowMs(); await registerResources(server); + profiler.mark('registerResources', stageStartMs); + + return { + runDeferredInitialization: async (): Promise => { + const deferredProfiler = createStartupProfiler('bootstrap-deferred'); + + if (!xcodeDetection.runningUnderXcode) { + return; + } + + log('info', `[xcode] Running under Xcode agent environment`); + + const configSessionDefaults = result.runtime.config.sessionDefaults; + const projectPath = configSessionDefaults?.projectPath; + const workspacePath = configSessionDefaults?.workspacePath; + + let deferredStageStartMs = getStartupProfileNowMs(); + const xcodeState = await readXcodeIdeState({ + executor, + cwd: result.runtime.cwd, + searchRoot: workspaceRoot, + projectPath, + workspacePath, + }); + deferredProfiler.mark('readXcodeIdeState', deferredStageStartMs); + + if (xcodeState.error) { + log('debug', `[xcode] Could not read Xcode IDE state: ${xcodeState.error}`); + } else { + const syncedDefaults: Record = {}; + if (xcodeState.scheme) { + syncedDefaults.scheme = xcodeState.scheme; + } + if (xcodeState.simulatorId) { + syncedDefaults.simulatorId = xcodeState.simulatorId; + } + if (xcodeState.simulatorName) { + syncedDefaults.simulatorName = xcodeState.simulatorName; + } + + if (Object.keys(syncedDefaults).length > 0) { + sessionStore.setDefaults(syncedDefaults); + log( + 'info', + `[xcode] Synced session defaults from Xcode: ${JSON.stringify(syncedDefaults)}`, + ); + } + + if (xcodeState.scheme) { + lookupBundleId(executor, xcodeState.scheme, projectPath, workspacePath) + .then((bundleId) => { + if (bundleId) { + sessionStore.setDefaults({ bundleId }); + log('info', `[xcode] Bundle ID resolved: "${bundleId}"`); + } + }) + .catch((e) => { + log('debug', `[xcode] Failed to lookup bundle ID: ${e}`); + }); + } + } + + if (!result.runtime.config.disableXcodeAutoSync) { + deferredStageStartMs = getStartupProfileNowMs(); + const watcherStarted = await startXcodeStateWatcher({ + executor, + cwd: result.runtime.cwd, + searchRoot: workspaceRoot, + projectPath, + workspacePath, + }); + deferredProfiler.mark('startXcodeStateWatcher', deferredStageStartMs); + if (watcherStarted) { + log('info', `[xcode] Started file watcher for automatic sync`); + } + } else { + log('info', `[xcode] Automatic Xcode sync disabled via config`); + } + }, + }; } diff --git a/src/server/start-mcp-server.ts b/src/server/start-mcp-server.ts index b43c3ef4..632b979e 100644 --- a/src/server/start-mcp-server.ts +++ b/src/server/start-mcp-server.ts @@ -9,12 +9,13 @@ import { createServer, startServer } from './server.ts'; import { log, setLogLevel } from '../utils/logger.ts'; -import { initSentry } from '../utils/sentry.ts'; +import { enrichSentryContext, initSentry } from '../utils/sentry.ts'; import { getDefaultDebuggerManager } from '../utils/debugger/index.ts'; import { version } from '../version.ts'; import process from 'node:process'; import { bootstrapServer } from './bootstrap.ts'; import { shutdownXcodeToolsBridge } from '../integrations/xcode-tools-bridge/index.ts'; +import { createStartupProfiler, getStartupProfileNowMs } from './startup-profiler.ts'; /** * Start the MCP server. @@ -23,17 +24,37 @@ import { shutdownXcodeToolsBridge } from '../integrations/xcode-tools-bridge/ind */ export async function startMcpServer(): Promise { try { + const profiler = createStartupProfiler('start-mcp-server'); + // MCP mode defaults to info level logging // Clients can override via logging/setLevel MCP request setLogLevel('info'); + let stageStartMs = getStartupProfileNowMs(); initSentry(); + profiler.mark('initSentry', stageStartMs); + stageStartMs = getStartupProfileNowMs(); const server = createServer(); + profiler.mark('createServer', stageStartMs); - await bootstrapServer(server); + stageStartMs = getStartupProfileNowMs(); + const bootstrap = await bootstrapServer(server); + profiler.mark('bootstrapServer', stageStartMs); + stageStartMs = getStartupProfileNowMs(); await startServer(server); + profiler.mark('startServer', stageStartMs); + + void bootstrap.runDeferredInitialization().catch((error) => { + log( + 'warning', + `Deferred bootstrap initialization failed: ${error instanceof Error ? error.message : String(error)}`, + ); + }); + setImmediate(() => { + enrichSentryContext(); + }); let shuttingDown = false; const shutdown = async (signal: NodeJS.Signals): Promise => { diff --git a/src/server/startup-profiler.ts b/src/server/startup-profiler.ts new file mode 100644 index 00000000..7d116bdb --- /dev/null +++ b/src/server/startup-profiler.ts @@ -0,0 +1,38 @@ +import { performance } from 'node:perf_hooks'; +import { log } from '../utils/logger.ts'; + +const PROFILE_ENV = 'XCODEBUILDMCP_STARTUP_PROFILE'; + +function isEnabled(): boolean { + const value = process.env[PROFILE_ENV]?.toLowerCase(); + return value === '1' || value === 'true'; +} + +export interface StartupProfiler { + readonly enabled: boolean; + readonly startedAtMs: number; + mark(stage: string, startedAtMs: number): void; +} + +export function createStartupProfiler(scope: string): StartupProfiler { + const enabled = isEnabled(); + const startedAtMs = performance.now(); + + return { + enabled, + startedAtMs, + mark(stage: string, stageStartedAtMs: number): void { + if (!enabled) return; + const elapsedMs = performance.now() - stageStartedAtMs; + const totalMs = performance.now() - startedAtMs; + log( + 'info', + `[startup-profile] scope=${scope} stage=${stage} ms=${elapsedMs.toFixed(1)} totalMs=${totalMs.toFixed(1)}`, + ); + }, + }; +} + +export function getStartupProfileNowMs(): number { + return performance.now(); +} diff --git a/src/utils/sentry.ts b/src/utils/sentry.ts index 8c7af75b..6decc492 100644 --- a/src/utils/sentry.ts +++ b/src/utils/sentry.ts @@ -82,6 +82,7 @@ function checkBinaryAvailability(binary: string): { available: boolean; version? } let initialized = false; +let enriched = false; function isSentryDisabled(): boolean { return ( @@ -119,13 +120,20 @@ export function initSentry(): void { // We recommend adjusting this value in production tracesSampleRate: 1.0, }); +} + +export function enrichSentryContext(): void { + if (!initialized || enriched || isSentryDisabled() || isTestEnv()) { + return; + } + + enriched = true; const axeAvailable = checkBinaryAvailability('axe'); const miseAvailable = checkBinaryAvailability('mise'); const envVars = getEnvironmentVariables(); const xcodeInfo = getXcodeInfo(); - // Add additional context that might be helpful for debugging const tags: Record = { nodeVersion: process.version, platform: process.platform, diff --git a/src/utils/tool-registry.ts b/src/utils/tool-registry.ts index 527d68ab..27b41be3 100644 --- a/src/utils/tool-registry.ts +++ b/src/utils/tool-registry.ts @@ -53,8 +53,6 @@ export function getMcpPredicateContext(): PredicateContext { runtime: 'mcp', config: getConfig(), runningUnderXcode: false, - xcodeToolsActive: false, - xcodeToolsAvailable: false, }; } @@ -153,8 +151,6 @@ export async function registerWorkflowsFromManifest( runtime: 'mcp', config: getConfig(), runningUnderXcode: false, - xcodeToolsActive: false, - xcodeToolsAvailable: false, }; await applyWorkflowSelectionFromManifest(workflowNames, effectiveCtx); } diff --git a/src/visibility/__tests__/exposure.test.ts b/src/visibility/__tests__/exposure.test.ts index ab43b0ad..236d9d37 100644 --- a/src/visibility/__tests__/exposure.test.ts +++ b/src/visibility/__tests__/exposure.test.ts @@ -39,8 +39,6 @@ function createContext(overrides: Partial = {}): PredicateCont runtime: 'mcp', config: createDefaultConfig(), runningUnderXcode: false, - xcodeToolsActive: false, - xcodeToolsAvailable: false, ...overrides, }; } diff --git a/src/visibility/__tests__/predicate-registry.test.ts b/src/visibility/__tests__/predicate-registry.test.ts index b9896324..a082738c 100644 --- a/src/visibility/__tests__/predicate-registry.test.ts +++ b/src/visibility/__tests__/predicate-registry.test.ts @@ -32,8 +32,6 @@ function createContext(overrides: Partial = {}): PredicateCont runtime: 'mcp', config: createDefaultConfig(), runningUnderXcode: false, - xcodeToolsActive: false, - xcodeToolsAvailable: false, ...overrides, }; } @@ -84,27 +82,15 @@ describe('predicate-registry', () => { }); }); - describe('requiresXcodeTools', () => { - it('should return true when Xcode tools are active', () => { - const ctx = createContext({ xcodeToolsActive: true }); - expect(PREDICATES.requiresXcodeTools(ctx)).toBe(true); + describe('mcpRuntimeOnly', () => { + it('should return true for MCP runtime', () => { + const ctx = createContext({ runtime: 'mcp' }); + expect(PREDICATES.mcpRuntimeOnly(ctx)).toBe(true); }); - it('should return false when Xcode tools are not active', () => { - const ctx = createContext({ xcodeToolsActive: false }); - expect(PREDICATES.requiresXcodeTools(ctx)).toBe(false); - }); - }); - - describe('xcodeToolsAvailable', () => { - it('should return true when Xcode tools bridge is available', () => { - const ctx = createContext({ xcodeToolsAvailable: true }); - expect(PREDICATES.xcodeToolsAvailable(ctx)).toBe(true); - }); - - it('should return false when Xcode tools bridge is not available', () => { - const ctx = createContext({ xcodeToolsAvailable: false }); - expect(PREDICATES.xcodeToolsAvailable(ctx)).toBe(false); + it('should return false for CLI runtime', () => { + const ctx = createContext({ runtime: 'cli' }); + expect(PREDICATES.mcpRuntimeOnly(ctx)).toBe(false); }); }); @@ -212,8 +198,7 @@ describe('predicate-registry', () => { expect(names).toContain('debugEnabled'); expect(names).toContain('experimentalWorkflowDiscoveryEnabled'); expect(names).toContain('runningUnderXcodeAgent'); - expect(names).toContain('requiresXcodeTools'); - expect(names).toContain('xcodeToolsAvailable'); + expect(names).toContain('mcpRuntimeOnly'); expect(names).toContain('hideWhenXcodeAgentMode'); expect(names).toContain('xcodeAutoSyncDisabled'); expect(names).toContain('always'); diff --git a/src/visibility/predicate-registry.ts b/src/visibility/predicate-registry.ts index 93ab37bf..f9ba366f 100644 --- a/src/visibility/predicate-registry.ts +++ b/src/visibility/predicate-registry.ts @@ -28,16 +28,10 @@ export const PREDICATES: Record = { runningUnderXcodeAgent: (ctx: PredicateContext): boolean => ctx.runningUnderXcode === true, /** - * Show only when Xcode Tools bridge is available and active. - * Use for tools/workflows that require the Xcode Tools integration. + * Show only for MCP runtime. + * Use for MCP-only gateway tools that should not appear in CLI workflows. */ - requiresXcodeTools: (ctx: PredicateContext): boolean => ctx.xcodeToolsActive === true, - - /** - * Show only when xcrun mcpbridge is available on the host system. - * Use for workflows/tools that depend on Xcode's MCP bridge binary. - */ - xcodeToolsAvailable: (ctx: PredicateContext): boolean => ctx.xcodeToolsAvailable === true, + mcpRuntimeOnly: (ctx: PredicateContext): boolean => ctx.runtime === 'mcp', /** * Hide when running inside Xcode's coding agent. diff --git a/src/visibility/predicate-types.ts b/src/visibility/predicate-types.ts index 52f472c0..323a4143 100644 --- a/src/visibility/predicate-types.ts +++ b/src/visibility/predicate-types.ts @@ -23,12 +23,6 @@ export interface PredicateContext { /** Whether running under Xcode agent environment */ runningUnderXcode: boolean; - - /** Whether Xcode Tools bridge is active (MCP only; false otherwise) */ - xcodeToolsActive: boolean; - - /** Whether the Xcode Tools bridge binary is available on this system */ - xcodeToolsAvailable?: boolean; } /**