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.
- package/dist/bridge-common.js +8 -0
- package/dist/mcp-server.js +139 -5
- 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 = '';
|
|
@@ -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 {
|