症状
MCPツール presentMulmoScript の 「ビート差し替え」(filePath + beatIndex + beat)が、
何も書き換えずに成功として返る。キャンバスも当然更新されない。
エラーは出ない。レスポンスは通常の再表示と同じ Loaded MulmoScript from stories/<file>.json で、
呼んだ側からは成功と区別がつかない。
原因
ホスト側のツール呼び出しハンドラが、引数の許可リストから beatIndex / beat を落としている。
server/backends/mulmoscript.ts handleToolCall:
const args: SaveMulmoScriptArgs = {
...(body.script !== undefined ? { script: body.script } : {}),
...(typeof body.filename === "string" ? { filename: body.filename } : {}),
...(typeof body.filePath === "string" ? { filePath: body.filePath } : {}),
...(typeof body.autoGenerateMovie === "boolean" ? { autoGenerateMovie: body.autoGenerateMovie } : {}),
};
const outcome = await executeMulmoScriptSave({ files: { artifacts: instance.backend.artifacts } }, args);
beatIndex と beat がここに無いため、executeMulmoScriptSave に届かない。
パッケージ側は実装済みで、型にも入っている:
// @mulmoclaude/mulmoscript-plugin/dist/core/types.d.ts
export interface SaveMulmoScriptArgs {
script?: unknown;
filename?: string | undefined;
filePath?: string | undefined;
autoGenerateMovie?: boolean | undefined;
/** With `filePath`: replace just this beat instead of re-sending the whole script. */
beatIndex?: number | undefined;
/** The replacement beat. Only meaningful with `filePath` + `beatIndex`. */
beat?: unknown;
}
// plugin-CAhxJ5WM.js executeMulmoScriptSave
if (beatIndex !== void 0 || beat !== void 0) {
if (beatIndex === void 0 || beat === void 0) return badRequest("`beatIndex` and `beat` go together — ...");
const updated = await executeUpdateBeat(context, { filePath, beatIndex, beat });
if (!updated.ok) return updated;
}
return loadExistingScript(context, filePath);
両方が undefined のまま渡るので分岐に入らず、loadExistingScript に素通りする。
これが「成功したように見える」正体。
ツールの description は、この機能を明示的に案内している:
- Edit one beat — pass
filePath PLUS beatIndex and beat. Replaces that one beat and
presents the result. Use this whenever the user asks to change part of an existing
presentation — re-sending the whole script instead wastes tokens and overwrites anything
edited in the canvas meanwhile.
2つめの欠陥: pubsub が飛ばない
仮に上を直して書き込みが通っても、script-changed が publish されない。
publish しているのは kind ルータの updateKind だけ:
// @mulmoclaude/mulmoscript-plugin/dist/server.js
async function updateKind(kind, args) {
const outcome = kind === "updateBeat" ? await executeUpdateBeat(...) : await executeUpdateScript(...);
if (!outcome.ok) return fromPackageFailure(outcome);
ops.publishScriptChanged(str(args.filePath) ?? "", str(args.origin)); // ← ここだけ
return { ok: true };
}
async function saveKind(args) {
const outcome = await executeMulmoScriptSave(executeContext, {
script: args.script, filename: str(args.filename), filePath: str(args.filePath)
}); // ← beatIndex / beat をここでも落としている
... // ← publishScriptChanged を呼ばない
}
saveKind にも同じ取りこぼしがあり、かつ publish もしない。
ホスト側の handleToolCall は executeMulmoScriptSave を直接呼ぶので、こちらも publish されない。
新規保存(script を渡す)で画面が変わって見えるのは、レスポンスの
Display the storyboard to the user. を受けてキャンバスがその新しいファイルを開くためで、
pubsub による更新ではない。既存ファイルを開いたまま中身だけ差し替えるビート差し替えでは、
pubsub が唯一の更新経路なので、何も起きない。
再現
presentMulmoScript に script を渡して新規保存(キャンバスに表示される)
- 同じ
filePath に beatIndex + beat を渡す
Loaded MulmoScript from stories/<file>.json が返る
- ファイルの中身は変わっていない / キャンバスも変わらない
実際に踏んだケース: 13枚のデッキの3枚目(タイムライン)を差し替えようとして、
成功レスポンスが返ったがキャンバスは変わらず。フルスクリプトを再送して回避した。
直し方(案)
server/backends/mulmoscript.ts の handleToolCall の許可リストに beatIndex / beat を追加
(typeof body.beatIndex === "number" / body.beat !== undefined)
- 書き込みが起きた場合に
publishScriptChanged を呼ぶ。
executeMulmoScriptSave はビート差し替えをしたかどうかを呼び出し側に返していないので、
ホスト側で beatIndex !== undefined を条件にするか、パッケージ側の戻り値に
「書き込んだ」フラグを持たせる
- パッケージ側
saveKind の取りこぼしも同様(別リポジトリなら upstream issue に分ける)
テスト
server/backends/mulmoscript.spec.ts に、ツール呼び出し経路で
beatIndex + beat を渡すとディスクの中身が変わり、script-changed が publish される、
というケースが無い。許可リストの取りこぼしはこの形でしか捕まらないので、追加が要る。
環境
- mulmoterminal
c8901e90
@mulmoclaude/mulmoscript-plugin(node_modules の dist を確認)
- MCPサーバー
mulmoterminal-media 経由の presentMulmoScript
症状
MCPツール
presentMulmoScriptの 「ビート差し替え」(filePath+beatIndex+beat)が、何も書き換えずに成功として返る。キャンバスも当然更新されない。
エラーは出ない。レスポンスは通常の再表示と同じ
Loaded MulmoScript from stories/<file>.jsonで、呼んだ側からは成功と区別がつかない。
原因
ホスト側のツール呼び出しハンドラが、引数の許可リストから
beatIndex/beatを落としている。server/backends/mulmoscript.tshandleToolCall:beatIndexとbeatがここに無いため、executeMulmoScriptSaveに届かない。パッケージ側は実装済みで、型にも入っている:
両方が undefined のまま渡るので分岐に入らず、
loadExistingScriptに素通りする。これが「成功したように見える」正体。
ツールの description は、この機能を明示的に案内している:
2つめの欠陥: pubsub が飛ばない
仮に上を直して書き込みが通っても、
script-changedが publish されない。publish しているのは kind ルータの
updateKindだけ:saveKindにも同じ取りこぼしがあり、かつ publish もしない。ホスト側の
handleToolCallはexecuteMulmoScriptSaveを直接呼ぶので、こちらも publish されない。新規保存(
scriptを渡す)で画面が変わって見えるのは、レスポンスのDisplay the storyboard to the user.を受けてキャンバスがその新しいファイルを開くためで、pubsub による更新ではない。既存ファイルを開いたまま中身だけ差し替えるビート差し替えでは、
pubsub が唯一の更新経路なので、何も起きない。
再現
presentMulmoScriptにscriptを渡して新規保存(キャンバスに表示される)filePathにbeatIndex+beatを渡すLoaded MulmoScript from stories/<file>.jsonが返る実際に踏んだケース: 13枚のデッキの3枚目(タイムライン)を差し替えようとして、
成功レスポンスが返ったがキャンバスは変わらず。フルスクリプトを再送して回避した。
直し方(案)
server/backends/mulmoscript.tsのhandleToolCallの許可リストにbeatIndex/beatを追加(
typeof body.beatIndex === "number"/body.beat !== undefined)publishScriptChangedを呼ぶ。executeMulmoScriptSaveはビート差し替えをしたかどうかを呼び出し側に返していないので、ホスト側で
beatIndex !== undefinedを条件にするか、パッケージ側の戻り値に「書き込んだ」フラグを持たせる
saveKindの取りこぼしも同様(別リポジトリなら upstream issue に分ける)テスト
server/backends/mulmoscript.spec.tsに、ツール呼び出し経路でbeatIndex+beatを渡すとディスクの中身が変わり、script-changedが publish される、というケースが無い。許可リストの取りこぼしはこの形でしか捕まらないので、追加が要る。
環境
c8901e90@mulmoclaude/mulmoscript-plugin(node_modules の dist を確認)mulmoterminal-media経由のpresentMulmoScript