lockin-mcp
LockIn MCP
A Model Context Protocol (MCP) server that blocks distracting websites at the system level by managing entries in /etc/hosts. Use it with Poke, Claude Desktop, or ChatGPT custom MCP connectors to stay focused.
Features
| Tool | Description |
|---|---|
get_agent_guidelines |
Coaching instructions for AI agents (when to enforce focus, how to handle unblock requests) |
block_domains |
Block a list of domains (e.g. youtube.com, x.com, tiktok.com) |
unblock_domains |
Permanently unblock specific domains |
temporarily_unblock_domains |
Allow access for a limited time, then auto re-block |
enter_focus_mode |
Block common distraction sites in one action (YouTube, X, TikTok, Instagram, Reddit, Facebook, Netflix, Twitch) |
get_block_status |
Show what is blocked, temporarily allowed, and system readiness |
Blocking redirects domains to 127.0.0.1 and ::1, including www. variants.
Pro license required: block_domains, unblock_domains, temporarily_unblock_domains, and enter_focus_mode require an active Pro license. get_agent_guidelines and get_block_status are read-only and work without Pro. For local development only, set MDB_SKIP_LICENSE_CHECK=1.
Requirements
- macOS / Linux / Windows (hosts-based blocking; macOS uses
dscacheutilfor DNS flush) - Node.js 18+
- Administrator privileges to modify
/etc/hosts(see below)
Web App & Pro Licensing
The web/ directory contains the landing page, user accounts, Stripe checkout, and license API.
Quick start (local)
# Terminal 1 — web app
cd web
cp ../.env.example .env.local # edit with your Stripe keys
npm install
npm run dev # http://localhost:3001
# Terminal 2 — CLI installer (points at local API by default)
export MDB_APP_URL=http://localhost:3001
export MDB_LICENSE_API_URL=http://localhost:3001/api/v1
npm run build
npm run install:local
# or from GitHub (no clone):
# npx -y lockin-mcp install
Architecture
| Component | Purpose |
|---|---|
Landing page (/) |
Features, how-it-works, pricing |
Device auth (/device) |
Browser login for CLI OAuth device flow |
Dashboard (/dashboard) |
License keys, Pro status, install instructions |
Report (/report) |
Bug reports and feature requests (rate-limited; not indexed) |
MCP OAuth consent (/oauth/consent) |
Browser approval for ChatGPT / Claude web MCP connectors |
Checkout (/checkout) |
Stripe checkout (requires verified email) |
API (/api/v1/*) |
License verify, OAuth device flow, token status |
| Stripe webhook | Pro upgrade + license key generation |
CLI auth handshake
- Installer calls
POST /api/v1/oauth/device→ receivesuser_code+device_code - Browser opens
/device?code=ABCD-1234 - User signs in and approves (requires Pro)
- Installer polls
POST /api/v1/oauth/token→ receivesaccess_token - Daemon verifies token via
POST /api/v1/license/statuson startup and hourly
MCP OAuth (ChatGPT, Claude web)
- MCP client registers via DCR at
POST /api/v1/oauth/register(proxied from relay atPOST /register) - Client discovers metadata from
/.well-known/oauth-authorization-server(web app and relay) - User authorizes at
/api/v1/oauth/authorize→ browser consent at/oauth/consent - Client exchanges code at
POST /api/v1/oauth/token - MCP requests to relay use OAuth access tokens or the device
mdb_*API key
Environment variables
See .env.example for all configuration options.
Production deployment
Use this checklist before pointing lockinmcp.com at live traffic.
1. Generate secrets (once)
openssl rand -base64 32 # MDB_JWT_SECRET
openssl rand -base64 32 # MDB_SERVER_SECRET
openssl rand -base64 32 # REGISTRATION_SECRET (relay)
Set Vercel and Convex production env vars separately in each dashboard — do not copy MDB_SERVER_SECRET from your laptop with a sync script (local dev secrets are different from production).
| Secret | Vercel | Convex prod |
|---|---|---|
MDB_SERVER_SECRET |
Yes — same value on both | Yes — npx convex env set MDB_SERVER_SECRET '…' --prod once |
MDB_JWT_SECRET |
No | Yes — Convex only |
STRIPE_* |
Yes | No |
2. Convex production
cd web
npx convex deploy # production only — not convex dev
Set env vars in the Convex dashboard → Production → Settings → Environment Variables. Do not run ./scripts/sync-convex-env.sh for production — it only targets your dev deployment and .env.local often has localhost URLs.
| Convex prod variable | Value |
|---|---|
MDB_APP_URL |
https://www.lockinmcp.com |
MDB_SERVER_SECRET |
Same string as Vercel |
MDB_JWT_SECRET |
Production JWT secret (Convex only) |
GOOGLE_* / GITHUB_* |
Production OAuth app credentials |
RESEND_API_KEY / RESEND_FROM_EMAIL |
Production Resend |
MDB_RELAY_URL / MDB_RELAY_PUBLIC_URL |
https://relay.lockinmcp.com |
REGISTRATION_SECRET |
Same as relay Worker |
One-off CLI (paste production values, not from .env.local):
cd web && npx convex env set MDB_APP_URL 'https://www.lockinmcp.com' --prod
3. Stripe live mode
In Stripe Dashboard (toggle Live):
| Variable | Where to get it |
|---|---|
STRIPE_SECRET_KEY |
Developers → API keys → sk_live_... |
STRIPE_PRICE_ID |
Products → one-time $9.99 price → price_... |
STRIPE_WEBHOOK_SECRET |
Developers → Webhooks → endpoint https://www.lockinmcp.com/api/stripe/webhook → whsec_... |
STRIPE_PROMOTION_CODE_ID |
Optional — locks one promo at checkout; leave unset so customers can enter any code (e.g. LOCKIN50) |
Webhook events: checkout.session.completed, checkout.session.created, checkout.session.expired (follow-up scheduling backup)
Abandoned checkout follow-up (24h email)
When a signed-in user starts Stripe Checkout but does not pay, a follow-up email is scheduled via Convex (ctx.scheduler.runAfter). After deploy:
cd web && npx convex deploy
./scripts/backfill-oauth-email-verified.sh --prod # one-time OAuth emailVerified backfill
Verify production:
- Convex
MDB_APP_URLishttps://www.lockinmcp.com(notlocalhost). - Vercel
MDB_SERVER_SECRET=== ConvexMDB_SERVER_SECRET. - Stripe webhook enables
checkout.session.createdandcheckout.session.expired. - Preview email:
./scripts/send-abandoned-checkout-preview.sh you@example.com - Dev smoke test (60s delay):
npx convex env set CHECKOUT_FOLLOW_UP_DELAY_MS 60000then./scripts/test-abandoned-checkout-schedule.sh <userId> - Inspect jobs:
./scripts/list-checkout-follow-ups.sh scheduled
Optional Convex env: CHECKOUT_FOLLOW_UP_DELAY_MS (default 86400000 = 24h).
4. OAuth (production callbacks)
| Provider | Redirect URI |
|---|---|
https://www.lockinmcp.com/api/auth/oauth/google/callback |
|
| GitHub | https://www.lockinmcp.com/api/auth/oauth/github/callback |
Add GOOGLE_* / GITHUB_* to Vercel and Convex production dashboards (not dev keys from .env.local).
5. App URLs (hosting + Convex)
MDB_APP_URL=https://www.lockinmcp.com
NEXT_PUBLIC_APP_URL=https://www.lockinmcp.com
MDB_LICENSE_API_URL=https://www.lockinmcp.com/api/v1
MDB_RELAY_PUBLIC_URL=https://relay.lockinmcp.com
MDB_RELAY_URL=https://relay.lockinmcp.com
The apex domain (lockinmcp.com) redirects to www in production.
6. Deploy web app
Deploy the web/ Next.js app to your host (Vercel, etc.) with all env vars from .env.example.
7. Smoke test
- Sign up → verify email → checkout with a real card (or Stripe test in test mode first)
- Confirm
/dashboardshows Pro + license key - Run installer:
curl -fsSL https://www.lockinmcp.com/install | bash - Approve device at
/device→ confirm MCP tunnel provisions
One-Line Installer
The fastest way to set up blocking, tunneling, and Poke integration:
macOS / Linux (curl)
curl -fsSL https://raw.githubusercontent.com/Kiog-Aser/LockIn/main/install.sh | bash
Or use the hosted installer (recommended):
curl -fsSL https://www.lockinmcp.com/install | bash
Windows (PowerShell)
iwr -useb https://raw.githubusercontent.com/Kiog-Aser/LockIn/main/install.ps1 | iex
npm
npx -y lockin-mcp@1.0.7 install
# or from a local clone:
npm run install:local
The npm package lockin-mcp (v1.0.7) replaces the legacy mac-distraction-blocker-mcp name. Legacy bin aliases (mac-distraction-blocker-mcp, mdb-install) still work.
What the installer does
- Detects your platform (macOS, Linux, or Windows)
- Verifies your $9.99 Pro license via browser login or license key
- Prompts for distraction sites — popular presets pre-selected (YouTube, X, TikTok, Instagram, Reddit) plus custom domains
- Applies initial blocks to
/etc/hosts - Starts the MCP HTTP server locally with an outbound relay connection (background LaunchAgent on macOS)
- Provisions a stable relay URL (
https://relay.lockinmcp.com/device/{id}/mcp) so Poke, Claude, and ChatGPT can reach your Mac - Connects your AI agent — Poke, Claude (web), and ChatGPT use OAuth via lockinmcp.com; Claude Desktop uses your API key via mcp-remote
Installer flags (non-interactive)
curl -fsSL .../install.sh | bash -s -- --yes --browser-login --poke-recipe
npx -y lockin-mcp install --license-key lockin_pro_xxx --sites all --poke-recipe
| Flag | Description |
|---|---|
--yes |
Non-interactive with defaults |
--license-key <key> |
Pro license key |
--browser-login |
Verify license via browser device flow (default) |
--skip-tunnel |
Local MCP only (no relay URL) |
--skip-block |
Skip initial domain blocking |
--skip-poke |
Skip Poke URL generation |
--port <number> |
Local MCP HTTP port (default 3000) |
--sites all |
Block all presets |
--custom-domains a.com,b.com |
Extra domains |
--poke-recipe |
Generate distraction-coach recipe URL |
--mcp-auth-token <token> |
Custom bearer token for tunneled MCP |
Setup details are saved to ~/.lockin/setup-manifest.json.
Pro licensing ($9.99 lifetime — 50% off $19.99)
- Purchase: Create an account at
/login(email or Google/GitHub;/signupredirects here), verify email, pay via Stripe - License key: Available on dashboard after purchase; pass
--license-keyto installer - Browser login: Default installer flow — OAuth device authorization in your browser
- Auth: Google and GitHub OAuth supported; email/password requires verification before checkout
- Support: Bug reports and feature requests at
/report(also linked in the site footer)
Quick Start (manual)
git clone https://github.com/Kiog-Aser/LockIn
cd LockIn
npm install
npm run build
Run locally (stdio — advanced)
For MCP clients that spawn a local subprocess:
node dist/index.js --stdio
MCP Client Configuration (after installer)
Your MCP URL and API key are in ~/.lockin/setup-manifest.json:
- MCP URL: from manifest — e.g.
https://relay.lockinmcp.com/device/abc123/mcp(Poke, Claude, ChatGPT) - API key:
mdb_…
Poke
The installer copies your relay MCP URL and opens poke.com/integrations/new. In Poke:
- Paste the MCP URL (ends with
/mcp) - Choose OAuth authentication (do not paste your API key in the integration form)
- Sign in on lockinmcp.com when prompted
- Install the LockIn recipe
Legacy CLI (optional): npx poke@latest mcp add 'https://YOUR_RELAY_HOST/device/YOUR_ID/mcp' -n 'LockIn MCP' with OAuth in the Poke UI.
Claude (claude.ai)
The installer opens a link that pre-fills the connector name and MCP URL (connectorName + connectorUrl). Claude web uses OAuth — do not paste your API key in Advanced settings. See the connector setup guide.
- Choose Claude (claude.ai) in the installer (or build the link with
connectorName=LockIn MCPand your encoded MCP URL) - Confirm the pre-filled MCP server URL (e.g.
https://relay.lockinmcp.com/device/abc123/mcp) - Leave Advanced OAuth settings empty. Click Add, then sign in on lockinmcp.com when Claude prompts (Pro license required)
- Start a new chat with LockIn enabled
For Bearer/API-key auth, use Claude Desktop instead — the installer can write claude_desktop_config.json automatically.
Claude Desktop
Claude Desktop only supports stdio MCP servers in claude_desktop_config.json — not direct url entries. The installer merges a config that uses mcp-remote to bridge your relay URL (requires Node.js 18+).
Config paths:
- macOS:
~/Library/Application Support/Claude/claude_desktop_config.json - Windows:
%APPDATA%\Claude\claude_desktop_config.json - Linux:
~/.config/Claude/claude_desktop_config.json
Manual example:
{
"mcpServers": {
"lockin-mcp": {
"command": "npx",
"args": [
"-y",
"mcp-remote@latest",
"https://relay.lockinmcp.com/device/YOUR_ID/mcp",
"--header",
"Authorization:${LOCKIN_MCP_AUTH_HEADER}"
],
"env": {
"LOCKIN_MCP_AUTH_HEADER": "Bearer mdb_..."
}
}
}
}
On Windows, use "command": "npx.cmd" instead of "npx". Quit Claude Desktop completely before editing, then reopen.
ChatGPT
Custom MCP connectors require Developer mode under Advanced settings. ChatGPT cannot accept credentials from an external link — copy from the installer or setup guide.
- Open ChatGPT → Settings → Connectors → Advanced
- Enable Developer mode
- Add connector → paste MCP server URL from your manifest
- Set Authentication to OAuth and leave DCR enabled — sign in on lockinmcp.com when prompted (do not paste your API key in OAuth fields)
- Start a new chat and enable the LockIn connector
Relay deployment (operators)
The relay/ package is a Cloudflare Worker + Durable Object that bridges remote MCP HTTP requests to each Mac daemon over an outbound WebSocket.
cd relay
npm install
npx wrangler secret put REGISTRATION_SECRET
npx wrangler secret put MDB_JWT_SECRET # optional, same as web app
npm run deploy
Set relay vars on Convex production in the dashboard. For local dev only: web/.env.local + ./scripts/sync-convex-env.sh (dev deployment).
Privileges & /etc/hosts
The server writes marked entries between:
# lockin-mcp BEGIN
...
# lockin-mcp END
Legacy installs may still show # mac-distraction-blocker-mcp markers; those are stripped on the next hosts sync.
Only those lines are touched; the rest of /etc/hosts is preserved.
Option A: Run the server with sudo (simplest)
sudo node dist/index.js --http --port 3000
For stdio MCP clients, configure the client to launch with sudo (see client config examples below).
Option B: Passwordless sudo for hosts updates (recommended)
The installer creates ~/.lockin/update-hosts.sh and a sudoers entry at /etc/sudoers.d/lockin-mcp-hosts that allows your user to run that script without a password. Temp files use the prefix lockin-mcp-hosts-{pid}.
If you installed manually, re-run the installer or configure sudoers to allow (replace YOUR_USER with your macOS username):
sudo visudo -f /etc/sudoers.d/lockin-mcp-hosts
Add:
YOUR_USER ALL=(ALL) NOPASSWD: /Users/YOUR_USER/.lockin/update-hosts.sh
(Adjust paths for Linux/Windows as needed.)
Verify permissions
Call get_block_status — hostsFileWritable: true means the server can update blocking without extra setup.
Legacy: local stdio (Claude Desktop)
~/Library/Application Support/Claude/claude_desktop_config.json:
{
"mcpServers": {
"lockin-mcp": {
"command": "node",
"args": ["/absolute/path/to/LockIn/dist/index.js", "--stdio"]
}
}
}
Security
- MCP bearer auth: HTTP MCP requires
MDB_MCP_AUTH_TOKENor--mcp-auth-token(mdb_*tokens). Relay-backed HTTP refuses to start without a token. - API rate limiting: Login, signup, OAuth (account + MCP), license verify, checkout, consent, and user-report routes are rate-limited.
- User reports:
POST /api/reportaccepts bug/feature submissions (5 per IP per hour). RequiresRESEND_API_KEYin production; falls back to Convex logs in dev. - Relay tokens: Device tokens are hashed at rest on the Cloudflare worker (SHA-256).
- Setup manifest: Written with
0o600permissions; contains your relay URL and API key — do not commit or share.
Hooking up to poke.com/kitchen
The installer tunnels your local MCP over HTTPS so Poke can connect remotely — no extra terminal tab. When you choose Poke during install, it copies your relay URL and opens the integrations page for OAuth setup.
Recipe / Kitchen (recommended)
Install the official LockIn recipe for the best Poke experience:
https://poke.com/r/KQ7myvC_Xpo
The recipe connects your MCP tools and coaches you through focus sessions. You can also build custom recipes in poke.com/kitchen using get_agent_guidelines, block_domains, get_block_status, etc.
Tool Examples
Get agent guidelines
No arguments. Returns focus-coaching instructions for AI agents — call at the start of focus or blocking conversations.
Block sites
{
"domains": ["youtube.com", "x.com", "tiktok.com", "reddit.com"]
}
Unblock one site
{
"domains": ["youtube.com"]
}
15-minute break
{
"domains": ["youtube.com"],
"duration_seconds": 900
}
Check status
No arguments. Returns blocked list, temporary allowances with expiry timestamps, and whether hosts is writable.
How it works
flowchart LR
A[MCP Client / Poke] --> R[Relay Worker]
R --> B[MCP Server on Mac]
B --> C[State JSON\n~/.lockin/]
B --> D[/etc/hosts]
B --> E[DNS cache flush]
C --> B
- State — Block list and temporary unblocks live in
~/.lockin/state.json - Hosts sync — Active blocks are written to
/etc/hostswith a managed marker - Expiry watcher — A background timer re-applies blocks when temporary unblocks expire (server process must be running)
- DNS flush — Best-effort
dscacheutil/mDNSResponderrefresh after changes
Development
npm install
npm run dev # watch TypeScript
npm run build
npm run lint
npm test # Vitest (root MCP tests)
cd web && npm test # web rate-limit tests
cd relay && npm test # relay token hashing tests
CI (.github/workflows/ci.yml) runs lint, test, build, and npm audit --audit-level=high for the MCP server, web app, and relay worker.
Reset local install state (development only):
npx lockin-mcp dev-reset # reset hosts, manifest, relay config
npx lockin-mcp dev-reset --full # also remove LaunchAgent / background service
npx lockin-mcp dev-reset --wipe-license # clear saved license
See relay/README.md for Cloudflare WebSocket hibernation details on the device relay worker.
Troubleshooting
| Issue | Fix |
|---|---|
EACCES on block/unblock |
Run with sudo or configure passwordless sudo (see above) |
| Site still loads | Hard-refresh browser; DNS cache may take a moment. Try private window. |
| Temp unblock didn't re-block | Keep the MCP server process running (expiry watcher runs in-process) |
| Poke can't connect | Re-run install and confirm OAuth completed in Poke. Check manifest relay URL + /health returns lockin-mcp with relay.connected: true. If offline, re-run npx lockin-mcp install once — the daemon self-heals reconnects automatically. |
| Wrong platform | macOS, Linux, and Windows are supported; DNS flush behavior differs by OS |
License
MIT