fullseye

Fullseye を MCP(Model Context Protocol)から使う

Claude Code / Claude Desktop などの MCP クライアントから、fullseye の op を探し、 使い方を読み、実際に走らせるための stdio サーバ。0.1.11 で PoC として入った。

py -3.11 -m fullseye.mcp             # stdio サーバ(クライアントが起動する)
py -3.11 -m fullseye.mcp --demo      # 自分を起動して一通り叩く(下の出力が出れば動いている)
py -3.11 -m fullseye.mcp --coverage  # カタログ 5 層の被覆を JSON で

wheel から動く(0.2.0〜)。0.1.11 はカタログの正本 docs/OP_INDEX.json と知識層 docs/ops/**/*.md をリポジトリ相対で読んでいたので、pip install fullseye した環境からは CatalogError で止まっていた(理由つきで止まるのは正しいが、動かない)。いまは tools/gen_mcp_data.py が 索引の複製とノートの frontmatter だけ(op / dim / category / in / out / halcon + 相対パス、本文は入れない)を fullseye/data/OP_INDEX.json / fullseye/data/OP_NOTES.json に書き、package-data として wheel に入る(約 0.8 MB)。 読む順は 索引 = パッケージ内 → リポジトリ docs/ → 無ければ CatalogError、 ノート = リポジトリ docs/ops(正本、本文も読める)→ パッケージ内の frontmatter → CatalogError。 どこから読んだかは fullseye_catalog_coverage の index_source / notes_source に出る。 wheel ではノート本文の代わりに同梱の Studio help HTML(studio_assets/op_help/)を返し、 返り値の note_body_unavailable にそう書く(黙って代替に落ちない)。図は「入力 → 出力」の 1 枚(op_help/fig/)だけになり、つまみの掃引図(docs/ops/_fig/)は checkout でだけ付く。

複製なのでずれる。py -3.11 tools/regen_all.py --check(CI で回る)と tests/test_mcp_server.py が「複製 = 正本」を件数ごと数え、tests/test_mcp_wheel.py (FULLSEYE_WHEEL_GATE=1 で有効)が wheel を建てて別 venv に入れ、リポジトリの外の cwd から 起動して索引 1,942 / ノート 1,942 が返ることを配布物の側で数える。

Claude Code への登録

claude mcp add fullseye -- py -3.11 -m fullseye.mcp

pip install fullseye した環境でも checkout でも同じ。checkout なら docs/ops の ノート本文と掃引図まで届く(cwd は問わない —— パッケージの位置から解決する)。 読み込める画像は既定で同梱サンプルだけ。自分の画像を読ませるには根を足す:

set FULLSEYE_MCP_ROOT=C:\path\to\images;D:\more   (PowerShell: $env:FULLSEYE_MCP_ROOT = "...")

最小プロファイルは10道具(opはデータとして検索する)

op はデータで、tool は「探す・読む・読み込む・走らせる・観察する」の数個だけ。 tool を 1,942 個並べると LLM の文脈を食い潰す(TheMCPCompany の実測: 18,000 tool は retrieval 無しでは使えない)。

