Command Palette

Search for a command to run...

UnylyUnyly
Browse all

Benchtop

FreeNot checked

MCP server for operating, recording, and analyzing serial-connected measurement instruments and circuits via AI agents. Includes a built-in mock device so all f

GitHubEmbed

About

MCP server for operating, recording, and analyzing serial-connected measurement instruments and circuits via AI agents. Includes a built-in mock device so all features can be tested without physical hardware.

README

シリアル接続された計測装置・回路を、AI エージェント(Claude など)から直接操作・記録・解析するための MCP サーバーです。

実機がなくても動きます。 内蔵の仮想装置(port="mock")があるので、ハードを繋ぐ前に全機能を試せます。

Version: 0.2.4 (2026-08-13) — v0.2.3 verify 中に発火した実 pain 対応 (initial external-verify-derived fix): search_sessionssince / untilYYYY-MM-DD (時刻部分なし) のとき local midnight として解釈 し UTC 換算後に比較。旧 v0.2.3 では UTC 文字列辞書順比較で 「JST 早朝に測った session を since='今日' で 検索すると 0 件」 の 静かな穴があった (session_id は JST 表示 / started_at は UTC で日付が 1 日ずれる)。完全 ISO (T + offset) は従来通り厳密。BENCHTOP_TZ env で tz override 可能。副 fix として各 tool の返り値に started_at_local を併記。詳細は下記「v0.2 で足したもの」節。

Compatibility note: v0.2.3 は interpretation field の値を "threshold_gate_on_welch_standard_error" (v0.2.2) → "welch_t_statistic_with_fixed_z_threshold" に rename しました。この文字列を pattern match していた caller は壊れます。現時点で外部 caller はいない想定なので実害ゼロですが、今後 return dict の field 値を触るときは breaking change 扱いとします (メジャー版 or 明示注記の伴う変更のみ)。

v0.2.4 は search_sessions(since='YYYY-MM-DD') の 解釈を UTC 文字列辞書順比較 → local midnight 換算比較 に変更しています。厳密には behavior change ですが、「UTC 基準の日付のみ指定」を意図して使っていた caller は現実には存在しない想定 (いたとすればそれが今回のバグ) なので breaking 扱いはしません。完全 ISO 指定 (T + 時刻 + offset) は従来動作を維持。

License: v0.x は MIT。 v1.0+ は AGPL-3.0 + commercial dual への切替 可能性 予告 (LICENSE file 参照)。 v0.x 分は 永久 MIT (irrevocable)。


これは何を解決するのか

計測の現場では、だいたい同じことが繰り返されています。

  1. 装置にコマンドを送って値を読む
  2. しばらく回してログを取る
  3. Excel に貼って平均と σ を出し、おかしい点を探す
  4. CSV にして誰かに渡す

一つ一つは簡単ですが、面倒で、毎回手でやっています。このサーバーを入れると、その全部を AI に日本語で頼めるようになります。

「mock に繋いで200回測って、電圧のばらつきを見て、外れ値があったら教えて。結果は CSV にして」

地味です。しかし前の会話で確認した「売れるものの条件」——誰もが必要とし、自作すると面倒で、壊れると静かに高くつく——に、正確に当てはまります。


公開しているツール(AI から呼べる機能)

ツール 役割
list_ports 利用可能な装置の一覧。実機が無くても mock が必ず出る
send_command 装置に1行送って応答を読む(*IDN? など)
measure N 回連続で測定し、セッションとして保存
list_sessions 保存済みセッションの一覧
analyze_session 平均・σ・最小/最大・ドリフト・3σ外れ値を算出
export_session_csv CSV に書き出し
plot_session (v0.2) ASCII スパークライン (▁▂▃▄▅▆▇█) でチャンネル別に視覚化
compare_sessions (v0.2) 2 セッションの mean/stdev/drift 差分と Welch 型 z スコアを返す (v0.2.1: z_threshold 明示パラメータ)
search_sessions (v0.2) 日付範囲・note キーワード・port・channel でセッションを絞り込む

存在しない session_id を渡した場合、plot_session / analyze_session / compare_sessions / export_session_csv は例外を投げずに {"error": "session_not_found", "session_id": "...", "hint": "..."} を返す (v0.2.1)。AI から見て tool 呼び出しが例外で落ちるより、error 情報を含む dict が返る方がリトライ or 別 tool に自然に繋がる。

