Skip to content

Commit 6ea6815

Browse files
mcp: bundle pyobjc ScriptingBridge binding on Darwin (#1294)
## What Bundle the **pyobjc `ScriptingBridge`** framework binding into the kernel interpreter on Darwin, so a session can drive any scriptable macOS app (Things, Music, Finder, Safari, ...) via native `SBApplication` objects — no AppleScript strings, no install step. Before this, the kernel had pyobjc-core (`objc`, `Foundation`, `AppKit`) but `import ScriptingBridge` failed (`find_spec` → None); you had to fall back to `objc.loadBundle(...)` by hand each session. ## How nixpkgs ships only a curated subset of pyobjc framework bindings and omits ScriptingBridge. Every binding lives as a sibling subdir in the pyobjc monorepo `src`, so this derives the wrapper from `pyobjc-framework-Quartz` by retargeting only `sourceRoot` + `pythonImportsCheck` — exactly the existing `coreLocationModule` pattern. Tracks nixpkgs Quartz build fixes automatically. - New `scriptingBridgeModule`, added to `darwinExtraPackages`. - New `scriptingBridgeBundled` import smoke test, wired into the Darwin `passthru.tests`. ## Verification ``` nix build .#mcp.tests.scriptingBridgeBundled # builds + import check passes nix run .#lint # 7/7 succeeded ``` Confirmed live: listed 5 Things3 to-dos through `SBApplication.applicationWithBundleIdentifier_("com.culturedcode.ThingsMac")`. 🤖 Authored by Claude (Opus 4.8) via Claude Code. <!-- Macroscope's pull request summary starts here --> <!-- Macroscope will only edit the content between these invisible markers, and the markers themselves will not be visible in the GitHub rendered markdown. --> <!-- If you delete either of the start / end markers from your PR's description, Macroscope will append its summary at the bottom of the description. --> > [!NOTE] > ### Bundle pyobjc ScriptingBridge binding in the MCP package on Darwin > - Adds a new `pyobjc-framework-ScriptingBridge` derivation in [default.nix](https://github.com/indexable-inc/index/pull/1294/files#diff-1620bd0b79c426472be159dd458dcedbb31de6b35b9507d98c31d4aed67e08db) by overriding the pyobjc-framework-Quartz package attributes to point at the ScriptingBridge subdirectory. > - Includes the binding in `darwinExtraPackages` so `import ScriptingBridge` is available at runtime on macOS. > - Adds a build-time import test that checks `SBApplication.applicationWithBundleIdentifier_` is callable, wired into the package's test phase. > > <!-- Macroscope's review summary starts here --> > > <sup><a href="https://app.macroscope.com">Macroscope</a> summarized 7dfd73d.</sup> > <!-- Macroscope's review summary ends here --> > <!-- macroscope-ui-refresh --> <!-- Macroscope's pull request summary ends here -->
1 parent ab2edf7 commit 6ea6815

1 file changed

Lines changed: 20 additions & 1 deletion

File tree

packages/mcp/default.nix

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -709,16 +709,33 @@ let
709709
};
710710
});
711711

712+
# `import ScriptingBridge` on Darwin: the pyobjc binding for Apple's Scripting
713+
# Bridge, so a session can drive any scriptable macOS app (Things, Music,
714+
# Finder, ...) as native Objective-C objects — `SBApplication` — with no
715+
# AppleScript strings and no install step. nixpkgs omits this binding too, so
716+
# derive it from Quartz the same way as `coreLocationModule` above (same
717+
# monorepo src, only the source subdir and import check change).
718+
scriptingBridgeModule = pkgs.python3.pkgs.pyobjc-framework-Quartz.overridePythonAttrs (old: {
719+
pname = "pyobjc-framework-ScriptingBridge";
720+
sourceRoot = "${old.src.name}/pyobjc-framework-ScriptingBridge";
721+
pythonImportsCheck = [ "ScriptingBridge" ];
722+
meta = old.meta // {
723+
description = "PyObjC wrappers for the Scripting Bridge framework on macOS";
724+
};
725+
});
726+
712727
# The `screen` helper is macOS-only, so its dependencies join the interpreter
713728
# only on Darwin. `pyobjc-framework-Quartz` is the maintained CoreGraphics
714729
# binding the helper wraps; Pillow (already transitive via matplotlib) carries
715730
# the PIL image type capture returns. `coreLocationModule` adds the Core
716-
# Location binding so location reads work out of the box.
731+
# Location binding so location reads work out of the box, and
732+
# `scriptingBridgeModule` the Scripting Bridge binding for app automation.
717733
darwinExtraPackages =
718734
ps:
719735
lib.optionals pkgs.stdenv.hostPlatform.isDarwin [
720736
ps.pyobjc-framework-Quartz
721737
coreLocationModule
738+
scriptingBridgeModule
722739
screenModule
723740
vmkitModule
724741
imessageModule
@@ -4537,6 +4554,7 @@ let
45374554

45384555
screenBundled = importTest "screen" "import screen; print('screen-ok', all(callable(getattr(screen, n)) for n in ('capture', 'click', 'write', 'press', 'key_down', 'key_up', 'apps', 'frontmost', 'launch', 'activate', 'terminate', 'accessibility_trusted')))";
45394556
coreLocationBundled = importTest "corelocation" "import CoreLocation; print('corelocation-ok', callable(CoreLocation.CLLocationManager.alloc))";
4557+
scriptingBridgeBundled = importTest "scriptingbridge" "import ScriptingBridge; print('scriptingbridge-ok', callable(ScriptingBridge.SBApplication.applicationWithBundleIdentifier_))";
45404558
vmkitBundled = importTest "vmkit" "import vmkit; print('vmkit-ok', callable(vmkit.boot_linux), callable(vmkit.drive), callable(vmkit.screenshot))";
45414559
imessageBundled = importTest "imessage" "import imessage; print('imessage-ok', all(callable(getattr(imessage, n)) for n in ('messages', 'chats', 'contacts', 'send')))";
45424560
xBundled = importTest "x" "import x; print('x-ok', callable(x.posts), x.__version__)";
@@ -4728,6 +4746,7 @@ package.overrideAttrs (old: {
47284746
inherit
47294747
screenBundled
47304748
coreLocationBundled
4749+
scriptingBridgeBundled
47314750
vmkitBundled
47324751
vmkitResourceSmoke
47334752
imessageBundled

0 commit comments

Comments
 (0)