Skip to main content
Glama

screentime-mcp

Lets your AI assistant see and control your screen time.

Ask Poke "how much time did I burn on Instagram this week" or "block me for an hour, I need to finish this" and it can actually answer and actually do it.

Works with Poke, Claude Desktop, OpenClaw, Hermes, or anything else that speaks MCP.

Usage comes from macOS's own screen time data, which covers your iPhone too. Blocking goes through Opal — that's the only blocker wired up so far, and it sits behind an interface so adding another is a new file rather than a rewrite. See ROADMAP.md for what's next and why.

What it can do

Tool

What it's for

screentime_status

One call, whole picture. What's blocking, how long is left, today's total so far. Block status is this Mac only.

screentime_report

Time per app over any period. "today", "last 7 days", "this week", a date, a range. Covers your iPhone too.

screentime_schedules

Your recurring blocks, when they run, which are paused.

screentime_blocked_apps

What's on the blocklist right now.

screentime_start_focus

Start a focus block. Takes "45m", "1h30m", "2 hours", or a plain number.

screentime_stop_focus

End the block that's running.

screentime_check_setup

What's working and what isn't. Run this when something's off.

screentime_debug

Shows the shape of the blocker's files. Only useful when a number looks wrong.

Read this before you install it

Opal has no API. No developer docs, no keys, nothing to sign up for, and their terms say not to automate against their servers. So this doesn't talk to Opal at all. It works two other ways, both on your own machine:

  • Reading macOS's screen time data and Opal's own files, straight off your disk. Read-only.

  • Controlling blocks through Apple Shortcuts, which is the automation route Opal themselves point people at.

Two things follow from that, and they're worth knowing up front:

  1. It has to run on your Mac. It reads local files, so it can't run on a server somewhere. For Poke that means a tunnel — see below.

  2. Starting and stopping blocks needs Opal's Shortcuts actions, and those have shown up on iPhone and iPad before the Mac app. If the actions aren't in your Mac's Shortcuts app yet, reading works fine and controlling doesn't. screentime_check_setup will tell you which situation you're in. There's no way around it from here — that part is Opal's to ship.

Reading your screen time works regardless.

Install

git clone https://github.com/lewisnsmith/opal-mcp.git
cd opal-mcp
npm install
npm run build

The repo is still called opal-mcp on GitHub; the package is screentime-mcp.

Then check what works on your machine:

npm run smoke

That starts the server the way an agent would and calls every read-only tool. It'll tell you what's missing.

Give it permission to read your screen time

The usage numbers live in a database macOS keeps locked down. Whatever app runs this server needs Full Disk Access:

System Settings → Privacy & Security → Full Disk Access, switch on the app that runs it — Terminal, iTerm, Claude Desktop, whichever — then restart that app. The restart matters, it won't take effect otherwise.

Without it you'll get a clear message saying so rather than a screen time total of zero.

Hook it up to Poke

Poke needs a public https URL, and this runs on your Mac, so you need a tunnel.

# terminal 1
MCP_AUTH_TOKEN=pick-a-long-random-string npm run serve

# terminal 2
cloudflared tunnel --url http://localhost:8000

Take the https URL cloudflared prints, then in Poke go to settings → connections → new:

  • URL: the tunnel URL with /mcp on the end. https://something.trycloudflare.com/mcp

  • Authorization: Bearer pick-a-long-random-string

The /mcp is not optional. Leaving it off is the single most common reason Poke says it can't see any tools.

Then tell Poke: "use the screentime integration's screentime_status tool". Naming the integration explicitly helps the first time.

If Poke keeps acting like it's still looking at an old version of the connection, send it clearhistory.

ngrok http 8000 works the same way if you'd rather use that.

Hook it up to Claude Desktop, OpenClaw, or Hermes

These run things locally, so no tunnel and no token needed.

{
  "mcpServers": {
    "screentime": {
      "command": "node",
      "args": ["/absolute/path/to/opal-mcp/dist/index.js"]
    }
  }
}

For Claude Desktop that goes in ~/Library/Application Support/Claude/claude_desktop_config.json. Other runners use the same shape — point them at the same command and args.

Your iPhone

Most people's screen time is mostly the phone, so this matters more than the Mac side.

Seeing your iPhone's screen time works already. Turn on Settings → Screen Time → Share Across Devices on the phone. Your iPhone usage syncs into the Mac's Screen Time store, and screentime_report reads it — you'll get one combined total with a per-device split, and device: "iphone" narrows to just the phone.

One thing worth knowing about how that's put together: several databases hold overlapping copies of this, so the numbers come from exactly one of them, never several added up. Summing them would count your Mac twice and inflate every total. The iCloud store wins when it's there, because it's the only one that knows about the phone.