対応する測定値の形式は3種類を自動判別します。

T=25.3,H=48.1     →  {"T": 25.3, "H": 48.1}
25.3,48.1         →  {"ch1": 25.3, "ch2": 48.1}
25.3              →  {"value": 25.3}

v0.2 で足したもの

「毎回手でやる面倒」 を 1 つずつ引き受ける ための 3 ツール。有料化予告済みの機能 (連続ロギング / 閾値アラート / 校正記録) とは非競合の、あくまで基本操作の拡張です。

  • plot_session — Excel を開かずに傾向・外れ値の位置がざっくり見える。matplotlib を入れない (依存を増やさない) ため、Unicode ブロック文字 8 段階で描画。サンプル数が多いときは平均でビン化して幅を合わせる。

  • compare_sessions — 「先週と比べて怪しくないか」 を AI が数値で判断できるように、2 セッションの mean/stdev/drift 差分と Welch 型 t 統計量 (mean_shift_z として返す) を計算する。使う式は 1 本だけ: z = (mean_A − mean_B) / sqrt(σ_A²/n_A + σ_B²/n_B)。これは Welch's t 検定の統計量そのもの (v0.2.3 訂正: v0.2.2 では 「Welch's t-test ではない」 と書いていたが、 statistic は同一で異なるのは判定則という書き分けが正確)。異なるのは:

    • Welch's t-test は t 分布 critical value (自由度依存) で判定 → p 値
    • この tool は 固定閾値 z_threshold (既定 3.0) で単純 gate

    welch_df (Welch-Satterthwaite 自由度) を per-channel に返すので、caller は 「今回の n で固定 3.0 が甘い/厳しい」 を自分で判定できる (v0.2.3 追加)。分母は per-sample の SD ではなく 平均の標準誤差 (SE) の Welch 合成 なので、n が大きいほど固定閾値は厳しくなる (n=100 なら 「平均が 0.3σ 分ずれれば z=3」)。判定は呼び出し側の責任: z_threshold パラメータ (既定 3.0) を明示指定でき、z_threshold_used / z_formula に採用値と式が反映されるので後から audit 可能。is_hypothesis_test: false / disclaimer / per-channel interpretation: "welch_t_statistic_with_fixed_z_threshold" を機械可読 field として置く。

    Guard (v0.2.3): n<2 (stdev 未定義) または σ_A²/n_A + σ_B²/n_B = 0 (定数装置) の場合、gate を評価せずに gate_evaluable: false + gate_skip_reason: "insufficient_samples" or "zero_variance" を返し、mean_shift_z / welch_df / standard_error / significant_shift はすべて None。旧実装 (v0.2.2 以前) では 0/0→NaN→significant_shift=False に落ちて 「差が無い」 と静かに誤報していた path を明示 guard で分離。None は 「差が無い」 ではなく 「gate 未評価」 と読むこと。

  • search_sessionslist_sessions は直近 30 件しか返さないので、~/.benchtop-mcp/ にセッションが溜まってきたらこちら。since/until (ISO 日時) + note キーワード (大文字小文字無視) + port + channel の AND 絞り込み。

v0.2.1 の追加改善 (v0.2.0 レビュー指摘対応):

  • measure 途中失敗の partial 保存 — 装置切断・Ctrl+C・SerialException 等が起きても、そこまでに取れた行はセッションに保存され、返り値の partial: trueabort_reason で通知される。100 回中 60 で止まっても「60 行取れた + 止まった理由」が残る。全部捨てるより実運用の失敗解析に使える。
  • 無効 session_id の structured error — 上の tool 表下の注記の通り、例外ではなく dict で返す。

