A MCP for real-time EEG: BrainFlow streaming with events, wall-clock replay of existing recordings, stateful online DSP, and stimulation event output to software and hardware (TMS/tES) targets.
Project description
⚡🧠 eeg-mcp
Real-time EEG for AI agents — stream, replay, visualize, record, stimulate
Documentation · Tutorial · Hardware · Safety · Tool Reference
A Model Context Protocol server that gives an AI agent one interface over the live EEG workflow: acquisition from ~66 BrainFlow boards, wall-clock replay of existing recordings, stateful online DSP, a live browser monitor, crash-safe recording, and gated stimulation output.
The offline counterpart is neuro-mcp (MNE processing, source imaging, BIDS/EHR storage). This is the real-time half — everything that has to happen while the signal is still arriving.
Concept
flowchart LR
Researcher(["🔬 BCI Researcher"])
Clinician(["🩺 Clinician"])
Agent[["🤖 AI Agent"]]
Server(("eeg-mcp<br/>FastMCP · 47 tools"))
Clinician -- talks to --> Agent
Researcher -- talks to --> Agent
Agent -- MCP --> Server
Server --> Acquire["Acquire<br/>66 boards · replay<br/>at true rate"]
Server --> Process["Process<br/>stateful online DSP<br/>custom plugins"]
Server --> Watch["Watch & Record<br/>live monitor · HTML<br/>crash-safe .fif"]
Server --> Stim["Stimulate<br/>LSL · TTL · TMS/tES<br/>3 safety gates"]
classDef acq fill:#14b8a6,stroke:#0d9488,color:#fff
classDef proc fill:#4f8cff,stroke:#2f5fbf,color:#fff
classDef viz fill:#b06fe0,stroke:#7c3fae,color:#fff
classDef stim fill:#eb5757,stroke:#b93b3b,color:#fff
class Acquire acq
class Process proc
class Watch viz
class Stim stim
Nobody calls a tool by hand — you talk to an agent in plain English and it drives the 47 tools underneath. The tutorial shows what that looks like end to end, with no hardware required.
What it does
|
📡 Stream 66 BrainFlow board identifiers — OpenBCI, Muse, ANT Neuro, g.tec, Mentalab and more — plus a synthetic board that needs no hardware. Samples land in a ring buffer filled by a background thread, so tool calls read a live view instead of blocking on a device. |
⏪ Replay Play an EDF/BDF/GDF/SET/FIF or BrainFlow CSV at the rate it was recorded, re-emitting annotations as events at their original timings. Adds speed, seek, pause and looping. A pipeline developed against a file runs unchanged against hardware. |
|
👁 Visualize A loopback-bound, token-gated live browser view: rolling traces, event markers, band power, per-electrode quality, and transport controls. Plus self-contained HTML reports — no CDN, no external assets, opens on an air-gapped machine. |
💾 Record Write continuously to MNE-native |
|
🧩 Extend Plug in your own real-time processor — feature extractor, classifier, artifact gate, or EEG tokenizer for sequence models — and it runs on the same footing as the built-ins, inside the acquisition loop. → Extending |
⚡ Stimulate One |
[!NOTE] One event log. Board markers, replayed annotations, dispatched stimulations and manual notes all land in the same table on the same clock, with absolute sample indices. A closed-loop run reconstructs afterwards with no clock join.
Install
conda create -n eeg-mcp python=3.11 -y && conda activate eeg-mcp
pip install eeg-mcp
Optional extras and MCP client registration
pip install "eeg-mcp[lsl]" # LSL marker outlets (PsychoPy, OpenViBE, ...)
pip install "eeg-mcp[serial]" # serial/TTL trigger delivery to hardware
Register with an MCP client using an absolute path to the env's interpreter:
{
"mcpServers": {
"eeg-realtime": {
"command": "/path/to/envs/eeg-mcp/bin/python",
"args": ["-m", "eeg_mcp"]
}
}
}
Or with the Claude Code CLI:
claude mcp add eeg-realtime -- /path/to/envs/eeg-mcp/bin/python -m eeg_mcp
→ Full guide: Installation
Quick start
Ask your agent for the outcome; it picks the calls. No hardware required:
start_stream(session_id="s1", board="synthetic")
check_signal_quality(session_id="s1") # before trusting anything
set_filters(session_id="s1", bandpass_low=1, bandpass_high=40, notch_freq=50)
get_band_power(session_id="s1", seconds=2)
start_monitor(session_id="s1") # → open the returned URL
Replay a real recording as if it were live, then keep the record:
inspect_recording(path="sub-04_rest.edf")
start_replay(session_id="r1", path="sub-04_rest.edf", speed=1.0)
get_events(session_id="r1", origin="annotation")
export_report(session_id="r1", notes="Routine review.")
flowchart LR
A["start_stream<br/><i>or</i> start_replay"] --> B[check_signal_quality]
B --> C[set_filters]
C --> D["get_band_power<br/>get_psd"]
C --> E[start_monitor]
C --> P[attach_processor]
A --> R[start_recording]
D --> S[send_stim_event]
P --> S
R --> X[stop_recording]
S --> X
E --> X
X --> Z[stop_stream]
classDef hot fill:#14b8a6,stroke:#0d9488,color:#fff
class A,X hot
Documentation
| Guide | |
|---|---|
| 🚀 Installation | Environment, client registration, troubleshooting |
| 📘 Tutorial | End to end, no hardware needed |
| ⏪ Replay-Driven Development | Build against a recording, deploy live |
| 🔁 Closed-Loop Neurofeedback | Feature → trigger, with a measured latency budget |
| 🩺 Live Clinical Review | Visual review, annotation, reporting |
| ⚡ Stimulation Protocols | TMS and tES through the safety gates |
| 🧩 Extending | Write a custom processor or EEG tokenizer |
| 🔌 Supported Hardware | All 66 boards, formats, stimulation transports |
| ⚠️ Safety | Read before connecting a stimulator |
| 🛠 Tool Reference | All 47 tools |
The one design decision worth knowing
[!IMPORTANT] Filtering happens in the producer thread, not at query time.
A stateful IIR filter must see every sample exactly once, in order. The common shortcut — filtering each query window independently — restarts the filter at every window boundary and injects a transient each time. It is invisible in a band-power plot and fatal for anything phase-sensitive.
So the producer filters each chunk once as it arrives, carrying sosfilt
delay-line state forward, and writes to a second ring buffer. Queries just read.
flowchart LR
BF[Board / Recording] -->|poll| PR{{Producer thread}}
PR -->|raw chunk| RB[(Raw ring buffer)]
PR -->|stateful sosfilt| FB[(Filtered ring buffer)]
PR -->|markers & annotations| EL[(Event log)]
PR -->|append| DISK[(.fif on disk)]
PR -->|streaming| PL[Your processors]
RB & FB & EL --> Q[MCP tools]
classDef hot fill:#14b8a6,stroke:#0d9488,color:#fff
class PR hot
The test suite asserts chunked filtering matches whole-signal filtering to 1e-9, and asserts as a control that the naive approach does not.
| Consequence | |
|---|---|
| Filters are causal | No zero-phase option — that needs future samples. stream_status reports group_delay_sec |
| Both buffers are kept | read_window(filtered=false) always gets raw signal, to check whether a feature is real or an artifact |
| Indices are shared | An event's sample_index means the same thing in either buffer |
[!TIP] Budget a closed loop as group delay + poll interval + dispatch latency — measured at ~71 ms in the reference configuration. Good for neurofeedback; not adequate for phase-locked stimulation.
Stimulation safety
[!CAUTION] This software is not a medical device and has not been validated for clinical use. TMS and tES can cause harm, including seizure. Use only under a protocol approved by your ethics board, on a rig whose device-level interlocks are intact, with a trained operator present.
Three gates apply to every hardware backend:
| # | Gate | Effect |
|---|---|---|
| 1 | Config | Hardware backends refuse to open unless the server was started with EEG_MCP_ALLOW_HARDWARE_STIM=1. An agent cannot set this. |
| 2 | Arming | arm_stim permits dispatch for a window that expires, so a stalled agent cannot resume and fire later |
| 3 | Limits | Intensity, duration and interval are clamped; violations raise rather than silently saturate |
None of this replaces the interlocks on the device itself.
[!WARNING] The hardware backends are generic transports driven by command templates you supply from your device's manual — not vendor drivers, and none has been tested against a physical stimulator. A plausible-looking untested driver would be worse than none: it would fail silently while connected to something pointed at a person's head.
Start every protocol on
backend="log", which accepts everything and emits nothing.
Verify
python testing/verify.py # core correctness
python testing/verify_recording.py # recording + metadata store
python testing/verify_processing.py # plugin processors
python testing/persona_bci_researcher.py # engineer workflow
python testing/persona_clinician.py # clinician workflow
All five drive the real server through FastMCP's in-memory client and assert against planted ground truth:
- a spike planted at an annotation onset lands at t = 0 ± 0 ms in the extracted epoch — proving annotation timing, replay clock, ring-buffer indexing and epoch extraction all agree;
- a deliberately broken plugin cannot stop acquisition — throughput holds at 1.0;
- hardware stimulation is refused while the config gate is unset.
→ What is and is not covered — including an honest list of what has never been tested against real hardware.
License
BSD-3-Clause. See LICENSE and NOTICE.
⬆ back to top · Part of the AImplifier neuro toolchain · sibling project neuro-mcp
Project details
Release history Release notifications | RSS feed
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 eeg_mcp-0.1.0.tar.gz.
File metadata
- Download URL: eeg_mcp-0.1.0.tar.gz
- Upload date:
- Size: 182.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0163db2fa9ad2c1a2e30dee4b48e6ff3dfc1fca206ed206c10d21649f75fb90c
|
|
| MD5 |
24dda694a159eea543608d2d7f17252e
|
|
| BLAKE2b-256 |
51084525b29da26729d8f4f542966afb9304ab3e84618b9d4e1ce68e4cc350e9
|
Provenance
The following attestation bundles were made for eeg_mcp-0.1.0.tar.gz:
Publisher:
publish.yml on AImplifier/eeg-mcp
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
eeg_mcp-0.1.0.tar.gz -
Subject digest:
0163db2fa9ad2c1a2e30dee4b48e6ff3dfc1fca206ed206c10d21649f75fb90c - Sigstore transparency entry: 2256147272
- Sigstore integration time:
-
Permalink:
AImplifier/eeg-mcp@ed86657679e03bce33ce57efd6542044655704f7 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/AImplifier
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@ed86657679e03bce33ce57efd6542044655704f7 -
Trigger Event:
workflow_dispatch
-
Statement type:
File details
Details for the file eeg_mcp-0.1.0-py3-none-any.whl.
File metadata
- Download URL: eeg_mcp-0.1.0-py3-none-any.whl
- Upload date:
- Size: 115.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7a2507d30ded3809c5ce004ad4cc06654a8aa014e2c8e047bf4a1abeb0bcdadb
|
|
| MD5 |
d849cdca2067e01dba51f8bb78d194ed
|
|
| BLAKE2b-256 |
c5ae9d1899829e4d5c5d558e1d9df79c9349601bc26b9b52fd51a0de2b51335d
|
Provenance
The following attestation bundles were made for eeg_mcp-0.1.0-py3-none-any.whl:
Publisher:
publish.yml on AImplifier/eeg-mcp
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
eeg_mcp-0.1.0-py3-none-any.whl -
Subject digest:
7a2507d30ded3809c5ce004ad4cc06654a8aa014e2c8e047bf4a1abeb0bcdadb - Sigstore transparency entry: 2256147275
- Sigstore integration time:
-
Permalink:
AImplifier/eeg-mcp@ed86657679e03bce33ce57efd6542044655704f7 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/AImplifier
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@ed86657679e03bce33ce57efd6542044655704f7 -
Trigger Event:
workflow_dispatch
-
Statement type: