friday-mcp-v2 3.0.4 → 3.0.6

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 +140 -254
  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} に対して使用されています。`);
@@ -976,16 +942,30 @@ function resolveRefFromState(snapshotId, ref, mode, sessionId, postId, currentSt
976
942
  (c.parentIndex ?? null) === snapBlock.parentIndex
977
943
  );
978
944
  if (refined.length === 1) return refined[0].index;
945
+
946
+ // 5b-2. ordinal(出現順序)で特定
947
+ if (refined.length > 1) {
948
+ const sameInSnapshot = snap.blocks.filter(b =>
949
+ b.fingerprint === snapBlock.fingerprint &&
950
+ (b.depth || 0) === snapBlock.depth &&
951
+ (b.parentIndex ?? null) === snapBlock.parentIndex
952
+ );
953
+ const ordinal = sameInSnapshot.findIndex(b => b.ref === ref);
954
+ if (ordinal >= 0 && ordinal < refined.length) {
955
+ return refined[ordinal].index;
956
+ }
957
+ }
958
+
979
959
  throw new Error(
980
960
  `ref "${ref}" (type: ${snapBlock.type}) の解決先が ${refined.length > 0 ? refined.length : candidates.length} 件見つかりました。` +
981
- `一意に特定できません。get_article_structure を再取得してください。`
961
+ `一意に特定できません。Re-fetch via get_article_structure.`
982
962
  );
983
963
  }
984
964
 
985
965
  // 5c. 一致 0件 → エラー
986
966
  throw new Error(
987
967
  `ref "${ref}" (${snapBlock.type}, fingerprint: ${snapBlock.fingerprint}) に一致するブロックが見つかりません。` +
988
- `構造が大幅に変更された可能性があります。get_article_structure を再取得してください。`
968
+ `構造が大幅に変更された可能性があります。Re-fetch via get_article_structure.`
989
969
  );
990
970
  }
991
971
 
@@ -1033,7 +1013,7 @@ async function resolveRefsAndCheckRevision({
1033
1013
  if (!snapshotId) {
1034
1014
  if (_debug) console.error(`[SNAPSHOT] resolveRefs: ref指定あり but snapshotId missing → error`);
1035
1015
  return { error: {
1036
- content: [{ type: "text", text: "❌ ref/refs を使用するには snapshotId が必要です。get_article_structure で取得してください。" }],
1016
+ content: [{ type: "text", text: "❌ snapshotId required for ref/refs. Get it from get_article_structure or any prior write response." }],
1037
1017
  isError: true,
1038
1018
  }};
1039
1019
  }
@@ -1365,13 +1345,13 @@ function appendRefChangesToText(text, changeInfo) {
1365
1345
  .join(', ');
1366
1346
  break;
1367
1347
  case 'updated':
1368
- out += `\nupdated: [${changeInfo.updatedRefs.join(', ')}] (use new snapshot)`;
1348
+ out += `\nupdated: [${changeInfo.updatedRefs.join(', ')}] (reuse this snapshotId for next operation)`;
1369
1349
  break;
1370
1350
  case 'expanded':
1371
1351
  out += `\nexpanded: ${changeInfo.expanded.oldRef} \u2192 [${changeInfo.expanded.newRefs.join(', ')}]`;
1372
1352
  break;
1373
1353
  case 'duplicated':
1374
- out += `\nduplicated: ${changeInfo.sourceRef} \u2192 ${changeInfo.newRef} (source:[${changeInfo.sourceIndex}], new:[${changeInfo.newIndex}]) (use new snapshot)`;
1354
+ out += `\nduplicated: ${changeInfo.sourceRef} \u2192 ${changeInfo.newRef} (source:[${changeInfo.sourceIndex}], new:[${changeInfo.newIndex}]) (reuse this snapshotId for next operation)`;
1375
1355
  break;
1376
1356
  }
1377
1357
 
@@ -2089,40 +2069,33 @@ const tools = [
2089
2069
  },
2090
2070
  {
2091
2071
  name: "delete_block",
2092
- description: "Delete block(s) by index, ref, or selection.",
2072
+ description: "Delete block(s) by ref or selection.",
2093
2073
  inputSchema: {
2094
2074
  type: "object",
2095
2075
  properties: {
2096
2076
  postId: postIdParam,
2097
2077
  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)." },
2078
+ snapshotId: { type: "string", description: "Snapshot ID (from get_article_structure or any write response). Required." },
2079
+ ref: { type: "string", description: "Block ref from snapshot." },
2080
+ refs: { type: "array", items: { type: "string" }, description: "Multiple block refs from snapshot." },
2104
2081
  expectedRevision: { type: "string", description: "Revision from snapshot. Rejects if structure changed." },
2105
2082
  },
2106
2083
  },
2107
2084
  },
2108
2085
  {
2109
2086
  name: "move_block",
2110
- description: "Move block(s). Use from/to (top-level), fromFlat/toFlat (nested), or fromRef+beforeRef/afterRef (ref-based).",
2087
+ description: "Move block(s). Use fromRef + beforeRef or afterRef. Requires snapshotId.",
2111
2088
  inputSchema: {
2112
2089
  type: "object",
2113
2090
  properties: {
2114
2091
  postId: postIdParam,
2115
2092
  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
2093
  count: { type: "integer", description: "Consecutive count (default: 1)" },
2121
- snapshotId: { type: "string", description: "Snapshot ID from get_article_structure. Required when using ref." },
2094
+ snapshotId: { type: "string", description: "Snapshot ID (from get_article_structure or any write response). Required." },
2122
2095
  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)." },
2096
+ fromRef: { type: "string", description: "Source block ref." },
2097
+ beforeRef: { type: "string", description: "Move before this ref (exclusive with afterRef)." },
2098
+ afterRef: { type: "string", description: "Move after this ref (exclusive with beforeRef)." },
2126
2099
  },
2127
2100
  },
2128
2101
  },
@@ -2152,15 +2125,14 @@ const tools = [
2152
2125
  },
2153
2126
  {
2154
2127
  name: "duplicate_block",
2155
- description: "Duplicate block by index, ref, or selection.",
2128
+ description: "Duplicate block by ref or selection.",
2156
2129
  inputSchema: {
2157
2130
  type: "object",
2158
2131
  properties: {
2159
2132
  postId: postIdParam,
2160
2133
  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)." },
2134
+ snapshotId: { type: "string", description: "Snapshot ID (from get_article_structure or any write response). Required." },
2135
+ ref: { type: "string", description: "Block ref from snapshot." },
2164
2136
  expectedRevision: { type: "string", description: "Revision from snapshot. Rejects if structure changed." },
2165
2137
  },
2166
2138
  },
@@ -2244,7 +2216,7 @@ const tools = [
2244
2216
  },
2245
2217
  {
2246
2218
  name: "insert_block",
2247
- description: "Insert Gutenberg HTML. Omit index to append. Use beforeRef/afterRef for ref-based positioning.",
2219
+ description: "Insert Gutenberg HTML. Use beforeRef/afterRef for positioning. Omit both to append to end.",
2248
2220
  inputSchema: {
2249
2221
  type: "object",
2250
2222
  properties: {
@@ -2252,12 +2224,10 @@ const tools = [
2252
2224
  site: siteParam,
2253
2225
  rawHTML: { type: "string", description: "HTML to insert (exclusive with filePath)" },
2254
2226
  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." },
2227
+ 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
2228
  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)." },
2229
+ beforeRef: { type: "string", description: "Insert before this ref." },
2230
+ afterRef: { type: "string", description: "Insert after this ref." },
2261
2231
  },
2262
2232
  },
2263
2233
  },
@@ -2281,6 +2251,10 @@ const tools = [
2281
2251
  postId: postIdParam,
2282
2252
  site: siteParam,
2283
2253
  target: targetSchema,
2254
+ snapshotId: {
2255
+ type: "string",
2256
+ description: "Snapshot ID (from get_article_structure or any write response). Required when using ref/refs in target.",
2257
+ },
2284
2258
  },
2285
2259
  required: ["target"],
2286
2260
  },
@@ -2295,7 +2269,7 @@ const tools = [
2295
2269
  site: siteParam,
2296
2270
  snapshotId: {
2297
2271
  type: "string",
2298
- description: "Snapshot ID from get_article_structure. Required when using ref/refs in target.",
2272
+ description: "Snapshot ID (from get_article_structure or any write response). Required when using ref/refs in target.",
2299
2273
  },
2300
2274
  expectedRevision: {
2301
2275
  type: "string",
@@ -2380,16 +2354,12 @@ const tools = [
2380
2354
  },
2381
2355
  {
2382
2356
  name: "table_operations",
2383
- description: "Table operations (get/update/add/delete rows/columns/cells). Use ref+snapshotId for safe index resolution.",
2357
+ description: "Table operations (get/update/add/delete rows/columns/cells). Requires ref+snapshotId to identify the table block.",
2384
2358
  inputSchema: {
2385
2359
  type: "object",
2386
2360
  properties: {
2387
2361
  postId: postIdParam,
2388
2362
  site: siteParam,
2389
- index: {
2390
- type: "number",
2391
- description: "Table index (0-based)",
2392
- },
2393
2363
  action: {
2394
2364
  type: "string",
2395
2365
  enum: ["get_structure", "update_cell", "add_row", "delete_row", "add_column", "delete_column", "move_row", "move_column", "update_row", "update_column"],
@@ -2416,8 +2386,8 @@ const tools = [
2416
2386
  items: { type: "string" },
2417
2387
  description: "add_row: new cells, add_column: init values, update_row/column: replacements. Omit for empty.",
2418
2388
  },
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)." },
2389
+ snapshotId: { type: "string", description: "Snapshot ID (from get_article_structure or any write response). Required." },
2390
+ ref: { type: "string", description: "Table block ref from snapshot." },
2421
2391
  expectedRevision: { type: "string", description: "Revision from snapshot. Rejects if structure changed." },
2422
2392
  },
2423
2393
  required: ["action"],
@@ -2533,7 +2503,7 @@ async function handleUpdateBlocksTool(args, toolName) {
2533
2503
  }
2534
2504
  if (!snapshotId) {
2535
2505
  return {
2536
- content: [{ type: "text", text: `❌ operations には snapshotId が必要です。get_article_structure で取得してください。` }],
2506
+ content: [{ type: "text", text: `❌ snapshotId required for operations. Get it from get_article_structure or any prior write response.` }],
2537
2507
  isError: true,
2538
2508
  };
2539
2509
  }
@@ -3418,9 +3388,9 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
3418
3388
  if (_guard) return _guard;
3419
3389
  }
3420
3390
 
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 };
3391
+ // ref または refs が必須(選択ブロック削除はeditorコマンド経由で処理)
3392
+ if (!args?.ref && !args?.refs) {
3393
+ return { content: [{ type: "text", text: "❌ ref or refs required, along with snapshotId." }], isError: true };
3424
3394
  }
3425
3395
 
3426
3396
  // ref 解決 + revision チェック
@@ -3432,20 +3402,15 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
3432
3402
  if (resolved.error) return resolved.error;
3433
3403
  const _preState = resolved.currentState || null; // Phase 2: 差分計算用
3434
3404
 
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; }
3405
+ let index = resolved.index;
3406
+ let indices = resolved.indices;
3407
+ const count = (index !== undefined) ? 1 : undefined;
3438
3408
 
3439
- // Phase 3: 入力 ref を保持(ref + count > 1 は差分化できないので Legacy)
3409
+ // Phase 3: 入力 ref を保持
3440
3410
  const _inputRefs = args?.refs ? [...new Set(args.refs)]
3441
- : (args?.ref && (count || 1) <= 1) ? [args.ref]
3411
+ : args?.ref ? [args.ref]
3442
3412
  : null;
3443
3413
 
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
3414
  // indices モード(非連続一括削除)
3450
3415
  if (indices !== undefined) {
3451
3416
  if (!Array.isArray(indices) || indices.length === 0) {
@@ -3490,10 +3455,10 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
3490
3455
  return { content: [{ type: "text", text: appendSnapshotToTextLegacy(_msg, _snap) }] };
3491
3456
  }
3492
3457
 
3493
- // 既存モード(index + count)
3458
+ // 単一 ref モード(ref → index 解決済み)
3494
3459
  if (mode === 'headless') {
3495
3460
  if (index === undefined) {
3496
- return { content: [{ type: "text", text: "❌ Headless モードでは index の指定が必須です。" }], isError: true };
3461
+ return { content: [{ type: "text", text: "❌ ref resolution failed. Check snapshotId and ref." }], isError: true };
3497
3462
  }
3498
3463
  try {
3499
3464
  const result = await client.headlessDelete(postId, index, count || 1);
@@ -3544,8 +3509,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
3544
3509
  if (_guard) return _guard;
3545
3510
  }
3546
3511
 
3547
- const { from, to } = args;
3548
- let { fromFlat, toFlat } = args;
3512
+ let fromFlat, toFlat;
3549
3513
  const count = args.count ?? 1;
3550
3514
 
3551
3515
  // count バリデーション
@@ -3553,70 +3517,41 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
3553
3517
  return { content: [{ type: "text", text: "❌ count は1以上の整数を指定してください。" }], isError: true };
3554
3518
  }
3555
3519
 
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 };
3520
+ // fromRef + beforeRef/afterRef 必須チェック
3521
+ if (!args.fromRef) {
3522
+ return { content: [{ type: "text", text: "❌ fromRef is required." }], isError: true };
3564
3523
  }
3565
- if (_modeCount === 0) {
3566
- return { content: [{ type: "text", text: "❌ from/to, fromFlat/toFlat, または fromRef+beforeRef/afterRef を指定してください。" }], isError: true };
3524
+ if (!args.beforeRef && !args.afterRef) {
3525
+ return { content: [{ type: "text", text: "❌ beforeRef or afterRef is required." }], isError: true };
3567
3526
  }
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;
3527
+ if (args.beforeRef && args.afterRef) {
3528
+ return { content: [{ type: "text", text: "❌ beforeRef と afterRef は同時に指定できません。" }], isError: true };
3529
+ }
3530
+ if (!args.snapshotId) {
3531
+ return { content: [{ type: "text", text: "❌ snapshotId required. Get it from get_article_structure or any prior write response." }], isError: true };
3579
3532
  }
3580
3533
 
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
- }
3534
+ // acquireFreshState state 取得 + revision チェック
3535
+ let _preState = null;
3536
+ const { currentState, error: _stateError } = await acquireFreshState({
3537
+ expectedRevision: args.expectedRevision,
3538
+ mode, client, postId, sessionId: _sessionId, siteName: _siteName,
3539
+ });
3540
+ if (_stateError) return _stateError;
3541
+ _preState = currentState || null;
3614
3542
 
3615
- // ref flat に変換完了。以降 flat モードに合流
3543
+ // ref 解決(同じ currentState 2 つの ref を解決)
3544
+ try {
3545
+ fromFlat = resolveRefFromState(args.snapshotId, args.fromRef, mode, _sessionId, postId, currentState);
3546
+ const destRef = args.beforeRef || args.afterRef;
3547
+ const resolvedDest = resolveRefFromState(args.snapshotId, destRef, mode, _sessionId, postId, currentState);
3548
+ toFlat = args.beforeRef ? resolvedDest : resolvedDest + 1;
3549
+ } catch (e) {
3550
+ return { content: [{ type: "text", text: `❌ ref 解決エラー: ${e.message}` }], isError: true };
3616
3551
  }
3617
3552
 
3618
- // Phase 4: 入力 ref 保持(ref モード + count <= 1 のみ差分化)
3619
- const _inputRefs = (hasRef && count <= 1) ? [args.fromRef] : null;
3553
+ // 入力 ref 保持(count <= 1 のみ差分化)
3554
+ const _inputRefs = (count <= 1) ? [args.fromRef] : null;
3620
3555
 
3621
3556
  // 移動結果のレスポンス生成ヘルパー
3622
3557
  const moveMsg = (moved) => {
@@ -3628,56 +3563,21 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
3628
3563
  : `✅ ブロック移動 (${typeLabel})${_mt_mb}`;
3629
3564
  };
3630
3565
 
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 };
3566
+ // 同位置チェック
3567
+ if (fromFlat === toFlat || toFlat === fromFlat + count) {
3568
+ return { content: [{ type: "text", text: `✅ 移動不要(同じ位置)${_mt_mb}` }] };
3674
3569
  }
3675
3570
 
3676
3571
  if (mode === 'headless') {
3677
3572
  try {
3678
- const result = await client.headlessMove(postId, from, to, count);
3573
+ const result = await client.headlessMoveFlat(postId, fromFlat, toFlat, count);
3679
3574
  const _snap = await buildResponseSnapshot(mode, client, postId, _sessionId, _siteName, result);
3680
- return { content: [{ type: "text", text: appendSnapshotToTextLegacy(moveMsg(result.moved), _snap) }] };
3575
+ const _msg = moveMsg(result.moved);
3576
+ if (_inputRefs) {
3577
+ const changeInfo = buildChangeInfoFromResult('moved', _snap, result, _preState, { inputRefs: _inputRefs });
3578
+ if (changeInfo) return { content: [{ type: "text", text: appendRefChangesToText(_msg, changeInfo) }] };
3579
+ }
3580
+ return { content: [{ type: "text", text: appendSnapshotToTextLegacy(_msg, _snap) }] };
3681
3581
  } catch (e) {
3682
3582
  const formatted = formatHeadlessConflictError(e);
3683
3583
  if (formatted) return formatted;
@@ -3685,13 +3585,18 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
3685
3585
  }
3686
3586
  }
3687
3587
 
3688
- const result = await client.sendEditorCommand("move_block", { from, to, count }, 90000, _postId, _sessionId);
3588
+ const result = await client.sendEditorCommand("move_block", { fromFlat, toFlat, count }, 90000, _postId, _sessionId);
3689
3589
  if (!result || result.timeout)
3690
3590
  return timeoutResponse(name, client, args?.site);
3691
3591
  if (!result.success)
3692
3592
  return errorResponse(name, result.error, args?.site);
3693
3593
  const _snap = await buildResponseSnapshot(mode, client, postId, _sessionId, _siteName, result);
3694
- return { content: [{ type: "text", text: appendSnapshotToTextLegacy(moveMsg(result.moved), _snap) }] };
3594
+ const _msg = moveMsg(result.moved);
3595
+ if (_inputRefs) {
3596
+ const changeInfo = buildChangeInfoFromResult('moved', _snap, result, _preState, { inputRefs: _inputRefs });
3597
+ if (changeInfo) return { content: [{ type: "text", text: appendRefChangesToText(_msg, changeInfo) }] };
3598
+ }
3599
+ return { content: [{ type: "text", text: appendSnapshotToTextLegacy(_msg, _snap) }] };
3695
3600
  }
3696
3601
 
3697
3602
  case "undo": {
@@ -3738,9 +3643,9 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
3738
3643
  const _mt_dup = process.env.FRIDAY_DEBUG === '1' ? `\n[DEBUG] mode=${mode}` : '';
3739
3644
  const _siteName = siteName || args?.site || 'default';
3740
3645
 
3741
- // ref と index 排他チェック
3742
- if (args?.ref && args?.index !== undefined) {
3743
- return { content: [{ type: "text", text: "❌ ref index は同時に指定できません。" }], isError: true };
3646
+ // ref 必須チェック
3647
+ if (!args?.ref) {
3648
+ return { content: [{ type: "text", text: "❌ ref and snapshotId are required." }], isError: true };
3744
3649
  }
3745
3650
 
3746
3651
  // ref 解決 + revision チェック
@@ -3750,18 +3655,16 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
3750
3655
  mode, client, postId, sessionId: _sessionId, siteName: _siteName,
3751
3656
  });
3752
3657
  if (resolved.error) return resolved.error;
3753
- const _preState = resolved.currentState || null; // Phase 2: 差分計算用
3754
-
3755
- const index = resolved.index !== undefined ? resolved.index : args?.index;
3658
+ const _preState = resolved.currentState || null;
3756
3659
 
3757
- // Phase 3: 入力 ref を保持(index 経路は差分化しない)
3758
- const _inputRef = args?.ref || null;
3660
+ const index = resolved.index;
3661
+ const _inputRef = args.ref;
3759
3662
 
3760
3663
  if (mode === 'headless') {
3761
3664
  const _guard = await guardHeadlessConflict(postId, client, name);
3762
3665
  if (_guard) return _guard;
3763
3666
  if (index === undefined) {
3764
- return { content: [{ type: "text", text: "❌ Headless モードでは index の指定が必須です。" }], isError: true };
3667
+ return { content: [{ type: "text", text: "❌ ref resolution failed. Check snapshotId and ref." }], isError: true };
3765
3668
  }
3766
3669
  try {
3767
3670
  const result = await client.headlessDuplicate(postId, index);
@@ -3995,38 +3898,18 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
3995
3898
  }
3996
3899
 
3997
3900
  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
- }
3901
+ let { rawHTML, filePath } = (args || {});
4004
3902
 
4005
3903
  // 排他チェック
4006
3904
  if (rawHTML && filePath) {
4007
3905
  return { content: [{ type: "text", text: "❌ rawHTML と filePath は同時に指定できません。どちらか一方を指定してください。" }], isError: true };
4008
3906
  }
4009
3907
 
4010
- // Phase 4b: beforeRef/afterRef と index/position の排他チェック
4011
3908
  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
3909
  if (args?.beforeRef && args?.afterRef) {
4017
3910
  return { content: [{ type: "text", text: "❌ beforeRef と afterRef は同時に指定できません。" }], isError: true };
4018
3911
  }
4019
3912
 
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
3913
  // filePath → rawHTML 解決
4031
3914
  if (filePath) {
4032
3915
  try { rawHTML = readHTMLFromFile(filePath).html; }
@@ -4036,13 +3919,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
4036
3919
  return { content: [{ type: "text", text: "❌ rawHTML または filePath を指定してください。Gutenberg HTML 形式のブロックマークアップが必要です。" }], isError: true };
4037
3920
  }
4038
3921
 
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
3922
  // update_blocks コードパスに委譲
4045
- // expectedRevision は全モードで委譲(ref/index/append 問わず)
4046
3923
  const delegatedArgs = {
4047
3924
  postId: args.postId,
4048
3925
  site: args.site,
@@ -4058,11 +3935,6 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
4058
3935
  delegatedArgs.insert = { position: args.beforeRef ? 'before' : 'after' };
4059
3936
  delegatedArgs.snapshotId = args.snapshotId;
4060
3937
  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
3938
  } else {
4067
3939
  // 末尾追加
4068
3940
  delegatedArgs.insert = { position: 'before' };
@@ -4073,7 +3945,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
4073
3945
  }
4074
3946
 
4075
3947
  case "get_block_html": {
4076
- let { mode, postId: _postId, sessionId: _sessionId, message, client } = await resolveMode(args, name);
3948
+ let { mode, postId: _postId, sessionId: _sessionId, message, client, siteName } = await resolveMode(args, name);
4077
3949
  if (mode === 'error') {
4078
3950
  return errorResponse(name, message, args?.site);
4079
3951
  }
@@ -4081,11 +3953,30 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
4081
3953
  if (_resolved.error) return { content: [{ type: "text", text: _resolved.error }], isError: true };
4082
3954
  if (_resolved.editorConnected) mode = 'editor';
4083
3955
  const postId = _resolved.postId ?? _postId;
3956
+ const _siteName = siteName || args?.site || 'default';
4084
3957
 
4085
3958
  let tp;
4086
3959
  try { tp = normalizeTarget(args?.target); }
4087
3960
  catch (e) { return { content: [{ type: "text", text: `❌ ${e.message}` }], isError: true }; }
4088
3961
 
3962
+ // ref 解決(snapshotId + ref → index に変換)
3963
+ const resolved = await resolveRefsAndCheckRevision({
3964
+ snapshotId: args?.snapshotId,
3965
+ ref: tp._ref,
3966
+ refs: tp._refs,
3967
+ expectedRevision: args?.expectedRevision,
3968
+ mode, client, postId, sessionId: _sessionId, siteName: _siteName,
3969
+ });
3970
+ if (resolved.error) return resolved.error;
3971
+ if (resolved.index !== undefined) {
3972
+ tp.index = resolved.index;
3973
+ delete tp._ref;
3974
+ }
3975
+ if (resolved.indices !== undefined) {
3976
+ tp.indices = resolved.indices;
3977
+ delete tp._refs;
3978
+ }
3979
+
4089
3980
  const { target, index, indices, startIndex, endIndex, section, blockType, typeIndex, contains, headingLevel, headingContains } = tp;
4090
3981
 
4091
3982
  if (mode === 'headless' && target === 'selected') {
@@ -4234,9 +4125,9 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
4234
4125
  return { content: [{ type: "text", text: "❌ action は必須です" }], isError: true };
4235
4126
  }
4236
4127
 
4237
- // ref と index 排他チェック
4238
- if (args?.ref && args?.index !== undefined) {
4239
- return { content: [{ type: "text", text: "❌ ref index は同時に指定できません。" }], isError: true };
4128
+ // ref 必須チェック
4129
+ if (!args?.ref) {
4130
+ return { content: [{ type: "text", text: "❌ ref and snapshotId are required." }], isError: true };
4240
4131
  }
4241
4132
 
4242
4133
  // ref 解決 + revision チェック
@@ -4246,11 +4137,11 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
4246
4137
  mode, client, postId, sessionId: _sessionId, siteName: _siteName,
4247
4138
  });
4248
4139
  if (resolved.error) return resolved.error;
4249
- const _preState = resolved.currentState || null; // Phase 2: 差分計算用
4140
+ const _preState = resolved.currentState || null;
4250
4141
 
4251
- const index = resolved.index !== undefined ? resolved.index : args?.index;
4142
+ const index = resolved.index;
4252
4143
  if (index === undefined) {
4253
- return { content: [{ type: "text", text: "❌ table_operations では index または ref のいずれかが必要です。" }], isError: true };
4144
+ return { content: [{ type: "text", text: "❌ ref resolution failed. Check snapshotId and ref." }], isError: true };
4254
4145
  }
4255
4146
 
4256
4147
  const tableParams = {
@@ -4539,9 +4430,6 @@ function _detectTargetType(args) {
4539
4430
  if (t) {
4540
4431
  if (t.ref) return 'ref';
4541
4432
  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
4433
  if (t.section) return 'section';
4546
4434
  if (t.heading) return 'heading';
4547
4435
  if (t.blockType) return 'blockType';
@@ -4549,8 +4437,6 @@ function _detectTargetType(args) {
4549
4437
  }
4550
4438
  if (args.ref || args.beforeRef || args.afterRef || args.fromRef) return 'ref';
4551
4439
  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
4440
  return null;
4555
4441
  }
4556
4442
 
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.6",
4
4
  "description": "WordPress MCP Server for Claude Code - REST API direct communication",
5
5
  "type": "module",
6
6
  "main": "dist/mcp-server.js",