v0.2.2 の追加改善 (v0.2.1 レビュー指摘対応):

  • compare_sessions の z 式明示 + 検定ではないことの機械可読契約 — 上の compare_sessions 説明参照。docstring は人間 (caller) には届くが LLM の出力語彙までは縛れないので、return dict の is_hypothesis_test: false + disclaimer + per-channel interpretation で field 名として置いている。
  • partial の下流波及Session.aborted_at が立った session は、analyze_session / plot_session の top-level に partial: true + abort_reason を mirror。compare_sessions は 2 入力の非対称 n が z を歪めうるので any_input_aborted: true + aborted_inputs: ["a" or "b"] を top-level に立てる。search_sessions / list_sessions の各行にも partial を出す。中断された session が下流のどの tool を通っても事実が消えない契約。
  • plot_session の legend contract — per-channel dict に label: str (= channel 名) を追加、top-level に channels_order: [str] で render 順を明示。§3.5-c で 「どの line が どの channel か」 を目視 verify する際の後方支援。単位 (unit) は v0.3 へ defer 継続。

v0.2.4 の追加改善 (v0.2.3 実 verify 発火の pain fix、 initial external-verify-derived 変更):

  • search_sessions の date-only 解釈を local midnight 化 — 藤本さん が v0.2.3 verify 中 §3.5-b/c 全 pass 直後 に発見した 実 pain。 session_id は local time (JST) 表示、 started_at は UTC 保存で 日付が 1 日ずれる → since='2026-08-13', note_contains='seed'0 件 (今日作った session を 「今日以降」 で検索すると 消える)。 v0.2.4 では YYYY-MM-DD 形式 (時刻部分なし) を検出し local midnight として解釈 し UTC 換算 (since = 当日 00:00 local、 until = 当日 23:59:59.999999 local)。 「今日以降と書いたら 手元の時計で今日 0 時以降が返る」 直感一致。 完全 ISO (T + 時刻 [+ offset]) は 従来通り 厳密比較。 tz は BENCHTOP_TZ env 優先、 無ければ system local。
  • filters に audit trail 併記 — 返り値の filterssince_resolved_utc / until_resolved_utc / since_date_only / until_date_only / tz_used を 追加。 「意図した通り 解釈されたか」 が 機械可読で 確認可能。
  • started_at_local 副 field を 各 tool に追加 (副役割、 主 fix と 独立)。 search_sessions / list_sessions 各 row + analyze_session / plot_session top-level + compare_sessions の a/b 各 input dict。 空振り時は 見えない (副役割の 明確化) が、 検索成功後に 日時を 読む時に 便利。

v0.2.4 で 明示 defer した もの (「隠す」 選択の 意味論設計先要): plot_session の 外れ値で スケール潰れ (3% 外れ値機構の 1 点が最大値になると 残り 29 点が最下段に潰れる) は sparkline の 数学的性質 (min/max 正規化) の限界。 percentile clip や 外れ値別記号は 「clip した」 事実の どう明示するかの 設計判断が 追加で必要 = v0.2.5+ or 別 tool (plot_session_clipped) 候補。 現状は analyze_sessionoutliers field で 外れ値 位置 + magnitude は 取れる (併用推奨)。


v0.2.3 の追加改善 (v0.2.2 レビュー指摘対応):

  • compare docstring の自己矛盾撤回 — v0.2.2 は 「これは Welch's t-test ではない」 と書いていたが、上の式が示す通り statistic は Welch t そのもの。訂正: 「statistic は Welch t と同一、異なるのは判定則 (df 依存 critical value vs 固定閾値)」 と書き分け。per-channel interpretation string も "threshold_gate_on_welch_standard_error""welch_t_statistic_with_fixed_z_threshold" に更新。
  • Welch-Satterthwaite welch_df 露出 — per-channel に welch_df field を追加。固定閾値 3.0 は n=5 で df≈8 なら本来 2.8〜4.6 が必要 (甘い側)、n=100 で df≈198 なら α≈0.003 相当 (厳しい側) と n 依存する。df を露出することで caller が 「今回の n でこの gate を信用していい範囲か」 を自分で判断できる。partial session は n が任意に小さくなり得るので、この露出は下流波及と繋がっている。
  • zero_variance / insufficient_samples guard — 実害寄りの gap: mock device が定数を返す構成 (σ=0) や n=1 の partial session を compare すると、旧実装は se=0 → z=0/0 (NaN) → abs(z) > 3.0 は常に False に落ちて 「差が無い」 と静かに報告していた。v0.2.3 で明示 guard を導入: n<2gate_skip_reason: "insufficient_samples"σ_A²/n_A + σ_B²/n_B = 0gate_skip_reason: "zero_variance"。どちらの場合も mean_shift_z / welch_df / significant_shift はすべて None (False にすると 「差が無い」 と 誤読されるので None = 「未評価」 signal に分離)。selftest phase [14] で _ConstDevice_FailingDevice(fail_after=1) の 2 case verify。

