/**
* TelosDB MCP クライアントテスト。
* 前提: MCP が 127.0.0.1:3001 で起動していること(npm run test-and-heal または test-and-heal:pro で起動+実行される)。
*
* テスト内容:
* [1] tools/list
* [2] search_text
* [3] Pro: スコアが全件 0.4 でないこと(警告のみの緩いチェック)
* [4] Pro: ベクトル化 E2E — RE-INDEX 実行 → indexing_status が idle になるまで待機 → 検索 → vector_search_used が true かつスコアが全件 0.4 でないことを検証(ここで失敗すればベクトル化が効いていない)
*/
import axios from 'axios';
const API_BASE = "http://127.0.0.1:3001";
async function postMessage(axiosInstance, method, params = {}) {
const res = await axiosInstance.post("/messages", {
jsonrpc: "2.0",
method: method,
params: params,
id: Date.now()
});
return res.data;
}
/**
* Pro 時: 複数ヒットで全件スコア 0.4 なら LIKE フォールバックのみ=近似近傍が動いていない欠陥を検出する。
* strict が true のときは失敗とする。false のときは警告のみ(既存データに依存するため)。
*/
async function assertProVectorScoresNotAllFallback(axiosInstance, strict = false) {
const editionRes = await axiosInstance.get("/edition");
if (editionRes.data?.edition !== "pro") return;
const searchResult = await postMessage(axiosInstance, "tools/call", {
name: "search_text",
arguments: { content: "文", limit: 5 }
});
const raw = searchResult.result?.content?.[0]?.text;
if (!raw) throw new Error("Pro vector check: no search response");
let parsed;
try {
parsed = JSON.parse(raw);
} catch {
throw new Error("Pro vector check: search result is not JSON");
}
const items = Array.isArray(parsed) ? parsed : (parsed?.items ?? []);
if (!Array.isArray(items) || items.length < 2) {
if (strict) throw new Error("Pro vector check: need at least 2 docs to assert scores");
return;
}
const likeFallbackScore = 0.4;
const allFallback = items.every(
(it) => Math.abs((it.similarity ?? 0) - likeFallbackScore) < 1e-5
);
if (allFallback) {
const msg = "Pro: 全件スコア 0.4(キーワード一致のみ)。RE-INDEX でベクトル化してください。";
if (strict) throw new Error(msg);
console.warn(" Pro vector check:", msg);
return;
}
console.log(" Pro vector check: at least one score != 0.4 (OK)");
}
/**
* Pro ベクトル化 E2E: RE-INDEX 実行 → 完了待ち → 検索で vector_search_used とスコアを検証。
* ベクトル化が正しく動いていることをテストする。
*/
async function testProVectorizationE2E(axiosInstance) {
const editionRes = await axiosInstance.get("/edition");
if (editionRes.data?.edition !== "pro") {
console.log(" Pro vectorization E2E: skip (not Pro)");
return;
}
console.log(" Pro vectorization E2E: calling RE-INDEX (lsa_retrain)...");
await postMessage(axiosInstance, "lsa_retrain", {});
const timeoutMs = 120_000;
const pollMs = 2_000;
const deadline = Date.now() + timeoutMs;
let status = "";
while (Date.now() < deadline) {
const r = await axiosInstance.get("/indexing_status");
status = (r.data && r.data.status) ? r.data.status : String(r.data ?? "");
if (status === "idle") break;
await new Promise((res) => setTimeout(res, pollMs));
}
if (status !== "idle") {
throw new Error(`Pro vectorization E2E: RE-INDEX did not become idle within ${timeoutMs / 1000}s (status=${status})`);
}
console.log(" Pro vectorization E2E: RE-INDEX done, searching...");
const searchResult = await postMessage(axiosInstance, "tools/call", {
name: "search_text",
arguments: { content: "文", limit: 5 }
});
const raw = searchResult.result?.content?.[0]?.text;
if (!raw) throw new Error("Pro vectorization E2E: no search response");
let parsed;
try {
parsed = JSON.parse(raw);
} catch {
throw new Error("Pro vectorization E2E: search result is not JSON");
}
const items = Array.isArray(parsed) ? parsed : (parsed?.items ?? []);
const vectorSearchUsed = parsed?.vector_search_used === true;
if (!vectorSearchUsed && items.length > 0) {
throw new Error(
"Pro vectorization E2E: vector_search_used が false。ベクトル化が反映されていません(vec_items/HNSW を確認してください)。"
);
}
const likeFallbackScore = 0.4;
const allFallback = items.length >= 2 && items.every(
(it) => Math.abs((it.similarity ?? 0) - likeFallbackScore) < 1e-5
);
if (allFallback) {
throw new Error(
"Pro vectorization E2E: RE-INDEX 後も全件スコア 0.4。ベクトル検索が効いていません。"
);
}
console.log(" Pro vectorization E2E: vector_search_used=true, スコア差あり (OK)");
}
async function testMcp() {
console.log("=== TelosDB MCP Tool Test ===");
const axiosInstance = axios.create({ baseURL: API_BASE });
try {
console.log("\n[1] Listing tools...");
const toolsResult = await postMessage(axiosInstance, "tools/list");
console.log(" Success: Found", toolsResult.result?.tools?.length, "tools.");
console.log("\n[2] Testing search_text...");
const searchResult = await postMessage(axiosInstance, "tools/call", {
name: "search_text",
arguments: { content: "宝くじ", limit: 5 }
});
const content = searchResult.result?.content?.[0]?.text;
console.log(" Result:\n", content || " No results");
console.log("\n[3] Pro: assert vector search contributes (not all scores 0.4)...");
await assertProVectorScoresNotAllFallback(axiosInstance);
console.log("\n[4] Pro: vectorization E2E (RE-INDEX → 完了待ち → 検索で vector_search_used 検証)...");
await testProVectorizationE2E(axiosInstance);
console.log("\n=== Test Finished ===");
} catch (e) {
console.error("\nTest failed:", e.message || e);
process.exit(1);
}
}
testMcp();