friday-mcp-v2 3.0.3 → 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.
- package/dist/bridge-common.js +8 -0
- package/dist/mcp-server.js +236 -253
- package/package.json +1 -1
package/dist/bridge-common.js
CHANGED
|
@@ -48,6 +48,14 @@ export function getBridgeLogPath() {
|
|
|
48
48
|
return join(os.homedir(), '.local', 'share', 'friday-mcp', 'logs', 'bridge.log');
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
+
export function getToolLogPath() {
|
|
52
|
+
if (process.platform === 'win32') {
|
|
53
|
+
const localAppData = process.env.LOCALAPPDATA || join(os.homedir(), 'AppData', 'Local');
|
|
54
|
+
return join(localAppData, 'FridayMCP', 'logs', 'tool-calls.jsonl');
|
|
55
|
+
}
|
|
56
|
+
return join(os.homedir(), '.local', 'share', 'friday-mcp', 'logs', 'tool-calls.jsonl');
|
|
57
|
+
}
|
|
58
|
+
|
|
51
59
|
export function getBridgePidPath() {
|
|
52
60
|
if (process.platform === 'win32') {
|
|
53
61
|
const localAppData = process.env.LOCALAPPDATA || join(os.homedir(), 'AppData', 'Local');
|
package/dist/mcp-server.js
CHANGED
|
@@ -12,9 +12,10 @@ import { FridayWPClient, ConnectionRegistry } from "./wordpress-api.js";
|
|
|
12
12
|
import { BridgeClient } from "./bridge-client.js";
|
|
13
13
|
import fetch from "node-fetch";
|
|
14
14
|
import { randomUUID } from "node:crypto";
|
|
15
|
-
import { readFileSync, statSync, realpathSync } from "node:fs";
|
|
15
|
+
import { readFileSync, statSync, realpathSync, appendFileSync, mkdirSync, existsSync, writeFileSync, renameSync } from "node:fs";
|
|
16
16
|
import { execFile } from "node:child_process";
|
|
17
|
-
import { resolve as resolvePath, sep, dirname } from "node:path";
|
|
17
|
+
import { resolve as resolvePath, sep, dirname, join as joinPath } from "node:path";
|
|
18
|
+
import os from "node:os";
|
|
18
19
|
import { fileURLToPath } from "node:url";
|
|
19
20
|
// package.json からバージョンを取得
|
|
20
21
|
const __pkg_dirname = dirname(fileURLToPath(import.meta.url));
|
|
@@ -48,6 +49,39 @@ if (_envWorkspace) {
|
|
|
48
49
|
}
|
|
49
50
|
}
|
|
50
51
|
|
|
52
|
+
// ========================================
|
|
53
|
+
// Tool Call Logger(FRIDAY_DEBUG=1 で有効)
|
|
54
|
+
// ========================================
|
|
55
|
+
const _TOOL_LOG_MAX_BYTES = 10 * 1024 * 1024; // 10MB
|
|
56
|
+
function _getToolLogPath() {
|
|
57
|
+
if (process.platform === 'win32') {
|
|
58
|
+
const localAppData = process.env.LOCALAPPDATA || joinPath(os.homedir(), 'AppData', 'Local');
|
|
59
|
+
return joinPath(localAppData, 'FridayMCP', 'logs', 'tool-calls.jsonl');
|
|
60
|
+
}
|
|
61
|
+
return joinPath(os.homedir(), '.local', 'share', 'friday-mcp', 'logs', 'tool-calls.jsonl');
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function writeToolLog(record) {
|
|
65
|
+
if (process.env.FRIDAY_DEBUG !== '1') return;
|
|
66
|
+
try {
|
|
67
|
+
const logPath = _getToolLogPath();
|
|
68
|
+
const logDir = dirname(logPath);
|
|
69
|
+
if (!existsSync(logDir)) mkdirSync(logDir, { recursive: true });
|
|
70
|
+
// ローテーション: 10MB 超えたら先頭半分を切り捨て
|
|
71
|
+
try {
|
|
72
|
+
const st = statSync(logPath);
|
|
73
|
+
if (st.size > _TOOL_LOG_MAX_BYTES) {
|
|
74
|
+
const content = readFileSync(logPath, 'utf-8');
|
|
75
|
+
const lines = content.split('\n');
|
|
76
|
+
const half = Math.floor(lines.length / 2);
|
|
77
|
+
writeFileSync(logPath, lines.slice(half).join('\n'), 'utf-8');
|
|
78
|
+
}
|
|
79
|
+
} catch (_) { /* ファイル未存在時は無視 */ }
|
|
80
|
+
const line = JSON.stringify({ ts: new Date().toISOString(), ...record }) + '\n';
|
|
81
|
+
appendFileSync(logPath, line, 'utf-8');
|
|
82
|
+
} catch (_) { /* ログ書き込み失敗は無視 */ }
|
|
83
|
+
}
|
|
84
|
+
|
|
51
85
|
const registry = new ConnectionRegistry();
|
|
52
86
|
const bridgeClient = new BridgeClient();
|
|
53
87
|
|
|
@@ -215,7 +249,7 @@ function checkRevisionMismatch(expectedRevision, currentState, mode, postId, ses
|
|
|
215
249
|
: '';
|
|
216
250
|
const text = `❌ REVISION_MISMATCH: expected ${expectedRevision}, actual ${currentRevision}` +
|
|
217
251
|
snapshotLine +
|
|
218
|
-
`\n→ get_article_structure
|
|
252
|
+
`\n→ Re-fetch via get_article_structure.`;
|
|
219
253
|
return { content: [{ type: "text", text }], isError: true };
|
|
220
254
|
}
|
|
221
255
|
|
|
@@ -695,24 +729,6 @@ const targetSchema = {
|
|
|
695
729
|
type: "boolean",
|
|
696
730
|
description: "Target user-selected block. Editor mode only.",
|
|
697
731
|
},
|
|
698
|
-
index: {
|
|
699
|
-
type: "number",
|
|
700
|
-
description: "Block index (0-based, flattened).",
|
|
701
|
-
},
|
|
702
|
-
indices: {
|
|
703
|
-
type: "array",
|
|
704
|
-
items: { type: "number" },
|
|
705
|
-
description: "Multiple indices (0-based).",
|
|
706
|
-
},
|
|
707
|
-
range: {
|
|
708
|
-
type: "object",
|
|
709
|
-
properties: {
|
|
710
|
-
start: { type: "number", description: "Start (inclusive)." },
|
|
711
|
-
end: { type: "number", description: "End (inclusive)." },
|
|
712
|
-
},
|
|
713
|
-
required: ["start", "end"],
|
|
714
|
-
description: "Index range.",
|
|
715
|
-
},
|
|
716
732
|
section: {
|
|
717
733
|
type: "string",
|
|
718
734
|
description: "Section by heading text (partial match).",
|
|
@@ -748,7 +764,7 @@ const targetSchema = {
|
|
|
748
764
|
description: "Multiple block refs from snapshot. Requires snapshotId.",
|
|
749
765
|
},
|
|
750
766
|
},
|
|
751
|
-
description: "Target: one of selected/
|
|
767
|
+
description: "Target: one of selected/ref/refs/heading/section/blockType + optional filters (contains/nth). Requires snapshotId when using ref/refs.",
|
|
752
768
|
};
|
|
753
769
|
|
|
754
770
|
const targetSchemaNoSelected = {
|
|
@@ -756,7 +772,7 @@ const targetSchemaNoSelected = {
|
|
|
756
772
|
properties: { ...targetSchema.properties },
|
|
757
773
|
};
|
|
758
774
|
delete targetSchemaNoSelected.properties.selected;
|
|
759
|
-
targetSchemaNoSelected.description = "Target: one of
|
|
775
|
+
targetSchemaNoSelected.description = "Target: one of ref/refs/heading/section/blockType + optional filters (contains/nth). Requires snapshotId when using ref/refs.";
|
|
760
776
|
|
|
761
777
|
const insertSchema = {
|
|
762
778
|
type: "object",
|
|
@@ -781,9 +797,6 @@ function normalizeTarget(target) {
|
|
|
781
797
|
// --- 排他バリデーション ---
|
|
782
798
|
const primaries = [
|
|
783
799
|
target.selected && 'selected',
|
|
784
|
-
target.index !== undefined && 'index',
|
|
785
|
-
target.indices && 'indices',
|
|
786
|
-
target.range && 'range',
|
|
787
800
|
target.heading && 'heading',
|
|
788
801
|
target.ref && 'ref',
|
|
789
802
|
target.refs && 'refs',
|
|
@@ -796,19 +809,6 @@ function normalizeTarget(target) {
|
|
|
796
809
|
if (target.ref) result._ref = target.ref;
|
|
797
810
|
if (target.refs) result._refs = target.refs;
|
|
798
811
|
if (target.selected) result.target = "selected";
|
|
799
|
-
if (target.index !== undefined) result.index = target.index;
|
|
800
|
-
if (target.indices) result.indices = target.indices;
|
|
801
|
-
if (target.range) {
|
|
802
|
-
const s = target.range.start;
|
|
803
|
-
const e = target.range.end;
|
|
804
|
-
if (s === undefined || s === null || e === undefined || e === null ||
|
|
805
|
-
typeof s !== "number" || typeof e !== "number" ||
|
|
806
|
-
!Number.isInteger(s) || !Number.isInteger(e) || s < 0 || e < 0) {
|
|
807
|
-
throw new Error("range.start と range.end は 0 以上の整数で両方指定してください。");
|
|
808
|
-
}
|
|
809
|
-
result.startIndex = s;
|
|
810
|
-
result.endIndex = e;
|
|
811
|
-
}
|
|
812
812
|
if (target.section) result.section = target.section;
|
|
813
813
|
if (target.blockType) result.blockType = target.blockType;
|
|
814
814
|
if (target.nth !== undefined) result.typeIndex = target.nth;
|
|
@@ -897,15 +897,15 @@ function resolveRefFromState(snapshotId, ref, mode, sessionId, postId, currentSt
|
|
|
897
897
|
// 1. snapshot 取得
|
|
898
898
|
const snap = snapshotCache.get(snapshotId);
|
|
899
899
|
if (!snap) {
|
|
900
|
-
throw new Error(`snapshot "${snapshotId}" が見つかりません(期限切れまたは無効)。get_article_structure
|
|
900
|
+
throw new Error(`snapshot "${snapshotId}" が見つかりません(期限切れまたは無効)。Re-fetch via get_article_structure.`);
|
|
901
901
|
}
|
|
902
902
|
|
|
903
903
|
// 2. 状態ソース束縛チェック
|
|
904
904
|
if (snap.mode !== mode) {
|
|
905
|
-
throw new Error(`snapshot は ${snap.mode} モードで取得されましたが、現在は ${mode} モードです。get_article_structure
|
|
905
|
+
throw new Error(`snapshot は ${snap.mode} モードで取得されましたが、現在は ${mode} モードです。Re-fetch via get_article_structure.`);
|
|
906
906
|
}
|
|
907
907
|
if (mode === 'editor' && snap.sessionId && sessionId && snap.sessionId !== sessionId) {
|
|
908
|
-
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.`);
|
|
909
909
|
}
|
|
910
910
|
if (snap.postId !== postId) {
|
|
911
911
|
throw new Error(`snapshot は postId ${snap.postId} 用ですが、postId ${postId} に対して使用されています。`);
|
|
@@ -944,14 +944,14 @@ function resolveRefFromState(snapshotId, ref, mode, sessionId, postId, currentSt
|
|
|
944
944
|
if (refined.length === 1) return refined[0].index;
|
|
945
945
|
throw new Error(
|
|
946
946
|
`ref "${ref}" (type: ${snapBlock.type}) の解決先が ${refined.length > 0 ? refined.length : candidates.length} 件見つかりました。` +
|
|
947
|
-
`一意に特定できません。get_article_structure
|
|
947
|
+
`一意に特定できません。Re-fetch via get_article_structure.`
|
|
948
948
|
);
|
|
949
949
|
}
|
|
950
950
|
|
|
951
951
|
// 5c. 一致 0件 → エラー
|
|
952
952
|
throw new Error(
|
|
953
953
|
`ref "${ref}" (${snapBlock.type}, fingerprint: ${snapBlock.fingerprint}) に一致するブロックが見つかりません。` +
|
|
954
|
-
`構造が大幅に変更された可能性があります。get_article_structure
|
|
954
|
+
`構造が大幅に変更された可能性があります。Re-fetch via get_article_structure.`
|
|
955
955
|
);
|
|
956
956
|
}
|
|
957
957
|
|
|
@@ -991,12 +991,15 @@ async function resolveRefsAndCheckRevision({
|
|
|
991
991
|
mode, client, postId, sessionId, siteName,
|
|
992
992
|
}) {
|
|
993
993
|
const hasRef = ref || refs;
|
|
994
|
+
const _debug = process.env.FRIDAY_DEBUG === '1';
|
|
995
|
+
if (_debug) console.error(`[SNAPSHOT] resolveRefs: ref=${ref || 'none'}, refs=${refs ? refs.join(',') : 'none'}, snapshotId=${snapshotId || 'none'}, expectedRevision=${expectedRevision || 'none'}`);
|
|
994
996
|
|
|
995
997
|
// Case 1: ref/refs 指定あり
|
|
996
998
|
if (hasRef) {
|
|
997
999
|
if (!snapshotId) {
|
|
1000
|
+
if (_debug) console.error(`[SNAPSHOT] resolveRefs: ref指定あり but snapshotId missing → error`);
|
|
998
1001
|
return { error: {
|
|
999
|
-
content: [{ type: "text", text: "❌ ref/refs
|
|
1002
|
+
content: [{ type: "text", text: "❌ snapshotId required for ref/refs. Get it from get_article_structure or any prior write response." }],
|
|
1000
1003
|
isError: true,
|
|
1001
1004
|
}};
|
|
1002
1005
|
}
|
|
@@ -1007,13 +1010,16 @@ async function resolveRefsAndCheckRevision({
|
|
|
1007
1010
|
try {
|
|
1008
1011
|
if (ref) {
|
|
1009
1012
|
const index = resolveRefFromState(snapshotId, ref, mode, sessionId, postId, currentState);
|
|
1013
|
+
if (_debug) console.error(`[SNAPSHOT] resolveRefs: ref=${ref} → index=${index}`);
|
|
1010
1014
|
return { index, currentState };
|
|
1011
1015
|
}
|
|
1012
1016
|
if (refs) {
|
|
1013
1017
|
const indices = refs.map(r => resolveRefFromState(snapshotId, r, mode, sessionId, postId, currentState));
|
|
1018
|
+
if (_debug) console.error(`[SNAPSHOT] resolveRefs: refs=[${refs.join(',')}] → indices=[${indices.join(',')}]`);
|
|
1014
1019
|
return { indices, currentState };
|
|
1015
1020
|
}
|
|
1016
1021
|
} catch (e) {
|
|
1022
|
+
if (_debug) console.error(`[SNAPSHOT] resolveRefs: ref解決失敗 — ${e.message}`);
|
|
1017
1023
|
return { error: {
|
|
1018
1024
|
content: [{ type: "text", text: `❌ ref 解決エラー: ${e.message}` }],
|
|
1019
1025
|
isError: true,
|
|
@@ -1023,6 +1029,7 @@ async function resolveRefsAndCheckRevision({
|
|
|
1023
1029
|
|
|
1024
1030
|
// Case 2: ref なし + expectedRevision のみ
|
|
1025
1031
|
if (expectedRevision) {
|
|
1032
|
+
if (_debug) console.error(`[SNAPSHOT] resolveRefs: index指定 (revisionチェックのみ)`);
|
|
1026
1033
|
const { currentState, error } = await acquireFreshState({
|
|
1027
1034
|
expectedRevision, mode, client, postId, sessionId, siteName,
|
|
1028
1035
|
});
|
|
@@ -1031,6 +1038,7 @@ async function resolveRefsAndCheckRevision({
|
|
|
1031
1038
|
}
|
|
1032
1039
|
|
|
1033
1040
|
// Case 3: どちらもなし
|
|
1041
|
+
if (_debug) console.error(`[SNAPSHOT] resolveRefs: ref/revision なし → index直接指定`);
|
|
1034
1042
|
return {};
|
|
1035
1043
|
}
|
|
1036
1044
|
|
|
@@ -1065,8 +1073,10 @@ function normalizeDownstreamBlocks(blocks) {
|
|
|
1065
1073
|
* @returns {object|null} { snapshotId, revision, blocks[] } or null
|
|
1066
1074
|
*/
|
|
1067
1075
|
function buildResponseSnapshotFromResult(result, mode, postId, sessionId, siteName) {
|
|
1076
|
+
const _debug = process.env.FRIDAY_DEBUG === '1';
|
|
1068
1077
|
const blocks = result?.blocks;
|
|
1069
1078
|
if (!blocks || !Array.isArray(blocks) || blocks.length === 0) {
|
|
1079
|
+
if (_debug) console.error(`[SNAPSHOT] fromResult: blocks=${blocks ? 'empty' : 'missing'}`);
|
|
1070
1080
|
return null;
|
|
1071
1081
|
}
|
|
1072
1082
|
const state = normalizeDownstreamBlocks(blocks);
|
|
@@ -1086,19 +1096,26 @@ function buildResponseSnapshotFromResult(result, mode, postId, sessionId, siteNa
|
|
|
1086
1096
|
* @returns {Promise<object|null>} { snapshotId, revision, blocks[] } or null
|
|
1087
1097
|
*/
|
|
1088
1098
|
async function buildResponseSnapshot(mode, client, postId, sessionId, siteName, result) {
|
|
1099
|
+
const _debug = process.env.FRIDAY_DEBUG === '1';
|
|
1089
1100
|
// Phase 1: 下流 result.blocks があればそこから構築(再取得不要)
|
|
1090
1101
|
if (result) {
|
|
1091
1102
|
const fromResult = buildResponseSnapshotFromResult(result, mode, postId, sessionId, siteName);
|
|
1092
|
-
if (fromResult)
|
|
1103
|
+
if (fromResult) {
|
|
1104
|
+
if (_debug) console.error(`[SNAPSHOT] buildResponseSnapshot: fromResult OK, snapshotId=${fromResult.snapshotId}, blocks=${fromResult.blocks?.length}`);
|
|
1105
|
+
return fromResult;
|
|
1106
|
+
}
|
|
1093
1107
|
}
|
|
1108
|
+
if (_debug) console.error(`[SNAPSHOT] buildResponseSnapshot: fromResult failed, fallback to getCurrentStructure`);
|
|
1094
1109
|
let newState;
|
|
1095
1110
|
try {
|
|
1096
1111
|
newState = await getCurrentStructure(mode, client, postId, sessionId);
|
|
1097
1112
|
} catch {
|
|
1113
|
+
if (_debug) console.error(`[SNAPSHOT] buildResponseSnapshot: getCurrentStructure threw → null`);
|
|
1098
1114
|
return null;
|
|
1099
1115
|
}
|
|
1100
1116
|
|
|
1101
1117
|
if (!newState?.allBlocks || newState.allBlocks.length === 0) {
|
|
1118
|
+
if (_debug) console.error(`[SNAPSHOT] buildResponseSnapshot: allBlocks empty → null`);
|
|
1102
1119
|
return null;
|
|
1103
1120
|
}
|
|
1104
1121
|
|
|
@@ -1145,7 +1162,12 @@ async function buildResponseSnapshot(mode, client, postId, sessionId, siteName,
|
|
|
1145
1162
|
* @returns {string} snapshot 行と blocks 一覧が付加されたテキスト
|
|
1146
1163
|
*/
|
|
1147
1164
|
function appendSnapshotToTextLegacy(text, snapshot, refInfo) {
|
|
1148
|
-
|
|
1165
|
+
const _debug = process.env.FRIDAY_DEBUG === '1';
|
|
1166
|
+
if (!snapshot) {
|
|
1167
|
+
if (_debug) console.error(`[SNAPSHOT] appendLegacy: snapshot=null → タグなし`);
|
|
1168
|
+
return text;
|
|
1169
|
+
}
|
|
1170
|
+
if (_debug) console.error(`[SNAPSHOT] appendLegacy: snapshotId=${snapshot.snapshotId}, blocks=${snapshot.blocks?.length}`);
|
|
1149
1171
|
|
|
1150
1172
|
// 1→N 展開チェック(単体操作時のみ)
|
|
1151
1173
|
let expandedLine = '';
|
|
@@ -1182,9 +1204,12 @@ function appendSnapshotToTextLegacy(text, snapshot, refInfo) {
|
|
|
1182
1204
|
* @returns {string}
|
|
1183
1205
|
*/
|
|
1184
1206
|
function buildUpdateDiffResponse(text, snap, result, preState, inputRef, isInsert, refInfo) {
|
|
1207
|
+
const _debug = process.env.FRIDAY_DEBUG === '1';
|
|
1185
1208
|
if (!inputRef || !snap) {
|
|
1209
|
+
if (_debug) console.error(`[SNAPSHOT] diffResponse: legacy path (inputRef=${inputRef || 'null'}, snap=${snap ? 'ok' : 'null'})`);
|
|
1186
1210
|
return appendSnapshotToTextLegacy(text, snap, refInfo);
|
|
1187
1211
|
}
|
|
1212
|
+
if (_debug) console.error(`[SNAPSHOT] diffResponse: diff path (inputRef=${inputRef}, snapId=${snap?.snapshotId})`);
|
|
1188
1213
|
|
|
1189
1214
|
if (isInsert) {
|
|
1190
1215
|
const changeInfo = buildChangeInfoFromResult('inserted', snap, result, preState);
|
|
@@ -1287,6 +1312,7 @@ function buildUpdateDiffResponse(text, snap, result, preState, inputRef, isInser
|
|
|
1287
1312
|
*/
|
|
1288
1313
|
function appendRefChangesToText(text, changeInfo) {
|
|
1289
1314
|
if (!changeInfo) return text;
|
|
1315
|
+
if (process.env.FRIDAY_DEBUG === '1') console.error(`[SNAPSHOT] appendDiff: type=${changeInfo.type}, snapshotId=${changeInfo.snapshotId}`);
|
|
1290
1316
|
|
|
1291
1317
|
let out = text + `\n\n[snapshot:${changeInfo.snapshotId} rev:${changeInfo.revision}]`;
|
|
1292
1318
|
|
|
@@ -1305,13 +1331,13 @@ function appendRefChangesToText(text, changeInfo) {
|
|
|
1305
1331
|
.join(', ');
|
|
1306
1332
|
break;
|
|
1307
1333
|
case 'updated':
|
|
1308
|
-
out += `\nupdated: [${changeInfo.updatedRefs.join(', ')}] (
|
|
1334
|
+
out += `\nupdated: [${changeInfo.updatedRefs.join(', ')}] (reuse this snapshotId for next operation)`;
|
|
1309
1335
|
break;
|
|
1310
1336
|
case 'expanded':
|
|
1311
1337
|
out += `\nexpanded: ${changeInfo.expanded.oldRef} \u2192 [${changeInfo.expanded.newRefs.join(', ')}]`;
|
|
1312
1338
|
break;
|
|
1313
1339
|
case 'duplicated':
|
|
1314
|
-
out += `\nduplicated: ${changeInfo.sourceRef} \u2192 ${changeInfo.newRef} (source:[${changeInfo.sourceIndex}], new:[${changeInfo.newIndex}]) (
|
|
1340
|
+
out += `\nduplicated: ${changeInfo.sourceRef} \u2192 ${changeInfo.newRef} (source:[${changeInfo.sourceIndex}], new:[${changeInfo.newIndex}]) (reuse this snapshotId for next operation)`;
|
|
1315
1341
|
break;
|
|
1316
1342
|
}
|
|
1317
1343
|
|
|
@@ -2029,40 +2055,33 @@ const tools = [
|
|
|
2029
2055
|
},
|
|
2030
2056
|
{
|
|
2031
2057
|
name: "delete_block",
|
|
2032
|
-
description: "Delete block(s) by
|
|
2058
|
+
description: "Delete block(s) by ref or selection.",
|
|
2033
2059
|
inputSchema: {
|
|
2034
2060
|
type: "object",
|
|
2035
2061
|
properties: {
|
|
2036
2062
|
postId: postIdParam,
|
|
2037
2063
|
site: siteParam,
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
snapshotId: { type: "string", description: "Snapshot ID from get_article_structure. Required when using ref/refs." },
|
|
2042
|
-
ref: { type: "string", description: "Block ref from snapshot (exclusive with index/indices)." },
|
|
2043
|
-
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." },
|
|
2044
2067
|
expectedRevision: { type: "string", description: "Revision from snapshot. Rejects if structure changed." },
|
|
2045
2068
|
},
|
|
2046
2069
|
},
|
|
2047
2070
|
},
|
|
2048
2071
|
{
|
|
2049
2072
|
name: "move_block",
|
|
2050
|
-
description: "Move block(s). Use
|
|
2073
|
+
description: "Move block(s). Use fromRef + beforeRef or afterRef. Requires snapshotId.",
|
|
2051
2074
|
inputSchema: {
|
|
2052
2075
|
type: "object",
|
|
2053
2076
|
properties: {
|
|
2054
2077
|
postId: postIdParam,
|
|
2055
2078
|
site: siteParam,
|
|
2056
|
-
from: { type: "number", description: "Source top-level index" },
|
|
2057
|
-
to: { type: "number", description: "Target position" },
|
|
2058
|
-
fromFlat: { type: "number", description: "Source flattened index" },
|
|
2059
|
-
toFlat: { type: "number", description: "Target flattened position" },
|
|
2060
2079
|
count: { type: "integer", description: "Consecutive count (default: 1)" },
|
|
2061
|
-
snapshotId: { type: "string", description: "Snapshot ID from get_article_structure
|
|
2080
|
+
snapshotId: { type: "string", description: "Snapshot ID (from get_article_structure or any write response). Required." },
|
|
2062
2081
|
expectedRevision: { type: "string", description: "Revision from snapshot. Rejects if structure changed." },
|
|
2063
|
-
fromRef: { type: "string", description: "Source block ref
|
|
2064
|
-
beforeRef: { type: "string", description: "Move before this ref (exclusive with
|
|
2065
|
-
afterRef: { type: "string", description: "Move after this ref (exclusive with
|
|
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)." },
|
|
2066
2085
|
},
|
|
2067
2086
|
},
|
|
2068
2087
|
},
|
|
@@ -2092,15 +2111,14 @@ const tools = [
|
|
|
2092
2111
|
},
|
|
2093
2112
|
{
|
|
2094
2113
|
name: "duplicate_block",
|
|
2095
|
-
description: "Duplicate block by
|
|
2114
|
+
description: "Duplicate block by ref or selection.",
|
|
2096
2115
|
inputSchema: {
|
|
2097
2116
|
type: "object",
|
|
2098
2117
|
properties: {
|
|
2099
2118
|
postId: postIdParam,
|
|
2100
2119
|
site: siteParam,
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
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." },
|
|
2104
2122
|
expectedRevision: { type: "string", description: "Revision from snapshot. Rejects if structure changed." },
|
|
2105
2123
|
},
|
|
2106
2124
|
},
|
|
@@ -2184,7 +2202,7 @@ const tools = [
|
|
|
2184
2202
|
},
|
|
2185
2203
|
{
|
|
2186
2204
|
name: "insert_block",
|
|
2187
|
-
description: "Insert Gutenberg HTML.
|
|
2205
|
+
description: "Insert Gutenberg HTML. Use beforeRef/afterRef for positioning. Omit both to append to end.",
|
|
2188
2206
|
inputSchema: {
|
|
2189
2207
|
type: "object",
|
|
2190
2208
|
properties: {
|
|
@@ -2192,12 +2210,10 @@ const tools = [
|
|
|
2192
2210
|
site: siteParam,
|
|
2193
2211
|
rawHTML: { type: "string", description: "HTML to insert (exclusive with filePath)" },
|
|
2194
2212
|
filePath: { type: "string", description: "Local file path (exclusive with rawHTML)" },
|
|
2195
|
-
|
|
2196
|
-
position: { type: "string", enum: ["before", "after"], description: "Insert relative to index: 'before' (default) or 'after'. Requires index." },
|
|
2197
|
-
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." },
|
|
2198
2214
|
expectedRevision: { type: "string", description: "Revision from snapshot. Rejects if structure changed." },
|
|
2199
|
-
beforeRef: { type: "string", description: "Insert before this ref
|
|
2200
|
-
afterRef: { type: "string", description: "Insert after this ref
|
|
2215
|
+
beforeRef: { type: "string", description: "Insert before this ref." },
|
|
2216
|
+
afterRef: { type: "string", description: "Insert after this ref." },
|
|
2201
2217
|
},
|
|
2202
2218
|
},
|
|
2203
2219
|
},
|
|
@@ -2235,7 +2251,7 @@ const tools = [
|
|
|
2235
2251
|
site: siteParam,
|
|
2236
2252
|
snapshotId: {
|
|
2237
2253
|
type: "string",
|
|
2238
|
-
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.",
|
|
2239
2255
|
},
|
|
2240
2256
|
expectedRevision: {
|
|
2241
2257
|
type: "string",
|
|
@@ -2320,16 +2336,12 @@ const tools = [
|
|
|
2320
2336
|
},
|
|
2321
2337
|
{
|
|
2322
2338
|
name: "table_operations",
|
|
2323
|
-
description: "Table operations (get/update/add/delete rows/columns/cells).
|
|
2339
|
+
description: "Table operations (get/update/add/delete rows/columns/cells). Requires ref+snapshotId to identify the table block.",
|
|
2324
2340
|
inputSchema: {
|
|
2325
2341
|
type: "object",
|
|
2326
2342
|
properties: {
|
|
2327
2343
|
postId: postIdParam,
|
|
2328
2344
|
site: siteParam,
|
|
2329
|
-
index: {
|
|
2330
|
-
type: "number",
|
|
2331
|
-
description: "Table index (0-based)",
|
|
2332
|
-
},
|
|
2333
2345
|
action: {
|
|
2334
2346
|
type: "string",
|
|
2335
2347
|
enum: ["get_structure", "update_cell", "add_row", "delete_row", "add_column", "delete_column", "move_row", "move_column", "update_row", "update_column"],
|
|
@@ -2356,8 +2368,8 @@ const tools = [
|
|
|
2356
2368
|
items: { type: "string" },
|
|
2357
2369
|
description: "add_row: new cells, add_column: init values, update_row/column: replacements. Omit for empty.",
|
|
2358
2370
|
},
|
|
2359
|
-
snapshotId: { type: "string", description: "Snapshot ID from get_article_structure
|
|
2360
|
-
ref: { type: "string", description: "
|
|
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." },
|
|
2361
2373
|
expectedRevision: { type: "string", description: "Revision from snapshot. Rejects if structure changed." },
|
|
2362
2374
|
},
|
|
2363
2375
|
required: ["action"],
|
|
@@ -2473,7 +2485,7 @@ async function handleUpdateBlocksTool(args, toolName) {
|
|
|
2473
2485
|
}
|
|
2474
2486
|
if (!snapshotId) {
|
|
2475
2487
|
return {
|
|
2476
|
-
content: [{ type: "text", text: `❌ operations
|
|
2488
|
+
content: [{ type: "text", text: `❌ snapshotId required for operations. Get it from get_article_structure or any prior write response.` }],
|
|
2477
2489
|
isError: true,
|
|
2478
2490
|
};
|
|
2479
2491
|
}
|
|
@@ -2816,10 +2828,12 @@ async function handleUpdateBlocksTool(args, toolName) {
|
|
|
2816
2828
|
}
|
|
2817
2829
|
|
|
2818
2830
|
// ツール実行のハンドラ
|
|
2831
|
+
const _WRITE_TOOLS = new Set(['update_blocks', 'delete_block', 'move_block', 'duplicate_block', 'insert_block', 'table_operations']);
|
|
2819
2832
|
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
2820
2833
|
const { name, arguments: args } = request.params;
|
|
2834
|
+
const _toolLogStart = Date.now();
|
|
2821
2835
|
try {
|
|
2822
|
-
switch (name) {
|
|
2836
|
+
const _result = await (async () => { switch (name) {
|
|
2823
2837
|
case "get_article_structure": {
|
|
2824
2838
|
let { mode, postId: _postId, sessionId: _sessionId, message, client, siteName } = await resolveMode(args, name);
|
|
2825
2839
|
if (mode === 'error') {
|
|
@@ -3356,9 +3370,9 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
3356
3370
|
if (_guard) return _guard;
|
|
3357
3371
|
}
|
|
3358
3372
|
|
|
3359
|
-
// ref
|
|
3360
|
-
if (
|
|
3361
|
-
return { content: [{ type: "text", text: "❌ ref
|
|
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 };
|
|
3362
3376
|
}
|
|
3363
3377
|
|
|
3364
3378
|
// ref 解決 + revision チェック
|
|
@@ -3370,20 +3384,15 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
3370
3384
|
if (resolved.error) return resolved.error;
|
|
3371
3385
|
const _preState = resolved.currentState || null; // Phase 2: 差分計算用
|
|
3372
3386
|
|
|
3373
|
-
let
|
|
3374
|
-
|
|
3375
|
-
|
|
3387
|
+
let index = resolved.index;
|
|
3388
|
+
let indices = resolved.indices;
|
|
3389
|
+
const count = (index !== undefined) ? 1 : undefined;
|
|
3376
3390
|
|
|
3377
|
-
// Phase 3: 入力 ref
|
|
3391
|
+
// Phase 3: 入力 ref を保持
|
|
3378
3392
|
const _inputRefs = args?.refs ? [...new Set(args.refs)]
|
|
3379
|
-
:
|
|
3393
|
+
: args?.ref ? [args.ref]
|
|
3380
3394
|
: null;
|
|
3381
3395
|
|
|
3382
|
-
// indices と index/count の排他バリデーション
|
|
3383
|
-
if (indices !== undefined && (index !== undefined || count !== undefined)) {
|
|
3384
|
-
return { content: [{ type: "text", text: "❌ indices と index/count は同時に指定できません。" }], isError: true };
|
|
3385
|
-
}
|
|
3386
|
-
|
|
3387
3396
|
// indices モード(非連続一括削除)
|
|
3388
3397
|
if (indices !== undefined) {
|
|
3389
3398
|
if (!Array.isArray(indices) || indices.length === 0) {
|
|
@@ -3428,10 +3437,10 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
3428
3437
|
return { content: [{ type: "text", text: appendSnapshotToTextLegacy(_msg, _snap) }] };
|
|
3429
3438
|
}
|
|
3430
3439
|
|
|
3431
|
-
//
|
|
3440
|
+
// 単一 ref モード(ref → index 解決済み)
|
|
3432
3441
|
if (mode === 'headless') {
|
|
3433
3442
|
if (index === undefined) {
|
|
3434
|
-
return { content: [{ type: "text", text: "❌
|
|
3443
|
+
return { content: [{ type: "text", text: "❌ ref resolution failed. Check snapshotId and ref." }], isError: true };
|
|
3435
3444
|
}
|
|
3436
3445
|
try {
|
|
3437
3446
|
const result = await client.headlessDelete(postId, index, count || 1);
|
|
@@ -3482,8 +3491,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
3482
3491
|
if (_guard) return _guard;
|
|
3483
3492
|
}
|
|
3484
3493
|
|
|
3485
|
-
|
|
3486
|
-
let { fromFlat, toFlat } = args;
|
|
3494
|
+
let fromFlat, toFlat;
|
|
3487
3495
|
const count = args.count ?? 1;
|
|
3488
3496
|
|
|
3489
3497
|
// count バリデーション
|
|
@@ -3491,70 +3499,41 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
3491
3499
|
return { content: [{ type: "text", text: "❌ count は1以上の整数を指定してください。" }], isError: true };
|
|
3492
3500
|
}
|
|
3493
3501
|
|
|
3494
|
-
//
|
|
3495
|
-
|
|
3496
|
-
|
|
3497
|
-
const hasRef = args?.fromRef !== undefined || args?.beforeRef !== undefined || args?.afterRef !== undefined;
|
|
3498
|
-
const _modeCount = [hasTopLevel, hasFlat, hasRef].filter(Boolean).length;
|
|
3499
|
-
|
|
3500
|
-
if (_modeCount > 1) {
|
|
3501
|
-
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 };
|
|
3502
3505
|
}
|
|
3503
|
-
if (
|
|
3504
|
-
return { content: [{ type: "text", text: "❌
|
|
3506
|
+
if (!args.beforeRef && !args.afterRef) {
|
|
3507
|
+
return { content: [{ type: "text", text: "❌ beforeRef or afterRef is required." }], isError: true };
|
|
3505
3508
|
}
|
|
3506
|
-
|
|
3507
|
-
|
|
3508
|
-
|
|
3509
|
-
|
|
3510
|
-
|
|
3511
|
-
const { currentState, error: _revError } = await acquireFreshState({
|
|
3512
|
-
expectedRevision: args.expectedRevision,
|
|
3513
|
-
mode, client, postId, sessionId: _sessionId, siteName: _siteName,
|
|
3514
|
-
});
|
|
3515
|
-
if (_revError) return _revError;
|
|
3516
|
-
_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 };
|
|
3517
3514
|
}
|
|
3518
3515
|
|
|
3519
|
-
//
|
|
3520
|
-
|
|
3521
|
-
|
|
3522
|
-
|
|
3523
|
-
|
|
3524
|
-
|
|
3525
|
-
|
|
3526
|
-
|
|
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: "❌ ref を使用するには snapshotId が必要です。get_article_structure で取得してください。" }], isError: true };
|
|
3532
|
-
}
|
|
3533
|
-
|
|
3534
|
-
// acquireFreshState で 1 回だけ state 取得 + revision チェック
|
|
3535
|
-
const { currentState, error: _stateError } = await acquireFreshState({
|
|
3536
|
-
expectedRevision: args.expectedRevision,
|
|
3537
|
-
mode, client, postId, sessionId: _sessionId, siteName: _siteName,
|
|
3538
|
-
});
|
|
3539
|
-
if (_stateError) return _stateError;
|
|
3540
|
-
_preState = currentState || null; // Phase 2: 差分計算用
|
|
3541
|
-
|
|
3542
|
-
// ref 解決(同じ currentState で 2 つの ref を解決)
|
|
3543
|
-
try {
|
|
3544
|
-
fromFlat = resolveRefFromState(args.snapshotId, args.fromRef, mode, _sessionId, postId, currentState);
|
|
3545
|
-
const destRef = args.beforeRef || args.afterRef;
|
|
3546
|
-
const resolvedDest = resolveRefFromState(args.snapshotId, destRef, mode, _sessionId, postId, currentState);
|
|
3547
|
-
// 位置計算: beforeRef → そのまま, afterRef → +1
|
|
3548
|
-
toFlat = args.beforeRef ? resolvedDest : resolvedDest + 1;
|
|
3549
|
-
} catch (e) {
|
|
3550
|
-
return { content: [{ type: "text", text: `❌ ref 解決エラー: ${e.message}` }], isError: true };
|
|
3551
|
-
}
|
|
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;
|
|
3552
3524
|
|
|
3553
|
-
|
|
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 };
|
|
3554
3533
|
}
|
|
3555
3534
|
|
|
3556
|
-
//
|
|
3557
|
-
const _inputRefs = (
|
|
3535
|
+
// 入力 ref 保持(count <= 1 のみ差分化)
|
|
3536
|
+
const _inputRefs = (count <= 1) ? [args.fromRef] : null;
|
|
3558
3537
|
|
|
3559
3538
|
// 移動結果のレスポンス生成ヘルパー
|
|
3560
3539
|
const moveMsg = (moved) => {
|
|
@@ -3566,56 +3545,21 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
3566
3545
|
: `✅ ブロック移動 (${typeLabel})${_mt_mb}`;
|
|
3567
3546
|
};
|
|
3568
3547
|
|
|
3569
|
-
//
|
|
3570
|
-
if (
|
|
3571
|
-
|
|
3572
|
-
return { content: [{ type: "text", text: "❌ fromFlat と toFlat の両方を指定してください。" }], isError: true };
|
|
3573
|
-
}
|
|
3574
|
-
if (fromFlat === toFlat || toFlat === fromFlat + count) {
|
|
3575
|
-
return { content: [{ type: "text", text: `✅ 移動不要(同じ位置)${_mt_mb}` }] };
|
|
3576
|
-
}
|
|
3577
|
-
|
|
3578
|
-
if (mode === 'headless') {
|
|
3579
|
-
try {
|
|
3580
|
-
const result = await client.headlessMoveFlat(postId, fromFlat, toFlat, count);
|
|
3581
|
-
const _snap = await buildResponseSnapshot(mode, client, postId, _sessionId, _siteName, result);
|
|
3582
|
-
const _msg = moveMsg(result.moved);
|
|
3583
|
-
if (_inputRefs) {
|
|
3584
|
-
const changeInfo = buildChangeInfoFromResult('moved', _snap, result, _preState, { inputRefs: _inputRefs });
|
|
3585
|
-
if (changeInfo) return { content: [{ type: "text", text: appendRefChangesToText(_msg, changeInfo) }] };
|
|
3586
|
-
}
|
|
3587
|
-
return { content: [{ type: "text", text: appendSnapshotToTextLegacy(_msg, _snap) }] };
|
|
3588
|
-
} catch (e) {
|
|
3589
|
-
const formatted = formatHeadlessConflictError(e);
|
|
3590
|
-
if (formatted) return formatted;
|
|
3591
|
-
throw e;
|
|
3592
|
-
}
|
|
3593
|
-
}
|
|
3594
|
-
|
|
3595
|
-
const result = await client.sendEditorCommand("move_block", { fromFlat, toFlat, count }, 90000, _postId, _sessionId);
|
|
3596
|
-
if (!result || result.timeout)
|
|
3597
|
-
return timeoutResponse(name, client, args?.site);
|
|
3598
|
-
if (!result.success)
|
|
3599
|
-
return errorResponse(name, result.error, args?.site);
|
|
3600
|
-
const _snap = await buildResponseSnapshot(mode, client, postId, _sessionId, _siteName, result);
|
|
3601
|
-
const _msg = moveMsg(result.moved);
|
|
3602
|
-
if (_inputRefs) {
|
|
3603
|
-
const changeInfo = buildChangeInfoFromResult('moved', _snap, result, _preState, { inputRefs: _inputRefs });
|
|
3604
|
-
if (changeInfo) return { content: [{ type: "text", text: appendRefChangesToText(_msg, changeInfo) }] };
|
|
3605
|
-
}
|
|
3606
|
-
return { content: [{ type: "text", text: appendSnapshotToTextLegacy(_msg, _snap) }] };
|
|
3607
|
-
}
|
|
3608
|
-
|
|
3609
|
-
// 既存モード(from/to トップレベル)
|
|
3610
|
-
if (from === undefined || to === undefined) {
|
|
3611
|
-
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}` }] };
|
|
3612
3551
|
}
|
|
3613
3552
|
|
|
3614
3553
|
if (mode === 'headless') {
|
|
3615
3554
|
try {
|
|
3616
|
-
const result = await client.
|
|
3555
|
+
const result = await client.headlessMoveFlat(postId, fromFlat, toFlat, count);
|
|
3617
3556
|
const _snap = await buildResponseSnapshot(mode, client, postId, _sessionId, _siteName, result);
|
|
3618
|
-
|
|
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) }] };
|
|
3619
3563
|
} catch (e) {
|
|
3620
3564
|
const formatted = formatHeadlessConflictError(e);
|
|
3621
3565
|
if (formatted) return formatted;
|
|
@@ -3623,13 +3567,18 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
3623
3567
|
}
|
|
3624
3568
|
}
|
|
3625
3569
|
|
|
3626
|
-
const result = await client.sendEditorCommand("move_block", {
|
|
3570
|
+
const result = await client.sendEditorCommand("move_block", { fromFlat, toFlat, count }, 90000, _postId, _sessionId);
|
|
3627
3571
|
if (!result || result.timeout)
|
|
3628
3572
|
return timeoutResponse(name, client, args?.site);
|
|
3629
3573
|
if (!result.success)
|
|
3630
3574
|
return errorResponse(name, result.error, args?.site);
|
|
3631
3575
|
const _snap = await buildResponseSnapshot(mode, client, postId, _sessionId, _siteName, result);
|
|
3632
|
-
|
|
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) }] };
|
|
3633
3582
|
}
|
|
3634
3583
|
|
|
3635
3584
|
case "undo": {
|
|
@@ -3676,9 +3625,9 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
3676
3625
|
const _mt_dup = process.env.FRIDAY_DEBUG === '1' ? `\n[DEBUG] mode=${mode}` : '';
|
|
3677
3626
|
const _siteName = siteName || args?.site || 'default';
|
|
3678
3627
|
|
|
3679
|
-
// ref
|
|
3680
|
-
if (args?.ref
|
|
3681
|
-
return { content: [{ type: "text", text: "❌ ref
|
|
3628
|
+
// ref 必須チェック
|
|
3629
|
+
if (!args?.ref) {
|
|
3630
|
+
return { content: [{ type: "text", text: "❌ ref and snapshotId are required." }], isError: true };
|
|
3682
3631
|
}
|
|
3683
3632
|
|
|
3684
3633
|
// ref 解決 + revision チェック
|
|
@@ -3688,18 +3637,16 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
3688
3637
|
mode, client, postId, sessionId: _sessionId, siteName: _siteName,
|
|
3689
3638
|
});
|
|
3690
3639
|
if (resolved.error) return resolved.error;
|
|
3691
|
-
const _preState = resolved.currentState || null;
|
|
3640
|
+
const _preState = resolved.currentState || null;
|
|
3692
3641
|
|
|
3693
|
-
const index = resolved.index
|
|
3694
|
-
|
|
3695
|
-
// Phase 3: 入力 ref を保持(index 経路は差分化しない)
|
|
3696
|
-
const _inputRef = args?.ref || null;
|
|
3642
|
+
const index = resolved.index;
|
|
3643
|
+
const _inputRef = args.ref;
|
|
3697
3644
|
|
|
3698
3645
|
if (mode === 'headless') {
|
|
3699
3646
|
const _guard = await guardHeadlessConflict(postId, client, name);
|
|
3700
3647
|
if (_guard) return _guard;
|
|
3701
3648
|
if (index === undefined) {
|
|
3702
|
-
return { content: [{ type: "text", text: "❌
|
|
3649
|
+
return { content: [{ type: "text", text: "❌ ref resolution failed. Check snapshotId and ref." }], isError: true };
|
|
3703
3650
|
}
|
|
3704
3651
|
try {
|
|
3705
3652
|
const result = await client.headlessDuplicate(postId, index);
|
|
@@ -3933,38 +3880,18 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
3933
3880
|
}
|
|
3934
3881
|
|
|
3935
3882
|
case "insert_block": {
|
|
3936
|
-
let { rawHTML, filePath
|
|
3937
|
-
|
|
3938
|
-
// parentIndex は廃止済み(v3.0.0 Phase 5)— position を使用
|
|
3939
|
-
if (args?.parentIndex !== undefined) {
|
|
3940
|
-
return { content: [{ type: "text", text: "❌ parentIndex は廃止されました。代わりに index + position ('before'/'after') を使用してください。" }], isError: true };
|
|
3941
|
-
}
|
|
3883
|
+
let { rawHTML, filePath } = (args || {});
|
|
3942
3884
|
|
|
3943
3885
|
// 排他チェック
|
|
3944
3886
|
if (rawHTML && filePath) {
|
|
3945
3887
|
return { content: [{ type: "text", text: "❌ rawHTML と filePath は同時に指定できません。どちらか一方を指定してください。" }], isError: true };
|
|
3946
3888
|
}
|
|
3947
3889
|
|
|
3948
|
-
// Phase 4b: beforeRef/afterRef と index/position の排他チェック
|
|
3949
3890
|
const _hasRefPosition = args?.beforeRef !== undefined || args?.afterRef !== undefined;
|
|
3950
|
-
const _hasIndexPosition = index !== undefined || position !== undefined;
|
|
3951
|
-
if (_hasRefPosition && _hasIndexPosition) {
|
|
3952
|
-
return { content: [{ type: "text", text: "❌ beforeRef/afterRef と index/position は同時に指定できません。" }], isError: true };
|
|
3953
|
-
}
|
|
3954
3891
|
if (args?.beforeRef && args?.afterRef) {
|
|
3955
3892
|
return { content: [{ type: "text", text: "❌ beforeRef と afterRef は同時に指定できません。" }], isError: true };
|
|
3956
3893
|
}
|
|
3957
3894
|
|
|
3958
|
-
// position 指定時は index 必須(ref モードでないとき)
|
|
3959
|
-
if (!_hasRefPosition && position !== undefined && index === undefined) {
|
|
3960
|
-
return { content: [{ type: "text", text: "❌ position を指定する場合は index も指定してください。末尾追加は index を省略してください。" }], isError: true };
|
|
3961
|
-
}
|
|
3962
|
-
|
|
3963
|
-
// position バリデーション
|
|
3964
|
-
if (position !== undefined && position !== "before" && position !== "after") {
|
|
3965
|
-
return { content: [{ type: "text", text: `❌ position は 'before' または 'after' を指定してください: ${position}` }], isError: true };
|
|
3966
|
-
}
|
|
3967
|
-
|
|
3968
3895
|
// filePath → rawHTML 解決
|
|
3969
3896
|
if (filePath) {
|
|
3970
3897
|
try { rawHTML = readHTMLFromFile(filePath).html; }
|
|
@@ -3974,13 +3901,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
3974
3901
|
return { content: [{ type: "text", text: "❌ rawHTML または filePath を指定してください。Gutenberg HTML 形式のブロックマークアップが必要です。" }], isError: true };
|
|
3975
3902
|
}
|
|
3976
3903
|
|
|
3977
|
-
// index バリデーション(現行 Editor 互換: 整数 >= 0)— ref モードでないとき
|
|
3978
|
-
if (!_hasRefPosition && index !== undefined && (!Number.isInteger(index) || index < 0)) {
|
|
3979
|
-
return { content: [{ type: "text", text: `❌ Invalid index: ${index}` }], isError: true };
|
|
3980
|
-
}
|
|
3981
|
-
|
|
3982
3904
|
// update_blocks コードパスに委譲
|
|
3983
|
-
// expectedRevision は全モードで委譲(ref/index/append 問わず)
|
|
3984
3905
|
const delegatedArgs = {
|
|
3985
3906
|
postId: args.postId,
|
|
3986
3907
|
site: args.site,
|
|
@@ -3996,11 +3917,6 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
3996
3917
|
delegatedArgs.insert = { position: args.beforeRef ? 'before' : 'after' };
|
|
3997
3918
|
delegatedArgs.snapshotId = args.snapshotId;
|
|
3998
3919
|
delegatedArgs.appendToEnd = false;
|
|
3999
|
-
} else if (index !== undefined) {
|
|
4000
|
-
// 既存: index モード
|
|
4001
|
-
delegatedArgs.target = { index };
|
|
4002
|
-
delegatedArgs.insert = { position: position ?? 'before' };
|
|
4003
|
-
delegatedArgs.appendToEnd = false;
|
|
4004
3920
|
} else {
|
|
4005
3921
|
// 末尾追加
|
|
4006
3922
|
delegatedArgs.insert = { position: 'before' };
|
|
@@ -4172,9 +4088,9 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
4172
4088
|
return { content: [{ type: "text", text: "❌ action は必須です" }], isError: true };
|
|
4173
4089
|
}
|
|
4174
4090
|
|
|
4175
|
-
// ref
|
|
4176
|
-
if (args?.ref
|
|
4177
|
-
return { content: [{ type: "text", text: "❌ ref
|
|
4091
|
+
// ref 必須チェック
|
|
4092
|
+
if (!args?.ref) {
|
|
4093
|
+
return { content: [{ type: "text", text: "❌ ref and snapshotId are required." }], isError: true };
|
|
4178
4094
|
}
|
|
4179
4095
|
|
|
4180
4096
|
// ref 解決 + revision チェック
|
|
@@ -4184,11 +4100,11 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
4184
4100
|
mode, client, postId, sessionId: _sessionId, siteName: _siteName,
|
|
4185
4101
|
});
|
|
4186
4102
|
if (resolved.error) return resolved.error;
|
|
4187
|
-
const _preState = resolved.currentState || null;
|
|
4103
|
+
const _preState = resolved.currentState || null;
|
|
4188
4104
|
|
|
4189
|
-
const index = resolved.index
|
|
4105
|
+
const index = resolved.index;
|
|
4190
4106
|
if (index === undefined) {
|
|
4191
|
-
return { content: [{ type: "text", text: "❌
|
|
4107
|
+
return { content: [{ type: "text", text: "❌ ref resolution failed. Check snapshotId and ref." }], isError: true };
|
|
4192
4108
|
}
|
|
4193
4109
|
|
|
4194
4110
|
const tableParams = {
|
|
@@ -4424,7 +4340,25 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
4424
4340
|
|
|
4425
4341
|
default:
|
|
4426
4342
|
throw new Error(`Unknown tool: ${name}`);
|
|
4343
|
+
} })();
|
|
4344
|
+
// 書き込みツールの成功時ログ
|
|
4345
|
+
if (_WRITE_TOOLS.has(name)) {
|
|
4346
|
+
const { newSnapshotId, diffIncluded } = _extractSnapshotFromResponse(_result);
|
|
4347
|
+
writeToolLog({
|
|
4348
|
+
tool: name,
|
|
4349
|
+
postId: args?.postId || null,
|
|
4350
|
+
targetType: _detectTargetType(args),
|
|
4351
|
+
snapshotIdProvided: !!args?.snapshotId,
|
|
4352
|
+
snapshotId: args?.snapshotId || null,
|
|
4353
|
+
operationType: _detectOperationType(name, args),
|
|
4354
|
+
responsePath: diffIncluded ? 'diff' : (newSnapshotId ? 'legacy' : 'none'),
|
|
4355
|
+
newSnapshotId,
|
|
4356
|
+
diffIncluded,
|
|
4357
|
+
isError: !!_result?.isError,
|
|
4358
|
+
durationMs: Date.now() - _toolLogStart,
|
|
4359
|
+
});
|
|
4427
4360
|
}
|
|
4361
|
+
return _result;
|
|
4428
4362
|
}
|
|
4429
4363
|
catch (error) {
|
|
4430
4364
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
@@ -4435,6 +4369,13 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
4435
4369
|
content: errorMessage,
|
|
4436
4370
|
site: args?.site || 'default',
|
|
4437
4371
|
});
|
|
4372
|
+
writeToolLog({
|
|
4373
|
+
tool: name,
|
|
4374
|
+
postId: args?.postId || null,
|
|
4375
|
+
targetType: _detectTargetType(args),
|
|
4376
|
+
snapshotIdProvided: !!args?.snapshotId,
|
|
4377
|
+
error: errorMessage,
|
|
4378
|
+
});
|
|
4438
4379
|
return {
|
|
4439
4380
|
content: [{
|
|
4440
4381
|
type: "text",
|
|
@@ -4445,6 +4386,48 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
4445
4386
|
}
|
|
4446
4387
|
});
|
|
4447
4388
|
|
|
4389
|
+
// ツール呼び出しログ用: ターゲット指定方法を判定
|
|
4390
|
+
function _detectTargetType(args) {
|
|
4391
|
+
if (!args) return null;
|
|
4392
|
+
const t = args.target;
|
|
4393
|
+
if (t) {
|
|
4394
|
+
if (t.ref) return 'ref';
|
|
4395
|
+
if (t.refs) return 'refs';
|
|
4396
|
+
if (t.section) return 'section';
|
|
4397
|
+
if (t.heading) return 'heading';
|
|
4398
|
+
if (t.blockType) return 'blockType';
|
|
4399
|
+
if (t.selected) return 'selected';
|
|
4400
|
+
}
|
|
4401
|
+
if (args.ref || args.beforeRef || args.afterRef || args.fromRef) return 'ref';
|
|
4402
|
+
if (args.refs) return 'refs';
|
|
4403
|
+
return null;
|
|
4404
|
+
}
|
|
4405
|
+
|
|
4406
|
+
// ツール呼び出しログ用: 操作タイプを判定
|
|
4407
|
+
function _detectOperationType(toolName, args) {
|
|
4408
|
+
if (toolName === 'delete_block') return 'delete';
|
|
4409
|
+
if (toolName === 'move_block') return 'move';
|
|
4410
|
+
if (toolName === 'duplicate_block') return 'duplicate';
|
|
4411
|
+
if (toolName === 'insert_block') return 'insert';
|
|
4412
|
+
if (toolName === 'table_operations') return `table_${args?.action || 'unknown'}`;
|
|
4413
|
+
// update_blocks
|
|
4414
|
+
if (args?.operations) return 'batch';
|
|
4415
|
+
if (args?.insert || args?._fromInsertBlock) return 'insert';
|
|
4416
|
+
if (args?.newHTML) return 'newHTML';
|
|
4417
|
+
if (args?.replacements?.length > 0) return 'replacements';
|
|
4418
|
+
if (args?.attributeUpdates) return 'attributeUpdates';
|
|
4419
|
+
return 'unknown';
|
|
4420
|
+
}
|
|
4421
|
+
|
|
4422
|
+
// ツール呼び出しログ用: レスポンスから snapshotId を抽出
|
|
4423
|
+
function _extractSnapshotFromResponse(response) {
|
|
4424
|
+
if (!response?.content?.[0]?.text) return { newSnapshotId: null, diffIncluded: false };
|
|
4425
|
+
const text = response.content[0].text;
|
|
4426
|
+
const snapMatch = text.match(/snapshotId: (snap_[a-f0-9]+)/);
|
|
4427
|
+
const diffIncluded = /(?:updated|deleted|inserted|moved|duplicated|expanded|compressed): \[/.test(text);
|
|
4428
|
+
return { newSnapshotId: snapMatch ? snapMatch[1] : null, diffIncluded };
|
|
4429
|
+
}
|
|
4430
|
+
|
|
4448
4431
|
// サーバー起動
|
|
4449
4432
|
async function main() {
|
|
4450
4433
|
try {
|