呼び出し例:

「今日 mock で 5 回、それぞれ 30 秒ずつ測って、最後に一日分をまとめて見せて」
→ measure × 5 → search_sessions(since='2026-08-12') → 各 session plot_session
「先週の校正データと今の値を比べて、有意に動いていないか教えて」
→ search_sessions(note_contains='calib') で基準を特定
→ measure で今の値
→ compare_sessions(session_id_a=今, session_id_b=基準)

導入手順

1. 依存関係を入れる

pip install "mcp>=2.0.0" pyserial

pyserial は実機に繋ぐときだけ必要です。無くてもモックは動きます。

2. 動作確認(Claude に繋ぐ前に、まずこれ)

python benchtop_mcp.py --selftest

以下のように出れば成功です。

== benchtop-mcp セルフテスト ==
[1] ポート一覧: 3件 / pyserial=True
[2] IDN応答: MOCK,BENCHTOP-SIM,0001,1.0.0
[3] パース: 4形式すべてOK
[4] 計測: 60行 / channels=['T', 'H', 'V']
[5] 解析: T平均=25.019967 σ=0.473851 外れ値=2件
[6] CSV書き出し: ... (61行=ヘッダ1+データ60)
[7] plot: width=40 T='▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▂▄▄▄▄▄▆' (len=40, range=5.041)
[8] compare: shared=['T', 'H', 'V'] T delta_mean=0.146467 z=1.573 significant=False
[9] search: note='selftest' で 8 件ヒット (s=True, s2=True)
[10] invalid id → structured error='session_not_found' / 有効 id → Session OK
[11] compare threshold: |z|=0.507 welch_df=89.075 strict(3.0)=False loose(0.5)=True
[12] partial measurement: n_rows=5/20 aborted=True reason='RuntimeError: simulated device failure after 5 reads'
[13] partial downstream: analyze.partial=True/False plot.partial=True/False cmp.any_aborted=True/False cmp.aborted_inputs=['b'] search.partial=True/False
[14a] zero variance guard: evaluable=False reason=zero_variance z=None sig=None df=None
[14b] insufficient n guard: evaluable=False reason=insufficient_samples n_a=1 n_b=60
[14c] guard 独立性: 正常 case (phase [11] 再利用) evaluable=True 対 guard case evaluable=False
[15a] date-only 'since=2026-08-13' → resolved_utc=2026-08-12T15:00:00+00:00 date_only=True tz=Asia/Tokyo
[15b] full ISO 'since=2026-08-13T00:00:00+00:00' → resolved_utc=2026-08-13T00:00:00+00:00 date_only=False
[15c] JST 早朝 session (started=2026-08-12T15:30:00+00:00) が since='2026-08-13' で 拾える → found=True (旧 v0.2.3 では 拾えなかった)
[15d] started_at_local 補助 field: search[0]=2026-08-13T00:30:00+09:00 analyze=2026-08-13T00:30:00+09:00 (JST +09:00 一貫)

全テスト成功。実機が無くてもこのサーバーは動作します。

数値はランダム性で毎回変わりますが、行の形と phase 数 (1〜15) が一致し、末尾が「全テスト成功」で終われば正常です。Windows の cp932 端末でも Unicode スパークラインが表示できるよう、selftest 内で stdout を UTF-8 に切り替えています。phase [15] は BENCHTOP_TZ='Asia/Tokyo' を test 内で 一時的に設定 (finally で復元) して tz 依存を 固定しています。

3. Claude Desktop に登録する

設定ファイル(%APPDATA%\Claude\claude_desktop_config.json)に追記します。

{
  "mcpServers": {
    "benchtop": {
      "command": "python",
      "args": ["C:\\path\\to\\benchtop_mcp.py"]
    }
  }
}

Claude Desktop を再起動すると、ツール一覧に benchtop が現れます。あとは普通に日本語で頼めます。

3.5. 再起動後の動作確認 (推奨)

