LogiSheets について
LogiSheets は Rust で書かれた表計算エンジンを提供する MCP サーバーで、Excel 互換の計算と実際の .xlsx 出力を必要とするエージェント向けです。セル座標ではなくキーとフィールド名で参照するブロック単位でデータを整理し、構造と意味をファイルに保存するため、後のセッションでも読み取れます。
主な機能
- 01エンジンが計算する Excel 互換の数式
- 02行キーとフィールド名で参照する名前付きブロック
- 03スキーマ説明を含む実際の .xlsx の保存
- 04説明に記載される BLOCKREF と #FIELD の規則
- 05変更のプレビュー、ゴールシーク、依存関係の追跡
- 06ブロックのフィールドに追従するネイティブ Excel チャート
活用シーン
- 01複数年の売上やキャッシュフローの予測モデルの作成
- 02前提条件の感度分析と逆算による入力値の算出
- 03既存のブックを新しいセッションで引き継ぎ、内容を説明
MCP 対応
MCP サーバーとして、ブックの作成、数式の評価、.xlsx の保存、チャート作成などのツールを既定で約 32 個公開します。
設定
ローカルサーバーの設定はどのクライアントでも共通です。コピーしてクライアントの MCP 設定ファイルに貼り付けてください。
npx -y logisheets-mcpLOGISHEETS_MCP_TOOLSTool surface: 'core' (default, 32 tools) or 'full', which adds formatting, comments, links, undo/redo and raw structure operations.ローカルサーバーにはマシン上の実行環境(Node.js 18 以上など)が必要です。設定ファイルの場所はクライアントごとに異なります。
ツール
32掲載時のツール一覧 · 2 週間前に更新
| パラメーター | 型 | 必須 | 説明 |
|---|---|---|---|
| name | string | いいえ | base64 から読み込む際にエンジンへ報告するファイル名。 |
| path | string | いいえ | 読み込む既存の .xlsx のパス。省略すると空のブックになります。 |
| xlsx_base64 | string | いいえ | 共有ファイルシステムがない場合に使用する、Base64 エンコードされた .xlsx です。可能であれば `path` を使用してください。 |
詳細
logisheets-mcp
エージェントが考えるための、本格的なスプレッドシートエンジンです。 頭の中で処理する必要のない Excel 互換の数式、座標ではなく名前で指定できるテーブル、そして最後には、人が開いて監査し、そのまま使い続けられる本物の .xlsx ファイルを作成できます。
Rust で書かれたスプレッドシートエンジンである LogiSheets 上で動作する MCP サーバーです。MIT ライセンスで、マシン上で動作し、ソケットを開きません。
グリッドの問題点
モデルに5年間の予測を頼むと、行番号を手作業で調整しながら20個の数式を書きます。そこに気づかれないミスが潜んでいます。B8 のつもりで B7 を参照しても、合計はもっともらしく見え、エラーも発生しません。
その後、シートが変更されます。誰かが先頭に行を挿入したり、年を削除したり、列を追加したりします。モデルが保持していた座標はすべて1つずれてしまい、それに気づく方法もありません。そのため、依頼された質問に取り組む代わりに、次のやり取りではセルを読み直して内容の位置を突き止めることになります。
「もしこうだったら」というシナリオを試すたびに、入出力の往復が必要になります。入力を書き込み、再計算し、出力を読み取り、元に戻すのです。16のシナリオを試すには、この作業を16回行います。途中で停止したスキャンでは、シナリオがモデル内に残ってしまいます。
ブロック
ブロックは、シート上の名前付きテーブルです。行にはキーがあり、列には名前があり、位置ではなく、それらを使ってすべてを指定します。
- フィールドの数式は、セルごとではなく列全体に対して一度だけ指定します。 行を追加すると、自動的に計算されます。20個目の数式を間違える心配はありません。
- 参照には、その意味を示す名前を指定します。たとえば、キーが
Y3の行のpvフィールドです。その上に行を挿入しても、参照の内容は変わりません。「8行目」とは指定していないからです。 - 計算値はエンジンが管理します。 数式フィールドを、モデルが自分で計算した数値で上書きすることはできません。
create_block proj fields: year, fcf, df, pv
set_field_rule proj.pv = fcf × df ← once, for the column
add_block_rows Y1 … Y5
describe_block proj
→ Y1 147.2727 Y2 144.5950 Y3 141.9660 Y4 139.3848 Y5 136.8506
… the sheet is then reshaped: two rows inserted at the top, a column at the left …
describe_block proj
→ Y3 141.9660 ← same answer, same address, nothing re-derived
ブロックはエージェントが作業中に作成するため、事前の準備は不要です。空のワークブックでも、メールで受け取ったスプレッドシートでも指定できます。convert_to_block は、通常のセルにあるテーブルを取り込み、ヘッダー行からフィールド名を読み取り、どの列がキーかを判別します。
2回目のセッション
スプレッドシートを作成する会話と、そのスプレッドシートについて質問に答える会話は、ほとんどの場合別のものです。1週間後、新しいセッションが始まります。そこには何のコンテキストもなく、ファイルだけがあり、そのセッションが知り得るのはファイルに記録された内容だけです。
グリッドに記録されるのは座標です。=B11*$B$3*(1-$B$4) は正しくても、エージェントがラベル列を取得し、A3 が B3 を説明していると推測するまでは、何の意味も持ちません。意味を記述する場所はスキーマであり、.xlsx に書き込まれます。フィールド名、キー列、エンジンが計算するフィールド、それぞれの背後にあるルールが含まれます。list_blocks を1回呼び出せば、ワークブックが自己紹介し、describe_block を1回呼び出せば、ルールが #FIELD("revenue")*BLOCKREF("assum","margin","v") として返されます。これは、2回目の検索を必要とする問題ではなく、説明そのものです。
スキーマが示すのは、レコードの形だけです。レコードが何を意味するのかは文章で表すため、ブロックには説明が付いています。create_block で1、2文を指定するか、後から取り込んだテーブルには set_block_description を使って、1行が何を表すのか、直感的には分かりにくいフィールドに何が入るのか、後から読む人が触れてはいけないものは何かを記します。説明はファイルに保存され、describe_block から返されます。これにより、次のセッションは列名から意図を推測するのではなく、意図を読み取れます。
src/cold-read.test.ts は、単に意図を主張するのではなく、テストで確かめています。あるセッションでモデルを構築して保存し、最初のセッションとは何も共有しない別のセッションでファイルを開きます。専用のサーバー、専用のワークブック、記憶はありません。次に、list_blocks を1回呼び出すだけで、すべてのブロックのフィールド、キーとなるフィールド、計算フィールド、行数が取得できることを確認します。返されたすべてのルールについて、#FIELD または BLOCKREF を含み、A1座標は一切含まないことを検証します。新しいセッションが向きだけを頼りに BLOCKREF の数式を書き、エンジンの結果がテスト内で独立して行った算術計算と一致することも確認します。また、誰かが編集する前に trace が、ある前提を読み取るものを示すことも確認します。コストは、ホストがモデルに見せるのと同じテキストを対象に、通信上で計測されます。5行のモデルでは540 B、105行のモデルでは545 Bで、どちらも呼び出しは1回です。スキーマの読み取りは O(columns)、グリッドを読み取って理解する場合は O(cells) です。データを要求すれば、そのデータに見合ったコストがかかります。105行の場合は11 kBです。重要なのは、2回目のセッションがどちらを選ぶか決められることです。
詳しい説明とその理由については、docs/the-second-session.md をご覧ください。
再計算されるグラフ
グラフを求められたエージェントは、通常、画像を描画します。その時点では画像は正しくても、その後、人間が前提を変更すると、もう正しくない数値を表す画像になってしまいます。
chart_from_block は画像ではなくワークブックにグラフを書き込みます。グラフに保存されるのは値ではなく、参照です。
chart_from_block rev value_fields: q1, q2 category_field: region
→ <c:val><c:numRef><c:f>Rev!$B$1:$B$3</c:f></c:numRef>
これは保存された .xlsx 内のネイティブな Excel の c:chartSpace であり、Excel 自身が書き込むオブジェクトと同じものです。ソースセルを編集すると、Excel でもここでもグラフが追従します。ブロックに地域を追加すると、その地域は自動的にグラフに表示されます。系列は、グラフ作成時にフィールドがたまたま配置されていたセルではなく、フィールドに紐づいているためです。行や列を挿入しても、参照先が誤った場所にずれることはありません。ブロックになっていないデータには、任意のA1範囲に対して同じ処理を行う chart_insert を使います。
src/agent-loop.test.ts は、ツールの戻り値ではなく、ファイルのバイト列からこれを検証します。ブロックを作成してグラフを追加し、保存した .xlsx を展開して、グラフのパーツが存在することと、その系列がシート内の <c:f> 参照になっていることを確認します。
ベンチマーク
主張ではなく、実測値です。ローカルの.xlsxで動作するほかの2つのMCPサーバー、独自のRust再計算エンジンを備えたspreadsheet-kit 0.11.1と、openpyxlを使用する、最もインストールされているexcel-mcp-server 0.1.8との比較です。
| このサーバー | spreadsheet-kit | excel-mcp-server | |
|---|---|---|---|
| 数式を書き込み、その値を読み取る | 30 | 30 | "=SUM(A1:A2)" |
| 5年間のDCF、1株当たり価値 | 20.803603 · 15回の呼び出し | 20.803603 · 6回の呼び出し | 数式テキスト |
| 4×4の感度分析、16個の回答 | 1回の呼び出し、950 B | 16回の呼び出し、1245 B | 不可 |
| 入力値を逆算する | 1回の呼び出し、202 B | 18回の呼び出し、1399 B | 不可 |
| 後で再度開いて内容を説明する | 4回の呼び出し、2.4 kB | 5回の呼び出し、21 kB | 2回の呼び出し、24 kB |
| 構成変更後に再度回答する | 19.383943 | #VALUE! | 数式テキスト |
| 引き継いだファイルの機能を維持する | 8個中8個 | 8個中8個 | 8個中8個 |
再現するには、タスクごとに1つのファイルを使います。各ファイルで3つのサーバーすべてを実行します。
npm run build # ours is driven as dist/cli.js
python3 bench/t1_compute.py # bench/t*.py
ほかの2つの候補には、事前にアクセスできる状態にしておく必要があります。spreadsheet-kitはamd64のDockerイメージとして、excel-mcp-serverは$BENCH_WORK/.venv(デフォルトは/tmp/bench-work)の仮想環境で実行します。それぞれの起動方法の詳細はbench/contestants.pyを参照してください。
ほかのサーバーのツール一覧を読む前に、タスクをコミットしました(bench/TASKS.md)。期待値はすべて、サーバーの出力を参照せずにPythonで独立して導き出しています。また、負けると予想したタスクも意図的にリストに含めています。
表の意味を過大に受け取らないよう、注意点が3つあります。"=SUM(A1:A2)"はバグではありません。openpyxlは数式を評価せずに保存するため、このサーバーは正しいモデルを書き込みますが、そのモデルについての質問には答えられません。spreadsheet-kitは正真正銘の同等製品で、実行できるタスクはすべて正しく処理し、私たちより少ない呼び出し回数でモデルを構築します。私たちの呼び出し回数が多いのはスキーマを宣言するためで、その代わりに以下の行で示す効果が得られます。また、読み取りの行では各サーバーがそれぞれ自身で書き込んだファイルを読み取っています。そのため、差のうちスプレッドシートを人から受け取った場合にも当てはまるのは半分だけです。最後の行は8個中0個から始まりました。このタスクを書いたことで、エンジンが扱わない機能が保存時にすべて失われていたことが分かりました。
インストール
Node 20以降が必要です。
npm install -g logisheets-mcp
Claude Desktopでは、claude_desktop_config.json(macOSの場合:~/Library/Application Support/Claude/claude_desktop_config.json、Windowsの場合:%APPDATA%\Claude\claude_desktop_config.json)に追加してから、再起動してください。
{
"mcpServers": {
"logisheets": {
"command": "npx",
"args": ["-y", "logisheets-mcp"]
}
}
}
stdioサーバーを起動するMCPホストなら、どれも同じ方法で動作します。Cursorは~/.cursor/mcp.jsonから同じ設定ブロックを読み込みます。
試してみる
3年間の売上モデルを作成してください。単価9.50ドルで100個から始め、年間40%成長し、売上原価は30%とします。その後、~/model.xlsxに保存してください。
数値はモデルの推測ではなくエンジンから返され、.xlsxには有効な数式が含まれています。Excelで前提条件を変更して、再計算される様子を確認してください。LLMを使わずに同じ処理を確認するには、npm run build && npm run demoを実行すると、実際のサーバーをstdio経由で操作し、処理中にすべての主張を検証します。
ツール
デフォルトでは32個です。リストが増えるにつれてツール選択の精度は低下し、すべての説明が毎ターンのコンテキストを消費します。
| ツール | 説明 |
|---|---|
open_workbook | 新しいブックを開始するか、既存の .xlsx を読み込みます。省略可能です。初回使用時に自動でブックが作成されます。 |
save_workbook | 実際の .xlsx ファイルを書き出します。作業結果を返すときに使用します。 |
export_xlsx | 共有ファイルシステムのないホスト向けに、ファイルを base64 で返します。 |
list_blocks | すべてのシートとブロック、および次のブロックを配置すべき場所を表示します。 |
describe_block | ブロックのスキーマ、キー、フィールドルール、説明、必要に応じて値を表示します。 |
eval_formula | 数式を評価して値を返します。結果は保存されません。 |
create_block | 名前付きのテーブルを作成します。最初のフィールドが行キーになります。 |
convert_to_block | 通常のセルにすでにあるテーブルを、その場でブロックに変換します。 |
set_block_description | ブロックの用途を文章でファイルに書き込みます。 |
add_block_rows | レコードを末尾、または after_key / before_key の位置に追加します。 |
delete_block_rows | レコードを削除します。 |
move_block_row | キーを指定して行の順序を変更します。表示上の変更のみで、値は変わりません。 |
set_block_cells | (block, row_key, field) でセルを書き込みます。まとめて実行され、処理はアトミックです。 |
set_field_rule | フィールドに数式、入力規則、または編集可否ルールを設定します。 |
create_analysis_block | テーブルの下に、手作業で書き込むのではなくレシピから生成した合計行を作成します。 |
edit_analysis_block | 参照名を維持したまま、集計するフィールドと方法を変更します。 |
create_pivot | ブロックをクロス集計します。グループごとに1行、値ごとに1列が作成され、内容はリアルタイムで更新されます。 |
edit_pivot | ピボットの構成をその場で変更します。壊れたピボットを修復する唯一の方法です。 |
refresh_pivot | ピボットの行と列をソースに合わせて更新します。 |
list_violations | フィールドの入力規則に違反しているセルと、その理由を表示します。 |
list_names | ブックで定義されている名前と、それぞれの参照先を表示します。 |
preview_changes | 編集を実行せずに、その結果を確認します。仮定を1つ指定する方法と、1回の呼び出しでシナリオのグリッド全体を試す方法があります。 |
trace | 依存関係グラフをたどり、セルが参照しているものと、そのセルを参照しているものを表示します。 |
goal_seek | 出力が目標値に達する入力を求めます。エンジン内で探索します。 |
create_sheet | シートを追加します。 |
chart_from_block | フィールド名を指定してブロックをグラフ化します。後から追加された行も反映されます。 |
chart_insert | 任意の A1 範囲をグラフ化します。生セル向けの方法です。 |
chart_list | シート上のグラフについて、種類、タイトル、各系列が参照する範囲を表示します。 |
chart_update | グラフの種類、系列、軸、ラベルをその場で再設定します。 |
chart_delete | グラフを削除します。 |
get_cells / set_cells | 構造化されていないデータ用の、生セル操作のための手段です。 |
数式は Excel と互換性があり、名前でブロックのセルを読み取る BLOCKREF(block, key, field) も使用できます。フィールドルール内では、#FIELD("name") は同じ行の兄弟フィールドを、#FIELD("name", "key") は同じブロックの別の行を参照します。後者は位置オフセットではなく、指定したキーを持つ行を参照します。
preview_changes と goal_seek は、モデルの探索方法を変える2つの機能です。各シナリオは専用の一時ブランチ上で実行され、破棄されます。そのため、4×4 の感度グリッドも1回の呼び出しで16個の数値を返し、ブックには何も書き込まれません。また、逆算も二分探索の各ステップで呼び出すのではなく、1回で実行できます。trace は数式のテキストだけでは分からないことに答えます。セルが何を参照しているかではなく、何がそのセルを参照しているかを示すため、前提条件に手を加える前に役立ちます。
分析ブロックとピボットは、エンジンが導出するサマリーです。エージェントが、対象のブロック、フィールド、関数を指定したレシピを宣言すると、エンジンがすべての数式を生成します。そのため、ソースフィールドの名前を変更してもサマリーは壊れずに再構築され、後から追加された行も最初から集計に含まれます。どちらも通常のブロックに作成されるため、BLOCKREF("by_region", "East", "Q2") でクロス集計の1セルを参照し、次の計算に使えます。そうしない場合にエージェントが選びがちな方法は、テーブルをコンテキストに読み込み、合計して答えを書き込むことです。この方法では定数が作成されるため、次の行が追加された時点で誤った値になります。
list_names はコア機能に含まれていますが、名前を書き込む 3 つのツールは含まれていません。これは意図的なものです。定義名は、ラベルが付いた座標範囲です。Sales = Sheet1!$B$2:$B$20 は行を追加しても拡張されません。まさにこの脆弱性を取り除くためにブロックがあります。しかし、他の人が作成したブックには定義名が多数使われていることがあり、Sales が何を範囲としているか知らなければ =SUM(Sales) を読み取れません。つまり、定義名の読み取りは状況把握のためであり、それを使って作成するのは、より適したツールの領域に踏み込むことになります。
ピボットを信頼する前に知っておくべきことが一つあります。数値は最新ですが、形状は更新されません。数式で行を追加することはできないため、ピボットの作成後にソースで初めて登場したグループは、そのまま表示されません。数値はすべて正しく、テーブルは不完全なのに、何も問題がないように見えます。describe_block は欠落しているグループを示し、refresh_pivot はそれを復元します。そのため、この 2 つはフラグの背後ではなく、コア機能に含まれています。
LOGISHEETS_MCP_TOOLS=full を設定すると、73 個のツールを利用できます。元に戻す/やり直し、書式設定、セル結合、コメント、チェックポイント、ブロックの移動/サイズ変更、ブロック間リンク、ブロックの権限、chart_suggest、名前の定義と変更、行/列の生の構造です。変更を伴うツールには MCP の readOnlyHint / destructiveHint アノテーションが付いているため、ホスト側で承認を必須にできます。
チャートツールには、他のすべてのツールが取り除いている名前空間の接頭辞が残されています。その名前空間内では list、insert、update、delete と呼びます。delete_rows や delete_block_rows の隣に何の接頭辞もない delete があると、モデルがどれを選ぶかは運任せです。この問題を避けるために、このセクション全体があります。
返されるファイル
save_workbook は実際の .xlsx を書き出し、バイト列ではなく MCP のリソースリンク(URI、メディアタイプ、サイズ)を返します。200 KB のブックに対して約 280 KB のコンテキストを消費するうえ、モデルに何も学習させないためです。ファイルが必要なホストは workbook://current.xlsx から読み取ります。export_xlsx は、リソースにまったく対応していないホスト向けに base64 を返します。
数式は、人が読めるように BLOCKREF("proj","Y3","pv") として書き出すことも、Excel が処理できるように単純な座標に解決することもできます。
1 つの MCP セッションで 1 つのアクティブなブックを保持し、ツール呼び出しの間もその状態を維持します。この永続性によって、単なる計算機ではなく記憶として機能します。
読み取りと書き込みは、サーバープロセスがアクセスできる場所ならどこでも行われます。これはローカルの stdio サーバーでは通常のことであり、公式のファイルシステムサーバーと同じ動作です。意図した範囲のアクセス権だけを持つユーザーとして実行してください。
ネットワークなし
ソケットもポートもテレメトリーもありません。ホストがこれを子プロセスとして起動し、標準入力と標準出力を介して改行区切りの JSON-RPC をやり取りします。エンジンも同じプロセス内の WASM であるため、数式はリクエストではなく関数呼び出しです。ネットワークから隔離されたマシンでも実行できます。これは確認済みです。ブロックの作成、フィールドルールの適用、数式の評価、.xlsx の保存からなるセッション全体の後も、プロセスが保持するのは 6 本のパイプだけで、ソケットはなく、リッスン中のポートもありません。
開発
3 つの LogiSheets パッケージを薄いシェルで包んでいます。
logisheets-runtime(ヘッドレスエンジン)、logisheets-logician(ツール定義)、および Rust/WASM コアです。
npm install && npm test
エンジンも同時に開発するには、LogiSheets を兄弟ディレクトリとしてチェックアウトし、そのパッケージをビルドして、npm run link:local を実行します。これにより、3 つのパッケージが node_modules にシンボリックリンクされ、再インストールしなくてもエンジンのローカル変更が反映されます。npm install の後は、もう一度実行してください。
ライブラリとして使う場合、createServer は MCP の Server、WorkbookSession、ツールマップを返すため、任意のトランスポートを介してホストできます。
import {createServer} from 'logisheets-mcp'
const {server, session, tools} = createServer({mode: 'full'})
ライセンス
MIT。LogiSheets プロジェクトの一部です。
コメント
AI アプリケーションが外部のツールやデータソースに統一的な方法で接続するためのオープンなプロトコルです。クライアントはモデルとの対話を担い、MCP サーバーはツール・リソース・プロンプトテンプレートを提供します。標準メッセージでやり取りするため、1 つのサーバーを複数のクライアントで再利用できます。