tool 何をするか
fullseye_search_ops 名前・HALCON 名・カテゴリ・次元の部分一致。返り値の sources が出どころ(index / registry / ledger / note / facade)、by_sources が層別の内訳
fullseye_find_ops やりたいことの言葉(日本語・英語・中文 簡/繁・한국어・Deutsch・हिन्दी)で探す。op の要約 7 言語・名前・HALCON 名・型を BM25 で照合し、順位つきで要約(問い合わせの言語)とノートの場所を返す。同義語は拾わない
fullseye_plan have・want・goalから型付きDAGを返す。後ろ向きも同じ超グラフ探索器を使う
fullseye_describe sort・op・族を1件だけ説明する
fullseye_op_cards 候補 op の短いカードを token 予算内で返す。カードの途中では切らない
fullseye_validate_pipeline op 名、型連鎖、a/b の範囲、段数を実行せず検査し、段番号つきの理由と候補を返す
fullseye_op_help op の知識層ノート(使い方・つまみ a/b の実効・HALCON 相当)+ 生成済みで実行が検証された図を resource_link で
fullseye_catalog_coverage カタログ 5 層の交差。検索がどれだけの機能を見えているか
fullseye_list_samples 同梱サンプル(来歴・ライセンスつき)
fullseye_load_image 画像 → ハンドル。画素は返さず、数値統計 + 判定
fullseye_load_stack 複数画像 → images / seq<image> / polsweep の束ハンドル
fullseye_apply ハンドルに op を 1 つ。出力ハンドル + 数値統計 + 判定 + 劣化台帳
fullseye_pipeline op を順に。段ごとに記録、型連鎖は走らせる前に検査、strict は失敗段で停止
fullseye_inspect ハンドルの数値統計 + 判定(+ 小図)
fullseye_fix_text 画像の中の文字を「本当はこう書いてあるべき文字列」に合わせて直す。行ごとに {text, bbox}、mode=repair_flagged(床を超えた字だけ置換、正しい字に触らない)/ rewrite_line(行を丸ごと同じ書体で描き直す)。直した画像はハンドル + 全解像度 PNG、報告に status / reason_code / mismatch(typo か unrelated = 元の字が指示と無関係な疑い)。検証を通らない置換は元に戻す
fullseye_import_json 型付きの値を JSON 封筒で引数に受け取り、以後の op が使えるハンドルにする。json(文字列)か envelope(オブジェクト)のどちらか一方。配列 sort(image / color / region / points / matrix …)はハンドルに、feature / contour / table は値を返す。壊れた封筒・未知 sort は -32602
fullseye_export_json ハンドルの中身を JSON 封筒で持ち出す。structuredContent に fullseye.from_jsonable で bit そのまま戻せる封筒、本文に fullseye.to_markdown の読める描画。readable=true で数をリストに。上限(512 KB)超の大きな画像は isError で断る(画像はハンドル/小図で扱う)
fullseye_write_viewer PoC の結果を自己完結した 1 枚の HTML ビューアにして書く(タブ・正直なバッジ passed / limit / null・数字・3-D の点群/メッシュ・2-D 動画と時系列・前後の分割・ja/en/zh/tw/ko/de/hi/fr の切り替え)。spec(JSON)・lang(言語の配列、先頭が既定)・name。spec は信頼境界で再検証し、形違い・NaN・未知のバッジ/種類・欠けた訳は -32602。書き先はサンドボックスだけで、resource_link で返す
fullseye_estimate_distortion 本来まっすぐな線群の点列(lines=[[x,y],…] を並べた配列、2 本以上・各線 3 点以上)と 3x3 内部行列 K から Brown–Conrady 歪み係数 dist=[k1,k2,p1,p2,k3] を推定(plumb-line 法、チェッカー不要)。radial=1/2/3、tangential 真偽。返す dist は undistort_image にそのまま渡せる。lines/K は信頼境界で再検証し、形が違えば -32602、推定不能は isError

小さな LLM は、候補を絞ってから短い説明だけを渡す。推奨順は find_ops / plan → op_cards → validate_pipeline → pipeline。

{"name":"fullseye_plan","arguments":{"have":"image","want":"feature","goal":"count connected defects"}}
{"name":"fullseye_op_cards","arguments":{"names":["otsu","count_obj"]}}
{"name":"fullseye_validate_pipeline","arguments":{"start_sort":"image","stages":[{"op":"otsu"},{"op":"count_obj"}]}}

find_ops の probe は既定入力での試し実行結果である。 失敗は「その入力では動かなかった」という選択材料であり、op 自体の故障とは断定しない。

触って見るビューアを書く(fullseye_write_viewer)

PoC の結果を、自己完結した 1 枚の HTML(タブ・正直なバッジ・3-D の点群/メッシュ・2-D 動画・前後の分割・言語切り替え)にして書く。Python の fullseye.write_viewer と同じ門を通る(能力ノート interactive-poc-viewer、例 examples/interactive_poc_viewer.py)。