いきなり本番の指示に入る前に、下の 3 step を挟むと「MCP server が本当に load されているか」「新 3 tool が Claude 側から見えているか」を確実に切り分けられます。MCP server load に失敗していても Claude は既存 tool で "それらしく" 答えてしまうので、失敗が結果に紛れないようにするための保険です。

Step 3.5-a: tool load 確認 (最優先)

使える tool を全部挙げて

期待値: benchtop の 9 tool (list_ports / send_command / measure / list_sessions / analyze_session / export_session_csv / plot_session / compare_sessions / search_sessions) が挙がること。挙がらなければ config が読まれていない → %APPDATA%\Claude\claude_desktop_config.json の path を確認。

Step 3.5-b: seed データを 3 つ作る

search_sessionscompare_sessions の verify には、note で区別できる複数セッションが必要です。

mock で 30 回、note='seed-A' で測って。終わったら少し待って、同じく note='seed-B' で 30 回、
また少し待って note='seed-C' で 30 回、計 3 セッション作って。

期待値: 3 つの session_id が返ること。各 started_at が数秒〜数十秒ずつずれていること (これが後の「一番古い/新しい」判定の根拠になる)。

Step 3.5-c: 新 3 tool を verify

1. さっき作った seed-B の T チャンネルを plot で見せて  (→ plot_session)
2. note に 'seed' を含むセッションを全部リストして      (→ search_sessions、started_at で新旧確認)
3. seed-A と seed-C を z_threshold=2.0 で比べて、
   T が有意にシフトしているか教えて                       (→ compare_sessions with explicit threshold)

期待値: (1) ▁▂▃▄▅▆▇█ のスパークライン + min/max/mean、(2) seed-A seed-B seed-C の 3 セッションが started_at 付きで一覧、(3) mean_shift_z の数値 + z_threshold_used: 2.0 + significant_shift の bool。compare_sessions の判定閾値は明示指定した値で解釈されること (v0.2.1 の責任分界)。


実機に繋ぐ

装置が1行1レコードで値を送ってくるなら、それだけで動きます。Arduino 側の最小例:

void setup() { Serial.begin(9600); }

void loop() {
  float t = analogRead(A0) * 0.0048828125 * 100.0;
  float v = analogRead(A1) * 0.0048828125;
  Serial.print("T="); Serial.print(t, 3);
  Serial.print(",V="); Serial.println(v, 4);
  delay(100);
}

あとは portCOM3(Windows)や /dev/ttyUSB0(Linux/Mac)を指定するだけです。


コードの読みどころ

学習目的で読む場合、この3点が設計の要点です。

1. コアと MCP 層を分離している Bench クラスは MCP を一切知りません。MCP 層(@server.tool() の部分)は薄い皮です。こうしておくと、コアだけ単体テストできますし、将来 CLI や Web API を生やすときにも書き直しが要りません。

2. モック装置を最初から入れている Device という共通インターフェースを挟み、実機(SerialDevice)とモック(MockDevice)を差し替え可能にしています。ハードが手元に無い日でも開発が止まりません。デモも CI も通せます。ハードウェア絡みのソフトでは、これがあるか無いかで開発速度が何倍も変わります。

3. docstring が AI への説明書になる @server.tool() を付けた関数の docstring と型注釈は、そのまま AI に渡されます。ここが雑だと AI は道具を正しく使えません。普通のコードではコメントは人間向けですが、MCP では機械向けの仕様書です。 ここを丁寧に書くことが、そのまま品質になります。


ライセンス

v0.x = MIT (LICENSE file 参照)。 藤本さん 2026-08-12 judgment per 選択。

v1.0+ 以降は AGPL-3.0 + commercial dual への 切替可能性 予告 (LICENSE file 内 「License trajectory notice」 参照)。 但し v0.x code は 永久 MIT (irrevocable) = fork 継続 path は 永久に開く。

背景: Product Transition Judgment Framework v0.1 の 5 checklist を benchtop-mcp v0.x 段階では 0-1 件該当 = 無料継続 default。

参考: 有料化候補 3 点 fit (chat-Claude 2026-08-12 arc 分析)

benchtop-mcp は 「売れる条件 3 点」 全該当 = 有料化 candidate:

  • 誰もが必要: 計測ログ + 統計 + CSV 出力 は 差別化要素でない (全部の 計測現場が やる作業)
  • 自作面倒: シリアル通信 + セッション管理 + AI 説明 docstring は 毎回書きたくない
  • 静かに壊れて高い: 監視ログが 数日 気付かず 溜まる、 品質記録が 消えると 監査で困る