This is also why the whole thing runs on your Mac rather than somewhere in the cloud. Apple doesn't let an app send Screen Time data off the phone — but the phone will sync it to your Mac over iCloud, so that's the only place it can be read from.

Blocking your iPhone works through a Focus mode. Opal's Shortcuts actions only exist on iOS, so there's nothing on the Mac to call directly. The way round it:

  1. On the iPhone, open Opal and bind a block to a Focus mode — that's Opal's Focus Filter setting.

  2. On the Mac, make a shortcut with the Set Focus action turning that same Focus on. Name it Opal Focus On.

  3. Do it again for off, named Opal Focus Off.

  4. Check Focus is set to share across devices, or the Mac flipping it won't reach the phone.

Then screentime_start_focus flips the Focus, the Focus syncs to the phone, and Opal starts the block there.

The honest caveat: screentime_status reads Opal's files on this Mac, so it can't confirm a block that's running on your phone. The tool says so in its reply rather than sending your agent looking for something it'll never find. Your iPhone's screen time still shows up fine — it's only the block status that's Mac-only.

Let it start and stop blocks

Reading works out of the box. Controlling needs a one-time setup, because the only sanctioned way in is Shortcuts.

  1. Open the Shortcuts app.

  2. New shortcut, add Opal's Start Session action.

  3. Name it exactly Opal Start Session.

  4. Do it again with End Session, named Opal End Session.

Prefer different names? Set SCREENTIME_SHORTCUT_START and SCREENTIME_SHORTCUT_STOP instead.

If you can't find Opal's actions in the Shortcuts app, they aren't in your Mac build yet. See the note near the top.

Settings

All optional, all environment variables.

SCREENTIME_TIMEZONE

Which timezone your days run in. Defaults to your machine's. Get this wrong and every number lands on the wrong day.

PORT

HTTP port. Default 8000.

HOST

HTTP bind address. Default 127.0.0.1, so nothing off your machine can reach it directly. Tunnels connect to localhost, so you rarely need to change this.

MCP_AUTH_TOKEN

Require this as the bearer token in HTTP mode. Set it before you put a tunnel in front of this, or anyone with the URL can read your screen time and start blocks.

SCREENTIME_SHORTCUT_START

Name of your start shortcut, if it isn't the default.

SCREENTIME_SHORTCUT_STOP

Name of your stop shortcut.

SCREENTIME_MCP_DEBUG=1

Chattier logging, on stderr.

The old OPAL_ names still work if you already set them. SCREENTIME_ wins where both are set.

Renamed from opal-mcp

Half of this was never Opal — the usage numbers come from macOS. If you set this up before the rename:

  • Tools went from opal_* to screentime_*. opal_screen_time is now screentime_report, opal_start_block is screentime_start_focus, opal_stop_block is screentime_stop_focus, opal_list_schedules is screentime_schedules, opal_debug_files is screentime_debug. The rest just swapped the prefix.

  • Environment variables went from OPAL_ to SCREENTIME_, and the old ones still work.

  • If Poke is still showing the old tools, send it clearhistory.

When something's wrong

Run screentime_check_setup first. It checks all three moving parts separately and names the broken one.

"Can't find Opal on this machine" — Opal needs to be installed and opened at least once, and this has to be running on that same Mac.

Screen time won't read — Full Disk Access, see above. Remember to restart the app afterwards.

Poke sees no tools — check the URL ends in /mcp, check the tunnel is still up, then send Poke clearhistory.

Numbers look wrong — run screentime_debug. Opal never published their file format, so this shows what's actually on your disk. It prints key names only, never values, so it's safe to paste into an issue.

How much to trust this

Being straight with you about the shaky parts:

  • The screen time numbers are solid. They come from the same macOS database System Settings reads, using a query that's been in forensics tooling for years.

  • The Opal file parsing is educated guesswork. Opal doesn't document their format. The field names came from one small open source project doing the same thing. So every field is optional, one weird file can't take the rest down, and screentime_debug exists for when it disagrees with reality. If you spot a wrong number, that tool's output is exactly what's needed to fix it.

  • Starting a block reports that the shortcut ran, not that Opal listened. Those aren't the same claim. Opal's harder modes are deliberately difficult to cancel, and that's the point of them. Check screentime_status after.

Development

npm test          # 136 tests, no Mac required, uses fixtures
npm run typecheck
npm run dev       # run from source over stdio
npm run smoke     # check against your real machine

The tests build a real SQLite database with Apple's actual table names and run the real query against it, and stand up a real MCP client against both transports. They don't mock the parts that matter.

Licence

MIT