friday-mcp-v2 3.0.4 → 3.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/mcp-server.js +102 -253
  2. package/package.json +1 -1
@@ -249,7 +249,7 @@ function checkRevisionMismatch(expectedRevision, currentState, mode, postId, ses
249
249
  : '';
250
250
  const text = `❌ REVISION_MISMATCH: expected ${expectedRevision}, actual ${currentRevision}` +
251
251
  snapshotLine +
252
- `\n→ get_article_structure で最新を取得してください`;
252
+ `\n→ Re-fetch via get_article_structure.`;
253
253
  return { content: [{ type: "text", text }], isError: true };
254
254
  }
255
255
 
@@ -729,24 +729,6 @@ const targetSchema = {
729
729
  type: "boolean",
730
730
  description: "Target user-selected block. Editor mode only.",
731
731
  },
732
- index: {
733
- type: "number",
734
- description: "Block index (0-based, flattened).",
735
- },
736
- indices: {
737
- type: "array",
738
- items: { type: "number" },
739
- description: "Multiple indices (0-based).",
740
- },
741
- range: {
742
- type: "object",
743
- properties: {
744
- start: { type: "number", description: "Start (inclusive)." },
745
- end: { type: "number", description: "End (inclusive)." },
746
- },
747
- required: ["start", "end"],
748
- description: "Index range.",
749
- },
750
732
  section: {
751
733
  type: "string",
752
734
  description: "Section by heading text (partial match).",
@@ -782,7 +764,7 @@ const targetSchema = {
782
764
  description: "Multiple block refs from snapshot. Requires snapshotId.",
783
765
  },
784
766
  },
785
- description: "Target: one of selected/index/indices/range/heading/ref/refs + optional filters.",
767
+ description: "Target: one of selected/ref/refs/heading/section/blockType + optional filters (contains/nth). Requires snapshotId when using ref/refs.",
786
768
  };
787
769
 