将来 v1.0+ で 有料 tier 検討する場合の 線引き案:

推奨する組み合わせ (将来 v1.0+ の 参考、 現時点 v0.x = MIT)

ソフト部分: AGPL-3.0 + 商用ライセンスのデュアル

  • 個人・研究・社内利用は無料で自由に使える(あなたの「個人にも使ってほしい」という希望を満たす)
  • ただし、これを組み込んだ製品を売るなら、ソース公開か商用ライセンス購入かを選ぶことになる
  • MIT にすると、この選択を後から追加できません。AGPL なら、後から緩めるのは自由(著作権者はいつでも緩められる)

貢献者対応: CLA を最初に用意する

外部から Pull Request を受け取ると、その部分の著作権は相手のものになり、あなた単独でライセンスを変更できなくなります。将来の商用化を残すなら、最初から CLA(貢献者ライセンス同意書)を置いてください。後から遡って集めるのは、ほぼ不可能です。

無料/有料の線引き案

無料(AGPL) 有料
装置操作・計測・解析・CSV
同時接続する装置の数 1台 無制限
長時間の連続ロギング・自動再接続
閾値アラート・異常通知
校正記録・監査ログ(トレーサビリティ)
サポート・SLA

企業が金を払うのは機能ではなく、「止まったときに誰かに電話できる権利」と「監査に出せる記録」 です。個人が欲しがるのは上段だけなので、この線引きなら両者は競合しません。

回路・装置を含める場合の警告 (依然 有効)

公開する前に特許出願の要否を判断してください。 ソフトと違い、ハードウェアは公開した瞬間に新規性が失われ、特許が取れなくなります。日本には新規性喪失の例外(特許法30条、公開から1年以内の出願)がありますが、国によって扱いが異なり、海外出願で詰むことがあります。

これは一方通行のドアです。ユーザーが0人でも損失は確定します。

本 project の scope: benchtop-mcp = 純 software (Python + pyserial via generic protocol) = 特許性事実上ゼロ (シリアル通信 + 統計 + CSV export は 数十年 well-known)。 ハードウェア回路 (Arduino sketch 例は README 参照用のみで 本 repo に含まず) は 依然 上記警告対象。


次にやるとよいこと

  1. --selftest を通す(最優先。ここが通れば土台は正しい)

  2. Claude Desktop に登録して、実際に日本語で呼んでみる (再起動後は上の Step 3.5 の 3 step verify を先に)

  3. 手持ちの装置1台を繋いで、send_command*IDN? に応答が返るか確認

  4. 自分が毎回手でやっている面倒な作業を1つ、ツールとして足す — ただし判定基準は AND 2 条件:

    • (i) 1 回の測定で手作業が 3 step 以上 (装置接続 → コマンド送信 → CSV 貼付、のような複数手順)
    • (ii) 週 1 回以上 発生 (月 1 未満の作業は tool 化コスト > 手作業コスト)

    どちらか外れる作業は tool 化しない。この 2 条件を通ったものだけ 4 tool 目候補として拾う。ここからが本当のオリジナルです。

  5. ライセンスを決めて GitHub に公開

Elasticsearch は奥さんのレシピアプリから始まりました。最初から立派である必要はありません。

from github.com/fc0web/benchtop-mcp

Installing Benchtop

This server has no published package — it is built from source. Open the repository and follow its README.

▸ github.com/fc0web/benchtop-mcp

FAQ

Is Benchtop MCP free?

Yes, Benchtop MCP is free — one-click install via Unyly at no cost.

Does Benchtop need an API key?

No, Benchtop runs without API keys or environment variables.

Is Benchtop hosted or self-hosted?

Self-hosted: the server runs locally on your machine via the install command above.

How do I install Benchtop in Claude Desktop, Claude Code or Cursor?

Open Benchtop on unyly.org, pick your client tab (Claude Desktop, Claude Code, Cursor) and press Install — the config is generated automatically, no JSON editing.

Related MCPs

Compare Benchtop with

Not sure what to pick?

Find your stack in 60 seconds

Author?

Embed badge for your README

Browse similar

All ai MCPs