{"method": "tools/call", "params": {"name": "fullseye_write_viewer", "arguments": {
  "lang": ["ja", "en"],
  "spec": {"id": "demo", "title": {"ja": "見本", "en": "Demo"}, "subtitle": {"ja": "副題", "en": "Subtitle"},
           "hint": {"ja": "操作", "en": "Hint"},
           "tabs": [{"id": "a", "kind": "points", "badge": "null",
                     "label": {"ja": "点群", "en": "Points"}, "text": {"ja": "説明", "en": "Text"},
                     "pos": [[0, 0, 0], [1, 0, 0], [0, 1, 1]], "val": [0.0, 0.5, 1.0],
                     "cbar": {"lo": 0, "hi": 1, "unit": "mm"}}]}}}}

文字を直す(fullseye_fix_text)の呼び出し例

画像は fullseye_load_image を color=true で読んでハンドルにし、行ごとに「本当はこう 書いてあるべき文字列」と行に密着した bbox を渡す。Python の fullseye.glyph_correct_spec と同じ items(能力ノート fix-text-in-images、 例 examples/fix_text_in_image.py)。

{"method": "tools/call", "params": {"name": "fullseye_fix_text", "arguments": {
  "handle": "fullseye://img/2c1e7a90b3d4f5e6",
  "items": [{"text": "電気設備", "bbox": [24, 40, 384, 96]}],
  "mode": "repair_flagged"}}}

返り値(本文の抜粋。structuredContent.report に行ごと・マスごとの全記録、fixed_png に 全解像度の PNG のパス、resource_link にも同じファイル):

fix_text(mode=repair_flagged, 床=0.0505 from typeface, 書体 3 本) → fullseye://img/9f0c…
- replaced            電気設備         ・・◆・  mismatch=typo(壊れ 25 %, 距離 0.099)
記号: ・無事 ◆直した ×検証不通過(元に戻した) ?直せない。unrelated は「元の字が指示と無関係」の疑い

JSON で値を出し入れする(fullseye_import_json / fullseye_export_json)の呼び出し例

型付きの値(点群・行列・小さな画像・region …)を JSON 封筒で MCP に注入したり、ハンドルの中身を JSON で持ち出す。封筒は fullseye.to_json / fullseye.to_jsonable が作る自己記述の形 (能力ノート typed-results-as-json、例 examples/typed_results_json.py)。

{"method": "tools/call", "params": {"name": "fullseye_import_json", "arguments": {
  "envelope": {"fullseye_sort": "points", "version": 1,
               "payload": {"encoding": "list", "dtype": "float64",
                           "shape": [2, 2], "data": [[1.5, 2.0], [3.25, 4.0]]}}}}}

返り値の structuredContent に handle / sort / shape(配列 sort のとき)。以後は fullseye_apply などにそのハンドルを渡せる。取り出しは:

{"method": "tools/call", "params": {"name": "fullseye_export_json",
  "arguments": {"handle": "fullseye://img/1524e3d5c6e2dc59", "readable": true}}}

直線群から歪み係数を推定する(fullseye_estimate_distortion)の呼び出し例

本来まっすぐな線(印刷線・建物のエッジ・定規)がレンズで曲がった点列だけから、Brown–Conrady 歪み係数を推定する(チェッカーボードの対応点は不要。能力ノート estimate-lens-distortion、例 examples/estimate_lens_distortion.py)。

{"method": "tools/call", "params": {"name": "fullseye_estimate_distortion", "arguments": {
  "lines": [[[12.0, 41.3], [40.0, 40.2], [100.0, 40.0], [188.0, 41.8]],
            [[41.0, 12.4], [40.2, 100.0], [41.9, 188.0]]],
  "K": [[190.0, 0.0, 99.5], [0.0, 190.0, 99.5], [0.0, 0.0, 1.0]],
  "radial": 2, "tangential": false}}}

返り値の structuredContent.dist が [k1, k2, p1, p2, k3]。そのまま undistort_image に渡せる。

型付き結果に JSON 封筒 + Markdown が自動で付く(fullseye_apply / fullseye_pipeline / fullseye_inspect)

apply / pipeline / inspect の結果が小さい型付きの値(scalar / feature / table / points / matrix / signal / vector / keypoints / counts / contour)のとき、structuredContent.json に fullseye.from_jsonable で bit そのまま戻せる封筒が、本文に fullseye.to_markdown の読める描画(表または 1 行要約)が 自動で付く。画素・場・ボリューム系(image / volume / region / color …)や上限(512 KB)超は付けない —— 大きい画像はハンドル/小図で扱う(fullseye_export_json と同じ判断)。追加情報なので既存の返り (handle / value / stats …)はそのまま。例: count_obj で終わるパイプラインは structuredContent.json に {"fullseye_sort":"scalar",…} が入り、from_jsonable で数値を厳密に回収できる。

