friday-mcp-v2 3.0.2 → 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 = '';
@@ -1166,13 +1222,6 @@ function appendSnapshotToTextLegacy(text, snapshot, refInfo) {
1166
1222
  }
1167
1223
 
1168
1224
  let out = text + `\n\n[snapshot:${snapshot.snapshotId} rev:${snapshot.revision}]${expandedLine}`;
1169
- if (snapshot.blocks && snapshot.blocks.length > 0) {
1170
- out += '\nblocks:';
1171
- for (const b of snapshot.blocks) {
1172
- const parent = b.parentIndex !== null ? `,p:${b.parentIndex}` : '';
1173
- out += `\n [${b.index}|${b.ref}] ${b.type} (d:${b.depth}${parent})`;
1174
- }
1175
- }
1176
1225
  return out;
1177
1226
  }
1178
1227
 
@@ -1189,9 +1238,12 @@ function appendSnapshotToTextLegacy(text, snapshot, refInfo) {
1189
1238
  * @returns {string}
1190
1239
  */
1191
1240
  function buildUpdateDiffResponse(text, snap, result, preState, inputRef, isInsert, refInfo) {
1241
+ const _debug = process.env.FRIDAY_DEBUG === '1';
1192
1242
  if (!inputRef || !snap) {
1243
+ if (_debug) console.error(`[SNAPSHOT] diffResponse: legacy path (inputRef=${inputRef || 'null'}, snap=${snap ? 'ok' : 'null'})`);
1193
1244
  return appendSnapshotToTextLegacy(text, snap, refInfo);
1194
1245
  }
1246
+ if (_debug) console.error(`[SNAPSHOT] diffResponse: diff path (inputRef=${inputRef}, snapId=${snap?.snapshotId})`);
1195
1247
 
1196
1248
  if (isInsert) {
1197
1249
  const changeInfo = buildChangeInfoFromResult('inserted', snap, result, preState);
@@ -1294,6 +1346,7 @@ function buildUpdateDiffResponse(text, snap, result, preState, inputRef, isInser
1294
1346
  */
1295
1347
  function appendRefChangesToText(text, changeInfo) {
1296
1348
  if (!changeInfo) return text;
1349
+ if (process.env.FRIDAY_DEBUG === '1') console.error(`[SNAPSHOT] appendDiff: type=${changeInfo.type}, snapshotId=${changeInfo.snapshotId}`);
1297
1350
 
1298
1351
  let out = text + `\n\n[snapshot:${changeInfo.snapshotId} rev:${changeInfo.revision}]`;
1299
1352
 
@@ -2823,10 +2876,12 @@ async function handleUpdateBlocksTool(args, toolName) {
2823
2876
  }
2824
2877
 
2825
2878
  // ツール実行のハンドラ
2879
+ const _WRITE_TOOLS = new Set(['update_blocks', 'delete_block', 'move_block', 'duplicate_block', 'insert_block', 'table_operations']);
2826
2880
  server.setRequestHandler(CallToolRequestSchema, async (request) => {
2827
2881
  const { name, arguments: args } = request.params;
2882
+ const _toolLogStart = Date.now();
2828
2883
  try {
2829
- switch (name) {
2884
+ const _result = await (async () => { switch (name) {
2830
2885
  case "get_article_structure": {
2831
2886
  let { mode, postId: _postId, sessionId: _sessionId, message, client, siteName } = await resolveMode(args, name);
2832
2887
  if (mode === 'error') {
@@ -4431,7 +4486,25 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
4431
4486
 
4432
4487
  default:
4433
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
+ });
4434
4506
  }
4507
+ return _result;
4435
4508
  }
4436
4509
  catch (error) {
4437
4510
  const errorMessage = error instanceof Error ? error.message : String(error);
@@ -4442,6 +4515,13 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
4442
4515
  content: errorMessage,
4443
4516
  site: args?.site || 'default',
4444
4517
  });
4518
+ writeToolLog({
4519
+ tool: name,
4520
+ postId: args?.postId || null,
4521
+ targetType: _detectTargetType(args),
4522
+ snapshotIdProvided: !!args?.snapshotId,
4523
+ error: errorMessage,
4524
+ });
4445
4525
  return {
4446
4526
  content: [{
4447
4527
  type: "text",
@@ -4452,6 +4532,53 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
4452
4532
  }
4453
4533
  });
4454
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
+
4455
4582
  // サーバー起動
4456
4583
  async function main() {
4457
4584
  try {
package/dist/ws-server.js CHANGED
@@ -42,7 +42,7 @@ export class FridayWSServer {
42
42
  throw err;
43
43
  }
44
44
  this._startHeartbeatCheck();
45
- console.error(`[F.R.I.D.A.Y][WS] Token: ${this.token.slice(0, 8)}...`);
45
+ dbg(`Token: ${this.token.slice(0, 8)}...`);
46
46
  }
47
47
 
48
48
  stop() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "friday-mcp-v2",
3
- "version": "3.0.2",
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",