
写这篇东西的起因其实很偶然。我一直在折腾脑机接口BCI和本地大模型应用的结合手头有 EEG 设备也在天天用 Claude 写代码、做分析。时间一长就发现一个非常别扭的地方Claude 再聪明它对我的大脑状态一无所知。我只能用大白话告诉它“我现在有点困”“刚才走了个神”可这种文字描述既不客观也不实时喂给模型之后它只能猜。直到我开始系统接触 MCPModel Context Protocol协议才意识到一个更好的路子——把 EEG 原始波形先在本地做处理提炼成一个个连续的“脑状态片段”然后通过 MCP Server 暴露给 Claude让它像读取系统信息一样实时地、结构化地拿到我的注意力和放松度数据。这就是 bci-mcp 项目的全部由来。这篇文章我会从项目动机、硬件选型、MCP 服务层设计、脑状态算法、本地联调步骤到踩坑记录全部展开。适合正在玩 EEG 硬件但不知道怎么接 LLM 的朋友也适合对 MCP 协议有基础、想找一个非传统数据源来练手的开发者。两者结合在一起的实战细节市面上讲得实在太少了。1. 项目起点脑机接口数据接入 LLM到底难在哪1.1 从“无法描述”到“被模型读取”的最后一公里先摆一个问题脑电数据本身有什么价值你戴着 EEG 头环写代码两小时原始波形几千万个采样点这些点本身没有任何 LLM 能直接消化的语义信息。但如果把它压缩成“最近30秒专注度指数 82%放松度 41%眨眼频率每分钟 12 次”这就变成了一段机器可读、人也看得懂的瞬时脑状态描述。Claude 拿到这串数值再结合我预设的提示词就能判断“你当前处于深度工作状态适合处理复杂逻辑任务建议继续保持”或者反过来建议我休息。这个从“原始波形”到“语义化状态”的转换就是 BCI 和 LLM 结合的最后一公里。问题在于传统 BCI 框架比如 Lab Streaming Layer、OpenViBE产出的是面向科研的数据流模型训练、可视化、标注工具一大堆唯独没有一个面向 LLM 应用的轻量输出接口。MCP 协议的出现正好补上这一环它定义了标准化的工具调用和资源读取方式我只需要把脑状态指标封装成 MCP 工具Claude 就能在对话中主动去“读取”这些实时指标。1.2 为什么一定是流式而非一次性快照做这个项目之前我做过一个非常原始的版本让 Python 脚本每隔 60 秒把一组脑波指标写成 JSON 文件然后在 Claude 的对话里手动引用这个文件。效果勉强能用但问题很明显——对话是异步的我写好问题发给 Claude它读取文件的时候文件里的数据已经是几十秒前的老状态了。用户真正想感知的是“现在的我”而不是“一分钟前的我”。MCP 的资源订阅和流式输出能力正好解决这个问题。它允许服务端持续推送更新Claude 可以拿到一个动态的“脑状态流”而不是静态文件。这个区别很关键流式数据让模型能够在一个时间窗口内追踪注意力曲线的变化趋势比如“你从 10 分钟前开始专注度缓慢下滑最近 30 秒下降速度加快”这种时间维度上的观察静态快照完全给不了。2. 脑电采集层硬件选型、采样率与导联设计2.1 消费级与科研级硬件的取舍EEG 设备的选择直接决定整个项目的下限。我手头常用的有三类设备参数差异巨大我整理了个对比表设备采样率通道数价格区间信号质量便携性Muse 2256Hz4TP9、AF7、AF8、TP10两千到三千中等适合消费级极高戴上就走OpenBCI Cyton250Hz/500Hz8/16可扩展八千到一万二较好科研入门常用中等需要携带主板OpenBCI Ganglion256Hz4六千左右中等偏上高体积小对于 bci-mcp 这个场景我的建议是别一上来就上科研级设备。原因很简单项目的核心目标是把脑状态送进 Claude需要的是稳定的时频特征而不是超高密度脑功能区定位。消费级 Muse 2 的四个通道虽然少但它的 AF7、AF8 正好覆盖额叶区域而这个区域恰恰是注意力和认知负荷最敏感的采集位点。折腾 OpenBCI 花在屏蔽、共地、电极接触上的时间足够你写出三版 MCP Server 了。当然如果你手头已经有脑电放大板比如国产的某些 OpenBCI 兼容板也完全可行。我后来就换成了国产品牌的 8 通道板采样率调到 250Hz前端加了右手腕参考电极比 Muse 2 默认的耳后参考在某些频段上的表现更稳定。关键不是设备贵不贵而是你能不能让信号源稳定下来。2.2 采样率、参考电极与信号质量的底层关系简单说下采样率。EEG 里面我们最关心的频段是 Delta1-4Hz、Theta4-8Hz、Alpha8-13Hz、Beta13-30Hz、Gamma30-50Hz。根据奈奎斯特采样定理要分析到 50Hz 的 Gamma 频段理论上采样率至少要到 100Hz。但实际应用中建议至少 250Hz因为还需要留出空间做抗混叠滤波。参考电极的位置是个容易被新手忽略的坑。Muse 2 用的是耳后参考这对消费级场景够用但如果你用可穿戴干电极设备自己搭系统参考电极放在耳垂还是手腕结果差异很大。按照国际 10-20 系统标准参考应该选在电信号相对平静的部位比如耳垂A1/A2或者乳突。我测试过把参考放在手腕和前臂效果差不太多但放在手腕上更容易受心电信号干扰处理时需要加更高阶的高通滤波器。2.3 干电极还是湿电极还有一个必须谈的问题电极类型。消费级设备基本都是干电极或半干电极不需要导电膏戴上就能用代价是阻抗偏高、信号偏弱、容易受运动伪影干扰。湿电极需要涂导电膏信号质量好得多但准备时间麻烦每次实验前要花十几分钟涂抹而且头皮清洁程度直接影响接触阻抗。我个人的使用习惯是日常办公场景用 Muse 2 的干电极因为我不可能每半小时涂一次导电膏做算法验证或需要高质量原始波形的时候用湿电极的 8 通道设备。bci-mcp 项目的定位决定了它要走“低侵入、可长时间佩戴”的路线所以干电极在很多场景下是更实际的选择。对于干电极信号偏弱的问题可以在算法侧用更长的滑动窗口求平均来弥补这个在后文有详细叙述。3. MCP 服务层波形数据如何变成 Claude 能消费的会话工具3.1 MCP 协议的核心机制在展开代码之前有必要先把 MCP 的机制讲清楚。MCP 就是模型上下文协议官方定义是“为 LLM 应用提供标准化上下文接入的开放协议”。它的核心抽象包括三类原语工具Tools、资源Resources和提示模板Prompts。工具是“让模型可以执行动作”资源是“让模型可以读取上下文片段”提示模板则是“预定义的交互模式”。对于 EEG 这种持续性传感器数据最关键的是资源和工具的混合使用。资源用来暴露“当前的脑状态快照”工具用来触发“启动/停止流式状态推送”。我见过一些人把 MCP 服务器设计成只有工具没有资源Claude 每次只能通过调用工具获取一帧快照这样虽然能工作但丢失了流式数据的时间连续性等于把 MCP 用成了 REST API。理解了资源和工具的分类差异你就能明白 bci-mcp 为什么需要两者搭配。3.2 进程内 server 与远程 server 的选择MCP Server 的部署方式有两种主流选择stdio和Streamable HTTP历史版本叫 HTTP with SSE。stdio 方式是 Claude Desktop 和 Claude Code 默认支持的方式客户端直接启动一个本地子进程通过标准输入输出进行 JSON-RPC 通信。这种方式配置简单、延迟低、不需要占用网络端口非常适合个人本机的 EEG 信号处理链路。Streamable HTTP 则更适合远程部署场景例如 EEG 数据在一台实验专用机器上采集而 Claude 跑在另一台工作站。我第一次采用 streamable HTTP 方式部署用 uvicorn 启了一个 8000 端口的服务然后在 Claude Code 的 MCP 配置里指向http://localhost:8000/mcp。但实际使用中发现这种方式多了一层 HTTP 框架的开销而且对连接保活处理没有 stdio 那么无脑。折腾一轮之后我又切回了 stdio 方式把 EEG 采集、信号处理和 MCP 服务放在同一个 Python 进程里。这里给出的建议是如果你只是本机使用直接用 stdio 模式只有当设备与 LLM 客户端不在同一台机器或者你需要多个客户端并发访问这一路 EEG 信号时再去考虑 HTTP 模式。多一层网络传输就多一个故障点这在实时传感器链路里是不可忽视的成本。3.3 bci-mcp 的 Server 功能拆分我最终给 bci-mcp 设计了三个核心操作面资源brain://current返回最近一个状态滑动窗口的格式化脑状态摘要内容包含各频段功率占比、专注度评分、放松度评分和眨眼标记。工具start_eeg_stream()开始采集数据并注册资源订阅推送让 Claude 可以通过订阅持续获取状态更新。工具stop_eeg_stream()停止采集释放串口/蓝牙连接断开设备。资源用来读取“现在”的状态工具用来控制“持续的流程”。这两者的结合让模型更加自然Claude 可以先调用start_eeg_stream()打开信号源然后每隔一段对话轮次读取一次brain://current感知状态变化。还有一个很容易忽略的细节MCP 工具声明里的description字段非常重要。Claude 模型依赖这个描述来决定什么时候调用工具。如果你的描述写的是“读取当前脑电状态指标”模型就只知道在问及大脑状态时使用但如果你把描述扩写为“读取当前大脑专注度、放松度、疲劳估计值用于判断用户是否适合处理复杂任务、是否需要休息提醒”模型的行为就会变得更加主动——它会在对话中判断“用户可能很疲惫”然后自动查询一次状态。这个描述工程某种意义上比算法本身更影响最终体验。4. 脑状态解析与算法从原始波形到可读状态4.1 频域特征的提取链路EEG 原始信号进来之后不可能直接往 MCP 里塞波形数据也没有任何意义。需要先完成以下处理链路去基线漂移 → 带通滤波 → 剔除伪影段 → 加窗 → FFT → 频段功率计算 → 相对功率归一化 → 指标合成。带通滤波我会设计成 0.5-50Hz也就是覆盖 Delta 到 Gamma 全频带再通过陷波滤波器去掉 50Hz 工频干扰国内电网是 50Hz。这个陷波器必须要有不然你会发现 Alpha 频段8-13Hz的功率经常被莫名其妙抬高——其实那是工频泄漏。之后用 4 秒滑动窗口窗内数据 1000 个采样点对应 250Hz 采样率窗口重叠 75%保证状态更新平滑。对窗口数据做 Hann 窗处理后进行 FFT计算各频段绝对功率再除以窗口内总功率得到相对功率值。相对功率的好处是抗基线漂移设备长时间佩戴、电极慢慢松脱导致的整体信号衰减不会让相对功率发生剧烈变化。4.2 专注度与放松度的合成计算专注度评分我采用的是一种工程上妥协的方案。纯科研级的专注度指标需要多通道的空间模式分析比如 frontal theta 与 parietal alpha 的比值甚至需要用机器学习模型训练个体标定模型。但 bci-mcp 定位是实时流式应用我采用了更稳健的启发式公式专注度 (Beta_power Gamma_power) / 全频段功率为什么用 Beta 和 Gamma核心理由是人处于注意力集中或认知活跃状态时额区 Beta13-30Hz和 Gamma30-50Hz频段的能量会相对上升这是脑电研究里比较公认的现象。当然这只是一个粗略估计受个体差异影响很大。但对流式实时监控来说它的优点是计算量小、实时性强、不需要个体标定而且趋势方向的可靠性足够好。放松度我用的是 Alpha 相对功率8-13Hz原因是人在闭眼休息、放松放空的状态下枕区和顶区 Alpha 波会显著增强。虽然 Muse 2 的通道都在额区但额区 Alpha 同样有可观测的变化。两个指标再经过一个滑动平均滤波器窗口 5 个状态帧平滑避免单帧毛刺导致状态跳变。4.3 眨眼伪影的检测与剔除这是整个项目里让我花时间最多的地方。眨眼产生的 EOG 伪影在 EEG 信号里幅值巨大尤其是额区电极AF7、AF8离眼睛非常近几乎每一次眨眼都会让信号翻几个数量级。如果不做处理眨眼瞬间的功率谱会被伪影污染Beta 和 Gamma 功率异常飙升专注度评分瞬间跳到 99这显然是错的。我用了一个双重策略第一层是阈值检测对原始信号做一阶差分如果差分绝对值的峰值超过正常脑电幅值的 3 倍以上且持续时间在 100ms 到 400ms 之间就标记为疑似眨眼事件第二层是对疑似事件做形态验证用窗口内波形的峰峰值和斜率特性做简单分类。一旦确认眨眼伪影就把对应的波形段直接从分析窗口里剔除而窗口剩余数据仍然参与功率计算。这样做不会丢失整段状态只是让伪影段不影响频段估计。实测下来这套策略在干电极设备上的检出率大约在 90% 左右剩下的 10% 是那些幅度偏低、被误当作正常脑动的软眨眼。对于流式实时场景这个精度够用了。科研级应用建议改用独立 EOG 通道做回归去除那是另一个话题了。4.4 输出格式与指标映射处理之后的输出数据结构按 JSON 格式组织{ timestamp: 1720429732.482, battery_level: 72, signal_quality: 0.91, relative_power: { delta: 0.30, theta: 0.22, alpha: 0.24, beta: 0.18, gamma: 0.06 }, metrics: { focus_score: 72.4, relax_score: 56.8, blink_count: 12 }, state_tag: moderately_focused }注意最后一个字段state_tag这是把连续数值映射成语义标签。我设计了三档标签highly_focused专注度 80、moderately_focused专注度 60-80、distracted专注度 60。之所以要这个标签是因为 Claude 处理连续数值虽然没问题但语义标签能让它更快地做出行动决策而不是去解析 72.4 这个数值背后的意义。这个字段设计思路源自 MCP 工具描述的“降低模型推理负担”原则——宁可多给一点结构化信息也不要让模型去猜。5. 本地跑通 bci-mcp环境准备、服务配置与实测5.1 项目目录与依赖整个项目的 Python 环境依赖比较简单mcp官方 Python SDK用于构建 MCP Servernumpy、scipy信号处理pylsl从 LSL 数据流接收 EEG 数据bleak如果使用 Muse 2 的蓝牙直连模式采集端驱动根据硬件厂商不同各有差异项目目录结构大致如下bci-mcp/ ├── server/ │ ├── mcp_server.py # MCP服务入口 │ ├── eeg_devices/ # 各种EEG设备的适配器 │ │ ├── muse.py │ │ └── openbci.py │ ├── processing/ # 信号处理与指标计算 │ │ ├── filter.py │ │ └── metrics.py │ └── resources/ # 脑状态格式与标签映射 ├── config/ │ └── settings.yaml # 设备参数、通道配置 └── scripts/ └── run_server.py # 启动脚本5.2 使用 Claude Code 连接 MCP 服务器现在 Claude Desktop 和 Claude Code 都支持通过配置文件加载 MCP Server。以 Claude Code 为例需要在 MCP 配置文件通常是~/.claude.json或项目级.mcp.json里声明{ mcpServers: { bci-mcp: { command: python, args: [/path/to/bci-mcp/scripts/run_server.py], env: { EEG_DEVICE: muse2, SAMPLE_RATE: 256, MCP_TRANSPORT: stdio } } } }配置文件写好之后在 Claude Code 交互界面里输入/mcp命令就可以看到 bci-mcp 服务器是否加载成功。第一次加载成功的瞬间在 MCP 工具列表里看到自己封装的start_eeg_stream出现那种兴奋感不亚于第一次让模型调通外部 API。5.3 从启动到对话的完整时序说说一次典型运行的时间线运行run_server.pyMCP 服务启动等待 Claude 连接。在 Claude Code 中向模型发消息“请读取我当前的脑电状态判断我适不适合做代码审查。”模型根据工具描述判断需要调用start_eeg_stream()请求发出采集启动。设备开始推送数据流服务端在后台持续进行信号处理。模型读取brain://current资源拿到第一帧状态数据。模型结合预设系统提示词生成判断“专注度 72.4放松度 56.8处于中度专注状态可以进行代码审查但建议每 45 分钟休息一次”。整个过程从请求到状态反馈大约 3-5 秒主要开销集中在模型推理和工具调用序列上数据链路本身延迟在 500ms 以内。对比之前手动写状态文件的方案这个体验堪称质的飞跃。5.4 提示词工程与状态解读为了让 Claude 正确理解脑状态数据我在系统提示词里写了这样一个说明你可以通过 brain://current 资源获取当前用户的脑电状态。focus_score 为 0-100代表认知专注程度relax_score 为 0-100代表放松程度blink_count 为最近一分钟眨眼次数。当 focus_score 低于 60 时用户可能处于注意力涣散状态建议简化任务或提醒休息低于 40 时建议停止复杂操作。这个提示词不复杂但效果很好。Claude 会非常理性地使用这些数据来调整自己的输出策略。有一次我戴着设备调试代码连续两小时高强度思考到后面 focus_score 掉到 50 左右relax_score 反而升到了 70Claude 主动提示“可能已经进入疲劳期的前兆建议起来活动五分钟”。这种体验确实有一种被 AI “看穿”的感觉。6. 避坑记录与进阶方向那些文档不会写的细节6.1 坑一蓝牙连接不稳定服务进程偶发崩溃Muse 2 的蓝牙连接在长时间运行后会出现句柄失效问题导致pylsl或bleak回调抛异常。最初的版本是异常直接向上抛整个 MCP Server 进程就退出了Claude Code 那边显示“工具不可用”只能重启服务。后来我在设备适配器外层加了一层看门狗逻辑遇到连接断开时不直接抛出而是尝试最多三次重连重连失败则返回一个带state_tag: signal_lost的状态帧。这样 Claude 至少知道信号断了而不是整个工具不可用。这个思路做远程设备接入时同样有效——所有传感器都可能有断连期MCP Server 应该把断连当成一种“数据状态”而不是“系统故障”来对待。6.2 坑二MCP 消息体的体积与频率控制刚开始我把每次状态推送的 JSON 做得很详细包括原始频段功率、几十个采样点特征、历史趋势单条消息能到 2KB。MCP 协议对单条消息本身没有严格限制但 Claude 的上下文窗口是有限的。在一段长时间会话里如果每 4 秒推一条 2KB 的消息十分钟就是 300KB这相当于几万个 token很快会把上下文窗口吃光后续对话质量急剧下滑。解决思路是降低推送频率并压缩消息体积。最终把状态推送间隔拉长到 10 秒通过滑动平均保证平滑度JSON 只保留紧凑字段去掉了冗长的原始特征数组。另外把趋势性信息比如 5 分钟前的专注度对比放到 Claude 主动查询的brain://current资源里而不是主动推送。这种“主动推送精简帧 按需查询详情”的组合能很好地平衡实时性和上下文成本。6.3 坑三把 MCP 当数据传输管道用方向就错了还有一个概念上的坑想特别提醒MCP 本身不是为高频数据流设计的。它是模型上下文协议解决的是“模型如何获取上下文”的问题而不是“如何传输大数据流”。如果每秒要推几十帧原始波形那应该走专用的数据流通道比如 LSL 或者 WebSocketMCP 只负责暴露经过提炼的语义化状态摘要。我一直把“至少 10 秒一帧的语义状态”作为设计底线这样 MCP 的异步消息语义和状态更新节奏刚好匹配。6.4 可能的扩展方向如果后续要在这个项目上深挖我认为有三个方向值得投入第一多模态融合。把 EEG 状态与屏幕使用时间、键盘输入频率、工作日志做关联生成更丰富的生理-行为上下文。这会让 Claude 的判断准确度上升一个层次因为它看到的不是单一脑电信号而是一个多维度的状态空间。第二个体化校准。把启发式公式换成轻量级机器学习模型比如用 5 分钟的基线数据训练一个小型分类器根据个体的脑电特征动态校准专注度阈值。我试过用简单的高斯朴素贝叶斯效果比启发式公式稳定不少但对训练数据采集流程有要求后续值得单独开一篇文章讲。第三状态感知自动化。让 Claude 基于脑状态自动调整 IDE 主题、降低通知频率、切换音乐队列甚至在下一次会议前根据你的疲劳状态自动精简议题。MCP 的工具调用机制完全能承载这种自动化——我们已经有了控制端Claude和感知端EEG剩下的就是想象力。回到文章开头那个问题让 Claude 知道你的大脑状态这件事到底有多大价值就我这几周的实测经验来看最有价值的不是那个“是否适合做代码审查”的判断而是它让我第一次从第三者的视角持续、客观地观察自己的认知状态波动。原本我以为自己下午两点工作效率还行数据告诉我其实那段时间专注度已经跌破 50真正的高峰在上午十点前后。这种认知偏移靠主观自评永远看不见。bci-mcp 对我来说已经不单纯是一个 MCP 工具链的练手项目而是一种新的自我观察方式。