788
770
  const targetSchemaNoSelected = {
@@ -790,7 +772,7 @@ const targetSchemaNoSelected = {
790
772
  properties: { ...targetSchema.properties },
791
773
  };
792
774
  delete targetSchemaNoSelected.properties.selected;
793
- targetSchemaNoSelected.description = "Target: one of index/range/heading + optional filters.";
775
+ targetSchemaNoSelected.description = "Target: one of ref/refs/heading/section/blockType + optional filters (contains/nth). Requires snapshotId when using ref/refs.";
794
776
 
795
777
  const insertSchema = {
796
778
  type: "object",
@@ -815,9 +797,6 @@ function normalizeTarget(target) {
815
797
  // --- 排他バリデーション ---
816
798
  const primaries = [
817
799
  target.selected && 'selected',
818
- target.index !== undefined && 'index',
819
- target.indices && 'indices',
820
- target.range && 'range',
821
800
  target.heading && 'heading',
822
801
  target.ref && 'ref',
823
802
  target.refs && 'refs',
@@ -830,19 +809,6 @@ function normalizeTarget(target) {
830
809
  if (target.ref) result._ref = target.ref;
831
810
  if (target.refs) result._refs = target.refs;
832
811
  if (target.selected) result.target = "selected";
833
- if (target.index !== undefined) result.index = target.index;
834
- if (target.indices) result.indices = target.indices;
835
- if (target.range) {
836
- const s = target.range.start;
837
- const e = target.range.end;
838
- if (s === undefined || s === null || e === undefined || e === null ||
839
- typeof s !== "number" || typeof e !== "number" ||
840
- !Number.isInteger(s) || !Number.isInteger(e) || s < 0 || e < 0) {
841
- throw new Error("range.start と range.end は 0 以上の整数で両方指定してください。");
842
- }
843
- result.startIndex = s;
844
- result.endIndex = e;
845
- }
846
812
  if (target.section) result.section = target.section;
847
813
  if (target.blockType) result.blockType = target.blockType;
848
814
  if (target.nth !== undefined) result.typeIndex = target.nth;
@@ -931,15 +897,15 @@ function resolveRefFromState(snapshotId, ref, mode, sessionId, postId, currentSt
931
897
  // 1. snapshot 取得
932
898
  const snap = snapshotCache.get(snapshotId);
933
899
  if (!snap) {
934
- throw new Error(`snapshot "${snapshotId}" が見つかりません(期限切れまたは無効)。get_article_structure を再取得してください。`);
900
+ throw new Error(`snapshot "${snapshotId}" が見つかりません(期限切れまたは無効)。Re-fetch via get_article_structure.`);
935
901
  }
936
902
 
937
903
  // 2. 状態ソース束縛チェック
938
904
  if (snap.mode !== mode) {
939
- throw new Error(`snapshot は ${snap.mode} モードで取得されましたが、現在は ${mode} モードです。get_article_structure を再取得してください。`);
905
+ throw new Error(`snapshot は ${snap.mode} モードで取得されましたが、現在は ${mode} モードです。Re-fetch via get_article_structure.`);
940
906
  }
941
907
  if (mode === 'editor' && snap.sessionId && sessionId && snap.sessionId !== sessionId) {
942
- throw new Error(`snapshot は sessionId "${snap.sessionId}" で取得されましたが、現在の sessionId は "${sessionId}" です。get_article_structure を再取得してください。`);
908
+ throw new Error(`snapshot は sessionId "${snap.sessionId}" で取得されましたが、現在の sessionId は "${sessionId}" です。Re-fetch via get_article_structure.`);
943
909
  }
944
910
  if (snap.postId !== postId) {
945
911
  throw new Error(`snapshot は postId ${snap.postId} 用ですが、postId ${postId} に対して使用されています。`);
@@ -978,14 +944,14 @@ function resolveRefFromState(snapshotId, ref, mode, sessionId, postId, currentSt
978
944
  if (refined.length === 1) return refined[0].index;
979
945
  throw new Error(
980
946
  `ref "${ref}" (type: ${snapBlock.type}) の解決先が ${refined.length > 0 ? refined.length : candidates.length} 件見つかりました。` +
981
- `一意に特定できません。get_article_structure を再取得してください。`
947
+ `一意に特定できません。Re-fetch via get_article_structure.`
982
948
  );
983
949
  }
984
950
 
985
951
  // 5c. 一致 0件 → エラー
986
952
  throw new Error(
987
953
  `ref "${ref}" (${snapBlock.type}, fingerprint: ${snapBlock.fingerprint}) に一致するブロックが見つかりません。` +
988
- `構造が大幅に変更された可能性があります。get_article_structure を再取得してください。`
954
+ `構造が大幅に変更された可能性があります。Re-fetch via get_article_structure.`
989
955
  );
990
956
  }
991
957
 
@@ -1033,7 +999,7 @@ async function resolveRefsAndCheckRevision({
1033
999
  if (!snapshotId) {
1034
1000
  if (_debug) console.error(`[SNAPSHOT] resolveRefs: ref指定あり but snapshotId missing → error`);
1035
1001
  return { error: {
1036
- content: [{ type: "text", text: "❌ ref/refs を使用するには snapshotId が必要です。get_article_structure で取得してください。" }],
1002
+ content: [{ type: "text", text: "❌ snapshotId required for ref/refs. Get it from get_article_structure or any prior write response." }],
1037
1003
  isError: true,
1038
1004
  }};
1039
1005
  }
@@ -1365,13 +1331,13 @@ function appendRefChangesToText(text, changeInfo) {
1365
1331
  .join(', ');
1366
1332
  break;
1367
1333
  case 'updated':
1368
- out += `\nupdated: [${changeInfo.updatedRefs.join(', ')}] (use new snapshot)`;
1334
+ out += `\nupdated: [${changeInfo.updatedRefs.join(', ')}] (reuse this snapshotId for next operation)`;
1369
1335
  break;
1370
1336
  case 'expanded':
1371
1337
  out += `\nexpanded: ${changeInfo.expanded.oldRef} \u2192 [${changeInfo.expanded.newRefs.join(', ')}]`;
1372
1338
  break;
1373
1339
  case 'duplicated':
1374
- out += `\nduplicated: ${changeInfo.sourceRef} \u2192 ${changeInfo.newRef} (source:[${changeInfo.sourceIndex}], new:[${changeInfo.newIndex}]) (use new snapshot)`;
1340
+ out += `\nduplicated: ${changeInfo.sourceRef} \u2192 ${changeInfo.newRef} (source:[${changeInfo.sourceIndex}], new:[${changeInfo.newIndex}]) (reuse this snapshotId for next operation)`;
1375
1341
  break;
1376
1342
  }
1377
1343
 
@@ -2089,40 +2055,33 @@ const tools = [
2089
2055
  },
2090
2056
  {
2091
2057
  name: "delete_block",
2092
- description: "Delete block(s) by index, ref, or selection.",
2058
+ description: "Delete block(s) by ref or selection.",
2093
2059
  inputSchema: {
2094
2060
  type: "object",
2095
2061
  properties: {
2096
2062
  postId: postIdParam,
2097
2063
  site: siteParam,
2098
- index: { type: "number", description: "Index (0-based). Omit for selected." },
2099
- count: { type: "number", description: "Consecutive count (default: 1)" },
2100
- indices: { type: "array", items: { type: "number" }, description: "Multiple indices (exclusive with index/count)" },
2101
- snapshotId: { type: "string", description: "Snapshot ID from get_article_structure. Required when using ref/refs." },
2102
- ref: { type: "string", description: "Block ref from snapshot (exclusive with index/indices)." },
2103
- refs: { type: "array", items: { type: "string" }, description: "Multiple block refs (exclusive with index/indices)." },
2064
+ snapshotId: { type: "string", description: "Snapshot ID (from get_article_structure or any write response). Required." },
2065
+ ref: { type: "string", description: "Block ref from snapshot." },
2066
+ refs: { type: "array", items: { type: "string" }, description: "Multiple block refs from snapshot." },
2104
2067
  expectedRevision: { type: "string", description: "Revision from snapshot. Rejects if structure changed." },
2105
2068
  },
2106
2069
  },
2107
2070
  },
2108
2071
  {
2109
2072
  name: "move_block",
2110
- description: "Move block(s). Use from/to (top-level), fromFlat/toFlat (nested), or fromRef+beforeRef/afterRef (ref-based).",
2073
+ description: "Move block(s). Use fromRef + beforeRef or afterRef. Requires snapshotId.",
2111
2074
  inputSchema: {
2112
2075
  type: "object",
2113
2076
  properties: {
2114
2077
  postId: postIdParam,
2115
2078
  site: siteParam,
2116
- from: { type: "number", description: "Source top-level index" },
2117
- to: { type: "number", description: "Target position" },
2118
- fromFlat: { type: "number", description: "Source flattened index" },
2119
- toFlat: { type: "number", description: "Target flattened position" },
2120
2079
  count: { type: "integer", description: "Consecutive count (default: 1)" },
2121
- snapshotId: { type: "string", description: "Snapshot ID from get_article_structure. Required when using ref." },
2080
+ snapshotId: { type: "string", description: "Snapshot ID (from get_article_structure or any write response). Required." },
2122
2081
  expectedRevision: { type: "string", description: "Revision from snapshot. Rejects if structure changed." },
2123
- fromRef: { type: "string", description: "Source block ref (exclusive with from/fromFlat)." },
2124
- beforeRef: { type: "string", description: "Move before this ref (exclusive with to/toFlat/afterRef)." },
2125
- afterRef: { type: "string", description: "Move after this ref (exclusive with to/toFlat/beforeRef)." },
2082
+ fromRef: { type: "string", description: "Source block ref." },
2083
+ beforeRef: { type: "string", description: "Move before this ref (exclusive with afterRef)." },
2084
+ afterRef: { type: "string", description: "Move after this ref (exclusive with beforeRef)." },
2126
2085
  },
2127
2086
  },
2128
2087
  },
@@ -2152,15 +2111,14 @@ const tools = [
2152
2111
  },
2153
2112
  {
2154
2113
  name: "duplicate_block",
2155
- description: "Duplicate block by index, ref, or selection.",
2114
+ description: "Duplicate block by ref or selection.",
2156
2115
  inputSchema: {
2157
2116
  type: "object",
2158
2117
  properties: {
2159
2118
  postId: postIdParam,
2160
2119
  site: siteParam,
2161
- index: { type: "number", description: "Index (0-based). Omit for selected." },
2162
- snapshotId: { type: "string", description: "Snapshot ID from get_article_structure. Required when using ref." },
2163
- ref: { type: "string", description: "Block ref from snapshot (exclusive with index)." },
2120
+ snapshotId: { type: "string", description: "Snapshot ID (from get_article_structure or any write response). Required." },
2121
+ ref: { type: "string", description: "Block ref from snapshot." },
2164
2122
  expectedRevision: { type: "string", description: "Revision from snapshot. Rejects if structure changed." },
2165
2123
  },
2166
2124
  },
@@ -2244,7 +2202,7 @@ const tools = [
2244
2202
  },
2245
2203
  {
2246
2204
  name: "insert_block",
2247
- description: "Insert Gutenberg HTML. Omit index to append. Use beforeRef/afterRef for ref-based positioning.",
2205
+ description: "Insert Gutenberg HTML. Use beforeRef/afterRef for positioning. Omit both to append to end.",
2248
2206
  inputSchema: {
2249
2207
  type: "object",
2250
2208
  properties: {
@@ -2252,12 +2210,10 @@ const tools = [
2252
2210
  site: siteParam,
2253
2211
  rawHTML: { type: "string", description: "HTML to insert (exclusive with filePath)" },
2254
2212
  filePath: { type: "string", description: "Local file path (exclusive with rawHTML)" },
2255
- index: { type: "number", description: "Position (0-based). Omit to append." },
2256
- position: { type: "string", enum: ["before", "after"], description: "Insert relative to index: 'before' (default) or 'after'. Requires index." },
2257
- snapshotId: { type: "string", description: "Snapshot ID. Required when using beforeRef/afterRef." },
2213
+ snapshotId: { type: "string", description: "Snapshot ID (from get_article_structure or any write response). Required when using beforeRef/afterRef. Omit to append to end." },
2258
2214
  expectedRevision: { type: "string", description: "Revision from snapshot. Rejects if structure changed." },
2259
- beforeRef: { type: "string", description: "Insert before this ref (exclusive with index)." },
2260
- afterRef: { type: "string", description: "Insert after this ref (exclusive with index)." },
2215
+ beforeRef: { type: "string", description: "Insert before this ref." },
2216
+ afterRef: { type: "string", description: "Insert after this ref." },
2261
2217
  },
2262
2218
  },
2263
2219
  },
@@ -2295,7 +2251,7 @@ const tools = [
2295
2251
  site: siteParam,
2296
2252
  snapshotId: {
2297
2253
  type: "string",
2298
- description: "Snapshot ID from get_article_structure. Required when using ref/refs in target.",
2254
+ description: "Snapshot ID (from get_article_structure or any write response). Required when using ref/refs in target.",
2299
2255
  },
2300
2256
  expectedRevision: {
2301
2257
  type: "string",
@@ -2380,16 +2336,12 @@ const tools = [
2380
2336
  },
2381
2337
  {
2382
2338
  name: "table_operations",
2383
- description: "Table operations (get/update/add/delete rows/columns/cells). Use ref+snapshotId for safe index resolution.",
2339
+ description: "Table operations (get/update/add/delete rows/columns/cells). Requires ref+snapshotId to identify the table block.",
2384
2340
  inputSchema: {
2385
2341
  type: "object",
2386
2342
  properties: {
2387
2343
  postId: postIdParam,
2388
2344
  site: siteParam,
2389
- index: {
2390
- type: "number",
2391
- description: "Table index (0-based)",
2392
- },
2393
2345
  action: {
2394
2346
  type: "string",
2395
2347
  enum: ["get_structure", "update_cell", "add_row", "delete_row", "add_column", "delete_column", "move_row", "move_column", "update_row", "update_column"],
@@ -2416,8 +2368,8 @@ const tools = [
2416
2368
  items: { type: "string" },
2417
2369
  description: "add_row: new cells, add_column: init values, update_row/column: replacements. Omit for empty.",
2418
2370
  },
2419
- snapshotId: { type: "string", description: "Snapshot ID from get_article_structure. Required when using ref." },
2420
- ref: { type: "string", description: "Block ref from snapshot (exclusive with index)." },
2371
+ snapshotId: { type: "string", description: "Snapshot ID (from get_article_structure or any write response). Required." },
2372
+ ref: { type: "string", description: "Table block ref from snapshot." },
2421
2373
  expectedRevision: { type: "string", description: "Revision from snapshot. Rejects if structure changed." },
2422
2374
  },
2423
2375
  required: ["action"],
@@ -2533,7 +2485,7 @@ async function handleUpdateBlocksTool(args, toolName) {
2533
2485
  }
2534
2486
  if (!snapshotId) {
2535
2487
  return {
2536
- content: [{ type: "text", text: `❌ operations には snapshotId が必要です。get_article_structure で取得してください。` }],
2488
+ content: [{ type: "text", text: `❌ snapshotId required for operations. Get it from get_article_structure or any prior write response.` }],
2537
2489
  isError: true,
2538
2490
  };
2539
2491
  }
@@ -3418,9 +3370,9 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
3418
3370
  if (_guard) return _guard;
3419
3371
  }
3420
3372
 
3421
- // ref/refs index/indices 排他チェック
3422
- if ((args?.ref || args?.refs) && (args?.index !== undefined || args?.indices !== undefined)) {
3423
- return { content: [{ type: "text", text: "❌ ref/refs index/indices は同時に指定できません。" }], isError: true };
3373
+ // ref または refs が必須(選択ブロック削除はeditorコマンド経由で処理)
3374
+ if (!args?.ref && !args?.refs) {
3375
+ return { content: [{ type: "text", text: "❌ ref or refs required, along with snapshotId." }], isError: true };
3424
3376
  }
3425
3377
 
3426
3378
  // ref 解決 + revision チェック
@@ -3432,20 +3384,15 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
3432
3384
  if (resolved.error) return resolved.error;
3433
3385
  const _preState = resolved.currentState || null; // Phase 2: 差分計算用
3434
3386
 
3435
- let { index, count, indices } = args;
3436
- if (resolved.index !== undefined) { index = resolved.index; count = count || 1; }
3437
- if (resolved.indices !== undefined) { indices = resolved.indices; }
3387
+ let index = resolved.index;
3388
+ let indices = resolved.indices;
3389
+ const count = (index !== undefined) ? 1 : undefined;
3438
3390
 
3439
- // Phase 3: 入力 ref を保持(ref + count > 1 は差分化できないので Legacy)
3391
+ // Phase 3: 入力 ref を保持
3440
3392
  const _inputRefs = args?.refs ? [...new Set(args.refs)]
3441
- : (args?.ref && (count || 1) <= 1) ? [args.ref]
3393
+ : args?.ref ? [args.ref]
3442
3394
  : null;
3443
3395
 
3444
- // indices と index/count の排他バリデーション
3445
- if (indices !== undefined && (index !== undefined || count !== undefined)) {
3446
- return { content: [{ type: "text", text: "❌ indices と index/count は同時に指定できません。" }], isError: true };
3447
- }
3448
-
3449
3396
  // indices モード(非連続一括削除)
3450
3397
  if (indices !== undefined) {
3451
3398
  if (!Array.isArray(indices) || indices.length === 0) {
@@ -3490,10 +3437,10 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
3490
3437
  return { content: [{ type: "text", text: appendSnapshotToTextLegacy(_msg, _snap) }] };
3491
3438
  }
3492
3439
 
3493
- // 既存モード(index + count)
3440
+ // 単一 ref モード(ref → index 解決済み)
3494
3441
  if (mode === 'headless') {
3495
3442
  if (index === undefined) {
3496
- return { content: [{ type: "text", text: "❌ Headless モードでは index の指定が必須です。" }], isError: true };
3443
+ return { content: [{ type: "text", text: "❌ ref resolution failed. Check snapshotId and ref." }], isError: true };
3497
3444
  }
3498
3445
  try {
3499
3446
  const result = await client.headlessDelete(postId, index, count || 1);
@@ -3544,8 +3491,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
3544
3491
  if (_guard) return _guard;
3545
3492
  }
3546
3493
 
3547
- const { from, to } = args;
3548
- let { fromFlat, toFlat } = args;
3494
+ let fromFlat, toFlat;
3549
3495
  const count = args.count ?? 1;
3550
3496
 
3551
3497
  // count バリデーション
@@ -3553,70 +3499,41 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
3553
3499
  return { content: [{ type: "text", text: "❌ count は1以上の整数を指定してください。" }], isError: true };
3554
3500
  }
3555
3501
 
3556
- // 3 モード排他バリデーション
3557
- const hasTopLevel = from !== undefined || to !== undefined;
3558
- const hasFlat = fromFlat !== undefined || toFlat !== undefined;
3559
- const hasRef = args?.fromRef !== undefined || args?.beforeRef !== undefined || args?.afterRef !== undefined;
3560
- const _modeCount = [hasTopLevel, hasFlat, hasRef].filter(Boolean).length;
3561
-
3562
- if (_modeCount > 1) {
3563
- return { content: [{ type: "text", text: "❌ from/to, fromFlat/toFlat, ref(fromRef/beforeRef/afterRef) は同時に指定できません。" }], isError: true };
3502
+ // fromRef + beforeRef/afterRef 必須チェック
3503
+ if (!args.fromRef) {
3504
+ return { content: [{ type: "text", text: "❌ fromRef is required." }], isError: true };
3564
3505
  }
3565
- if (_modeCount === 0) {
3566
- return { content: [{ type: "text", text: "❌ from/to, fromFlat/toFlat, または fromRef+beforeRef/afterRef を指定してください。" }], isError: true };
3506
+ if (!args.beforeRef && !args.afterRef) {
3507
+ return { content: [{ type: "text", text: "❌ beforeRef or afterRef is required." }], isError: true };
3567
3508
  }
3568
-
3569
- // expectedRevision チェック(全モード共通)
3570
- // ref モード以外でも expectedRevision が指定されていれば revision チェックを実施
3571
- let _preState = null; // Phase 2: 差分計算用
3572
- if (args?.expectedRevision && !hasRef) {
3573
- const { currentState, error: _revError } = await acquireFreshState({
3574
- expectedRevision: args.expectedRevision,
3575
- mode, client, postId, sessionId: _sessionId, siteName: _siteName,
3576
- });
3577
- if (_revError) return _revError;
3578
- _preState = currentState || null;
3509
+ if (args.beforeRef && args.afterRef) {
3510
+ return { content: [{ type: "text", text: "❌ beforeRef と afterRef は同時に指定できません。" }], isError: true };
3511
+ }
3512
+ if (!args.snapshotId) {
3513
+ return { content: [{ type: "text", text: "❌ snapshotId required. Get it from get_article_structure or any prior write response." }], isError: true };
3579
3514
  }
3580
3515
 
3581
- // ref モード: fromRef + beforeRef/afterRef
3582
- if (hasRef) {
3583
- if (!args.fromRef) {
3584
- return { content: [{ type: "text", text: "❌ ref モードでは fromRef が必須です。" }], isError: true };
3585
- }
3586
- if (!args.beforeRef && !args.afterRef) {
3587
- return { content: [{ type: "text", text: "❌ ref モードでは beforeRef または afterRef が必須です。" }], isError: true };
3588
- }
3589
- if (args.beforeRef && args.afterRef) {
3590
- return { content: [{ type: "text", text: "❌ beforeRef と afterRef は同時に指定できません。" }], isError: true };
3591
- }
3592
- if (!args.snapshotId) {
3593
- return { content: [{ type: "text", text: "❌ ref を使用するには snapshotId が必要です。get_article_structure で取得してください。" }], isError: true };
3594
- }
3595
-
3596
- // acquireFreshState で 1 回だけ state 取得 + revision チェック
3597
- const { currentState, error: _stateError } = await acquireFreshState({
3598
- expectedRevision: args.expectedRevision,
3599
- mode, client, postId, sessionId: _sessionId, siteName: _siteName,
3600
- });
3601
- if (_stateError) return _stateError;
3602
- _preState = currentState || null; // Phase 2: 差分計算用
3603
-
3604
- // ref 解決(同じ currentState で 2 つの ref を解決)
3605
- try {
3606
- fromFlat = resolveRefFromState(args.snapshotId, args.fromRef, mode, _sessionId, postId, currentState);
3607
- const destRef = args.beforeRef || args.afterRef;
3608
- const resolvedDest = resolveRefFromState(args.snapshotId, destRef, mode, _sessionId, postId, currentState);
3609
- // 位置計算: beforeRef → そのまま, afterRef → +1
3610
- toFlat = args.beforeRef ? resolvedDest : resolvedDest + 1;
3611
- } catch (e) {
3612
- return { content: [{ type: "text", text: `❌ ref 解決エラー: ${e.message}` }], isError: true };
3613
- }
3516
+ // acquireFreshState state 取得 + revision チェック
3517
+ let _preState = null;
3518
+ const { currentState, error: _stateError } = await acquireFreshState({
3519
+ expectedRevision: args.expectedRevision,
3520
+ mode, client, postId, sessionId: _sessionId, siteName: _siteName,
3521
+ });
3522
+ if (_stateError) return _stateError;
3523
+ _preState = currentState || null;
3614
3524
 
3615
- // ref flat に変換完了。以降 flat モードに合流
3525
+ // ref 解決(同じ currentState 2 つの ref を解決)
3526
+ try {
3527
+ fromFlat = resolveRefFromState(args.snapshotId, args.fromRef, mode, _sessionId, postId, currentState);
3528
+ const destRef = args.beforeRef || args.afterRef;
3529
+ const resolvedDest = resolveRefFromState(args.snapshotId, destRef, mode, _sessionId, postId, currentState);
3530
+ toFlat = args.beforeRef ? resolvedDest : resolvedDest + 1;
3531
+ } catch (e) {
3532
+ return { content: [{ type: "text", text: `❌ ref 解決エラー: ${e.message}` }], isError: true };
3616
3533
  }
3617
3534
 
3618
- // Phase 4: 入力 ref 保持(ref モード + count <= 1 のみ差分化)
3619
- const _inputRefs = (hasRef && count <= 1) ? [args.fromRef] : null;
3535
+ // 入力 ref 保持(count <= 1 のみ差分化)
3536
+ const _inputRefs = (count <= 1) ? [args.fromRef] : null;
3620
3537
 
3621
3538
  // 移動結果のレスポンス生成ヘルパー
3622
3539
  const moveMsg = (moved) => {
@@ -3628,56 +3545,21 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
3628
3545
  : `✅ ブロック移動 (${typeLabel})${_mt_mb}`;
3629
3546
  };
3630
3547
 
3631
- // fromFlat/toFlat モード(フラットインデックス移動)— ref モードもここに合流
3632
- if (hasFlat || hasRef) {
3633
- if (fromFlat === undefined || toFlat === undefined) {
3634
- return { content: [{ type: "text", text: "❌ fromFlat と toFlat の両方を指定してください。" }], isError: true };
3635
- }
3636
- if (fromFlat === toFlat || toFlat === fromFlat + count) {
3637
- return { content: [{ type: "text", text: `✅ 移動不要(同じ位置)${_mt_mb}` }] };
3638
- }
3639
-
3640
- if (mode === 'headless') {
3641
- try {
3642
- const result = await client.headlessMoveFlat(postId, fromFlat, toFlat, count);
3643
- const _snap = await buildResponseSnapshot(mode, client, postId, _sessionId, _siteName, result);
3644
- const _msg = moveMsg(result.moved);
3645
- if (_inputRefs) {
3646
- const changeInfo = buildChangeInfoFromResult('moved', _snap, result, _preState, { inputRefs: _inputRefs });
3647
- if (changeInfo) return { content: [{ type: "text", text: appendRefChangesToText(_msg, changeInfo) }] };
3648
- }
3649
- return { content: [{ type: "text", text: appendSnapshotToTextLegacy(_msg, _snap) }] };
3650
- } catch (e) {
3651
- const formatted = formatHeadlessConflictError(e);
3652
- if (formatted) return formatted;
3653
- throw e;
3654
- }
3655
- }
3656
-
3657
- const result = await client.sendEditorCommand("move_block", { fromFlat, toFlat, count }, 90000, _postId, _sessionId);
3658
- if (!result || result.timeout)
3659
- return timeoutResponse(name, client, args?.site);
3660
- if (!result.success)
3661
- return errorResponse(name, result.error, args?.site);
3662
- const _snap = await buildResponseSnapshot(mode, client, postId, _sessionId, _siteName, result);
3663
- const _msg = moveMsg(result.moved);
3664
- if (_inputRefs) {
3665
- const changeInfo = buildChangeInfoFromResult('moved', _snap, result, _preState, { inputRefs: _inputRefs });
3666
- if (changeInfo) return { content: [{ type: "text", text: appendRefChangesToText(_msg, changeInfo) }] };
3667
- }
3668
- return { content: [{ type: "text", text: appendSnapshotToTextLegacy(_msg, _snap) }] };
3669
- }
3670
-
3671
- // 既存モード(from/to トップレベル)
3672
- if (from === undefined || to === undefined) {
3673
- return { content: [{ type: "text", text: "❌ from と to の両方を指定してください" }], isError: true };
3548
+ // 同位置チェック
3549
+ if (fromFlat === toFlat || toFlat === fromFlat + count) {
3550
+ return { content: [{ type: "text", text: `✅ 移動不要(同じ位置)${_mt_mb}` }] };
3674
3551
  }
3675
3552
 
3676
3553
  if (mode === 'headless') {
3677
3554
  try {
3678
- const result = await client.headlessMove(postId, from, to, count);
3555
+ const result = await client.headlessMoveFlat(postId, fromFlat, toFlat, count);
3679
3556
  const _snap = await buildResponseSnapshot(mode, client, postId, _sessionId, _siteName, result);
3680
- return { content: [{ type: "text", text: appendSnapshotToTextLegacy(moveMsg(result.moved), _snap) }] };
3557
+ const _msg = moveMsg(result.moved);
3558
+ if (_inputRefs) {
3559
+ const changeInfo = buildChangeInfoFromResult('moved', _snap, result, _preState, { inputRefs: _inputRefs });
3560
+ if (changeInfo) return { content: [{ type: "text", text: appendRefChangesToText(_msg, changeInfo) }] };
3561
+ }
3562
+ return { content: [{ type: "text", text: appendSnapshotToTextLegacy(_msg, _snap) }] };
3681
3563
  } catch (e) {
3682
3564
  const formatted = formatHeadlessConflictError(e);
3683
3565
  if (formatted) return formatted;
@@ -3685,13 +3567,18 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
3685
3567
  }
3686
3568
  }
3687
3569
 
3688
- const result = await client.sendEditorCommand("move_block", { from, to, count }, 90000, _postId, _sessionId);
3570
+ const result = await client.sendEditorCommand("move_block", { fromFlat, toFlat, count }, 90000, _postId, _sessionId);
3689
3571
  if (!result || result.timeout)
3690
3572
  return timeoutResponse(name, client, args?.site);
3691
3573
  if (!result.success)
3692
3574
  return errorResponse(name, result.error, args?.site);
3693
3575
  const _snap = await buildResponseSnapshot(mode, client, postId, _sessionId, _siteName, result);
3694
- return { content: [{ type: "text", text: appendSnapshotToTextLegacy(moveMsg(result.moved), _snap) }] };
3576
+ const _msg = moveMsg(result.moved);
3577
+ if (_inputRefs) {
3578
+ const changeInfo = buildChangeInfoFromResult('moved', _snap, result, _preState, { inputRefs: _inputRefs });
3579
+ if (changeInfo) return { content: [{ type: "text", text: appendRefChangesToText(_msg, changeInfo) }] };
3580
+ }
3581
+ return { content: [{ type: "text", text: appendSnapshotToTextLegacy(_msg, _snap) }] };
3695
3582
  }
3696
3583
 
3697
3584
  case "undo": {
@@ -3738,9 +3625,9 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
3738
3625
  const _mt_dup = process.env.FRIDAY_DEBUG === '1' ? `\n[DEBUG] mode=${mode}` : '';
3739
3626
  const _siteName = siteName || args?.site || 'default';
3740
3627
 
3741
- // ref と index 排他チェック
3742
- if (args?.ref && args?.index !== undefined) {
3743
- return { content: [{ type: "text", text: "❌ ref index は同時に指定できません。" }], isError: true };
3628
+ // ref 必須チェック
3629
+ if (!args?.ref) {
3630
+ return { content: [{ type: "text", text: "❌ ref and snapshotId are required." }], isError: true };
3744
3631
  }
3745
3632
 
3746
3633
  // ref 解決 + revision チェック
@@ -3750,18 +3637,16 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
3750
3637
  mode, client, postId, sessionId: _sessionId, siteName: _siteName,
3751
3638
  });
3752
3639
  if (resolved.error) return resolved.error;
3753
- const _preState = resolved.currentState || null; // Phase 2: 差分計算用
3640
+ const _preState = resolved.currentState || null;
3754
3641
 
3755
- const index = resolved.index !== undefined ? resolved.index : args?.index;
3756
-
3757
- // Phase 3: 入力 ref を保持(index 経路は差分化しない)
3758
- const _inputRef = args?.ref || null;
3642
+ const index = resolved.index;
3643
+ const _inputRef = args.ref;
3759
3644
 
3760
3645
  if (mode === 'headless') {
3761
3646
  const _guard = await guardHeadlessConflict(postId, client, name);
3762
3647
  if (_guard) return _guard;
3763
3648
  if (index === undefined) {
3764
- return { content: [{ type: "text", text: "❌ Headless モードでは index の指定が必須です。" }], isError: true };
3649
+ return { content: [{ type: "text", text: "❌ ref resolution failed. Check snapshotId and ref." }], isError: true };
3765
3650
  }
3766
3651
  try {
3767
3652
  const result = await client.headlessDuplicate(postId, index);
@@ -3995,38 +3880,18 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
3995
3880
  }
3996
3881
 
3997
3882
  case "insert_block": {
3998
- let { rawHTML, filePath, index, position } = (args || {});
3999
-
4000
- // parentIndex は廃止済み(v3.0.0 Phase 5)— position を使用
4001
- if (args?.parentIndex !== undefined) {
4002
- return { content: [{ type: "text", text: "❌ parentIndex は廃止されました。代わりに index + position ('before'/'after') を使用してください。" }], isError: true };
4003
- }
3883
+ let { rawHTML, filePath } = (args || {});
4004
3884
 
4005
3885
  // 排他チェック
4006
3886
  if (rawHTML && filePath) {
4007
3887
  return { content: [{ type: "text", text: "❌ rawHTML と filePath は同時に指定できません。どちらか一方を指定してください。" }], isError: true };
4008
3888
  }
4009
3889
 
4010
- // Phase 4b: beforeRef/afterRef と index/position の排他チェック
4011
3890
  const _hasRefPosition = args?.beforeRef !== undefined || args?.afterRef !== undefined;
4012
- const _hasIndexPosition = index !== undefined || position !== undefined;
4013
- if (_hasRefPosition && _hasIndexPosition) {
4014
- return { content: [{ type: "text", text: "❌ beforeRef/afterRef と index/position は同時に指定できません。" }], isError: true };
4015
- }
4016
3891
  if (args?.beforeRef && args?.afterRef) {
4017
3892
  return { content: [{ type: "text", text: "❌ beforeRef と afterRef は同時に指定できません。" }], isError: true };
4018
3893
  }
4019
3894
 
4020
- // position 指定時は index 必須(ref モードでないとき)
4021
- if (!_hasRefPosition && position !== undefined && index === undefined) {
4022
- return { content: [{ type: "text", text: "❌ position を指定する場合は index も指定してください。末尾追加は index を省略してください。" }], isError: true };
4023
- }
4024
-
4025
- // position バリデーション
4026
- if (position !== undefined && position !== "before" && position !== "after") {
4027
- return { content: [{ type: "text", text: `❌ position は 'before' または 'after' を指定してください: ${position}` }], isError: true };
4028
- }
4029
-
4030
3895
  // filePath → rawHTML 解決
4031
3896
  if (filePath) {
4032
3897
  try { rawHTML = readHTMLFromFile(filePath).html; }
@@ -4036,13 +3901,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
4036
3901
  return { content: [{ type: "text", text: "❌ rawHTML または filePath を指定してください。Gutenberg HTML 形式のブロックマークアップが必要です。" }], isError: true };
4037
3902
  }
4038
3903
 
4039
- // index バリデーション(現行 Editor 互換: 整数 >= 0)— ref モードでないとき
4040
- if (!_hasRefPosition && index !== undefined && (!Number.isInteger(index) || index < 0)) {
4041
- return { content: [{ type: "text", text: `❌ Invalid index: ${index}` }], isError: true };
4042
- }
4043
-
4044
3904
  // update_blocks コードパスに委譲
4045
- // expectedRevision は全モードで委譲(ref/index/append 問わず)
4046
3905
  const delegatedArgs = {
4047
3906
  postId: args.postId,
4048
3907
  site: args.site,
@@ -4058,11 +3917,6 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
4058
3917
  delegatedArgs.insert = { position: args.beforeRef ? 'before' : 'after' };
4059
3918
  delegatedArgs.snapshotId = args.snapshotId;
4060
3919
  delegatedArgs.appendToEnd = false;
4061
- } else if (index !== undefined) {
4062
- // 既存: index モード
4063
- delegatedArgs.target = { index };
4064
- delegatedArgs.insert = { position: position ?? 'before' };
4065
- delegatedArgs.appendToEnd = false;
4066
3920
  } else {
4067
3921
  // 末尾追加
4068
3922
  delegatedArgs.insert = { position: 'before' };
@@ -4234,9 +4088,9 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
4234
4088
  return { content: [{ type: "text", text: "❌ action は必須です" }], isError: true };
4235
4089
  }
4236
4090
 
4237
- // ref と index 排他チェック
4238
- if (args?.ref && args?.index !== undefined) {
4239
- return { content: [{ type: "text", text: "❌ ref index は同時に指定できません。" }], isError: true };
4091
+ // ref 必須チェック
4092
+ if (!args?.ref) {
4093
+ return { content: [{ type: "text", text: "❌ ref and snapshotId are required." }], isError: true };
4240
4094
  }
4241
4095
 
4242
4096
  // ref 解決 + revision チェック
@@ -4246,11 +4100,11 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
4246
4100
  mode, client, postId, sessionId: _sessionId, siteName: _siteName,
4247
4101
  });
4248
4102
  if (resolved.error) return resolved.error;
4249
- const _preState = resolved.currentState || null; // Phase 2: 差分計算用
4103
+ const _preState = resolved.currentState || null;
4250
4104
 
4251
- const index = resolved.index !== undefined ? resolved.index : args?.index;
4105
+ const index = resolved.index;
4252
4106
  if (index === undefined) {
4253
- return { content: [{ type: "text", text: "❌ table_operations では index または ref のいずれかが必要です。" }], isError: true };
4107
+ return { content: [{ type: "text", text: "❌ ref resolution failed. Check snapshotId and ref." }], isError: true };
4254
4108
  }
4255
4109
 
4256
4110
  const tableParams = {
@@ -4539,9 +4393,6 @@ function _detectTargetType(args) {
4539
4393
  if (t) {
4540
4394
  if (t.ref) return 'ref';
4541
4395
  if (t.refs) return 'refs';
4542
- if (t.index !== undefined) return 'index';
4543
- if (t.indices) return 'indices';
4544
- if (t.range) return 'range';
4545
4396
  if (t.section) return 'section';
4546
4397
  if (t.heading) return 'heading';
4547
4398
  if (t.blockType) return 'blockType';
@@ -4549,8 +4400,6 @@ function _detectTargetType(args) {
4549
4400
  }
4550
4401
  if (args.ref || args.beforeRef || args.afterRef || args.fromRef) return 'ref';
4551
4402
  if (args.refs) return 'refs';
4552
- if (args.index !== undefined || args.fromFlat !== undefined || args.from !== undefined) return 'index';
4553
- if (args.indices) return 'indices';
4554
4403
  return null;
4555
4404
  }
4556
4405
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "friday-mcp-v2",
3
- "version": "3.0.4",
3
+ "version": "3.0.5",
4
4
  "description": "WordPress MCP Server for Claude Code - REST API direct communication",
5
5
  "type": "module",
6
6
  "main": "dist/mcp-server.js",