├── .DS_Store
├── .gitattributes
├── Users
└── toddbruss
│ └── Documents
│ └── GitHub
│ └── xcf
│ └── test_absolute_dir
│ └── test.swift
├── xcf
├── Mcp
│ ├── XcDoc
│ │ ├── Assets.xcassets
│ │ │ ├── Contents.json
│ │ │ ├── AppIcon.appiconset
│ │ │ │ ├── Artboard 1.png
│ │ │ │ ├── Artboard 2.png
│ │ │ │ ├── Artboard 3.png
│ │ │ │ ├── Artboard 4.png
│ │ │ │ ├── Artboard 5.png
│ │ │ │ ├── Artboard 6.png
│ │ │ │ ├── Artboard 7.png
│ │ │ │ ├── Artboard 8.png
│ │ │ │ ├── Artboard 9.png
│ │ │ │ ├── Artboard 10.png
│ │ │ │ └── Contents.json
│ │ │ └── AccentColor.colorset
│ │ │ │ └── Contents.json
│ │ ├── XcfMcpXcodeDocumentHandlers.swift
│ │ └── XcfXcodeProjectManager.swift
│ ├── Tools
│ │ ├── XcfMcpHelpHandlers.swift
│ │ ├── XcfMcpCodeAnalysisHandlers.swift
│ │ ├── XcfMcpActionHandlers.swift
│ │ ├── XcfMcpCodeSnippetHandlers.swift
│ │ ├── XcfMcpToolCallHandlers.swift
│ │ ├── XcfMcpDiffHandlers.swift
│ │ └── XcfMcpFileSystemHandlers.swift
│ ├── File
│ │ ├── XcfFileFinder.swift
│ │ ├── XcfFuzzyLogicService.swift
│ │ └── XcfSecurityManager.swift
│ ├── ServerConfig
│ │ ├── XcfMcpServer.swift
│ │ ├── XcfMcpHandlers.swift
│ │ ├── XcfMcpConstants.swift
│ │ └── XcfHelpText.swift
│ ├── Resources
│ │ ├── XcfMcpResourceHandlers.swift
│ │ └── XcfMcpResources.swift
│ ├── Script
│ │ ├── XcfOsaScript.swift
│ │ ├── XcfCaptureSnippet.swift
│ │ ├── XcfSwiftDiff.swift
│ │ └── XcfSwiftScript.swift
│ └── Prompts
│ │ ├── XcfMcpPromptHandlers.swift
│ │ └── XcfMcpPrompts.swift
├── xcf.entitlements
├── main.swift
└── ORGANIZATION_SUMMARY.md
├── xcf.xcodeproj
├── project.xcworkspace
│ ├── contents.xcworkspacedata
│ ├── xcuserdata
│ │ └── toddbruss.xcuserdatad
│ │ │ └── UserInterfaceState.xcuserstate
│ └── xcshareddata
│ │ └── swiftpm
│ │ └── Package.resolved
├── xcuserdata
│ └── toddbruss.xcuserdatad
│ │ ├── xcdebugger
│ │ └── Breakpoints_v2.xcbkptlist
│ │ └── xcschemes
│ │ └── xcschememanagement.plist
└── xcshareddata
│ └── xcschemes
│ └── xcf.xcscheme
├── mcp.json
├── .cursor
├── mcp.json
└── rules
│ ├── xcf-quickref.mdc
│ ├── xcf-ai-guide.mdc
│ └── xcf-user-guide.mdc
├── .gitignore
├── LICENSE
├── xcf-quickref.md
├── xcf-ai-guide.md
└── xcf-user-guide.md
/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeFreezeAI/xcf/HEAD/.DS_Store
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
--------------------------------------------------------------------------------
/Users/toddbruss/Documents/GitHub/xcf/test_absolute_dir/test.swift:
--------------------------------------------------------------------------------
1 | Testing absolute path: \(path)
--------------------------------------------------------------------------------
/xcf/Mcp/XcDoc/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/xcf/Mcp/XcDoc/Assets.xcassets/AppIcon.appiconset/Artboard 1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeFreezeAI/xcf/HEAD/xcf/Mcp/XcDoc/Assets.xcassets/AppIcon.appiconset/Artboard 1.png
--------------------------------------------------------------------------------
/xcf/Mcp/XcDoc/Assets.xcassets/AppIcon.appiconset/Artboard 2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeFreezeAI/xcf/HEAD/xcf/Mcp/XcDoc/Assets.xcassets/AppIcon.appiconset/Artboard 2.png
--------------------------------------------------------------------------------
/xcf/Mcp/XcDoc/Assets.xcassets/AppIcon.appiconset/Artboard 3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeFreezeAI/xcf/HEAD/xcf/Mcp/XcDoc/Assets.xcassets/AppIcon.appiconset/Artboard 3.png
--------------------------------------------------------------------------------
/xcf/Mcp/XcDoc/Assets.xcassets/AppIcon.appiconset/Artboard 4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeFreezeAI/xcf/HEAD/xcf/Mcp/XcDoc/Assets.xcassets/AppIcon.appiconset/Artboard 4.png
--------------------------------------------------------------------------------
/xcf/Mcp/XcDoc/Assets.xcassets/AppIcon.appiconset/Artboard 5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeFreezeAI/xcf/HEAD/xcf/Mcp/XcDoc/Assets.xcassets/AppIcon.appiconset/Artboard 5.png
--------------------------------------------------------------------------------
/xcf/Mcp/XcDoc/Assets.xcassets/AppIcon.appiconset/Artboard 6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeFreezeAI/xcf/HEAD/xcf/Mcp/XcDoc/Assets.xcassets/AppIcon.appiconset/Artboard 6.png
--------------------------------------------------------------------------------
/xcf/Mcp/XcDoc/Assets.xcassets/AppIcon.appiconset/Artboard 7.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeFreezeAI/xcf/HEAD/xcf/Mcp/XcDoc/Assets.xcassets/AppIcon.appiconset/Artboard 7.png
--------------------------------------------------------------------------------
/xcf/Mcp/XcDoc/Assets.xcassets/AppIcon.appiconset/Artboard 8.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeFreezeAI/xcf/HEAD/xcf/Mcp/XcDoc/Assets.xcassets/AppIcon.appiconset/Artboard 8.png
--------------------------------------------------------------------------------
/xcf/Mcp/XcDoc/Assets.xcassets/AppIcon.appiconset/Artboard 9.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeFreezeAI/xcf/HEAD/xcf/Mcp/XcDoc/Assets.xcassets/AppIcon.appiconset/Artboard 9.png
--------------------------------------------------------------------------------
/xcf/Mcp/XcDoc/Assets.xcassets/AppIcon.appiconset/Artboard 10.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeFreezeAI/xcf/HEAD/xcf/Mcp/XcDoc/Assets.xcassets/AppIcon.appiconset/Artboard 10.png
--------------------------------------------------------------------------------
/xcf.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/xcf/xcf.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/xcf/Mcp/XcDoc/Assets.xcassets/AccentColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "idiom" : "universal"
5 | }
6 | ],
7 | "info" : {
8 | "author" : "xcode",
9 | "version" : 1
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/xcf.xcodeproj/project.xcworkspace/xcuserdata/toddbruss.xcuserdatad/UserInterfaceState.xcuserstate:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeFreezeAI/xcf/HEAD/xcf.xcodeproj/project.xcworkspace/xcuserdata/toddbruss.xcuserdatad/UserInterfaceState.xcuserstate
--------------------------------------------------------------------------------
/xcf.xcodeproj/xcuserdata/toddbruss.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
--------------------------------------------------------------------------------
/mcp.json:
--------------------------------------------------------------------------------
1 | {
2 | "mcpServers": {
3 | "xcf": {
4 | "type": "stdio",
5 | "command": "/Applications/xcf.app/Contents/MacOS/xcf server",
6 | "env": {
7 | "XCODE_PROJECT_FOLDER_optional": "/path/to/project/",
8 | "XCODE_PROJECT_optional": "/path/to/project/project.xcodeproj"
9 | }
10 | }
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/.cursor/mcp.json:
--------------------------------------------------------------------------------
1 | {
2 | "mcpServers": {
3 | "xcf": {
4 | "type": "stdio",
5 | "command": "/Applications/xcf.app/Contents/MacOS/xcf server",
6 | "env": {
7 | "XCODE_PROJECT_FOLDER_optional": "/path/to/project/",
8 | "XCODE_PROJECT_optional": "/path/to/project/project.xcodeproj"
9 | }
10 | }
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | xcf.xcodeproj/project.xcworkspace/xcuserdata/toddbruss.xcuserdatad/UserInterfaceState.xcuserstate
2 | xcf.xcodeproj/project.xcworkspace/xcuserdata/toddbruss.xcuserdatad/UserInterfaceState.xcuserstate
3 | .DS_Store
4 | *.xcuserstate
5 | .DS_Store
6 | xcf.xcodeproj/project.xcworkspace/xcuserdata/toddbruss.xcuserdatad/UserInterfaceState.xcuserstate
7 | .DS_Store
8 | xcf.xcodeproj/project.xcworkspace/xcuserdata/toddbruss.xcuserdatad/UserInterfaceState.xcuserstate
9 | .DS_Store
10 | xcf.xcodeproj/project.xcworkspace/xcuserdata/toddbruss.xcuserdatad/UserInterfaceState.xcuserstate
11 | *.xcuserstate
12 | .DS_Store
13 | .DS_Store
14 |
--------------------------------------------------------------------------------
/xcf.xcodeproj/xcuserdata/toddbruss.xcuserdatad/xcschemes/xcschememanagement.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | SchemeUserState
6 |
7 | xcf.xcscheme_^#shared#^_
8 |
9 | orderHint
10 | 0
11 |
12 |
13 | SuppressBuildableAutocreation
14 |
15 | EA8D401B2DD0533700464A89
16 |
17 | primary
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/xcf/Mcp/Tools/XcfMcpHelpHandlers.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import MCP
3 |
4 | // MARK: - Help & Documentation Tool Handlers
5 |
6 | class XcfMcpHelpHandlers {
7 |
8 | /// Handles a call to the quick help tool (xcf actions only)
9 | static func handleQuickHelpToolCall(_ params: CallTool.Parameters) -> CallTool.Result {
10 | return CallTool.Result(content: [.text(HelpText.basic)])
11 | }
12 |
13 | /// Handles a call to the regular help tool
14 | static func handleHelpToolCall(_ params: CallTool.Parameters) -> CallTool.Result {
15 | return CallTool.Result(content: [.text(HelpText.detailed)])
16 | }
17 |
18 | /// Handles a call to the tools reference tool
19 | static func handleToolsReferenceToolCall(_ params: CallTool.Parameters) -> CallTool.Result {
20 | return CallTool.Result(content: [.text(HelpText.toolsReference)])
21 | }
22 | }
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2025 SuperBox64
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/xcf/Mcp/XcDoc/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "Artboard 10.png",
5 | "idiom" : "mac",
6 | "scale" : "1x",
7 | "size" : "16x16"
8 | },
9 | {
10 | "filename" : "Artboard 9.png",
11 | "idiom" : "mac",
12 | "scale" : "2x",
13 | "size" : "16x16"
14 | },
15 | {
16 | "filename" : "Artboard 8.png",
17 | "idiom" : "mac",
18 | "scale" : "1x",
19 | "size" : "32x32"
20 | },
21 | {
22 | "filename" : "Artboard 7.png",
23 | "idiom" : "mac",
24 | "scale" : "2x",
25 | "size" : "32x32"
26 | },
27 | {
28 | "filename" : "Artboard 6.png",
29 | "idiom" : "mac",
30 | "scale" : "1x",
31 | "size" : "128x128"
32 | },
33 | {
34 | "filename" : "Artboard 5.png",
35 | "idiom" : "mac",
36 | "scale" : "2x",
37 | "size" : "128x128"
38 | },
39 | {
40 | "filename" : "Artboard 4.png",
41 | "idiom" : "mac",
42 | "scale" : "1x",
43 | "size" : "256x256"
44 | },
45 | {
46 | "filename" : "Artboard 3.png",
47 | "idiom" : "mac",
48 | "scale" : "2x",
49 | "size" : "256x256"
50 | },
51 | {
52 | "filename" : "Artboard 2.png",
53 | "idiom" : "mac",
54 | "scale" : "1x",
55 | "size" : "512x512"
56 | },
57 | {
58 | "filename" : "Artboard 1.png",
59 | "idiom" : "mac",
60 | "scale" : "2x",
61 | "size" : "512x512"
62 | }
63 | ],
64 | "info" : {
65 | "author" : "xcode",
66 | "version" : 1
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/xcf/Mcp/File/XcfFileFinder.swift:
--------------------------------------------------------------------------------
1 | //
2 | // XcfFileFinder.swift
3 | // xcf
4 | //
5 | // Created by Todd Bruss on 5/9/25.
6 | //
7 |
8 | import Foundation
9 |
10 | /// A utility class to find files in the workspace using multiple strategies.
11 | struct FileFinder {
12 | /// Attempts to intelligently resolve a file path using multiple strategies
13 | /// - Parameter originalPath: The original path provided
14 | /// - Returns: A tuple containing (resolvedPath, warningMessage) where resolvedPath is the resolved path or original if no strategies succeed, and warningMessage contains any warnings about duplicate files
15 | static func resolveFilePath(_ originalPath: String) -> (String, String) {
16 | return FuzzyLogicService.resolveFilePath(originalPath)
17 | }
18 |
19 | /// Determines the language of a file based on its extension
20 | /// - Parameter filePath: The path to the file
21 | /// - Returns: A string indicating the language of the file
22 | public static func determineLanguage(from filePath: String) -> String {
23 | let fileExtension = (filePath as NSString).pathExtension.lowercased()
24 |
25 | switch fileExtension {
26 | case "swift":
27 | return "swift"
28 | case "m", "h":
29 | return "objc"
30 | case "c", "cpp", "cc":
31 | return "c"
32 | case "js":
33 | return "javascript"
34 | case "py":
35 | return "python"
36 | case "rb":
37 | return "ruby"
38 | case "java":
39 | return "java"
40 | case "html", "htm":
41 | return "html"
42 | case "css":
43 | return "css"
44 | case "json":
45 | return "json"
46 | case "xml":
47 | return "xml"
48 | default:
49 | return "text"
50 | }
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/xcf/Mcp/ServerConfig/XcfMcpServer.swift:
--------------------------------------------------------------------------------
1 | //
2 | // XcfMcpServer.swift
3 | // xcf
4 | //
5 | // Created by Todd Bruss on 5/7/25.
6 | //
7 |
8 | import Foundation
9 | import MCP
10 |
11 | /// McpServer handles all MCP protocol interactions for the xcf tool.
12 | /// It defines tools, resources, and prompts, and configures the MCP server with appropriate handlers.
13 | class XcfMcpServer {
14 | static var XcfScript = XcfSwiftScript.shared
15 |
16 | // MARK: - Core Server Setup
17 |
18 | /// Configures and starts the MCP server
19 | /// - Returns: The configured MCP server
20 | /// - Throws: Errors from server initialization or start
21 | static func configureMcpServer() async throws -> Server {
22 | let projectManager = XcfXcodeProjectManager.shared
23 |
24 | // Initialize the project manager
25 | projectManager.initialize()
26 |
27 | // If no project was set from environment, try to select one
28 | if projectManager.currentProject == nil {
29 | projectManager.currentProject = await XcfMcpActionHandler.selectProject()
30 | }
31 |
32 | // Set up the server with enhanced capabilities
33 | let server = Server(
34 | name: McpConfig.serverName,
35 | version: McpConfig.serverVersion,
36 | capabilities: .init(
37 | prompts: .init(listChanged: true),
38 | resources: .init(subscribe: true, listChanged: true),
39 | tools: .init(listChanged: true)
40 | )
41 | )
42 |
43 | // Configure the transport
44 | let transport = StdioTransport()
45 |
46 | do {
47 | // Start the server with the configured transport
48 | try await server.start(transport: transport)
49 |
50 | // Register all handlers using the new handler classes
51 | await XcfMcpHandlers.registerAllHandlers(server: server)
52 |
53 | return server
54 | } catch {
55 | throw error
56 | }
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/xcf/main.swift:
--------------------------------------------------------------------------------
1 | //
2 | // main.swift
3 | // xcf
4 | //
5 | // Created by Todd Bruss on 5/3/25.
6 | //
7 |
8 | import Cocoa
9 | import MCP
10 | import MultiLineDiff
11 |
12 | // Process command line arguments
13 | func handleArguments() {
14 | // Check command-line arguments for "server" flag
15 | if CommandLine.arguments.count > 1 && CommandLine.arguments[1] == "server" {
16 | // Print welcome message
17 |
18 | // Main async task
19 | Task.detached {
20 | do {
21 | // Configure and start the MCP server
22 | let server = try await XcfMcpServer.configureMcpServer()
23 |
24 | // Wait until the server completes
25 | await server.waitUntilCompleted()
26 | } catch {
27 | print(String(format: McpConfig.errorStartingServer, "\(error)"))
28 | }
29 | }
30 |
31 | // Initialize the application
32 | NSApplication.shared.setActivationPolicy(.prohibited)
33 | RunLoop.current.run()
34 |
35 | } else {
36 | NSApplication.shared.setActivationPolicy(.accessory)
37 |
38 | ModalDisplay().showServerRequiredAlert()
39 | NSApplication.shared.terminate(nil)
40 |
41 | }
42 | }
43 |
44 |
45 | class ModalDisplay {
46 |
47 | func showServerRequiredAlert() {
48 | let alert = NSAlert()
49 | alert.messageText = "Use 'server' argument in your mcp.json"
50 | alert.informativeText = "/Applications/xcf.app/Contents/MacOS/xcf server"
51 | alert.alertStyle = .warning
52 | let quitButton = alert.addButton(withTitle: "Press to Quit this XCF Xcode MCP Server")
53 | quitButton.hasDestructiveAction = false
54 | quitButton.keyEquivalent = "\r" // Return key
55 | alert.runModal()
56 |
57 |
58 | }
59 | }
60 |
61 | // Set up signal handling for clean shutdown
62 | signal(SIGINT) { _ in
63 | print("\nShutting down...")
64 | exit(0)
65 | }
66 |
67 | signal(SIGTERM) { _ in
68 | print("\nShutting down...")
69 | exit(0)
70 | }
71 |
72 | handleArguments()
73 |
--------------------------------------------------------------------------------
/xcf.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved:
--------------------------------------------------------------------------------
1 | {
2 | "originHash" : "b58ab3d2524826d1ee6381aab41445f36db72aa6c60122ae51658543ea6e9fee",
3 | "pins" : [
4 | {
5 | "identity" : "eventsource",
6 | "kind" : "remoteSourceControl",
7 | "location" : "https://github.com/mattt/eventsource.git",
8 | "state" : {
9 | "revision" : "07957602bb99a5355c810187e66e6ce378a1057d",
10 | "version" : "1.1.1"
11 | }
12 | },
13 | {
14 | "identity" : "swift-log",
15 | "kind" : "remoteSourceControl",
16 | "location" : "https://github.com/apple/swift-log.git",
17 | "state" : {
18 | "revision" : "ce592ae52f982c847a4efc0dd881cc9eb32d29f2",
19 | "version" : "1.6.4"
20 | }
21 | },
22 | {
23 | "identity" : "swift-multi-line-diff",
24 | "kind" : "remoteSourceControl",
25 | "location" : "https://github.com/codefreezeai/swift-multi-line-diff.git",
26 | "state" : {
27 | "revision" : "696849afadd73e2fbecb64a2f2e1272d17ea298c",
28 | "version" : "2.0.2"
29 | }
30 | },
31 | {
32 | "identity" : "swift-sdk",
33 | "kind" : "remoteSourceControl",
34 | "location" : "https://github.com/modelcontextprotocol/swift-sdk.git",
35 | "state" : {
36 | "revision" : "f1b50e6de22b5206068bb09851d585f560d893c4",
37 | "version" : "0.10.1"
38 | }
39 | },
40 | {
41 | "identity" : "swift-syntax",
42 | "kind" : "remoteSourceControl",
43 | "location" : "https://github.com/apple/swift-syntax.git",
44 | "state" : {
45 | "revision" : "f99ae8aa18f0cf0d53481901f88a0991dc3bd4a2",
46 | "version" : "601.0.1"
47 | }
48 | },
49 | {
50 | "identity" : "swift-system",
51 | "kind" : "remoteSourceControl",
52 | "location" : "https://github.com/apple/swift-system.git",
53 | "state" : {
54 | "revision" : "890830fff1a577dc83134890c7984020c5f6b43b",
55 | "version" : "1.6.2"
56 | }
57 | },
58 | {
59 | "identity" : "xcf-swift",
60 | "kind" : "remoteSourceControl",
61 | "location" : "https://github.com/codefreezeai/xcf-swift",
62 | "state" : {
63 | "revision" : "2bc70856401230b093956dee35ac9bec174fec62",
64 | "version" : "1.0.0"
65 | }
66 | }
67 | ],
68 | "version" : 3
69 | }
70 |
--------------------------------------------------------------------------------
/xcf/Mcp/Tools/XcfMcpCodeAnalysisHandlers.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import MCP
3 |
4 | // MARK: - Code Analysis Tool Handlers
5 |
6 | class XcfMcpCodeAnalysisHandlers {
7 |
8 | /// Handles a call to the snippet tool
9 | static func handleSnippetToolCall(_ params: CallTool.Parameters) throws -> CallTool.Result {
10 | guard let arguments = params.arguments else {
11 | return CallTool.Result(content: [.text(McpConfig.missingFilePathError)])
12 | }
13 |
14 | // Try to get filePath from arguments in two ways:
15 | // 1. As a named parameter (filePath=...)
16 | // 2. As a direct argument (first argument after command)
17 | let filePath: String
18 | if let namedPath = arguments[McpConfig.filePathParamName]?.stringValue {
19 | filePath = namedPath
20 | } else if let firstArg = arguments.first?.value.stringValue {
21 | filePath = firstArg
22 | } else {
23 | return CallTool.Result(content: [.text(McpConfig.missingFilePathError)])
24 | }
25 |
26 | let entireFile = arguments[McpConfig.entireFileParamName]?.boolValue ?? false
27 | let startLine = arguments[McpConfig.startLineParamName]?.intValue
28 | let endLine = arguments[McpConfig.endLineParamName]?.intValue
29 |
30 | return XcfMcpCodeSnippetHandlers.handleCodeSnippet(
31 | filePath: filePath,
32 | entireFile: entireFile,
33 | startLine: startLine,
34 | endLine: endLine
35 | )
36 | }
37 |
38 | /// Handles a call to the analyzer tool
39 | static func handleAnalyzerToolCall(_ params: CallTool.Parameters) throws -> CallTool.Result {
40 | guard let arguments = params.arguments else {
41 | return CallTool.Result(content: [.text(McpConfig.missingFilePathError)])
42 | }
43 |
44 | // Try to get filePath from arguments in two ways:
45 | // 1. As a named parameter (filePath=...)
46 | // 2. As a direct argument (first argument after command)
47 | let filePath: String
48 | if let namedPath = arguments[McpConfig.filePathParamName]?.stringValue {
49 | filePath = namedPath
50 | } else if let firstArg = arguments.first?.value.stringValue {
51 | filePath = firstArg
52 | } else {
53 | return CallTool.Result(content: [.text(McpConfig.missingFilePathError)])
54 | }
55 |
56 | let entireFile = arguments[McpConfig.entireFileParamName]?.boolValue ?? false
57 | let startLine = arguments[McpConfig.startLineParamName]?.intValue
58 | let endLine = arguments[McpConfig.endLineParamName]?.intValue
59 |
60 | return XcfMcpCodeSnippetHandlers.handleAnalyzerCodeSnippet(
61 | filePath: filePath,
62 | entireFile: entireFile,
63 | startLine: startLine,
64 | endLine: endLine
65 | )
66 | }
67 | }
--------------------------------------------------------------------------------
/xcf/Mcp/Resources/XcfMcpResourceHandlers.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import MCP
3 |
4 | // MARK: - MCP Resource Handlers
5 |
6 | class XcfMcpResourceHandlers {
7 |
8 | /// Handles a resource request
9 | static func handleResourceRequest(_ params: ReadResource.Parameters) async throws -> ReadResource.Result {
10 | switch params.uri {
11 | case McpConfig.xcodeProjResourceURI:
12 | return try await handleXcodeProjectsResource()
13 |
14 | case McpConfig.fileContentsResourceURI:
15 | return try handleFileContentsResource(uri: params.uri)
16 |
17 | case McpConfig.buildResultsResourceURI:
18 | return try await handleBuildResultsResource()
19 |
20 | default:
21 | throw MCPError.invalidParams(String(format: McpConfig.unknownResourceUriError, params.uri))
22 | }
23 | }
24 |
25 | /// Handles a request for the Xcode projects resource
26 | static func handleXcodeProjectsResource() async throws -> ReadResource.Result {
27 | let projects = await XcfMcpActionHandler.getSortedXcodeProjects()
28 | let content = Resource.Content.text(
29 | projects.joined(separator: McpConfig.newLineSeparator),
30 | uri: McpConfig.xcodeProjResourceURI
31 | )
32 | return ReadResource.Result(contents: [content])
33 | }
34 |
35 | /// Handles a request for the build results resource
36 | static func handleBuildResultsResource() async throws -> ReadResource.Result {
37 | guard let currentProject = await XcfMcpActionHandler.getCurrentProject() else {
38 | throw MCPError.invalidParams(ErrorMessages.noProjectSelected)
39 | }
40 |
41 | let buildResults = XcfMcpServer.XcfScript.buildCurrentWorkspace(projectPath: currentProject, run: false)
42 | let content = Resource.Content.text(
43 | buildResults,
44 | uri: McpConfig.buildResultsResourceURI
45 | )
46 | return ReadResource.Result(contents: [content])
47 | }
48 |
49 | /// Handles a request for the file contents resource
50 | static func handleFileContentsResource(uri: String) throws -> ReadResource.Result {
51 | guard let filePath = XcfMcpHandlers.extractFilePathFromUri(uri) else {
52 | throw MCPError.invalidParams(McpConfig.missingFilePathParamError)
53 | }
54 |
55 | do {
56 | let (fileContents, warning) = try XcfFileManager.readFile(at: filePath)
57 | let content = Resource.Content.text(
58 | warning.isEmpty ? fileContents : warning + "\n\n" + fileContents,
59 | uri: "\(McpConfig.fileContentsResourceURI)/\(filePath)",
60 | mimeType: McpConfig.plainTextMimeType
61 | )
62 | return ReadResource.Result(contents: [content])
63 | } catch let error as NSError {
64 | throw MCPError.invalidParams(error.localizedDescription)
65 | }
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/xcf.xcodeproj/xcshareddata/xcschemes/xcf.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
9 |
10 |
16 |
22 |
23 |
24 |
25 |
26 |
32 |
33 |
43 |
45 |
51 |
52 |
53 |
54 |
60 |
62 |
68 |
69 |
70 |
71 |
73 |
74 |
77 |
78 |
79 |
--------------------------------------------------------------------------------
/xcf/Mcp/Script/XcfOsaScript.swift:
--------------------------------------------------------------------------------
1 | //
2 | // XcfOsascript.swift
3 | // xcf
4 | //
5 | // Created by Todd Bruss on 5/4/25.
6 | //
7 |
8 | import Foundation
9 |
10 | //MARK: Some Systems only approve over OSAScript - this is a workaround
11 | //TODO: Monitor and see if we can switch the approval to ScriptingBridge
12 | func grantAutomation() -> String {
13 | """
14 | tell application "Xcode"
15 | set xcDoc to first document
16 |
17 | tell xcDoc
18 | set buildResult to build
19 |
20 | repeat
21 | if completed of buildResult is true then
22 | exit repeat
23 | end if
24 | delay 0.5
25 | end repeat
26 |
27 | return "Xcode Automation permission has been granted"
28 | end tell
29 | end tell
30 | """
31 | }
32 |
33 | func executeWithOsascript(script: String, timeout: TimeInterval = 30.0) -> String {
34 | let process = Process()
35 | process.executableURL = URL(fileURLWithPath: Paths.osascriptPath)
36 |
37 | // Create a pipe to capture the output
38 | let outputPipe = Pipe()
39 | let errorPipe = Pipe() // Add error pipe to capture stderr
40 |
41 | process.standardOutput = outputPipe
42 | process.standardError = errorPipe
43 | process.arguments = ["-e", script]
44 |
45 | do {
46 | try process.run()
47 |
48 | // Create a timeout
49 | let waitSemaphore = DispatchSemaphore(value: 0)
50 | let timeoutTask = DispatchWorkItem {
51 | if process.isRunning {
52 | process.terminate()
53 | }
54 | }
55 |
56 | // Schedule timeout
57 | DispatchQueue.global().asyncAfter(deadline: .now() + timeout, execute: timeoutTask)
58 |
59 | // Wait in background
60 | DispatchQueue.global().async {
61 | process.waitUntilExit()
62 | timeoutTask.cancel() // Cancel timeout if process completes normally
63 | waitSemaphore.signal()
64 | }
65 |
66 | // Wait for completion or timeout
67 | let waitResult = waitSemaphore.wait(timeout: .now() + timeout + 1.0)
68 | if waitResult == .timedOut {
69 | return "Error: Process timed out after \(timeout) seconds"
70 | }
71 |
72 | // Check process exit status
73 | if process.terminationStatus != 0 {
74 | let errorData = errorPipe.fileHandleForReading.readDataToEndOfFile()
75 | if let errorString = String(data: errorData, encoding: .utf8), !errorString.isEmpty {
76 | return String(format: ErrorMessages.failedToExecuteOsascript, errorString)
77 | }
78 | }
79 |
80 | // Read the output data
81 | let data = outputPipe.fileHandleForReading.readDataToEndOfFile()
82 |
83 | // Convert the data to a string
84 | if let result = String(data: data, encoding: .utf8) {
85 | return result.trimmingCharacters(in: .whitespacesAndNewlines)
86 | } else {
87 | return ErrorMessages.failedToConvertOutput
88 | }
89 | } catch {
90 | return String(format: ErrorMessages.failedToExecuteOsascript, "\(error)")
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/xcf/Mcp/Prompts/XcfMcpPromptHandlers.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import MCP
3 |
4 | // MARK: - MCP Prompt Handlers
5 |
6 | class XcfMcpPromptHandlers {
7 |
8 | /// Handles a prompt request
9 | static func handlePromptRequest(_ params: GetPrompt.Parameters) throws -> GetPrompt.Result {
10 | switch params.name {
11 | case McpConfig.buildPromptName:
12 | return handleBuildPrompt(params)
13 |
14 | case McpConfig.runPromptName:
15 | return handleRunPrompt(params)
16 |
17 | case McpConfig.analyzeCodePromptName:
18 | return try handleAnalyzeCodePrompt(params)
19 |
20 | default:
21 | throw MCPError.invalidParams(String(format: McpConfig.unknownPromptNameError, params.name))
22 | }
23 | }
24 |
25 | /// Handles a request for the build project prompt
26 | static func handleBuildPrompt(_ params: GetPrompt.Parameters) -> GetPrompt.Result {
27 | // Get project path from arguments or use placeholder
28 | let projectPath = params.arguments?[McpConfig.projectPathArgName]?.stringValue ?? McpConfig.projectPathPlaceholder
29 |
30 | let messages: [Prompt.Message] = [
31 | .user(.text(text: String(format: McpConfig.buildProjectTemplate, projectPath)))
32 | ]
33 |
34 | return GetPrompt.Result(description: McpConfig.buildProjectResultDesc, messages: messages)
35 | }
36 |
37 | /// Handles a request for the run project prompt
38 | static func handleRunPrompt(_ params: GetPrompt.Parameters) -> GetPrompt.Result {
39 | // Get project path from arguments or use placeholder
40 | let projectPath = params.arguments?[McpConfig.projectPathArgName]?.stringValue ?? McpConfig.projectPathPlaceholder
41 |
42 | let messages: [Prompt.Message] = [
43 | .user(.text(text: String(format: McpConfig.runProjectTemplate, projectPath)))
44 | ]
45 |
46 | return GetPrompt.Result(description: McpConfig.runProjectResultDesc, messages: messages)
47 | }
48 |
49 | /// Handles a request for the analyze code prompt
50 | static func handleAnalyzeCodePrompt(_ params: GetPrompt.Parameters) throws -> GetPrompt.Result {
51 | // Get file path from arguments or use placeholder
52 | let filePath = params.arguments?[McpConfig.filePathArgName]?.stringValue ?? McpConfig.filePathPlaceholder
53 | let includeSnippet = params.arguments?[McpConfig.includeSnippetArgName]?.boolValue ?? false
54 |
55 | // Create the base message
56 | let baseMessage = Prompt.Message.user(
57 | .text(text: String(format: McpConfig.analyzeCodeTemplate, filePath))
58 | )
59 |
60 | var messages: [Prompt.Message] = [baseMessage]
61 |
62 | // Add code snippet if requested
63 | if includeSnippet {
64 | if let snippetMessage = try? XcfMcpCodeSnippetHandlers.createCodeSnippetMessage(filePath: filePath) {
65 | messages.append(snippetMessage)
66 | }
67 | // If snippet creation fails, we still return the base message
68 | }
69 |
70 | return GetPrompt.Result(description: McpConfig.analyzeCodeResultDesc, messages: messages)
71 | }
72 | }
--------------------------------------------------------------------------------
/xcf/Mcp/Script/XcfCaptureSnippet.swift:
--------------------------------------------------------------------------------
1 | //
2 | // XcfCaptureSnippets.swift
3 | // xcf
4 | //
5 | // Created by Todd Bruss on 5/6/25.
6 | //
7 |
8 | import Foundation
9 |
10 | /// A utility struct that provides functionality for capturing code snippets from files.
11 | /// This struct offers methods to extract specific line ranges from source code files
12 | /// and determine the programming language based on file extensions.
13 | struct CaptureSnippet {
14 | /// Extracts a code snippet from a file at the specified line range.
15 | ///
16 | /// This function attempts to locate the file using FuzzyLogicService,
17 | /// then extracts either the specified line range or the entire file content.
18 | /// It also determines the programming language based on the file extension.
19 | ///
20 | /// - Parameters:
21 | /// - filePath: The path to the source code file
22 | /// - startLine: The first line to include in the snippet (1-indexed)
23 | /// - endLine: The last line to include in the snippet (1-indexed)
24 | /// - entireFile: If true, returns the entire file content regardless of line numbers
25 | /// - Returns: A tuple containing (snippet text with any warnings, detected language)
26 | static func getCodeSnippet(filePath: String, startLine: Int, endLine: Int, entireFile: Bool = false) -> (String, String) {
27 | var snippet = ""
28 | let language = FileFinder.determineLanguage(from: filePath)
29 |
30 | // Try to find the file using FuzzyLogicService
31 | let (resolvedPath, warning) = FuzzyLogicService.resolveFilePath(filePath)
32 |
33 | // If we got a warning about duplicate files, prepend it to the snippet
34 | var warningMessage = ""
35 | if !warning.isEmpty {
36 | warningMessage = warning + Format.newLine + Format.newLine
37 | }
38 |
39 | do {
40 | let fileContents = try String(contentsOfFile: resolvedPath, encoding: .utf8)
41 |
42 | if entireFile {
43 | // If entireFile is true, return the whole file content regardless of line numbers
44 | return (warningMessage + fileContents, language)
45 | }
46 |
47 | let lines = fileContents.components(separatedBy: Format.newlinesCharSet())
48 |
49 | // Validate line numbers
50 | if startLine < 1 || endLine > lines.count || endLine < startLine {
51 | return (warningMessage + ErrorMessages.invalidLineNumbers, language)
52 | }
53 |
54 | // Extract the snippet
55 | for i in startLine...min(endLine, lines.count) {
56 | if i > startLine {
57 | snippet += Format.newLine
58 | }
59 | if i-1 < lines.count {
60 | snippet += lines[i-1]
61 | }
62 | }
63 | } catch {
64 | return (String(format: ErrorMessages.errorReadingFile, error.localizedDescription), language)
65 | }
66 |
67 | return (warningMessage + snippet, language)
68 | }
69 |
70 | /// Determines the programming language based on the file extension
71 | ///
72 | /// This function examines the file extension and maps it to a language
73 | /// identifier string that can be used for syntax highlighting or other
74 | /// language-specific processing.
75 | ///
76 | /// - Parameter filePath: The path to the file
77 | /// - Returns: A string identifier for the detected programming language
78 | public static func determineLanguage(from filePath: String) -> String {
79 | return FileFinder.determineLanguage(from: filePath)
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/xcf/Mcp/Resources/XcfMcpResources.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import MCP
3 |
4 | // MARK: - MCP Resources
5 |
6 | class XcfMcpResources {
7 |
8 | static func getAllResources() -> [Resource] {
9 | return [
10 | createXcodeProjResource(),
11 | createBuildResultsResource(),
12 | createCodeSnippetResource(),
13 | createCodeAnalysisResource(),
14 | createXcodeDocumentResource(),
15 | createHelpResource(),
16 | createPermissionResource(),
17 | createProjectManagementResource(),
18 | createEnvironmentResource(),
19 | createDirectoryResource()
20 | ]
21 | }
22 |
23 | // MARK: - Resource Creation Functions
24 |
25 | private static func createXcodeProjResource() -> Resource {
26 | Resource(
27 | name: McpConfig.xcodeProjResourceName,
28 | uri: McpConfig.xcodeProjResourceURI,
29 | description: McpConfig.xcodeProjResourceDesc
30 | )
31 | }
32 |
33 | private static func createBuildResultsResource() -> Resource {
34 | Resource(
35 | name: McpConfig.buildResultsResourceName,
36 | uri: McpConfig.buildResultsResourceURI,
37 | description: McpConfig.buildResultsResourceDesc
38 | )
39 | }
40 |
41 | private static func createCodeSnippetResource() -> Resource {
42 | Resource(
43 | name: "codeSnippet",
44 | uri: "\(AppConstants.appName)://resources/codeSnippet",
45 | description: "Extracted code snippets from files"
46 | )
47 | }
48 |
49 | private static func createCodeAnalysisResource() -> Resource {
50 | Resource(
51 | name: "codeAnalysis",
52 | uri: "\(AppConstants.appName)://resources/codeAnalysis",
53 | description: "Results of code analysis"
54 | )
55 | }
56 |
57 | private static func createXcodeDocumentResource() -> Resource {
58 | Resource(
59 | name: "xcodeDocument",
60 | uri: "\(AppConstants.appName)://resources/xcodeDocument",
61 | description: "Content and status of Xcode documents"
62 | )
63 | }
64 |
65 | private static func createHelpResource() -> Resource {
66 | Resource(
67 | name: McpConfig.helpResourceName,
68 | uri: McpConfig.helpResourceURI,
69 | description: McpConfig.helpResourceDesc
70 | )
71 | }
72 |
73 | private static func createPermissionResource() -> Resource {
74 | Resource(
75 | name: McpConfig.permissionResourceName,
76 | uri: McpConfig.permissionResourceURI,
77 | description: McpConfig.permissionResourceDesc
78 | )
79 | }
80 |
81 | private static func createProjectManagementResource() -> Resource {
82 | Resource(
83 | name: McpConfig.projectManagementResourceName,
84 | uri: McpConfig.projectManagementResourceURI,
85 | description: McpConfig.projectManagementResourceDesc
86 | )
87 | }
88 |
89 | private static func createEnvironmentResource() -> Resource {
90 | Resource(
91 | name: McpConfig.environmentResourceName,
92 | uri: McpConfig.environmentResourceURI,
93 | description: McpConfig.environmentResourceDesc
94 | )
95 | }
96 |
97 | private static func createDirectoryResource() -> Resource {
98 | Resource(
99 | name: McpConfig.directoryResourceName,
100 | uri: McpConfig.directoryResourceURI,
101 | description: McpConfig.directoryResourceDesc
102 | )
103 | }
104 | }
--------------------------------------------------------------------------------
/xcf/Mcp/Tools/XcfMcpActionHandlers.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import MCP
3 |
4 | // MARK: - Action-Specific Tool Handlers
5 |
6 | class XcfMcpActionHandlers {
7 |
8 | /// Handles a call to the show help tool
9 | static func handleShowHelpToolCall(_ params: CallTool.Parameters) async -> CallTool.Result {
10 | return CallTool.Result(content: [.text(await XcfMcpActionHandler.getHelpText())])
11 | }
12 |
13 | /// Handles a call to the grant permission tool
14 | static func handleGrantPermissionToolCall(_ params: CallTool.Parameters) async -> CallTool.Result {
15 | return CallTool.Result(content: [.text(await XcfMcpActionHandler.grantPermission())])
16 | }
17 |
18 | /// Handles a call to the run project tool
19 | static func handleRunProjectToolCall(_ params: CallTool.Parameters) async -> CallTool.Result {
20 | return CallTool.Result(content: [.text(await XcfMcpActionHandler.runProject())])
21 | }
22 |
23 | /// Handles a call to the build project tool
24 | static func handleBuildProjectToolCall(_ params: CallTool.Parameters) async -> CallTool.Result {
25 | return CallTool.Result(content: [.text(await XcfMcpActionHandler.buildProject())])
26 | }
27 |
28 | /// Handles a call to the show current project tool
29 | static func handleShowCurrentProjectToolCall(_ params: CallTool.Parameters) async -> CallTool.Result {
30 | return CallTool.Result(content: [.text(await XcfMcpActionHandler.showCurrentProject())])
31 | }
32 |
33 | /// Handles a call to the show environment variables tool
34 | static func handleShowEnvToolCall(_ params: CallTool.Parameters) async -> CallTool.Result {
35 | return CallTool.Result(content: [.text(await XcfMcpActionHandler.showEnvironmentVariables())])
36 | }
37 |
38 | /// Handles a call to the show current folder tool
39 | static func handleShowFolderToolCall(_ params: CallTool.Parameters) async -> CallTool.Result {
40 | return CallTool.Result(content: [.text(await XcfMcpActionHandler.showCurrentFolder())])
41 | }
42 |
43 | /// Handles a call to the list projects tool
44 | static func handleListProjectsToolCall(_ params: CallTool.Parameters) async -> CallTool.Result {
45 | return CallTool.Result(content: [.text(await XcfMcpActionHandler.listProjects())])
46 | }
47 |
48 | /// Handles a call to the select project tool
49 | static func handleSelectProjectToolCall(_ params: CallTool.Parameters) async -> CallTool.Result {
50 | if let projectNumber = params.arguments?[McpConfig.projectNumberParamName]?.intValue {
51 | let action = "open \(projectNumber)"
52 | return CallTool.Result(content: [.text(await XcfMcpActionHandler.selectProject(action: action))])
53 | } else {
54 | return CallTool.Result(content: [.text(ErrorMessages.invalidProjectSelection)])
55 | }
56 | }
57 |
58 | /// Handles a call to the analyze Swift code tool
59 | static func handleAnalyzeSwiftCodeToolCall(_ params: CallTool.Parameters) async -> CallTool.Result {
60 | guard let filePath = params.arguments?[McpConfig.filePathParamName]?.stringValue else {
61 | return CallTool.Result(content: [.text(McpConfig.missingFilePathError)])
62 | }
63 |
64 | var analyzeCommand = "analyze \(filePath)"
65 |
66 | // Add optional parameters if provided
67 | if let startLine = params.arguments?[McpConfig.startLineParamName]?.intValue,
68 | let endLine = params.arguments?[McpConfig.endLineParamName]?.intValue {
69 | analyzeCommand += " \(startLine) \(endLine)"
70 | }
71 |
72 | // Add check groups if provided
73 | if let checkGroups = params.arguments?[McpConfig.checkGroupsParamName]?.arrayValue,
74 | !checkGroups.isEmpty {
75 | for group in checkGroups {
76 | if let groupName = group.stringValue {
77 | analyzeCommand += " \(groupName)"
78 | }
79 | }
80 | }
81 |
82 | return CallTool.Result(content: [.text(await XcfMcpActionHandler.handleAnalyzeAction(action: analyzeCommand))])
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/xcf/Mcp/ServerConfig/XcfMcpHandlers.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import MCP
3 | import MultiLineDiff
4 |
5 | typealias StringIndex = String.Index
6 |
7 | // MARK: - MCP Handlers
8 |
9 | class XcfMcpHandlers {
10 |
11 | // MARK: - Handler Registration
12 |
13 | /// Registers all method handlers with the server
14 | static func registerAllHandlers(server: Server) async {
15 | await registerToolsHandlers(server: server)
16 | await registerResourcesHandlers(server: server)
17 | await registerPromptsHandlers(server: server)
18 | }
19 |
20 | /// Registers handlers for tool-related methods
21 | private static func registerToolsHandlers(server: Server) async {
22 | // Handle tool listing
23 | await server.withMethodHandler(ListTools.self) { _ in
24 | ListTools.Result(tools: XcfMcpTools.getAllTools())
25 | }
26 |
27 | // Handle tool calls - delegate to specialized handler
28 | await server.withMethodHandler(CallTool.self) { params in
29 | try await XcfMcpToolCallHandlers.handleToolCall(params)
30 | }
31 | }
32 |
33 | /// Registers handlers for resource-related methods
34 | private static func registerResourcesHandlers(server: Server) async {
35 | // Handle resource listing
36 | await server.withMethodHandler(ListResources.self) { _ in
37 | ListResources.Result(resources: XcfMcpResources.getAllResources())
38 | }
39 |
40 | // Handle resource retrieval - delegate to specialized handler
41 | await server.withMethodHandler(ReadResource.self) { params in
42 | try await XcfMcpResourceHandlers.handleResourceRequest(params)
43 | }
44 | }
45 |
46 | /// Registers handlers for prompt-related methods
47 | private static func registerPromptsHandlers(server: Server) async {
48 | // Handle prompt listing
49 | await server.withMethodHandler(ListPrompts.self) { _ in
50 | ListPrompts.Result(prompts: XcfMcpPrompts.getAllPrompts())
51 | }
52 |
53 | // Handle prompt retrieval - delegate to specialized handler
54 | await server.withMethodHandler(GetPrompt.self) { params in
55 | try XcfMcpPromptHandlers.handlePromptRequest(params)
56 | }
57 | }
58 |
59 | // MARK: - Utility Methods
60 |
61 | /// Extracts a file path from a URI query string
62 | static func extractFilePathFromUri(_ uri: String) -> String? {
63 | let uriComponents = uri.components(separatedBy: "?")
64 | guard uriComponents.count > 1,
65 | let queryItems = URLComponents(string: "?" + uriComponents[1])?.queryItems,
66 | let filePathItem = queryItems.first(where: { $0.name == McpConfig.filePathQueryParam }),
67 | let filePath = filePathItem.value,
68 | !filePath.isEmpty else {
69 | return nil
70 | }
71 | return filePath
72 | }
73 |
74 | /// Generate Tool List
75 | static func getToolsList() -> String {
76 | var result = McpConfig.availableTools
77 |
78 | for tool in XcfMcpTools.getAllTools() {
79 | result += String(format: McpConfig.toolListFormat, tool.name, tool.description)
80 | }
81 |
82 | return result
83 | }
84 |
85 | static func getResourcesList() -> String {
86 | var result = McpConfig.availableResources
87 |
88 | for resource in XcfMcpResources.getAllResources() {
89 | result += String(format: McpConfig.resourceListFormat,
90 | resource.name,
91 | resource.uri,
92 | resource.description ?? "")
93 | }
94 |
95 | return result
96 | }
97 |
98 | static func getPromptsList() -> String {
99 | var result = McpConfig.availablePrompts
100 |
101 | for prompt in XcfMcpPrompts.getAllPrompts() {
102 | result += String(format: McpConfig.promptListFormat,
103 | prompt.name,
104 | prompt.description ?? "")
105 | }
106 |
107 | return result
108 | }
109 | }
110 |
--------------------------------------------------------------------------------
/xcf/Mcp/Tools/XcfMcpCodeSnippetHandlers.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import MCP
3 |
4 | // MARK: - Code Snippet Handlers
5 |
6 | class XcfMcpCodeSnippetHandlers {
7 |
8 | // MARK: - Code Snippet Handling
9 |
10 | /// Handles code snippet extraction
11 | static func handleCodeSnippet(filePath: String, entireFile: Bool, startLine: Int? = nil, endLine: Int? = nil) -> CallTool.Result {
12 | // Resolve the file path using multiple strategies
13 | let (resolvedPath, warning) = FuzzyLogicService.resolveFilePath(filePath)
14 |
15 | // Validate file path - use the resolved path
16 | guard FileManager.default.fileExists(atPath: resolvedPath) else {
17 | return CallTool.Result(content: [.text(String(format: ErrorMessages.errorReadingFile, "File not found. Tried searching for \(filePath) in multiple locations."))])
18 | }
19 |
20 | // Add warning message if there is one
21 | var warningText = ""
22 | if !warning.isEmpty {
23 | warningText = warning + Format.newLine + Format.newLine
24 | }
25 |
26 | if entireFile {
27 | return handleEntireFileSnippet(filePath: resolvedPath, warning: warningText)
28 | } else if let startLine = startLine, let endLine = endLine {
29 | return handleLineRangeSnippet(filePath: resolvedPath, startLine: startLine, endLine: endLine, warning: warningText)
30 | } else {
31 | return CallTool.Result(content: [.text(McpConfig.missingLineParamsError)])
32 | }
33 | }
34 |
35 | /// Handles extracting an entire file as a code snippet
36 | static func handleEntireFileSnippet(filePath: String, warning: String = "") -> CallTool.Result {
37 | do {
38 | let fileContents = try String(contentsOfFile: filePath, encoding: .utf8)
39 | let language = FileFinder.determineLanguage(from: filePath)
40 | return CallTool.Result(content: [.text(warning + String(format: McpConfig.codeBlockFormat, language, fileContents))])
41 | } catch {
42 | return CallTool.Result(content: [.text(warning + String(format: ErrorMessages.errorReadingFile, error.localizedDescription))])
43 | }
44 | }
45 |
46 | /// Handles extracting a range of lines as a code snippet
47 | static func handleLineRangeSnippet(filePath: String, startLine: Int, endLine: Int, warning: String = "") -> CallTool.Result {
48 | let (snippet, language) = CaptureSnippet.getCodeSnippet(
49 | filePath: filePath,
50 | startLine: startLine,
51 | endLine: endLine
52 | )
53 |
54 | return CallTool.Result(content: [.text(warning + String(format: McpConfig.codeBlockFormat, language, snippet))])
55 | }
56 |
57 | /// Handles extracting a code snippet for the analyzer tool
58 | static func handleAnalyzerCodeSnippet(filePath: String, entireFile: Bool, startLine: Int? = nil, endLine: Int? = nil) -> CallTool.Result {
59 | // Use SwiftAnalyzer to analyze the code
60 | let (analysisResult, language) = SwiftAnalyzer.analyzeCode(
61 | filePath: filePath,
62 | entireFile: entireFile,
63 | startLine: startLine,
64 | endLine: endLine
65 | )
66 |
67 | // Format the result based on the language
68 | if language == "markdown" {
69 | // Markdown output doesn't need code block formatting
70 | return CallTool.Result(content: [.text(analysisResult)])
71 | } else {
72 | // Format as code block
73 | return CallTool.Result(content: [.text(String(format: McpConfig.codeBlockFormat, language, analysisResult))])
74 | }
75 | }
76 |
77 | /// Creates a message containing a code snippet
78 | static func createCodeSnippetMessage(filePath: String) throws -> Prompt.Message {
79 | let fileContents = try String(contentsOfFile: filePath, encoding: .utf8)
80 | let language = FileFinder.determineLanguage(from: filePath)
81 | let resourceUri = "\(McpConfig.fileContentsResourceURI)/\(filePath)"
82 |
83 | return Prompt.Message.user(
84 | .resource(
85 | uri: resourceUri,
86 | mimeType: McpConfig.plainTextMimeType,
87 | text: String(format: McpConfig.codeBlockFormat, language, fileContents),
88 | blob: nil
89 | )
90 | )
91 | }
92 | }
--------------------------------------------------------------------------------
/xcf/Mcp/Tools/XcfMcpToolCallHandlers.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import MCP
3 | import MultiLineDiff
4 |
5 | //typealias StringIndex = String.Index
6 |
7 | // MARK: - MCP Tool Call Handlers
8 |
9 | class XcfMcpToolCallHandlers {
10 |
11 | // MARK: - Main Tool Call Handler
12 |
13 | /// Handles a tool call request
14 | static func handleToolCall(_ params: CallTool.Parameters) async throws -> CallTool.Result {
15 | switch params.name {
16 | case McpConfig.listToolsName:
17 | return CallTool.Result(content: [.text(XcfMcpHandlers.getToolsList())])
18 |
19 | case McpConfig.xcfToolName:
20 | return try await handleXcfToolCall(params)
21 |
22 | case McpConfig.snippetToolName:
23 | return try XcfMcpCodeAnalysisHandlers.handleSnippetToolCall(params)
24 |
25 | case McpConfig.analyzerToolName:
26 | return try XcfMcpCodeAnalysisHandlers.handleAnalyzerToolCall(params)
27 |
28 | case McpConfig.quickHelpToolName:
29 | return XcfMcpHelpHandlers.handleQuickHelpToolCall(params)
30 |
31 | case McpConfig.helpToolName:
32 | return XcfMcpHelpHandlers.handleHelpToolCall(params)
33 |
34 | case McpConfig.useXcfToolName:
35 | return CallTool.Result(content: [.text(SuccessMessages.xcfActive)])
36 |
37 | case McpConfig.readFileToolName:
38 | return try XcfMcpFileSystemHandlers.handleReadFileToolCall(params)
39 |
40 | case McpConfig.cdDirToolName:
41 | return try XcfMcpFileSystemHandlers.handleCdDirToolCall(params)
42 |
43 | case McpConfig.openDocToolName:
44 | return try XcfMcpXcodeDocumentHandlers.handleOpenDocToolCall(params)
45 |
46 | case McpConfig.createDocToolName:
47 | return try XcfMcpXcodeDocumentHandlers.handleCreateDocToolCall(params)
48 |
49 | case McpConfig.readDocToolName:
50 | return try XcfMcpXcodeDocumentHandlers.handleReadDocToolCall(params)
51 |
52 | case McpConfig.saveDocToolName:
53 | return try XcfMcpXcodeDocumentHandlers.handleSaveDocToolCall(params)
54 |
55 | case McpConfig.readDirToolName:
56 | return try XcfMcpFileSystemHandlers.handleReadDirToolCall(params)
57 |
58 | case McpConfig.closeDocToolName:
59 | return try XcfMcpXcodeDocumentHandlers.handleCloseDocToolCall(params)
60 |
61 | case McpConfig.tools:
62 | return XcfMcpHelpHandlers.handleToolsReferenceToolCall(params)
63 |
64 | case McpConfig.xcfHelp:
65 | return XcfMcpHelpHandlers.handleQuickHelpToolCall(params)
66 |
67 | case McpConfig.createDiff:
68 | return try XcfMcpDiffHandlers.handleCreateDiffToolCall(params)
69 |
70 | case McpConfig.applyDiff:
71 | return try XcfMcpDiffHandlers.handleApplyDiffToolCall(params)
72 |
73 | case McpConfig.getAsciiDiff:
74 | return try XcfMcpDiffHandlers.handleGetAsciiDiffToolCall(params)
75 |
76 | case McpConfig.createDiffFromDocToolName:
77 | return try XcfMcpDiffHandlers.handleCreateDiffFromDocToolCall(params)
78 |
79 | case McpConfig.applyUndoDiffToDocToolName:
80 | return try XcfMcpDiffHandlers.handleApplyUndoDiffToDocToolCall(params)
81 |
82 | case McpConfig.applyDiffToDocToolName:
83 | return try XcfMcpDiffHandlers.handleApplyDiffToDocToolCall(params)
84 |
85 | // Action-specific tool handlers - delegate to specialized handler
86 | case McpConfig.showHelpToolName:
87 | return await XcfMcpActionHandlers.handleShowHelpToolCall(params)
88 |
89 | case McpConfig.grantPermissionToolName:
90 | return await XcfMcpActionHandlers.handleGrantPermissionToolCall(params)
91 |
92 | case McpConfig.runProjectToolName:
93 | return await XcfMcpActionHandlers.handleRunProjectToolCall(params)
94 |
95 | case McpConfig.buildProjectToolName:
96 | return await XcfMcpActionHandlers.handleBuildProjectToolCall(params)
97 |
98 | case McpConfig.showCurrentProjectToolName:
99 | return await XcfMcpActionHandlers.handleShowCurrentProjectToolCall(params)
100 |
101 | case McpConfig.showEnvToolName:
102 | return await XcfMcpActionHandlers.handleShowEnvToolCall(params)
103 |
104 | case McpConfig.showFolderToolName:
105 | return await XcfMcpActionHandlers.handleShowFolderToolCall(params)
106 |
107 | case McpConfig.listProjectsToolName:
108 | return await XcfMcpActionHandlers.handleListProjectsToolCall(params)
109 |
110 | case McpConfig.selectProjectToolName:
111 | return await XcfMcpActionHandlers.handleSelectProjectToolCall(params)
112 |
113 | case McpConfig.analyzeSwiftCodeToolName:
114 | return await XcfMcpActionHandlers.handleAnalyzeSwiftCodeToolCall(params)
115 |
116 | default:
117 | throw MCPError.invalidParams(String(format: ErrorMessages.unknownTool, params.name))
118 | }
119 | }
120 |
121 | // MARK: - Core Tool Handler
122 |
123 | /// Handles a call to the xcf tool
124 | static func handleXcfToolCall(_ params: CallTool.Parameters) async throws -> CallTool.Result {
125 | if let action = params.arguments?[McpConfig.actionParamName]?.stringValue {
126 | return CallTool.Result(content: [.text(await XcfMcpActionHandler.handleAction(action: action))])
127 | } else {
128 | // If no action specified, return the help information
129 | return CallTool.Result(content: [.text(await XcfMcpActionHandler.handleAction(action: Actions.help))])
130 | }
131 | }
132 | }
133 |
--------------------------------------------------------------------------------
/xcf/ORGANIZATION_SUMMARY.md:
--------------------------------------------------------------------------------
1 | # XCF MCP Organization Summary
2 |
3 | ## New Organization (No Extensions!)
4 |
5 | The codebase has been reorganized into focused, single-responsibility classes without using Swift extensions.
6 |
7 | ### File Structure
8 |
9 | 1. **`XcfMcpServer.swift`** - Core server setup and initialization
10 | 2. **`XcfMcpTools.swift`** - All tool definitions (30+ tools)
11 | 3. **`XcfMcpResources.swift`** - All resource definitions (10 resources)
12 | 4. **`XcfMcpPrompts.swift`** - All prompt definitions (20+ prompts)
13 | 5. **`XcfMcpHandlers.swift`** - Main handler registration and utility methods
14 | 6. **`XcfMcpToolCallHandlers.swift`** - Main tool call router and core tool handlers
15 | 7. **`XcfMcpResourceHandlers.swift`** - Resource request handlers (Xcode projects, build results, file contents)
16 | 8. **`XcfMcpPromptHandlers.swift`** - Prompt request handlers (build, run, analyze code)
17 | 9. **`XcfMcpHelpHandlers.swift`** - Help and documentation tool handlers
18 | 10. **`XcfMcpFileSystemHandlers.swift`** - File system operation handlers
19 | 11. **`XcfMcpXcodeDocumentHandlers.swift`** - Xcode document operation handlers
20 | 12. **`XcfMcpCodeAnalysisHandlers.swift`** - Code snippet and analyzer handlers
21 | 13. **`XcfMcpCodeSnippetHandlers.swift`** - Code snippet extraction and analysis
22 | 14. **`XcfMcpDiffHandlers.swift`** - Diff creation and application handlers
23 | 15. **`XcfMcpActionHandlers.swift`** - Action-specific tool handlers (project, environment, etc.)
24 | 16. **`XcfMcpDefinitions.swift`** - Simple coordinator that delegates to specialized classes
25 | 17. **`XcfMcpCollections.swift`** - Legacy compatibility layer (can be removed later)
26 | 18. **`XcfMcpConfig.swift`** - MCP-specific configuration constants
27 | 19. **`XcfMcpStructStrings.swift`** - String constants and message structs
28 |
29 | ### Benefits of This Organization
30 |
31 | - **No Extensions**: Uses plain classes with static methods
32 | - **Single Responsibility**: Each file has one clear purpose
33 | - **Easy to Find**: Tools, resources, prompts, and handlers are in separate files by category
34 | - **Maintainable**: Adding new functionality is straightforward
35 | - **Clean Dependencies**: Clear hierarchy and delegation patterns
36 | - **Focused Files**: Each handler file focuses on one type of functionality
37 | - **Separated Constants**: String constants are now in their own dedicated file
38 | - **Complete Separation**: Resources and prompts have their own dedicated handler files
39 |
40 | ### Architecture Flow
41 |
42 | ```
43 | XcfMcpServer
44 | └── XcfMcpHandlers (registration + utilities)
45 | ├── XcfMcpToolCallHandlers (main tool router)
46 | │ ├── XcfMcpHelpHandlers (help & docs)
47 | │ ├── XcfMcpFileSystemHandlers (file ops)
48 | │ ├── XcfMcpXcodeDocumentHandlers (xcode docs)
49 | │ ├── XcfMcpCodeAnalysisHandlers (code analysis)
50 | │ │ └── XcfMcpCodeSnippetHandlers (snippet extraction)
51 | │ ├── XcfMcpDiffHandlers (diff ops)
52 | │ └── XcfMcpActionHandlers (project/env actions)
53 | ├── XcfMcpResourceHandlers (resource handling)
54 | ├── XcfMcpPromptHandlers (prompt handling)
55 | ├── XcfMcpTools (tool definitions)
56 | ├── XcfMcpResources (resource definitions)
57 | ├── XcfMcpPrompts (prompt definitions)
58 | ├── XcfMcpConfig (mcp config constants)
59 | └── XcfMcpStructStrings (string constants)
60 | ```
61 |
62 | ### How to Add New Items
63 |
64 | - **New Tool**: Add to `XcfMcpTools.swift`
65 | - **New Resource**: Add to `XcfMcpResources.swift`
66 | - **New Prompt**: Add to `XcfMcpPrompts.swift`
67 | - **New Help Handler**: Add to `XcfMcpHelpHandlers.swift`
68 | - **New File System Handler**: Add to `XcfMcpFileSystemHandlers.swift`
69 | - **New Xcode Document Handler**: Add to `XcfMcpXcodeDocumentHandlers.swift`
70 | - **New Code Analysis Handler**: Add to `XcfMcpCodeAnalysisHandlers.swift`
71 | - **New Code Snippet Handler**: Add to `XcfMcpCodeSnippetHandlers.swift`
72 | - **New Diff Handler**: Add to `XcfMcpDiffHandlers.swift`
73 | - **New Action Handler**: Add to `XcfMcpActionHandlers.swift`
74 | - **New Resource Handler**: Add to `XcfMcpResourceHandlers.swift`
75 | - **New Prompt Handler**: Add to `XcfMcpPromptHandlers.swift`
76 | - **New MCP Config**: Add to `XcfMcpConfig.swift`
77 | - **New String Constant**: Add to `XcfMcpStructStrings.swift`
78 |
79 | ### Handler Categories
80 |
81 | The handlers are now organized into logical categories:
82 |
83 | - **`XcfMcpHelpHandlers.swift`**: Help, documentation, and reference tools
84 | - **`XcfMcpFileSystemHandlers.swift`**: File reading, directory operations
85 | - **`XcfMcpXcodeDocumentHandlers.swift`**: Xcode document management (open, create, read, save, close)
86 | - **`XcfMcpCodeAnalysisHandlers.swift`**: Code snippet and analyzer tool coordination
87 | - **`XcfMcpCodeSnippetHandlers.swift`**: Code extraction and analysis logic
88 | - **`XcfMcpDiffHandlers.swift`**: Diff creation and application
89 | - **`XcfMcpActionHandlers.swift`**: Action-specific tools (project management, environment, build, run)
90 | - **`XcfMcpResourceHandlers.swift`**: Resource operations (Xcode projects, build results, file contents)
91 | - **`XcfMcpPromptHandlers.swift`**: Prompt processing (build, run, analyze code prompts)
92 |
93 | ### Configuration and Constants
94 |
95 | The configuration is now properly separated:
96 |
97 | - **`XcfMcpConfig.swift`**: MCP-specific configuration (tools, resources, prompts, schema definitions)
98 | - **`XcfMcpStructStrings.swift`**: String constants (error messages, success messages, app constants, actions, paths, format, Xcode constants)
99 |
100 | ### Migration Notes
101 |
102 | The old extension-based files have been removed and replaced with this focused structure:
103 | - ~~`XcfMcpToolCall.swift`~~ → split into multiple specialized handler files
104 | - ~~`XcfMcpRegistration.swift`~~ → merged into `XcfMcpHandlers.swift`
105 | - ~~`XcfMcpToolParams.swift`~~ → split into `XcfMcpTools.swift`
106 |
107 | Individual tool handlers are now distributed across focused files by functionality, and constants are properly separated by purpose. The codebase now has **19 perfectly organized files** with complete separation of concerns, making it much easier to navigate and maintain!
--------------------------------------------------------------------------------
/xcf/Mcp/Tools/XcfMcpDiffHandlers.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import MCP
3 | import MultiLineDiff
4 |
5 | // MARK: - Diff Tool Handlers
6 |
7 | class XcfMcpDiffHandlers {
8 |
9 | /// Handles a call to create a diff for a document
10 | static func handleCreateDiffToolCall(_ params: CallTool.Parameters) throws -> CallTool.Result {
11 | guard let arguments = params.arguments else {
12 | throw MCPError.invalidParams("Missing Params, fix later")
13 | }
14 |
15 | guard let destString = arguments["destString"]?.stringValue else {
16 | return CallTool.Result(content: [.text("Missing destinationString")])
17 | }
18 |
19 | guard let sourceString = arguments["sourceString"]?.stringValue else {
20 | return CallTool.Result(content: [.text("Missing sourceString")])
21 | }
22 |
23 | do {
24 | let diffHash = try createDiffFromString(original: sourceString, modified: destString)
25 | return CallTool.Result(content: [.text(diffHash)])
26 |
27 | } catch {
28 | throw MCPError.invalidParams(String(format: ErrorMessages.errorCreatingDiff, error.localizedDescription))
29 | }
30 | }
31 |
32 | /// Handles a call to the apply_diff tool
33 | static func handleGetAsciiDiffToolCall(_ params: CallTool.Parameters) throws -> CallTool.Result {
34 | guard let arguments = params.arguments else {
35 | return CallTool.Result(content: [.text("I dunno, not done yet.")])
36 | }
37 |
38 | guard let diffHash = arguments["diffHash"]?.stringValue else {
39 | return CallTool.Result(content: [.text("Missing diff hash")])
40 | }
41 |
42 | do {
43 | var diff = try getAsciiDiffFromHash(diffHash: diffHash)
44 | if diff.isEmpty {
45 | diff = "Having Issues, \(diffHash)"
46 | }
47 | return CallTool.Result(content: [.text(diff )])
48 |
49 | } catch {
50 | throw MCPError.invalidParams(String(format: ErrorMessages.errorCreatingDiff, error.localizedDescription))
51 | }
52 | }
53 |
54 | /// Handles a call to the apply_diff tool
55 | static func handleApplyDiffToolCall(_ params: CallTool.Parameters) throws -> CallTool.Result {
56 | guard let arguments = params.arguments else {
57 | return CallTool.Result(content: [.text("I dunno, not done yet.")])
58 | }
59 |
60 | guard let diffHash = arguments["diffHash"]?.stringValue else {
61 | return CallTool.Result(content: [.text("Missing diff hash")])
62 | }
63 |
64 | guard let sourceString = arguments["sourceString"]?.stringValue else {
65 | return CallTool.Result(content: [.text("Missing sourceString")])
66 | }
67 |
68 | do {
69 | var diff = try applyDiffFromString(original: sourceString, diffHash: diffHash)
70 | if diff.isEmpty {
71 | diff = "Having Issues, \(sourceString) \(diffHash)"
72 | }
73 | return CallTool.Result(content: [.text(diff )])
74 |
75 | } catch {
76 | throw MCPError.invalidParams(String(format: ErrorMessages.errorCreatingDiff, error.localizedDescription))
77 | }
78 | }
79 |
80 | /// Handles a call to create a diff from a document
81 | static func handleCreateDiffFromDocToolCall(_ params: CallTool.Parameters) throws -> CallTool.Result {
82 | guard let arguments = params.arguments else {
83 | throw MCPError.invalidParams("Missing arguments")
84 | }
85 |
86 | guard let filePath = arguments[McpConfig.filePathParamName]?.stringValue else {
87 | return CallTool.Result(content: [.text("Missing filePath parameter")])
88 | }
89 |
90 | guard let modifiedContent = arguments[McpConfig.modifiedContentParamName]?.stringValue else {
91 | return CallTool.Result(content: [.text("Missing modifiedContent parameter")])
92 | }
93 |
94 | do {
95 | let diffHash = try createDiffFromDocument(filePath: filePath, modifiedContent: modifiedContent)
96 | return CallTool.Result(content: [.text(diffHash)])
97 | } catch {
98 | throw MCPError.invalidParams(String(format: "Error creating diff from document: %@", error.localizedDescription))
99 | }
100 | }
101 |
102 | /// Handles a call to apply a diff to a document
103 | static func handleApplyDiffToDocToolCall(_ params: CallTool.Parameters) throws -> CallTool.Result {
104 | guard let arguments = params.arguments else {
105 | throw MCPError.invalidParams("Missing arguments")
106 | }
107 |
108 | guard let filePath = arguments[McpConfig.filePathParamName]?.stringValue else {
109 | return CallTool.Result(content: [.text("Missing filePath parameter")])
110 | }
111 |
112 | guard let diffHash = arguments[McpConfig.diffHashParamName]?.stringValue else {
113 | return CallTool.Result(content: [.text("Missing diffHash parameter")])
114 | }
115 |
116 | do {
117 | let diffResult = try getDiffResultFromHash(diffHash: diffHash)
118 | let success = try applyDiffToDocument(filePath: filePath, operations: diffResult)
119 | if success {
120 | return CallTool.Result(content: [.text("Diff applied successfully to document: \(filePath)")])
121 | } else {
122 | return CallTool.Result(content: [.text("Failed to apply diff to document: \(filePath)")])
123 | }
124 | } catch {
125 | throw MCPError.invalidParams(String(format: "Error applying diff to document: %@", error.localizedDescription))
126 | }
127 | }
128 |
129 | /// Handles a call to apply a diff to a document
130 | static func handleApplyUndoDiffToDocToolCall(_ params: CallTool.Parameters) throws -> CallTool.Result {
131 | guard let arguments = params.arguments else {
132 | throw MCPError.invalidParams("Missing arguments")
133 | }
134 |
135 | guard let filePath = arguments[McpConfig.filePathParamName]?.stringValue else {
136 | return CallTool.Result(content: [.text("Missing filePath parameter")])
137 | }
138 |
139 | guard let diffHash = arguments[McpConfig.diffHashParamName]?.stringValue else {
140 | return CallTool.Result(content: [.text("Missing diffHash parameter")])
141 | }
142 |
143 | do {
144 | let diffResult = try getDiffResultFromHash(diffHash: diffHash)
145 | let success = try applyDiffToDocument(filePath: filePath, operations: diffResult)
146 | if success {
147 | return CallTool.Result(content: [.text("Diff applied successfully to document: \(filePath)")])
148 | } else {
149 | return CallTool.Result(content: [.text("Failed to apply diff to document: \(filePath)")])
150 | }
151 | } catch {
152 | throw MCPError.invalidParams(String(format: "Error applying diff to document: %@", error.localizedDescription))
153 | }
154 | }
155 | }
156 |
--------------------------------------------------------------------------------
/xcf/Mcp/XcDoc/XcfMcpXcodeDocumentHandlers.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import MCP
3 |
4 | // MARK: - Xcode Document Tool Handlers
5 |
6 | class XcfMcpXcodeDocumentHandlers {
7 |
8 | /// Handles opening a document in Xcode
9 | static func handleOpenDocToolCall(_ params: CallTool.Parameters) throws -> CallTool.Result {
10 | guard let arguments = params.arguments else {
11 | return CallTool.Result(content: [.text(McpConfig.missingFilePathParamError)])
12 | }
13 |
14 | // Try to get filePath from arguments in two ways:
15 | // 1. As a named parameter (filePath=...)
16 | // 2. As a direct argument (first argument after command)
17 | let filePath: String
18 | if let namedPath = arguments[McpConfig.filePathParamName]?.stringValue {
19 | filePath = namedPath
20 | } else if let firstArg = arguments.first?.value.stringValue {
21 | filePath = firstArg
22 | } else {
23 | return CallTool.Result(content: [.text(McpConfig.missingFilePathParamError)])
24 | }
25 |
26 | // Use FuzzyLogicService to resolve the path for better error messages
27 | let (resolvedPath, warning) = FuzzyLogicService.resolveFilePath(filePath)
28 |
29 | // If there was a warning, include it in the response
30 | var responseMessage = ""
31 | if !warning.isEmpty {
32 | responseMessage += "Warning: \(warning)\n"
33 | }
34 |
35 | if XcfMcpServer.XcfScript.openSwiftDocument(filePath: resolvedPath) {
36 | responseMessage += McpConfig.documentOpenedSuccessfully
37 | return CallTool.Result(content: [.text(responseMessage)])
38 | } else {
39 | // Check if file exists to provide more specific error
40 | if !FileManager.default.fileExists(atPath: resolvedPath) {
41 | responseMessage += "Error: File does not exist: \(resolvedPath)"
42 | } else if !FileManager.default.isReadableFile(atPath: resolvedPath) {
43 | responseMessage += "Error: File is not readable: \(resolvedPath)"
44 | } else {
45 | responseMessage += String(format: ErrorMessages.errorOpeningFile, resolvedPath)
46 | }
47 | return CallTool.Result(content: [.text(responseMessage)])
48 | }
49 | }
50 |
51 | /// Handles creating a document in Xcode
52 | static func handleCreateDocToolCall(_ params: CallTool.Parameters) throws -> CallTool.Result {
53 | guard let arguments = params.arguments,
54 | let filePath = arguments[McpConfig.filePathParamName]?.stringValue else {
55 | return CallTool.Result(content: [.text(McpConfig.missingFilePathParamError)])
56 | }
57 |
58 | let content = arguments[McpConfig.contentParamName]?.stringValue ?? ""
59 |
60 | if XcfMcpServer.XcfScript.createSwiftDocumentWithFileManager(filePath: filePath, content: content) {
61 | return CallTool.Result(content: [.text(McpConfig.documentCreatedSuccessfully)])
62 | } else {
63 | return CallTool.Result(content: [.text(String(format: ErrorMessages.errorCreatingFile, filePath))])
64 | }
65 | }
66 |
67 | /// Handles reading a document from Xcode
68 | static func handleReadDocToolCall(_ params: CallTool.Parameters) throws -> CallTool.Result {
69 | guard let arguments = params.arguments else {
70 | return CallTool.Result(content: [.text(McpConfig.missingFilePathParamError)])
71 | }
72 |
73 | // Try to get filePath from arguments in two ways:
74 | // 1. As a named parameter (filePath=...)
75 | // 2. As a direct argument (first argument after command)
76 | let filePath: String
77 | if let namedPath = arguments[McpConfig.filePathParamName]?.stringValue {
78 | filePath = namedPath
79 | } else if let firstArg = arguments.first?.value.stringValue {
80 | filePath = firstArg
81 | } else {
82 | return CallTool.Result(content: [.text(McpConfig.missingFilePathParamError)])
83 | }
84 |
85 | // Use FuzzyLogicService to resolve the path
86 | let (resolvedPath, warning) = FuzzyLogicService.resolveFilePath(filePath)
87 |
88 | if let content = XcfMcpServer.XcfScript.readSwiftDocumentWithFileManager(filePath: resolvedPath) {
89 | return CallTool.Result(content: [.text(content)])
90 | } else {
91 | return CallTool.Result(content: [.text(String(format: ErrorMessages.errorReadingFile, filePath))])
92 | }
93 | }
94 |
95 | /// Handles saving a document in Xcode
96 | static func handleSaveDocToolCall(_ params: CallTool.Parameters) throws -> CallTool.Result {
97 | guard let arguments = params.arguments,
98 | let filePath = arguments[McpConfig.filePathParamName]?.stringValue else {
99 | return CallTool.Result(content: [.text(McpConfig.missingFilePathParamError)])
100 | }
101 |
102 | if XcfMcpServer.XcfScript.writeSwiftDocumentWithFileManager(filePath: filePath, content: "") {
103 | return CallTool.Result(content: [.text(McpConfig.documentSavedSuccessfully)])
104 | } else {
105 | return CallTool.Result(content: [.text(String(format: ErrorMessages.errorWritingFile, filePath))])
106 | }
107 | }
108 |
109 | /// Handles closing a document in Xcode
110 | static func handleCloseDocToolCall(_ params: CallTool.Parameters) throws -> CallTool.Result {
111 | // Debug: Print out the entire params
112 |
113 | guard let arguments = params.arguments else {
114 | return CallTool.Result(content: [.text(McpConfig.missingFilePathParamError)])
115 | }
116 |
117 | // Try to get filePath from arguments in two ways:
118 | // 1. As a named parameter (filePath=...)
119 | // 2. As a direct argument (first argument after command)
120 | let filePath: String
121 | if let namedPath = arguments[McpConfig.filePathParamName]?.stringValue {
122 | filePath = namedPath
123 | } else if let firstArg = arguments.first?.value.stringValue {
124 | filePath = firstArg
125 | } else {
126 | return CallTool.Result(content: [.text(McpConfig.missingFilePathParamError)])
127 | }
128 |
129 | // Get saving option, handling both boolean and string representations
130 | let savingParam: Bool
131 | if let boolValue = arguments["saving"]?.boolValue {
132 | savingParam = boolValue
133 | } else if let stringValue = arguments["saving"]?.stringValue {
134 | // Handle string representations of boolean
135 | savingParam = ["true", "yes", "1"].contains(stringValue.lowercased())
136 | } else {
137 | return CallTool.Result(content: [.text("Missing 'saving' parameter")])
138 | }
139 |
140 | // Convert boolean to XcodeSaveOptions
141 | let saveOptions: XcodeSaveOptions = savingParam ? .yes : .no
142 |
143 | if XcfMcpServer.XcfScript.closeSwiftDocument(filePath: filePath, xcSaveOptions: saveOptions) {
144 | return CallTool.Result(content: [.text(McpConfig.documentClosedSuccessfully)])
145 | } else {
146 | return CallTool.Result(content: [.text(String(format: ErrorMessages.errorClosingFile, filePath))])
147 | }
148 | }
149 | }
150 |
--------------------------------------------------------------------------------
/xcf/Mcp/XcDoc/XcfXcodeProjectManager.swift:
--------------------------------------------------------------------------------
1 | //
2 | // XcfXcodeProjectManager.swift
3 | // xcf
4 | //
5 | // Created by Todd Bruss on 5/4/25.
6 | //
7 |
8 | import Foundation
9 |
10 | // Environment variable keys
11 | private enum EnvKeys {
12 | static let xcodeProject = EnvVarKeys.xcodeProject
13 | static let workspaceFolderPaths = EnvVarKeys.workspaceFolderPaths
14 | static let xcodeProjectFolder = EnvVarKeys.xcodeProjectFolder
15 | static let xcodeProjectPath = EnvVarKeys.xcodeProjectPath
16 | }
17 |
18 | // File extensions
19 | private enum FileExtensions {
20 | static let xcodeproj = Format.xcodeProjExtension
21 | static let xcworkspace = Format.xcodeWorkExtension
22 |
23 | static func isValidXcodeProjectPath(_ path: String) -> Bool {
24 | path.hasSuffix(xcodeproj) || path.hasSuffix(xcworkspace)
25 | }
26 |
27 | static func removeProjectSuffix(from path: String) -> String {
28 | if path.hasSuffix(xcodeproj) {
29 | return String(path.dropLast(xcodeproj.count))
30 | } else if path.hasSuffix(xcworkspace) {
31 | return String(path.dropLast(xcworkspace.count))
32 | }
33 | return path
34 | }
35 | }
36 |
37 | // Global variables with clear prefix for backward compatibility
38 | private var XcfXcodeProject: String? = {
39 | if let workspaceProjectPath = ProcessInfo.processInfo.environment[EnvKeys.workspaceFolderPaths],
40 | !workspaceProjectPath.contains(Format.commaSeparator) {
41 | let projectName = URL(fileURLWithPath: workspaceProjectPath).lastPathComponent
42 | let constructedProjectPath = workspaceProjectPath + "/" + projectName + ".xcodeproj"
43 |
44 | // Validate the constructed path exists before returning
45 | if FileManager.default.fileExists(atPath: constructedProjectPath) {
46 | return constructedProjectPath
47 | }
48 |
49 | // Fallback if constructed path doesn't exist
50 | return ProcessInfo.processInfo.environment[EnvKeys.xcodeProject] ??
51 | XcfSwiftScript.shared.activeWorkspacePath()
52 | }
53 |
54 | // Original fallback
55 | return ProcessInfo.processInfo.environment[EnvKeys.xcodeProject] ??
56 | XcfSwiftScript.shared.activeWorkspacePath()
57 | }()
58 |
59 | private var XcfXcodeFolder: String? = {
60 | if let workspaceFolderPath = ProcessInfo.processInfo.environment[EnvKeys.workspaceFolderPaths],
61 | !workspaceFolderPath.contains(",") {
62 | let projectName = URL(fileURLWithPath: workspaceFolderPath).lastPathComponent
63 | let constructedFolderPath = workspaceFolderPath + "/" + projectName
64 |
65 | // Validate the constructed path exists before returning
66 | if FileManager.default.fileExists(atPath: constructedFolderPath) {
67 | return constructedFolderPath
68 | }
69 |
70 | // Fallback if constructed path doesn't exist
71 | return ProcessInfo.processInfo.environment[EnvKeys.xcodeProjectFolder] ??
72 | ProcessInfo.processInfo.environment[EnvKeys.xcodeProjectPath]
73 | }
74 |
75 | // Original fallback
76 | return ProcessInfo.processInfo.environment[EnvKeys.xcodeProjectFolder] ??
77 | ProcessInfo.processInfo.environment[EnvKeys.xcodeProjectPath]
78 | }()
79 |
80 | /// Centralized manager for Xcode project and folder paths
81 | class XcfXcodeProjectManager {
82 | // Singleton instance
83 | static let shared = XcfXcodeProjectManager()
84 |
85 | private init() {}
86 |
87 | // MARK: - Properties
88 |
89 | /// Get the current Xcode project path
90 | var currentProject: String? {
91 | get { return XcfXcodeProject }
92 | set { XcfXcodeProject = newValue }
93 | }
94 |
95 | /// Get the current working folder path
96 | var currentFolder: String? {
97 | get { return XcfXcodeFolder }
98 | set { XcfXcodeFolder = newValue }
99 | }
100 |
101 | // MARK: - Public Methods
102 |
103 | /// Update both project and folder paths based on project selection
104 | /// - Parameter projectPath: The path to the selected Xcode project
105 | func updateFromProjectSelection(_ projectPath: String) {
106 | guard FileExtensions.isValidXcodeProjectPath(projectPath) else {
107 | return
108 | }
109 |
110 | currentProject = projectPath
111 | currentFolder = FileExtensions.removeProjectSuffix(from: projectPath)
112 |
113 | updateWorkingDirectory()
114 | }
115 |
116 | /// Initializes the manager with values from environment variables
117 | func initialize() {
118 | initializeProject()
119 | initializeFolder()
120 | updateWorkingDirectory()
121 | }
122 |
123 | /// Check if the project is within the allowed workspace folder
124 | /// - Parameters:
125 | /// - projectPath: The path to check
126 | /// - workspaceFolder: The workspace folder to check against
127 | /// - Returns: True if the project is within the workspace folder
128 | func isProjectInWorkspaceFolder(projectPath: String, workspaceFolder: String) -> Bool {
129 | let projectURL = URL(fileURLWithPath: projectPath)
130 | let workspaceFolderURL = URL(fileURLWithPath: workspaceFolder)
131 | return projectURL.path.contains(workspaceFolderURL.path)
132 | }
133 |
134 | // MARK: - Private Methods
135 |
136 | private func initializeProject() {
137 | if currentProject == nil {
138 | let potentialProject = ProcessInfo.processInfo.environment[EnvKeys.xcodeProject] ?? XcfSwiftScript.shared.activeWorkspacePath()
139 |
140 | if let projectPath = potentialProject,
141 | FileExtensions.isValidXcodeProjectPath(projectPath) {
142 | currentProject = projectPath
143 | }
144 | }
145 | }
146 |
147 | private func initializeFolder() {
148 | if currentFolder == nil {
149 | if let paths = ProcessInfo.processInfo.environment[EnvKeys.workspaceFolderPaths] {
150 | // Split by comma and take the first path
151 | let components = paths.components(separatedBy: Format.commaSeparator)
152 | currentFolder = components.first?.trimmingCharacters(in: .whitespaces)
153 | } else if let folderPath = ProcessInfo.processInfo.environment[EnvKeys.xcodeProjectFolder] ??
154 | ProcessInfo.processInfo.environment[EnvKeys.xcodeProjectPath] {
155 | currentFolder = folderPath
156 | } else if let projectPath = currentProject {
157 | currentFolder = FileExtensions.removeProjectSuffix(from: projectPath)
158 | }
159 | }
160 | }
161 |
162 | private func updateWorkingDirectory() {
163 | guard let folderPath = currentFolder else { return }
164 |
165 | do {
166 | try XcfFileManager.changeDirectory(to: folderPath)
167 | } catch {
168 | print(String(format: ErrorMessages.errorChangingDirectory, error.localizedDescription))
169 | }
170 | }
171 | }
172 |
173 | // For backward compatibility - consider deprecating in future versions
174 | var currentProject: String? {
175 | get { return XcfXcodeProjectManager.shared.currentProject }
176 | set { XcfXcodeProjectManager.shared.currentProject = newValue }
177 | }
178 |
179 | var currentFolder: String? {
180 | get { return XcfXcodeProjectManager.shared.currentFolder }
181 | set { XcfXcodeProjectManager.shared.currentFolder = newValue }
182 | }
183 |
--------------------------------------------------------------------------------
/xcf/Mcp/ServerConfig/XcfMcpConstants.swift:
--------------------------------------------------------------------------------
1 | //
2 | // XcfMcpStructStrings.swift
3 | // xcf
4 | //
5 | // Created by Todd Bruss on 5/17/25.
6 | //
7 | import Foundation
8 |
9 | // Core app constants
10 | struct AppConstants {
11 | static let appName = "xcf"
12 | }
13 |
14 | // Define string constants for commands
15 | struct Actions {
16 | static let xcf = AppConstants.appName,
17 | help = "help",
18 | show = "show",
19 | open = "open",
20 | run = "run",
21 | build = "build",
22 | grant = "grant",
23 | current = "current",
24 | env = "env",
25 | pwd = "pwd",
26 | dir = "dir",
27 | path = "path",
28 | analyze = "analyze",
29 | lz = "lz" // Short alias for analyze
30 | }
31 |
32 | // Environment variable constants
33 | struct EnvVarKeys {
34 | static let xcodeProject = "XCODE_PROJECT",
35 | workspaceFolderPaths = "WORKSPACE_FOLDER_PATHS",
36 | xcodeProjectFolder = "XCODE_PROJECT_FOLDER",
37 | xcodeProjectPath = "XCODE_PROJECT_PATH"
38 | }
39 |
40 | // Define error messages
41 | struct ErrorMessages {
42 | // MARK: - Project Selection Errors
43 | static let noProjectSelected = "No project selected yet. Use 'show' to see available projects.",
44 | noOpenProjects = "I don't see any open Xcode projects. Try opening one first.",
45 | invalidProjectSelection = "That's not a valid selection. Try 'open 1' to select the first project.",
46 | projectOutOfRange = "Project %@ doesn't exist. I only found %@ projects.",
47 | noProjectInWorkspace = "I couldn't find a project in your workspace.",
48 | invalidProjectPath = "Warning: %@ is not a valid Xcode project or workspace path"
49 |
50 | // MARK: - Action Errors
51 | static let unrecognizedAction = "I don't understand '%@'. Try 'help' to see what I can do.",
52 | toolCallError = "Error executing tool '%@': %@"
53 |
54 | // MARK: - Xcode Connection Errors
55 | static let failedToConnectXcode = "I couldn't connect to Xcode. Is it running?",
56 | noWorkspaceFound = "I couldn't find a workspace document. Try opening an Xcode project first."
57 |
58 | // MARK: - Build Errors
59 | static let failedToStartBuild = "I had trouble starting the build. Please try again.",
60 | failedToGetBuildResult = "I couldn't get the build results. Something went wrong.",
61 | errorGettingBuildResults = "Error getting build results: %@"
62 |
63 | // MARK: - File Operation Errors
64 | static let errorReadingFile = "Error reading file: %@",
65 | errorWritingFile = "Error writing file: %@",
66 | errorCreatingFile = "Error creating file: %@",
67 | errorEditingFile = "Error editing file: %@",
68 | errorDeletingFile = "Error deleting file: %@",
69 | errorOpeningFile = "Error opening file: %@",
70 | errorClosingFile = "Error closing file: %@",
71 | errorReadingDirectory = "Error reading directory: %@",
72 | errorCreatingDirectory = "Error creating directory: %@",
73 | errorSelectingDirectory = "Error selecting directory: %@",
74 | errorListingProjects = "Error listing projects: %@",
75 | fileNotFound = "File not found: %@",
76 | directoryNotFound = "Directory not found: %@",
77 | invalidFilePath = "Invalid file path: %@",
78 | invalidDirectoryPath = "Invalid directory path: %@",
79 | fileAlreadyExists = "File already exists: %@",
80 | directoryAlreadyExists = "Directory already exists: %@",
81 | permissionDenied = "Permission denied: %@",
82 | unknownFileError = "Unknown error occurred while operating on: %@"
83 |
84 | // MARK: - Osascript Errors
85 | static let failedToConvertOutput = "Failed to convert output data to string.",
86 | failedToExecuteOsascript = "Failed to execute osascript: %@",
87 | failedToCreateAppleScript = "Failed to create AppleScript.",
88 | appleScriptError = "Error: %@"
89 |
90 | // MARK: - MCP Errors
91 | static let unknownTool = "Unknown tool: %@"
92 |
93 | // MARK: - Code Snippet Errors
94 | static let missingLineParamsError = "Missing required line parameters when entireFile is false",
95 | missingFilePathError = "Missing required filePath parameter",
96 | invalidLineNumbers = "Those line numbers don't look right. Please check them."
97 |
98 | // Resource error messages
99 | static let missingFilePathParamError = "Missing required filePath parameter for file operation",
100 | missingDirectoryPathParamError = "Missing required directoryPath parameter",
101 | unknownResourceUriError = "Unknown resource URI: %@",
102 | unknownPromptNameError = "Unknown prompt name: %@"
103 |
104 | // Directory operation errors
105 | static let errorChangingDirectory = "Error changing directory: %@",
106 | errorRemovingDirectory = "Error removing directory: %@"
107 |
108 | // Error message for creating a diff
109 | static let errorCreatingDiff = "Error creating diff: %@"
110 | }
111 |
112 | // Define success messages
113 | struct SuccessMessages {
114 | static let xcfActive = "All \(AppConstants.appName) systems go!",
115 | buildSuccess = "🐦📜 Built successfully",
116 | runSuccess = "🐦📜 Ran successfully",
117 | permissionGranted = "Permission Granted",
118 | success = "success",
119 | pwdSuccess = "Current directory: %@",
120 | currentProject = "%@",
121 | environmentVariables = "Environment Variables: %@",
122 | securityPreventManualSelection = "Staying safe! I've kept you in your workspace.\nYour workspace: %@\nUsing: %@"
123 | }
124 |
125 | // Define path constants
126 | struct Paths {
127 | static let osascriptPath = "/usr/bin/osascript"
128 | }
129 |
130 | // Define file extensions and formats
131 | struct Format {
132 | static let xcodeProjExtension = ".xcodeproj",
133 | xcodeWorkExtension = ".xcworkspace",
134 | projectListFormat = "%d. %@\n",
135 | newLine = "\n",
136 | spaceSeparator = " ",
137 | commaSeparator = ","
138 |
139 | // Regex patterns
140 | static let quoteExtractPattern = /\"([^\"]+)\"/
141 |
142 | // Character sets
143 | static func newlinesCharSet() -> CharacterSet {
144 | return .newlines
145 | }
146 | }
147 |
148 | // Define Xcode-related constants
149 | struct XcodeConstants {
150 | // Bundle IDs
151 | static let xcodeBundleIdentifier = "com.apple.dt.Xcode"
152 |
153 | // Issue types
154 | static let errorIssueType = "Error",
155 | warningIssueType = "Warning",
156 | analyzerIssueType = "Analyzer Issue",
157 | testFailureIssueType = "Test Failure"
158 |
159 | // Time intervals
160 | static let buildPollInterval = 0.5, // seconds
161 | runDelayInterval: UInt32 = 1 // seconds
162 |
163 | // File prefix and format
164 | static let filePrefix = "File:`",
165 | fileSuffix = "`:"
166 |
167 | // Code formatting
168 | static let codeBlockStart = "```",
169 | codeBlockEnd = "```",
170 | issueFormat = "%@:%d:%d [%@] %@"
171 | }
172 |
--------------------------------------------------------------------------------
/xcf/Mcp/Script/XcfSwiftDiff.swift:
--------------------------------------------------------------------------------
1 | //
2 | // XcfSwiftDiff.swift
3 | // xcf
4 | //
5 | // Created by Todd Bruss on 5/19/25.
6 | //
7 |
8 | import Foundation
9 | import MultiLineDiff
10 |
11 | var DiffOperationDict: [String : DiffResult] = [:]
12 |
13 | func createDiff(original: String, modified: String) -> DiffResult {
14 | MultiLineDiff.createDiff(source: original, destination: modified)
15 | }
16 |
17 | func applyDiff(original: String, diff: DiffResult) throws -> String {
18 | try MultiLineDiff.applyDiff(to: original, diff: diff)
19 | }
20 |
21 | /// Applies a diff to a document
22 | /// - Parameters:
23 | /// - filePath: Path to the source document
24 | /// - operations: The diff operations to apply
25 | /// - Returns: True if successful, false otherwise
26 | /// - Throws: File access, parsing, or application errors
27 | func applyUndoDiffToDocument(
28 | filePath: String,
29 | operations: DiffResult
30 | ) throws -> Bool {
31 | // Resolve the file path
32 | let (resolvedPath, warning) = FuzzyLogicService.resolveFilePath(filePath)
33 |
34 | // Validate file exists
35 | guard FileManager.default.fileExists(atPath: resolvedPath) else {
36 | throw NSError(domain: "XcfSwiftDiff", code: 1, userInfo: [
37 | NSLocalizedDescriptionKey: "File not found: \(filePath)"
38 | ])
39 | }
40 |
41 | // Read the original content using ScriptingBridge
42 | guard let originalContent = XcfSwiftScript.shared.readSwiftDocumentWithFileManager(filePath: resolvedPath) else {
43 | throw NSError(domain: "XcfSwiftDiff", code: 3, userInfo: [
44 | NSLocalizedDescriptionKey: "Failed to read document content"
45 | ])
46 | }
47 |
48 | // Apply the diff to the entire content using the existing applyDiff function
49 | guard let operations = MultiLineDiff.createUndoDiff(from: operations) else {
50 | return false
51 | }
52 |
53 | let modifiedContent = try applyDiff(original: originalContent, diff: operations)
54 |
55 | // Write the modified content back to the file using ScriptingBridge
56 | if !XcfSwiftScript.shared.writeSwiftDocumentWithFileManager(filePath: resolvedPath, content: modifiedContent) {
57 | throw NSError(domain: "XcfSwiftDiff", code: 4, userInfo: [
58 | NSLocalizedDescriptionKey: "Failed to write modified content to document"
59 | ])
60 | }
61 |
62 | return true
63 | }
64 |
65 | func getAsciiDiffFromHash(diffHash: String) throws -> String {
66 | // Try to retrieve diff operations from the dictionary
67 | guard let storedOperations = DiffOperationDict[diffHash] else {
68 | throw NSError(domain: "XcfSwiftDiff", code: 5, userInfo: [
69 | NSLocalizedDescriptionKey: "No diff operations found for the given hash"
70 | ])
71 | }
72 |
73 | guard let source = storedOperations.metadata?.sourceContent else { return "bad source" }
74 | guard let destination = storedOperations.metadata?.destinationContent else { return "bad destination"}
75 | return MultiLineDiff.generateASCIIDiff(source: source, destination: destination)
76 | }
77 |
78 | func getDiffResultFromHash(diffHash: String) throws -> DiffResult {
79 | // Try to retrieve diff operations from the dictionary
80 | guard let storedOperations = DiffOperationDict[diffHash] else {
81 | throw NSError(domain: "XcfSwiftDiff", code: 5, userInfo: [
82 | NSLocalizedDescriptionKey: "No diff operations found for the given hash"
83 | ])
84 | }
85 | return storedOperations
86 | }
87 |
88 | /// Applies a diff to a document
89 | /// - Parameters:
90 | /// - filePath: Path to the source document
91 | /// - operations: The diff operations to apply
92 | /// - Returns: True if successful, false otherwise
93 | /// - Throws: File access, parsing, or application errors
94 | func applyDiffToDocument(
95 | filePath: String,
96 | operations: DiffResult
97 | ) throws -> Bool {
98 | // Resolve the file path
99 | let (resolvedPath, warning) = FuzzyLogicService.resolveFilePath(filePath)
100 |
101 | // Validate file exists
102 | guard FileManager.default.fileExists(atPath: resolvedPath) else {
103 | throw NSError(domain: "XcfSwiftDiff", code: 1, userInfo: [
104 | NSLocalizedDescriptionKey: "File not found: \(filePath)"
105 | ])
106 | }
107 |
108 | // Read the original content using ScriptingBridge
109 | guard let originalContent = XcfSwiftScript.shared.readSwiftDocumentWithFileManager(filePath: resolvedPath) else {
110 | throw NSError(domain: "XcfSwiftDiff", code: 3, userInfo: [
111 | NSLocalizedDescriptionKey: "Failed to read document content"
112 | ])
113 | }
114 |
115 | // Apply the diff to the entire content using the existing applyDiff function
116 | let modifiedContent = try applyDiff(original: originalContent, diff: operations)
117 |
118 | // Write the modified content back to the file using ScriptingBridge
119 | if !XcfSwiftScript.shared.writeSwiftDocumentWithFileManager(filePath: resolvedPath, content: modifiedContent) {
120 | throw NSError(domain: "XcfSwiftDiff", code: 4, userInfo: [
121 | NSLocalizedDescriptionKey: "Failed to write modified content to document"
122 | ])
123 | }
124 |
125 | return true
126 | }
127 |
128 | // Add new function to create diff from document and store in dictionary
129 | func createDiffFromString(original: String, modified: String) throws -> String {
130 |
131 | // Create the diff operations
132 | let diffResult = createDiff(original: original, modified: modified)
133 |
134 | guard let diffHash = diffResult.metadata?.diffHash else {
135 | return "The Diff did not create a hash"
136 | }
137 |
138 | // Store the diff operations in the dictionary
139 | DiffOperationDict[diffHash] = diffResult
140 |
141 | return diffHash
142 | }
143 |
144 | // Add new function to create diff from document and store in dictionary
145 | func applyDiffFromString(original: String, diffHash: String) throws -> String {
146 |
147 | // Store the diff operations in the dictionary
148 | guard let diff = DiffOperationDict[diffHash] else {
149 | return "Diff hash not found"
150 | }
151 |
152 | // Create the diff operations
153 | return try applyDiff(original: original, diff: diff)
154 |
155 | }
156 |
157 | // Add new function to create diff from document and store in dictionary
158 | func createDiffFromDocument(filePath: String, modifiedContent: String) throws -> String {
159 | // Resolve the file path
160 | let (resolvedPath, warning) = FuzzyLogicService.resolveFilePath(filePath)
161 |
162 | // Validate file exists
163 | guard FileManager.default.fileExists(atPath: resolvedPath) else {
164 | throw NSError(domain: "XcfSwiftDiff", code: 1, userInfo: [
165 | NSLocalizedDescriptionKey: "File not found: \(filePath)"
166 | ])
167 | }
168 |
169 | // Read the original content using ScriptingBridge
170 | guard let originalContent = XcfSwiftScript.shared.readSwiftDocumentWithFileManager(filePath: resolvedPath) else {
171 | throw NSError(domain: "XcfSwiftDiff", code: 3, userInfo: [
172 | NSLocalizedDescriptionKey: "Failed to read document content"
173 | ])
174 | }
175 |
176 | // Create the diff operations
177 | let diffResult = createDiff(original: originalContent, modified: modifiedContent)
178 |
179 | // Create a new hash for this diff operation
180 | let diffHash = diffResult.metadata?.diffHash ?? "Diff hash is missing"
181 |
182 | // Store the diff operations in the dictionary
183 | DiffOperationDict[diffHash] = diffResult
184 |
185 | return diffHash
186 | }
187 |
--------------------------------------------------------------------------------
/xcf/Mcp/Tools/XcfMcpFileSystemHandlers.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import MCP
3 |
4 | // MARK: - File System Tool Handlers
5 |
6 | class XcfMcpFileSystemHandlers {
7 |
8 | /// Handles a call to the read file tool
9 | static func handleReadFileToolCall(_ params: CallTool.Parameters) throws -> CallTool.Result {
10 | guard let arguments = params.arguments else {
11 | return CallTool.Result(content: [.text(McpConfig.missingFilePathParamError)])
12 | }
13 |
14 | // Try to get filePath from arguments in two ways:
15 | // 1. As a named parameter (filePath=...)
16 | // 2. As a direct argument (first argument after command)
17 | let filePath: String
18 | if let namedPath = arguments[McpConfig.filePathParamName]?.stringValue {
19 | filePath = namedPath
20 | } else if let firstArg = arguments.first?.value.stringValue {
21 | filePath = firstArg
22 | } else {
23 | return CallTool.Result(content: [.text(McpConfig.missingFilePathParamError)])
24 | }
25 |
26 | // Get and verify the project directory
27 | guard let projectDir = XcfXcodeProjectManager.shared.currentFolder else {
28 | return CallTool.Result(content: [.text("No current project directory set")])
29 | }
30 |
31 | // Determine the full path
32 | let fullPath = FuzzyLogicService.expandPath(filePath, relativeTo: projectDir)
33 |
34 | do {
35 | let fileContents = try String(contentsOfFile: fullPath, encoding: .utf8)
36 | return CallTool.Result(content: [.text(fileContents)])
37 | } catch {
38 | return CallTool.Result(content: [.text(String(format: ErrorMessages.errorReadingFile, error.localizedDescription))])
39 | }
40 | }
41 |
42 | /// Handles a call to the change directory tool
43 | static func handleCdDirToolCall(_ params: CallTool.Parameters) throws -> CallTool.Result {
44 | // Get the current folder first as we'll need it
45 | guard let currentFolder = XcfXcodeProjectManager.shared.currentFolder else {
46 | return CallTool.Result(content: [.text("No current folder is set. Please select a project first.")])
47 | }
48 |
49 | // If no arguments provided, show current directory
50 | if params.arguments == nil || params.arguments?.isEmpty == true {
51 | return CallTool.Result(content: [.text("Current directory: \(currentFolder)")])
52 | }
53 |
54 | let arguments = params.arguments!
55 |
56 | // Determine the directory to change to
57 | let directoryPath: String
58 | if let namedPath = arguments[McpConfig.directoryPathParamName]?.stringValue {
59 | // Case 1: Named parameter
60 | if namedPath == "." {
61 | directoryPath = currentFolder
62 | } else if namedPath == ".." {
63 | directoryPath = (currentFolder as NSString).deletingLastPathComponent
64 | } else {
65 | directoryPath = namedPath
66 | }
67 | } else if let firstArg = arguments.first?.value.stringValue {
68 | // Case 2: First argument
69 | if firstArg == "." {
70 | directoryPath = currentFolder
71 | } else if firstArg == ".." {
72 | directoryPath = (currentFolder as NSString).deletingLastPathComponent
73 | } else {
74 | directoryPath = firstArg
75 | }
76 | } else {
77 | // Default to showing current directory
78 | return CallTool.Result(content: [.text("Current directory: \(currentFolder)")])
79 | }
80 |
81 | // Security check
82 | let (allowed, resolvedPath, error) = SecurityManager.shared.isDirectoryOperationAllowed(directoryPath, operation: "change to")
83 | if !allowed {
84 | return CallTool.Result(content: [.text(error ?? "Access denied")])
85 | }
86 |
87 | do {
88 | // Update both FileManager's current directory and XcfXcodeProjectManager's currentFolder
89 | try XcfFileManager.changeDirectory(to: resolvedPath)
90 | XcfXcodeProjectManager.shared.currentFolder = resolvedPath
91 | return CallTool.Result(content: [.text("Changed directory to: \(resolvedPath)")])
92 | } catch {
93 | return CallTool.Result(content: [.text(String(format: ErrorMessages.errorChangingDirectory, error.localizedDescription))])
94 | }
95 | }
96 |
97 | /// Handles a call to the read directory tool
98 | static func handleReadDirToolCall(_ params: CallTool.Parameters) throws -> CallTool.Result {
99 | // Get the current folder first as we'll need it
100 | guard let currentFolder = XcfXcodeProjectManager.shared.currentFolder else {
101 | return CallTool.Result(content: [.text("No current folder is set. Please select a project first.")])
102 | }
103 |
104 | let arguments = params.arguments!
105 |
106 | // Determine the directory to read
107 | let directoryPath: String
108 | if let namedPath = arguments[McpConfig.directoryPathParamName]?.stringValue {
109 | // Case 1: Named parameter
110 | if namedPath == "." || params.arguments == nil || params.arguments?.isEmpty == true {
111 | directoryPath = currentFolder
112 | } else if namedPath == ".." {
113 | directoryPath = (currentFolder as NSString).deletingLastPathComponent
114 | } else if namedPath == "cd" {
115 | do {
116 | let filePaths = try XcfFileManager.readDirectory(at: currentFolder, fileExtension: nil)
117 | let content = filePaths.joined(separator: McpConfig.newLineSeparator)
118 | return CallTool.Result(content: [.text(content)])
119 | } catch {
120 | return CallTool.Result(content: [.text(String(format: ErrorMessages.errorReadingDirectory, error.localizedDescription))])
121 | }
122 | } else {
123 | directoryPath = namedPath
124 | }
125 | } else if let firstArg = arguments.first?.value.stringValue {
126 | // Case 2: First argument
127 | if firstArg == "." {
128 | directoryPath = currentFolder
129 | } else if firstArg == ".." {
130 | directoryPath = (currentFolder as NSString).deletingLastPathComponent
131 | } else {
132 | directoryPath = firstArg
133 | }
134 | } else {
135 | // Default to current folder
136 | directoryPath = currentFolder
137 | }
138 |
139 | // Security check
140 | let (allowed, resolvedPath, error) = SecurityManager.shared.isDirectoryOperationAllowed(directoryPath, operation: "read")
141 | if !allowed {
142 | return CallTool.Result(content: [.text(error ?? "Access denied")])
143 | }
144 |
145 | // Get optional file extension filter
146 | let fileExtension: String?
147 | if let namedExt = arguments[McpConfig.fileExtensionParamName]?.stringValue {
148 | fileExtension = namedExt.isEmpty ? nil : namedExt
149 | } else {
150 | let keys = arguments.keys.sorted()
151 | if keys.count >= 2 {
152 | fileExtension = arguments[keys[1]]?.stringValue
153 | } else {
154 | fileExtension = nil
155 | }
156 | }
157 |
158 | do {
159 | let filePaths = try XcfFileManager.readDirectory(at: resolvedPath, fileExtension: fileExtension)
160 | let content = filePaths.joined(separator: McpConfig.newLineSeparator)
161 | return CallTool.Result(content: [.text(content)])
162 | } catch {
163 | return CallTool.Result(content: [.text(String(format: ErrorMessages.errorReadingDirectory, error.localizedDescription))])
164 | }
165 | }
166 | }
--------------------------------------------------------------------------------
/xcf-quickref.md:
--------------------------------------------------------------------------------
1 | # XCF Quick Reference Guide 🚀
2 |
3 | ## 🎛️ XCF Core Actions
4 |
5 | ### Permissions & Project Management
6 | | Action | Description | Example | Output |
7 | |--------|-------------|---------|--------|
8 | | `grant` | Grant Xcode automation | `grant` | Permissions granted |
9 | | `show` | List open projects | `show` | 1. /path/to/project.xcodeproj |
10 | | `open #` | Select project | `open 1` | Project selected |
11 | | `current` | Show selected project | `current` | /path/to/current/project |
12 |
13 | ### Build & Execute
14 | | Action | Description | Example | Output |
15 | |--------|-------------|---------|--------|
16 | | `build` | Build project | `build` | 🐦📜 Built successfully |
17 | | `run` | Run project | `run` | 🐦📜 Ran successfully |
18 |
19 | ### System & Analysis
20 | | Action | Description | Example | Output |
21 | |--------|-------------|---------|--------|
22 | | `env` | Show environment | `env` | [Environment variables] |
23 | | `pwd` | Show current folder | `pwd` | /current/directory |
24 | | `analyze ` | Analyze Swift code | `analyze main.swift` | Code analysis report |
25 | | `lz ` | Quick code analysis | `lz main.swift` | Condensed analysis |
26 |
27 | ### Aliases
28 | - `pwd` = `dir` = `path`
29 | - `lz` = shorthand for `analyze`
30 |
31 | ### Quick Workflow
32 | ```bash
33 | grant # Authorize XCF
34 | show # List projects
35 | open 1 # Select project
36 | build # Compile project
37 | run # Execute project
38 | ```
39 |
40 | ### Code Analysis Workflow
41 | ```bash
42 | analyze main.swift # Detailed analysis
43 | lz main.swift # Quick analysis
44 | ```
45 |
46 | ## 📋 Core Commands
47 |
48 | ### Activation & Project Management
49 | | Command | Description | Example | Output |
50 | |---------|-------------|---------|--------|
51 | | `show` | List open Xcode projects | `show` | 1. /path/to/project.xcodeproj |
52 | | `open #` | Select project by number | `open 1` | [Selects project] |
53 | | `current` | Show current project | `current` | Current project: /path/to/project.xcodeproj |
54 |
55 | ### Build & Run
56 | | Command | Description | Example | Output |
57 | |---------|-------------|---------|--------|
58 | | `build` | Build current project | `build` | 🐦📜 Built successfully |
59 | | `run` | Run current project | `run` | 🐦📜 Ran successfully |
60 |
61 | ### System Information
62 | | Command | Description | Example | Output |
63 | |---------|-------------|---------|--------|
64 | | `env` | Show environment variables | `env` | [List of environment variables] |
65 | | `pwd` | Show current folder | `pwd` | Current folder: /path/to/folder |
66 | | `help` | Display all commands | `help` | [List of available commands] |
67 |
68 | ## 🔍 Path Resolution & Quoting
69 |
70 | ### Supported Path Types
71 | - Current directory: `file.swift`
72 | - Child directories: `src/file.swift`
73 | - Parent directory: `../file.swift`
74 | - Multiple directories up: `../../file.swift`
75 | - Full system paths: `/Users/username/project/file.swift`
76 |
77 | ### Quoting Rules
78 | - Use quotes for content with spaces
79 | - Single `'` or double `"` quotes work
80 | - Recommended for complex content
81 |
82 | ### Path Resolution Examples
83 | ```bash
84 | read_file main.swift # Current directory
85 | read_file src/utils.swift # Child directory
86 | read_file ../shared/config.swift # Parent directory
87 | write_file test.txt "Hello World" # Simple content
88 | write_file config.json '{"key": "value"}' # JSON content
89 | ```
90 |
91 | ## 🗂️ File Operations
92 | | Command | Description | Example |
93 | |---------|-------------|---------|
94 | | `read_file ` | Read file contents | `read_file main.swift` |
95 | | `write_file ` | Write to file | `write_file test.txt "Hello World"` |
96 | | `edit_file ` | Edit file lines | `edit_file main.swift 10 20 "Updated code"` |
97 | | `delete_file ` | Delete file | `delete_file temp.txt` |
98 | | `move_file ` | Move file | `move_file old.swift new.swift` |
99 |
100 | ## 📂 Directory Operations
101 | | Command | Description | Example |
102 | |---------|-------------|---------|
103 | | `cd_dir ` | Change directory | `cd_dir /path/to/project` |
104 | | `read_dir [path] [ext]` | List directory contents | `read_dir . swift` |
105 | | `add_dir ` | Create directory | `add_dir new_folder` |
106 | | `rm_dir ` | Remove directory | `rm_dir old_folder` |
107 | | `move_dir ` | Move directory | `move_dir old_dir new_dir` |
108 |
109 | ## 📄 Xcode Document Operations
110 | | Command | Description | Example |
111 | |---------|-------------|---------|
112 | | `open_doc ` | Open in Xcode | `open_doc main.swift` |
113 | | `create_doc [content]` | Create new document | `create_doc new.swift "import Foundation"` |
114 | | `read_doc ` | Read Xcode document | `read_doc main.swift` |
115 | | `save_doc ` | Save Xcode document | `save_doc main.swift` |
116 |
117 | | `close_doc ` | Close document | `close_doc main.swift true` |
118 |
119 | ## 🔍 Code Analysis
120 | | Command | Description | Example |
121 | |---------|-------------|---------|
122 | | `snippet [start] [end]` | Extract code snippets | `snippet main.swift 10 20` |
123 | | `analyzer [start] [end]` | Analyze Swift code | `analyzer main.swift 10 50` |
124 | | `lz ` | Shorthand analyzer | `lz main.swift` |
125 |
126 | ## 🤖 AI Assistant MCP Tools
127 | | Tool | Purpose | Example |
128 | |------|---------|---------|
129 | | `mcp_xcf_xcf` | Execute XCF actions | `mcp_xcf_xcf(action="build")` |
130 | | `mcp_xcf_snippet` | Extract code snippets | `mcp_xcf_snippet(filePath="main.swift", entireFile=true)` |
131 | | `mcp_xcf_analyzer` | Analyze Swift code | `mcp_xcf_analyzer(filePath="main.swift", entireFile=true)` |
132 | | `mcp_xcf_read_file` | Read file contents | `mcp_xcf_read_file(filePath="main.swift")` |
133 | | `mcp_xcf_write_file` | Write file contents | `mcp_xcf_write_file(filePath="test.txt", content="Hello World")` |
134 |
135 | ## 🎮 Standalone Action Tools
136 | | Tool | Purpose | Example |
137 | |------|---------|---------|
138 | | `mcp_xcf_show_help` | Display help | `mcp_xcf_show_help()` |
139 | | `mcp_xcf_grant_permission` | Grant permissions | `mcp_xcf_grant_permission()` |
140 | | `mcp_xcf_run_project` | Run project | `mcp_xcf_run_project()` |
141 | | `mcp_xcf_build_project` | Build project | `mcp_xcf_build_project()` |
142 | | `mcp_xcf_show_current_project` | Show project | `mcp_xcf_show_current_project()` |
143 | | `mcp_xcf_show_env` | Show env vars | `mcp_xcf_show_env()` |
144 | | `mcp_xcf_show_folder` | Show directory | `mcp_xcf_show_folder()` |
145 | | `mcp_xcf_list_projects` | List projects | `mcp_xcf_list_projects()` |
146 | | `mcp_xcf_select_project` | Select project | `mcp_xcf_select_project(projectNumber=1)` |
147 | | `mcp_xcf_analyze_swift_code` | Analyze code | `mcp_xcf_analyze_swift_code(filePath="main.swift")` |
148 |
149 | ## 🚀 Quick Workflow Examples
150 |
151 | ### Basic Project Workflow
152 | ```
153 | use_xcf
154 | xcf pwd # Check current directory
155 | cd_dir . # Confirm directory
156 | show
157 | open 1
158 | build
159 | run
160 | ```
161 |
162 | ### Code Analysis Workflow
163 | ```
164 | use_xcf
165 | xcf pwd # Verify working directory
166 | cd_dir . # Ensure correct context
167 | current
168 | snippet main.swift
169 | lz main.swift
170 | build
171 | ```
172 |
173 | ### File Manipulation Workflow
174 | ```
175 | use_xcf
176 | xcf pwd # Check current directory
177 | cd_dir . # Confirm directory
178 | read_dir .
179 | read_file main.swift
180 | edit_file main.swift 10 15 "// Updated code"
181 | build
182 | ```
183 |
184 | ## 🔒 Security & Performance Tips
185 | - Always activate XCF before operations
186 | - Use specific file paths
187 | - Leverage smart path resolution
188 | - Implement error handling
189 | - Use code analysis before major changes
190 |
191 | ## 🔍 Directory Management Tips
192 | - Always use `xcf pwd` before operations
193 | - Use `cd_dir .` to confirm current directory
194 | - Verify path before file/directory actions
195 |
196 | ---
197 |
198 | Created by XCodeFreeze Automation - Swift Development at Light Speed! 🚀
--------------------------------------------------------------------------------
/.cursor/rules/xcf-quickref.mdc:
--------------------------------------------------------------------------------
1 | ---
2 | description: xcf Xcode MCP Server - Quick Reference
3 | globs:
4 | alwaysApply: false
5 | ---
6 | # XCF Quick Reference Guide 🚀
7 |
8 | ## 🎛️ XCF Core Actions
9 |
10 | ### Permissions & Project Management
11 | | Action | Description | Example | Output |
12 | |--------|-------------|---------|--------|
13 | | `grant` | Grant Xcode automation | `grant` | Permissions granted |
14 | | `show` | List open projects | `show` | 1. /path/to/project.xcodeproj |
15 | | `open #` | Select project | `open 1` | Project selected |
16 | | `current` | Show selected project | `current` | /path/to/current/project |
17 |
18 | ### Build & Execute
19 | | Action | Description | Example | Output |
20 | |--------|-------------|---------|--------|
21 | | `build` | Build project | `build` | 🐦📜 Built successfully |
22 | | `run` | Run project | `run` | 🐦📜 Ran successfully |
23 |
24 | ### System & Analysis
25 | | Action | Description | Example | Output |
26 | |--------|-------------|---------|--------|
27 | | `env` | Show environment | `env` | [Environment variables] |
28 | | `pwd` | Show current folder | `pwd` | /current/directory |
29 | | `analyze ` | Analyze Swift code | `analyze main.swift` | Code analysis report |
30 | | `lz ` | Quick code analysis | `lz main.swift` | Condensed analysis |
31 |
32 | ### Aliases
33 | - `pwd` = `dir` = `path`
34 | - `lz` = shorthand for `analyze`
35 |
36 | ### Quick Workflow
37 | ```bash
38 | grant # Authorize XCF
39 | show # List projects
40 | open 1 # Select project
41 | build # Compile project
42 | run # Execute project
43 | ```
44 |
45 | ### Code Analysis Workflow
46 | ```bash
47 | analyze main.swift # Detailed analysis
48 | lz main.swift # Quick analysis
49 | ```
50 |
51 | ## 📋 Core Commands
52 |
53 | ### Activation & Project Management
54 | | Command | Description | Example | Output |
55 | |---------|-------------|---------|--------|
56 | | `show` | List open Xcode projects | `show` | 1. /path/to/project.xcodeproj |
57 | | `open #` | Select project by number | `open 1` | [Selects project] |
58 | | `current` | Show current project | `current` | Current project: /path/to/project.xcodeproj |
59 |
60 | ### Build & Run
61 | | Command | Description | Example | Output |
62 | |---------|-------------|---------|--------|
63 | | `build` | Build current project | `build` | 🐦📜 Built successfully |
64 | | `run` | Run current project | `run` | 🐦📜 Ran successfully |
65 |
66 | ### System Information
67 | | Command | Description | Example | Output |
68 | |---------|-------------|---------|--------|
69 | | `env` | Show environment variables | `env` | [List of environment variables] |
70 | | `pwd` | Show current folder | `pwd` | Current folder: /path/to/folder |
71 | | `help` | Display all commands | `help` | [List of available commands] |
72 |
73 | ## 🔍 Path Resolution & Quoting
74 |
75 | ### Supported Path Types
76 | - Current directory: `file.swift`
77 | - Child directories: `src/file.swift`
78 | - Parent directory: `../file.swift`
79 | - Multiple directories up: `../../file.swift`
80 | - Full system paths: `/Users/username/project/file.swift`
81 |
82 | ### Quoting Rules
83 | - Use quotes for content with spaces
84 | - Single `'` or double `"` quotes work
85 | - Recommended for complex content
86 |
87 | ### Path Resolution Examples
88 | ```bash
89 | read_file main.swift # Current directory
90 | read_file src/utils.swift # Child directory
91 | read_file ../shared/config.swift # Parent directory
92 | write_file test.txt "Hello World" # Simple content
93 | write_file config.json '{"key": "value"}' # JSON content
94 | ```
95 |
96 | ## 🗂️ File Operations
97 | | Command | Description | Example |
98 | |---------|-------------|---------|
99 | | `read_file ` | Read file contents | `read_file main.swift` |
100 | | `write_file ` | Write to file | `write_file test.txt "Hello World"` |
101 | | `edit_file ` | Edit file lines | `edit_file main.swift 10 20 "Updated code"` |
102 | | `delete_file ` | Delete file | `delete_file temp.txt` |
103 | | `move_file ` | Move file | `move_file old.swift new.swift` |
104 |
105 | ## 📂 Directory Operations
106 | | Command | Description | Example |
107 | |---------|-------------|---------|
108 | | `cd_dir ` | Change directory | `cd_dir /path/to/project` |
109 | | `read_dir [path] [ext]` | List directory contents | `read_dir . swift` |
110 | | `add_dir ` | Create directory | `add_dir new_folder` |
111 | | `rm_dir ` | Remove directory | `rm_dir old_folder` |
112 | | `move_dir ` | Move directory | `move_dir old_dir new_dir` |
113 |
114 | ## 📄 Xcode Document Operations
115 | | Command | Description | Example |
116 | |---------|-------------|---------|
117 | | `open_doc ` | Open in Xcode | `open_doc main.swift` |
118 | | `create_doc [content]` | Create new document | `create_doc new.swift "import Foundation"` |
119 | | `read_doc ` | Read Xcode document | `read_doc main.swift` |
120 | | `save_doc ` | Save Xcode document | `save_doc main.swift` |
121 |
122 | | `close_doc ` | Close document | `close_doc main.swift true` |
123 |
124 | ## 🔍 Code Analysis
125 | | Command | Description | Example |
126 | |---------|-------------|---------|
127 | | `snippet [start] [end]` | Extract code snippets | `snippet main.swift 10 20` |
128 | | `analyzer [start] [end]` | Analyze Swift code | `analyzer main.swift 10 50` |
129 | | `lz ` | Shorthand analyzer | `lz main.swift` |
130 |
131 | ## 🤖 AI Assistant MCP Tools
132 | | Tool | Purpose | Example |
133 | |------|---------|---------|
134 | | `mcp_xcf_xcf` | Execute XCF actions | `mcp_xcf_xcf(action="build")` |
135 | | `mcp_xcf_snippet` | Extract code snippets | `mcp_xcf_snippet(filePath="main.swift", entireFile=true)` |
136 | | `mcp_xcf_analyzer` | Analyze Swift code | `mcp_xcf_analyzer(filePath="main.swift", entireFile=true)` |
137 | | `mcp_xcf_read_file` | Read file contents | `mcp_xcf_read_file(filePath="main.swift")` |
138 | | `mcp_xcf_write_file` | Write file contents | `mcp_xcf_write_file(filePath="test.txt", content="Hello World")` |
139 |
140 | ## 🎮 Standalone Action Tools
141 | | Tool | Purpose | Example |
142 | |------|---------|---------|
143 | | `mcp_xcf_show_help` | Display help | `mcp_xcf_show_help()` |
144 | | `mcp_xcf_grant_permission` | Grant permissions | `mcp_xcf_grant_permission()` |
145 | | `mcp_xcf_run_project` | Run project | `mcp_xcf_run_project()` |
146 | | `mcp_xcf_build_project` | Build project | `mcp_xcf_build_project()` |
147 | | `mcp_xcf_show_current_project` | Show project | `mcp_xcf_show_current_project()` |
148 | | `mcp_xcf_show_env` | Show env vars | `mcp_xcf_show_env()` |
149 | | `mcp_xcf_show_folder` | Show directory | `mcp_xcf_show_folder()` |
150 | | `mcp_xcf_list_projects` | List projects | `mcp_xcf_list_projects()` |
151 | | `mcp_xcf_select_project` | Select project | `mcp_xcf_select_project(projectNumber=1)` |
152 | | `mcp_xcf_analyze_swift_code` | Analyze code | `mcp_xcf_analyze_swift_code(filePath="main.swift")` |
153 |
154 | ## 🚀 Quick Workflow Examples
155 |
156 | ### Basic Project Workflow
157 | ```
158 | use_xcf
159 | xcf pwd # Check current directory
160 | cd_dir . # Confirm directory
161 | show
162 | open 1
163 | build
164 | run
165 | ```
166 |
167 | ### Code Analysis Workflow
168 | ```
169 | use_xcf
170 | cd_dir . # Ensure correct context
171 | current
172 | snippet main.swift
173 | lz main.swift
174 | build
175 | ```
176 |
177 | ### File Manipulation Workflow
178 | ```
179 | use_xcf
180 | xcf pwd # Check current directory
181 | read_dir .
182 | read_file main.swift
183 | edit_file main.swift 10 15 "// Updated code"
184 | build
185 | ```
186 |
187 | ## 🔒 Security & Performance Tips
188 | - Always activate XCF before operations
189 | - Use specific file paths
190 | - Leverage smart path resolution
191 | - Implement error handling
192 | - Use code analysis before major changes
193 |
194 | ## 🔍 Directory Management Tips
195 | - Always use `xcf pwd` before operations
196 | - OR 'cd_dir .` to confirm current directory
197 | - Verify path before file/directory actions
198 |
199 | ---
200 |
201 | Created by XCodeFreeze Automation - Swift Development at Light Speed! 🚀
202 |
--------------------------------------------------------------------------------
/xcf-ai-guide.md:
--------------------------------------------------------------------------------
1 | # XCF - AI Assistant Integration Guide
2 |
3 | ## 🤖 Overview for AI Assistants
4 |
5 | XCF (Xcode MCP Server) is a powerful Swift-based automation tool designed to streamline Xcode project management for AI assistants. This guide provides comprehensive instructions for integrating XCF into AI-powered development workflows.
6 |
7 | ## 🔧 Configuration
8 |
9 | ### MCP Server Setup
10 |
11 | Add XCF to your MCP configuration file with the following JSON:
12 |
13 | ```json
14 | {
15 | "mcpServers": {
16 | "xcf": {
17 | "type": "stdio",
18 | "command": "/Applications/xcf.app/Contents/MacOS/xcf server"
19 | }
20 | }
21 | }
22 | ```
23 |
24 | #### Configuration Locations
25 | - **Cursor**: `~/.cursor/mcp.json`
26 | - **Claude Desktop**: `~/Library/Application Support/Claude/claude_desktop_config.json`
27 |
28 | ### Advanced Project Configuration
29 |
30 | For project-specific control, use environment variables:
31 |
32 | ```json
33 | {
34 | "mcpServers": {
35 | "xcf": {
36 | "type": "stdio",
37 | "command": "/Applications/xcf.app/Contents/MacOS/xcf server",
38 | "env": {
39 | "XCODE_PROJECT_FOLDER": "/path/to/project/",
40 | "XCODE_PROJECT": "/path/to/project/project.xcodeproj"
41 | }
42 | }
43 | }
44 | }
45 | ```
46 |
47 | ## 🛠️ AI-Powered Workflow Tools
48 |
49 | ### MCP Function Tools
50 |
51 | XCF provides a rich set of tools for AI assistants:
52 |
53 | | Tool | Purpose | Example Usage |
54 | |------|---------|---------------|
55 | | `mcp_xcf_xcf` | Execute XCF actions | `mcp_xcf_xcf(action="build")` |
56 | | `mcp_xcf_snippet` | Extract code snippets | `mcp_xcf_snippet(filePath="main.swift", entireFile=true)` |
57 | | `mcp_xcf_analyzer` | Analyze Swift code | `mcp_xcf_analyzer(filePath="main.swift", entireFile=true)` |
58 | | `mcp_xcf_read_file` | Read file contents | `mcp_xcf_read_file(filePath="main.swift")` |
59 | | `mcp_xcf_write_file` | Write file contents | `mcp_xcf_write_file(filePath="test.txt", content="Hello World")` |
60 |
61 | ### Standalone Action Tools
62 |
63 | In addition to the general-purpose tools, XCF now provides dedicated tools for each action:
64 |
65 | | Tool | Purpose | Example Usage |
66 | |------|---------|---------------|
67 | | `mcp_xcf_show_help` | Display help information | `mcp_xcf_show_help()` |
68 | | `mcp_xcf_grant_permission` | Grant Xcode permissions | `mcp_xcf_grant_permission()` |
69 | | `mcp_xcf_run_project` | Run the current project | `mcp_xcf_run_project()` |
70 | | `mcp_xcf_build_project` | Build the current project | `mcp_xcf_build_project()` |
71 | | `mcp_xcf_show_current_project` | Show selected project | `mcp_xcf_show_current_project()` |
72 | | `mcp_xcf_show_env` | Display environment variables | `mcp_xcf_show_env()` |
73 | | `mcp_xcf_show_folder` | Show current directory | `mcp_xcf_show_folder()` |
74 | | `mcp_xcf_list_projects` | List all Xcode projects | `mcp_xcf_list_projects()` |
75 | | `mcp_xcf_select_project` | Select a project by number | `mcp_xcf_select_project(projectNumber=1)` |
76 | | `mcp_xcf_analyze_swift_code` | Analyze Swift code | `mcp_xcf_analyze_swift_code(filePath="main.swift")` |
77 |
78 | ### Workflow Patterns
79 |
80 | #### Basic Project Management
81 |
82 | ```
83 | # Activate XCF
84 | use_xcf
85 |
86 | # List and select project
87 | show
88 | open 1
89 |
90 | # Build and run
91 | build
92 | run
93 | ```
94 |
95 | #### Code Analysis Workflow
96 |
97 | ```
98 | # Activate XCF
99 | use_xcf
100 |
101 | # Get current project
102 | current
103 |
104 | # Extract and analyze code
105 | snippet main.swift
106 | analyzer main.swift
107 |
108 | # Implement fixes
109 | edit_file main.swift 10 20 "# Improved implementation"
110 |
111 | # Rebuild
112 | build
113 | ```
114 |
115 | ## 🔍 Smart Path Resolution
116 |
117 | XCF uses intelligent path resolution for file operations:
118 |
119 | 1. Exact path provided
120 | 2. Relative path from current directory
121 | 3. Current project directory
122 | 4. Workspace folder
123 | 5. Recursive workspace search
124 | 6. Fuzzy filename matching
125 |
126 | ### File Access Examples
127 |
128 | ```python
129 | # These are equivalent
130 | mcp_xcf_snippet(filePath="/full/path/to/file.swift")
131 | mcp_xcf_snippet(filePath="file.swift") # Smart resolution
132 | ```
133 |
134 | ## 📊 Code Analysis Capabilities
135 |
136 | The Swift code analyzer provides:
137 | - Code style checks
138 | - Complexity analysis
139 | - Unused variable detection
140 | - Magic number identification
141 | - Refactoring suggestions
142 | - Method length evaluation
143 |
144 | ### Analysis Example
145 |
146 | ```python
147 | # Analyze entire file
148 | mcp_xcf_analyzer(filePath="main.swift", entireFile=true)
149 |
150 | # Analyze specific line range
151 | mcp_xcf_analyzer(
152 | filePath="main.swift",
153 | startLine=10,
154 | endLine=50
155 | )
156 | ```
157 |
158 | ## 🔒 Security Considerations
159 |
160 | - Workspace-bound operations
161 | - Automatic access prevention outside workspace
162 | - Environment variable-based security
163 | - Safe redirection of potentially unsafe actions
164 |
165 | ## 🚀 Performance Tips
166 |
167 | - Use specific file paths when possible
168 | - Leverage smart path resolution
169 | - Utilize line-range analysis for large files
170 | - Prefer MCP function tools for programmatic interactions
171 |
172 | ## 🤝 Integration Best Practices
173 |
174 | 1. Always activate XCF before operations
175 | 2. Use environment variables for project configuration
176 | 3. Leverage smart path resolution
177 | 4. Implement error handling
178 | 5. Use code analysis before major changes
179 |
180 | ## 📋 Comprehensive Tool List
181 |
182 | Refer to the User Guide for a complete list of available tools and their detailed usage.
183 |
184 | ## 🎛️ XCF Core Actions
185 |
186 | ### Available Actions
187 |
188 | | Action | Description | Example | Purpose |
189 | |--------|-------------|---------|---------|
190 | | `grant` | Grant Xcode automation permissions | `mcp_xcf_xcf(action="grant")` | Authorize XCF to interact with Xcode |
191 | | `show` | List open projects | `mcp_xcf_xcf(action="show")` | Display available Xcode projects |
192 | | `open #` | Select project by number | `mcp_xcf_xcf(action="open 1")` | Choose a specific project to work on |
193 | | `current` | Show selected project | `mcp_xcf_xcf(action="current")` | Display the currently active project |
194 | | `build` | Build current project | `mcp_xcf_xcf(action="build")` | Compile the selected Xcode project |
195 | | `run` | Run current project | `mcp_xcf_xcf(action="run")` | Execute the current project |
196 | | `env` | Show environment variables | `mcp_xcf_xcf(action="env")` | Display system environment configuration |
197 | | `pwd` | Show current folder | `mcp_xcf_xcf(action="pwd")` | Display current working directory |
198 | | `analyze` | Analyze Swift code | `mcp_xcf_xcf(action="analyze main.swift")` | Perform code analysis on a file |
199 | | `lz` | Shorthand for analyze | `mcp_xcf_xcf(action="lz main.swift")` | Quick code analysis |
200 |
201 | ### Comprehensive Workflow Example
202 |
203 | ```
204 | # Full project lifecycle management
205 | mcp_xcf_xcf(action="grant") # Authorize XCF
206 | mcp_xcf_xcf(action="show") # List projects
207 | mcp_xcf_xcf(action="open 1") # Select first project
208 | mcp_xcf_xcf(action="current") # Confirm project
209 | mcp_xcf_xcf(action="build") # Build project
210 | mcp_xcf_xcf(action="run") # Run project
211 |
212 | # Code analysis workflow
213 | mcp_xcf_xcf(action="analyze main.swift") # Detailed analysis
214 | mcp_xcf_xcf(action="lz main.swift") # Quick analysis
215 | ```
216 |
217 | ### Environment and Context Management
218 |
219 | ```python
220 | # Check environment and context
221 | mcp_xcf_xcf(action="env") # Show environment variables
222 | mcp_xcf_xcf(action="pwd") # Show current working directory
223 | ```
224 |
225 | ### Aliases and Shortcuts
226 |
227 | Some commands have convenient aliases:
228 | - `pwd` can also use `dir` or `path`
229 | - `lz` is a quick shorthand for `analyze`
230 |
231 | ## 🔍 Smart Action Resolution
232 |
233 | XCF intelligently handles actions by:
234 | - Automatically selecting the most recent project
235 | - Providing fallback mechanisms
236 | - Offering clear, actionable feedback
237 | - Supporting relative and absolute paths
238 |
239 | ---
240 |
241 | **Pro Tip:** Always use `grant` first to ensure full XCF functionality!
242 |
243 | ---
244 |
245 | Created by XCodeFreeze Automation - Empowering AI-driven Swift development!
--------------------------------------------------------------------------------
/xcf-user-guide.md:
--------------------------------------------------------------------------------
1 | # XCF Xcode MCP Server - Comprehensive User Guide 🚀
2 |
3 | ## 🌟 Introduction
4 |
5 | XCF (Xcode MCP Server) is a powerful, Swift-native automation tool designed to revolutionize your Xcode development workflow. This guide provides an in-depth look at installation, configuration, and usage of XCF.
6 |
7 | ## 🔧 Installation & Setup
8 |
9 | ### System Requirements
10 | - macOS (latest version recommended)
11 | - Xcode installed
12 | - Basic understanding of terminal and Swift development
13 |
14 | ### Installation Steps
15 |
16 | 1. **Download XCF**
17 | - Visit the official website or GitHub repository
18 | - Download the latest XCF application
19 |
20 | 2. **Install Application**
21 | - Drag the XCF.app to your `/Applications` folder
22 | - Launch the application once to approve internet downloads
23 |
24 | 3. **Codesign (if needed)**
25 | ```bash
26 | codesign --force --deep --sign - /Applications/xcf.app
27 | ```
28 |
29 | ### Configuration
30 |
31 | #### MCP Server Configuration
32 |
33 | Add XCF to your MCP configuration file:
34 |
35 | ```json
36 | {
37 | "mcpServers": {
38 | "xcf": {
39 | "type": "stdio",
40 | "command": "/Applications/xcf.app/Contents/MacOS/xcf server"
41 | }
42 | }
43 | }
44 | ```
45 |
46 | ##### Configuration Locations
47 | - **Cursor**: `~/.cursor/mcp.json`
48 | - **Claude Desktop**: `~/Library/Application Support/Claude/claude_desktop_config.json`
49 |
50 | #### Advanced Configuration
51 |
52 | For project-specific control:
53 |
54 | ```json
55 | {
56 | "mcpServers": {
57 | "xcf": {
58 | "type": "stdio",
59 | "command": "/Applications/xcf.app/Contents/MacOS/xcf server",
60 | "env": {
61 | "XCODE_PROJECT_FOLDER": "/path/to/project/",
62 | "XCODE_PROJECT": "/path/to/project/project.xcodeproj"
63 | }
64 | }
65 | }
66 | }
67 | ```
68 |
69 | ## 🎬 Getting Started
70 |
71 | ### Activation
72 |
73 | Activate XCF mode:
74 | ```
75 | use_xcf
76 | ```
77 |
78 | ### Basic Workflow
79 |
80 | 1. List available projects
81 | ```
82 | show
83 | ```
84 |
85 | 2. Select a project
86 | ```
87 | open 1 # Opens the first project in the list
88 | ```
89 |
90 | 3. Build the project
91 | ```
92 | build
93 | ```
94 |
95 | 4. Run the project
96 | ```
97 | run
98 | ```
99 |
100 | ## 📂 File Operations
101 |
102 | ### Path Resolution and Quoting
103 |
104 | XCF supports flexible path resolution:
105 | - Current directory: `file.swift`
106 | - Child directories: `src/file.swift`
107 | - Parent directory: `../file.swift`
108 | - Multiple directories up: `../../file.swift`
109 | - Full system paths: `/Users/username/project/file.swift`
110 |
111 | **Quoting Rules:**
112 | - Use quotes for content with spaces
113 | - Can use single `'` or double `"` quotes
114 | - Recommended for complex content or paths with spaces
115 |
116 | ### Reading Files
117 | ```bash
118 | read_file main.swift # Current directory
119 | read_file src/utils.swift # Child directory
120 | read_file ../shared/config.swift # Parent directory
121 | read_file /full/path/to/main.swift # Full path
122 | ```
123 |
124 | ### Writing Files
125 | ```bash
126 | write_file test.txt "Hello World" # Simple content
127 | write_file config.json '{"key": "value"}' # JSON content
128 | write_file "../shared/types.swift" 'import Foundation' # Quoted path
129 | ```
130 |
131 | ### Editing Files
132 | ```bash
133 | edit_file main.swift 10 20 "Updated code" # Replace lines 10-20
134 | edit_file src/test.swift 5 5 "import UIKit" # Replace single line
135 | ```
136 |
137 | ### File Management
138 | ```bash
139 | delete_file temp.txt # Delete file
140 | move_file old.swift new.swift # Move/rename file
141 | ```
142 |
143 | ## 📁 Directory Operations
144 |
145 | ### Checking Current Directory
146 | ```
147 | xcf pwd # Show current working directory
148 | cd_dir . # Confirm or reset to current directory
149 | ```
150 |
151 | ### Changing Directories
152 | ```
153 | cd_dir /path/to/project # Change to specific directory
154 | ```
155 |
156 | ### Listing Contents
157 | ```
158 | read_dir . # Current directory
159 | read_dir . swift # Swift files only
160 | ```
161 |
162 | ### Directory Management
163 | - Create: `add_dir new_folder`
164 | - Remove: `rm_dir old_folder`
165 | - Move: `move_dir source_dir destination_dir`
166 |
167 | ### Best Practices
168 | 1. Always check current directory before file operations
169 | 2. Use `xcf pwd` to verify your working context
170 | 3. Use `cd_dir .` to ensure you're in the expected directory
171 |
172 | ## 📄 Xcode Document Management
173 |
174 | ### Opening Documents
175 | ```bash
176 | open_doc main.swift
177 | ```
178 |
179 | ### Creating Documents
180 | ```bash
181 | create_doc new.swift "import Foundation"
182 | ```
183 |
184 | ### Editing Documents
185 | ```bash
186 |
187 | ```
188 |
189 | ### Document Lifecycle
190 | - Read: `read_doc main.swift`
191 | - Save: `save_doc main.swift`
192 | - Close: `close_doc main.swift true # With saving`
193 |
194 | ## 🔍 Code Analysis
195 |
196 | ### Snippet Extraction
197 | ```bash
198 | snippet main.swift # Entire file
199 | snippet main.swift 10 20 # Specific lines
200 | ```
201 |
202 | ### Code Analysis
203 | ```bash
204 | analyzer main.swift # Full file analysis
205 | lz main.swift # Shorthand analysis
206 | ```
207 |
208 | ### Analysis Features
209 | - Code style checks
210 | - Complexity evaluation
211 | - Unused variable detection
212 | - Refactoring suggestions
213 | - Method length analysis
214 |
215 | ## 🤖 AI Assistant Integration
216 |
217 | ### MCP Tools
218 | - `mcp_xcf_xcf`: Execute actions
219 | - `mcp_xcf_snippet`: Extract code
220 | - `mcp_xcf_analyzer`: Code analysis
221 | - `mcp_xcf_read_file`: Read files
222 | - `mcp_xcf_write_file`: Write files
223 |
224 | ### Standalone Action Tools
225 | XCF now provides direct tools for each action, making it easier to use and discover functionality:
226 |
227 | | Tool | Description |
228 | |------|-------------|
229 | | `mcp_xcf_show_help` | Display help information about available commands |
230 | | `mcp_xcf_grant_permission` | Grant Xcode automation permissions |
231 | | `mcp_xcf_run_project` | Run the current Xcode project |
232 | | `mcp_xcf_build_project` | Build the current Xcode project |
233 | | `mcp_xcf_show_current_project` | Show information about the currently selected project |
234 | | `mcp_xcf_show_env` | Display all environment variables |
235 | | `mcp_xcf_show_folder` | Display the current working folder |
236 | | `mcp_xcf_list_projects` | List all open Xcode projects |
237 | | `mcp_xcf_select_project` | Select an Xcode project by number |
238 | | `mcp_xcf_analyze_swift_code` | Analyze Swift code for potential issues |
239 |
240 | ### AI-Optimized Workflow Example
241 | ```python
242 | # Basic workflow using standalone tools
243 | mcp_xcf_use_xcf() # Activate XCF
244 | mcp_xcf_list_projects() # List available projects
245 | mcp_xcf_select_project(projectNumber=1) # Select first project
246 | mcp_xcf_build_project() # Build the project
247 | mcp_xcf_run_project() # Run the project
248 |
249 | # Code analysis using dedicated tools
250 | mcp_xcf_show_current_project() # Check current project
251 | mcp_xcf_analyze_swift_code(filePath="main.swift") # Analyze code
252 | ```
253 |
254 | ## 🔒 Security & Best Practices
255 |
256 | ### Security Features
257 | - Workspace-bound operations
258 | - Automatic access prevention
259 | - Environment variable security
260 | - Safe action redirection
261 |
262 | ### Best Practices
263 | 1. Always activate XCF before operations
264 | 2. Use specific file paths
265 | 3. Leverage smart path resolution
266 | 4. Implement error handling
267 | 5. Analyze code before major changes
268 |
269 | ## 🚨 Troubleshooting
270 |
271 | ### Common Issues
272 | - Verify Xcode installation
273 | - Check XCF permissions
274 | - Validate configuration
275 | - Review environment variables
276 |
277 | ### Debugging Commands
278 | ```bash
279 | env # Show environment
280 | pwd # Current directory
281 | help # Available commands
282 | ```
283 |
284 | ## 🌐 Community & Support
285 |
286 | - GitHub Repository: [XCF GitHub](https://github.com/codefreezeai/xcf)
287 | - Website: [xcf.ai](https://xcf.ai)
288 | - Community: Open to contributions!
289 |
290 | ## 📋 Version Information
291 | - Check current version with `xcf version`
292 | - Stay updated with latest releases
293 |
294 | ---
295 |
296 | Created by XCodeFreeze Automation - Empowering Swift Developers Worldwide! 🚀
--------------------------------------------------------------------------------
/.cursor/rules/xcf-ai-guide.mdc:
--------------------------------------------------------------------------------
1 | ---
2 | description: xcf Xcode MCP Server - AI Guide
3 | globs:
4 | alwaysApply: false
5 | ---
6 | # XCF - AI Assistant Integration Guide
7 |
8 | ## 🤖 Overview for AI Assistants
9 |
10 | XCF (Xcode MCP Server) is a powerful Swift-based automation tool designed to streamline Xcode project management for AI assistants. This guide provides comprehensive instructions for integrating XCF into AI-powered development workflows.
11 |
12 | ## 🔧 Configuration
13 |
14 | ### MCP Server Setup
15 |
16 | Add XCF to your MCP configuration file with the following JSON:
17 |
18 | ```json
19 | {
20 | "mcpServers": {
21 | "xcf": {
22 | "type": "stdio",
23 | "command": "/Applications/xcf.app/Contents/MacOS/xcf server"
24 | }
25 | }
26 | }
27 | ```
28 |
29 | #### Configuration Locations
30 | - **Cursor**: `~/.cursor/mcp.json`
31 | - **Claude Desktop**: `~/Library/Application Support/Claude/claude_desktop_config.json`
32 |
33 | ### Advanced Project Configuration
34 |
35 | For project-specific control, use environment variables:
36 |
37 | ```json
38 | {
39 | "mcpServers": {
40 | "xcf": {
41 | "type": "stdio",
42 | "command": "/Applications/xcf.app/Contents/MacOS/xcf server",
43 | "env": {
44 | "XCODE_PROJECT_FOLDER": "/path/to/project/",
45 | "XCODE_PROJECT": "/path/to/project/project.xcodeproj"
46 | }
47 | }
48 | }
49 | }
50 | ```
51 |
52 | ## 🛠️ AI-Powered Workflow Tools
53 |
54 | ### MCP Function Tools
55 |
56 | XCF provides a rich set of tools for AI assistants:
57 |
58 | | Tool | Purpose | Example Usage |
59 | |------|---------|---------------|
60 | | `mcp_xcf_xcf` | Execute XCF actions | `mcp_xcf_xcf(action="build")` |
61 | | `mcp_xcf_snippet` | Extract code snippets | `mcp_xcf_snippet(filePath="main.swift", entireFile=true)` |
62 | | `mcp_xcf_analyzer` | Analyze Swift code | `mcp_xcf_analyzer(filePath="main.swift", entireFile=true)` |
63 | | `mcp_xcf_read_file` | Read file contents | `mcp_xcf_read_file(filePath="main.swift")` |
64 | | `mcp_xcf_write_file` | Write file contents | `mcp_xcf_write_file(filePath="test.txt", content="Hello World")` |
65 |
66 | ### Standalone Action Tools
67 |
68 | In addition to the general-purpose tools, XCF now provides dedicated tools for each action:
69 |
70 | | Tool | Purpose | Example Usage |
71 | |------|---------|---------------|
72 | | `mcp_xcf_show_help` | Display help information | `mcp_xcf_show_help()` |
73 | | `mcp_xcf_grant_permission` | Grant Xcode permissions | `mcp_xcf_grant_permission()` |
74 | | `mcp_xcf_run_project` | Run the current project | `mcp_xcf_run_project()` |
75 | | `mcp_xcf_build_project` | Build the current project | `mcp_xcf_build_project()` |
76 | | `mcp_xcf_show_current_project` | Show selected project | `mcp_xcf_show_current_project()` |
77 | | `mcp_xcf_show_env` | Display environment variables | `mcp_xcf_show_env()` |
78 | | `mcp_xcf_show_folder` | Show current directory | `mcp_xcf_show_folder()` |
79 | | `mcp_xcf_list_projects` | List all Xcode projects | `mcp_xcf_list_projects()` |
80 | | `mcp_xcf_select_project` | Select a project by number | `mcp_xcf_select_project(projectNumber=1)` |
81 | | `mcp_xcf_analyze_swift_code` | Analyze Swift code | `mcp_xcf_analyze_swift_code(filePath="main.swift")` |
82 |
83 | ### Workflow Patterns
84 |
85 | #### Basic Project Management
86 |
87 | ```
88 | # Activate XCF
89 | use_xcf
90 |
91 | # List and select project
92 | show
93 | open 1
94 |
95 | # Build and run
96 | build
97 | run
98 | ```
99 |
100 | #### Code Analysis Workflow
101 |
102 | ```
103 | # Activate XCF
104 | use_xcf
105 |
106 | # Get current project
107 | current
108 |
109 | # Extract and analyze code
110 | snippet main.swift
111 | analyzer main.swift
112 |
113 | # Implement fixes
114 | edit_file main.swift 10 20 "# Improved implementation"
115 |
116 | # Rebuild
117 | build
118 | ```
119 |
120 | ## 🔍 Smart Path Resolution
121 |
122 | XCF uses intelligent path resolution for file operations:
123 |
124 | 1. Exact path provided
125 | 2. Relative path from current directory
126 | 3. Current project directory
127 | 4. Workspace folder
128 | 5. Recursive workspace search
129 | 6. Fuzzy filename matching
130 |
131 | ### File Access Examples
132 |
133 | ```python
134 | # These are equivalent
135 | mcp_xcf_snippet(filePath="/full/path/to/file.swift")
136 | mcp_xcf_snippet(filePath="file.swift") # Smart resolution
137 | ```
138 |
139 | ## 📊 Code Analysis Capabilities
140 |
141 | The Swift code analyzer provides:
142 | - Code style checks
143 | - Complexity analysis
144 | - Unused variable detection
145 | - Magic number identification
146 | - Refactoring suggestions
147 | - Method length evaluation
148 |
149 | ### Analysis Example
150 |
151 | ```python
152 | # Analyze entire file
153 | mcp_xcf_analyzer(filePath="main.swift", entireFile=true)
154 |
155 | # Analyze specific line range
156 | mcp_xcf_analyzer(
157 | filePath="main.swift",
158 | startLine=10,
159 | endLine=50
160 | )
161 | ```
162 |
163 | ## 🔒 Security Considerations
164 |
165 | - Workspace-bound operations
166 | - Automatic access prevention outside workspace
167 | - Environment variable-based security
168 | - Safe redirection of potentially unsafe actions
169 |
170 | ## 🚀 Performance Tips
171 |
172 | - Use specific file paths when possible
173 | - Leverage smart path resolution
174 | - Utilize line-range analysis for large files
175 | - Prefer MCP function tools for programmatic interactions
176 |
177 | ## 🤝 Integration Best Practices
178 |
179 | 1. Always activate XCF before operations
180 | 2. Use environment variables for project configuration
181 | 3. Leverage smart path resolution
182 | 4. Implement error handling
183 | 5. Use code analysis before major changes
184 |
185 | ## 📋 Comprehensive Tool List
186 |
187 | Refer to the User Guide for a complete list of available tools and their detailed usage.
188 |
189 | ## 🎛️ XCF Core Actions
190 |
191 | ### Available Actions
192 |
193 | | Action | Description | Example | Purpose |
194 | |--------|-------------|---------|---------|
195 | | `grant` | Grant Xcode automation permissions | `mcp_xcf_xcf(action="grant")` | Authorize XCF to interact with Xcode |
196 | | `show` | List open projects | `mcp_xcf_xcf(action="show")` | Display available Xcode projects |
197 | | `open #` | Select project by number | `mcp_xcf_xcf(action="open 1")` | Choose a specific project to work on |
198 | | `current` | Show selected project | `mcp_xcf_xcf(action="current")` | Display the currently active project |
199 | | `build` | Build current project | `mcp_xcf_xcf(action="build")` | Compile the selected Xcode project |
200 | | `run` | Run current project | `mcp_xcf_xcf(action="run")` | Execute the current project |
201 | | `env` | Show environment variables | `mcp_xcf_xcf(action="env")` | Display system environment configuration |
202 | | `pwd` | Show current folder | `mcp_xcf_xcf(action="pwd")` | Display current working directory |
203 | | `analyze` | Analyze Swift code | `mcp_xcf_xcf(action="analyze main.swift")` | Perform code analysis on a file |
204 | | `lz` | Shorthand for analyze | `mcp_xcf_xcf(action="lz main.swift")` | Quick code analysis |
205 |
206 | ### Comprehensive Workflow Example
207 |
208 | ```
209 | # Full project lifecycle management
210 | mcp_xcf_xcf(action="grant") # Authorize XCF
211 | mcp_xcf_xcf(action="show") # List projects
212 | mcp_xcf_xcf(action="open 1") # Select first project
213 | mcp_xcf_xcf(action="current") # Confirm project
214 | mcp_xcf_xcf(action="build") # Build project
215 | mcp_xcf_xcf(action="run") # Run project
216 |
217 | # Code analysis workflow
218 | mcp_xcf_xcf(action="analyze main.swift") # Detailed analysis
219 | mcp_xcf_xcf(action="lz main.swift") # Quick analysis
220 | ```
221 |
222 | ### Environment and Context Management
223 |
224 | ```python
225 | # Check environment and context
226 | mcp_xcf_xcf(action="env") # Show environment variables
227 | mcp_xcf_xcf(action="pwd") # Show current working directory
228 | ```
229 |
230 | ### Aliases and Shortcuts
231 |
232 | Some commands have convenient aliases:
233 | - `pwd` can also use `dir` or `path`
234 | - `lz` is a quick shorthand for `analyze`
235 |
236 | ## 🔍 Smart Action Resolution
237 |
238 | XCF intelligently handles actions by:
239 | - Automatically selecting the most recent project
240 | - Providing fallback mechanisms
241 | - Offering clear, actionable feedback
242 | - Supporting relative and absolute paths
243 |
244 | ---
245 |
246 | **Pro Tip:** Always use `grant` first to ensure full XCF functionality!
247 |
248 | ---
249 |
250 | Created by XCodeFreeze Automation - Empowering AI-driven Swift development!
251 |
--------------------------------------------------------------------------------
/.cursor/rules/xcf-user-guide.mdc:
--------------------------------------------------------------------------------
1 | ---
2 | description: xcf Xcode MCP Server - User Guide
3 | globs:
4 | alwaysApply: false
5 | ---
6 | # XCF Xcode MCP Server - Comprehensive User Guide 🚀
7 |
8 | ## 🌟 Introduction
9 |
10 | XCF (Xcode MCP Server) is a powerful, Swift-native automation tool designed to revolutionize your Xcode development workflow. This guide provides an in-depth look at installation, configuration, and usage of XCF.
11 |
12 | ## 🔧 Installation & Setup
13 |
14 | ### System Requirements
15 | - macOS (latest version recommended)
16 | - Xcode installed
17 | - Basic understanding of terminal and Swift development
18 |
19 | ### Installation Steps
20 |
21 | 1. **Download XCF**
22 | - Visit the official website or GitHub repository
23 | - Download the latest XCF application
24 |
25 | 2. **Install Application**
26 | - Drag the XCF.app to your `/Applications` folder
27 | - Launch the application once to approve internet downloads
28 |
29 | 3. **Codesign (if needed)**
30 | ```bash
31 | codesign --force --deep --sign - /Applications/xcf.app
32 | ```
33 |
34 | ### Configuration
35 |
36 | #### MCP Server Configuration
37 |
38 | Add XCF to your MCP configuration file:
39 |
40 | ```json
41 | {
42 | "mcpServers": {
43 | "xcf": {
44 | "type": "stdio",
45 | "command": "/Applications/xcf.app/Contents/MacOS/xcf server"
46 | }
47 | }
48 | }
49 | ```
50 |
51 | ##### Configuration Locations
52 | - **Cursor**: `~/.cursor/mcp.json`
53 | - **Claude Desktop**: `~/Library/Application Support/Claude/claude_desktop_config.json`
54 |
55 | #### Advanced Configuration
56 |
57 | For project-specific control:
58 |
59 | ```json
60 | {
61 | "mcpServers": {
62 | "xcf": {
63 | "type": "stdio",
64 | "command": "/Applications/xcf.app/Contents/MacOS/xcf server",
65 | "env": {
66 | "XCODE_PROJECT_FOLDER": "/path/to/project/",
67 | "XCODE_PROJECT": "/path/to/project/project.xcodeproj"
68 | }
69 | }
70 | }
71 | }
72 | ```
73 |
74 | ## 🎬 Getting Started
75 |
76 | ### Activation
77 |
78 | Activate XCF mode:
79 | ```
80 | use_xcf
81 | ```
82 |
83 | ### Basic Workflow
84 |
85 | 1. List available projects
86 | ```
87 | show
88 | ```
89 |
90 | 2. Select a project
91 | ```
92 | open 1 # Opens the first project in the list
93 | ```
94 |
95 | 3. Build the project
96 | ```
97 | build
98 | ```
99 |
100 | 4. Run the project
101 | ```
102 | run
103 | ```
104 |
105 | ## 📂 File Operations
106 |
107 | ### Path Resolution and Quoting
108 |
109 | XCF supports flexible path resolution:
110 | - Current directory: `file.swift`
111 | - Child directories: `src/file.swift`
112 | - Parent directory: `../file.swift`
113 | - Multiple directories up: `../../file.swift`
114 | - Full system paths: `/Users/username/project/file.swift`
115 |
116 | **Quoting Rules:**
117 | - Use quotes for content with spaces
118 | - Can use single `'` or double `"` quotes
119 | - Recommended for complex content or paths with spaces
120 |
121 | ### Reading Files
122 | ```bash
123 | read_file main.swift # Current directory
124 | read_file src/utils.swift # Child directory
125 | read_file ../shared/config.swift # Parent directory
126 | read_file /full/path/to/main.swift # Full path
127 | ```
128 |
129 | ### Writing Files
130 | ```bash
131 | write_file test.txt "Hello World" # Simple content
132 | write_file config.json '{"key": "value"}' # JSON content
133 | write_file "../shared/types.swift" 'import Foundation' # Quoted path
134 | ```
135 |
136 | ### Editing Files
137 | ```bash
138 | edit_file main.swift 10 20 "Updated code" # Replace lines 10-20
139 | edit_file src/test.swift 5 5 "import UIKit" # Replace single line
140 | ```
141 |
142 | ### File Management
143 | ```bash
144 | delete_file temp.txt # Delete file
145 | move_file old.swift new.swift # Move/rename file
146 | ```
147 |
148 | ## 📁 Directory Operations
149 |
150 | ### Checking Current Directory
151 | ```
152 | xcf pwd # Show current working directory
153 | cd_dir . # Confirm or reset to current directory
154 | ```
155 |
156 | ### Changing Directories
157 | ```
158 | cd_dir /path/to/project # Change to specific directory
159 | ```
160 |
161 | ### Listing Contents
162 | ```
163 | read_dir . # Current directory
164 | read_dir . swift # Swift files only
165 | ```
166 |
167 | ### Directory Management
168 | - Create: `add_dir new_folder`
169 | - Remove: `rm_dir old_folder`
170 | - Move: `move_dir source_dir destination_dir`
171 |
172 | ### Best Practices
173 | 1. Always check current directory before file operations
174 | 2. Use `xcf pwd` to verify your working context
175 | 3. Use `cd_dir .` to ensure you're in the expected directory
176 |
177 | ## 📄 Xcode Document Management
178 |
179 | ### Opening Documents
180 | ```bash
181 | open_doc main.swift
182 | ```
183 |
184 | ### Creating Documents
185 | ```bash
186 | create_doc new.swift "import Foundation"
187 | ```
188 |
189 | ### Editing Documents
190 | ```bash
191 |
192 | ```
193 |
194 | ### Document Lifecycle
195 | - Read: `read_doc main.swift`
196 | - Save: `save_doc main.swift`
197 | - Close: `close_doc main.swift true # With saving`
198 |
199 | ## 🔍 Code Analysis
200 |
201 | ### Snippet Extraction
202 | ```bash
203 | snippet main.swift # Entire file
204 | snippet main.swift 10 20 # Specific lines
205 | ```
206 |
207 | ### Code Analysis
208 | ```bash
209 | analyzer main.swift # Full file analysis
210 | lz main.swift # Shorthand analysis
211 | ```
212 |
213 | ### Analysis Features
214 | - Code style checks
215 | - Complexity evaluation
216 | - Unused variable detection
217 | - Refactoring suggestions
218 | - Method length analysis
219 |
220 | ## 🤖 AI Assistant Integration
221 |
222 | ### MCP Tools
223 | - `mcp_xcf_xcf`: Execute actions
224 | - `mcp_xcf_snippet`: Extract code
225 | - `mcp_xcf_analyzer`: Code analysis
226 | - `mcp_xcf_read_file`: Read files
227 | - `mcp_xcf_write_file`: Write files
228 |
229 | ### Standalone Action Tools
230 | XCF now provides direct tools for each action, making it easier to use and discover functionality:
231 |
232 | | Tool | Description |
233 | |------|-------------|
234 | | `mcp_xcf_show_help` | Display help information about available commands |
235 | | `mcp_xcf_grant_permission` | Grant Xcode automation permissions |
236 | | `mcp_xcf_run_project` | Run the current Xcode project |
237 | | `mcp_xcf_build_project` | Build the current Xcode project |
238 | | `mcp_xcf_show_current_project` | Show information about the currently selected project |
239 | | `mcp_xcf_show_env` | Display all environment variables |
240 | | `mcp_xcf_show_folder` | Display the current working folder |
241 | | `mcp_xcf_list_projects` | List all open Xcode projects |
242 | | `mcp_xcf_select_project` | Select an Xcode project by number |
243 | | `mcp_xcf_analyze_swift_code` | Analyze Swift code for potential issues |
244 |
245 | ### AI-Optimized Workflow Example
246 | ```python
247 | # Basic workflow using standalone tools
248 | mcp_xcf_use_xcf() # Activate XCF
249 | mcp_xcf_list_projects() # List available projects
250 | mcp_xcf_select_project(projectNumber=1) # Select first project
251 | mcp_xcf_build_project() # Build the project
252 | mcp_xcf_run_project() # Run the project
253 |
254 | # Code analysis using dedicated tools
255 | mcp_xcf_show_current_project() # Check current project
256 | mcp_xcf_analyze_swift_code(filePath="main.swift") # Analyze code
257 | ```
258 |
259 | ## 🔒 Security & Best Practices
260 |
261 | ### Security Features
262 | - Workspace-bound operations
263 | - Automatic access prevention
264 | - Environment variable security
265 | - Safe action redirection
266 |
267 | ### Best Practices
268 | 1. Always activate XCF before operations
269 | 2. Use specific file paths
270 | 3. Leverage smart path resolution
271 | 4. Implement error handling
272 | 5. Analyze code before major changes
273 |
274 | ## 🚨 Troubleshooting
275 |
276 | ### Common Issues
277 | - Verify Xcode installation
278 | - Check XCF permissions
279 | - Validate configuration
280 | - Review environment variables
281 |
282 | ### Debugging Commands
283 | ```bash
284 | env # Show environment
285 | pwd # Current directory
286 | help # Available commands
287 | ```
288 |
289 | ## 🌐 Community & Support
290 |
291 | - GitHub Repository: [XCF GitHub](mdc:https:/github.com/codefreezeai/xcf)
292 | - Website: [xcf.ai](mdc:https:/xcf.ai)
293 | - Community: Open to contributions!
294 |
295 | ## 📋 Version Information
296 | - Check current version with `xcf version`
297 | - Stay updated with latest releases
298 |
299 | ---
300 |
301 | Created by XCodeFreeze Automation - Empowering Swift Developers Worldwide! 🚀
302 |
--------------------------------------------------------------------------------
/xcf/Mcp/File/XcfFuzzyLogicService.swift:
--------------------------------------------------------------------------------
1 | //
2 | // XcfFuzzyLogicService.swift
3 | // xcf
4 | //
5 | // Created by Todd Bruss on 5/4/25.
6 | //
7 |
8 | import Foundation
9 |
10 | /// A service that provides fuzzy matching and file finding capabilities
11 | struct FuzzyLogicService {
12 | // MARK: - Public Interface
13 |
14 | /// Expands a path handling both absolute paths and relative paths
15 | /// - Parameters:
16 | /// - path: The path to expand
17 | /// - projectDir: The project directory to use for relative paths
18 | /// - Returns: The expanded path
19 | static func expandPath(_ path: String, relativeTo projectDir: String) -> String {
20 | // For absolute paths
21 | if path.hasPrefix("/") || path.hasPrefix("~") {
22 | return (path as NSString).expandingTildeInPath
23 | }
24 |
25 | // For relative paths
26 | return (projectDir as NSString).appendingPathComponent(path)
27 | }
28 |
29 | /// Resolves any path (file or directory) using consistent rules
30 | /// - Parameter path: The path to resolve
31 | /// - Returns: A tuple containing the resolved path and any warning messages
32 | static func resolvePath(_ path: String, isDirectory: Bool = false) -> (path: String, warning: String) {
33 | // Get and verify the project directory
34 | guard let projectDir = XcfXcodeProjectManager.shared.currentFolder else {
35 | return (path, "No current project directory set")
36 | }
37 |
38 | // For "." or empty path, use project directory directly
39 | if path.isEmpty || path == "." {
40 | return (projectDir, "")
41 | }
42 |
43 | // For ".." path
44 | if path == ".." {
45 | return ((projectDir as NSString).deletingLastPathComponent, "")
46 | }
47 |
48 | // Expand the path
49 | let expandedPath = expandPath(path, relativeTo: projectDir)
50 |
51 | // Validate the path if needed
52 | var isDir: ObjCBool = ObjCBool(isDirectory)
53 | if FileManager.default.fileExists(atPath: expandedPath, isDirectory: &isDir) {
54 | if isDirectory == isDir.boolValue {
55 | return (expandedPath, "")
56 | }
57 | }
58 |
59 | // Even if file doesn't exist, return the expanded path for creation
60 | return (expandedPath, "")
61 | }
62 |
63 | /// Resolves a file path using consistent rules
64 | /// - Parameter filePath: The file path to resolve
65 | /// - Returns: A tuple containing the resolved path and any warning messages
66 | static func resolveFilePath(_ filePath: String) -> (path: String, warning: String) {
67 | return resolvePath(filePath, isDirectory: false)
68 | }
69 |
70 | /// Resolves a directory path using consistent rules
71 | /// - Parameter dirPath: The directory path to resolve
72 | /// - Returns: A tuple containing the resolved path and any warning messages
73 | static func resolveDirectoryPath(_ dirPath: String) -> (path: String, warning: String) {
74 | return resolvePath(dirPath, isDirectory: true)
75 | }
76 |
77 | // MARK: - Private Methods
78 |
79 | /// Search recursively in a directory for a file
80 | private static func searchRecursively(in directory: String, forFile filename: String, currentDepth: Int = 0, maxDepth: Int, duplicates: inout [String]) {
81 | guard currentDepth <= maxDepth else { return }
82 |
83 | let fileManager = FileManager.default
84 | guard let items = try? fileManager.contentsOfDirectory(atPath: directory) else { return }
85 |
86 | for item in items {
87 | let itemPath = (directory as NSString).appendingPathComponent(item)
88 |
89 | // Check if this is the file we're looking for
90 | if item == filename && fileManager.fileExists(atPath: itemPath) {
91 | duplicates.append(itemPath)
92 | }
93 |
94 | // If it's a directory, search recursively
95 | let isDirectory = (try? fileManager.attributesOfItem(atPath: itemPath)[.type] as? FileAttributeType) == .typeDirectory
96 | if isDirectory {
97 | searchRecursively(in: itemPath, forFile: filename, currentDepth: currentDepth + 1, maxDepth: maxDepth, duplicates: &duplicates)
98 | }
99 | }
100 | }
101 |
102 | /// Find the best fuzzy match for a filename across all available directories
103 | private static func findBestFuzzyMatch(for filename: String) -> (path: String, score: Double)? {
104 | var bestMatch: (path: String, score: Double)? = nil
105 | let directories = getSearchDirectories()
106 |
107 | for directory in directories {
108 | if let match = searchDirectoryForFuzzyMatch(filename, in: directory) {
109 | if bestMatch == nil || match.score > bestMatch!.score {
110 | bestMatch = match
111 | }
112 | }
113 | }
114 |
115 | // Only return matches above 70% similarity
116 | return bestMatch?.score ?? 0 > 0.7 ? bestMatch : nil
117 | }
118 |
119 | /// Get all directories to search in
120 | private static func getSearchDirectories() -> Set {
121 | var directories = Set()
122 |
123 | // Add current folder from project manager
124 | if let currentFolder = XcfXcodeProjectManager.shared.currentFolder {
125 | directories.insert(currentFolder)
126 | }
127 |
128 | // Add project directory
129 | if let projectPath = XcfXcodeProjectManager.shared.currentProject {
130 | let projectDir = (projectPath as NSString).deletingLastPathComponent
131 | directories.insert(projectDir)
132 | // Also add parent directory
133 | directories.insert((projectDir as NSString).deletingLastPathComponent)
134 | }
135 |
136 | return directories
137 | }
138 |
139 | /// Search a directory for fuzzy matches
140 | private static func searchDirectoryForFuzzyMatch(_ filename: String, in directory: String) -> (path: String, score: Double)? {
141 | var bestMatch: (path: String, score: Double)? = nil
142 |
143 | func searchDirectory(_ dir: String, maxDepth: Int = 2, currentDepth: Int = 0) {
144 | guard currentDepth <= maxDepth else { return }
145 |
146 | let fileManager = FileManager.default
147 | guard let items = try? fileManager.contentsOfDirectory(atPath: dir) else { return }
148 |
149 | for item in items {
150 | let itemPath = (dir as NSString).appendingPathComponent(item)
151 |
152 | // Check similarity for files
153 | let isDirectory = (try? fileManager.attributesOfItem(atPath: itemPath)[.type] as? FileAttributeType) == .typeDirectory
154 | if !isDirectory {
155 | let similarity = calculateStringSimilarity(filename, item)
156 | if bestMatch == nil || similarity > bestMatch!.score {
157 | bestMatch = (itemPath, similarity)
158 | }
159 | } else if currentDepth < maxDepth {
160 | // Recurse into subdirectories
161 | searchDirectory(itemPath, maxDepth: maxDepth, currentDepth: currentDepth + 1)
162 | }
163 | }
164 | }
165 |
166 | searchDirectory(directory)
167 | return bestMatch
168 | }
169 |
170 | /// Calculate string similarity using Levenshtein distance
171 | private static func calculateStringSimilarity(_ a: String, _ b: String) -> Double {
172 | let a = Array(a.lowercased())
173 | let b = Array(b.lowercased())
174 |
175 | let len_a = a.count
176 | let len_b = b.count
177 |
178 | // Special cases
179 | if len_a == 0 { return len_b == 0 ? 1.0 : 0.0 }
180 | if len_b == 0 { return 0.0 }
181 |
182 | // Initialize the distance matrix
183 | var matrix = [[Int]](repeating: [Int](repeating: 0, count: len_b + 1), count: len_a + 1)
184 |
185 | // Set the first row and column
186 | for i in 0...len_a {
187 | matrix[i][0] = i
188 | }
189 | for j in 0...len_b {
190 | matrix[0][j] = j
191 | }
192 |
193 | // Fill the matrix
194 | for i in 1...len_a {
195 | for j in 1...len_b {
196 | let cost = a[i-1] == b[j-1] ? 0 : 1
197 | matrix[i][j] = min(
198 | matrix[i-1][j] + 1, // deletion
199 | matrix[i][j-1] + 1, // insertion
200 | matrix[i-1][j-1] + cost // substitution
201 | )
202 | }
203 | }
204 |
205 | // Calculate normalized similarity (1 - normalized distance)
206 | let maxLen = max(len_a, len_b)
207 | let distance = Double(matrix[len_a][len_b])
208 | return 1.0 - (distance / Double(maxLen))
209 | }
210 | }
211 |
--------------------------------------------------------------------------------
/xcf/Mcp/File/XcfSecurityManager.swift:
--------------------------------------------------------------------------------
1 | //
2 | // XcfSecurityManager.swift
3 | // xcf
4 | //
5 | // Created by Todd Bruss on 5/7/25.
6 | //
7 |
8 | import Foundation
9 |
10 | /// Manages security checks for file operations
11 | class SecurityManager {
12 | // Singleton instance
13 | static let shared = SecurityManager()
14 |
15 | private init() {}
16 |
17 | /// The user's home directory
18 | private var homeDirectory: String {
19 | return FileManager.default.homeDirectoryForCurrentUser.path
20 | }
21 |
22 | /// The current working directory
23 | private var currentWorkingDirectory: String {
24 | return FileManager.default.currentDirectoryPath
25 | }
26 |
27 | /// Check if a path is within the user's home directory or current working directory
28 | /// - Parameter path: The path to check
29 | /// - Returns: A tuple containing (isAllowed, resolvedPath, errorMessage)
30 | func isPathAllowed(_ path: String) -> (allowed: Bool, resolvedPath: String, error: String?) {
31 | // First try to resolve the path using FileFinder for fuzzy matching
32 | let (resolvedPath, _) = FileFinder.resolveFilePath(path)
33 | let standardizedPath = (resolvedPath as NSString).standardizingPath
34 |
35 | // Always allow access to home directory itself
36 | if standardizedPath == homeDirectory {
37 | return (true, standardizedPath, nil)
38 | }
39 |
40 | // Check if path is within home directory
41 | if standardizedPath.hasPrefix(homeDirectory) {
42 | return (true, standardizedPath, nil)
43 | }
44 |
45 | // Check if it's a relative path or in current working directory
46 | let currentDir = currentWorkingDirectory
47 | if currentDir.hasPrefix(homeDirectory) {
48 | // If the current directory is within home directory, try relative path resolution
49 | let relativePath = (currentDir as NSString).appendingPathComponent(path)
50 | let standardizedRelativePath = (relativePath as NSString).standardizingPath
51 |
52 | if standardizedRelativePath.hasPrefix(homeDirectory) {
53 | return (true, standardizedRelativePath, nil)
54 | }
55 |
56 | // Try fuzzy matching within current directory
57 | if let fuzzyMatch = findFuzzyMatch(for: path, in: currentDir) {
58 | let standardizedFuzzyPath = (fuzzyMatch as NSString).standardizingPath
59 | if standardizedFuzzyPath.hasPrefix(homeDirectory) {
60 | return (true, standardizedFuzzyPath, nil)
61 | }
62 | }
63 | }
64 |
65 | return (false, standardizedPath, "Security Error: Access denied. Operations are restricted to your home directory (\(homeDirectory))")
66 | }
67 |
68 | /// Find a fuzzy match for a path in the specified directory
69 | /// - Parameters:
70 | /// - path: The path to find a match for
71 | /// - directory: The directory to search in
72 | /// - Returns: The matched path if found, nil otherwise
73 | private func findFuzzyMatch(for path: String, in directory: String) -> String? {
74 | let fileManager = FileManager.default
75 | guard let contents = try? fileManager.contentsOfDirectory(atPath: directory) else {
76 | return nil
77 | }
78 |
79 | // Clean up the path by trimming whitespace and normalizing separators
80 | let cleanPath = path.trimmingCharacters(in: .whitespaces)
81 | .replacingOccurrences(of: "\\", with: "/")
82 | let searchPath = (cleanPath as NSString).lastPathComponent
83 |
84 | // First try exact match (case-sensitive)
85 | if contents.contains(searchPath) {
86 | return (directory as NSString).appendingPathComponent(searchPath)
87 | }
88 |
89 | // Try case-insensitive exact match
90 | let lowercasePath = searchPath.lowercased()
91 | if let match = contents.first(where: { $0.lowercased() == lowercasePath }) {
92 | return (directory as NSString).appendingPathComponent(match)
93 | }
94 |
95 | // Try prefix match (case-insensitive)
96 | if let match = contents.first(where: { $0.lowercased().hasPrefix(lowercasePath) }) {
97 | return (directory as NSString).appendingPathComponent(match)
98 | }
99 |
100 | // Try contains match (case-insensitive)
101 | if let match = contents.first(where: { $0.lowercased().contains(lowercasePath) }) {
102 | return (directory as NSString).appendingPathComponent(match)
103 | }
104 |
105 | // Try matching by removing common file extensions
106 | let pathWithoutExt = (searchPath as NSString).deletingPathExtension.lowercased()
107 | if let match = contents.first(where: {
108 | let itemWithoutExt = ($0 as NSString).deletingPathExtension.lowercased()
109 | return itemWithoutExt == pathWithoutExt ||
110 | itemWithoutExt.hasPrefix(pathWithoutExt) ||
111 | itemWithoutExt.contains(pathWithoutExt)
112 | }) {
113 | return (directory as NSString).appendingPathComponent(match)
114 | }
115 |
116 | // Try Levenshtein distance for close matches
117 | let threshold = min(3, searchPath.count / 3) // Allow 1 error per 3 characters, max 3
118 | if threshold > 0 {
119 | var bestMatch: String? = nil
120 | var bestDistance = Int.max
121 |
122 | for item in contents {
123 | let itemWithoutExt = (item as NSString).deletingPathExtension.lowercased()
124 | let searchWithoutExt = pathWithoutExt
125 |
126 | let distance = levenshteinDistance(itemWithoutExt, searchWithoutExt)
127 | if distance < bestDistance && distance <= threshold {
128 | bestDistance = distance
129 | bestMatch = item
130 | }
131 | }
132 |
133 | if let match = bestMatch {
134 | return (directory as NSString).appendingPathComponent(match)
135 | }
136 | }
137 |
138 | return nil
139 | }
140 |
141 | /// Calculate Levenshtein distance between two strings
142 | private func levenshteinDistance(_ s1: String, _ s2: String) -> Int {
143 | let s1Array = Array(s1)
144 | let s2Array = Array(s2)
145 | let m = s1Array.count
146 | let n = s2Array.count
147 |
148 | var matrix = Array(repeating: Array(repeating: 0, count: n + 1), count: m + 1)
149 |
150 | // Initialize first row and column
151 | for i in 0...m {
152 | matrix[i][0] = i
153 | }
154 | for j in 0...n {
155 | matrix[0][j] = j
156 | }
157 |
158 | // Fill in the rest of the matrix
159 | for i in 1...m {
160 | for j in 1...n {
161 | if s1Array[i-1] == s2Array[j-1] {
162 | matrix[i][j] = matrix[i-1][j-1]
163 | } else {
164 | matrix[i][j] = min(
165 | matrix[i-1][j] + 1, // deletion
166 | matrix[i][j-1] + 1, // insertion
167 | matrix[i-1][j-1] + 1 // substitution
168 | )
169 | }
170 | }
171 | }
172 |
173 | return matrix[m][n]
174 | }
175 |
176 | /// Check if a directory operation is allowed
177 | /// - Parameters:
178 | /// - path: The directory path to check
179 | /// - operation: The operation being performed (for error messages)
180 | /// - Returns: A tuple containing (isAllowed, resolvedPath, errorMessage)
181 | func isDirectoryOperationAllowed(_ path: String, operation: String) -> (allowed: Bool, resolvedPath: String, error: String?) {
182 | return isPathAllowed(path)
183 | }
184 |
185 | /// Check if a file operation is allowed
186 | /// - Parameters:
187 | /// - path: The file path to check
188 | /// - operation: The operation being performed (for error messages)
189 | /// - Returns: A tuple containing (isAllowed, resolvedPath, errorMessage)
190 | func isFileOperationAllowed(_ path: String, operation: String) -> (allowed: Bool, resolvedPath: String, error: String?) {
191 | return isPathAllowed(path)
192 | }
193 |
194 | /// Resolve and validate a path for security
195 | /// - Parameter path: The path to resolve and validate
196 | /// - Returns: A tuple containing (resolvedPath, errorMessage)
197 | func resolveAndValidatePath(_ path: String) -> (resolvedPath: String?, error: String?) {
198 | // First use FileFinder to resolve the path
199 | let (resolvedPath, _) = FileFinder.resolveFilePath(path)
200 |
201 | // Then check if the resolved path is allowed
202 | let (allowed, resolvedPath2, error) = isPathAllowed(resolvedPath)
203 | if !allowed {
204 | return (nil, error)
205 | }
206 |
207 | return (resolvedPath2, nil)
208 | }
209 | }
210 |
--------------------------------------------------------------------------------
/xcf/Mcp/ServerConfig/XcfHelpText.swift:
--------------------------------------------------------------------------------
1 | //
2 | // XcfHelpText.swift
3 | // xcf
4 | //
5 | // Created by Todd Bruss on 5/18/25.
6 | //
7 |
8 | import Foundation
9 |
10 | // Define help text
11 | struct HelpText {
12 | // Quick help for xcf actions only
13 | static let basic = """
14 | XCF Actions:
15 | grant - Grant Xcode automation permissions
16 | show - List open projects
17 | open # - Select project by number
18 | current - Show selected project
19 | build - Build current project
20 | run - Run current project
21 | env - Show environment variables
22 | pwd - Show current folder (aliases: dir, path)
23 | analyze - Analyze Swift code
24 | lz - Short for analyze
25 |
26 | Examples:
27 | xcf show
28 | xcf open 1
29 | xcf current
30 | xcf build
31 | xcf run
32 | xcf analyze main.swift
33 | xcf lz main.swift
34 | """
35 |
36 | // Regular help with common examples
37 | static let detailed = """
38 | Tool Commands:
39 |
40 | File Tools:
41 | read_file
42 | Read content from a file
43 | Example: read_file main.swift
44 | Example: read_file src/utils.swift
45 | Example: read_file ../shared/config.swift
46 | Example: read_file ../../other/project/file.swift
47 | Example: read_file /Users/username/Projects/app/main.swift
48 |
49 | write_file
50 | Write content to a file
51 | Example: write_file test.txt "Hello World"
52 | Example: write_file src/config.json '{"key": "value"}'
53 | Example: write_file ../shared/types.swift 'import Foundation'
54 |
55 | edit_file
56 | Edit specific lines in a file
57 | Example: edit_file main.swift 10 20 "new content"
58 | Example: edit_file src/test.swift 5 5 "import UIKit"
59 | Example: edit_file ../shared/types.swift 1 10 "new code"
60 |
61 | delete_file
62 | Delete a file
63 | Example: delete_file test.txt
64 | Example: delete_file src/old/backup.swift
65 | Example: delete_file ../backup/old.swift
66 |
67 | Directory Tools:
68 | cd_dir
69 | Change directory
70 | Example: cd_dir .
71 | Example: cd_dir src
72 | Example: cd_dir ..
73 | Example: cd_dir ../shared
74 |
75 | read_dir [path] [extension]
76 | List directory contents
77 | Example: read_dir .
78 | Example: read_dir src
79 | Example: read_dir ../shared
80 | Example: read_dir src swift
81 |
82 | add_dir
83 | Create directory
84 | Example: add_dir utils
85 | Example: add_dir src/models
86 | Example: add_dir ../shared/types
87 |
88 | rm_dir
89 | Remove directory
90 | Example: rm_dir temp
91 | Example: rm_dir src/cache
92 | Example: rm_dir ../old
93 |
94 | Xcode Tools:
95 | open_doc
96 | Open document in Xcode
97 | Example: open_doc main.swift
98 | Example: open_doc src/views/main.swift
99 | Example: open_doc ../shared/helpers.swift
100 |
101 | create_doc [content]
102 | Create new Xcode document
103 | Example: create_doc test.swift
104 | Example: create_doc src/models/user.swift
105 | Example: create_doc ../shared/types.swift
106 |
107 | read_doc
108 | Read Xcode document
109 | Example: read_doc main.swift
110 | Example: read_doc src/views/main.swift
111 | Example: read_doc ../shared/helpers.swift
112 |
113 | save_doc
114 | Save Xcode document
115 | Example: save_doc main.swift
116 | Example: save_doc src/views/main.swift
117 | Example: save_doc ../shared/helpers.swift
118 |
119 |
120 |
121 | Analysis Tools:
122 | snippet [start] [end]
123 | Extract code snippets
124 | Example: snippet main.swift
125 | Example: snippet src/utils.swift
126 | Example: snippet ../shared/types.swift
127 | Example: snippet main.swift 10 20
128 |
129 | analyzer [start] [end]
130 | Analyze Swift code
131 | Example: analyzer main.swift
132 | Example: analyzer src/models/user.swift
133 | Example: analyzer ../shared/types.swift
134 | Example: analyzer main.swift 10 20
135 |
136 | Notes:
137 | - All paths can be:
138 | - In current directory: file.swift
139 | - In child directories: src/file.swift
140 | - In parent directory: ../file.swift
141 | - Multiple directories up: ../../file.swift
142 | - Full system paths: /Users/username/project/file.swift
143 | - Content with spaces should be quoted
144 | - Use either ' or " for quoting content
145 | """
146 |
147 | // Super detailed help with all tools and examples
148 | static let toolsReference = """
149 | MCP Tool Reference
150 |
151 | Core Tools:
152 | xcf
153 | Description: Execute an xcf action or command
154 | Required Parameters:
155 | - action: string (The xcf action to execute)
156 | Example: mcp_xcf_xcf action="build"
157 | Example: mcp_xcf_xcf action="run"
158 |
159 | list
160 | Description: Lists all available tools on this server
161 | Parameters: none (type: string)
162 | Example: mcp_xcf_list
163 |
164 | xcf_help
165 | Description: Quick help for xcf commands
166 | Parameters: none (type: string)
167 | Example: mcp_xcf_xcf action="xcf_help"
168 |
169 | help
170 | Description: Regular help with common examples
171 | Parameters: none (type: string)
172 | Example: mcp_xcf_xcf action="help"
173 |
174 | File Operations:
175 | snippet
176 | Description: Extract code snippets from files in the current project
177 | Required Parameters:
178 | - filePath: string (Path to the file to extract snippet from)
179 | Optional Parameters:
180 | - startLine: integer (Starting line number, 1-indexed)
181 | - endLine: integer (Ending line number, 1-indexed)
182 | - entireFile: boolean (Set to true to get the entire file content)
183 | Example: mcp_xcf_snippet filePath="main.swift" entireFile=true
184 | Example: mcp_xcf_snippet filePath="main.swift" startLine=10 endLine=20
185 |
186 | analyzer
187 | Description: Analyze Swift code for potential issues
188 | Required Parameters:
189 | - filePath: string (Path to the file to extract snippet from)
190 | Optional Parameters:
191 | - startLine: integer (Starting line number, 1-indexed)
192 | - endLine: integer (Ending line number, 1-indexed)
193 | - entireFile: boolean (Set to true to get the entire file content)
194 | Example: mcp_xcf_analyzer filePath="main.swift" entireFile=true
195 | Example: mcp_xcf_analyzer filePath="main.swift" startLine=10 endLine=20
196 |
197 | read_dir
198 | Description: List contents of a directory
199 | Required Parameters:
200 | - directoryPath: string (Path to the directory)
201 | Optional Parameters:
202 | - fileExtension: string (Filter files by extension, e.g., 'swift')
203 | Example: mcp_xcf_read_dir directoryPath="src"
204 | Example: mcp_xcf_read_dir directoryPath="src" fileExtension="swift"
205 |
206 | write_file
207 | Description: Write content to a file
208 | Required Parameters:
209 | - filePath: string (Path to the file to write to)
210 | - content: string (Content to write to the file)
211 | Example: mcp_xcf_write_file filePath="main.swift" content="print(\"Hello\")"
212 |
213 | read_file
214 | Description: Read content from a file
215 | Required Parameters:
216 | - filePath: string (Path to the file to read from)
217 | Example: mcp_xcf_read_file filePath="main.swift"
218 |
219 | cd_dir
220 | Description: Change current directory
221 | Required Parameters:
222 | - directoryPath: string (Path to the directory)
223 | Example: mcp_xcf_cd_dir directoryPath="src"
224 |
225 | edit_file
226 | Description: Edit content in a file
227 | Required Parameters:
228 | - filePath: string (Path to the file to edit)
229 | - startLine: integer (Starting line number, 1-indexed)
230 | - endLine: integer (Ending line number, 1-indexed)
231 | - replacement: string (Replacement text for the specified lines)
232 | Example: mcp_xcf_edit_file filePath="main.swift" startLine=10 endLine=20 replacement="new code"
233 |
234 | delete_file
235 | Description: Delete a file
236 | Required Parameters:
237 | - filePath: string (Path to the file to delete)
238 | Example: mcp_xcf_delete_file filePath="old.swift"
239 |
240 | Directory Operations:
241 | add_dir
242 | Description: Create a new directory
243 | Required Parameters:
244 | - directoryPath: string (Path to the directory)
245 | Example: mcp_xcf_add_dir directoryPath="src/models"
246 |
247 | rm_dir
248 | Description: Remove a directory
249 | Required Parameters:
250 | - directoryPath: string (Path to the directory)
251 | Example: mcp_xcf_rm_dir directoryPath="old"
252 |
253 | Xcode Document Operations:
254 | open_doc
255 | Description: Open a document in Xcode
256 | Required Parameters:
257 | - filePath: string (Path to the file to open)
258 | Example: mcp_xcf_open_doc filePath="main.swift"
259 |
260 | create_doc
261 | Description: Create a new document in Xcode
262 | Required Parameters:
263 | - filePath: string (Path to the file to create)
264 | Optional Parameters:
265 | - content: string (Content to write to the file)
266 | Example: mcp_xcf_create_doc filePath="new.swift"
267 | Example: mcp_xcf_create_doc filePath="new.swift" content="import Foundation"
268 |
269 | read_doc
270 | Description: Read document content from Xcode
271 | Required Parameters:
272 | - filePath: string (Path to the file to read)
273 | Example: mcp_xcf_read_doc filePath="main.swift"
274 |
275 | save_doc
276 | Description: Save document in Xcode
277 | Required Parameters:
278 | - filePath: string (Path to the file to save)
279 | Example: mcp_xcf_save_doc filePath="main.swift"
280 |
281 |
282 |
283 | Mode Activation:
284 | use_xcf
285 | Description: Activate XCF mode
286 | Parameters: none (type: string)
287 | Example: mcp_xcf_use_xcf
288 |
289 | Parameter Types:
290 | - string: Text values, use quotes for values with spaces
291 | - integer: Whole numbers, 1-indexed for line numbers
292 | - boolean: true or false
293 | - object: Complex parameter type containing multiple values
294 |
295 | Notes:
296 | - All file paths can be:
297 | - Relative to current directory: main.swift
298 | - In subdirectories: src/main.swift
299 | - In parent directory: ../main.swift
300 | - Full system paths: /Users/username/project/main.swift
301 | - Line numbers are always 1-indexed
302 | - Content with spaces must be quoted
303 | - Boolean values are specified as true or false (lowercase)
304 | - All MCP calls start with mcp_xcf_
305 | """
306 | }
307 |
--------------------------------------------------------------------------------
/xcf/Mcp/Prompts/XcfMcpPrompts.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import MCP
3 |
4 | // MARK: - MCP Prompts
5 |
6 | class XcfMcpPrompts {
7 |
8 | static func getAllPrompts() -> [Prompt] {
9 | return [
10 | createBuildPrompt(),
11 | createRunPrompt(),
12 | createAnalyzeCodePrompt(),
13 | createSnippetPrompt(),
14 | createReadFilePrompt(),
15 | createCdDirPrompt(),
16 | createOpenDocPrompt(),
17 | createCreateDocPrompt(),
18 | createReadDocPrompt(),
19 | createSaveDocPrompt(),
20 | createCloseDocPrompt(),
21 | createXcfActionPrompt(),
22 | createShowHelpPrompt(),
23 | createGrantPermissionPrompt(),
24 | createRunProjectPrompt(),
25 | createBuildProjectPrompt(),
26 | createShowCurrentProjectPrompt(),
27 | createShowEnvPrompt(),
28 | createShowFolderPrompt(),
29 | createListProjectsPrompt(),
30 | createSelectProjectPrompt(),
31 | createAnalyzeSwiftCodePrompt()
32 | ]
33 | }
34 |
35 | // MARK: - Prompt Creation Functions
36 |
37 | private static func createBuildPrompt() -> Prompt {
38 | Prompt(
39 | name: McpConfig.buildPromptName,
40 | description: McpConfig.buildPromptDesc,
41 | arguments: [
42 | Prompt.Argument(name: McpConfig.projectPathArgName, description: McpConfig.projectPathArgDesc, required: true)
43 | ]
44 | )
45 | }
46 |
47 | private static func createRunPrompt() -> Prompt {
48 | Prompt(
49 | name: McpConfig.runPromptName,
50 | description: McpConfig.runPromptDesc,
51 | arguments: [
52 | Prompt.Argument(name: McpConfig.projectPathArgName, description: McpConfig.projectPathArgDesc, required: true)
53 | ]
54 | )
55 | }
56 |
57 | private static func createAnalyzeCodePrompt() -> Prompt {
58 | Prompt(
59 | name: McpConfig.analyzeCodePromptName,
60 | description: McpConfig.analyzeCodePromptDesc,
61 | arguments: [
62 | Prompt.Argument(name: McpConfig.filePathArgName, description: McpConfig.filePathArgDesc, required: true),
63 | Prompt.Argument(name: McpConfig.includeSnippetArgName, description: McpConfig.includeSnippetArgDesc, required: false)
64 | ]
65 | )
66 | }
67 |
68 | private static func createSnippetPrompt() -> Prompt {
69 | Prompt(
70 | name: "extractCodeSnippet",
71 | description: "Extract a code snippet from a file",
72 | arguments: [
73 | Prompt.Argument(name: McpConfig.filePathArgName, description: McpConfig.filePathArgDesc, required: true),
74 | Prompt.Argument(name: "startLine", description: "Starting line number", required: false),
75 | Prompt.Argument(name: "endLine", description: "Ending line number", required: false),
76 | Prompt.Argument(name: "entireFile", description: "Whether to extract the entire file", required: false)
77 | ]
78 | )
79 | }
80 |
81 | private static func createReadFilePrompt() -> Prompt {
82 | Prompt(
83 | name: "readFile",
84 | description: "Read content from a file",
85 | arguments: [
86 | Prompt.Argument(name: McpConfig.filePathArgName, description: McpConfig.filePathArgDesc, required: true)
87 | ]
88 | )
89 | }
90 |
91 | private static func createCdDirPrompt() -> Prompt {
92 | Prompt(
93 | name: "changeDirectory",
94 | description: "Change current directory",
95 | arguments: [
96 | Prompt.Argument(name: "directoryPath", description: "Path to the directory", required: true)
97 | ]
98 | )
99 | }
100 |
101 | private static func createOpenDocPrompt() -> Prompt {
102 | Prompt(
103 | name: "openDocument",
104 | description: "Open a document in Xcode",
105 | arguments: [
106 | Prompt.Argument(name: McpConfig.filePathArgName, description: McpConfig.filePathArgDesc, required: true)
107 | ]
108 | )
109 | }
110 |
111 | private static func createCreateDocPrompt() -> Prompt {
112 | Prompt(
113 | name: "createDocument",
114 | description: "Create a new document in Xcode",
115 | arguments: [
116 | Prompt.Argument(name: McpConfig.filePathArgName, description: McpConfig.filePathArgDesc, required: true),
117 | Prompt.Argument(name: "content", description: "Initial content", required: false)
118 | ]
119 | )
120 | }
121 |
122 | private static func createReadDocPrompt() -> Prompt {
123 | Prompt(
124 | name: "readDocument",
125 | description: "Read document content from Xcode",
126 | arguments: [
127 | Prompt.Argument(name: McpConfig.filePathArgName, description: McpConfig.filePathArgDesc, required: true)
128 | ]
129 | )
130 | }
131 |
132 | private static func createSaveDocPrompt() -> Prompt {
133 | Prompt(
134 | name: "saveDocument",
135 | description: "Save document in Xcode",
136 | arguments: [
137 | Prompt.Argument(name: McpConfig.filePathArgName, description: McpConfig.filePathArgDesc, required: true)
138 | ]
139 | )
140 | }
141 |
142 | private static func createCloseDocPrompt() -> Prompt {
143 | Prompt(
144 | name: "closeDocument",
145 | description: "Close a document in Xcode",
146 | arguments: [
147 | Prompt.Argument(name: McpConfig.filePathArgName, description: McpConfig.filePathArgDesc, required: true),
148 | Prompt.Argument(name: "saving", description: "Whether to save before closing", required: true)
149 | ]
150 | )
151 | }
152 |
153 | private static func createXcfActionPrompt() -> Prompt {
154 | Prompt(
155 | name: "executeXcfAction",
156 | description: "Execute an xcf action or command",
157 | arguments: [
158 | Prompt.Argument(name: "action", description: "The xcf action to execute", required: true)
159 | ]
160 | )
161 | }
162 |
163 | private static func createShowHelpPrompt() -> Prompt {
164 | Prompt(
165 | name: McpConfig.showHelpPromptName,
166 | description: McpConfig.showHelpToolDesc,
167 | arguments: []
168 | )
169 | }
170 |
171 | private static func createGrantPermissionPrompt() -> Prompt {
172 | Prompt(
173 | name: McpConfig.grantPermissionPromptName,
174 | description: McpConfig.grantPermissionToolDesc,
175 | arguments: []
176 | )
177 | }
178 |
179 | private static func createRunProjectPrompt() -> Prompt {
180 | Prompt(
181 | name: McpConfig.runProjectPromptName,
182 | description: McpConfig.runProjectToolDesc,
183 | arguments: []
184 | )
185 | }
186 |
187 | private static func createBuildProjectPrompt() -> Prompt {
188 | Prompt(
189 | name: McpConfig.buildProjectPromptName,
190 | description: McpConfig.buildProjectToolDesc,
191 | arguments: []
192 | )
193 | }
194 |
195 | private static func createShowCurrentProjectPrompt() -> Prompt {
196 | Prompt(
197 | name: McpConfig.showCurrentProjectPromptName,
198 | description: McpConfig.showCurrentProjectToolDesc,
199 | arguments: []
200 | )
201 | }
202 |
203 | private static func createShowEnvPrompt() -> Prompt {
204 | Prompt(
205 | name: McpConfig.showEnvPromptName,
206 | description: McpConfig.showEnvToolDesc,
207 | arguments: []
208 | )
209 | }
210 |
211 | private static func createShowFolderPrompt() -> Prompt {
212 | Prompt(
213 | name: McpConfig.showFolderPromptName,
214 | description: McpConfig.showFolderToolDesc,
215 | arguments: []
216 | )
217 | }
218 |
219 | private static func createListProjectsPrompt() -> Prompt {
220 | Prompt(
221 | name: McpConfig.listProjectsPromptName,
222 | description: McpConfig.listProjectsToolDesc,
223 | arguments: []
224 | )
225 | }
226 |
227 | private static func createSelectProjectPrompt() -> Prompt {
228 | Prompt(
229 | name: McpConfig.selectProjectPromptName,
230 | description: McpConfig.selectProjectToolDesc,
231 | arguments: [
232 | Prompt.Argument(name: McpConfig.projectNumberParamName, description: McpConfig.projectNumberParamDesc, required: true)
233 | ]
234 | )
235 | }
236 |
237 | private static func createAnalyzeSwiftCodePrompt() -> Prompt {
238 | Prompt(
239 | name: McpConfig.analyzeSwiftCodePromptName,
240 | description: McpConfig.analyzeSwiftCodeToolDesc,
241 | arguments: [
242 | Prompt.Argument(name: McpConfig.filePathParamName, description: McpConfig.filePathParamDesc, required: true),
243 | Prompt.Argument(name: McpConfig.startLineParamName, description: McpConfig.startLineParamDesc, required: false),
244 | Prompt.Argument(name: McpConfig.endLineParamName, description: McpConfig.endLineParamDesc, required: false),
245 | Prompt.Argument(name: McpConfig.checkGroupsParamName, description: McpConfig.checkGroupsParamDesc, required: false)
246 | ]
247 | )
248 | }
249 | }
--------------------------------------------------------------------------------
/xcf/Mcp/Script/XcfSwiftScript.swift:
--------------------------------------------------------------------------------
1 | //
2 | // XcfSwiftScript.swift
3 | // xcf
4 | //
5 | // Created by Todd Bruss on 5/7/25.
6 | //
7 |
8 | import AppKit
9 | import Foundation
10 | import ScriptingBridge
11 |
12 | class XcfSwiftScript {
13 | static let shared = XcfSwiftScript()
14 | private var files: Set = []
15 | func buildCurrentWorkspace(projectPath: String, run: Bool = false) -> String {
16 |
17 | // Get Xcode application instance
18 | guard let xcode: XcodeApplication = SBApplication(bundleIdentifier: XcodeConstants.xcodeBundleIdentifier) else {
19 | return ErrorMessages.failedToConnectXcode
20 | }
21 |
22 | // Open the project
23 | let xcDoc = xcode.open?(projectPath as Any)
24 |
25 | // Run the document
26 | guard
27 | let currentWorkspace = xcDoc as? XcodeWorkspaceDocument
28 | else {
29 | return ErrorMessages.noWorkspaceFound
30 | }
31 |
32 | currentWorkspace.stop?()
33 |
34 | var buildResult: XcodeSchemeActionResult?
35 |
36 | if run {
37 | sleep(XcodeConstants.runDelayInterval) // time for last one to get killed
38 |
39 | Task {
40 | currentWorkspace.runWithCommandLineArguments?(nil, withEnvironmentVariables: nil)
41 | }
42 |
43 | return SuccessMessages.runSuccess
44 | } else {
45 | buildResult = currentWorkspace.build?()
46 | if buildResult == nil {
47 | return ErrorMessages.failedToStartBuild
48 | }
49 | }
50 |
51 | // Now we can safely use buildResult outside the if/else blocks
52 | guard let result = buildResult else {
53 | return ErrorMessages.failedToGetBuildResult
54 | }
55 |
56 | // Wait for build to complete
57 | while !(result.completed ?? false) {
58 | Thread.sleep(forTimeInterval: XcodeConstants.buildPollInterval)
59 | }
60 |
61 | var buildResults = ""
62 |
63 | files = [] // Reset files collection before processing
64 |
65 | // Handle build errors with the original approach
66 | if let buildErrors = result.buildErrors?() {
67 | processIssues(issues: buildErrors, issueType: XcodeConstants.errorIssueType, buildResults: &buildResults)
68 | }
69 |
70 | // Handle other issue types if they're available
71 | if let buildWarnings = result.buildWarnings?() {
72 | processIssues(issues: buildWarnings, issueType: XcodeConstants.warningIssueType, buildResults: &buildResults)
73 | }
74 |
75 | if let analyzerIssues = result.analyzerIssues?() {
76 | processIssues(issues: analyzerIssues, issueType: XcodeConstants.analyzerIssueType, buildResults: &buildResults)
77 | }
78 |
79 | if let testFailures = result.testFailures?() {
80 | processIssues(issues: testFailures, issueType: XcodeConstants.testFailureIssueType, buildResults: &buildResults)
81 | }
82 |
83 | // Send entire file(s) at the end, uses a set to avoid duplicateso
84 | // Commented out: this uses too many tokens.. may send specific snippets later
85 | // for file in files {
86 | // buildResults += "\(XcodeConstants.filePrefix)\(file)\(XcodeConstants.fileSuffix)\(Format.newLine)"
87 | // let (fileContent, language) = CaptureSnippet.getCodeSnippet(filePath: file, startLine: 1, endLine: Int.max, entireFile: true)
88 | // buildResults += "\(XcodeConstants.codeBlockStart)\(language)\(Format.newLine)"
89 | // buildResults += fileContent
90 | // buildResults += "\(Format.newLine)\(XcodeConstants.codeBlockEnd)\(Format.newLine)"
91 | // }
92 |
93 | // Return build results
94 | return buildResults.isEmpty ? SuccessMessages.buildSuccess : buildResults
95 | }
96 |
97 | // Helper function to process different types of build issues
98 | private func processIssues(issues: SBElementArray, issueType: String, buildResults: inout String) {
99 | for case let issue as XcodeBuildError in issues {
100 | if let issueMessage = issue.message {
101 | if let filePath = issue.filePath,
102 | let startingColNum = issue.startingColumnNumber,
103 | let startLine = issue.startingLineNumber,
104 | let endLine = issue.endingLineNumber {
105 | files.insert(filePath)
106 | buildResults += String(format: XcodeConstants.issueFormat,
107 | filePath, startLine, startingColNum, issueType, issueMessage) + Format.newLine
108 | buildResults += formatCodeSnippet(filePath: filePath, startLine: startLine, endLine: endLine)
109 | } else {
110 | buildResults += "[\(issueType)] \(issueMessage)\(Format.newLine)"
111 | }
112 | }
113 | }
114 | }
115 |
116 | // Helper function to format code snippets consistently
117 | private func formatCodeSnippet(filePath: String, startLine: Int, endLine: Int) -> String {
118 | let (snippet, language) = CaptureSnippet.getCodeSnippet(filePath: filePath, startLine: startLine, endLine: endLine)
119 | var formattedSnippet = "\(XcodeConstants.codeBlockStart)\(language)\(Format.newLine)"
120 | formattedSnippet += snippet
121 | formattedSnippet += "\(Format.newLine)\(XcodeConstants.codeBlockEnd)\(Format.newLine)"
122 | return formattedSnippet
123 | }
124 |
125 | /// Gets paths of all open Xcode documents whose name contains the specified extension
126 | /// - Parameter ext: The extension or substring to filter document names by
127 | /// - Returns: Array of document paths matching the criteria, or empty array if none found or error occurred
128 | func getXcodeDocumentPaths(ext: String) -> [String] {
129 | // Get Xcode application instance
130 | guard let xcode: XcodeApplication = SBApplication(bundleIdentifier: XcodeConstants.xcodeBundleIdentifier) else {
131 | return []
132 | }
133 |
134 | // Get all documents
135 | guard let documents = xcode.documents?() else {
136 | return []
137 | }
138 |
139 | var paths: Set = []
140 |
141 | // Iterate through all documents and filter by extension
142 | for case let document as XcodeDocument in documents {
143 | if let name = document.name, name.contains(ext), let path = document.path {
144 | paths.insert(path)
145 | }
146 | }
147 |
148 | return Array(paths).sorted()
149 | }
150 |
151 | // New method to get schemes (not implemented)
152 | func getSchemes() -> [String] {
153 | // Get Xcode application instance
154 | guard let xcode: XcodeApplication = SBApplication(bundleIdentifier: XcodeConstants.xcodeBundleIdentifier) else {
155 | return []
156 | }
157 |
158 | // Get the active workspace document
159 | guard let activeWorkspace = xcode.activeWorkspaceDocument else {
160 | return []
161 | }
162 |
163 | // Retrieve the schemes
164 | if let schemes = activeWorkspace.schemes?() as? [XcodeScheme] {
165 | return schemes.compactMap { $0.name }
166 | } else {
167 | return []
168 | }
169 | }
170 |
171 | func activeWorkspacePath() -> String? {
172 | guard let xcode: XcodeApplication = SBApplication(bundleIdentifier: XcodeConstants.xcodeBundleIdentifier) else {
173 | return nil
174 | }
175 |
176 | guard let activeWorkspace = xcode.activeWorkspaceDocument else {
177 | return nil
178 | }
179 |
180 | let projectPath = activeWorkspace.path
181 |
182 | // Only return the path if currentFolder exists and path contains currentFolder
183 | if let currentFolder = XcfXcodeProjectManager.shared.currentFolder {
184 | if let path = projectPath, path.contains(currentFolder) {
185 | return path
186 | }
187 | return nil
188 | }
189 |
190 | return projectPath
191 | }
192 |
193 | // MARK: - Swift Document Management
194 |
195 | /// Opens a Swift document in Xcode
196 | /// - Parameter filePath: The path to the Swift file to open
197 | /// - Returns: True if successful, false otherwise
198 | func openSwiftDocument(filePath: String) -> Bool {
199 | guard let xcode: XcodeApplication = SBApplication(bundleIdentifier: XcodeConstants.xcodeBundleIdentifier) else {
200 | return false
201 | }
202 |
203 | // Use FileFinder to resolve the path
204 | let (resolvedPath, warning) = FileFinder.resolveFilePath(filePath)
205 |
206 | // First check if the file exists on disk using FileManager
207 | if !FileManager.default.fileExists(atPath: resolvedPath) {
208 | return false
209 | }
210 |
211 | // Check if the document is already open in Xcode using Scripting Bridge
212 | if let documents = xcode.documents?() {
213 | for case let document as XcodeDocument in documents {
214 | if let docPath = document.path, docPath == resolvedPath {
215 | return true
216 | }
217 | }
218 | }
219 |
220 | // Check if the file is accessible and readable
221 | guard FileManager.default.isReadableFile(atPath: resolvedPath) else {
222 | return false
223 | }
224 |
225 | // Try to open the document in Xcode using Scripting Bridge
226 | guard let openedDocument = xcode.open?(resolvedPath as Any) as? XcodeTextDocument else {
227 | return false
228 | }
229 |
230 | // Verify the document was actually opened by checking its path
231 | if let openedPath = openedDocument.path, openedPath == resolvedPath {
232 | return true
233 | } else {
234 | return false
235 | }
236 | }
237 |
238 | /// Creates a new Swift document using Xcode ScriptingBridge
239 | /// - Parameters:
240 | /// - filePath: The path where the Swift file should be created
241 | /// - content: Initial content of the Swift file (optional)
242 | /// - Returns: True if successful, false otherwise
243 | func createSwiftDocumentWithFileManager(filePath: String, content: String = "") -> Bool {
244 | guard let _: XcodeApplication = SBApplication(bundleIdentifier: XcodeConstants.xcodeBundleIdentifier) else {
245 | return false
246 | }
247 |
248 | // Use FileFinder to resolve the path
249 | let (resolvedPath, warning) = FileFinder.resolveFilePath(filePath)
250 |
251 | // First create the file using FileManager
252 | do {
253 | try XcfFileManager.writeFile(content: content, to: resolvedPath)
254 | } catch {
255 | return false
256 | }
257 |
258 | // Try to open the document
259 | return openSwiftDocument(filePath: resolvedPath)
260 | }
261 |
262 | // MARK: - Document Reading
263 |
264 | /// Reads the contents of a Swift document using FileManager
265 | /// - Parameter filePath: The path to the Swift file to read
266 | /// - Returns: The file contents as a string, or nil if the operation failed
267 | func readSwiftDocumentWithFileManager(filePath: String) -> String? {
268 | do {
269 | let (content, _) = try XcfFileManager.readFile(at: filePath)
270 | return content
271 | } catch {
272 | return nil
273 | }
274 | }
275 |
276 | // MARK: - Document Writing
277 |
278 | /// Writes content to a Swift document using FileManager
279 | /// - Parameters:
280 | /// - filePath: The path to the Swift file to write to
281 | /// - content: The content to write to the file
282 | /// - Returns: True if successful, false otherwise
283 | func writeSwiftDocumentWithFileManager(filePath: String, content: String) -> Bool {
284 | do {
285 | try XcfFileManager.writeFile(content: content, to: filePath)
286 | return true
287 | } catch {
288 | return false
289 | }
290 | }
291 |
292 | // MARK: - Directory Operations
293 | /// Shows a directory selection dialog and returns the selected path
294 | /// - Returns: The selected directory path, or nil if cancelled
295 | func selectDirectory() -> String? {
296 | return XcfFileManager.selectDirectory()
297 | }
298 |
299 | /// Closes a Swift document in Xcode
300 | /// - Parameter filePath: The path to the Swift file to close
301 | /// - Returns: True if successful, false otherwise
302 | func closeSwiftDocument(filePath: String, xcSaveOptions: XcodeSaveOptions ) -> Bool {
303 | guard let xcode: XcodeApplication = SBApplication(bundleIdentifier: XcodeConstants.xcodeBundleIdentifier) else {
304 | return false
305 | }
306 |
307 | // Use FileFinder to resolve the path
308 | let (resolvedPath, warning) = FuzzyLogicService.resolveFilePath(filePath)
309 |
310 | // Find the document in Xcode
311 | guard let documents = xcode.documents?() else {
312 | return false
313 | }
314 |
315 | // Try to find document with exact path
316 | if let document = (documents as? [XcodeDocument])?.first(where: { $0.path == resolvedPath }) {
317 | // Close the document
318 | document.closeSaving?(xcSaveOptions, savingIn: nil)
319 | return true
320 | }
321 |
322 | // Try to find document with filename match (more lenient)
323 | let filename = (resolvedPath as NSString).lastPathComponent
324 | if let document = (documents as? [XcodeDocument])?.first(where: {
325 | if let docPath = $0.path {
326 | return (docPath as NSString).lastPathComponent == filename
327 | }
328 | return false
329 | }) {
330 | // Close the document
331 | document.closeSaving?(xcSaveOptions, savingIn: nil)
332 | return true
333 | }
334 |
335 | return false
336 | }
337 | }
338 |
--------------------------------------------------------------------------------