friday-mcp-v2 3.0.3 → 3.0.4

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.
@@ -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');
@@ -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
 
@@ -991,10 +1025,13 @@ async function resolveRefsAndCheckRevision({
991
1025
  mode, client, postId, sessionId, siteName,
992
1026
  }) {
993
1027
  const hasRef = ref || refs;
1028
+ const _debug = process.env.FRIDAY_DEBUG === '1';
1029
+ if (_debug) console.error(`[SNAPSHOT] resolveRefs: ref=${ref || 'none'}, refs=${refs ? refs.join(',') : 'none'}, snapshotId=${snapshotId || 'none'}, expectedRevision=${expectedRevision || 'none'}`);
994
1030
 
995
1031
  // Case 1: ref/refs 指定あり
996
1032
  if (hasRef) {
997
1033
  if (!snapshotId) {
1034
+ if (_debug) console.error(`[SNAPSHOT] resolveRefs: ref指定あり but snapshotId missing → error`);
998
1035
  return { error: {
999
1036
  content: [{ type: "text", text: "❌ ref/refs を使用するには snapshotId が必要です。get_article_structure で取得してください。" }],
1000
1037
  isError: true,
@@ -1007,13 +1044,16 @@ async function resolveRefsAndCheckRevision({
1007
1044
  try {
1008
1045
  if (ref) {
1009
1046
  const index = resolveRefFromState(snapshotId, ref, mode, sessionId, postId, currentState);
1047
+ if (_debug) console.error(`[SNAPSHOT] resolveRefs: ref=${ref} → index=${index}`);
1010
1048
  return { index, currentState };
1011
1049
  }
1012
1050
  if (refs) {
1013
1051
  const indices = refs.map(r => resolveRefFromState(snapshotId, r, mode, sessionId, postId, currentState));
1052
+ if (_debug) console.error(`[SNAPSHOT] resolveRefs: refs=[${refs.join(',')}] → indices=[${indices.join(',')}]`);
1014
1053
  return { indices, currentState };
1015
1054
  }
1016
1055
  } catch (e) {
1056
+ if (_debug) console.error(`[SNAPSHOT] resolveRefs: ref解決失敗 — ${e.message}`);
1017
1057
  return { error: {
1018
1058
  content: [{ type: "text", text: `❌ ref 解決エラー: ${e.message}` }],
1019
1059
  isError: true,
@@ -1023,6 +1063,7 @@ async function resolveRefsAndCheckRevision({
1023
1063
 
1024
1064
  // Case 2: ref なし + expectedRevision のみ
1025
1065
  if (expectedRevision) {
1066
+ if (_debug) console.error(`[SNAPSHOT] resolveRefs: index指定 (revisionチェックのみ)`);
1026
1067
  const { currentState, error } = await acquireFreshState({
1027
1068
  expectedRevision, mode, client, postId, sessionId, siteName,
1028
1069
  });
@@ -1031,6 +1072,7 @@ async function resolveRefsAndCheckRevision({
1031
1072
  }
1032
1073
 
1033
1074
  // Case 3: どちらもなし
1075
+ if (_debug) console.error(`[SNAPSHOT] resolveRefs: ref/revision なし → index直接指定`);
1034
1076
  return {};
1035
1077
  }
1036
1078
 
@@ -1065,8 +1107,10 @@ function normalizeDownstreamBlocks(blocks) {
1065
1107
  * @returns {object|null} { snapshotId, revision, blocks[] } or null
1066
1108
  */
1067
1109
  function buildResponseSnapshotFromResult(result, mode, postId, sessionId, siteName) {
1110
+ const _debug = process.env.FRIDAY_DEBUG === '1';
1068
1111
  const blocks = result?.blocks;
1069
1112
  if (!blocks || !Array.isArray(blocks) || blocks.length === 0) {
1113
+ if (_debug) console.error(`[SNAPSHOT] fromResult: blocks=${blocks ? 'empty' : 'missing'}`);
1070
1114
  return null;
1071
1115
  }
1072
1116
  const state = normalizeDownstreamBlocks(blocks);
@@ -1086,19 +1130,26 @@ function buildResponseSnapshotFromResult(result, mode, postId, sessionId, siteNa
1086
1130
  * @returns {Promise<object|null>} { snapshotId, revision, blocks[] } or null
1087
1131
  */
1088
1132
  async function buildResponseSnapshot(mode, client, postId, sessionId, siteName, result) {
1133
+ const _debug = process.env.FRIDAY_DEBUG === '1';
1089
1134
  // Phase 1: 下流 result.blocks があればそこから構築(再取得不要)
1090
1135
  if (result) {
1091
1136
  const fromResult = buildResponseSnapshotFromResult(result, mode, postId, sessionId, siteName);
1092
- if (fromResult) return fromResult;
1137
+ if (fromResult) {
1138
+ if (_debug) console.error(`[SNAPSHOT] buildResponseSnapshot: fromResult OK, snapshotId=${fromResult.snapshotId}, blocks=${fromResult.blocks?.length}`);
1139
+ return fromResult;
1140
+ }
1093
1141
  }
1142
+ if (_debug) console.error(`[SNAPSHOT] buildResponseSnapshot: fromResult failed, fallback to getCurrentStructure`);
1094
1143
  let newState;
1095
1144
  try {
1096
1145
  newState = await getCurrentStructure(mode, client, postId, sessionId);
1097
1146
  } catch {
1147
+ if (_debug) console.error(`[SNAPSHOT] buildResponseSnapshot: getCurrentStructure threw → null`);
1098
1148
  return null;
1099
1149
  }
1100
1150
 
1101
1151
  if (!newState?.allBlocks || newState.allBlocks.length === 0) {
1152
+ if (_debug) console.error(`[SNAPSHOT] buildResponseSnapshot: allBlocks empty → null`);
1102
1153
  return null;
1103
1154
  }
1104
1155
 
@@ -1145,7 +1196,12 @@ async function buildResponseSnapshot(mode, client, postId, sessionId, siteName,
1145
1196
  * @returns {string} snapshot 行と blocks 一覧が付加されたテキスト
1146
1197
  */
1147
1198
  function appendSnapshotToTextLegacy(text, snapshot, refInfo) {
1148
- if (!snapshot) return text;
1199
+ const _debug = process.env.FRIDAY_DEBUG === '1';
1200
+ if (!snapshot) {
1201
+ if (_debug) console.error(`[SNAPSHOT] appendLegacy: snapshot=null → タグなし`);
1202
+ return text;
1203
+ }
1204
+ if (_debug) console.error(`[SNAPSHOT] appendLegacy: snapshotId=${snapshot.snapshotId}, blocks=${snapshot.blocks?.length}`);
1149
1205
 
1150
1206
  // 1→N 展開チェック(単体操作時のみ)
1151
1207
  let expandedLine = '';
@@ -1182,9 +1238,12 @@ function appendSnapshotToTextLegacy(text, snapshot, refInfo) {
1182
1238
  * @returns {string}
1183
1239
  */
1184
1240
  function buildUpdateDiffResponse(text, snap, result, preState, inputRef, isInsert, refInfo) {
1241
+ const _debug = process.env.FRIDAY_DEBUG === '1';
1185
1242
  if (!inputRef || !snap) {
1243
+ if (_debug) console.error(`[SNAPSHOT] diffResponse: legacy path (inputRef=${inputRef || 'null'}, snap=${snap ? 'ok' : 'null'})`);
1186
1244
  return appendSnapshotToTextLegacy(text, snap, refInfo);
1187
1245
  }
1246
+ if (_debug) console.error(`[SNAPSHOT] diffResponse: diff path (inputRef=${inputRef}, snapId=${snap?.snapshotId})`);
1188
1247
 
1189
1248
  if (isInsert) {
1190
1249
  const changeInfo = buildChangeInfoFromResult('inserted', snap, result, preState);
@@ -1287,6 +1346,7 @@ function buildUpdateDiffResponse(text, snap, result, preState, inputRef, isInser
1287
1346
  */
1288
1347
  function appendRefChangesToText(text, changeInfo) {
1289
1348
  if (!changeInfo) return text;
1349
+ if (process.env.FRIDAY_DEBUG === '1') console.error(`[SNAPSHOT] appendDiff: type=${changeInfo.type}, snapshotId=${changeInfo.snapshotId}`);
1290
1350
 
1291
1351
  let out = text + `\n\n[snapshot:${changeInfo.snapshotId} rev:${changeInfo.revision}]`;
1292
1352
 
@@ -2816,10 +2876,12 @@ async function handleUpdateBlocksTool(args, toolName) {
2816
2876
  }
2817
2877
 
2818
2878
  // ツール実行のハンドラ
2879
+ const _WRITE_TOOLS = new Set(['update_blocks', 'delete_block', 'move_block', 'duplicate_block', 'insert_block', 'table_operations']);
2819
2880
  server.setRequestHandler(CallToolRequestSchema, async (request) => {
2820
2881
  const { name, arguments: args } = request.params;
2882
+ const _toolLogStart = Date.now();
2821
2883
  try {
2822
- switch (name) {
2884
+ const _result = await (async () => { switch (name) {
2823
2885
  case "get_article_structure": {
2824
2886
  let { mode, postId: _postId, sessionId: _sessionId, message, client, siteName } = await resolveMode(args, name);
2825
2887
  if (mode === 'error') {
@@ -4424,7 +4486,25 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
4424
4486
 
4425
4487
  default:
4426
4488
  throw new Error(`Unknown tool: ${name}`);
4489
+ } })();
4490
+ // 書き込みツールの成功時ログ
4491
+ if (_WRITE_TOOLS.has(name)) {
4492
+ const { newSnapshotId, diffIncluded } = _extractSnapshotFromResponse(_result);
4493
+ writeToolLog({
4494
+ tool: name,
4495
+ postId: args?.postId || null,
4496
+ targetType: _detectTargetType(args),
4497
+ snapshotIdProvided: !!args?.snapshotId,
4498
+ snapshotId: args?.snapshotId || null,
4499
+ operationType: _detectOperationType(name, args),
4500
+ responsePath: diffIncluded ? 'diff' : (newSnapshotId ? 'legacy' : 'none'),
4501
+ newSnapshotId,
4502
+ diffIncluded,
4503
+ isError: !!_result?.isError,
4504
+ durationMs: Date.now() - _toolLogStart,
4505
+ });
4427
4506
  }
4507
+ return _result;
4428
4508
  }
4429
4509
  catch (error) {
4430
4510
  const errorMessage = error instanceof Error ? error.message : String(error);
@@ -4435,6 +4515,13 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
4435
4515
  content: errorMessage,
4436
4516
  site: args?.site || 'default',
4437
4517
  });
4518
+ writeToolLog({
4519
+ tool: name,
4520
+ postId: args?.postId || null,
4521
+ targetType: _detectTargetType(args),
4522
+ snapshotIdProvided: !!args?.snapshotId,
4523
+ error: errorMessage,
4524
+ });
4438
4525
  return {
4439
4526
  content: [{
4440
4527
  type: "text",
@@ -4445,6 +4532,53 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
4445
4532
  }
4446
4533
  });
4447
4534
 
4535
+ // ツール呼び出しログ用: ターゲット指定方法を判定
4536
+ function _detectTargetType(args) {
4537
+ if (!args) return null;
4538
+ const t = args.target;
4539
+ if (t) {
4540
+ if (t.ref) return 'ref';
4541
+ 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
+ if (t.section) return 'section';
4546
+ if (t.heading) return 'heading';
4547
+ if (t.blockType) return 'blockType';
4548
+ if (t.selected) return 'selected';
4549
+ }
4550
+ if (args.ref || args.beforeRef || args.afterRef || args.fromRef) return 'ref';
4551
+ 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
+ return null;
4555
+ }
4556
+
4557
+ // ツール呼び出しログ用: 操作タイプを判定
4558
+ function _detectOperationType(toolName, args) {
4559
+ if (toolName === 'delete_block') return 'delete';
4560
+ if (toolName === 'move_block') return 'move';
4561
+ if (toolName === 'duplicate_block') return 'duplicate';
4562
+ if (toolName === 'insert_block') return 'insert';
4563
+ if (toolName === 'table_operations') return `table_${args?.action || 'unknown'}`;
4564
+ // update_blocks
4565
+ if (args?.operations) return 'batch';
4566
+ if (args?.insert || args?._fromInsertBlock) return 'insert';
4567
+ if (args?.newHTML) return 'newHTML';
4568
+ if (args?.replacements?.length > 0) return 'replacements';
4569
+ if (args?.attributeUpdates) return 'attributeUpdates';
4570
+ return 'unknown';
4571
+ }
4572
+
4573
+ // ツール呼び出しログ用: レスポンスから snapshotId を抽出
4574
+ function _extractSnapshotFromResponse(response) {
4575
+ if (!response?.content?.[0]?.text) return { newSnapshotId: null, diffIncluded: false };
4576
+ const text = response.content[0].text;
4577
+ const snapMatch = text.match(/snapshotId: (snap_[a-f0-9]+)/);
4578
+ const diffIncluded = /(?:updated|deleted|inserted|moved|duplicated|expanded|compressed): \[/.test(text);
4579
+ return { newSnapshotId: snapMatch ? snapMatch[1] : null, diffIncluded };
4580
+ }
4581
+
4448
4582
  // サーバー起動
4449
4583
  async function main() {
4450
4584
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "friday-mcp-v2",
3
- "version": "3.0.3",
3
+ "version": "3.0.4",
4
4
  "description": "WordPress MCP Server for Claude Code - REST API direct communication",
5
5
  "type": "module",
6
6
  "main": "dist/mcp-server.js",