@impart-security/impart-mcp 0.1.3 → 0.2.0
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 +76 -35
- package/dist/index.js +1030 -745
- package/dist/test/integration.test.d.ts +1 -0
- package/dist/test/integration.test.js +262 -0
- package/dist/test/rule-recipe.test.d.ts +1 -0
- package/dist/test/rule-recipe.test.js +278 -0
- package/dist/test/smoke.test.d.ts +1 -0
- package/dist/test/smoke.test.js +37 -0
- package/dist/types.d.ts +1588 -86
- package/dist/types.js +127 -0
- package/package.json +20 -12
- package/dist/client.d.ts +0 -4
- package/dist/client.js +0 -38
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const node_test_1 = require("node:test");
|
|
7
|
+
const node_assert_1 = __importDefault(require("node:assert"));
|
|
8
|
+
const node_child_process_1 = require("node:child_process");
|
|
9
|
+
// Skip integration tests if IMPART_AUTH_TOKEN is not set
|
|
10
|
+
if (!process.env["IMPART_AUTH_TOKEN"]) {
|
|
11
|
+
console.log("⊘ Integration tests skipped (IMPART_AUTH_TOKEN not set)");
|
|
12
|
+
process.exit(0);
|
|
13
|
+
}
|
|
14
|
+
const config_js_1 = require("../config.js");
|
|
15
|
+
class MCPTestClient {
|
|
16
|
+
process;
|
|
17
|
+
requestId = 0;
|
|
18
|
+
responses = new Map();
|
|
19
|
+
buffer = "";
|
|
20
|
+
constructor() {
|
|
21
|
+
this.process = (0, node_child_process_1.spawn)("node", ["dist/index.js"], {
|
|
22
|
+
env: {
|
|
23
|
+
...process.env,
|
|
24
|
+
IMPART_AUTH_TOKEN: config_js_1.config.authToken,
|
|
25
|
+
IMPART_API_BASE: config_js_1.config.apiBase,
|
|
26
|
+
},
|
|
27
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
28
|
+
});
|
|
29
|
+
this.process.stdout?.on("data", (data) => {
|
|
30
|
+
this.buffer += data.toString();
|
|
31
|
+
const lines = this.buffer.split("\n");
|
|
32
|
+
this.buffer = lines.pop() || "";
|
|
33
|
+
for (const line of lines) {
|
|
34
|
+
if (line.trim()) {
|
|
35
|
+
try {
|
|
36
|
+
const response = JSON.parse(line);
|
|
37
|
+
if (response.id !== undefined) {
|
|
38
|
+
this.responses.set(response.id, response);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
catch (e) {
|
|
42
|
+
// Ignore non-JSON lines (like server logs)
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
async initialize() {
|
|
49
|
+
const response = await this.sendRequest("initialize", {
|
|
50
|
+
protocolVersion: "2024-11-05",
|
|
51
|
+
capabilities: {},
|
|
52
|
+
clientInfo: {
|
|
53
|
+
name: "test-client",
|
|
54
|
+
version: "1.0.0",
|
|
55
|
+
},
|
|
56
|
+
});
|
|
57
|
+
node_assert_1.default.ok(response.result, "Initialize should succeed");
|
|
58
|
+
}
|
|
59
|
+
async listTools() {
|
|
60
|
+
const response = await this.sendRequest("tools/list");
|
|
61
|
+
if (!response.result) {
|
|
62
|
+
throw new Error("List tools returned no result");
|
|
63
|
+
}
|
|
64
|
+
return response.result;
|
|
65
|
+
}
|
|
66
|
+
async callTool(name, args = {}) {
|
|
67
|
+
const response = await this.sendRequest("tools/call", {
|
|
68
|
+
name,
|
|
69
|
+
arguments: args,
|
|
70
|
+
});
|
|
71
|
+
if (response.error) {
|
|
72
|
+
throw new Error(`Tool call failed: ${response.error.message}`);
|
|
73
|
+
}
|
|
74
|
+
if (!response.result) {
|
|
75
|
+
throw new Error("Tool call returned no result");
|
|
76
|
+
}
|
|
77
|
+
return response.result;
|
|
78
|
+
}
|
|
79
|
+
async sendRequest(method, params) {
|
|
80
|
+
const id = ++this.requestId;
|
|
81
|
+
const request = {
|
|
82
|
+
jsonrpc: "2.0",
|
|
83
|
+
id,
|
|
84
|
+
method,
|
|
85
|
+
...(params && { params }),
|
|
86
|
+
};
|
|
87
|
+
this.process.stdin?.write(JSON.stringify(request) + "\n");
|
|
88
|
+
// Wait for response (with timeout)
|
|
89
|
+
return new Promise((resolve, reject) => {
|
|
90
|
+
const timeout = setTimeout(() => {
|
|
91
|
+
reject(new Error(`Request ${id} timed out`));
|
|
92
|
+
}, 30000);
|
|
93
|
+
const checkResponse = setInterval(() => {
|
|
94
|
+
const response = this.responses.get(id);
|
|
95
|
+
if (response) {
|
|
96
|
+
clearInterval(checkResponse);
|
|
97
|
+
clearTimeout(timeout);
|
|
98
|
+
this.responses.delete(id);
|
|
99
|
+
resolve(response);
|
|
100
|
+
}
|
|
101
|
+
}, 100);
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
close() {
|
|
105
|
+
this.process.kill();
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
(0, node_test_1.describe)("MCP Server Integration Tests", () => {
|
|
109
|
+
let client;
|
|
110
|
+
let toolNames = [];
|
|
111
|
+
(0, node_test_1.before)(async () => {
|
|
112
|
+
if (!process.env["IMPART_AUTH_TOKEN"]) {
|
|
113
|
+
throw new Error("IMPART_AUTH_TOKEN environment variable is required for integration tests");
|
|
114
|
+
}
|
|
115
|
+
client = new MCPTestClient();
|
|
116
|
+
await client.initialize();
|
|
117
|
+
const { tools } = await client.listTools();
|
|
118
|
+
toolNames = tools.map((t) => t.name);
|
|
119
|
+
console.log(`\nFound ${tools.length} tools to test`);
|
|
120
|
+
});
|
|
121
|
+
(0, node_test_1.describe)("Tool Discovery", () => {
|
|
122
|
+
(0, node_test_1.it)("should list all 26 tools", async () => {
|
|
123
|
+
node_assert_1.default.strictEqual(toolNames.length, 26, "Should have exactly 26 tools");
|
|
124
|
+
});
|
|
125
|
+
(0, node_test_1.it)("should have properly named tools with org ID suffix", async () => {
|
|
126
|
+
const expectedTools = [
|
|
127
|
+
"list_inspectors",
|
|
128
|
+
"list_rules_scripts",
|
|
129
|
+
"get_rules_script",
|
|
130
|
+
"list_rule_recipes",
|
|
131
|
+
"get_rule_recipe",
|
|
132
|
+
"generate_rule_recipe",
|
|
133
|
+
"create_rule_recipe",
|
|
134
|
+
"list_endpoints",
|
|
135
|
+
"list_tags",
|
|
136
|
+
"get_tag_metrics",
|
|
137
|
+
"list_observed_hosts",
|
|
138
|
+
"get_observed_host",
|
|
139
|
+
"list_specs",
|
|
140
|
+
"get_spec",
|
|
141
|
+
"list_api_bindings",
|
|
142
|
+
"create_api_binding",
|
|
143
|
+
"list_lists",
|
|
144
|
+
"get_list_items",
|
|
145
|
+
"create_list",
|
|
146
|
+
"update_list_items",
|
|
147
|
+
"create_label",
|
|
148
|
+
"list_labels",
|
|
149
|
+
"list_test_cases",
|
|
150
|
+
"get_test_case",
|
|
151
|
+
"get_requests",
|
|
152
|
+
"get_request_details",
|
|
153
|
+
];
|
|
154
|
+
for (const expectedTool of expectedTools) {
|
|
155
|
+
const found = toolNames.some((name) => name.startsWith(expectedTool + "_"));
|
|
156
|
+
node_assert_1.default.ok(found, `Should have tool starting with ${expectedTool}`);
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
(0, node_test_1.describe)("Inspector Tools", () => {
|
|
161
|
+
(0, node_test_1.it)("should list inspectors", async () => {
|
|
162
|
+
const toolName = toolNames.find((name) => name.startsWith("list_inspectors_"));
|
|
163
|
+
node_assert_1.default.ok(toolName, "Should have list_inspectors tool");
|
|
164
|
+
const result = await client.callTool(toolName, { page: 1, max_results: 10 });
|
|
165
|
+
node_assert_1.default.ok(result, "Should return result");
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
(0, node_test_1.describe)("Rules Tools", () => {
|
|
169
|
+
(0, node_test_1.it)("should list rules scripts", async () => {
|
|
170
|
+
const toolName = toolNames.find((name) => name.startsWith("list_rules_scripts_"));
|
|
171
|
+
node_assert_1.default.ok(toolName, "Should have list_rules_scripts tool");
|
|
172
|
+
const result = await client.callTool(toolName, { page: 1, max_results: 5 });
|
|
173
|
+
node_assert_1.default.ok(result, "Should return result");
|
|
174
|
+
});
|
|
175
|
+
(0, node_test_1.it)("should list rule recipes", async () => {
|
|
176
|
+
const toolName = toolNames.find((name) => name.startsWith("list_rule_recipes_"));
|
|
177
|
+
node_assert_1.default.ok(toolName, "Should have list_rule_recipes tool");
|
|
178
|
+
const result = await client.callTool(toolName, { page: 1, max_results: 5 });
|
|
179
|
+
node_assert_1.default.ok(result, "Should return result");
|
|
180
|
+
});
|
|
181
|
+
});
|
|
182
|
+
(0, node_test_1.describe)("API Inventory Tools", () => {
|
|
183
|
+
(0, node_test_1.it)("should list endpoints", async () => {
|
|
184
|
+
const toolName = toolNames.find((name) => name.startsWith("list_endpoints_"));
|
|
185
|
+
node_assert_1.default.ok(toolName, "Should have list_endpoints tool");
|
|
186
|
+
const result = await client.callTool(toolName, { page: 1, max_results: 10 });
|
|
187
|
+
node_assert_1.default.ok(result, "Should return result");
|
|
188
|
+
});
|
|
189
|
+
(0, node_test_1.it)("should list specs", async () => {
|
|
190
|
+
const toolName = toolNames.find((name) => name.startsWith("list_specs_"));
|
|
191
|
+
node_assert_1.default.ok(toolName, "Should have list_specs tool");
|
|
192
|
+
const result = await client.callTool(toolName, { page: 1, max_results: 10 });
|
|
193
|
+
node_assert_1.default.ok(result, "Should return result");
|
|
194
|
+
});
|
|
195
|
+
(0, node_test_1.it)("should list api bindings", async () => {
|
|
196
|
+
const toolName = toolNames.find((name) => name.startsWith("list_api_bindings_"));
|
|
197
|
+
node_assert_1.default.ok(toolName, "Should have list_api_bindings tool");
|
|
198
|
+
const result = await client.callTool(toolName, { page: 1, max_results: 10 });
|
|
199
|
+
node_assert_1.default.ok(result, "Should return result");
|
|
200
|
+
});
|
|
201
|
+
(0, node_test_1.it)("should list observed hosts", async () => {
|
|
202
|
+
const toolName = toolNames.find((name) => name.startsWith("list_observed_hosts_"));
|
|
203
|
+
node_assert_1.default.ok(toolName, "Should have list_observed_hosts tool");
|
|
204
|
+
const result = await client.callTool(toolName, { page: 1, max_results: 10 });
|
|
205
|
+
node_assert_1.default.ok(result, "Should return result");
|
|
206
|
+
});
|
|
207
|
+
});
|
|
208
|
+
(0, node_test_1.describe)("Analytics Tools", () => {
|
|
209
|
+
(0, node_test_1.it)("should get tag metrics", async () => {
|
|
210
|
+
const toolName = toolNames.find((name) => name.startsWith("get_tag_metrics_"));
|
|
211
|
+
node_assert_1.default.ok(toolName, "Should have get_tag_metrics tool");
|
|
212
|
+
const result = await client.callTool(toolName, {
|
|
213
|
+
metric: ["http-request.count"],
|
|
214
|
+
from: "-24h",
|
|
215
|
+
until: "-0h",
|
|
216
|
+
max_results: 10,
|
|
217
|
+
});
|
|
218
|
+
node_assert_1.default.ok(result, "Should return result");
|
|
219
|
+
});
|
|
220
|
+
(0, node_test_1.it)("should get requests", async () => {
|
|
221
|
+
const toolName = toolNames.find((name) => name.startsWith("get_requests_"));
|
|
222
|
+
node_assert_1.default.ok(toolName, "Should have get_requests tool");
|
|
223
|
+
const result = await client.callTool(toolName, {
|
|
224
|
+
from: "-1d",
|
|
225
|
+
max_results: 10,
|
|
226
|
+
});
|
|
227
|
+
node_assert_1.default.ok(result, "Should return result");
|
|
228
|
+
});
|
|
229
|
+
});
|
|
230
|
+
(0, node_test_1.describe)("Organization Tools", () => {
|
|
231
|
+
(0, node_test_1.it)("should list tags", async () => {
|
|
232
|
+
const toolName = toolNames.find((name) => name.startsWith("list_tags_"));
|
|
233
|
+
node_assert_1.default.ok(toolName, "Should have list_tags tool");
|
|
234
|
+
const result = await client.callTool(toolName, { page: 1, max_results: 10 });
|
|
235
|
+
node_assert_1.default.ok(result, "Should return result");
|
|
236
|
+
});
|
|
237
|
+
(0, node_test_1.it)("should list labels", async () => {
|
|
238
|
+
const toolName = toolNames.find((name) => name.startsWith("list_labels_"));
|
|
239
|
+
node_assert_1.default.ok(toolName, "Should have list_labels tool");
|
|
240
|
+
const result = await client.callTool(toolName, { page: 1, max_results: 10 });
|
|
241
|
+
node_assert_1.default.ok(result, "Should return result");
|
|
242
|
+
});
|
|
243
|
+
(0, node_test_1.it)("should list lists", async () => {
|
|
244
|
+
const toolName = toolNames.find((name) => name.startsWith("list_lists_"));
|
|
245
|
+
node_assert_1.default.ok(toolName, "Should have list_lists tool");
|
|
246
|
+
const result = await client.callTool(toolName, { page: 1, max_results: 10 });
|
|
247
|
+
node_assert_1.default.ok(result, "Should return result");
|
|
248
|
+
});
|
|
249
|
+
(0, node_test_1.it)("should list test cases", async () => {
|
|
250
|
+
const toolName = toolNames.find((name) => name.startsWith("list_test_cases_"));
|
|
251
|
+
node_assert_1.default.ok(toolName, "Should have list_test_cases tool");
|
|
252
|
+
const result = await client.callTool(toolName, { page: 1, max_results: 10 });
|
|
253
|
+
node_assert_1.default.ok(result, "Should return result");
|
|
254
|
+
});
|
|
255
|
+
});
|
|
256
|
+
// Cleanup after all tests
|
|
257
|
+
process.on("exit", () => {
|
|
258
|
+
if (client) {
|
|
259
|
+
client.close();
|
|
260
|
+
}
|
|
261
|
+
});
|
|
262
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const node_test_1 = require("node:test");
|
|
7
|
+
const node_assert_1 = __importDefault(require("node:assert"));
|
|
8
|
+
const node_child_process_1 = require("node:child_process");
|
|
9
|
+
const config_js_1 = require("../config.js");
|
|
10
|
+
class MCPTestClient {
|
|
11
|
+
process;
|
|
12
|
+
requestId = 0;
|
|
13
|
+
responses = new Map();
|
|
14
|
+
buffer = "";
|
|
15
|
+
constructor() {
|
|
16
|
+
this.process = (0, node_child_process_1.spawn)("node", ["dist/index.js"], {
|
|
17
|
+
env: {
|
|
18
|
+
...process.env,
|
|
19
|
+
IMPART_AUTH_TOKEN: config_js_1.config.authToken,
|
|
20
|
+
IMPART_API_BASE: config_js_1.config.apiBase,
|
|
21
|
+
},
|
|
22
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
23
|
+
});
|
|
24
|
+
this.process.stdout?.on("data", (data) => {
|
|
25
|
+
this.buffer += data.toString();
|
|
26
|
+
const lines = this.buffer.split("\n");
|
|
27
|
+
this.buffer = lines.pop() || "";
|
|
28
|
+
for (const line of lines) {
|
|
29
|
+
if (line.trim()) {
|
|
30
|
+
try {
|
|
31
|
+
const response = JSON.parse(line);
|
|
32
|
+
if (response.id !== undefined) {
|
|
33
|
+
this.responses.set(response.id, response);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
catch (e) {
|
|
37
|
+
// Ignore non-JSON lines (like server logs)
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
this.process.stderr?.on("data", (_data) => {
|
|
43
|
+
// Optional: log stderr for debugging
|
|
44
|
+
// console.error("Server stderr:", _data.toString());
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
async initialize() {
|
|
48
|
+
const response = await this.sendRequest("initialize", {
|
|
49
|
+
protocolVersion: "2024-11-05",
|
|
50
|
+
capabilities: {},
|
|
51
|
+
clientInfo: {
|
|
52
|
+
name: "test-client",
|
|
53
|
+
version: "1.0.0",
|
|
54
|
+
},
|
|
55
|
+
});
|
|
56
|
+
node_assert_1.default.ok(response.result, "Initialize should succeed");
|
|
57
|
+
}
|
|
58
|
+
async callTool(name, args = {}) {
|
|
59
|
+
const response = await this.sendRequest("tools/call", {
|
|
60
|
+
name,
|
|
61
|
+
arguments: args,
|
|
62
|
+
});
|
|
63
|
+
if (response.error) {
|
|
64
|
+
throw new Error(`Tool call failed: ${response.error.message}`);
|
|
65
|
+
}
|
|
66
|
+
if (!response.result) {
|
|
67
|
+
throw new Error("Tool call returned no result");
|
|
68
|
+
}
|
|
69
|
+
return response.result;
|
|
70
|
+
}
|
|
71
|
+
async sendRequest(method, params) {
|
|
72
|
+
const id = ++this.requestId;
|
|
73
|
+
const request = {
|
|
74
|
+
jsonrpc: "2.0",
|
|
75
|
+
id,
|
|
76
|
+
method,
|
|
77
|
+
...(params && { params }),
|
|
78
|
+
};
|
|
79
|
+
this.process.stdin?.write(JSON.stringify(request) + "\n");
|
|
80
|
+
// Wait for response (with timeout)
|
|
81
|
+
return new Promise((resolve, reject) => {
|
|
82
|
+
const timeout = setTimeout(() => {
|
|
83
|
+
reject(new Error(`Request ${id} timed out after 60s`));
|
|
84
|
+
}, 60000); // 60s timeout for Sparkle AI
|
|
85
|
+
const checkResponse = setInterval(() => {
|
|
86
|
+
const response = this.responses.get(id);
|
|
87
|
+
if (response) {
|
|
88
|
+
clearInterval(checkResponse);
|
|
89
|
+
clearTimeout(timeout);
|
|
90
|
+
this.responses.delete(id);
|
|
91
|
+
resolve(response);
|
|
92
|
+
}
|
|
93
|
+
}, 100);
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
close() {
|
|
97
|
+
this.process.kill();
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
(0, node_test_1.describe)("Rule Recipe Tools - Critical Fixes", () => {
|
|
101
|
+
let client;
|
|
102
|
+
let generateToolName;
|
|
103
|
+
let createToolName;
|
|
104
|
+
let listToolName;
|
|
105
|
+
let generatedComponents;
|
|
106
|
+
const testRuleRecipeId = `test-rule-${Date.now()}`;
|
|
107
|
+
(0, node_test_1.before)(async () => {
|
|
108
|
+
if (!process.env["IMPART_AUTH_TOKEN"]) {
|
|
109
|
+
throw new Error("IMPART_AUTH_TOKEN environment variable is required");
|
|
110
|
+
}
|
|
111
|
+
client = new MCPTestClient();
|
|
112
|
+
await client.initialize();
|
|
113
|
+
// Find tool names with org suffix
|
|
114
|
+
const orgSuffix = config_js_1.config.orgId.slice(-4);
|
|
115
|
+
generateToolName = `generate_rule_recipe_${orgSuffix}`;
|
|
116
|
+
createToolName = `create_rule_recipe_${orgSuffix}`;
|
|
117
|
+
listToolName = `list_rule_recipes_${orgSuffix}`;
|
|
118
|
+
console.log(`\nTesting with org suffix: ${orgSuffix}`);
|
|
119
|
+
});
|
|
120
|
+
(0, node_test_1.after)(() => {
|
|
121
|
+
client.close();
|
|
122
|
+
});
|
|
123
|
+
(0, node_test_1.describe)("Fix #1: Sparkle API query format", () => {
|
|
124
|
+
(0, node_test_1.it)("should generate rule recipe with properly formatted query", async () => {
|
|
125
|
+
console.log("\n Testing generate_rule_recipe with Sparkle AI...");
|
|
126
|
+
const result = await client.callTool(generateToolName, {
|
|
127
|
+
query: "Create a simple rule recipe that blocks requests with user-agent containing 'badbot'",
|
|
128
|
+
});
|
|
129
|
+
// Verify result structure
|
|
130
|
+
node_assert_1.default.ok(result, "Should return a result");
|
|
131
|
+
const typedResult = result;
|
|
132
|
+
node_assert_1.default.ok(Array.isArray(typedResult.content), "Should have content array");
|
|
133
|
+
node_assert_1.default.ok(typedResult.content[0], "Should have first content item");
|
|
134
|
+
node_assert_1.default.strictEqual(typedResult.content[0].type, "text", "Should have text content");
|
|
135
|
+
const responseText = typedResult.content[0].text;
|
|
136
|
+
node_assert_1.default.ok(responseText.includes("Generated Rule Recipe Configuration"), "Should include success message");
|
|
137
|
+
node_assert_1.default.ok(responseText.includes("rate_limit"), "Should include rate_limit component");
|
|
138
|
+
node_assert_1.default.ok(responseText.includes("inspect_request"), "Should include inspect_request component");
|
|
139
|
+
node_assert_1.default.ok(responseText.includes("enforce"), "Should include enforce component");
|
|
140
|
+
node_assert_1.default.ok(responseText.includes("inspect_response"), "Should include inspect_response component");
|
|
141
|
+
// Extract the JSON components for next test
|
|
142
|
+
const jsonMatch = responseText.match(/\{[\s\S]*\}/);
|
|
143
|
+
node_assert_1.default.ok(jsonMatch, "Should contain JSON components");
|
|
144
|
+
generatedComponents = jsonMatch[0];
|
|
145
|
+
console.log(" ✓ Sparkle AI generated rule recipe successfully");
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
(0, node_test_1.describe)("Fix #2 & #3: blocking_effect field naming and placement", () => {
|
|
149
|
+
(0, node_test_1.it)("should create rule recipe with snake_case blocking_effect", async () => {
|
|
150
|
+
console.log("\n Testing create_rule_recipe with proper field naming...");
|
|
151
|
+
// Use simple components structure for testing
|
|
152
|
+
const simpleComponents = {
|
|
153
|
+
rate_limit: { clauses: [] },
|
|
154
|
+
inspect_request: { clauses: [] },
|
|
155
|
+
enforce: { clauses: [] },
|
|
156
|
+
inspect_response: { clauses: [] },
|
|
157
|
+
};
|
|
158
|
+
const result = await client.callTool(createToolName, {
|
|
159
|
+
name: testRuleRecipeId,
|
|
160
|
+
description: "Test rule recipe to verify field naming",
|
|
161
|
+
disabled: true, // Keep disabled so it doesn't affect anything
|
|
162
|
+
blocking_effect: "simulate", // ← snake_case, top-level
|
|
163
|
+
components: JSON.stringify(simpleComponents),
|
|
164
|
+
});
|
|
165
|
+
// Verify success
|
|
166
|
+
node_assert_1.default.ok(result, "Should return a result");
|
|
167
|
+
const typedResult = result;
|
|
168
|
+
node_assert_1.default.ok(typedResult.content[0], "Should have content");
|
|
169
|
+
node_assert_1.default.ok(typedResult.content[0].text.includes("created successfully"), "Should create successfully");
|
|
170
|
+
node_assert_1.default.ok(typedResult.content[0].text.includes(testRuleRecipeId), "Should include rule recipe name");
|
|
171
|
+
console.log(" ✓ Rule recipe created with snake_case blocking_effect");
|
|
172
|
+
});
|
|
173
|
+
(0, node_test_1.it)("should verify created rule recipe exists", async () => {
|
|
174
|
+
console.log("\n Verifying rule recipe was created...");
|
|
175
|
+
const result = await client.callTool(listToolName, {
|
|
176
|
+
search: testRuleRecipeId,
|
|
177
|
+
});
|
|
178
|
+
const typedResult = result;
|
|
179
|
+
node_assert_1.default.ok(typedResult.content[0], "Should have content");
|
|
180
|
+
const responseText = typedResult.content[0].text;
|
|
181
|
+
node_assert_1.default.ok(responseText.includes(testRuleRecipeId), "Should find the created rule recipe");
|
|
182
|
+
node_assert_1.default.ok(responseText.includes("simulate"), "Should have simulate blocking effect");
|
|
183
|
+
console.log(" ✓ Rule recipe verified in list");
|
|
184
|
+
});
|
|
185
|
+
});
|
|
186
|
+
(0, node_test_1.describe)("Regression: blocking_effect in components should be stripped", () => {
|
|
187
|
+
(0, node_test_1.it)("should handle components with blocking_effect inside (legacy format)", async () => {
|
|
188
|
+
console.log("\n Testing backward compatibility with blocking_effect in components...");
|
|
189
|
+
// Simulate what Sparkle might have generated in old format
|
|
190
|
+
const componentsWithBlockingEffect = {
|
|
191
|
+
rate_limit: { clauses: [] },
|
|
192
|
+
inspect_request: { clauses: [] },
|
|
193
|
+
enforce: { clauses: [] },
|
|
194
|
+
inspect_response: { clauses: [] },
|
|
195
|
+
blocking_effect: "block", // ← This should be stripped out
|
|
196
|
+
};
|
|
197
|
+
const result = await client.callTool(createToolName, {
|
|
198
|
+
name: `${testRuleRecipeId}-legacy`,
|
|
199
|
+
description: "Test backward compatibility",
|
|
200
|
+
disabled: true,
|
|
201
|
+
blocking_effect: "simulate", // ← Top-level should win
|
|
202
|
+
components: JSON.stringify(componentsWithBlockingEffect),
|
|
203
|
+
});
|
|
204
|
+
node_assert_1.default.ok(result, "Should handle legacy format");
|
|
205
|
+
const typedResult = result;
|
|
206
|
+
node_assert_1.default.ok(typedResult.content[0], "Should have content");
|
|
207
|
+
node_assert_1.default.ok(typedResult.content[0].text.includes("created successfully"), "Should create successfully");
|
|
208
|
+
console.log(" ✓ Legacy format handled correctly");
|
|
209
|
+
});
|
|
210
|
+
});
|
|
211
|
+
(0, node_test_1.describe)("Error handling", () => {
|
|
212
|
+
(0, node_test_1.it)("should handle invalid components JSON", async () => {
|
|
213
|
+
console.log("\n Testing invalid JSON components...");
|
|
214
|
+
try {
|
|
215
|
+
await client.callTool(createToolName, {
|
|
216
|
+
name: "invalid-components-test",
|
|
217
|
+
disabled: true,
|
|
218
|
+
components: "{invalid json}", // ← Invalid JSON
|
|
219
|
+
});
|
|
220
|
+
node_assert_1.default.fail("Should have thrown an error");
|
|
221
|
+
}
|
|
222
|
+
catch (error) {
|
|
223
|
+
node_assert_1.default.ok(error instanceof Error);
|
|
224
|
+
node_assert_1.default.ok(error.message.includes("components must be valid JSON"), "Should have helpful error message");
|
|
225
|
+
console.log(" ✓ Invalid JSON rejected with clear error");
|
|
226
|
+
}
|
|
227
|
+
});
|
|
228
|
+
(0, node_test_1.it)("should handle missing required components", async () => {
|
|
229
|
+
console.log("\n Testing missing required components...");
|
|
230
|
+
try {
|
|
231
|
+
const incompleteComponents = {
|
|
232
|
+
rate_limit: { clauses: [] },
|
|
233
|
+
// Missing inspect_request, enforce, inspect_response
|
|
234
|
+
};
|
|
235
|
+
await client.callTool(createToolName, {
|
|
236
|
+
name: "incomplete-components-test",
|
|
237
|
+
disabled: true,
|
|
238
|
+
components: JSON.stringify(incompleteComponents),
|
|
239
|
+
});
|
|
240
|
+
node_assert_1.default.fail("Should have thrown an error");
|
|
241
|
+
}
|
|
242
|
+
catch (error) {
|
|
243
|
+
node_assert_1.default.ok(error instanceof Error);
|
|
244
|
+
console.log(" ✓ Incomplete components rejected by API");
|
|
245
|
+
}
|
|
246
|
+
});
|
|
247
|
+
});
|
|
248
|
+
(0, node_test_1.describe)("Full workflow", () => {
|
|
249
|
+
(0, node_test_1.it)("should complete full generate → create workflow", async () => {
|
|
250
|
+
if (!generatedComponents) {
|
|
251
|
+
console.log(" ⊘ Skipping - no generated components from previous test");
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
console.log("\n Testing full workflow: generate → create...");
|
|
255
|
+
// Parse and clean the generated components
|
|
256
|
+
const components = JSON.parse(generatedComponents);
|
|
257
|
+
// Remove blocking_effect if present
|
|
258
|
+
if ("blocking_effect" in components) {
|
|
259
|
+
delete components.blocking_effect;
|
|
260
|
+
}
|
|
261
|
+
if ("blockingEffect" in components) {
|
|
262
|
+
delete components.blockingEffect;
|
|
263
|
+
}
|
|
264
|
+
const result = await client.callTool(createToolName, {
|
|
265
|
+
name: `${testRuleRecipeId}-generated`,
|
|
266
|
+
description: "Created from Sparkle-generated components",
|
|
267
|
+
disabled: true,
|
|
268
|
+
blocking_effect: "simulate",
|
|
269
|
+
components: JSON.stringify(components),
|
|
270
|
+
});
|
|
271
|
+
node_assert_1.default.ok(result, "Should create from generated components");
|
|
272
|
+
const typedResult = result;
|
|
273
|
+
node_assert_1.default.ok(typedResult.content[0], "Should have content");
|
|
274
|
+
node_assert_1.default.ok(typedResult.content[0].text.includes("created successfully"), "Should create successfully");
|
|
275
|
+
console.log(" ✓ Full generate → create workflow completed");
|
|
276
|
+
});
|
|
277
|
+
});
|
|
278
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const node_test_1 = require("node:test");
|
|
7
|
+
const node_assert_1 = __importDefault(require("node:assert"));
|
|
8
|
+
/**
|
|
9
|
+
* Smoke tests that don't require API access
|
|
10
|
+
* These verify basic module loading and exports
|
|
11
|
+
*/
|
|
12
|
+
(0, node_test_1.describe)("MCP Server Smoke Tests", () => {
|
|
13
|
+
(0, node_test_1.it)("should load config module", async () => {
|
|
14
|
+
// Don't actually import if no token is set
|
|
15
|
+
if (process.env["IMPART_AUTH_TOKEN"]) {
|
|
16
|
+
const { config } = await import("../config.js");
|
|
17
|
+
node_assert_1.default.ok(config.authToken, "Config should have auth token");
|
|
18
|
+
node_assert_1.default.ok(config.orgId, "Config should have org ID");
|
|
19
|
+
node_assert_1.default.ok(config.apiBase, "Config should have API base");
|
|
20
|
+
node_assert_1.default.ok(config.userAgent, "Config should have user agent");
|
|
21
|
+
}
|
|
22
|
+
else {
|
|
23
|
+
console.log("⚠️ Skipping config test (no IMPART_AUTH_TOKEN)");
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
(0, node_test_1.it)("should load types module", async () => {
|
|
27
|
+
// Just verify the module loads without errors
|
|
28
|
+
await import("../types.js");
|
|
29
|
+
node_assert_1.default.ok(true, "Types module should load");
|
|
30
|
+
});
|
|
31
|
+
(0, node_test_1.it)("should have valid package.json", async () => {
|
|
32
|
+
const pkg = await import("../../package.json", { with: { type: "json" } });
|
|
33
|
+
node_assert_1.default.strictEqual(pkg.default.name, "@impart-security/impart-mcp");
|
|
34
|
+
node_assert_1.default.ok(pkg.default.version, "Should have version");
|
|
35
|
+
node_assert_1.default.strictEqual(pkg.default.main, "dist/index.js");
|
|
36
|
+
});
|
|
37
|
+
});
|