Mullionby Atris
How it worksFeaturesFor agentsPricing
Buy Mullion
‹ Mullion for Mac

Window tools your agent can use.

Mullion includes a small local tool pack. A compatible agent can inspect your windows, preview a layout, apply it and undo it. There is no remote control port.

Install Mullion first

Buy and download Mullion, move it to Applications and open it. Allow Accessibility in System Settings > Privacy & Security > Accessibility. Window operations use that permission; the tool pack never grants or requests it for you.

Connect over MCP

The MCP adapter ships inside the app. Install uv, then add this server to your MCP host. uv prepares Python 3.13 and the official MCP SDK the first time it runs.

{
  "mcpServers": {
    "mullion": {
      "command": "uv",
      "args": [
        "run", "--python", "3.13", "--with", "mcp>=1.28.1,<1.29",
        "/Applications/Mullion.app/Contents/Resources/integrations/windowpilot_mcp.py",
        "--app", "/Applications/Mullion.app"
      ]
    }
  }
}

Your host may use a different config format. The paths assume the app is at /Applications/Mullion.app; if your copy has a different name or location, change both. Adding the config does not grant permission or connect anything by itself.

Connect Codex

Run codex mcp list to check that the name mullion is free, then add the bridge:

codex mcp add mullion -- uv run --python 3.13 \
  --with 'mcp>=1.28.1,<1.29' \
  /Applications/Mullion.app/Contents/Resources/integrations/windowpilot_mcp.py \
  --app /Applications/Mullion.app

Run codex mcp get mullion to check the saved command. Start a new Codex session and ask it to inspect windows without moving them. If the app has a different name or location, change both app paths. See the Codex MCP guide for host setup.

Give it a small task

Inspect my open windows. Show me the target window you chose.
Preview putting that window on the left half of its current display.
Apply only the requested layout, verify the result, and keep Undo available.

Preview never moves a window. Apply checks that the window and display have not changed, verifies the result, and tries to restore the previous position if a write fails. A window found in an unexpected position is left where it is. If you did not ask for a change, the agent should show you the preview first.

What each tool does

  • inspect: permissions, model availability, open windows, displays and supported zones.
  • list_setups: saved setup names, IDs, apps and window counts.
  • preview_setup: preview a saved setup whose windows are already open. Opens no apps, folders or links.
  • preview: a quick layout zone, a custom size or an optional local language command, with before and after frames and a short-lived token.
  • apply: use a preview token once, with validation and a geometry check.
  • undo: restore an earlier arrangement from the same agent session.
  • retry_accessibility_recovery: retry an app accessibility setting without touching window Undo history.
  • open_app: open an installed app by its exact bundle identifier.

Or use JSON lines directly

/Applications/Mullion.app/Contents/MacOS/WindowPilot --agent

{"id":"inspect-1","op":"inspect"}

Write one JSON object per line to stdin and read one response per line from stdout. Keep the same process alive from preview to apply to undo: tokens and Undo history belong to that process, and preview tokens expire after five minutes. Undo history here is separate from the Undo button in the app.

To wire Mullion into your own model’s tool calls, a minimal Python bridge looks like this:

import json
import subprocess
import threading
import uuid

process = subprocess.Popen(
    ["/Applications/Mullion.app/Contents/MacOS/WindowPilot", "--agent"],
    stdin=subprocess.PIPE, stdout=subprocess.PIPE, text=True, bufsize=1,
)
lock = threading.Lock()

def call_mullion(operation, **arguments):
    request_id = str(uuid.uuid4())
    with lock:
        process.stdin.write(json.dumps({"op": operation, "id": request_id, **arguments}) + "\n")
        process.stdin.flush()
        response = json.loads(process.stdout.readline())
    if response["id"] != request_id:
        raise RuntimeError("Mullion response ID mismatch")
    return response

# Inspect first; select a deliberate windowId before previewing any change.
state = call_mullion("inspect")
# On shutdown: process.stdin.close(); process.wait()

Scope and privacy

Quick layouts and custom sizes use no model. Language commands run locally with Apple Intelligence or an existing Atris local runtime. There is no shell, browser, clicking or general computer control endpoint, and no network port.

Window titles and positions go to the agent you connect, and a hosted model may send them to its provider. A timed-out request can leave an uncertain result, so start a new session and inspect before you retry.

Full protocolSummary for language modelsMullion privacy

Mullion for Mac. Made by Atris.

SupportFor agentsMullion privacyAtris privacyTerms