Skip to main content

focuslens-mcp

The local context layer that tells your AI tools when to leave you alone.

Your copilot interrupts you the same way whether you're in deep flow or fried at 4pm. It has context on your code — and zero context on you. FocusLens gives any MCP-aware agent (Claude, Cursor, …), any editor, and any script a live, on-device read of your cognitive state, so tools can adapt:

  • In flow → hold interruptions, keep answers terse, bigger steps are fine
  • Fatiguing → scaffold more, prefer small safe diffs, suggest a break
  • should_interrupt() → the one call a tool makes before pinging you

Privacy is the whole design: the state is computed on-device from typing rhythm (timing metadata — never keystrokes, content, or app text) and served only on 127.0.0.1. Nothing leaves your machine. This package contains no tracking code at all — it only reads the state file the FocusLens app publishes, and returns safe defaults when it's absent.

Install

pip install focuslens-mcp          # CLI + HTTP server (stdlib-only)
pip install "focuslens-mcp[mcp]"   # + the MCP server

MCP server (Claude, Cursor, anything MCP-aware)

30-second setup — print a ready-to-paste config for your client:

focuslens mcp-config                 # Claude Desktop (default) — tells you the file + path
focuslens mcp-config --client cursor # Cursor

…or add it by hand:

// e.g. in your MCP client config
{ "mcpServers": { "focuslens": { "command": "focuslens-mcp" } } }

Tools exposed — each documented so agents call them before deciding whether (and how) to engage:

  • Cognitive state & pacing: get_cognitive_state, should_interrupt, how_to_help, get_focus_forecast, recommend_break
  • Focus analytics (from the on-device session log): today_summary, week_digest, focus_rhythm, next_focus_window, goal_status, coach
  • Open standard: get_cogctx_state — the same context as a vendor-neutral cogctx/v1 packet
  • Neurology research desk: neurology_research_agent, healthcare_research_agent — a guarded Research Desk for support only; it is not clinical advice, diagnosis, treatment, dosing, or emergency triage.

Resources: focuslens://state and cogctx://state.

CLI — compose it with anything

focuslens should-interrupt && notify-send "build done"   # only ping me out of flow
focuslens wait-until-interruptible && notify-send "build done"   # HOLD it until I'm free
claude -p "$(focuslens directive) Refactor this."        # hand any LLM your state
focuslens state | jq .agent_directives                   # the raw context
focuslens research-agent                                 # Neuro Research Desk packet
focuslens neurology-agent                                # same packet, explicit specialty
focuslens neurology-agent --question "migraine aura and stroke risk in adults under 45"
focuslens neurology-handoff --demo                       # Markdown clinician-review handoff packet
focuslens neurology-audit --demo                         # JSON audit manifest with packet ID/hash
focuslens watch                                          # live-print the state as it changes
focuslens doctor                                         # setup/status checklist

should-interrupt exits 0 when it's fine to interrupt and 1 in deep flow — so it drops into shell scripts, git hooks, and notification daemons.

Is my integration actually working?

Wire up FocusGate, see nothing happen, and you can't tell whether your code is broken or you just haven't changed state yet. watch puts the signal on screen:

focuslens watch            # one line each time the state changes
focuslens demo-state deep-focus   # ...in another terminal, to drive it
TIME      STATE            SCORE  INTERRUPT VERBOSITY  NOTE
21:56:21  unavailable          —  ok        —          FocusLens not running
21:56:23  Deep Focus          84  hold      terse
21:56:25  Fatigued            36  ok        scaffolded
21:56:27  Distracted          45  ok        normal

It names why there's no signal — "FocusLens not running" and "stale reading" need different fixes.

Testing your integration

Simulating states by hand is how fixtures drift — every hand-rolled one in this repo was missing fields, and passed only because nothing read them. Use the supported doubles instead; they're built through the real pipeline, so they can't drift from what /state actually returns:

from agent_api.testing import fake_gate, unavailable_gate, stale_gate

def test_my_agent_stays_quiet_in_flow():
    assert my_agent.should_ping(fake_gate("deep-focus")) is False

def test_my_agent_still_works_without_focuslens():   # the case people forget
    assert my_agent.should_ping(unavailable_gate()) is True

sequence_gate(...) returns a different state on each read, for code that reacts to change. stale_gate() covers "the app stopped" — distinct from "never started", because they need different handling.

