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.
- package/dist/bridge-common.js +8 -0
- package/dist/mcp-server.js +139 -12
- package/dist/ws-server.js +1 -1
- 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
|
|
|
@@ -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)
|
|
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
|
-
|
|
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