xcode-mcp-server 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,21 @@
1
+ """Xcode MCP Server - Model Context Protocol server for Xcode integration"""
2
+
3
+ from .__main__ import mcp, ALLOWED_FOLDERS, get_allowed_folders
4
+
5
+ def main():
6
+ """Entry point for the xcode-mcp-server command"""
7
+ import sys
8
+ from .__main__ import mcp, ALLOWED_FOLDERS, get_allowed_folders
9
+
10
+ # Initialize allowed folders
11
+ global ALLOWED_FOLDERS
12
+ ALLOWED_FOLDERS = get_allowed_folders()
13
+
14
+ # Debug info
15
+ print(f"Allowed folders: {ALLOWED_FOLDERS}", file=sys.stderr)
16
+
17
+ # Run the server
18
+ mcp.run()
19
+
20
+ __version__ = "1.0.0"
21
+ __all__ = ["mcp", "main"]
@@ -0,0 +1,541 @@
1
+ #!/usr/bin/env python3
2
+ import os
3
+ import sys
4
+ import subprocess
5
+ import json
6
+ from typing import Optional, Dict, List, Any, Tuple, Set
7
+ from dataclasses import dataclass
8
+
9
+ from mcp.server.fastmcp import FastMCP, Context
10
+
11
+ # Global variables for allowed folders
12
+ ALLOWED_FOLDERS: Set[str] = set()
13
+
14
+ class XCodeMCPError(Exception):
15
+ def __init__(self, message, code=None):
16
+ self.message = message
17
+ self.code = code
18
+ super().__init__(self.message)
19
+
20
+ class AccessDeniedError(XCodeMCPError):
21
+ pass
22
+
23
+ class InvalidParameterError(XCodeMCPError):
24
+ pass
25
+
26
+ def get_allowed_folders() -> Set[str]:
27
+ """
28
+ Get the allowed folders from environment variable.
29
+ Validates that paths are absolute, exist, and are directories.
30
+ """
31
+ allowed_folders = set()
32
+
33
+ # Get from environment variable
34
+ folder_list_str = os.environ.get("XCODEMCP_ALLOWED_FOLDERS", "monkies")
35
+
36
+ if folder_list_str:
37
+ print(f"Using allowed folders from environment: {folder_list_str}", file=sys.stderr)
38
+ else:
39
+ print("Warning: No allowed folders specified. Access will be restricted.", file=sys.stderr)
40
+ print("Set XCODEMCP_ALLOWED_FOLDERS environment variable to specify allowed folders.", file=sys.stderr)
41
+ return allowed_folders
42
+
43
+ # Process the list
44
+ folder_list = folder_list_str.split(":")
45
+ for folder in folder_list:
46
+ folder = folder.rstrip("/") # Normalize by removing trailing slash
47
+
48
+ # Skip empty entries
49
+ if not folder:
50
+ print(f"Warning: Skipping empty folder entry", file=sys.stderr)
51
+ continue
52
+
53
+ # Check if path is absolute
54
+ if not os.path.isabs(folder):
55
+ print(f"Warning: Skipping non-absolute path: {folder}", file=sys.stderr)
56
+ continue
57
+
58
+ # Check if path contains ".." components
59
+ if ".." in folder:
60
+ print(f"Warning: Skipping path with '..' components: {folder}", file=sys.stderr)
61
+ continue
62
+
63
+ # Check if path exists and is a directory
64
+ if not os.path.exists(folder):
65
+ print(f"Warning: Skipping non-existent path: {folder}", file=sys.stderr)
66
+ continue
67
+
68
+ if not os.path.isdir(folder):
69
+ print(f"Warning: Skipping non-directory path: {folder}", file=sys.stderr)
70
+ continue
71
+
72
+ # Add to allowed folders
73
+ allowed_folders.add(folder)
74
+ print(f"Added allowed folder: {folder}", file=sys.stderr)
75
+
76
+ return allowed_folders
77
+
78
+ def is_path_allowed(project_path: str) -> bool:
79
+ """
80
+ Check if a project path is allowed based on the allowed folders list.
81
+ Path must be a subfolder or direct match of an allowed folder.
82
+ """
83
+
84
+ global ALLOWED_FOLDERS
85
+ if not project_path:
86
+ print(f"Warning: not project_path: {project_path}", file=sys.stderr)
87
+ return False
88
+
89
+ # If no allowed folders are specified, nothing is allowed
90
+ if not ALLOWED_FOLDERS:
91
+ # try to fetch folder list
92
+ ALLOWED_FOLDERS = get_allowed_folders()
93
+ if not ALLOWED_FOLDERS:
94
+ print(f"Warning: not ALLOWED_FOLDERS: {', '.join(ALLOWED_FOLDERS)}", file=sys.stderr)
95
+ return False
96
+
97
+ # Normalize the path
98
+ project_path = os.path.abspath(project_path).rstrip("/")
99
+
100
+ # Check if path is in allowed folders
101
+ print(f"Warning: Normalized project_path: {project_path}", file=sys.stderr)
102
+ for allowed_folder in ALLOWED_FOLDERS:
103
+ # Direct match
104
+ if project_path == allowed_folder:
105
+ print(f"direct match to {allowed_folder}", file=sys.stderr)
106
+ return True
107
+
108
+ # Path is a subfolder
109
+ if project_path.startswith(allowed_folder + "/"):
110
+ print(f"Match to startswith {allowed_folder}", file=sys.stderr)
111
+ return True
112
+ print(f"no match of {project_path} with allowed folder {allowed_folder}", file=sys.stderr)
113
+ return False
114
+
115
+ # Initialize the MCP server
116
+ mcp = FastMCP("Xcode MCP Server")
117
+
118
+ # Helper functions for Xcode interaction
119
+ def get_frontmost_project() -> str:
120
+ """
121
+ Get the path to the frontmost Xcode project/workspace.
122
+ Returns empty string if no project is open.
123
+ """
124
+ script = '''
125
+ tell application "Xcode"
126
+ if it is running then
127
+ try
128
+ tell application "System Events"
129
+ tell process "Xcode"
130
+ set frontWindow to name of front window
131
+ end tell
132
+ end tell
133
+
134
+ set docPath to ""
135
+ try
136
+ set docPath to path of document 1
137
+ end try
138
+
139
+ return docPath
140
+ on error errMsg
141
+ return "ERROR: " & errMsg
142
+ end try
143
+ else
144
+ return "ERROR: Xcode is not running"
145
+ end if
146
+ end tell
147
+ '''
148
+ try:
149
+ result = subprocess.run(['osascript', '-e', script],
150
+ capture_output=True, text=True, check=True)
151
+ output = result.stdout.strip()
152
+
153
+ # Check if we got an error message from our AppleScript
154
+ if output.startswith("ERROR:"):
155
+ print(f"AppleScript error: {output}")
156
+ return ""
157
+
158
+ return output
159
+ except subprocess.CalledProcessError as e:
160
+ print(f"Error executing AppleScript: {e.stderr}")
161
+ return ""
162
+
163
+ def run_applescript(script: str) -> Tuple[bool, str]:
164
+ """Run an AppleScript and return success status and output"""
165
+ try:
166
+ result = subprocess.run(['osascript', '-e', script],
167
+ capture_output=True, text=True, check=True)
168
+ return True, result.stdout.strip()
169
+ except subprocess.CalledProcessError as e:
170
+ return False, e.stderr.strip()
171
+
172
+ # MCP Tools for Xcode
173
+
174
+ # @mcp.tool()
175
+ # def reinit_dirs() -> str:
176
+ # """
177
+ # Reinitialize the allowed folders.
178
+ # """
179
+ # global ALLOWED_FOLDERS
180
+ # ALLOWED_FOLDERS = get_allowed_folders()
181
+ # return f"Allowed folders reinitialized to: {ALLOWED_FOLDERS}"
182
+
183
+
184
+ @mcp.tool()
185
+ def get_xcode_projects(search_path: str) -> str:
186
+ """
187
+ Search the given search_path to find .xcodeproj (Xcode project) and
188
+ .xcworkspace (Xcode workspace) paths. If the search_path is empty,
189
+ all paths to which this tool has been granted access are searched.
190
+
191
+ Args:
192
+ search_path: Path to searched.
193
+
194
+ Returns:
195
+ A string which is a newline-separated list of .xcodeproj and
196
+ .xcworkspace paths found. If none are found, returns an empty string.
197
+ """
198
+
199
+
200
+ project_path = search_path
201
+
202
+ # Validate input
203
+ if not search_path or search_path.strip() == "":
204
+ project_path = "/Users/andrew/Documents/ncc_source"
205
+ # return "Error: project_path cannot be empty"
206
+
207
+
208
+ # Security check
209
+ if not is_path_allowed(project_path):
210
+ raise AccessDeniedError(f"Access to path '{project_path}' is not allowed. Set XCODEMCP_ALLOWED_FOLDERS environment variable.")
211
+ # return f"Error: Access to path '{project_path}' is not allowed. Set XCODEMCP_ALLOWED_FOLDERS environment variable."
212
+
213
+ # Check if the path exists
214
+ if os.path.exists(project_path):
215
+ # Show the basic file structure
216
+ try:
217
+ #mdfind -onlyin /Users/andrew/Documents/ncc_source/cursor 'kMDItemFSName == "*.xcodeproj" || kMDItemFSName == "*.xcworkspace"'
218
+ mdfindResult = subprocess.run(['mdfind', '-onlyin', project_path, 'kMDItemFSName == "*.xcodeproj" || kMDItemFSName == "*.xcworkspace"'],
219
+ capture_output=True, text=True, check=True)
220
+ result = mdfindResult.stdout.strip()
221
+ return result
222
+ except Exception as e:
223
+ raise XCodeMCPError(f"Error listing files in {project_path}: {str(e)}")
224
+ # return f"Error listing files in {project_path}: {str(e)}"
225
+ else:
226
+ raise InvalidParameterError(f"Project path does not exist: {project_path}")
227
+ # return f"Project path does not exist: {project_path}"
228
+
229
+
230
+ @mcp.tool()
231
+ def get_project_hierarchy(project_path: str) -> str:
232
+ """
233
+ Get the hierarchy of the specified Xcode project or workspace.
234
+
235
+ Args:
236
+ project_path: Path to an Xcode project/workspace directory.
237
+
238
+ Returns:
239
+ A string representation of the project hierarchy
240
+ """
241
+ # Validate input
242
+ if not project_path or project_path.strip() == "":
243
+ raise InvalidParameterError("project_path cannot be empty")
244
+ # return "Error: project_path cannot be empty"
245
+
246
+ # Security check
247
+ if not is_path_allowed(project_path):
248
+ raise AccessDeniedError(f"Access to path '{project_path}' is not allowed. Set XCODEMCP_ALLOWED_FOLDERS environment variable.")
249
+ # return f"Error: Access to path '{project_path}' is not allowed. Set XCODEMCP_ALLOWED_FOLDERS environment variable."
250
+
251
+ # Check if the path exists
252
+ if os.path.exists(project_path):
253
+ # Show the basic file structure
254
+ try:
255
+ result = subprocess.run(['find', project_path, '-type', 'f', '-name', '*.swift', '-o', '-name', '*.h', '-o', '-name', '*.m'],
256
+ capture_output=True, text=True, check=True)
257
+ files = result.stdout.strip().split('\n')
258
+ if not files or (len(files) == 1 and files[0] == ''):
259
+ raise InvalidParameterError(f"No source files found in {project_path}")
260
+ # return f"No source files found in {project_path}"
261
+
262
+ return f"Project at {project_path} contains {len(files)} source files:\n" + '\n'.join(files)
263
+ except Exception as e:
264
+ raise XCodeMCPError(f"Error listing files in {project_path}: {str(e)}")
265
+ # return f"Error listing files in {project_path}: {str(e)}"
266
+ else:
267
+ raise InvalidParameterError(f"Project path does not exist: {project_path}")
268
+ # return f"Project path does not exist: {project_path}"
269
+
270
+ @mcp.tool()
271
+ def build_project(project_path: str,
272
+ scheme: str) -> str:
273
+ """
274
+ Build the specified Xcode project or workspace.
275
+
276
+ Args:
277
+ project_path: Path to an Xcode project/workspace directory.
278
+ scheme: Name of the scheme to build.
279
+
280
+ Returns:
281
+ On success, returns "Build succeeded with 0 errors."
282
+ On failure, returns the first (up to) 100 error lines from the build log.
283
+ """
284
+ # Validate input
285
+ if not project_path or project_path.strip() == "":
286
+ raise InvalidParameterError("project_path cannot be empty")
287
+
288
+ # Security check
289
+ if not is_path_allowed(project_path):
290
+ raise AccessDeniedError(f"Access to path '{project_path}' is not allowed. Set XCODEMCP_ALLOWED_FOLDERS environment variable.")
291
+
292
+ if not os.path.exists(project_path):
293
+ raise InvalidParameterError(f"Project path does not exist: {project_path}")
294
+
295
+ # TODO: Implement build command using AppleScript or shell
296
+ script = f'''
297
+ set projectPath to "{project_path}"
298
+ set schemeName to "{scheme}"
299
+
300
+ --
301
+ -- Then run with: osascript <thisfilename>
302
+ --
303
+ tell application "Xcode"
304
+ -- 1. Open the project file
305
+ open projectPath
306
+
307
+ -- 2. Get the workspace document
308
+ set workspaceDoc to first workspace document whose path is projectPath
309
+
310
+ -- 3. Wait for it to load (timeout after ~30 seconds)
311
+ repeat 60 times
312
+ if loaded of workspaceDoc is true then exit repeat
313
+ delay 0.5
314
+ end repeat
315
+
316
+ if loaded of workspaceDoc is false then
317
+ error "Xcode workspace did not load in time."
318
+ end if
319
+
320
+ -- 4. Set the active scheme
321
+ set active scheme of workspaceDoc to (first scheme of workspaceDoc whose name is schemeName)
322
+
323
+ -- 5. Build
324
+ set actionResult to build workspaceDoc
325
+
326
+ -- 6. Wait for completion
327
+ repeat
328
+ if completed of actionResult is true then exit repeat
329
+ delay 0.5
330
+ end repeat
331
+
332
+ -- 7. Check result
333
+ set buildStatus to status of actionResult
334
+ if buildStatus is succeeded then
335
+ -- display dialog "Build succeeded"
336
+ return "Build succeeded."
337
+ else
338
+ return build log of actionResult
339
+ end if
340
+
341
+ end tell
342
+ '''
343
+
344
+ success, output = run_applescript(script)
345
+
346
+ if success:
347
+ if output == "Build succeeded.":
348
+ return "Build succeeded with 0 errors."
349
+ else:
350
+ output_lines = output.split("\n")
351
+ error_lines = [line for line in output_lines if "error" in line]
352
+
353
+ # Limit to first 100 error lines
354
+ if len(error_lines) > 100:
355
+ error_lines = error_lines[:100]
356
+ error_lines.append("... (truncated to first 100 error lines)")
357
+
358
+ error_list = "\n".join(error_lines)
359
+ return f"Build failed with errors:\n{error_list}"
360
+ else:
361
+ raise XCodeMCPError(f"Build failed to start for scheme {scheme} in project {project_path}: {output}")
362
+
363
+ @mcp.tool()
364
+ def run_project(project_path: str,
365
+ scheme: Optional[str] = None) -> str:
366
+ """
367
+ Run the specified Xcode project or workspace.
368
+
369
+ Args:
370
+ project_path: Path to an Xcode project/workspace directory.
371
+ scheme: Optional scheme to run. If not provided, uses the active scheme.
372
+
373
+ Returns:
374
+ Output message
375
+ """
376
+ # Validate input
377
+ if not project_path or project_path.strip() == "":
378
+ raise InvalidParameterError("project_path cannot be empty")
379
+
380
+ # Security check
381
+ if not is_path_allowed(project_path):
382
+ raise AccessDeniedError(f"Access to path '{project_path}' is not allowed. Set XCODEMCP_ALLOWED_FOLDERS environment variable.")
383
+
384
+ if not os.path.exists(project_path):
385
+ raise InvalidParameterError(f"Project path does not exist: {project_path}")
386
+
387
+ # TODO: Implement run command using AppleScript
388
+ script = f'''
389
+ tell application "Xcode"
390
+ open "{project_path}"
391
+ delay 1
392
+ set frontWindow to front window
393
+ tell frontWindow
394
+ set currentWorkspace to workspace
395
+ run currentWorkspace
396
+ end tell
397
+ end tell
398
+ '''
399
+
400
+ success, output = run_applescript(script)
401
+
402
+ if success:
403
+ return "Run started successfully"
404
+ else:
405
+ raise XCodeMCPError(f"Run failed to start: {output}")
406
+
407
+ @mcp.tool()
408
+ def get_build_errors(project_path: str) -> str:
409
+ """
410
+ Get the build errors for the specified Xcode project or workspace.
411
+
412
+ Args:
413
+ project_path: Path to an Xcode project/workspace directory.
414
+
415
+ Returns:
416
+ A string containing the build errors or a message if there are none
417
+ """
418
+ # Validate input
419
+ if not project_path or project_path.strip() == "":
420
+ raise InvalidParameterError("project_path cannot be empty")
421
+
422
+ # Security check
423
+ if not is_path_allowed(project_path):
424
+ raise AccessDeniedError(f"Access to path '{project_path}' is not allowed. Set XCODEMCP_ALLOWED_FOLDERS environment variable.")
425
+
426
+ if not os.path.exists(project_path):
427
+ raise InvalidParameterError(f"Project path does not exist: {project_path}")
428
+
429
+ # TODO: Implement error retrieval using AppleScript or by parsing logs
430
+ script = f'''
431
+ tell application "Xcode"
432
+ open "{project_path}"
433
+ delay 1
434
+ set frontWindow to front window
435
+ tell frontWindow
436
+ set currentWorkspace to workspace
437
+ set issuesList to get issues
438
+ set issuesText to ""
439
+ set issueCount to 0
440
+
441
+ repeat with anIssue in issuesList
442
+ if issueCount ≥ 100 then exit repeat
443
+ set issuesText to issuesText & "- " & message of anIssue & "\n"
444
+ set issueCount to issueCount + 1
445
+ end repeat
446
+
447
+ return issuesText
448
+ end tell
449
+ end tell
450
+ '''
451
+
452
+ # This script syntax may need to be adjusted based on actual AppleScript capabilities
453
+ success, output = run_applescript(script)
454
+
455
+ if success and output:
456
+ return output
457
+ elif success:
458
+ return "No build errors found."
459
+ else:
460
+ raise XCodeMCPError(f"Failed to retrieve build errors: {output}")
461
+
462
+ @mcp.tool()
463
+ def clean_project(project_path: str) -> str:
464
+ """
465
+ Clean the specified Xcode project or workspace.
466
+
467
+ Args:
468
+ project_path: Path to an Xcode project/workspace directory.
469
+
470
+ Returns:
471
+ Output message
472
+ """
473
+ # Validate input
474
+ if not project_path or project_path.strip() == "":
475
+ raise InvalidParameterError("project_path cannot be empty")
476
+
477
+ # Security check
478
+ if not is_path_allowed(project_path):
479
+ raise AccessDeniedError(f"Access to path '{project_path}' is not allowed. Set XCODEMCP_ALLOWED_FOLDERS environment variable.")
480
+
481
+ if not os.path.exists(project_path):
482
+ raise InvalidParameterError(f"Project path does not exist: {project_path}")
483
+
484
+ # TODO: Implement clean command using AppleScript
485
+ script = f'''
486
+ tell application "Xcode"
487
+ open "{project_path}"
488
+ delay 1
489
+ set frontWindow to front window
490
+ tell frontWindow
491
+ set currentWorkspace to workspace
492
+ clean currentWorkspace
493
+ end tell
494
+ end tell
495
+ '''
496
+
497
+ success, output = run_applescript(script)
498
+
499
+ if success:
500
+ return "Clean completed successfully"
501
+ else:
502
+ raise XCodeMCPError(f"Clean failed: {output}")
503
+
504
+ @mcp.tool()
505
+ def get_runtime_output(project_path: str,
506
+ max_lines: int = 100) -> str:
507
+ """
508
+ Get the runtime output from the console for the specified Xcode project.
509
+
510
+ Args:
511
+ project_path: Path to an Xcode project/workspace directory.
512
+ max_lines: Maximum number of lines to retrieve. Defaults to 100.
513
+
514
+ Returns:
515
+ Console output as a string
516
+ """
517
+ # Validate input
518
+ if not project_path or project_path.strip() == "":
519
+ raise InvalidParameterError("project_path cannot be empty")
520
+
521
+ # Security check
522
+ if not is_path_allowed(project_path):
523
+ raise AccessDeniedError(f"Access to path '{project_path}' is not allowed. Set XCODEMCP_ALLOWED_FOLDERS environment variable.")
524
+
525
+ if not os.path.exists(project_path):
526
+ raise InvalidParameterError(f"Project path does not exist: {project_path}")
527
+
528
+ # TODO: Implement console output retrieval
529
+ # This is a placeholder as you mentioned this functionality isn't available yet
530
+ raise XCodeMCPError("Runtime output retrieval not yet implemented")
531
+
532
+ # Run the server if executed directly
533
+ if __name__ == "__main__":
534
+ # Initialize allowed folders
535
+ ALLOWED_FOLDERS = get_allowed_folders()
536
+
537
+ # Debug info
538
+ print(f"Allowed folders: {ALLOWED_FOLDERS}", file=sys.stderr)
539
+
540
+ # Run the server
541
+ mcp.run()
@@ -0,0 +1,153 @@
1
+ Metadata-Version: 2.4
2
+ Name: xcode-mcp-server
3
+ Version: 1.0.0
4
+ Summary: MCP server for Xcode integration
5
+ Project-URL: Homepage, https://github.com/yourusername/xcode-mcp-server
6
+ Project-URL: Repository, https://github.com/yourusername/xcode-mcp-server
7
+ Project-URL: Issues, https://github.com/yourusername/xcode-mcp-server/issues
8
+ Author-email: Your Name <your.email@example.com>
9
+ License: MIT
10
+ Keywords: mcp,server,xcode
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.8
16
+ Classifier: Programming Language :: Python :: 3.9
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Requires-Python: >=3.8
21
+ Requires-Dist: mcp[cli]>=1.2.0
22
+ Description-Content-Type: text/markdown
23
+
24
+ # Xcode MCP Server
25
+
26
+ An MCP (Model Context Protocol) server for controlling and interacting with Xcode from AI assistants like Claude.
27
+
28
+ ## Features
29
+
30
+ - Get project hierarchy
31
+ - Build and run projects
32
+ - Retrieve build errors
33
+ - Get runtime output (placeholder)
34
+ - Clean projects
35
+
36
+ ## Security
37
+
38
+ The server implements path-based security to prevent unauthorized access to files outside of allowed directories:
39
+
40
+ - You must specify allowed folders using the environment variable:
41
+ - `XCODEMCP_ALLOWED_FOLDERS=/path1:/path2:/path3`
42
+
43
+ Security requirements:
44
+ - All paths must be absolute (starting with /)
45
+ - No path components with `..` are allowed
46
+ - All paths must exist and be directories
47
+
48
+ Example:
49
+ ```bash
50
+ # Set the environment variable
51
+ export XCODEMCP_ALLOWED_FOLDERS=/Users/username/Projects:/Users/username/checkouts
52
+ python3 xcode_mcp.py
53
+
54
+ # Or inline with the MCP command
55
+ XCODEMCP_ALLOWED_FOLDERS=/Users/username/Projects mcp dev xcode_mcp.py
56
+ ```
57
+
58
+ If no allowed folders are specified, access will be restricted and tools will return error messages.
59
+
60
+ ## Setup
61
+
62
+ 1. Install dependencies:
63
+
64
+ ```bash
65
+ # Using pip
66
+ pip install -r requirements.txt
67
+
68
+ # Or using uv (recommended)
69
+ uv pip install -r requirements.txt
70
+ ```
71
+
72
+ If you don't have pip installed, you can do:
73
+ ```
74
+ brew install pip
75
+ ```
76
+
77
+ 2. Configure Claude for Desktop:
78
+
79
+ Open/create your Claude for Desktop configuration file at `~/Library/Application Support/Claude/claude_desktop_config.json` and add:
80
+
81
+ ```json
82
+ {
83
+ "mcpServers": {
84
+ "xcode": {
85
+ "command": "python3",
86
+ "args": [
87
+ "/ABSOLUTE/PATH/TO/xcode_mcp.py"
88
+ ],
89
+ "env": {
90
+ "XCODEMCP_ALLOWED_FOLDERS": "/path/to/projects:/path/to/other/projects"
91
+ }
92
+ }
93
+ }
94
+ }
95
+ ```
96
+
97
+ Replace `/ABSOLUTE/PATH/TO/xcode_mcp.py` with the actual path to your xcode_mcp.py file, and set appropriate allowed folders in the `env` section.
98
+
99
+ ## Usage
100
+
101
+ 1. Open Xcode with a project
102
+ 2. Start Claude for Desktop
103
+ 3. Look for the hammer icon to find available Xcode tools
104
+ 4. Use natural language to interact with Xcode, for example:
105
+ - "Build the project at /path/to/MyProject.xcodeproj"
106
+ - "Run the app in /path/to/MyProject"
107
+ - "What build errors are there in /path/to/MyProject.xcodeproj?"
108
+ - "Clean the project at /path/to/MyProject"
109
+
110
+ ### Parameter Format
111
+
112
+ All tools require a `project_path` parameter pointing to an Xcode project/workspace directory:
113
+
114
+ ```
115
+ "/path/to/your/project.xcodeproj"
116
+ ```
117
+
118
+ or
119
+
120
+ ```
121
+ "/path/to/your/project"
122
+ ```
123
+
124
+ ## Development
125
+
126
+ The server is built with the MCP Python SDK and uses AppleScript to communicate with Xcode.
127
+
128
+ To test the server locally without Claude, use:
129
+
130
+ ```bash
131
+ # Set the environment variable first
132
+ export XCODEMCP_ALLOWED_FOLDERS=/Users/username/Projects
133
+ mcp dev xcode_mcp.py
134
+
135
+ # Or inline with the command
136
+ XCODEMCP_ALLOWED_FOLDERS=/Users/username/Projects mcp dev xcode_mcp.py
137
+ ```
138
+
139
+ This will open the MCP Inspector interface where you can test the tools directly.
140
+
141
+ ### Testing in MCP Inspector
142
+
143
+ When testing in the MCP Inspector, provide input values as quoted strings:
144
+
145
+ ```
146
+ "/Users/username/Projects/MyApp"
147
+ ```
148
+
149
+ ## Limitations
150
+
151
+ - Runtime output retrieval is not yet implemented
152
+ - Project hierarchy is a simple file listing implementation
153
+ - AppleScript syntax may need adjustments for specific Xcode versions # xcode-mcp-server
@@ -0,0 +1,6 @@
1
+ xcode_mcp_server/__init__.py,sha256=isXCmR_ASdjNbBCThfuDBh5mukoOQLlhYZIxZci5l6E,578
2
+ xcode_mcp_server/__main__.py,sha256=9V6gTwUEYPkf5bVtCjNyjw3_YF-zpVhOYbzdFJTTWAk,18997
3
+ xcode_mcp_server-1.0.0.dist-info/METADATA,sha256=3G4TaoqRHu8o98cK3Qd3Tj4vIR3bPK76UcWCQRZeRrw,4281
4
+ xcode_mcp_server-1.0.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
5
+ xcode_mcp_server-1.0.0.dist-info/entry_points.txt,sha256=u3sbPCAACGxesL3YtGByZRj6hXkL_FqncBmUMW1SEzo,59
6
+ xcode_mcp_server-1.0.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.27.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ xcode-mcp-server = xcode_mcp_server:main