画像は「在らず、必要なときだけ在る」

画像は fullseye://img/<sha16> のハンドルでやり取りし、LLM はバイト列を見ない (画像を本文に載せると 1 枚で文脈が破裂する)。代わりに返り値が自分の妥当性を名乗る:

otsu(a=0.50, b=0.50) → fullseye://img/2c1e…
判定=ok(std=0.499 range=1)  shape=[512, 512] min=0 max=1 mean=0.46 std=0.499 nonfinite=0

判定は「標準偏差 0 / 値域 0.1 未満 / 空 / 非有限 / [0,1] の外」を機械的に見て ok / constant / flat / saturated / nonfinite / out_of_range / empty を返す。 判定は必ず生の数値と併記する —— 言葉だけだと、判定器が中身を見ていなくても「健全」と 言えてしまう。免除は ops.NONFINITE_IS_MEANINGFUL(inf が答えの op)と ops.UNIT_RANGE_IS_NOT_THE_CONTRACT(image を名乗るが物理量を運ぶ op)を正本として引く。

判定が ok でないときだけ、入出力を左右に並べた 96 px の小図が resource_link で 自動で付く(vision="thumb" で強制、"none" で抑止)。パイプラインでは最初に割れた段の 1 枚だけ —— 知りたいのは「どの段で壊れたか」だから。

黙って劣化しない(strict 既定)

fullseye には fail-soft 層があるが、MCP pipeline はstrict固定とし、op が失敗したら 拒否して理由を返す。途中までの段階記録は返すが、劣化値を後続段へ流さない。

fail-closed の一覧

境界 規約
ファイルパス 根の下だけ。.. もシンボリックリンクも realpath で潰してから比べる
op 名 registry の完全一致のみ。近い名前は提示するだけで、勝手に倒さない
型 ハンドルの sort ≠ op の in_sort なら拒否。パイプラインは走らせる前に全段を検査
つまみ a, b ∈ [0,1]。外は拒否(丸めない)
引数 スキーマは additionalProperties: false。知らないキーは拒否
結果サイズ structuredContent が 512 KB を超えたら落とし、落としたと本文と _meta に書く
stdout プロトコル専用。ログ・監査は stderr

検証(どう確かめてあるか)

tests/test_mcp_server.py / tests/test_mcp_images.py(66 件): subprocess で本物の stdio を往復させ、stdout にプロトコル以外の 1 バイトも無いことまで見る。 拒否はそれぞれ狙った文言で落ちることを本文で判定する。小図は実際に開いて高さと非黒を見る。 --demo は同じ経路を人が目で見る用。

設計の出どころ

RAD(agents コーパス)の 4 本 —— TheMCPCompany(18,000 tool は retrieval が要る)、 MCP-Universe(GPT-5 でも 43.7 %、ボトルネックは long-context と unknown tools)、 Self-Healing Router(失敗は必ず記録か昇格、沈黙のスキップにしない)、 Function Hijacking(tool 選択の乗っ取り ASR 70〜100 %)—— と、TRIZ の分離原理 (画像は「在らねばならない」かつ「在ってはならない」→ 時間で分離 + #25 セルフサービス)。 画像処理ライブラリを MCP で公開した先行例は、8 コーパスを引いて 0 件だった(2026-09-15)。

AI に op を選ばせる短い手順

公開する計画入口は fullseye_plan、fullseye_describe、fullseye_choose の3つだけである。 未計測の制約は推測で補わず、fail-closedの理由として返す。tools/list に {"profile":"minimal"} を渡すか、FULLSEYE_MCP_PROFILE=minimalを設定すると実行に必要な10道具を公開する。 docs/mcp_context_benchmark.jsonを生成する実測では、全20道具が3,256トークン、最小10道具が1,458トークンである。 未検証の設計パターンと重複した計画入口はMCP一覧に出さない。