mcp-server-macos-use 0.1.6 → 0.1.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Package.resolved +1 -1
- package/Sources/main.swift +93 -4
- package/package.json +1 -1
package/Package.resolved
CHANGED
package/Sources/main.swift
CHANGED
|
@@ -459,7 +459,7 @@ func buildToolResponse(_ result: ActionResult, hasDiff: Bool) -> ToolResponse {
|
|
|
459
459
|
|
|
460
460
|
/// Build a concise text summary for the MCP response instead of returning the full JSON.
|
|
461
461
|
/// The full JSON is written to a file; this summary contains just the key info + file path.
|
|
462
|
-
func buildCompactSummary(toolName: String, params: CallTool.Parameters, toolResponse: ToolResponse, filepath: String) -> String {
|
|
462
|
+
func buildCompactSummary(toolName: String, params: CallTool.Parameters, toolResponse: ToolResponse, filepath: String, fileSize: Int) -> String {
|
|
463
463
|
var lines: [String] = []
|
|
464
464
|
|
|
465
465
|
// Status line
|
|
@@ -474,8 +474,19 @@ func buildCompactSummary(toolName: String, params: CallTool.Parameters, toolResp
|
|
|
474
474
|
lines.append("app: \(appName)")
|
|
475
475
|
}
|
|
476
476
|
|
|
477
|
-
// File path
|
|
477
|
+
// File path + metadata
|
|
478
478
|
lines.append("file: \(filepath)")
|
|
479
|
+
let elementCount: Int
|
|
480
|
+
if let traversal = toolResponse.traversal {
|
|
481
|
+
elementCount = traversal.elements.count
|
|
482
|
+
} else {
|
|
483
|
+
let added = toolResponse.diff?.added.count ?? 0
|
|
484
|
+
let removed = toolResponse.diff?.removed.count ?? 0
|
|
485
|
+
let modified = toolResponse.diff?.modified.count ?? 0
|
|
486
|
+
elementCount = added + removed + modified
|
|
487
|
+
}
|
|
488
|
+
lines.append("file_size: \(fileSize) bytes (\(elementCount) elements)")
|
|
489
|
+
lines.append("⚠️ DO NOT read the full file — use grep to search for specific elements by text or role.")
|
|
479
490
|
|
|
480
491
|
// Errors if any
|
|
481
492
|
if let err = toolResponse.primaryActionError {
|
|
@@ -562,6 +573,17 @@ func buildCompactSummary(toolName: String, params: CallTool.Parameters, toolResp
|
|
|
562
573
|
}
|
|
563
574
|
}
|
|
564
575
|
|
|
576
|
+
// Inline visible interactive elements
|
|
577
|
+
if let traversal = toolResponse.traversal {
|
|
578
|
+
// Full traversal (open/refresh): show all visible interactive elements
|
|
579
|
+
let visLines = buildVisibleElementsSection(elements: traversal.elements, label: "visible_elements")
|
|
580
|
+
lines.append(contentsOf: visLines)
|
|
581
|
+
} else if let diff = toolResponse.diff, !diff.added.isEmpty {
|
|
582
|
+
// Diff (click/type/press/scroll): show newly added visible elements
|
|
583
|
+
let visLines = buildVisibleElementsSection(elements: diff.added, label: "visible_elements", interactiveCap: 20, textCap: 10)
|
|
584
|
+
lines.append(contentsOf: visLines)
|
|
585
|
+
}
|
|
586
|
+
|
|
565
587
|
return lines.joined(separator: "\n")
|
|
566
588
|
}
|
|
567
589
|
|
|
@@ -580,6 +602,73 @@ func truncate(_ s: String, maxLen: Int) -> String {
|
|
|
580
602
|
s.count > maxLen ? String(s.prefix(maxLen)) + "..." : s
|
|
581
603
|
}
|
|
582
604
|
|
|
605
|
+
/// Protocol for element types that can be displayed in visible elements section
|
|
606
|
+
protocol VisibleElement {
|
|
607
|
+
var role: String { get }
|
|
608
|
+
var text: String? { get }
|
|
609
|
+
var in_viewport: Bool? { get }
|
|
610
|
+
var x: Double? { get }
|
|
611
|
+
var y: Double? { get }
|
|
612
|
+
var width: Double? { get }
|
|
613
|
+
var height: Double? { get }
|
|
614
|
+
}
|
|
615
|
+
extension EnrichedElementData: VisibleElement {}
|
|
616
|
+
extension DiffElementData: VisibleElement {}
|
|
617
|
+
|
|
618
|
+
/// Interactive role prefixes worth showing inline in the compact summary
|
|
619
|
+
private let interactiveRolePrefixes: [String] = [
|
|
620
|
+
"AXButton", "AXLink", "AXTextField", "AXTextArea", "AXCheckBox",
|
|
621
|
+
"AXRadioButton", "AXPopUpButton", "AXComboBox", "AXSlider",
|
|
622
|
+
"AXMenuItem", "AXMenuButton", "AXTab"
|
|
623
|
+
]
|
|
624
|
+
|
|
625
|
+
/// Check if a role string matches any interactive prefix
|
|
626
|
+
private func isInteractiveRole(_ role: String) -> Bool {
|
|
627
|
+
interactiveRolePrefixes.contains { role.hasPrefix($0) }
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
/// Check if a role string is static text
|
|
631
|
+
private func isStaticTextRole(_ role: String) -> Bool {
|
|
632
|
+
role.hasPrefix("AXStaticText")
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
/// Build a visible_elements section from a list of elements
|
|
636
|
+
func buildVisibleElementsSection<T: VisibleElement>(elements: [T], label: String, interactiveCap: Int = 30, textCap: Int = 10) -> [String] {
|
|
637
|
+
var interactive: [String] = []
|
|
638
|
+
var staticText: [String] = []
|
|
639
|
+
|
|
640
|
+
for el in elements {
|
|
641
|
+
guard el.in_viewport == true else { continue }
|
|
642
|
+
guard let text = el.text, !text.isEmpty else { continue }
|
|
643
|
+
|
|
644
|
+
let truncatedText = truncate(text, maxLen: 50)
|
|
645
|
+
let pos: String
|
|
646
|
+
if let x = el.x, let y = el.y, let w = el.width, let h = el.height {
|
|
647
|
+
pos = " (\(Int(x)),\(Int(y)) \(Int(w))×\(Int(h)))"
|
|
648
|
+
} else {
|
|
649
|
+
pos = ""
|
|
650
|
+
}
|
|
651
|
+
let line = " [\(el.role)] \"\(truncatedText)\"\(pos)"
|
|
652
|
+
|
|
653
|
+
if isInteractiveRole(el.role) {
|
|
654
|
+
if interactive.count < interactiveCap {
|
|
655
|
+
interactive.append(line)
|
|
656
|
+
}
|
|
657
|
+
} else if isStaticTextRole(el.role) {
|
|
658
|
+
if staticText.count < textCap {
|
|
659
|
+
staticText.append(line)
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
if interactive.isEmpty && staticText.isEmpty { return [] }
|
|
665
|
+
|
|
666
|
+
var result = ["\(label):"]
|
|
667
|
+
result.append(contentsOf: interactive)
|
|
668
|
+
result.append(contentsOf: staticText)
|
|
669
|
+
return result
|
|
670
|
+
}
|
|
671
|
+
|
|
583
672
|
// --- Direct AX Element Interaction ---
|
|
584
673
|
|
|
585
674
|
// --- Auto-Scroll via Scroll Wheel Events ---
|
|
@@ -926,7 +1015,7 @@ func setupAndStartServer() async throws -> Server {
|
|
|
926
1015
|
|
|
927
1016
|
let server = Server(
|
|
928
1017
|
name: "SwiftMacOSServerDirect", // Renamed slightly
|
|
929
|
-
version: "1.
|
|
1018
|
+
version: "1.4.0", // Inline visible elements in compact summary
|
|
930
1019
|
capabilities: .init(
|
|
931
1020
|
tools: .init(listChanged: true)
|
|
932
1021
|
)
|
|
@@ -1142,7 +1231,7 @@ func setupAndStartServer() async throws -> Server {
|
|
|
1142
1231
|
try? resultJsonString.write(toFile: filepath, atomically: true, encoding: .utf8)
|
|
1143
1232
|
fputs("log: handler(CallTool): wrote full response to \(filepath) (\(resultJsonString.count) bytes)\n", stderr)
|
|
1144
1233
|
|
|
1145
|
-
let summary = buildCompactSummary(toolName: params.name, params: params, toolResponse: toolResponse, filepath: filepath)
|
|
1234
|
+
let summary = buildCompactSummary(toolName: params.name, params: params, toolResponse: toolResponse, filepath: filepath, fileSize: resultJsonString.count)
|
|
1146
1235
|
fputs("log: handler(CallTool): returning compact summary (\(summary.count) chars)\n", stderr)
|
|
1147
1236
|
|
|
1148
1237
|
return .init(content: [.text(summary)], isError: isError)
|
package/package.json
CHANGED