Metadata-Version: 2.4
Name: focuslens-mcp
Version: 0.1.0
Summary: The local context layer that tells your AI tools when to leave you alone — live cognitive state (focus/fatigue) for MCP agents, CLIs, and editors. On-device; no keystrokes, no content, nothing leaves your machine.
Author: NeuroSense
License-Expression: MIT
Project-URL: Homepage, https://pypi.org/project/focuslens-mcp/
Keywords: mcp,model-context-protocol,ai-agents,focus,developer-tools,context
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: Software Development :: Libraries
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Provides-Extra: mcp
Requires-Dist: mcp<3.0,>=1.0; extra == "mcp"
Dynamic: license-file

# 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

```bash
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:

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

…or add it by hand:

```jsonc
// 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](https://github.com/ritvikpeddi7-creator/cogctx) 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

```bash
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:

```bash
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:

```python
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.

```bash
focuslens wait-until-interruptible && notify-send "build done"   # holds it for you
focuslens wait-until-interruptible --timeout 600                 # exit 1 if still in flow
```

```python
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
```

```bash
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.

```python
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:

```bash
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)

```bash
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:

```bash
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.
