@argosvix/mcp-server 0.26.2-alpha.1 → 0.28.0-alpha.1
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/README.md +3 -3
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +694 -1
- package/dist/tools.js.map +1 -1
- package/dist/tools.test.js +213 -16
- package/dist/tools.test.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +2 -2
package/dist/tools.test.js
CHANGED
|
@@ -19,45 +19,56 @@ describe("MCP version source of truth", () => {
|
|
|
19
19
|
});
|
|
20
20
|
});
|
|
21
21
|
describe("MCP tools metadata", () => {
|
|
22
|
-
it("exposes
|
|
22
|
+
it("exposes 71 tools (70 prior + Team read 1 件: list_members。 招待/ロール変更/削除の mutation は承認ゲート経由前提で非提供)", () => {
|
|
23
23
|
expect(tools.map((t) => t.name).sort()).toEqual([
|
|
24
24
|
"acknowledge_alert",
|
|
25
25
|
"aggregate_calls",
|
|
26
|
+
"apply_promo_code_to_customer",
|
|
26
27
|
"auto_silence_noisy_alert",
|
|
27
28
|
"bulk_delete_calls",
|
|
28
29
|
"classify_calls_batch",
|
|
29
30
|
"compare_eval_runs",
|
|
30
31
|
"create_alert",
|
|
31
32
|
"create_annotation",
|
|
33
|
+
"create_budget_gate",
|
|
32
34
|
"create_eval_criterion",
|
|
35
|
+
"create_policy_gate",
|
|
33
36
|
"create_project",
|
|
34
37
|
"create_prompt",
|
|
35
38
|
"create_saved_view",
|
|
36
39
|
"delete_alert",
|
|
37
40
|
"delete_annotation",
|
|
41
|
+
"delete_budget_gate",
|
|
38
42
|
"delete_eval_criterion",
|
|
43
|
+
"delete_policy_gate",
|
|
39
44
|
"delete_project",
|
|
40
45
|
"delete_prompt",
|
|
41
46
|
"delete_saved_view",
|
|
42
47
|
"detect_anomaly",
|
|
43
48
|
"export_calls",
|
|
49
|
+
"extend_customer_trial",
|
|
44
50
|
"get_account_health",
|
|
45
51
|
"get_alert",
|
|
46
52
|
"get_annotation",
|
|
53
|
+
"get_approval",
|
|
54
|
+
"get_budget_gate",
|
|
47
55
|
"get_cost_summary",
|
|
48
56
|
"get_eval_criterion",
|
|
49
57
|
"get_eval_run",
|
|
50
58
|
"get_llm_budget",
|
|
51
59
|
"get_percentiles",
|
|
60
|
+
"get_policy_gate",
|
|
52
61
|
"get_prompt",
|
|
53
62
|
"get_safety_assessment",
|
|
54
63
|
"list_alert_events",
|
|
55
64
|
"list_alerts",
|
|
56
65
|
"list_annotations_by_label",
|
|
57
66
|
"list_annotations_for_call",
|
|
67
|
+
"list_approvals",
|
|
58
68
|
"list_audit_log",
|
|
59
69
|
"list_eval_criteria",
|
|
60
70
|
"list_eval_runs",
|
|
71
|
+
"list_members",
|
|
61
72
|
"list_projects",
|
|
62
73
|
"list_prompts",
|
|
63
74
|
"list_safety_assessments",
|
|
@@ -69,6 +80,7 @@ describe("MCP tools metadata", () => {
|
|
|
69
80
|
"raise_llm_budget",
|
|
70
81
|
"rename_project",
|
|
71
82
|
"rename_prompt",
|
|
83
|
+
"request_approval",
|
|
72
84
|
"retry_failed_webhook",
|
|
73
85
|
"run_eval",
|
|
74
86
|
"silence_alert",
|
|
@@ -76,7 +88,9 @@ describe("MCP tools metadata", () => {
|
|
|
76
88
|
"unsilence_alert",
|
|
77
89
|
"update_alert",
|
|
78
90
|
"update_annotation",
|
|
91
|
+
"update_budget_gate",
|
|
79
92
|
"update_eval_criterion",
|
|
93
|
+
"update_policy_gate",
|
|
80
94
|
"update_prompt",
|
|
81
95
|
]);
|
|
82
96
|
});
|
|
@@ -90,21 +104,22 @@ describe("MCP tools metadata", () => {
|
|
|
90
104
|
it("create_alert.alertType enum matches backend ALERT_TYPES (= enum drift 回帰防止)", () => {
|
|
91
105
|
// backend packages/backend/src/types.ts ALERT_TYPES と完全一致させる。
|
|
92
106
|
// ここが drift すると mismatch した type の create_alert が backend で 400 になる。
|
|
93
|
-
// 2026-06-06
|
|
94
|
-
// していた (= cost_daily / cost_monthly / latency_p95
|
|
95
|
-
//
|
|
96
|
-
//
|
|
97
|
-
//
|
|
98
|
-
// truth
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
"
|
|
106
|
-
|
|
107
|
-
];
|
|
107
|
+
// 2026-06-06 R37 medium #1 fix carry: 旧 hard-coded list は backend と drift
|
|
108
|
+
// していた (= 旧 list: cost_daily / cost_monthly / latency_p95、 backend reality:
|
|
109
|
+
// cost_threshold / monthly_budget / latency_degradation)。 同 hard-code を MCP
|
|
110
|
+
// description から copy していたため drift gate test 自体が drift を検出できない
|
|
111
|
+
// 構造軸だった。 backend `src/alerts/types.ts` の ALERT_TYPES を test 時点で
|
|
112
|
+
// fs.readFileSync で 読み出して regex 抽出 → これで source of truth が backend
|
|
113
|
+
// 側に固定、 backend が enum を 追加すると 本 test が 自動的に拾って fail する。
|
|
114
|
+
const here2 = dirname(fileURLToPath(import.meta.url));
|
|
115
|
+
const backendTypesPath = resolve(here2, "..", "..", "backend", "src", "alerts", "types.ts");
|
|
116
|
+
const backendTypesContent = readFileSync(backendTypesPath, "utf8");
|
|
117
|
+
const arrayMatch = backendTypesContent.match(/export const ALERT_TYPES = \[([\s\S]*?)\] as const;/);
|
|
118
|
+
if (!arrayMatch) {
|
|
119
|
+
throw new Error("ALERT_TYPES export not found in backend/src/alerts/types.ts (= drift gate が source-of-truth を 見失った、 backend の ALERT_TYPES export 形を 確認してください)");
|
|
120
|
+
}
|
|
121
|
+
const BACKEND_ALERT_TYPES = Array.from(arrayMatch[1].matchAll(/"([^"]+)"/g)).map((m) => m[1]);
|
|
122
|
+
expect(BACKEND_ALERT_TYPES.length).toBeGreaterThan(0);
|
|
108
123
|
const createAlert = tools.find((t) => t.name === "create_alert");
|
|
109
124
|
const schema = createAlert?.inputSchema;
|
|
110
125
|
const enumValues = schema?.properties?.alertType?.enum ?? [];
|
|
@@ -166,6 +181,20 @@ describe("dispatchTool", () => {
|
|
|
166
181
|
// 7d 後ろから now まで = 終端より始端が前
|
|
167
182
|
expect(new Date(body.startTime).getTime()).toBeLessThan(new Date(body.endTime).getTime());
|
|
168
183
|
});
|
|
184
|
+
it("R74 HIGH 1 regression: query_calls の latencyMin/Max が outgoing body に乗る (= allowlist 落ち防止)", async () => {
|
|
185
|
+
const res = await dispatchTool({
|
|
186
|
+
name: "query_calls",
|
|
187
|
+
args: { latencyMin: 1500, latencyMax: 2500 },
|
|
188
|
+
apiKey: "argosvix_live_test",
|
|
189
|
+
apiBase: "https://ingest.example.com",
|
|
190
|
+
});
|
|
191
|
+
expect(res.isError).toBeUndefined();
|
|
192
|
+
const fetchMock = global.fetch;
|
|
193
|
+
const init = fetchMock.mock.calls[0]?.[1];
|
|
194
|
+
const body = JSON.parse(init.body);
|
|
195
|
+
expect(body.latencyMin).toBe(1500);
|
|
196
|
+
expect(body.latencyMax).toBe(2500);
|
|
197
|
+
});
|
|
169
198
|
it("get_cost_summary uses /v1/query/aggregate endpoint", async () => {
|
|
170
199
|
const res = await dispatchTool({
|
|
171
200
|
name: "get_cost_summary",
|
|
@@ -177,6 +206,28 @@ describe("dispatchTool", () => {
|
|
|
177
206
|
const parsed = JSON.parse(res.content[0]?.text ?? "{}");
|
|
178
207
|
expect(parsed.groups[0].provider).toBe("openai");
|
|
179
208
|
});
|
|
209
|
+
it("list_members GETs /v1/memberships (= #31 Team read tool)", async () => {
|
|
210
|
+
vi.stubGlobal("fetch", vi.fn(async () => new Response(JSON.stringify({
|
|
211
|
+
members: [
|
|
212
|
+
{ id: "mem_1", email: "a@example.com", role: "admin", status: "active" },
|
|
213
|
+
],
|
|
214
|
+
}), { status: 200, headers: { "content-type": "application/json" } })));
|
|
215
|
+
const res = await dispatchTool({
|
|
216
|
+
name: "list_members",
|
|
217
|
+
args: {},
|
|
218
|
+
apiKey: "argosvix_live_test",
|
|
219
|
+
apiBase: "https://ingest.example.com",
|
|
220
|
+
});
|
|
221
|
+
expect(res.isError).toBeUndefined();
|
|
222
|
+
const fetchMock = global.fetch;
|
|
223
|
+
const fetchedUrl = String(fetchMock.mock.calls[0]?.[0]);
|
|
224
|
+
expect(fetchedUrl).toContain("/v1/memberships");
|
|
225
|
+
// read tool = GET (= 明示 method 指定なし → callApi default GET)
|
|
226
|
+
const init = fetchMock.mock.calls[0]?.[1];
|
|
227
|
+
expect(init?.method ?? "GET").toBe("GET");
|
|
228
|
+
const parsed = JSON.parse(res.content[0]?.text ?? "{}");
|
|
229
|
+
expect(parsed.members[0].role).toBe("admin");
|
|
230
|
+
});
|
|
180
231
|
it("classify_calls_batch POSTs to /v1/safety-assessments/scan-batch with maxRecords", async () => {
|
|
181
232
|
vi.stubGlobal("fetch", vi.fn(async () => new Response(JSON.stringify({ scanned: 10, assessed: 9, flagged: 1, failures: 0, skipped: 1 }), { status: 200 })));
|
|
182
233
|
const res = await dispatchTool({
|
|
@@ -1443,5 +1494,151 @@ describe("dispatchTool", () => {
|
|
|
1443
1494
|
expect(url).toContain("limit=5");
|
|
1444
1495
|
expect(url).not.toContain("account_id");
|
|
1445
1496
|
});
|
|
1497
|
+
// 2026-06-09 v1.7 hygiene #13 carry = v1.5/v1.6 で追加した 7 safety/eval
|
|
1498
|
+
// read tools の path + method 固定 test。 既存は metadata gate のみで dispatch
|
|
1499
|
+
// path/body 軸の structural drift 防御がなかった軸を carry。
|
|
1500
|
+
// R51 LOW carry (2026-06-10): toContain → new URL(...).pathname + exact searchParams
|
|
1501
|
+
// で suffix/prefix drift を 構造的に検出 (= 旧 path = `/v1/eval-runs` が
|
|
1502
|
+
// `/v1/eval-runs/foo` でも pass する弱さを narrow)。
|
|
1503
|
+
function urlOf(call) {
|
|
1504
|
+
return new URL(String(call));
|
|
1505
|
+
}
|
|
1506
|
+
it("list_eval_criteria GETs /v1/eval-criteria", async () => {
|
|
1507
|
+
vi.stubGlobal("fetch", vi.fn(async () => new Response(JSON.stringify({ criteria: [] }), {
|
|
1508
|
+
status: 200,
|
|
1509
|
+
headers: { "content-type": "application/json" },
|
|
1510
|
+
})));
|
|
1511
|
+
await dispatchTool({
|
|
1512
|
+
name: "list_eval_criteria",
|
|
1513
|
+
args: {},
|
|
1514
|
+
apiKey: "argosvix_live_test",
|
|
1515
|
+
apiBase: "https://ingest.example.com",
|
|
1516
|
+
});
|
|
1517
|
+
const fetchMock = global.fetch;
|
|
1518
|
+
const u = urlOf(fetchMock.mock.calls[0]?.[0]);
|
|
1519
|
+
const init = fetchMock.mock.calls[0]?.[1];
|
|
1520
|
+
expect(u.pathname).toBe("/v1/eval-criteria");
|
|
1521
|
+
expect(Array.from(u.searchParams.keys())).toEqual([]);
|
|
1522
|
+
expect(init.method ?? "GET").toBe("GET");
|
|
1523
|
+
});
|
|
1524
|
+
it("get_eval_criterion GETs /v1/eval-criteria/:id", async () => {
|
|
1525
|
+
vi.stubGlobal("fetch", vi.fn(async () => new Response(JSON.stringify({ id: 42, name: "accuracy" }), {
|
|
1526
|
+
status: 200,
|
|
1527
|
+
headers: { "content-type": "application/json" },
|
|
1528
|
+
})));
|
|
1529
|
+
await dispatchTool({
|
|
1530
|
+
name: "get_eval_criterion",
|
|
1531
|
+
args: { criterionId: 42 },
|
|
1532
|
+
apiKey: "argosvix_live_test",
|
|
1533
|
+
apiBase: "https://ingest.example.com",
|
|
1534
|
+
});
|
|
1535
|
+
const fetchMock = global.fetch;
|
|
1536
|
+
const u = urlOf(fetchMock.mock.calls[0]?.[0]);
|
|
1537
|
+
const init = fetchMock.mock.calls[0]?.[1];
|
|
1538
|
+
expect(u.pathname).toBe("/v1/eval-criteria/42");
|
|
1539
|
+
expect(Array.from(u.searchParams.keys())).toEqual([]);
|
|
1540
|
+
expect(init.method ?? "GET").toBe("GET");
|
|
1541
|
+
});
|
|
1542
|
+
it("list_safety_assessments GETs /v1/safety-assessments with call_id + limit", async () => {
|
|
1543
|
+
vi.stubGlobal("fetch", vi.fn(async () => new Response(JSON.stringify({ assessments: [] }), {
|
|
1544
|
+
status: 200,
|
|
1545
|
+
headers: { "content-type": "application/json" },
|
|
1546
|
+
})));
|
|
1547
|
+
await dispatchTool({
|
|
1548
|
+
name: "list_safety_assessments",
|
|
1549
|
+
args: { callId: "call_abc123", limit: 25 },
|
|
1550
|
+
apiKey: "argosvix_live_test",
|
|
1551
|
+
apiBase: "https://ingest.example.com",
|
|
1552
|
+
});
|
|
1553
|
+
const fetchMock = global.fetch;
|
|
1554
|
+
const u = urlOf(fetchMock.mock.calls[0]?.[0]);
|
|
1555
|
+
const init = fetchMock.mock.calls[0]?.[1];
|
|
1556
|
+
expect(u.pathname).toBe("/v1/safety-assessments");
|
|
1557
|
+
expect(u.searchParams.get("call_id")).toBe("call_abc123");
|
|
1558
|
+
expect(u.searchParams.get("limit")).toBe("25");
|
|
1559
|
+
expect(init.method ?? "GET").toBe("GET");
|
|
1560
|
+
});
|
|
1561
|
+
it("get_safety_assessment GETs /v1/safety-assessments/:id", async () => {
|
|
1562
|
+
vi.stubGlobal("fetch", vi.fn(async () => new Response(JSON.stringify({ id: 7, flagged: 0 }), {
|
|
1563
|
+
status: 200,
|
|
1564
|
+
headers: { "content-type": "application/json" },
|
|
1565
|
+
})));
|
|
1566
|
+
await dispatchTool({
|
|
1567
|
+
name: "get_safety_assessment",
|
|
1568
|
+
args: { assessmentId: 7 },
|
|
1569
|
+
apiKey: "argosvix_live_test",
|
|
1570
|
+
apiBase: "https://ingest.example.com",
|
|
1571
|
+
});
|
|
1572
|
+
const fetchMock = global.fetch;
|
|
1573
|
+
const u = urlOf(fetchMock.mock.calls[0]?.[0]);
|
|
1574
|
+
const init = fetchMock.mock.calls[0]?.[1];
|
|
1575
|
+
expect(u.pathname).toBe("/v1/safety-assessments/7");
|
|
1576
|
+
expect(Array.from(u.searchParams.keys())).toEqual([]);
|
|
1577
|
+
expect(init.method ?? "GET").toBe("GET");
|
|
1578
|
+
});
|
|
1579
|
+
it("list_eval_runs GETs /v1/eval-runs with limit", async () => {
|
|
1580
|
+
vi.stubGlobal("fetch", vi.fn(async () => new Response(JSON.stringify({ runs: [] }), {
|
|
1581
|
+
status: 200,
|
|
1582
|
+
headers: { "content-type": "application/json" },
|
|
1583
|
+
})));
|
|
1584
|
+
await dispatchTool({
|
|
1585
|
+
name: "list_eval_runs",
|
|
1586
|
+
args: { limit: 15 },
|
|
1587
|
+
apiKey: "argosvix_live_test",
|
|
1588
|
+
apiBase: "https://ingest.example.com",
|
|
1589
|
+
});
|
|
1590
|
+
const fetchMock = global.fetch;
|
|
1591
|
+
const u = urlOf(fetchMock.mock.calls[0]?.[0]);
|
|
1592
|
+
const init = fetchMock.mock.calls[0]?.[1];
|
|
1593
|
+
expect(u.pathname).toBe("/v1/eval-runs");
|
|
1594
|
+
expect(u.searchParams.get("limit")).toBe("15");
|
|
1595
|
+
expect(init.method ?? "GET").toBe("GET");
|
|
1596
|
+
});
|
|
1597
|
+
it("get_eval_run GETs /v1/eval-runs/:id", async () => {
|
|
1598
|
+
vi.stubGlobal("fetch", vi.fn(async () => new Response(JSON.stringify({ id: 99, status: "completed" }), {
|
|
1599
|
+
status: 200,
|
|
1600
|
+
headers: { "content-type": "application/json" },
|
|
1601
|
+
})));
|
|
1602
|
+
await dispatchTool({
|
|
1603
|
+
name: "get_eval_run",
|
|
1604
|
+
args: { runId: 99 },
|
|
1605
|
+
apiKey: "argosvix_live_test",
|
|
1606
|
+
apiBase: "https://ingest.example.com",
|
|
1607
|
+
});
|
|
1608
|
+
const fetchMock = global.fetch;
|
|
1609
|
+
const u = urlOf(fetchMock.mock.calls[0]?.[0]);
|
|
1610
|
+
const init = fetchMock.mock.calls[0]?.[1];
|
|
1611
|
+
expect(u.pathname).toBe("/v1/eval-runs/99");
|
|
1612
|
+
expect(Array.from(u.searchParams.keys())).toEqual([]);
|
|
1613
|
+
expect(init.method ?? "GET").toBe("GET");
|
|
1614
|
+
});
|
|
1615
|
+
it("run_eval POSTs to /v1/eval-runs with name + recentCount + idempotencyKey", async () => {
|
|
1616
|
+
vi.stubGlobal("fetch", vi.fn(async () => new Response(JSON.stringify({ id: 100, status: "queued" }), {
|
|
1617
|
+
status: 200,
|
|
1618
|
+
headers: { "content-type": "application/json" },
|
|
1619
|
+
})));
|
|
1620
|
+
await dispatchTool({
|
|
1621
|
+
name: "run_eval",
|
|
1622
|
+
args: {
|
|
1623
|
+
name: "baseline-2026-06-09",
|
|
1624
|
+
recentCount: 30,
|
|
1625
|
+
idempotencyKey: "idem-abc",
|
|
1626
|
+
},
|
|
1627
|
+
apiKey: "argosvix_live_test",
|
|
1628
|
+
apiBase: "https://ingest.example.com",
|
|
1629
|
+
});
|
|
1630
|
+
const fetchMock = global.fetch;
|
|
1631
|
+
const u = urlOf(fetchMock.mock.calls[0]?.[0]);
|
|
1632
|
+
const init = fetchMock.mock.calls[0]?.[1];
|
|
1633
|
+
expect(u.pathname).toBe("/v1/eval-runs");
|
|
1634
|
+
expect(Array.from(u.searchParams.keys())).toEqual([]);
|
|
1635
|
+
expect(init.method).toBe("POST");
|
|
1636
|
+
const body = JSON.parse(init.body);
|
|
1637
|
+
expect(body).toEqual({
|
|
1638
|
+
name: "baseline-2026-06-09",
|
|
1639
|
+
recentCount: 30,
|
|
1640
|
+
idempotencyKey: "idem-abc",
|
|
1641
|
+
});
|
|
1642
|
+
});
|
|
1446
1643
|
});
|
|
1447
1644
|
//# sourceMappingURL=tools.test.js.map
|