Don't just check — wait

Every other call answers "may I interrupt right now?", which leaves you writing a polling loop for the thing FocusLens is actually for: hold this until they're out of flow.

focuslens wait-until-interruptible && notify-send "build done"   # holds it for you
focuslens wait-until-interruptible --timeout 600                 # exit 1 if still in flow
result = gate.wait_until_interruptible(timeout=600)
if result["timed_out"]:
    queue_for_later()          # still deep in flow — don't barge in
else:
    notify("build finished")   # they surfaced
curl -s "localhost:7113/wait-until-interruptible?timeout=600"

It returns the moment they leave flow (not on a fixed schedule), returns immediately if they're already free, and — because no signal must never mean wait forever — returns immediately when FocusLens isn't running or the reading is stale. Waits are capped at 900s; call again to keep holding.

Python: drop it into your agent loop

FocusGate is a dependency-free wrapper (stdlib only) that turns the live state into decisions. Safe by construction — when FocusLens isn't running, every method returns the neutral default, so wiring it in never changes behavior with no signal.

from agent_api.focus_gate import FocusGate

gate = FocusGate()                              # http://127.0.0.1:7113/state

if gate.should_interrupt():                     # False in deep flow, True otherwise/off
    notify("build finished")

system_prompt = gate.system_prompt(base=MY_SYSTEM_PROMPT)   # base + a focus-state addendum
plan = gate.plan()                              # {defer_interruptions, verbosity, prefer_small_steps, ...}

gate.should_interrupt(urgent=True) always returns True — real blockers always surface. For fuller worked examples — a LangChain callback, a Claude Code hook, and a reference adaptive_agent.py — see the examples/ directory in the project repository. (They ship with the source, not inside this package.)

For demos, publish a canned state without launching the desktop sensor:

focuslens demo-state deep-focus
focuslens directive
# [FocusLens context — the user is in Deep Focus ... Do not interrupt ...]

focuslens demo-state fatigued
focuslens directive
# ... scaffold the work into small, clear steps.

Local HTTP API (any language)

curl -s localhost:7113/should-interrupt   # {"interrupt_ok": false, "reason": "..."}
curl -s localhost:7113/directive          # one-line instruction for any AI
curl -s localhost:7113/state              # full context + fatigue forecast
curl -s localhost:7113/research-agent     # Neuro Research Desk packet
curl -s localhost:7113/neurology-agent    # same packet, explicit specialty
curl -s localhost:7113/neurology-sources?question=migraine+aura
curl -s localhost:7113/neurology-demo     # polished investor/demo packet
curl -s localhost:7113/neurology-handoff?demo=1
curl -s localhost:7113/neurology-audit?demo=1
curl -s localhost:7113/llm-status
curl -s localhost:7113/neurology-llm-draft?question=migraine+aura
curl -s localhost:7113/review-packets     # local packet history
curl -s localhost:7113/production-readiness.json
curl -s localhost:7113/production-preflight.json
curl -s localhost:7113/launch-handoff.json
curl -s localhost:7113/launch-handoff.md
curl -s localhost:7113/vc-suite.json
curl -s localhost:7113/vc-suite.md
curl -s "localhost:7113/neurology-agent?question=migraine+aura+and+stroke+risk+in+adults+under+45"

Start it with python -m agent_api.server (loopback only, dependency-free). After installing the package, focuslens serve starts the same local API.

Open http://127.0.0.1:7113/vc-suite for the fastest investor-ready path, or http://127.0.0.1:7113/launch for the first-run Launch Center. From there you can open http://127.0.0.1:7113/demo for the local agent console or http://127.0.0.1:7113/neurology for the Research Desk UI; it loads the investor-grade sample packet from /neurology-demo by default and can export /neurology-handoff?demo=1 as a Markdown handoff packet plus /neurology-audit?demo=1 as a packet ID/hash manifest.

Set FOCUSLENS_REVIEW_TOKEN to require Authorization: Bearer ... for mutating review endpoints. Packet history is stored locally in SQLite under the FocusLens data directory; the HTTP endpoint does not persist raw packets unless the user saves them.

Optional FreeLLMAPI / OpenAI-compatible bridge

The Neuro Research Desk can call an optional OpenAI-compatible chat endpoint for drafting help. It is disabled unless both environment variables are set:

