Window tools your agent can use.
Kilter 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 Kilter first
Buy and download Kilter, 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": {
"kilter": {
"command": "uv",
"args": [
"run", "--python", "3.13", "--with", "mcp>=1.28.1,<1.29",
"/Applications/Kilter.app/Contents/Resources/integrations/windowpilot_mcp.py",
"--app", "/Applications/Kilter.app"
]
}
}
}Your host may use a different config format. The server identifies itself as Kilter. Adding the config does not grant permission or connect anything by itself.
Connect Codex
Run codex mcp list to check that the name kilter is free, then add the bridge:
codex mcp add kilter -- uv run --python 3.13 \
--with 'mcp>=1.28.1,<1.29' \
/Applications/Kilter.app/Contents/Resources/integrations/windowpilot_mcp.py \
--app /Applications/Kilter.appRun codex mcp get kilter to check the saved command. Start a new Codex session and ask it to inspect windows without moving them. If Kilter is somewhere other than Applications, 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/Kilter.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 Kilter 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/Kilter.app/Contents/MacOS/WindowPilot", "--agent"],
stdin=subprocess.PIPE, stdout=subprocess.PIPE, text=True, bufsize=1,
)
lock = threading.Lock()
def call_kilter(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("Kilter response ID mismatch")
return response
# Inspect first; select a deliberate windowId before previewing any change.
state = call_kilter("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.