{"schema_version":"draft-focus-1","name":"Focus Keys","version":"0.1.0","description":"Agent-controlled Screen Time for iPhone. The user groups distracting apps in the Focus Keys app; the agent blocks groups for a while, sets recurring windows, and decides the timed passes the phone asks for — so the AI, not the user's willpower, holds the keys.","publisher":{"name":"Keelcroft","website":"https://keelcroft.com","support_email":"support@keelcroft.com","privacy_policy":"https://focus.keelcroft.com/privacy","terms":"https://focus.keelcroft.com/terms"},"companion_app":{"platform":"ios","minimum_os":"16.0","url":"https://focus.keelcroft.com/app","required":true},"auth":{"type":"oauth2","authorization_server_metadata":"https://focus.keelcroft.com/.well-known/oauth-authorization-server","authorization_endpoint":"https://focus.keelcroft.com/authorize","token_endpoint":"https://focus.keelcroft.com/oauth/token","registration_endpoint":"https://focus.keelcroft.com/oauth/register","scopes":["focus:read","focus:manage","alerts:read"],"pkce_required":true,"api_key_fallback":{"header":"Authorization","scheme":"Bearer","obtain_at":"https://focus.keelcroft.com/account"}},"interfaces":{"mcp":{"transport":"streamable-http","url":"https://focus.keelcroft.com/mcp"},"rest":{"openapi":"https://focus.keelcroft.com/openapi.json","base_url":"https://focus.keelcroft.com/v1"}},"tools":[{"name":"get_status","title":"Get current status","description":"Returns the paired phones, every app group with its current state (open / blocked / on a pass), active blocks and passes, pending pass requests waiting for a decision, the pass policy and the subscription state. Call this first.","inputSchema":{"type":"object","properties":{}},"annotations":{"title":"Get current status","readOnlyHint":true,"destructiveHint":false,"idempotentHint":true,"openWorldHint":false},"requires_user_approval":false,"required_scope":"focus:read"},{"name":"list_groups","title":"List app groups","description":"Lists the app groups the user built on the phone (name, kind, how many apps/categories/websites). Apple never reveals which apps are inside a group — only the counts — so refer to groups by name.","inputSchema":{"type":"object","properties":{}},"annotations":{"title":"List app groups","readOnlyHint":true,"destructiveHint":false,"idempotentHint":true,"openWorldHint":false},"requires_user_approval":false,"required_scope":"focus:read"},{"name":"list_schedules","title":"List recurring blocks","description":"Lists recurring block windows (e.g. \"Weeknights: Social, Sun–Thu 21:30–07:00\"). Windows whose end is before their start cross midnight.","inputSchema":{"type":"object","properties":{}},"annotations":{"title":"List recurring blocks","readOnlyHint":true,"destructiveHint":false,"idempotentHint":true,"openWorldHint":false},"requires_user_approval":false,"required_scope":"focus:read"},{"name":"get_history","title":"Get history and pattern facts","description":"Returns recent blocks and pass requests plus plain facts: how many blocks were ended early, how many passes were granted/denied/unanswered, the most-requested group and the hour requests cluster in. Facts only — use them to coach, not to judge.","inputSchema":{"type":"object","properties":{"days":{"description":"How many days back to look (default 7).","type":"integer","minimum":1,"maximum":90}}},"annotations":{"title":"Get history and pattern facts","readOnlyHint":true,"destructiveHint":false,"idempotentHint":true,"openWorldHint":false},"requires_user_approval":false,"required_scope":"focus:read"},{"name":"get_recent_alerts","title":"Get recent alerts","description":"Returns proactive events: pass_requested (needs a decision), pass_granted/denied/expired, block_started/ended/extended/failed, phone_unreachable, screen_time_permission_changed, device_paired/unpaired. Poll with since (ISO-8601) to get only new events.","inputSchema":{"type":"object","properties":{"since":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"},"types":{"description":"Only these event types.","maxItems":20,"type":"array","items":{"type":"string"}},"limit":{"type":"integer","minimum":1,"maximum":100}}},"annotations":{"title":"Get recent alerts","readOnlyHint":true,"destructiveHint":false,"idempotentHint":true,"openWorldHint":false},"requires_user_approval":false,"required_scope":"alerts:read"},{"name":"block_now","title":"Block a group now","description":"Shields an app group on the phone right away for a number of minutes (15–720). If the group is already blocked, the block is extended when the new end is later. The phone applies it within seconds when online. The user must approve this action.","inputSchema":{"type":"object","properties":{"group":{"description":"Group id or name from list_groups (e.g. \"Social\"). Optional when the account has exactly one group.","type":"string","minLength":1},"minutes":{"type":"integer","minimum":15,"maximum":720,"description":"How long to block."},"reason":{"description":"Shown on the phone, e.g. \"Report due at 9\".","type":"string","maxLength":140}},"required":["minutes"]},"annotations":{"title":"Block a group now","readOnlyHint":false,"destructiveHint":false,"idempotentHint":false,"openWorldHint":true},"requires_user_approval":true,"required_scope":"focus:manage"},{"name":"end_block","title":"End a block early","description":"Lifts a block before its planned end. Prefer grant_pass (a timed unlock that re-locks automatically) — use this only when the user clearly wants the block gone. The user must approve this action.","inputSchema":{"type":"object","properties":{"block_id":{"description":"From get_status.active_blocks. Optional when group is given.","type":"string","minLength":1},"group":{"description":"Group id or name from list_groups (e.g. \"Social\"). Optional when the account has exactly one group.","type":"string","minLength":1},"note":{"type":"string","maxLength":140}}},"annotations":{"title":"End a block early","readOnlyHint":false,"destructiveHint":true,"idempotentHint":false,"openWorldHint":true},"requires_user_approval":true,"required_scope":"focus:manage"},{"name":"grant_pass","title":"Grant a pass","description":"Unlocks a group for a limited time (15–120 minutes), then it re-locks automatically. Pass pass_id to answer a pending request from the phone (minutes may be shortened), or just a group to give an unsolicited break. Say why in note — it is shown on the phone.","inputSchema":{"type":"object","properties":{"pass_id":{"description":"Pending request id from get_status.pending_pass_requests or a pass_requested alert.","type":"string","minLength":1},"group":{"description":"Group id or name from list_groups (e.g. \"Social\"). Optional when the account has exactly one group.","type":"string","minLength":1},"minutes":{"description":"Length; defaults to the requested minutes or the account default.","type":"integer","minimum":15,"maximum":120},"note":{"description":"Shown on the phone, e.g. \"15 min, then back to the report.\"","type":"string","maxLength":140}}},"annotations":{"title":"Grant a pass","readOnlyHint":false,"destructiveHint":false,"idempotentHint":false,"openWorldHint":true},"requires_user_approval":false,"required_scope":"focus:manage"},{"name":"deny_pass","title":"Deny a pass request","description":"Refuses a pending pass request. Give a short, kind reason in note — it is shown on the phone.","inputSchema":{"type":"object","properties":{"pass_id":{"type":"string","minLength":1},"note":{"type":"string","maxLength":140}},"required":["pass_id"]},"annotations":{"title":"Deny a pass request","readOnlyHint":false,"destructiveHint":false,"idempotentHint":true,"openWorldHint":false},"requires_user_approval":false,"required_scope":"focus:manage"},{"name":"set_schedule","title":"Create or update a recurring block","description":"Creates a recurring block window for a group (or updates one when schedule_id is given). Times are the phone's local time; an end before the start crosses midnight (21:30–07:00). Each window must last at least 15 minutes. Windows on fewer than 7 days cost one monitor per day (Apple allows 20 per phone).","inputSchema":{"type":"object","properties":{"schedule_id":{"type":"string","minLength":1},"group":{"description":"Group id or name from list_groups (e.g. \"Social\"). Optional when the account has exactly one group.","type":"string","minLength":1},"name":{"description":"e.g. \"Weeknights\", \"Deep work\"","type":"string","minLength":1,"maxLength":40},"days":{"description":"Days the window starts on: [\"mon\",\"tue\",...], or \"weekdays\", \"weekends\", \"daily\" (default daily).","anyOf":[{"type":"array","items":{"type":"string"}},{"type":"string"}]},"start":{"type":"string","pattern":"^([01]?\\d|2[0-3]):[0-5]\\d$","description":"Local time on the phone, HH:MM 24-hour"},"end":{"type":"string","pattern":"^([01]?\\d|2[0-3]):[0-5]\\d$","description":"Local time on the phone, HH:MM 24-hour"},"enabled":{"type":"boolean"}}},"annotations":{"title":"Create or update a recurring block","readOnlyHint":false,"destructiveHint":false,"idempotentHint":true,"openWorldHint":false},"requires_user_approval":false,"required_scope":"focus:manage"},{"name":"delete_schedule","title":"Delete a recurring block","description":"Removes a recurring block window. The user must approve this action.","inputSchema":{"type":"object","properties":{"schedule_id":{"type":"string","minLength":1}},"required":["schedule_id"]},"annotations":{"title":"Delete a recurring block","readOnlyHint":false,"destructiveHint":true,"idempotentHint":true,"openWorldHint":false},"requires_user_approval":true,"required_scope":"focus:manage"},{"name":"set_settings","title":"Change pass policy and settings","description":"Changes how the phone handles pass requests when the agent is not asked: ask_agent (default — requests wait for grant_pass/deny_pass and expire after request_timeout_minutes), auto_grant (granted up to daily_pass_budget_minutes per day), always_deny. agent_name is how the shield refers to you. alert_email receives every pass request (set it to the agent's own inbox to be woken up).","inputSchema":{"type":"object","properties":{"pass_policy":{"type":"string","enum":["ask_agent","auto_grant","always_deny"]},"default_pass_minutes":{"type":"integer","minimum":15,"maximum":120},"daily_pass_budget_minutes":{"type":"integer","minimum":0,"maximum":600},"request_timeout_minutes":{"type":"integer","minimum":2,"maximum":120},"agent_name":{"anyOf":[{"type":"string","minLength":1,"maxLength":30},{"type":"null"}]},"alert_email":{"anyOf":[{"type":"string","format":"email","pattern":"^(?:[A-Za-z0-9_'+\\-]+\\.)*[A-Za-z0-9_'+\\-]*[A-Za-z0-9_+-]@(?:[A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},{"type":"null"}]}}},"annotations":{"title":"Change pass policy and settings","readOnlyHint":false,"destructiveHint":false,"idempotentHint":true,"openWorldHint":false},"requires_user_approval":false,"required_scope":"focus:manage"},{"name":"create_pairing_code","title":"Create a phone pairing code","description":"Creates a 6-character code (valid 10 minutes) the user types into the Focus Keys iOS app to attach their phone to this account. Read the code back to the user.","inputSchema":{"type":"object","properties":{}},"annotations":{"title":"Create a phone pairing code","readOnlyHint":false,"destructiveHint":false,"idempotentHint":false,"openWorldHint":false},"requires_user_approval":false,"required_scope":"focus:manage"}],"proactive_events":{"enabled":true,"poll":"https://focus.keelcroft.com/v1/alerts?since={iso8601}","email":"Optional: set_settings.alert_email receives every pass request so an agent inbox can wake the agent.","types":["pass_requested","pass_granted","pass_denied","pass_expired","pass_started","pass_ended","pass_failed","block_started","block_extended","block_ended","block_failed","unshield_failed","phone_unreachable","schedule_changed","screen_time_permission_changed","device_paired","device_unpaired"]},"data_handling":{"stores":["account email","phone name/model/OS and push token","app-group names, kinds and counts (never the apps themselves — Apple's selection tokens stay on the device)","schedules, blocks, passes and their reasons/notes","command and event log"],"does_not_collect":["which apps are in a group","screen-time minutes or app usage (these never leave the phone)","location","contacts"],"consent":"The user grants Screen Time permission on their own phone and pairs it with a code; unpairing revokes everything.","retention":"until the user unpairs the phone or deletes the account (all rows removed); the event log is kept 90 days","sharing":"none beyond Expo (push delivery), Resend (email) and the payment provider (billing)","not_medical":"Focus Keys is a productivity tool, not a medical, mental-health or parental-control product."}}