eeg-mcp¶
Real-time EEG for AI agents. Stream from hardware, replay recordings against a wall clock, watch the signal live, record it safely, and dispatch stimulation — all through one session model.
Get started Tool reference GitHub
47 tools over stdio. 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.
What it does¶
-
Stream
66 BrainFlow board identifiers — OpenBCI, Muse, ANT Neuro, g.tec, Mentalab — plus a synthetic board needing no hardware. A background thread fills a ring buffer, 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. A pipeline built against a file runs unchanged against hardware.
-
Process
Stateful online filtering that carries filter state across chunks, band power, PSD, and per-electrode quality — plus your own processors running inside the acquisition loop.
-
Visualize
A loopback-bound, token-gated live browser view with rolling traces, event markers and transport controls. Self-contained HTML reports with no CDN — they open on an air-gapped machine.
-
Record
Write continuously to MNE-native
.fifwith the event log attached as annotations, plus a metadata store an analysis tool can read directly. Crash-safe — an interrupted session is recoverable. -
Stimulate
One
send_stim_eventcontract over LSL, BrainFlow markers, serial/TTL, and templated protocols for TMS and tES — behind three safety gates.
One event log, one clock
Board markers, replayed annotations, dispatched stimulations and manual notes all land in the same table with absolute sample indices. A closed-loop run reconstructs afterwards with no clock join.
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"]
Server --> Process["Process<br/>online DSP · plugins"]
Server --> Watch["Watch & Record<br/>monitor · .fif"]
Server --> Stim["Stimulate<br/>LSL · TTL · TMS/tES"]
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 ask an agent for an outcome in plain English and it drives the tools underneath.
Try it in 60 seconds¶
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
The one design decision worth knowing¶
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 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 |
Budget your loop
group delay + poll interval + dispatch latency — measured at ~71 ms in the reference configuration. Good for neurofeedback; not adequate for phase-locked stimulation.
Where to go next¶
-
Environment, MCP client registration for Claude Code / Desktop / Codex, and troubleshooting.
-
Eleven steps end to end, no hardware needed. Start here.
-
Write a custom processor or an EEG tokenizer for sequence models.
-
All 66 boards, replay formats, and stimulation transports.
-
Closed-loop neurofeedback, clinical review, TMS/tES protocols.
-
254 checks — and an honest list of what is not covered.
Research use only
eeg-mcp is not a medical device and has not been validated for clinical use. Stimulation hardware — especially TMS and tES — can cause harm, including seizure.
The hardware stimulation backends are generic transports you configure from your device's manual, not vendor drivers, and none has been tested against a physical stimulator. Read Safety before connecting anything to a person.