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
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
9c44cf4e6cf227aec44dac90edf9a699cd633aed7b1f068cd07ade73304d6c83
|
|
| MD5 |
5d4d19993589517dfc9745d554d1422c
|
|
| BLAKE2b-256 |
594f9e182ca052bc6cce46e1ad857a490b4a79dd31b55b1bb9207a626806f5af
|
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b9566d733a6252adb9934aa0c7b160dbda87873f984a58275ee77e0f345e0978
|
|
| MD5 |
923860de836176b2cd90a502dc5e8295
|
|
| BLAKE2b-256 |
9276930f49ecd53977ba44513cb18210e0f4289bf3f0a3b85db0992e5f40fb83
|