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 CHANGED
@@ -15,7 +15,7 @@
15
15
  "location" : "https://github.com/mediar-ai/MacosUseSDK.git",
16
16
  "state" : {
17
17
  "branch" : "main",
18
- "revision" : "181506cca7b6d1fe709e8b00eb35d375810bb5ca"
18
+ "revision" : "df6989da30b580c203828fa24e9e25a318c2d113"
19
19
  }
20
20
  },
21
21
  {
@@ -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.3.0", // Incremented version for major change
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mcp-server-macos-use",
3
- "version": "0.1.6",
3
+ "version": "0.1.8",
4
4
  "description": "MCP server that lets AI agents control any macOS application through accessibility APIs",
5
5
  "bin": {
6
6
  "mcp-server-macos-use": "bin/mcp-server-macos-use"