export FOCUSLENS_LLM_BASE_URL="http://localhost:3001/v1"
export FOCUSLENS_LLM_API_KEY="<unified FreeLLMAPI key>"
export FOCUSLENS_LLM_MODEL="auto:balanced"

Use /llm-status to inspect configuration and /neurology-llm-draft to draft from the guarded research packet. FocusLens redacts common direct identifiers and refuses urgent/clinical medical prompts before any LLM call, but any enabled bridge may still send the de-identified research packet to external providers behind your router.

If anything looks off, run focuslens doctor. It prints the live-state path, whether context is available, whether the local API responds, whether the MCP SDK is installed, and the next commands to try.

Use focuslens production-readiness or /production-readiness for stage readiness across GitHub Pages, signed/notarized macOS release, enterprise production, and medical/compliance approval. Use focuslens production-preflight or /production-preflight for the operator checklist covering Stripe, SSO, monitoring, standalone Mac QA, website deploy, source connectors, and clinical/legal gates. Use focuslens launch-handoff, focuslens launch-handoff --markdown, or /launch-handoff for the combined investor/operator/review bundle.

Neuro Research Desk

focuslens research-agent, focuslens neurology-agent, /research-agent, /neurology-agent, /neurology-sources, /neurology-demo, /neurology-handoff, /neurology-audit, /neurology-llm-draft, /review-packets, and the MCP neurology_research_agent tool expose the neurology research workflow as a safety-bounded desk packet: evidence worklist, source-quality rubric, claim ledger, red-flag education boundaries, neurologist-review questions, uncertainty flags, and FocusLens-aware pacing. It intentionally refuses to be a diagnostic or treatment agent.

Pass --question or ?question= to generate a structured research packet with PubMed/ClinicalTrials/FDA/NIH source searches, claim-support requirements, red-flag boundary language, and questions to bring to a neurologist.

/neurology-sources performs live no-key source metadata pulls from PubMed (NCBI E-utilities), ClinicalTrials.gov API v2, openFDA drug labels, and an honest NIH/NINDS education-search fallback where a general JSON API is not available.

Where the state comes from

Stale readings are treated as no signal. The app republishes its state every few seconds, so a reading older than 5 minutes means it isn't running — the context reports available: false (with stale: true and age_seconds) rather than serving last night's state as if it were live. Without that, a "Deep Focus" written before you closed your laptop would keep should_interrupt() returning false and silently swallow every notification. Tune or disable the cutoff with FOCUSLENS_MAX_STATE_AGE_SECONDS (0 disables it).

The FocusLens app watches your typing rhythm on-device (a Kalman-filtered personal baseline + an HMM over work states) and writes a small JSON state file every cycle. This package is the open interface to that file. No app running? Every surface degrades gracefully to "state unavailable — respond normally."

By default, the live state is read from the standard local app-data directory (~/Library/Application Support/FocusLens/focuslens_live_state.json on macOS). Set FOCUSLENS_DATA_DIR for tests, custom installs, or portable/dev runs.

License

MIT (this package). The FocusLens app itself is licensed separately.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

focuslens_mcp-0.1.0.tar.gz (227.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

focuslens_mcp-0.1.0-py3-none-any.whl (232.8 kB view details)

Uploaded Python 3

File details

Details for the file focuslens_mcp-0.1.0.tar.gz.

File metadata

  • Download URL: focuslens_mcp-0.1.0.tar.gz
  • Upload date:
  • Size: 227.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.12.3

File hashes

Hashes for focuslens_mcp-0.1.0.tar.gz
Algorithm Hash digest
SHA256 9c44cf4e6cf227aec44dac90edf9a699cd633aed7b1f068cd07ade73304d6c83
MD5 5d4d19993589517dfc9745d554d1422c
BLAKE2b-256 594f9e182ca052bc6cce46e1ad857a490b4a79dd31b55b1bb9207a626806f5af

See more details on using hashes here.

File details

Details for the file focuslens_mcp-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: focuslens_mcp-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 232.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.12.3

File hashes

Hashes for focuslens_mcp-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 b9566d733a6252adb9934aa0c7b160dbda87873f984a58275ee77e0f345e0978
MD5 923860de836176b2cd90a502dc5e8295
BLAKE2b-256 9276930f49ecd53977ba44513cb18210e0f4289bf3f0a3b85db0992e5f40fb83

See more details on using hashes here.

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page