Skip to main content
Glama
README.md
# mmm-mcp

MikuMikuMoving(MMM 1.2.9.2・64bit)のモデル・ボーン・キーを、外から読み書きするための道具です。
MMM の中で動く橋渡しのプラグイン(C#)と、それに頼む MCP のサーバー(Python)でできています。

```
MMM の中の常駐プラグイン(C#)── 127.0.0.1:9560・1 行 1 JSON ──┬─ mmm-mcp(MCP・Python)
  bridge/ 入口  core/ 中身                                       └─ AIナカワリ の MMM 版
```

- [AIナカワリ](https://github.com/BeamManP/MMD-AINakawari)(MMD・MMM で、選んだキーの間を AI が作るツール)の MMM 版の土台です。AIナカワリ の配布の zip には、この橋渡しの DLL が入っています
- MCP に対応した AI(Claude など)から、MMM のキーを読んだり、差し替えたり、元に戻したりできます
- MMD(MikuMikuDance)用の同じ道具は [mmd-mcp](https://github.com/BeamManP/mmd-mcp) です

## 中身

| 場所 | 何か |
|---|---|
| `bridge/` | 入口の DLL(`MmmBridge.dll`)。MMM の常駐型プラグイン。127.0.0.1:9560 で待ち受けて、頼みを MMM の画面の糸に渡すだけ。ほぼ変えない |
| `core/` | 中身の DLL(`MmmBridge.Core.dll`)。頼みの処理。入口がファイルをバイト列で読むので、MMM を開いたまま入れ替えられる |
| `src/mmm_mcp/` | MCP のサーバー(`server.py`)と、橋渡しに頼む関数(`bridge.py`) |

## 組み立てと入れ方

.NET の SDK(`dotnet`)で組み立てます。対象は .NET Framework 4.8(MMM は .NET Framework 4 で動きます)。
MMM の `System\x64\MikuMikuPlugin.dll`・`DxMath.dll` を参照するので、`MmmDir` に MMM のフォルダを渡します
(省くと、このフォルダの隣の `..\MikuMikuMoving64\`)。

```bash
dotnet build bridge/MmmBridge.csproj -c Release -p:MmmDir=C:/MikuMikuMoving64/
```

```bash
dotnet build core/MmmBridge.Core.csproj -c Release -p:MmmDir=C:/MikuMikuMoving64/
```

MMM のフォルダに `Plugins\` があれば、組み立てた物を次の場所へ写します(無ければ `bin\Release\` に残るだけ)。

```
MikuMikuMoving64\Plugins\
  MmmBridge.dll
  MmmBridgeCore\MmmBridge.Core.dll
  MmmBridge.ini            (任意。下の「試し用の頼み」)
```

入口は MMM が握るので、**入口を組み立て直す時だけ MMM を閉じます**。中身は MMM を開いたまま組み立てて、`mmm_reload_bridge`(頼み `{"cmd": "reload"}`)で読み直せます。
MMM を起動すると約 10 秒で待ち受けが始まります。様子は `Plugins\MmmBridge.log` に出ます。

MCP の環境は uv で作ります。

```bash
uv venv --python 3.10 .venv && uv pip install --python .venv/Scripts/python.exe -e .
```

Claude Code への登録の例:

```bash
claude mcp add mmm -- <このフォルダ>/.venv/Scripts/python.exe -m mmm_mcp.server
```

## 試し用の頼み(ふだんは閉じています)

橋渡しが受けるのは、ふだんはキーの読み書きなどの頼みだけです(`ping`・`models`・`bones`・`keys`・`marker`・`set_marker`・`model_file`・`replace_keys`・`select_keys`・`load_model`・`load_motion`・`undo`・`redo`)。
MMM の中身を覗く・MMM の中のメソッドを呼ぶ・画面を操作する頼み(`inspect`・`undo_call`・`undo_peek`・`plugin_run`・`ui_*`・`reload`)は、
`Plugins\MmmBridge.ini` に次の 2 行がある時だけ受けます。頼みのたびに読むので、MMM を閉じ開きしなくても効きます。

```ini
[MmmBridge]
debug=1
```

待ち受けは 127.0.0.1(この PC の中)だけです。ほかの PC からは届きません。

## MCP の道具

| 道具 | すること |
|---|---|
| `mmm_status` | つながるか。MMM の版・fps・モデルの数・橋渡しの版 |
| `mmm_list_models` | モデルの一覧(番号・名前・アクティブか) |
| `mmm_get_model_file` | モデルを読み込んだ PMX の場所(MMM の中を読む。zip の中のモデルは zip の場所と中の名前) |
| `mmm_list_bones` | ボーンの一覧(番号・名前・親・初期位置・付与・フラグ・レイヤ) |
| `mmm_get_keys` | キーを読む(フレーム・位置・四元数・補間曲線・選択中か)。ボーン名・範囲・選択中だけ・数だけ、で絞れる |
| `mmm_replace_keys` | 範囲のキーを差し替える。元に戻す(Ctrl+Z)に 1 段で載る |
| `mmm_select_keys` | タイムラインのキーの選択を付け直す(フレームか範囲・ボーンで。中身は変えない) |
| `mmm_undo`・`mmm_redo` | MMM の Ctrl+Z・Ctrl+Y と同じ |
| `mmm_get_marker` | 今のフレーム(頼み `set_marker` で動かせる) |
| `mmm_load_model` | モデルを読み込む(MMM がプロジェクトを開く時の処理を使うので、確認の窓が出ない) |
| `mmm_load_motion` | 選択中のモデルに VMD を読み込む(MMM の「モーション読込」と同じ足し込み) |
| `mmm_plugin_run` ※ | コマンド型プラグインを、ツールバーのボタンを押したのと同じように動かす |
| `mmm_ui_forms`・`mmm_ui_read` ※ | MMM の中で開いている窓の一覧・窓の部品の字と状態 |
| `mmm_ui_click`・`mmm_ui_check`・`mmm_ui_set_text`・`mmm_ui_reveal` ※ | 窓のボタンを押す・選択肢を入れる・文の欄に字を入れる・タブを前に出す(画面をクリックしない) |
| `mmm_reload_bridge` ※ | 橋渡しの中身を読み直す |

※ は試し用の頼みを使います(`MmmBridge.ini` の `debug=1` が要ります)。

`mmm_load_model`・`mmm_load_motion` があるので、MMM を閉じ開きしても、場面は外から作り直せます(読み込み直した後のキーが最初と全部一致)。

**MMM の起動直後に読み込ませない**: 待ち受けが始まってすぐ(1 秒以内)に `mmm_load_model` を呼んだら、MMM が DevExpress の描画(`Graphics.get_PageUnit` の `InvalidOperationException`)で落ちました。
起動から 15 秒待ってから読み込ませると落ちません。`mmm_plugin_run` に渡す本体の窓は、見えていて題が「MikuMikuMoving」で始まる窓(MMM がプラグインに渡すのと同じ TopForm)。

## 確かめた事(2026-11-10・MMM 1.2.9.2・Tda式初音ミクV4X・許してにゃん体操モーション)

- 読んだキー 8287 個を元の VMD と突き合わせて、位置の差 最大 2.4e-7・回転の差 最大 5e-6 度・補間曲線の違い 0
- 補間曲線は VMD の 64 バイトの並びと同じ意味(`x: [ax, ay, bx, by]` = VMD の `[0], [4], [8], [12]`。y・z・r は 1 つずつずれる)
- MMM は、キーの無いボーンにも 0 フレーム目に既定のキーを持つ(VMD に無くても在る)。一番下のレイヤは名前が無い(null)
- 2 本のボーンを一度に差し替え → 元に戻す → やり直す → 元に戻す で、全 8465 キーが前と一致。人の手の Ctrl+Z でも一致
- 元に戻した後は、戻ってきたキーが選択された状態になる(中身は同じ。選択の印だけ違う)
- `mmm_get_model_file` の PMX を読み、MMM のボーンの情報と比べて、239 本の番号・名前・親・初期位置・付与の食い違い 0。
  IK・変形階層は公開の口に無いが、PMX をそのまま読めば足りる(ボーンの番号も PMX と同じ)

## 元に戻すの仕組み(非公開の中身を使っています)

MMM のプラグインの公開の口には、元に戻すがありません。MMM の中の `Scene.Controller`(`SystemController`)と `UndoSystem`(`UndoRedo`)を、リフレクションで使っています。
考え方は mfakane さんの [MikuMikuMoving-Plugins](https://github.com/mfakane/MikuMikuMoving-Plugins)(WTFPL)の `SceneExtensions.BeginUndoBlock` と同じです。
MMM は 2018 年の 1.2.9.2 が最終版なので、この中身が変わる心配は小さいですが、ほかの版では動かないかもしれません。

1. 書き換えるレイヤで、消すキーだけを選択 → `ActiveObject.AddSelectedToRemoveFrameData()`
2. キーを書き換える(新しいキーは選択した状態で入れる)
3. `ActiveObject.AddSelectedToAddFrameData()` → `UndoSystem.PushUndo()`
4. 書き換えていないレイヤの選択を元に戻す

**踏んだ所**: MMM の元に戻すは、足したキーを消す時に「そのフレームのキーが、記録した実物と同じ物か」を見て、違えば黙って何もしません(`Character.RemoveUndoFrameData`)。
なので 3 の後で、書き換えたレイヤのキーを入れ直してはいけません(`ReplaceAllKeyFrames` は全部消して作り直す)。
また、プラグインの口は `Bone`・`MotionLayer` を取り出すたびに新しい包みを返すので、「同じレイヤか」は包みで比べず、`BoneID:LayerID` で比べます。

元に戻すに載るのは、**アクティブなモデル**のキーだけです(MMM の記録が ActiveObject を相手にするため)。

**選択の付け外し**は、キーを入れ直さず、`foreach` で取れる `IMotionFrameData`/`IMorphFrameData` の `Selected` に書きます(MMM の中のキーに直接書く作りなので、実物がすり替わらない)。
書き換えた後は、範囲の外で選んでいたキーの選択を元に戻し、新しいキーは選択しません。

## ライセンス

[MIT-0](LICENSE)。

- MikuMikuMoving(Mogg さん)の `MikuMikuPlugin.dll`・`DxMath.dll` を参照して組み立てます。どちらもこのリポジトリには入れていません(MMM に入っている物を使います)
- 元に戻すの作りは、mfakane さんの [MikuMikuMoving-Plugins](https://github.com/mfakane/MikuMikuMoving-Plugins)(WTFPL)の考え方によります