@impart-security/impart-mcp 0.1.3 → 0.2.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 +76 -35
- package/dist/index.js +1031 -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
package/dist/index.js
CHANGED
|
@@ -9,6 +9,7 @@ const config_js_1 = require("./config.js");
|
|
|
9
9
|
const server = new mcp_js_1.McpServer({
|
|
10
10
|
name: "impart-mcp",
|
|
11
11
|
version: "1.0.0",
|
|
12
|
+
}, {
|
|
12
13
|
capabilities: {
|
|
13
14
|
resources: {},
|
|
14
15
|
tools: {},
|
|
@@ -18,6 +19,46 @@ const server = new mcp_js_1.McpServer({
|
|
|
18
19
|
function validatePage(page) {
|
|
19
20
|
return Math.max(1, page); // Ensure page is at least 1
|
|
20
21
|
}
|
|
22
|
+
// Helper function to convert date strings to Date objects recursively
|
|
23
|
+
function convertDatesToObjects(obj) {
|
|
24
|
+
if (obj === null || obj === undefined) {
|
|
25
|
+
return obj;
|
|
26
|
+
}
|
|
27
|
+
if (Array.isArray(obj)) {
|
|
28
|
+
return obj.map(convertDatesToObjects);
|
|
29
|
+
}
|
|
30
|
+
if (typeof obj === "object") {
|
|
31
|
+
const converted = {};
|
|
32
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
33
|
+
// Convert RFC3339 date strings to Date objects
|
|
34
|
+
if (typeof value === "string" && isDateField(key)) {
|
|
35
|
+
try {
|
|
36
|
+
// Check if it looks like an ISO date string
|
|
37
|
+
if (/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/.exec(value)) {
|
|
38
|
+
converted[key] = new Date(value);
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
// For debugging: if it's a date field but doesn't match pattern, keep as null
|
|
42
|
+
converted[key] = null;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
converted[key] = null; // Set to null if date parsing fails
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
converted[key] = convertDatesToObjects(value);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return converted;
|
|
54
|
+
}
|
|
55
|
+
return obj;
|
|
56
|
+
}
|
|
57
|
+
// Helper function to identify date fields
|
|
58
|
+
function isDateField(key) {
|
|
59
|
+
const dateFieldPatterns = ["createdAt", "updatedAt", "lastSeen", "observedAt", "from", "until", "expiration"];
|
|
60
|
+
return dateFieldPatterns.includes(key) || key.endsWith("At");
|
|
61
|
+
}
|
|
21
62
|
// Helper function for making Impart API requests
|
|
22
63
|
async function makeImpartRequest(endpoint, queryParams, method = "GET", body) {
|
|
23
64
|
const headers = {
|
|
@@ -49,14 +90,86 @@ async function makeImpartRequest(endpoint, queryParams, method = "GET", body) {
|
|
|
49
90
|
if (body && method !== "GET" && method !== "HEAD") {
|
|
50
91
|
requestOptions.body = JSON.stringify(body);
|
|
51
92
|
}
|
|
93
|
+
// Log request details for debugging
|
|
94
|
+
console.error(`[makeImpartRequest] ${method} ${url.toString()}`);
|
|
95
|
+
console.error(`[makeImpartRequest] Headers:`, JSON.stringify(headers, null, 2));
|
|
96
|
+
if (body) {
|
|
97
|
+
console.error(`[makeImpartRequest] Body:`, JSON.stringify(body, null, 2));
|
|
98
|
+
}
|
|
52
99
|
const response = await fetch(url.toString(), requestOptions);
|
|
100
|
+
console.error(`[makeImpartRequest] Response status: ${response.status} ${response.statusText}`);
|
|
53
101
|
if (!response.ok) {
|
|
54
|
-
|
|
102
|
+
let errorMessage = `HTTP error! status: ${response.status}`;
|
|
103
|
+
try {
|
|
104
|
+
const errorBody = await response.text();
|
|
105
|
+
if (errorBody)
|
|
106
|
+
errorMessage += ` - ${errorBody}`;
|
|
107
|
+
}
|
|
108
|
+
catch (e) {
|
|
109
|
+
// Ignore error reading response body
|
|
110
|
+
}
|
|
111
|
+
throw new Error(errorMessage);
|
|
55
112
|
}
|
|
56
|
-
|
|
113
|
+
const jsonData = await response.json();
|
|
114
|
+
const converted = convertDatesToObjects(jsonData);
|
|
115
|
+
return converted;
|
|
57
116
|
}
|
|
58
117
|
catch (error) {
|
|
59
118
|
console.error("Error making Impart request:", error);
|
|
119
|
+
// Parse API error for better AI-friendly messages
|
|
120
|
+
let errorMessage = "Unknown error";
|
|
121
|
+
if (error instanceof Error) {
|
|
122
|
+
const message = error.message;
|
|
123
|
+
// Extract HTTP status and body for better error messages
|
|
124
|
+
if (message.includes("status: 400")) {
|
|
125
|
+
// Extract error details from response body
|
|
126
|
+
const bodyMatch = message.match(/status: 400 - (.+)/);
|
|
127
|
+
if (bodyMatch) {
|
|
128
|
+
errorMessage = `Bad Request - ${bodyMatch[1]}`;
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
errorMessage =
|
|
132
|
+
"Bad Request - Invalid parameters or format. Check parameter types, ranges, and formats (e.g., time strings should be '-1d', '-24h', or ISO 8601).";
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
else if (message.includes("status: 401")) {
|
|
136
|
+
errorMessage = "Unauthorized - Invalid or expired API token. Please check your IMPART_AUTH_TOKEN.";
|
|
137
|
+
}
|
|
138
|
+
else if (message.includes("status: 403")) {
|
|
139
|
+
const scopeMatch = message.match(/Insufficient scope|permissions/i);
|
|
140
|
+
if (scopeMatch) {
|
|
141
|
+
errorMessage = "Forbidden - Insufficient permissions. Your API token does not have the required scope for this operation.";
|
|
142
|
+
}
|
|
143
|
+
else {
|
|
144
|
+
errorMessage = "Forbidden - Access denied. Please check your API token has the correct permissions.";
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
else if (message.includes("status: 404")) {
|
|
148
|
+
errorMessage = "Not Found - The requested resource does not exist or may have been deleted.";
|
|
149
|
+
}
|
|
150
|
+
else if (message.includes("status: 422")) {
|
|
151
|
+
const bodyMatch = message.match(/status: 422 - (.+)/);
|
|
152
|
+
if (bodyMatch) {
|
|
153
|
+
errorMessage = `Validation Error - ${bodyMatch[1]}`;
|
|
154
|
+
}
|
|
155
|
+
else {
|
|
156
|
+
errorMessage = "Validation Error - One or more parameters failed validation.";
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
else if (message.includes("status: 429")) {
|
|
160
|
+
errorMessage = "Rate Limit Exceeded - Too many requests. Please wait before retrying.";
|
|
161
|
+
}
|
|
162
|
+
else if (message.includes("status: 500")) {
|
|
163
|
+
errorMessage = "Server Error - Internal server error. Please try again later.";
|
|
164
|
+
}
|
|
165
|
+
else if (message.includes("status: 503")) {
|
|
166
|
+
errorMessage = "Service Unavailable - API is temporarily unavailable. Please try again later.";
|
|
167
|
+
}
|
|
168
|
+
else {
|
|
169
|
+
errorMessage = message;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
makeImpartRequest.lastError = errorMessage;
|
|
60
173
|
return null;
|
|
61
174
|
}
|
|
62
175
|
}
|
|
@@ -64,46 +177,92 @@ async function makeImpartRequest(endpoint, queryParams, method = "GET", body) {
|
|
|
64
177
|
function getToolName(baseName) {
|
|
65
178
|
return `${baseName}_${config_js_1.config.orgId.slice(-4)}`;
|
|
66
179
|
}
|
|
180
|
+
// Helper function to validate pagination parameters
|
|
181
|
+
function validatePagination(page, max_results) {
|
|
182
|
+
if (page !== null && page !== undefined && page < 1) {
|
|
183
|
+
return {
|
|
184
|
+
valid: false,
|
|
185
|
+
error: "Invalid pagination: 'page' must be >= 1. Example: page=1",
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
if (max_results !== null && max_results !== undefined) {
|
|
189
|
+
if (max_results < 1) {
|
|
190
|
+
return {
|
|
191
|
+
valid: false,
|
|
192
|
+
error: "Invalid pagination: 'max_results' must be >= 1. Example: max_results=10",
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
if (max_results > 1000) {
|
|
196
|
+
return {
|
|
197
|
+
valid: false,
|
|
198
|
+
error: "Invalid pagination: 'max_results' must be <= 1000. Maximum allowed is 1000.",
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
return { valid: true };
|
|
203
|
+
}
|
|
204
|
+
// Helper function to validate time format
|
|
205
|
+
function validateTimeFormat(time) {
|
|
206
|
+
// Relative time format: -1d, -24h, -7d, etc.
|
|
207
|
+
const relativeTimeRegex = /^-\d+[dhms]$/;
|
|
208
|
+
if (relativeTimeRegex.test(time)) {
|
|
209
|
+
return { valid: true };
|
|
210
|
+
}
|
|
211
|
+
// ISO 8601 timestamp
|
|
212
|
+
try {
|
|
213
|
+
const date = new Date(time);
|
|
214
|
+
if (!isNaN(date.getTime())) {
|
|
215
|
+
return { valid: true };
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
catch {
|
|
219
|
+
// Fall through to error
|
|
220
|
+
}
|
|
221
|
+
return {
|
|
222
|
+
valid: false,
|
|
223
|
+
error: `Invalid time format '${time}'. Use relative time (e.g., '-1d', '-24h', '-7d') or ISO 8601 timestamp (e.g., '2025-10-14T12:00:00Z').`,
|
|
224
|
+
};
|
|
225
|
+
}
|
|
67
226
|
// Register tools with consistent naming patterns
|
|
68
227
|
server.tool(getToolName("list_inspectors"), "Get a list of inspectors for the organization", {
|
|
69
|
-
page: zod_1.z
|
|
70
|
-
.number()
|
|
71
|
-
.default(1)
|
|
72
|
-
.optional()
|
|
73
|
-
.describe("The page of results to return (minimum value: 1)"),
|
|
228
|
+
page: zod_1.z.number().default(1).optional().describe("The page of results to return (minimum value: 1, e.g., 1, 2, 3)"),
|
|
74
229
|
max_results: zod_1.z
|
|
75
230
|
.number()
|
|
76
231
|
.default(100)
|
|
77
232
|
.optional()
|
|
78
|
-
.describe("The maximum number of results to return"),
|
|
233
|
+
.describe("The maximum number of results to return (e.g., 10, 50, 100)"),
|
|
79
234
|
}, async ({ page, max_results }) => {
|
|
235
|
+
// Validate pagination parameters
|
|
236
|
+
const paginationCheck = validatePagination(page, max_results);
|
|
237
|
+
if (!paginationCheck.valid) {
|
|
238
|
+
return {
|
|
239
|
+
content: [{ type: "text", text: paginationCheck.error }],
|
|
240
|
+
};
|
|
241
|
+
}
|
|
80
242
|
const queryParams = {};
|
|
81
243
|
if (page !== null && page !== undefined) {
|
|
82
|
-
queryParams["page"] =
|
|
244
|
+
queryParams["page"] = page;
|
|
83
245
|
}
|
|
84
246
|
if (max_results !== null && max_results !== undefined) {
|
|
85
247
|
queryParams["max_results"] = max_results;
|
|
86
248
|
}
|
|
87
249
|
const data = await makeImpartRequest("inspectors", queryParams);
|
|
88
250
|
if (!data) {
|
|
251
|
+
const lastError = makeImpartRequest.lastError || "Unknown error";
|
|
89
252
|
return {
|
|
90
|
-
content: [
|
|
91
|
-
{ type: "text", text: "Unable to fetch inspectors: Unknown error" },
|
|
92
|
-
],
|
|
253
|
+
content: [{ type: "text", text: `Unable to fetch inspectors: ${lastError}` }],
|
|
93
254
|
};
|
|
94
255
|
}
|
|
95
256
|
const inspectors = data.items || [];
|
|
96
257
|
if (inspectors.length === 0) {
|
|
97
258
|
return {
|
|
98
|
-
content: [
|
|
99
|
-
{ type: "text", text: "No inspectors found for this organization." },
|
|
100
|
-
],
|
|
259
|
+
content: [{ type: "text", text: "No inspectors found for this organization." }],
|
|
101
260
|
};
|
|
102
261
|
}
|
|
103
262
|
const formattedInspectors = inspectors.map((inspector) => [
|
|
104
263
|
`ID: ${inspector.id}`,
|
|
105
264
|
`Version: ${inspector.version}`,
|
|
106
|
-
`Last Seen: ${inspector.
|
|
265
|
+
`Last Seen: ${inspector.lastSeen ? inspector.lastSeen.toISOString() : "N/A"}`,
|
|
107
266
|
"---",
|
|
108
267
|
].join("\n"));
|
|
109
268
|
return {
|
|
@@ -114,40 +273,34 @@ server.tool(getToolName("list_inspectors"), "Get a list of inspectors for the or
|
|
|
114
273
|
server.tool(getToolName("list_rules_scripts"), "Get a list of rules scripts for your Impart organization", {
|
|
115
274
|
page: zod_1.z
|
|
116
275
|
.number()
|
|
276
|
+
.optional()
|
|
117
277
|
.default(1)
|
|
118
278
|
.nullable()
|
|
119
|
-
.describe("The page of results to return (minimum value: 1)"),
|
|
279
|
+
.describe("The page of results to return (minimum value: 1, e.g., 1, 2, 3)"),
|
|
120
280
|
max_results: zod_1.z
|
|
121
281
|
.number()
|
|
282
|
+
.optional()
|
|
122
283
|
.default(100)
|
|
123
284
|
.nullable()
|
|
124
|
-
.describe("The maximum number of results to return"),
|
|
285
|
+
.describe("The maximum number of results to return (e.g., 10, 50, 100)"),
|
|
125
286
|
type: zod_1.z
|
|
126
|
-
.
|
|
287
|
+
.enum(["custom", "core", "recipe"])
|
|
127
288
|
.optional()
|
|
128
|
-
.
|
|
129
|
-
|
|
130
|
-
exclude_src: zod_1.z
|
|
131
|
-
.boolean()
|
|
132
|
-
.default(false)
|
|
133
|
-
.nullable()
|
|
134
|
-
.describe("Whether to exclude the rule script source"),
|
|
289
|
+
.describe("Filter by type of rule script ('custom', 'core' or 'recipe')"),
|
|
290
|
+
exclude_src: zod_1.z.boolean().optional().default(false).nullable().describe("Whether to exclude the rule script source"),
|
|
135
291
|
exclude_revisions: zod_1.z
|
|
136
292
|
.boolean()
|
|
293
|
+
.optional()
|
|
137
294
|
.default(false)
|
|
138
295
|
.nullable()
|
|
139
296
|
.describe("Whether to exclude rule script revisions"),
|
|
140
|
-
is_disabled: zod_1.z
|
|
141
|
-
.string()
|
|
142
|
-
.optional()
|
|
143
|
-
.nullable()
|
|
144
|
-
.describe("Filter by disabled status ('true' or 'false')"),
|
|
297
|
+
is_disabled: zod_1.z.string().optional().describe("Filter by disabled status ('true' or 'false')"),
|
|
145
298
|
label: zod_1.z
|
|
146
299
|
.array(zod_1.z.string())
|
|
147
300
|
.optional()
|
|
148
301
|
.nullable()
|
|
149
|
-
.describe(
|
|
150
|
-
}, async ({ page, max_results, type, exclude_src, exclude_revisions, is_disabled, label
|
|
302
|
+
.describe('Filter by label slugs (e.g., ["llm-protection", "waf"])'),
|
|
303
|
+
}, async ({ page, max_results, type, exclude_src, exclude_revisions, is_disabled, label }) => {
|
|
151
304
|
const queryParams = {};
|
|
152
305
|
if (page !== null && page !== undefined) {
|
|
153
306
|
queryParams["page"] = validatePage(page);
|
|
@@ -176,12 +329,12 @@ server.tool(getToolName("list_rules_scripts"), "Get a list of rules scripts for
|
|
|
176
329
|
content: [
|
|
177
330
|
{
|
|
178
331
|
type: "text",
|
|
179
|
-
text:
|
|
332
|
+
text: `Unable to fetch rules scripts: ${makeImpartRequest.lastError || "Unknown error"}`,
|
|
180
333
|
},
|
|
181
334
|
],
|
|
182
335
|
};
|
|
183
336
|
}
|
|
184
|
-
const scripts = data.items
|
|
337
|
+
const scripts = data.items;
|
|
185
338
|
if (scripts.length === 0) {
|
|
186
339
|
return {
|
|
187
340
|
content: [
|
|
@@ -192,41 +345,29 @@ server.tool(getToolName("list_rules_scripts"), "Get a list of rules scripts for
|
|
|
192
345
|
],
|
|
193
346
|
};
|
|
194
347
|
}
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
`
|
|
200
|
-
`
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
`
|
|
206
|
-
|
|
207
|
-
`Updated By: ${script.updated_by}`,
|
|
208
|
-
`Updated At: ${script.updated_at}`,
|
|
209
|
-
"---",
|
|
210
|
-
].join("\n"));
|
|
348
|
+
// Limit display for performance
|
|
349
|
+
const maxDisplay = Math.min(scripts.length, 15);
|
|
350
|
+
const showingLimited = scripts.length > maxDisplay;
|
|
351
|
+
const result = [
|
|
352
|
+
`Found ${scripts.length} rules scripts`,
|
|
353
|
+
showingLimited ? `Showing first ${maxDisplay} for performance. Use filters to narrow results.` : "",
|
|
354
|
+
"=".repeat(50),
|
|
355
|
+
"",
|
|
356
|
+
];
|
|
357
|
+
scripts.slice(0, maxDisplay).forEach((script, index) => {
|
|
358
|
+
result.push(`${index + 1}. ${script.name} (${script.type})`, ` ID: ${script.id} | Rev: ${script.revision} | ${script.disabled ? "DISABLED" : "ENABLED"}`, ` Labels: ${script.labels.length > 0 ? script.labels.join(", ") : "None"}`, ` Created: ${script.createdAt ? script.createdAt.toISOString() : "N/A"}`, "");
|
|
359
|
+
});
|
|
211
360
|
return {
|
|
212
|
-
content: [{ type: "text", text:
|
|
361
|
+
content: [{ type: "text", text: result.join("\n") }],
|
|
213
362
|
};
|
|
214
363
|
});
|
|
215
364
|
server.tool(getToolName("get_rules_script"), "Get details of a specific rules script", {
|
|
216
|
-
rules_script_id: zod_1.z
|
|
217
|
-
|
|
218
|
-
.describe("The unique identifier of the rules script"),
|
|
219
|
-
revision: zod_1.z
|
|
220
|
-
.number()
|
|
221
|
-
.optional()
|
|
222
|
-
.nullable()
|
|
223
|
-
.describe("The specific revision to fetch"),
|
|
365
|
+
rules_script_id: zod_1.z.string().describe("The unique identifier of the rules script"),
|
|
366
|
+
revision: zod_1.z.number().optional().nullable().describe("The specific revision to fetch"),
|
|
224
367
|
}, async ({ rules_script_id, revision }) => {
|
|
225
368
|
if (!rules_script_id) {
|
|
226
369
|
return {
|
|
227
|
-
content: [
|
|
228
|
-
{ type: "text", text: "Error: rules_script_id is required." },
|
|
229
|
-
],
|
|
370
|
+
content: [{ type: "text", text: "Error: rules_script_id is required." }],
|
|
230
371
|
};
|
|
231
372
|
}
|
|
232
373
|
const queryParams = {};
|
|
@@ -235,10 +376,9 @@ server.tool(getToolName("get_rules_script"), "Get details of a specific rules sc
|
|
|
235
376
|
}
|
|
236
377
|
const data = await makeImpartRequest(`rules_scripts/${rules_script_id}`, queryParams);
|
|
237
378
|
if (!data) {
|
|
379
|
+
const lastError = makeImpartRequest.lastError || "Unknown error";
|
|
238
380
|
return {
|
|
239
|
-
content: [
|
|
240
|
-
{ type: "text", text: "Unable to fetch rules script: Unknown error" },
|
|
241
|
-
],
|
|
381
|
+
content: [{ type: "text", text: `Unable to fetch rules script: ${lastError}` }],
|
|
242
382
|
};
|
|
243
383
|
}
|
|
244
384
|
const formattedScript = [
|
|
@@ -247,294 +387,316 @@ server.tool(getToolName("get_rules_script"), "Get details of a specific rules sc
|
|
|
247
387
|
`Description: ${data.description ?? "N/A"}`,
|
|
248
388
|
`Type: ${data.type}`,
|
|
249
389
|
`Disabled: ${data.disabled}`,
|
|
250
|
-
`Blocking Effect: ${data.
|
|
390
|
+
`Blocking Effect: ${data.blockingEffect}`,
|
|
251
391
|
`Language: ${data.lang}`,
|
|
252
392
|
`Source Code: ${data.src ?? "N/A"}`,
|
|
253
|
-
`Autogenerated Explanation: ${data.
|
|
393
|
+
`Autogenerated Explanation: ${data.autogeneratedExplanation}`,
|
|
254
394
|
`Revision: ${data.revision}`,
|
|
255
395
|
`Dependencies: ${(data.dependencies ?? []).join(", ")}`,
|
|
256
396
|
`Labels: ${data.labels.join(", ")}`,
|
|
257
|
-
`Created By: ${data.
|
|
258
|
-
`Created At: ${data.
|
|
259
|
-
`Updated By: ${data.
|
|
260
|
-
`Updated At: ${data.
|
|
397
|
+
`Created By: ${data.createdBy}`,
|
|
398
|
+
`Created At: ${data.createdAt ? data.createdAt.toISOString() : "N/A"}`,
|
|
399
|
+
`Updated By: ${data.updatedBy}`,
|
|
400
|
+
`Updated At: ${data.updatedAt?.toISOString() ?? "N/A"}`,
|
|
261
401
|
"---",
|
|
262
402
|
"Revisions:",
|
|
263
403
|
];
|
|
264
404
|
data.revisions.forEach((revision) => {
|
|
265
|
-
formattedScript.push(` Revision: ${revision.revision}`, ` Created By: ${revision.
|
|
405
|
+
formattedScript.push(` Revision: ${revision.revision}`, ` Created By: ${revision.createdBy}`, ` Created At: ${revision.createdAt ? revision.createdAt.toISOString() : "N/A"}`, "---");
|
|
266
406
|
});
|
|
267
407
|
return {
|
|
268
408
|
content: [{ type: "text", text: formattedScript.join("\n") }],
|
|
269
409
|
};
|
|
270
410
|
});
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
.
|
|
276
|
-
|
|
277
|
-
.string()
|
|
278
|
-
.default("assemblyscript")
|
|
279
|
-
.nullable()
|
|
280
|
-
.describe("The language of the script"),
|
|
281
|
-
description: zod_1.z
|
|
282
|
-
.string()
|
|
411
|
+
// Rule Recipe Tools
|
|
412
|
+
// -----------------
|
|
413
|
+
server.tool(getToolName("list_rule_recipes"), "Get a list of rule recipes for your Impart organization", {
|
|
414
|
+
page: zod_1.z
|
|
415
|
+
.number()
|
|
416
|
+
.default(1)
|
|
283
417
|
.optional()
|
|
284
418
|
.nullable()
|
|
285
|
-
.describe("
|
|
286
|
-
|
|
287
|
-
.
|
|
288
|
-
.default(
|
|
289
|
-
.nullable()
|
|
290
|
-
.describe("Effect when rule blocks"),
|
|
291
|
-
disabled: zod_1.z
|
|
292
|
-
.boolean()
|
|
293
|
-
.default(false)
|
|
294
|
-
.nullable()
|
|
295
|
-
.describe("Whether the script is disabled"),
|
|
296
|
-
labels: zod_1.z
|
|
297
|
-
.array(zod_1.z.string())
|
|
419
|
+
.describe("The page of results to return (minimum value: 1, e.g., 1, 2, 3)"),
|
|
420
|
+
max_results: zod_1.z
|
|
421
|
+
.number()
|
|
422
|
+
.default(100)
|
|
298
423
|
.optional()
|
|
299
424
|
.nullable()
|
|
300
|
-
.describe("
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
payload["lang"] = lang;
|
|
308
|
-
}
|
|
309
|
-
if (description !== null && description !== undefined) {
|
|
310
|
-
payload["description"] = description;
|
|
425
|
+
.describe("The maximum number of results to return (e.g., 10, 50, 100)"),
|
|
426
|
+
search: zod_1.z.string().optional().describe("Search string to filter rule recipes"),
|
|
427
|
+
}, async ({ page, max_results, search }) => {
|
|
428
|
+
// Validate pagination parameters
|
|
429
|
+
const paginationCheck = validatePagination(page, max_results);
|
|
430
|
+
if (!paginationCheck.valid) {
|
|
431
|
+
return { content: [{ type: "text", text: paginationCheck.error }] };
|
|
311
432
|
}
|
|
312
|
-
|
|
313
|
-
|
|
433
|
+
const queryParams = {};
|
|
434
|
+
if (page !== null && page !== undefined) {
|
|
435
|
+
queryParams["page"] = page;
|
|
314
436
|
}
|
|
315
|
-
if (
|
|
316
|
-
|
|
437
|
+
if (max_results !== null && max_results !== undefined) {
|
|
438
|
+
queryParams["max_results"] = max_results;
|
|
317
439
|
}
|
|
318
|
-
if (
|
|
319
|
-
|
|
440
|
+
if (search !== null && search !== undefined) {
|
|
441
|
+
queryParams["search"] = search;
|
|
320
442
|
}
|
|
321
|
-
const data = await makeImpartRequest("
|
|
443
|
+
const data = await makeImpartRequest("rule_recipes", queryParams);
|
|
322
444
|
if (!data) {
|
|
445
|
+
const lastError = makeImpartRequest.lastError || "Unknown error";
|
|
323
446
|
return {
|
|
324
|
-
content: [{ type: "text", text:
|
|
447
|
+
content: [{ type: "text", text: `Unable to fetch rule recipes: ${lastError}` }],
|
|
325
448
|
};
|
|
326
449
|
}
|
|
327
|
-
const
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
`
|
|
335
|
-
`
|
|
336
|
-
`
|
|
450
|
+
const recipes = data.items ?? [];
|
|
451
|
+
if (recipes.length === 0) {
|
|
452
|
+
return {
|
|
453
|
+
content: [{ type: "text", text: "No rule recipes found." }],
|
|
454
|
+
};
|
|
455
|
+
}
|
|
456
|
+
const formattedRecipes = recipes.map((recipe) => [
|
|
457
|
+
`ID: ${recipe.id}`,
|
|
458
|
+
`Name: ${recipe.name}`,
|
|
459
|
+
`Description: ${recipe.description}`,
|
|
460
|
+
`Disabled: ${recipe.disabled}`,
|
|
461
|
+
`Blocking Effect: ${recipe.blockingEffect}`,
|
|
462
|
+
`Labels: ${recipe.labels.join(", ")}`,
|
|
463
|
+
`Dependencies: ${recipe.dependencies.join(", ")}`,
|
|
464
|
+
`Created By: ${recipe.createdBy}`,
|
|
465
|
+
`Created At: ${recipe.createdAt ? recipe.createdAt.toISOString() : "N/A"}`,
|
|
466
|
+
`Updated At: ${recipe.updatedAt?.toISOString() ?? "N/A"}`,
|
|
337
467
|
"---",
|
|
338
|
-
].join("\n");
|
|
468
|
+
].join("\n"));
|
|
339
469
|
return {
|
|
340
|
-
content: [{ type: "text", text:
|
|
470
|
+
content: [{ type: "text", text: formattedRecipes.join("\n") }],
|
|
341
471
|
};
|
|
342
472
|
});
|
|
343
|
-
server.tool(getToolName("
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
name: zod_1.z.string().describe("The name of the rules script"),
|
|
348
|
-
src: zod_1.z.string().describe("The source code of the rules script"),
|
|
349
|
-
lang: zod_1.z
|
|
350
|
-
.string()
|
|
351
|
-
.default("assemblyscript")
|
|
352
|
-
.nullable()
|
|
353
|
-
.describe("The language of the script"),
|
|
354
|
-
description: zod_1.z
|
|
355
|
-
.string()
|
|
356
|
-
.optional()
|
|
357
|
-
.nullable()
|
|
358
|
-
.describe("Optional description of the script"),
|
|
359
|
-
blocking_effect: zod_1.z
|
|
360
|
-
.string()
|
|
361
|
-
.default("block")
|
|
362
|
-
.nullable()
|
|
363
|
-
.describe("Effect when rule blocks"),
|
|
364
|
-
disabled: zod_1.z
|
|
365
|
-
.boolean()
|
|
366
|
-
.default(false)
|
|
367
|
-
.nullable()
|
|
368
|
-
.describe("Whether the script is disabled"),
|
|
369
|
-
labels: zod_1.z
|
|
370
|
-
.array(zod_1.z.string())
|
|
371
|
-
.optional()
|
|
372
|
-
.nullable()
|
|
373
|
-
.describe("Optional list of label slugs"),
|
|
374
|
-
}, async ({ rules_script_id, name, src, lang, description, blocking_effect, disabled, labels, }) => {
|
|
375
|
-
if (!rules_script_id) {
|
|
473
|
+
server.tool(getToolName("get_rule_recipe"), "Get details of a specific rule recipe", {
|
|
474
|
+
rule_recipe_id: zod_1.z.string().describe("The unique identifier of the rule recipe"),
|
|
475
|
+
}, async ({ rule_recipe_id }) => {
|
|
476
|
+
if (!rule_recipe_id) {
|
|
376
477
|
return {
|
|
377
|
-
content: [
|
|
378
|
-
{ type: "text", text: "Error: rules_script_id is required." },
|
|
379
|
-
],
|
|
478
|
+
content: [{ type: "text", text: "Error: rule_recipe_id is required." }],
|
|
380
479
|
};
|
|
381
480
|
}
|
|
382
|
-
const
|
|
383
|
-
name,
|
|
384
|
-
src,
|
|
385
|
-
};
|
|
386
|
-
if (lang !== null && lang !== undefined) {
|
|
387
|
-
payload["lang"] = lang;
|
|
388
|
-
}
|
|
389
|
-
if (description !== null && description !== undefined) {
|
|
390
|
-
payload["description"] = description;
|
|
391
|
-
}
|
|
392
|
-
if (blocking_effect !== null && blocking_effect !== undefined) {
|
|
393
|
-
payload["blocking_effect"] = blocking_effect;
|
|
394
|
-
}
|
|
395
|
-
if (disabled !== null && disabled !== undefined) {
|
|
396
|
-
payload["disabled"] = disabled;
|
|
397
|
-
}
|
|
398
|
-
if (labels !== null && labels !== undefined) {
|
|
399
|
-
payload["labels"] = labels;
|
|
400
|
-
}
|
|
401
|
-
const data = await makeImpartRequest(`rules_scripts/${rules_script_id}`, undefined, "PUT", payload);
|
|
481
|
+
const data = await makeImpartRequest(`rule_recipes/${rule_recipe_id}`, undefined);
|
|
402
482
|
if (!data) {
|
|
483
|
+
const lastError = makeImpartRequest.lastError || "Unknown error";
|
|
403
484
|
return {
|
|
404
|
-
content: [{ type: "text", text:
|
|
485
|
+
content: [{ type: "text", text: `Unable to fetch rule recipe: ${lastError}` }],
|
|
405
486
|
};
|
|
406
487
|
}
|
|
407
|
-
const
|
|
408
|
-
"Rules script updated successfully:",
|
|
488
|
+
const formattedRecipe = [
|
|
409
489
|
`ID: ${data.id}`,
|
|
410
490
|
`Name: ${data.name}`,
|
|
411
|
-
`Description: ${data.description
|
|
412
|
-
`Language: ${data.lang}`,
|
|
413
|
-
`Blocking Effect: ${data.blocking_effect}`,
|
|
491
|
+
`Description: ${data.description}`,
|
|
414
492
|
`Disabled: ${data.disabled}`,
|
|
493
|
+
`Blocking Effect: ${data.blockingEffect}`,
|
|
415
494
|
`Labels: ${data.labels.join(", ")}`,
|
|
416
|
-
`
|
|
417
|
-
|
|
495
|
+
`Dependencies: ${data.dependencies.join(", ")}`,
|
|
496
|
+
`Components:`,
|
|
497
|
+
` Rate Limit: ${JSON.stringify(data.components.rate_limit, null, 2)}`,
|
|
498
|
+
` Inspect Request: ${JSON.stringify(data.components.inspect_request, null, 2)}`,
|
|
499
|
+
` Enforce: ${JSON.stringify(data.components.enforce, null, 2)}`,
|
|
500
|
+
` Inspect Response: ${JSON.stringify(data.components.inspect_response, null, 2)}`,
|
|
501
|
+
`Created By: ${data.createdBy}`,
|
|
502
|
+
`Created At: ${data.createdAt ? data.createdAt.toISOString() : "N/A"}`,
|
|
503
|
+
`Updated By: ${data.updatedBy ?? "N/A"}`,
|
|
504
|
+
`Updated At: ${data.updatedAt?.toISOString() ?? "N/A"}`,
|
|
418
505
|
].join("\n");
|
|
419
506
|
return {
|
|
420
|
-
content: [{ type: "text", text:
|
|
507
|
+
content: [{ type: "text", text: formattedRecipe }],
|
|
421
508
|
};
|
|
422
509
|
});
|
|
423
|
-
server.tool(getToolName("
|
|
424
|
-
query: zod_1.z
|
|
510
|
+
server.tool(getToolName("generate_rule_recipe"), "Generate a rule recipe configuration using Impart's AI (Sparkle). Returns a JSON configuration that can be saved as a rule recipe.", {
|
|
511
|
+
query: zod_1.z
|
|
512
|
+
.string()
|
|
513
|
+
.describe('Natural language description of the rule recipe to generate (e.g., "Create a rate limit rule that blocks requests after 100 requests per minute")'),
|
|
425
514
|
}, async ({ query }) => {
|
|
515
|
+
if (!query) {
|
|
516
|
+
return {
|
|
517
|
+
content: [{ type: "text", text: "Error: query is required." }],
|
|
518
|
+
};
|
|
519
|
+
}
|
|
520
|
+
// The Sparkle API expects the query field to be a JSON-encoded string
|
|
521
|
+
// containing the agent-specific input format: {"prompt": "..."}
|
|
522
|
+
const agentQuery = JSON.stringify({
|
|
523
|
+
prompt: query,
|
|
524
|
+
});
|
|
426
525
|
const payload = {
|
|
427
|
-
agent: "
|
|
428
|
-
query,
|
|
526
|
+
agent: "rule_recipe_generator",
|
|
527
|
+
query: agentQuery,
|
|
429
528
|
};
|
|
529
|
+
console.error(`[generate_rule_recipe] Calling Sparkle API with payload:`, JSON.stringify(payload, null, 2));
|
|
430
530
|
const data = await makeImpartRequest("sparkle", undefined, "POST", payload);
|
|
531
|
+
console.error(`[generate_rule_recipe] Response received:`, data ? "Success" : "Failure");
|
|
431
532
|
if (!data) {
|
|
533
|
+
const lastError = makeImpartRequest.lastError || "Unknown error";
|
|
432
534
|
return {
|
|
433
|
-
content: [
|
|
434
|
-
{ type: "text", text: "Error: Failed to generate rule script" },
|
|
435
|
-
],
|
|
535
|
+
content: [{ type: "text", text: `Unable to generate rule recipe: ${lastError}` }],
|
|
436
536
|
};
|
|
437
537
|
}
|
|
438
538
|
if (!data.output) {
|
|
439
539
|
return {
|
|
440
|
-
content: [
|
|
441
|
-
{
|
|
442
|
-
type: "text",
|
|
443
|
-
text: "Error: No output received from the Sparkle API",
|
|
444
|
-
},
|
|
445
|
-
],
|
|
540
|
+
content: [{ type: "text", text: "Error: No output received from the Sparkle API" }],
|
|
446
541
|
};
|
|
447
542
|
}
|
|
448
543
|
return {
|
|
449
544
|
content: [
|
|
450
|
-
{
|
|
545
|
+
{
|
|
546
|
+
type: "text",
|
|
547
|
+
text: `Generated Rule Recipe Configuration:\n\n${data.output}\n\nYou can save this configuration using the create_rule_recipe tool.`,
|
|
548
|
+
},
|
|
451
549
|
],
|
|
452
550
|
};
|
|
453
551
|
});
|
|
454
|
-
server.tool(getToolName("
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
552
|
+
server.tool(getToolName("create_rule_recipe"), "Create a new rule recipe from a configuration. IMPORTANT: The 'components' field requires a complex nested structure with clauses, thresholds, conditions, and actions. You MUST use the output from generate_rule_recipe (Sparkle AI) or copy from an existing rule recipe - do not manually create the components structure as it has a very specific schema.", {
|
|
553
|
+
name: zod_1.z.string().min(1).max(64).describe("The name of the rule recipe"),
|
|
554
|
+
description: zod_1.z.string().min(1).max(1024).optional().describe("Optional description of the rule recipe"),
|
|
555
|
+
disabled: zod_1.z.boolean().default(false).describe("Whether the rule recipe is disabled"),
|
|
556
|
+
blocking_effect: zod_1.z
|
|
557
|
+
.enum(["block", "simulate"])
|
|
558
|
+
.default("block")
|
|
559
|
+
.optional()
|
|
560
|
+
.describe("Effect when rule blocks (block or simulate)"),
|
|
561
|
+
labels: zod_1.z.array(zod_1.z.string()).optional().nullable().describe("Optional list of label slugs"),
|
|
562
|
+
components: zod_1.z
|
|
459
563
|
.string()
|
|
460
|
-
.
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
}, async ({ src, lang }) => {
|
|
464
|
-
if (!src) {
|
|
564
|
+
.describe("JSON string containing the rule recipe components (rate_limit, inspect_request, enforce, inspect_response)"),
|
|
565
|
+
}, async ({ name, description, disabled, blocking_effect, labels, components }) => {
|
|
566
|
+
if (!name || !components) {
|
|
465
567
|
return {
|
|
466
|
-
content: [
|
|
467
|
-
{
|
|
468
|
-
type: "text",
|
|
469
|
-
text: "Error: The source code (src) is required and must be base64 encoded.",
|
|
470
|
-
},
|
|
471
|
-
],
|
|
568
|
+
content: [{ type: "text", text: "Error: name and components are required." }],
|
|
472
569
|
};
|
|
473
570
|
}
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
if (lang !== null && lang !== undefined) {
|
|
478
|
-
payload["lang"] = lang;
|
|
571
|
+
let parsedComponents;
|
|
572
|
+
try {
|
|
573
|
+
parsedComponents = JSON.parse(components);
|
|
479
574
|
}
|
|
480
|
-
|
|
481
|
-
if (!data) {
|
|
575
|
+
catch (e) {
|
|
482
576
|
return {
|
|
483
|
-
content: [{ type: "text", text: "
|
|
577
|
+
content: [{ type: "text", text: "Error: components must be valid JSON" }],
|
|
484
578
|
};
|
|
485
579
|
}
|
|
486
|
-
|
|
487
|
-
if (
|
|
580
|
+
// Remove blocking_effect from components if present - it should be a top-level field
|
|
581
|
+
if (parsedComponents.blocking_effect) {
|
|
582
|
+
delete parsedComponents.blocking_effect;
|
|
583
|
+
}
|
|
584
|
+
if (parsedComponents.blockingEffect) {
|
|
585
|
+
delete parsedComponents.blockingEffect;
|
|
586
|
+
}
|
|
587
|
+
const payload = {
|
|
588
|
+
name,
|
|
589
|
+
disabled,
|
|
590
|
+
components: parsedComponents,
|
|
591
|
+
};
|
|
592
|
+
if (description !== null && description !== undefined) {
|
|
593
|
+
payload["description"] = description;
|
|
594
|
+
}
|
|
595
|
+
if (blocking_effect !== null && blocking_effect !== undefined) {
|
|
596
|
+
payload["blocking_effect"] = blocking_effect;
|
|
597
|
+
}
|
|
598
|
+
if (labels !== null && labels !== undefined) {
|
|
599
|
+
payload["labels"] = labels;
|
|
600
|
+
}
|
|
601
|
+
console.error(`[create_rule_recipe] Payload:`, JSON.stringify(payload, null, 2));
|
|
602
|
+
const data = await makeImpartRequest("rule_recipes", undefined, "POST", payload);
|
|
603
|
+
if (!data) {
|
|
604
|
+
const lastError = makeImpartRequest.lastError || "Unknown error";
|
|
488
605
|
return {
|
|
489
|
-
content: [
|
|
490
|
-
{
|
|
491
|
-
type: "text",
|
|
492
|
-
text: "Validation successful! No errors or warnings found.",
|
|
493
|
-
},
|
|
494
|
-
],
|
|
606
|
+
content: [{ type: "text", text: `Unable to create rule recipe: ${lastError}` }],
|
|
495
607
|
};
|
|
496
608
|
}
|
|
497
|
-
const
|
|
609
|
+
const formattedRecipe = [
|
|
610
|
+
"Rule recipe created successfully:",
|
|
611
|
+
`ID: ${data.id}`,
|
|
612
|
+
`Name: ${data.name}`,
|
|
613
|
+
`Description: ${data.description ?? "N/A"}`,
|
|
614
|
+
`Disabled: ${data.disabled}`,
|
|
615
|
+
`Blocking Effect: ${data.blockingEffect}`,
|
|
616
|
+
`Labels: ${data.labels.join(", ")}`,
|
|
617
|
+
`Created At: ${data.createdAt ? data.createdAt.toISOString() : "N/A"}`,
|
|
618
|
+
].join("\n");
|
|
498
619
|
return {
|
|
499
|
-
content: [
|
|
500
|
-
{
|
|
501
|
-
type: "text",
|
|
502
|
-
text: "Validation completed with the following issues:\n" +
|
|
503
|
-
formattedDiagnostics.join("\n"),
|
|
504
|
-
},
|
|
505
|
-
],
|
|
620
|
+
content: [{ type: "text", text: formattedRecipe }],
|
|
506
621
|
};
|
|
507
622
|
});
|
|
508
623
|
server.tool(getToolName("list_endpoints"), "Get an inventory of endpoints for the organization", {
|
|
624
|
+
page: zod_1.z
|
|
625
|
+
.number()
|
|
626
|
+
.default(1)
|
|
627
|
+
.optional()
|
|
628
|
+
.nullable()
|
|
629
|
+
.describe("The page of results to return (minimum value: 1, e.g., 1, 2, 3)"),
|
|
509
630
|
max_results: zod_1.z
|
|
510
631
|
.number()
|
|
511
632
|
.default(100)
|
|
633
|
+
.optional()
|
|
634
|
+
.nullable()
|
|
635
|
+
.describe("The maximum number of results to return (e.g., 10, 50, 100)"),
|
|
636
|
+
is_deprecated: zod_1.z.enum(["true", "false"]).optional().describe("Filter by deprecated status"),
|
|
637
|
+
is_orphaned: zod_1.z.enum(["true", "false"]).optional().describe("Filter by orphaned status"),
|
|
638
|
+
source: zod_1.z.enum(["learned", "provided"]).optional().describe("Filter by source"),
|
|
639
|
+
search: zod_1.z.string().optional().describe("Filter by search string"),
|
|
640
|
+
spec_id: zod_1.z.array(zod_1.z.string()).optional().nullable().describe("Filter by spec IDs"),
|
|
641
|
+
collection_id: zod_1.z.array(zod_1.z.string()).optional().nullable().describe("Filter by collection IDs"),
|
|
642
|
+
tag: zod_1.z.array(zod_1.z.string()).optional().nullable().describe("Filter by tags"),
|
|
643
|
+
method: zod_1.z.array(zod_1.z.string()).optional().nullable().describe("Filter by methods"),
|
|
644
|
+
label: zod_1.z
|
|
645
|
+
.array(zod_1.z.string())
|
|
646
|
+
.optional()
|
|
512
647
|
.nullable()
|
|
513
|
-
.describe(
|
|
514
|
-
}, async ({ max_results }) => {
|
|
648
|
+
.describe('Filter by label slugs (e.g., ["llm-protection", "waf"])'),
|
|
649
|
+
}, async ({ page, max_results, is_deprecated, is_orphaned, source, search, spec_id, collection_id, tag, method, label, }) => {
|
|
515
650
|
const queryParams = {};
|
|
651
|
+
if (page !== null && page !== undefined) {
|
|
652
|
+
queryParams["page"] = page;
|
|
653
|
+
}
|
|
516
654
|
if (max_results !== null && max_results !== undefined) {
|
|
517
655
|
queryParams["max_results"] = max_results;
|
|
518
656
|
}
|
|
657
|
+
if (is_deprecated !== null && is_deprecated !== undefined) {
|
|
658
|
+
queryParams["is_deprecated"] = is_deprecated;
|
|
659
|
+
}
|
|
660
|
+
if (is_orphaned !== null && is_orphaned !== undefined) {
|
|
661
|
+
queryParams["is_orphaned"] = is_orphaned;
|
|
662
|
+
}
|
|
663
|
+
if (source !== null && source !== undefined) {
|
|
664
|
+
queryParams["source"] = source;
|
|
665
|
+
}
|
|
666
|
+
if (search !== null && search !== undefined) {
|
|
667
|
+
queryParams["search"] = search;
|
|
668
|
+
}
|
|
669
|
+
if (spec_id !== null && spec_id !== undefined) {
|
|
670
|
+
queryParams["spec_id"] = spec_id;
|
|
671
|
+
}
|
|
672
|
+
if (collection_id !== null && collection_id !== undefined) {
|
|
673
|
+
queryParams["collection_id"] = collection_id;
|
|
674
|
+
}
|
|
675
|
+
if (tag !== null && tag !== undefined) {
|
|
676
|
+
queryParams["tag"] = tag;
|
|
677
|
+
}
|
|
678
|
+
if (method !== null && method !== undefined) {
|
|
679
|
+
queryParams["method"] = method;
|
|
680
|
+
}
|
|
681
|
+
if (label !== null && label !== undefined) {
|
|
682
|
+
queryParams["label"] = label;
|
|
683
|
+
}
|
|
519
684
|
const data = await makeImpartRequest("endpoints", queryParams);
|
|
520
685
|
if (!data) {
|
|
686
|
+
const lastError = makeImpartRequest.lastError || "Unknown error";
|
|
521
687
|
return {
|
|
522
|
-
content: [
|
|
523
|
-
{ type: "text", text: "Unable to fetch endpoints: Unknown error" },
|
|
524
|
-
],
|
|
688
|
+
content: [{ type: "text", text: `Unable to fetch endpoints: ${lastError}` }],
|
|
525
689
|
};
|
|
526
690
|
}
|
|
527
691
|
const endpoints = data.items || [];
|
|
528
692
|
if (endpoints.length === 0) {
|
|
529
693
|
return {
|
|
530
|
-
content: [
|
|
531
|
-
{ type: "text", text: "No endpoints found for this organization." },
|
|
532
|
-
],
|
|
694
|
+
content: [{ type: "text", text: "No endpoints found for this organization." }],
|
|
533
695
|
};
|
|
534
696
|
}
|
|
535
697
|
const formattedEndpoints = endpoints.map((endpoint) => [
|
|
536
698
|
`ID: ${endpoint.id}`,
|
|
537
|
-
`Spec ID: ${endpoint.
|
|
699
|
+
`Spec ID: ${endpoint.specId}`,
|
|
538
700
|
`Path: ${endpoint.path}`,
|
|
539
701
|
`Method: ${endpoint.method}`,
|
|
540
702
|
`Source: ${endpoint.source}`,
|
|
@@ -546,39 +708,54 @@ server.tool(getToolName("list_endpoints"), "Get an inventory of endpoints for th
|
|
|
546
708
|
};
|
|
547
709
|
});
|
|
548
710
|
server.tool(getToolName("list_tags"), "Get a list of tags for your Impart organization", {
|
|
711
|
+
page: zod_1.z
|
|
712
|
+
.number()
|
|
713
|
+
.default(1)
|
|
714
|
+
.optional()
|
|
715
|
+
.nullable()
|
|
716
|
+
.describe("The page of results to return (minimum value: 1, e.g., 1, 2, 3)"),
|
|
549
717
|
max_results: zod_1.z
|
|
550
718
|
.number()
|
|
551
719
|
.default(100)
|
|
720
|
+
.optional()
|
|
552
721
|
.nullable()
|
|
553
|
-
.describe("The maximum number of results to return"),
|
|
554
|
-
|
|
722
|
+
.describe("The maximum number of results to return (e.g., 10, 50, 100)"),
|
|
723
|
+
label: zod_1.z
|
|
724
|
+
.array(zod_1.z.string())
|
|
725
|
+
.optional()
|
|
726
|
+
.nullable()
|
|
727
|
+
.describe('Filter by label slugs (e.g., ["llm-protection", "waf"])'),
|
|
728
|
+
}, async ({ page, max_results, label }) => {
|
|
555
729
|
const queryParams = {};
|
|
730
|
+
if (page !== null && page !== undefined) {
|
|
731
|
+
queryParams["page"] = page;
|
|
732
|
+
}
|
|
556
733
|
if (max_results !== null && max_results !== undefined) {
|
|
557
734
|
queryParams["max_results"] = max_results;
|
|
558
735
|
}
|
|
736
|
+
if (label !== null && label !== undefined) {
|
|
737
|
+
queryParams["label"] = label;
|
|
738
|
+
}
|
|
559
739
|
const data = await makeImpartRequest("tags", queryParams);
|
|
560
740
|
if (!data) {
|
|
741
|
+
const lastError = makeImpartRequest.lastError || "Unknown error";
|
|
561
742
|
return {
|
|
562
|
-
content: [
|
|
563
|
-
{ type: "text", text: "Unable to fetch tags: Unknown error" },
|
|
564
|
-
],
|
|
743
|
+
content: [{ type: "text", text: `Unable to fetch tags: ${lastError}` }],
|
|
565
744
|
};
|
|
566
745
|
}
|
|
567
746
|
const tags = data.items || [];
|
|
568
747
|
if (tags.length === 0) {
|
|
569
748
|
return {
|
|
570
|
-
content: [
|
|
571
|
-
{ type: "text", text: "No tags found for this organization." },
|
|
572
|
-
],
|
|
749
|
+
content: [{ type: "text", text: "No tags found for this organization." }],
|
|
573
750
|
};
|
|
574
751
|
}
|
|
575
752
|
const formattedTags = tags.map((tag) => [
|
|
576
753
|
`Name: ${tag.name}`,
|
|
577
|
-
`Display Name: ${tag.
|
|
754
|
+
`Display Name: ${tag.displayName}`,
|
|
578
755
|
`Type: ${tag.type}`,
|
|
579
756
|
`Description: ${tag.description ?? "N/A"}`,
|
|
580
|
-
`Risk Statement: ${tag.
|
|
581
|
-
`External URL: ${tag.
|
|
757
|
+
`Risk Statement: ${tag.riskStatement ?? "N/A"}`,
|
|
758
|
+
`External URL: ${tag.externalUrl ?? "N/A"}`,
|
|
582
759
|
`Category: ${tag.category}`,
|
|
583
760
|
"---",
|
|
584
761
|
].join("\n"));
|
|
@@ -586,54 +763,72 @@ server.tool(getToolName("list_tags"), "Get a list of tags for your Impart organi
|
|
|
586
763
|
content: [{ type: "text", text: formattedTags.join("\n") }],
|
|
587
764
|
};
|
|
588
765
|
});
|
|
589
|
-
server.tool(getToolName("get_tag_metrics"), "Get timeseries metrics for one or more tags"
|
|
766
|
+
server.tool(getToolName("get_tag_metrics"), "Get timeseries metrics for one or more tags. Returns multiple time series datasets when multiple metrics are requested. Examples: \n\n" +
|
|
767
|
+
'• Single metric: {"metric": ["http-request.count"]}\n' +
|
|
768
|
+
'• Multiple metrics: {"metric": ["http-request.count", "attack.count", "blocked.count"]}\n' +
|
|
769
|
+
'• With time range: {"metric": ["http-request.count"], "from": "-7d", "until": "-0h"}\n' +
|
|
770
|
+
'• With filtering: {"metric": ["attack.count"], "spec_id": ["spec-123"], "rollup": "1h"}\n' +
|
|
771
|
+
'• Complex query: {"metric": ["http-request.count", "blocked.count"], "from": "-24h", "rollup": "1h", "unlearned": "exclude"}', {
|
|
772
|
+
max_results: zod_1.z
|
|
773
|
+
.number()
|
|
774
|
+
.default(100)
|
|
775
|
+
.nullable()
|
|
776
|
+
.describe("The maximum number of results to return (e.g., 10, 50, 100)"),
|
|
590
777
|
metric: zod_1.z
|
|
591
778
|
.array(zod_1.z.string())
|
|
592
779
|
.default(["http-request.count"])
|
|
593
|
-
.nullable()
|
|
594
|
-
.describe("List of metric names"),
|
|
595
|
-
from_time: zod_1.z.string().default("-1d").nullable().describe("Start time"),
|
|
596
|
-
until_time: zod_1.z.string().default("-0h").nullable().describe("End time"),
|
|
597
|
-
spec_id: zod_1.z
|
|
598
|
-
.array(zod_1.z.string())
|
|
599
|
-
.optional()
|
|
600
|
-
.nullable()
|
|
601
|
-
.describe("Optional list of spec IDs"),
|
|
602
|
-
collection_id: zod_1.z
|
|
603
|
-
.array(zod_1.z.string())
|
|
604
780
|
.optional()
|
|
605
781
|
.nullable()
|
|
606
|
-
.describe("
|
|
607
|
-
|
|
608
|
-
.
|
|
782
|
+
.describe("List of metric names. Common metrics: 'http-request.count', 'attack.count', 'blocked.count', 'allowed.count'"),
|
|
783
|
+
from: zod_1.z
|
|
784
|
+
.string()
|
|
785
|
+
.default("-1d")
|
|
609
786
|
.optional()
|
|
610
787
|
.nullable()
|
|
611
|
-
.describe("
|
|
612
|
-
|
|
788
|
+
.describe("Start time (relative like '-1d', '-24h', '-7d' or absolute timestamp)"),
|
|
789
|
+
until: zod_1.z
|
|
613
790
|
.string()
|
|
791
|
+
.default("-0h")
|
|
614
792
|
.optional()
|
|
615
793
|
.nullable()
|
|
616
|
-
.describe("
|
|
794
|
+
.describe("End time (relative like '-0h', '-1h' or absolute timestamp)"),
|
|
795
|
+
spec_id: zod_1.z.array(zod_1.z.string()).optional().nullable().describe("Optional list of spec IDs to filter by"),
|
|
796
|
+
collection_id: zod_1.z.array(zod_1.z.string()).optional().nullable().describe("Optional list of collection IDs to filter by"),
|
|
797
|
+
endpoint_id: zod_1.z.array(zod_1.z.string()).optional().nullable().describe("Optional list of endpoint IDs to filter by"),
|
|
798
|
+
rollup: zod_1.z.string().optional().describe("Time window rollup resolution (e.g., '1h', '15m', '1d')"),
|
|
617
799
|
unlearned: zod_1.z
|
|
618
|
-
.
|
|
800
|
+
.enum(["exclude", "include", "only"])
|
|
619
801
|
.default("include")
|
|
620
|
-
.
|
|
621
|
-
.describe("How to handle unlearned endpoints"),
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
.
|
|
627
|
-
|
|
802
|
+
.optional()
|
|
803
|
+
.describe("How to handle unlearned endpoints: 'exclude' (ignore), 'include' (default), 'only' (only unlearned)"),
|
|
804
|
+
}, async ({ metric, from, until, spec_id, collection_id, endpoint_id, rollup, unlearned, max_results }) => {
|
|
805
|
+
// Validate time format parameters
|
|
806
|
+
if (from) {
|
|
807
|
+
const fromCheck = validateTimeFormat(from);
|
|
808
|
+
if (!fromCheck.valid) {
|
|
809
|
+
return {
|
|
810
|
+
content: [{ type: "text", text: fromCheck.error }],
|
|
811
|
+
};
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
if (until) {
|
|
815
|
+
const untilCheck = validateTimeFormat(until);
|
|
816
|
+
if (!untilCheck.valid) {
|
|
817
|
+
return {
|
|
818
|
+
content: [{ type: "text", text: untilCheck.error }],
|
|
819
|
+
};
|
|
820
|
+
}
|
|
821
|
+
}
|
|
628
822
|
const queryParams = {};
|
|
629
823
|
if (metric !== null && metric !== undefined) {
|
|
630
|
-
|
|
824
|
+
// Convert array to comma-separated string for metrics API
|
|
825
|
+
queryParams["metric"] = Array.isArray(metric) ? metric.join(",") : metric;
|
|
631
826
|
}
|
|
632
|
-
if (
|
|
633
|
-
queryParams["from"] =
|
|
827
|
+
if (from !== null && from !== undefined) {
|
|
828
|
+
queryParams["from"] = from;
|
|
634
829
|
}
|
|
635
|
-
if (
|
|
636
|
-
queryParams["until"] =
|
|
830
|
+
if (until !== null && until !== undefined) {
|
|
831
|
+
queryParams["until"] = until;
|
|
637
832
|
}
|
|
638
833
|
if (spec_id !== null && spec_id !== undefined) {
|
|
639
834
|
queryParams["spec_id"] = spec_id;
|
|
@@ -659,7 +854,7 @@ server.tool(getToolName("get_tag_metrics"), "Get timeseries metrics for one or m
|
|
|
659
854
|
content: [
|
|
660
855
|
{
|
|
661
856
|
type: "text",
|
|
662
|
-
text:
|
|
857
|
+
text: `Unable to fetch timeseries metrics: ${makeImpartRequest.lastError || "Unknown error"}`,
|
|
663
858
|
},
|
|
664
859
|
],
|
|
665
860
|
};
|
|
@@ -677,10 +872,10 @@ server.tool(getToolName("get_tag_metrics"), "Get timeseries metrics for one or m
|
|
|
677
872
|
}
|
|
678
873
|
const formattedMetrics = metrics.map((metric) => [
|
|
679
874
|
`Metric: ${metric.metric}`,
|
|
680
|
-
`From: ${metric.from}`,
|
|
681
|
-
`Until: ${metric.until}`,
|
|
875
|
+
`From: ${metric.from ? metric.from.toISOString() : "N/A"}`,
|
|
876
|
+
`Until: ${metric.until ? metric.until.toISOString() : "N/A"}`,
|
|
682
877
|
`Increment: ${metric.inc} seconds`,
|
|
683
|
-
`Total Points: ${metric.
|
|
878
|
+
`Total Points: ${metric.totalPoints}`,
|
|
684
879
|
`Data: ${metric.data.join(", ")}`,
|
|
685
880
|
"---",
|
|
686
881
|
].join("\n"));
|
|
@@ -692,13 +887,15 @@ server.tool(getToolName("list_observed_hosts"), "Get a list of observed hosts fo
|
|
|
692
887
|
page: zod_1.z
|
|
693
888
|
.number()
|
|
694
889
|
.default(1)
|
|
890
|
+
.optional()
|
|
695
891
|
.nullable()
|
|
696
|
-
.describe("The page of results to return (minimum value: 1)"),
|
|
892
|
+
.describe("The page of results to return (minimum value: 1, e.g., 1, 2, 3)"),
|
|
697
893
|
max_results: zod_1.z
|
|
698
894
|
.number()
|
|
699
895
|
.default(100)
|
|
896
|
+
.optional()
|
|
700
897
|
.nullable()
|
|
701
|
-
.describe("The maximum number of results to return"),
|
|
898
|
+
.describe("The maximum number of results to return (e.g., 10, 50, 100)"),
|
|
702
899
|
}, async ({ page, max_results }) => {
|
|
703
900
|
const queryParams = {};
|
|
704
901
|
if (page !== null && page !== undefined) {
|
|
@@ -713,7 +910,7 @@ server.tool(getToolName("list_observed_hosts"), "Get a list of observed hosts fo
|
|
|
713
910
|
content: [
|
|
714
911
|
{
|
|
715
912
|
type: "text",
|
|
716
|
-
text:
|
|
913
|
+
text: `Unable to fetch observed hosts: ${makeImpartRequest.lastError || "Unknown error"}`,
|
|
717
914
|
},
|
|
718
915
|
],
|
|
719
916
|
};
|
|
@@ -731,9 +928,12 @@ server.tool(getToolName("list_observed_hosts"), "Get a list of observed hosts fo
|
|
|
731
928
|
}
|
|
732
929
|
const formattedHosts = hosts.map((host) => [
|
|
733
930
|
`ID: ${host.id}`,
|
|
734
|
-
`
|
|
735
|
-
`
|
|
736
|
-
`
|
|
931
|
+
`Hostname: ${host.hostname}`,
|
|
932
|
+
`Port: ${host.port}`,
|
|
933
|
+
`Created At: ${host.createdAt ? host.createdAt.toISOString() : "N/A"}`,
|
|
934
|
+
`Observed At: ${host.observedAt ? host.observedAt.toISOString() : "N/A"}`,
|
|
935
|
+
`Suspected: ${host.suspected}`,
|
|
936
|
+
`Env: ${host.env}`,
|
|
737
937
|
"---",
|
|
738
938
|
].join("\n"));
|
|
739
939
|
return {
|
|
@@ -741,15 +941,11 @@ server.tool(getToolName("list_observed_hosts"), "Get a list of observed hosts fo
|
|
|
741
941
|
};
|
|
742
942
|
});
|
|
743
943
|
server.tool(getToolName("get_observed_host"), "Get details of a specific observed host", {
|
|
744
|
-
observed_host_id: zod_1.z
|
|
745
|
-
.string()
|
|
746
|
-
.describe("The unique identifier of the observed host"),
|
|
944
|
+
observed_host_id: zod_1.z.string().describe("The unique identifier of the observed host"),
|
|
747
945
|
}, async ({ observed_host_id }) => {
|
|
748
946
|
if (!observed_host_id) {
|
|
749
947
|
return {
|
|
750
|
-
content: [
|
|
751
|
-
{ type: "text", text: "Error: observed_host_id is required." },
|
|
752
|
-
],
|
|
948
|
+
content: [{ type: "text", text: "Error: observed_host_id is required." }],
|
|
753
949
|
};
|
|
754
950
|
}
|
|
755
951
|
const data = await makeImpartRequest(`observed_hosts/${observed_host_id}`);
|
|
@@ -758,23 +954,24 @@ server.tool(getToolName("get_observed_host"), "Get details of a specific observe
|
|
|
758
954
|
content: [
|
|
759
955
|
{
|
|
760
956
|
type: "text",
|
|
761
|
-
text:
|
|
957
|
+
text: `Unable to fetch observed host: ${makeImpartRequest.lastError || "Unknown error"}`,
|
|
762
958
|
},
|
|
763
959
|
],
|
|
764
960
|
};
|
|
765
961
|
}
|
|
766
962
|
const formattedHost = [
|
|
767
963
|
`ID: ${data.id}`,
|
|
768
|
-
`Hostname: ${data.hostname
|
|
769
|
-
`Port: ${data.port
|
|
770
|
-
`Created At: ${data.
|
|
771
|
-
`Observed At: ${data.
|
|
772
|
-
`Suspected
|
|
964
|
+
`Hostname: ${data.hostname}`,
|
|
965
|
+
`Port: ${data.port}`,
|
|
966
|
+
`Created At: ${data.createdAt ? data.createdAt.toISOString() : "N/A"}`,
|
|
967
|
+
`Observed At: ${data.observedAt ? data.observedAt.toISOString() : "N/A"}`,
|
|
968
|
+
`Suspected: ${data.suspected}`,
|
|
969
|
+
`Env: ${data.env}`,
|
|
773
970
|
"---",
|
|
774
971
|
"Related IDs:",
|
|
775
972
|
];
|
|
776
|
-
if (data.
|
|
777
|
-
data.
|
|
973
|
+
if (data.relatedIds && data.relatedIds.length > 0) {
|
|
974
|
+
data.relatedIds.forEach((id) => formattedHost.push(` - Binding ID: ${id.bindingId}, Spec ID: ${id.specId}`));
|
|
778
975
|
}
|
|
779
976
|
else {
|
|
780
977
|
formattedHost.push(" No related IDs");
|
|
@@ -787,14 +984,17 @@ server.tool(getToolName("list_specs"), "Get a list of specs for your Impart orga
|
|
|
787
984
|
page: zod_1.z
|
|
788
985
|
.number()
|
|
789
986
|
.default(1)
|
|
987
|
+
.optional()
|
|
790
988
|
.nullable()
|
|
791
|
-
.describe("The page of results to return (minimum value: 1)"),
|
|
989
|
+
.describe("The page of results to return (minimum value: 1, e.g., 1, 2, 3)"),
|
|
792
990
|
max_results: zod_1.z
|
|
793
991
|
.number()
|
|
794
992
|
.default(100)
|
|
993
|
+
.optional()
|
|
795
994
|
.nullable()
|
|
796
|
-
.describe("The maximum number of results to return"),
|
|
797
|
-
|
|
995
|
+
.describe("The maximum number of results to return (e.g., 10, 50, 100)"),
|
|
996
|
+
search: zod_1.z.string().optional().describe("Search string to filter specs"),
|
|
997
|
+
}, async ({ page, max_results, search }) => {
|
|
798
998
|
const queryParams = {};
|
|
799
999
|
if (page !== null && page !== undefined) {
|
|
800
1000
|
queryParams["page"] = validatePage(page);
|
|
@@ -802,20 +1002,20 @@ server.tool(getToolName("list_specs"), "Get a list of specs for your Impart orga
|
|
|
802
1002
|
if (max_results !== null && max_results !== undefined) {
|
|
803
1003
|
queryParams["max_results"] = max_results;
|
|
804
1004
|
}
|
|
1005
|
+
if (search !== null && search !== undefined) {
|
|
1006
|
+
queryParams["search"] = search;
|
|
1007
|
+
}
|
|
805
1008
|
const data = await makeImpartRequest("specs", queryParams);
|
|
806
1009
|
if (!data) {
|
|
1010
|
+
const lastError = makeImpartRequest.lastError || "Unknown error";
|
|
807
1011
|
return {
|
|
808
|
-
content: [
|
|
809
|
-
{ type: "text", text: "Unable to fetch specs: Unknown error" },
|
|
810
|
-
],
|
|
1012
|
+
content: [{ type: "text", text: `Unable to fetch specs: ${lastError}` }],
|
|
811
1013
|
};
|
|
812
1014
|
}
|
|
813
1015
|
const specs = data.items || [];
|
|
814
1016
|
if (specs.length === 0) {
|
|
815
1017
|
return {
|
|
816
|
-
content: [
|
|
817
|
-
{ type: "text", text: "No specs found for this organization." },
|
|
818
|
-
],
|
|
1018
|
+
content: [{ type: "text", text: "No specs found for this organization." }],
|
|
819
1019
|
};
|
|
820
1020
|
}
|
|
821
1021
|
const formattedSpecs = specs.map((spec) => [
|
|
@@ -823,10 +1023,10 @@ server.tool(getToolName("list_specs"), "Get a list of specs for your Impart orga
|
|
|
823
1023
|
`Name: ${spec.name}`,
|
|
824
1024
|
`Revision: ${spec.revision}`,
|
|
825
1025
|
`Score: ${spec.score}`,
|
|
826
|
-
`Created By: ${spec.
|
|
827
|
-
`Created At: ${spec.
|
|
828
|
-
`Updated By: ${spec.
|
|
829
|
-
`Updated At: ${spec.
|
|
1026
|
+
`Created By: ${spec.createdBy}`,
|
|
1027
|
+
`Created At: ${spec.createdAt ? spec.createdAt.toISOString() : "N/A"}`,
|
|
1028
|
+
`Updated By: ${spec.updatedBy}`,
|
|
1029
|
+
`Updated At: ${spec.updatedAt?.toISOString() ?? "N/A"}`,
|
|
830
1030
|
"---",
|
|
831
1031
|
].join("\n"));
|
|
832
1032
|
return {
|
|
@@ -835,11 +1035,7 @@ server.tool(getToolName("list_specs"), "Get a list of specs for your Impart orga
|
|
|
835
1035
|
});
|
|
836
1036
|
server.tool(getToolName("get_spec"), "Get details of a specific spec", {
|
|
837
1037
|
spec_id: zod_1.z.string().describe("The unique identifier of the spec"),
|
|
838
|
-
revision: zod_1.z
|
|
839
|
-
.number()
|
|
840
|
-
.optional()
|
|
841
|
-
.nullable()
|
|
842
|
-
.describe("The specific revision to fetch"),
|
|
1038
|
+
revision: zod_1.z.number().optional().nullable().describe("The specific revision to fetch"),
|
|
843
1039
|
}, async ({ spec_id, revision }) => {
|
|
844
1040
|
if (!spec_id) {
|
|
845
1041
|
return {
|
|
@@ -852,10 +1048,9 @@ server.tool(getToolName("get_spec"), "Get details of a specific spec", {
|
|
|
852
1048
|
}
|
|
853
1049
|
const data = await makeImpartRequest(`specs/${spec_id}`, queryParams);
|
|
854
1050
|
if (!data) {
|
|
1051
|
+
const lastError = makeImpartRequest.lastError || "Unknown error";
|
|
855
1052
|
return {
|
|
856
|
-
content: [
|
|
857
|
-
{ type: "text", text: "Unable to fetch spec: Unknown error" },
|
|
858
|
-
],
|
|
1053
|
+
content: [{ type: "text", text: `Unable to fetch spec: ${lastError}` }],
|
|
859
1054
|
};
|
|
860
1055
|
}
|
|
861
1056
|
const formattedSpec = [
|
|
@@ -863,16 +1058,16 @@ server.tool(getToolName("get_spec"), "Get details of a specific spec", {
|
|
|
863
1058
|
`Name: ${data.name}`,
|
|
864
1059
|
`Revision: ${data.revision}`,
|
|
865
1060
|
`Score: ${data.score}`,
|
|
866
|
-
`Created By: ${data.
|
|
867
|
-
`Created At: ${data.
|
|
868
|
-
`Updated By: ${data.
|
|
869
|
-
`Updated At: ${data.
|
|
1061
|
+
`Created By: ${data.createdBy}`,
|
|
1062
|
+
`Created At: ${data.createdAt ? data.createdAt.toISOString() : "N/A"}`,
|
|
1063
|
+
`Updated By: ${data.updatedBy}`,
|
|
1064
|
+
`Updated At: ${data.updatedAt?.toISOString() ?? "N/A"}`,
|
|
870
1065
|
"---",
|
|
871
1066
|
"Revisions:",
|
|
872
1067
|
];
|
|
873
1068
|
if (data.revisions && data.revisions.length > 0) {
|
|
874
1069
|
data.revisions.forEach((revision) => {
|
|
875
|
-
formattedSpec.push(` Revision: ${revision.revision}`, ` Created By: ${revision.
|
|
1070
|
+
formattedSpec.push(` Revision: ${revision.revision}`, ` Created By: ${revision.createdBy}`, ` Created At: ${revision.createdAt ? revision.createdAt.toISOString() : "N/A"}`, "---");
|
|
876
1071
|
});
|
|
877
1072
|
}
|
|
878
1073
|
formattedSpec.push(`Specification (Base64 Encoded):\n${data.spec ?? "N/A"}`);
|
|
@@ -884,14 +1079,19 @@ server.tool(getToolName("list_api_bindings"), "Get a list of API bindings for th
|
|
|
884
1079
|
page: zod_1.z
|
|
885
1080
|
.number()
|
|
886
1081
|
.default(1)
|
|
1082
|
+
.optional()
|
|
887
1083
|
.nullable()
|
|
888
|
-
.describe("The page of results to return (minimum value: 1)"),
|
|
1084
|
+
.describe("The page of results to return (minimum value: 1, e.g., 1, 2, 3)"),
|
|
889
1085
|
max_results: zod_1.z
|
|
890
1086
|
.number()
|
|
891
1087
|
.default(100)
|
|
1088
|
+
.optional()
|
|
892
1089
|
.nullable()
|
|
893
|
-
.describe("The maximum number of results to return"),
|
|
894
|
-
|
|
1090
|
+
.describe("The maximum number of results to return (e.g., 10, 50, 100)"),
|
|
1091
|
+
search: zod_1.z.string().optional().describe("The search string to filter API bindings"),
|
|
1092
|
+
spec_id: zod_1.z.array(zod_1.z.string()).optional().nullable().describe("Optional list of spec IDs"),
|
|
1093
|
+
collection_id: zod_1.z.array(zod_1.z.string()).optional().nullable().describe("Optional list of collection IDs"),
|
|
1094
|
+
}, async ({ page, max_results, search, spec_id, collection_id }) => {
|
|
895
1095
|
const queryParams = {};
|
|
896
1096
|
if (page !== null && page !== undefined) {
|
|
897
1097
|
queryParams["page"] = validatePage(page);
|
|
@@ -899,12 +1099,20 @@ server.tool(getToolName("list_api_bindings"), "Get a list of API bindings for th
|
|
|
899
1099
|
if (max_results !== null && max_results !== undefined) {
|
|
900
1100
|
queryParams["max_results"] = max_results;
|
|
901
1101
|
}
|
|
1102
|
+
if (search !== null && search !== undefined) {
|
|
1103
|
+
queryParams["search"] = search;
|
|
1104
|
+
}
|
|
1105
|
+
if (spec_id !== null && spec_id !== undefined) {
|
|
1106
|
+
queryParams["spec_id"] = spec_id;
|
|
1107
|
+
}
|
|
1108
|
+
if (collection_id !== null && collection_id !== undefined) {
|
|
1109
|
+
queryParams["collection_id"] = collection_id;
|
|
1110
|
+
}
|
|
902
1111
|
const data = await makeImpartRequest("api_bindings", queryParams);
|
|
903
1112
|
if (!data) {
|
|
1113
|
+
const lastError = makeImpartRequest.lastError || "Unknown error";
|
|
904
1114
|
return {
|
|
905
|
-
content: [
|
|
906
|
-
{ type: "text", text: "Unable to fetch API bindings: Unknown error" },
|
|
907
|
-
],
|
|
1115
|
+
content: [{ type: "text", text: `Unable to fetch API bindings: ${lastError}` }],
|
|
908
1116
|
};
|
|
909
1117
|
}
|
|
910
1118
|
const bindings = data.items || [];
|
|
@@ -921,10 +1129,10 @@ server.tool(getToolName("list_api_bindings"), "Get a list of API bindings for th
|
|
|
921
1129
|
const formattedBindings = bindings.map((binding) => [
|
|
922
1130
|
`ID: ${binding.id}`,
|
|
923
1131
|
`Name: ${binding.name}`,
|
|
924
|
-
`
|
|
925
|
-
`
|
|
926
|
-
`
|
|
927
|
-
`Created At: ${binding.
|
|
1132
|
+
`Host: ${binding.hostname}`,
|
|
1133
|
+
`Type: ${binding.port}`,
|
|
1134
|
+
`Disabled: ${binding.disabled}`,
|
|
1135
|
+
`Created At: ${binding.createdAt ? binding.createdAt.toISOString() : "N/A"}`,
|
|
928
1136
|
"---",
|
|
929
1137
|
].join("\n"));
|
|
930
1138
|
return {
|
|
@@ -932,93 +1140,112 @@ server.tool(getToolName("list_api_bindings"), "Get a list of API bindings for th
|
|
|
932
1140
|
};
|
|
933
1141
|
});
|
|
934
1142
|
server.tool(getToolName("create_api_binding"), "Create a new API binding for the organization", {
|
|
935
|
-
name: zod_1.z.string().describe("The name of the API binding"),
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
patch_operations: zod_1.z
|
|
950
|
-
.array(zod_1.z.record(zod_1.z.any()))
|
|
951
|
-
.optional()
|
|
952
|
-
.nullable()
|
|
953
|
-
.describe("Optional list of JSON patch operations"),
|
|
954
|
-
enable_learning: zod_1.z
|
|
955
|
-
.boolean()
|
|
956
|
-
.default(true)
|
|
957
|
-
.nullable()
|
|
958
|
-
.describe("Whether to enable learning for this binding"),
|
|
959
|
-
}, async ({ name, binding_type, host, api_binding_source, binding_value, description, patch_operations, enable_learning, }) => {
|
|
960
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1143
|
+
name: zod_1.z.string().min(1).max(64).describe("The name of the API binding"),
|
|
1144
|
+
hostname: zod_1.z.string().describe("The hostname to bind to"),
|
|
1145
|
+
port: zod_1.z.number().describe("The port to bind to"),
|
|
1146
|
+
base_path: zod_1.z.string().describe("The base path for the API binding"),
|
|
1147
|
+
spec_id: zod_1.z.string().describe("The ID of the spec to bind to"),
|
|
1148
|
+
disabled: zod_1.z.boolean().default(false).optional().nullable().describe("Whether the API binding is disabled"),
|
|
1149
|
+
upstream_origin: zod_1.z.string().optional().describe("The upstream origin URL for the API binding"),
|
|
1150
|
+
hops: zod_1.z.number().min(-1).max(10).optional().nullable().describe("Number of hops for the API binding"),
|
|
1151
|
+
use_forwarded: zod_1.z.boolean().optional().nullable().describe("Whether to use forwarded headers"),
|
|
1152
|
+
forwarded_for: zod_1.z.array(zod_1.z.string()).optional().nullable().describe("List of forwarded 'for' headers"),
|
|
1153
|
+
forwarded_host: zod_1.z.array(zod_1.z.string()).optional().nullable().describe("List of forwarded 'host' headers"),
|
|
1154
|
+
forwarded_proto: zod_1.z.array(zod_1.z.string()).optional().nullable().describe("List of forwarded 'proto' headers"),
|
|
1155
|
+
forwarded_id: zod_1.z.array(zod_1.z.string()).optional().nullable().describe("List of forwarded IDs"),
|
|
1156
|
+
}, async ({ name, hostname, port, base_path, spec_id, disabled, upstream_origin, hops, use_forwarded, forwarded_for, forwarded_host, forwarded_proto, forwarded_id, }) => {
|
|
961
1157
|
const payload = {
|
|
962
1158
|
name,
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
1159
|
+
hostname,
|
|
1160
|
+
port,
|
|
1161
|
+
base_path,
|
|
1162
|
+
spec_id,
|
|
967
1163
|
};
|
|
968
|
-
if (
|
|
969
|
-
payload["
|
|
1164
|
+
if (disabled !== null && disabled !== undefined) {
|
|
1165
|
+
payload["disabled"] = disabled;
|
|
1166
|
+
}
|
|
1167
|
+
if (upstream_origin !== null && upstream_origin !== undefined) {
|
|
1168
|
+
payload["upstream_origin"] = upstream_origin;
|
|
1169
|
+
}
|
|
1170
|
+
if (hops !== null && hops !== undefined) {
|
|
1171
|
+
payload["hops"] = hops;
|
|
970
1172
|
}
|
|
971
|
-
if (
|
|
972
|
-
payload["
|
|
1173
|
+
if (use_forwarded !== null && use_forwarded !== undefined) {
|
|
1174
|
+
payload["use_forwarded"] = use_forwarded;
|
|
973
1175
|
}
|
|
974
|
-
if (
|
|
975
|
-
payload["
|
|
1176
|
+
if (forwarded_for !== null && forwarded_for !== undefined) {
|
|
1177
|
+
payload["forwarded_for"] = forwarded_for;
|
|
1178
|
+
}
|
|
1179
|
+
if (forwarded_host !== null && forwarded_host !== undefined) {
|
|
1180
|
+
payload["forwarded_host"] = forwarded_host;
|
|
1181
|
+
}
|
|
1182
|
+
if (forwarded_proto !== null && forwarded_proto !== undefined) {
|
|
1183
|
+
payload["forwarded_proto"] = forwarded_proto;
|
|
1184
|
+
}
|
|
1185
|
+
if (forwarded_id !== null && forwarded_id !== undefined) {
|
|
1186
|
+
payload["forwarded_id"] = forwarded_id;
|
|
976
1187
|
}
|
|
977
1188
|
const data = await makeImpartRequest("api_bindings", undefined, "POST", payload);
|
|
978
1189
|
if (!data) {
|
|
1190
|
+
const lastError = makeImpartRequest.lastError || "Unknown error";
|
|
979
1191
|
return {
|
|
980
|
-
content: [{ type: "text", text:
|
|
1192
|
+
content: [{ type: "text", text: `Unable to create API binding: ${lastError}` }],
|
|
981
1193
|
};
|
|
982
1194
|
}
|
|
983
1195
|
const formattedBinding = [
|
|
984
1196
|
"API Binding created successfully:",
|
|
985
1197
|
`ID: ${data.id}`,
|
|
986
1198
|
`Name: ${data.name}`,
|
|
987
|
-
`
|
|
988
|
-
`
|
|
989
|
-
`
|
|
990
|
-
`
|
|
991
|
-
`Description: ${data.description ?? "N/A"}`,
|
|
992
|
-
`Learning Enabled: ${data.enable_learning}`,
|
|
993
|
-
`Created At: ${data.created_at}`,
|
|
1199
|
+
`Host: ${data.hostname}`,
|
|
1200
|
+
`Port: ${data.port}`,
|
|
1201
|
+
`Disabled: ${data.disabled}`,
|
|
1202
|
+
`Created At: ${data.createdAt ? data.createdAt.toISOString() : "N/A"}`,
|
|
994
1203
|
"---",
|
|
995
1204
|
].join("\n");
|
|
996
1205
|
return {
|
|
997
1206
|
content: [{ type: "text", text: formattedBinding }],
|
|
998
1207
|
};
|
|
999
1208
|
});
|
|
1000
|
-
server.tool(getToolName("list_lists"), "Get a list of lists for the organization"
|
|
1209
|
+
server.tool(getToolName("list_lists"), "Get a list of lists for the organization. Lists are collections used for filtering and blocking/allowing specific values. Examples:\n\n" +
|
|
1210
|
+
"• Get all lists: {} (no parameters needed)\n" +
|
|
1211
|
+
'• Filter by kind: {"kind": "ip"} or {"kind": "string"}\n' +
|
|
1212
|
+
'• Search lists: {"search": "blocklist"}\n' +
|
|
1213
|
+
'• With pagination: {"page": 1, "max_results": 25}\n' +
|
|
1214
|
+
'• Filter by labels: {"label": ["security", "waf"]}\n' +
|
|
1215
|
+
'• Combined filters: {"kind": "ip", "search": "suspicious", "max_results": 10}', {
|
|
1001
1216
|
kind: zod_1.z
|
|
1002
1217
|
.string()
|
|
1003
1218
|
.optional()
|
|
1004
1219
|
.nullable()
|
|
1005
|
-
.describe("
|
|
1220
|
+
.describe("Filter by list kind: 'string', 'uuid', 'ip', 'country', 'path', or 'asn' (e.g., \"ip\", \"string\")"),
|
|
1006
1221
|
subkind: zod_1.z
|
|
1007
1222
|
.string()
|
|
1008
1223
|
.optional()
|
|
1009
1224
|
.nullable()
|
|
1010
|
-
.describe("
|
|
1225
|
+
.describe("Filter by list subkind: 'spec_uuid' or 'endpoint_uuid' (e.g., \"spec_uuid\")"),
|
|
1226
|
+
search: zod_1.z
|
|
1227
|
+
.string()
|
|
1228
|
+
.optional()
|
|
1229
|
+
.nullable()
|
|
1230
|
+
.describe('Search in list names and descriptions (e.g., "blocklist", "suspicious")'),
|
|
1011
1231
|
page: zod_1.z
|
|
1012
1232
|
.number()
|
|
1013
1233
|
.default(1)
|
|
1234
|
+
.optional()
|
|
1014
1235
|
.nullable()
|
|
1015
|
-
.describe("
|
|
1236
|
+
.describe("Page number for pagination, starting from 1 (e.g., 1, 2, 3). Default: 1"),
|
|
1016
1237
|
max_results: zod_1.z
|
|
1017
1238
|
.number()
|
|
1018
|
-
.default(
|
|
1239
|
+
.default(25)
|
|
1240
|
+
.optional()
|
|
1241
|
+
.nullable()
|
|
1242
|
+
.describe("Maximum results per page, 1-100 (e.g., 10, 25, 50). Default: 25 for performance"),
|
|
1243
|
+
label: zod_1.z
|
|
1244
|
+
.array(zod_1.z.string())
|
|
1245
|
+
.optional()
|
|
1019
1246
|
.nullable()
|
|
1020
|
-
.describe(
|
|
1021
|
-
}, async ({ kind, subkind, page, max_results }) => {
|
|
1247
|
+
.describe('Filter by label slugs (e.g., ["security", "waf", "blocklist"])'),
|
|
1248
|
+
}, async ({ kind, subkind, search, page, max_results, label }) => {
|
|
1022
1249
|
const queryParams = {};
|
|
1023
1250
|
if (kind !== null && kind !== undefined) {
|
|
1024
1251
|
queryParams["kind"] = kind;
|
|
@@ -1026,26 +1253,32 @@ server.tool(getToolName("list_lists"), "Get a list of lists for the organization
|
|
|
1026
1253
|
if (subkind !== null && subkind !== undefined) {
|
|
1027
1254
|
queryParams["subkind"] = subkind;
|
|
1028
1255
|
}
|
|
1256
|
+
if (search !== null && search !== undefined) {
|
|
1257
|
+
queryParams["search"] = search;
|
|
1258
|
+
}
|
|
1029
1259
|
if (page !== null && page !== undefined) {
|
|
1030
1260
|
queryParams["page"] = validatePage(page);
|
|
1031
1261
|
}
|
|
1032
1262
|
if (max_results !== null && max_results !== undefined) {
|
|
1033
1263
|
queryParams["max_results"] = max_results;
|
|
1034
1264
|
}
|
|
1265
|
+
if (max_results !== null && max_results !== undefined) {
|
|
1266
|
+
queryParams["max_results"] = max_results;
|
|
1267
|
+
}
|
|
1268
|
+
if (label !== null && label !== undefined) {
|
|
1269
|
+
queryParams["label"] = label;
|
|
1270
|
+
}
|
|
1035
1271
|
const data = await makeImpartRequest("lists", queryParams);
|
|
1036
1272
|
if (!data) {
|
|
1273
|
+
const lastError = makeImpartRequest.lastError || "Unknown error";
|
|
1037
1274
|
return {
|
|
1038
|
-
content: [
|
|
1039
|
-
{ type: "text", text: "Unable to fetch lists: Unknown error" },
|
|
1040
|
-
],
|
|
1275
|
+
content: [{ type: "text", text: `Unable to fetch lists: ${lastError}` }],
|
|
1041
1276
|
};
|
|
1042
1277
|
}
|
|
1043
1278
|
const lists = data.items || [];
|
|
1044
1279
|
if (lists.length === 0) {
|
|
1045
1280
|
return {
|
|
1046
|
-
content: [
|
|
1047
|
-
{ type: "text", text: "No lists found for this organization." },
|
|
1048
|
-
],
|
|
1281
|
+
content: [{ type: "text", text: "No lists found for this organization." }],
|
|
1049
1282
|
};
|
|
1050
1283
|
}
|
|
1051
1284
|
const formattedLists = lists.map((list) => [
|
|
@@ -1054,8 +1287,8 @@ server.tool(getToolName("list_lists"), "Get a list of lists for the organization
|
|
|
1054
1287
|
`Kind: ${list.kind}`,
|
|
1055
1288
|
`Subkind: ${list.subkind ?? "N/A"}`,
|
|
1056
1289
|
`Description: ${list.description ?? "N/A"}`,
|
|
1057
|
-
`Created
|
|
1058
|
-
`
|
|
1290
|
+
`Created By: ${list.createdBy}`,
|
|
1291
|
+
`Created At: ${list.createdAt ? list.createdAt.toISOString() : "N/A"}`,
|
|
1059
1292
|
"---",
|
|
1060
1293
|
].join("\n"));
|
|
1061
1294
|
return {
|
|
@@ -1064,14 +1297,8 @@ server.tool(getToolName("list_lists"), "Get a list of lists for the organization
|
|
|
1064
1297
|
});
|
|
1065
1298
|
server.tool(getToolName("get_list_items"), "Get items from a specific list", {
|
|
1066
1299
|
list_id: zod_1.z.string().describe("The ID of the list"),
|
|
1067
|
-
page: zod_1.z
|
|
1068
|
-
|
|
1069
|
-
.default(1)
|
|
1070
|
-
.describe("The page of results to return (minimum: 1)"),
|
|
1071
|
-
max_results: zod_1.z
|
|
1072
|
-
.number()
|
|
1073
|
-
.default(100)
|
|
1074
|
-
.describe("The maximum number of results to return"),
|
|
1300
|
+
page: zod_1.z.number().default(1).describe("The page of results to return (minimum: 1, e.g., 1, 2, 3)"),
|
|
1301
|
+
max_results: zod_1.z.number().default(100).describe("The maximum number of results to return (e.g., 10, 50, 100)"),
|
|
1075
1302
|
}, async ({ list_id, page, max_results }) => {
|
|
1076
1303
|
if (!list_id) {
|
|
1077
1304
|
return {
|
|
@@ -1084,56 +1311,75 @@ server.tool(getToolName("get_list_items"), "Get items from a specific list", {
|
|
|
1084
1311
|
};
|
|
1085
1312
|
const data = await makeImpartRequest(`lists/${list_id}/items`, queryParams);
|
|
1086
1313
|
if (!data) {
|
|
1314
|
+
const lastError = makeImpartRequest.lastError || "Unknown error";
|
|
1087
1315
|
return {
|
|
1088
|
-
content: [
|
|
1089
|
-
{ type: "text", text: "Unable to fetch list items: Unknown error" },
|
|
1090
|
-
],
|
|
1316
|
+
content: [{ type: "text", text: `Unable to fetch list items: ${lastError}` }],
|
|
1091
1317
|
};
|
|
1092
1318
|
}
|
|
1093
|
-
|
|
1319
|
+
// Handle direct array response from API
|
|
1320
|
+
const items = Array.isArray(data) ? data : [];
|
|
1094
1321
|
if (items.length === 0) {
|
|
1095
1322
|
return {
|
|
1096
1323
|
content: [{ type: "text", text: "No items found in this list." }],
|
|
1097
1324
|
};
|
|
1098
1325
|
}
|
|
1099
|
-
const formattedItems = items.map((item) => [`Value: ${item.value}`, `
|
|
1326
|
+
const formattedItems = items.map((item) => [`Value: ${item.value}`, `Expiration: ${item.expiration?.toISOString() ?? "N/A"}`, "---"].join("\n"));
|
|
1100
1327
|
return {
|
|
1101
1328
|
content: [{ type: "text", text: formattedItems.join("\n") }],
|
|
1102
1329
|
};
|
|
1103
1330
|
});
|
|
1331
|
+
// items:
|
|
1332
|
+
// description: The items in the list.
|
|
1333
|
+
// items:
|
|
1334
|
+
// $ref: "#/components/schemas/list_items_inner"
|
|
1335
|
+
// maxItems: 100000
|
|
1336
|
+
// type: array
|
|
1337
|
+
// required:
|
|
1338
|
+
// - kind
|
|
1339
|
+
// - name
|
|
1104
1340
|
server.tool(getToolName("create_list"), "Create a new list", {
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1341
|
+
name: zod_1.z.string().min(1).max(64).describe("The name of the list"),
|
|
1342
|
+
kind: zod_1.z.enum(["string", "uuid", "ip", "country", "path", "asn"]).describe("The kind of list to create"),
|
|
1343
|
+
subkind: zod_1.z.enum(["spec_uuid", "endpoint_uuid"]).optional().describe("The subkind of list to create"),
|
|
1344
|
+
functionality: zod_1.z.enum(["add"]).default("add").optional().describe("The functionality of the list"),
|
|
1345
|
+
description: zod_1.z.string().optional().describe("The description of the list"),
|
|
1346
|
+
items: zod_1.z.array(zod_1.z.string()).optional().nullable().describe("The items to add to the list"),
|
|
1347
|
+
labels: zod_1.z
|
|
1110
1348
|
.array(zod_1.z.string())
|
|
1349
|
+
.optional()
|
|
1111
1350
|
.nullable()
|
|
1112
|
-
.describe(
|
|
1113
|
-
}, async ({ kind, subkind,
|
|
1114
|
-
if (!kind || !
|
|
1351
|
+
.describe('The labels to apply to the list (e.g., ["llm-protection", "waf"])'),
|
|
1352
|
+
}, async ({ name, kind, subkind, functionality, description, items, labels }) => {
|
|
1353
|
+
if (!kind || !name) {
|
|
1115
1354
|
return {
|
|
1116
|
-
content: [
|
|
1117
|
-
{ type: "text", text: "Error: kind, subkind, and name are required" },
|
|
1118
|
-
],
|
|
1355
|
+
content: [{ type: "text", text: "Error: name and kind are required" }],
|
|
1119
1356
|
isError: true,
|
|
1120
1357
|
};
|
|
1121
1358
|
}
|
|
1122
1359
|
const body = {
|
|
1123
1360
|
kind,
|
|
1124
|
-
subkind,
|
|
1125
1361
|
name,
|
|
1126
1362
|
};
|
|
1363
|
+
if (subkind !== null && subkind !== undefined) {
|
|
1364
|
+
body["subkind"] = subkind;
|
|
1365
|
+
}
|
|
1366
|
+
if (functionality !== null && functionality !== undefined) {
|
|
1367
|
+
body["functionality"] = functionality;
|
|
1368
|
+
}
|
|
1127
1369
|
if (description !== null && description !== undefined) {
|
|
1128
1370
|
body["description"] = description;
|
|
1129
1371
|
}
|
|
1130
1372
|
if (items !== null && items !== undefined) {
|
|
1131
1373
|
body["items"] = items;
|
|
1132
1374
|
}
|
|
1375
|
+
if (labels !== null && labels !== undefined) {
|
|
1376
|
+
body["labels"] = labels;
|
|
1377
|
+
}
|
|
1133
1378
|
const data = await makeImpartRequest("lists", {}, "POST", body);
|
|
1134
1379
|
if (!data) {
|
|
1380
|
+
const lastError = makeImpartRequest.lastError || "Unknown error";
|
|
1135
1381
|
return {
|
|
1136
|
-
content: [{ type: "text", text:
|
|
1382
|
+
content: [{ type: "text", text: `Unable to create list: ${lastError}` }],
|
|
1137
1383
|
isError: true,
|
|
1138
1384
|
};
|
|
1139
1385
|
}
|
|
@@ -1148,18 +1394,31 @@ server.tool(getToolName("create_list"), "Create a new list", {
|
|
|
1148
1394
|
`Kind: ${kind}`,
|
|
1149
1395
|
`Subkind: ${subkind}`,
|
|
1150
1396
|
`Description: ${description ?? "N/A"}`,
|
|
1151
|
-
`Created
|
|
1397
|
+
`Created By: ${data.createdBy}`,
|
|
1398
|
+
`Created At: ${data.createdAt ? data.createdAt.toISOString() : "N/A"}`,
|
|
1152
1399
|
"---",
|
|
1153
1400
|
].join("\n"),
|
|
1154
1401
|
},
|
|
1155
1402
|
],
|
|
1156
1403
|
};
|
|
1157
1404
|
});
|
|
1405
|
+
// add:
|
|
1406
|
+
// description: The items in the list.
|
|
1407
|
+
// items:
|
|
1408
|
+
// $ref: "#/components/schemas/list_items_inner"
|
|
1409
|
+
// maxItems: 100000
|
|
1410
|
+
// type: array
|
|
1411
|
+
// remove:
|
|
1412
|
+
// description: Items to remove from the list.
|
|
1413
|
+
// items:
|
|
1414
|
+
// maxLength: 1000
|
|
1415
|
+
// minLength: 1
|
|
1416
|
+
// type: string
|
|
1417
|
+
// maxItems: 100000
|
|
1418
|
+
// type: array
|
|
1158
1419
|
server.tool(getToolName("update_list_items"), "Updates items in a list", {
|
|
1159
1420
|
list_id: zod_1.z.string().describe("The ID of the list to update"),
|
|
1160
|
-
items: zod_1.z
|
|
1161
|
-
.array(zod_1.z.record(zod_1.z.any()))
|
|
1162
|
-
.describe("The items to update in the list"),
|
|
1421
|
+
items: zod_1.z.array(zod_1.z.record(zod_1.z.string(), zod_1.z.any())).describe("The items to update in the list"),
|
|
1163
1422
|
}, async ({ list_id, items }) => {
|
|
1164
1423
|
if (!list_id) {
|
|
1165
1424
|
return {
|
|
@@ -1169,8 +1428,9 @@ server.tool(getToolName("update_list_items"), "Updates items in a list", {
|
|
|
1169
1428
|
const payload = { items };
|
|
1170
1429
|
const data = await makeImpartRequest(`lists/${list_id}/items`, undefined, "PATCH", payload);
|
|
1171
1430
|
if (!data) {
|
|
1431
|
+
const lastError = makeImpartRequest.lastError || "Unknown error";
|
|
1172
1432
|
return {
|
|
1173
|
-
content: [{ type: "text", text:
|
|
1433
|
+
content: [{ type: "text", text: `Unable to update list items: ${lastError}` }],
|
|
1174
1434
|
};
|
|
1175
1435
|
}
|
|
1176
1436
|
return {
|
|
@@ -1178,28 +1438,42 @@ server.tool(getToolName("update_list_items"), "Updates items in a list", {
|
|
|
1178
1438
|
};
|
|
1179
1439
|
});
|
|
1180
1440
|
server.tool(getToolName("create_label"), "Creates a new label for the organization", {
|
|
1181
|
-
|
|
1441
|
+
slug: zod_1.z.string().min(1).max(64).describe("Name of the label"),
|
|
1442
|
+
display_name: zod_1.z.string().optional().describe("Optional display name"),
|
|
1182
1443
|
description: zod_1.z.string().optional().describe("Optional description"),
|
|
1183
|
-
color: zod_1.z
|
|
1184
|
-
|
|
1444
|
+
color: zod_1.z
|
|
1445
|
+
.enum(["gray", "red", "orange", "yellow", "green", "teal", "cyan", "blue", "purple", "pink"])
|
|
1446
|
+
.default("gray")
|
|
1447
|
+
.optional()
|
|
1448
|
+
.nullable()
|
|
1449
|
+
.describe("Label color"),
|
|
1450
|
+
}, async ({ slug, display_name, description, color }) => {
|
|
1185
1451
|
const payload = {
|
|
1186
|
-
|
|
1187
|
-
description,
|
|
1188
|
-
color,
|
|
1452
|
+
slug,
|
|
1189
1453
|
};
|
|
1454
|
+
if (display_name !== null && display_name !== undefined) {
|
|
1455
|
+
payload["display_name"] = display_name;
|
|
1456
|
+
}
|
|
1457
|
+
if (description !== null && description !== undefined) {
|
|
1458
|
+
payload["description"] = description;
|
|
1459
|
+
}
|
|
1460
|
+
if (color !== null && color !== undefined) {
|
|
1461
|
+
payload["color"] = color;
|
|
1462
|
+
}
|
|
1190
1463
|
const data = await makeImpartRequest("labels", undefined, "POST", payload);
|
|
1191
1464
|
if (!data) {
|
|
1465
|
+
const lastError = makeImpartRequest.lastError || "Unknown error";
|
|
1192
1466
|
return {
|
|
1193
|
-
content: [{ type: "text", text:
|
|
1467
|
+
content: [{ type: "text", text: `Unable to create label: ${lastError}` }],
|
|
1194
1468
|
};
|
|
1195
1469
|
}
|
|
1196
1470
|
const formattedLabel = [
|
|
1197
1471
|
"Label created successfully:",
|
|
1198
1472
|
`Slug: ${data.slug}`,
|
|
1199
|
-
`Name: ${data.
|
|
1200
|
-
`Description: ${data.description
|
|
1473
|
+
`Name: ${data.displayName}`,
|
|
1474
|
+
`Description: ${data.description}`,
|
|
1201
1475
|
`Color: ${data.color}`,
|
|
1202
|
-
`Created At: ${data.
|
|
1476
|
+
`Created At: ${data.createdAt ? data.createdAt.toISOString() : "N/A"}`,
|
|
1203
1477
|
"---",
|
|
1204
1478
|
].join("\n");
|
|
1205
1479
|
return {
|
|
@@ -1210,14 +1484,17 @@ server.tool(getToolName("list_labels"), "Get a list of labels for the organizati
|
|
|
1210
1484
|
page: zod_1.z
|
|
1211
1485
|
.number()
|
|
1212
1486
|
.default(1)
|
|
1487
|
+
.optional()
|
|
1213
1488
|
.nullable()
|
|
1214
|
-
.describe("The page of results to return (minimum: 1)"),
|
|
1489
|
+
.describe("The page of results to return (minimum: 1, e.g., 1, 2, 3)"),
|
|
1215
1490
|
max_results: zod_1.z
|
|
1216
1491
|
.number()
|
|
1217
1492
|
.default(100)
|
|
1493
|
+
.optional()
|
|
1218
1494
|
.nullable()
|
|
1219
|
-
.describe("The maximum number of results to return"),
|
|
1220
|
-
|
|
1495
|
+
.describe("The maximum number of results to return (e.g., 10, 50, 100)"),
|
|
1496
|
+
search: zod_1.z.string().nullable().optional().describe("Search string to filter labels"),
|
|
1497
|
+
}, async ({ page, max_results, search }) => {
|
|
1221
1498
|
const queryParams = {};
|
|
1222
1499
|
if (page !== null && page !== undefined) {
|
|
1223
1500
|
queryParams["page"] = validatePage(page);
|
|
@@ -1225,55 +1502,61 @@ server.tool(getToolName("list_labels"), "Get a list of labels for the organizati
|
|
|
1225
1502
|
if (max_results !== null && max_results !== undefined) {
|
|
1226
1503
|
queryParams["max_results"] = max_results;
|
|
1227
1504
|
}
|
|
1505
|
+
if (search !== null && search !== undefined) {
|
|
1506
|
+
queryParams["search"] = search;
|
|
1507
|
+
}
|
|
1228
1508
|
const data = await makeImpartRequest("labels", queryParams);
|
|
1229
1509
|
if (!data) {
|
|
1510
|
+
const lastError = makeImpartRequest.lastError || "Unknown error";
|
|
1230
1511
|
return {
|
|
1231
|
-
content: [
|
|
1232
|
-
{ type: "text", text: "Unable to fetch labels: Unknown error" },
|
|
1233
|
-
],
|
|
1512
|
+
content: [{ type: "text", text: `Unable to fetch labels: ${lastError}` }],
|
|
1234
1513
|
};
|
|
1235
1514
|
}
|
|
1236
1515
|
const labels = data.items || [];
|
|
1237
1516
|
if (labels.length === 0) {
|
|
1238
1517
|
return {
|
|
1239
|
-
content: [
|
|
1240
|
-
{ type: "text", text: "No labels found for this organization." },
|
|
1241
|
-
],
|
|
1518
|
+
content: [{ type: "text", text: "No labels found for this organization." }],
|
|
1242
1519
|
};
|
|
1243
1520
|
}
|
|
1244
1521
|
const formattedLabels = labels.map((label) => [
|
|
1245
1522
|
`Slug: ${label.slug}`,
|
|
1246
|
-
`Name: ${label.
|
|
1523
|
+
`Name: ${label.displayName}`,
|
|
1247
1524
|
`Description: ${label.description ?? "N/A"}`,
|
|
1248
1525
|
`Color: ${label.color}`,
|
|
1249
|
-
`Created At: ${label.
|
|
1526
|
+
`Created At: ${label.createdAt ? label.createdAt.toISOString() : "N/A"}`,
|
|
1250
1527
|
"---",
|
|
1251
1528
|
].join("\n"));
|
|
1252
1529
|
return {
|
|
1253
1530
|
content: [{ type: "text", text: formattedLabels.join("\n") }],
|
|
1254
1531
|
};
|
|
1255
1532
|
});
|
|
1256
|
-
server.tool(getToolName("
|
|
1533
|
+
server.tool(getToolName("list_test_cases"), "Get a list of test cases for security rules in your Impart organization. Test cases are predefined request/response scenarios used to validate rule behavior and ensure rules work correctly. Examples:\n\n" +
|
|
1534
|
+
"• All test cases: {} (no parameters needed)\n" +
|
|
1535
|
+
'• Search test cases: {"search": "malicious"}\n' +
|
|
1536
|
+
'• Filter by labels: {"label": ["security", "validation"]}\n' +
|
|
1537
|
+
'• With pagination: {"page": 1, "max_results": 25}', {
|
|
1257
1538
|
page: zod_1.z
|
|
1258
1539
|
.number()
|
|
1259
1540
|
.default(1)
|
|
1541
|
+
.optional()
|
|
1260
1542
|
.nullable()
|
|
1261
|
-
.describe("
|
|
1543
|
+
.describe("Page number for pagination, starting from 1 (e.g., 1, 2, 3). Default: 1"),
|
|
1262
1544
|
max_results: zod_1.z
|
|
1263
1545
|
.number()
|
|
1264
|
-
.default(
|
|
1546
|
+
.default(25)
|
|
1547
|
+
.optional()
|
|
1265
1548
|
.nullable()
|
|
1266
|
-
.describe("
|
|
1549
|
+
.describe("Maximum test cases per page, 1-100 (e.g., 10, 25, 50). Default: 25 for performance"),
|
|
1267
1550
|
label: zod_1.z
|
|
1268
1551
|
.array(zod_1.z.string())
|
|
1269
1552
|
.optional()
|
|
1270
1553
|
.nullable()
|
|
1271
|
-
.describe(
|
|
1554
|
+
.describe('Filter by label slugs (e.g., ["security", "validation", "attack-simulation"])'),
|
|
1272
1555
|
search: zod_1.z
|
|
1273
1556
|
.string()
|
|
1274
1557
|
.optional()
|
|
1275
1558
|
.nullable()
|
|
1276
|
-
.describe(
|
|
1559
|
+
.describe('Search in test case names and descriptions (e.g., "malicious", "XSS", "SQL injection")'),
|
|
1277
1560
|
}, async ({ page, max_results, label, search }) => {
|
|
1278
1561
|
const queryParams = {};
|
|
1279
1562
|
if (page !== null && page !== undefined) {
|
|
@@ -1294,7 +1577,7 @@ server.tool(getToolName("list_rules_test_cases"), "Get a list of rules test case
|
|
|
1294
1577
|
content: [
|
|
1295
1578
|
{
|
|
1296
1579
|
type: "text",
|
|
1297
|
-
text:
|
|
1580
|
+
text: `Unable to fetch test cases: ${makeImpartRequest.lastError || "Unknown error"}`,
|
|
1298
1581
|
},
|
|
1299
1582
|
],
|
|
1300
1583
|
};
|
|
@@ -1305,137 +1588,45 @@ server.tool(getToolName("list_rules_test_cases"), "Get a list of rules test case
|
|
|
1305
1588
|
content: [
|
|
1306
1589
|
{
|
|
1307
1590
|
type: "text",
|
|
1308
|
-
text: "No
|
|
1591
|
+
text: "No test cases found for this organization. Test cases are used to validate security rule behavior.",
|
|
1309
1592
|
},
|
|
1310
1593
|
],
|
|
1311
1594
|
};
|
|
1312
1595
|
}
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
`
|
|
1318
|
-
`
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
`
|
|
1324
|
-
|
|
1325
|
-
"---",
|
|
1326
|
-
].join("\n"));
|
|
1327
|
-
return {
|
|
1328
|
-
content: [{ type: "text", text: formattedTestCases.join("\n") }],
|
|
1329
|
-
};
|
|
1330
|
-
});
|
|
1331
|
-
server.tool(getToolName("create_rules_test_case"), "Create a new rules test case", {
|
|
1332
|
-
name: zod_1.z.string().describe("The name of the test case"),
|
|
1333
|
-
description: zod_1.z
|
|
1334
|
-
.string()
|
|
1335
|
-
.optional()
|
|
1336
|
-
.nullable()
|
|
1337
|
-
.describe("Optional description of the test case"),
|
|
1338
|
-
required: zod_1.z
|
|
1339
|
-
.boolean()
|
|
1340
|
-
.default(false)
|
|
1341
|
-
.nullable()
|
|
1342
|
-
.describe("Whether the test case is required to pass"),
|
|
1343
|
-
messages: zod_1.z
|
|
1344
|
-
.array(zod_1.z.object({
|
|
1345
|
-
request: zod_1.z
|
|
1346
|
-
.object({
|
|
1347
|
-
method: zod_1.z.string().optional(),
|
|
1348
|
-
url: zod_1.z.string().optional(),
|
|
1349
|
-
headers: zod_1.z.record(zod_1.z.array(zod_1.z.string())).optional(),
|
|
1350
|
-
body: zod_1.z.string().optional(),
|
|
1351
|
-
})
|
|
1352
|
-
.optional(),
|
|
1353
|
-
response: zod_1.z
|
|
1354
|
-
.object({
|
|
1355
|
-
status_code: zod_1.z.number().optional(),
|
|
1356
|
-
headers: zod_1.z.record(zod_1.z.array(zod_1.z.string())).optional(),
|
|
1357
|
-
body: zod_1.z.string().optional(),
|
|
1358
|
-
})
|
|
1359
|
-
.optional(),
|
|
1360
|
-
}))
|
|
1361
|
-
.describe("The test case messages"),
|
|
1362
|
-
assertions: zod_1.z
|
|
1363
|
-
.array(zod_1.z.object({
|
|
1364
|
-
message_index: zod_1.z.number(),
|
|
1365
|
-
message_definition: zod_1.z.object({
|
|
1366
|
-
line: zod_1.z.number(),
|
|
1367
|
-
column: zod_1.z.number(),
|
|
1368
|
-
}),
|
|
1369
|
-
passed: zod_1.z.boolean(),
|
|
1370
|
-
}))
|
|
1371
|
-
.optional()
|
|
1372
|
-
.nullable()
|
|
1373
|
-
.describe("Optional test case assertions"),
|
|
1374
|
-
labels: zod_1.z
|
|
1375
|
-
.array(zod_1.z.string())
|
|
1376
|
-
.optional()
|
|
1377
|
-
.nullable()
|
|
1378
|
-
.describe("Optional list of label slugs"),
|
|
1379
|
-
}, async ({ name, description, required, messages, assertions, labels }) => {
|
|
1380
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1381
|
-
const payload = {
|
|
1382
|
-
name,
|
|
1383
|
-
messages,
|
|
1384
|
-
};
|
|
1385
|
-
if (description !== null && description !== undefined) {
|
|
1386
|
-
payload["description"] = description;
|
|
1387
|
-
}
|
|
1388
|
-
if (required !== null && required !== undefined) {
|
|
1389
|
-
payload["required"] = required;
|
|
1390
|
-
}
|
|
1391
|
-
if (assertions !== null && assertions !== undefined) {
|
|
1392
|
-
payload["assertions"] = assertions;
|
|
1393
|
-
}
|
|
1394
|
-
if (labels !== null && labels !== undefined) {
|
|
1395
|
-
payload["labels"] = labels;
|
|
1396
|
-
}
|
|
1397
|
-
const data = await makeImpartRequest("rules_test_cases", undefined, "POST", payload);
|
|
1398
|
-
if (!data) {
|
|
1399
|
-
return {
|
|
1400
|
-
content: [{ type: "text", text: "Failed to create rules test case" }],
|
|
1401
|
-
};
|
|
1402
|
-
}
|
|
1403
|
-
const formattedTestCase = [
|
|
1404
|
-
"Rules test case created successfully:",
|
|
1405
|
-
`ID: ${data.id}`,
|
|
1406
|
-
`Name: ${data.name}`,
|
|
1407
|
-
`Description: ${data.description ?? "N/A"}`,
|
|
1408
|
-
`Required: ${data.required}`,
|
|
1409
|
-
`Labels: ${data.labels.join(", ")}`,
|
|
1410
|
-
`Messages Count: ${data.messages.length}`,
|
|
1411
|
-
`Assertions Count: ${data.assertions.length}`,
|
|
1412
|
-
`Created By: ${data.created_by}`,
|
|
1413
|
-
`Created At: ${data.created_at}`,
|
|
1414
|
-
"---",
|
|
1415
|
-
].join("\n");
|
|
1596
|
+
// Limit display for performance
|
|
1597
|
+
const maxDisplay = Math.min(testCases.length, 15);
|
|
1598
|
+
const showingLimited = testCases.length > maxDisplay;
|
|
1599
|
+
const result = [
|
|
1600
|
+
`Found ${testCases.length} test cases`,
|
|
1601
|
+
showingLimited ? `Showing first ${maxDisplay} for performance. Use search/filters to narrow results.` : "",
|
|
1602
|
+
"=".repeat(50),
|
|
1603
|
+
"",
|
|
1604
|
+
];
|
|
1605
|
+
testCases.slice(0, maxDisplay).forEach((testCase, index) => {
|
|
1606
|
+
result.push(`${index + 1}. ${testCase.name} ${testCase.required ? "(REQUIRED)" : ""}`, ` ID: ${testCase.id}`, ` Messages: ${testCase.messages.length} | Assertions: ${testCase.assertions.length}`, ` Labels: ${testCase.labels.length > 0 ? testCase.labels.join(", ") : "None"}`, ` Created: ${testCase.createdAt ? testCase.createdAt.toISOString() : "N/A"}`, "");
|
|
1607
|
+
});
|
|
1416
1608
|
return {
|
|
1417
|
-
content: [{ type: "text", text:
|
|
1609
|
+
content: [{ type: "text", text: result.join("\n") }],
|
|
1418
1610
|
};
|
|
1419
1611
|
});
|
|
1420
|
-
server.tool(getToolName("
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1612
|
+
server.tool(getToolName("get_test_case"), "Get detailed information about a specific test case for security rules. Test cases contain request/response scenarios and assertions to validate rule behavior. Examples:\n\n" +
|
|
1613
|
+
'• Get test case details: {"test_case_id": "tc-abc123"}\n' +
|
|
1614
|
+
'• View test scenario: {"test_case_id": "tc-def456"}\n\n' +
|
|
1615
|
+
"Note: Use test case IDs from the list_test_cases tool or your Impart dashboard.", {
|
|
1616
|
+
test_case_id: zod_1.z.string().describe("The unique identifier of the test case (e.g., 'tc-abc123', 'tc-def456')"),
|
|
1617
|
+
}, async ({ test_case_id }) => {
|
|
1618
|
+
if (!test_case_id) {
|
|
1426
1619
|
return {
|
|
1427
|
-
content: [
|
|
1428
|
-
{ type: "text", text: "Error: rules_test_case_id is required." },
|
|
1429
|
-
],
|
|
1620
|
+
content: [{ type: "text", text: "Error: test_case_id is required." }],
|
|
1430
1621
|
};
|
|
1431
1622
|
}
|
|
1432
|
-
const data = await makeImpartRequest(`rules_test_cases/${
|
|
1623
|
+
const data = await makeImpartRequest(`rules_test_cases/${test_case_id}`);
|
|
1433
1624
|
if (!data) {
|
|
1434
1625
|
return {
|
|
1435
1626
|
content: [
|
|
1436
1627
|
{
|
|
1437
1628
|
type: "text",
|
|
1438
|
-
text:
|
|
1629
|
+
text: `Unable to fetch test case: ${makeImpartRequest.lastError || "Unknown error"}`,
|
|
1439
1630
|
},
|
|
1440
1631
|
],
|
|
1441
1632
|
};
|
|
@@ -1445,205 +1636,300 @@ server.tool(getToolName("get_rules_test_case"), "Get details of a specific rules
|
|
|
1445
1636
|
`Name: ${data.name}`,
|
|
1446
1637
|
`Description: ${data.description ?? "N/A"}`,
|
|
1447
1638
|
`Required: ${data.required}`,
|
|
1448
|
-
`Labels: ${data.labels.join(", ")}`,
|
|
1639
|
+
`Labels: ${data.labels && data.labels.length > 0 ? data.labels.join(", ") : "None"}`,
|
|
1449
1640
|
"Messages:",
|
|
1450
1641
|
];
|
|
1451
|
-
data.messages.forEach((message, index) => {
|
|
1642
|
+
(data.messages || []).forEach((message, index) => {
|
|
1452
1643
|
formattedTestCase.push(`Message ${index + 1}:`);
|
|
1453
|
-
if (message.
|
|
1454
|
-
formattedTestCase.push(" Request:", ` Method: ${message.
|
|
1455
|
-
if (message.
|
|
1456
|
-
Object.entries(message.
|
|
1457
|
-
formattedTestCase.push(` ${key}: ${values
|
|
1644
|
+
if (message.req) {
|
|
1645
|
+
formattedTestCase.push(" Request:", ` Method: ${message.req.method ?? "N/A"}`, ` URL: ${message.req.url ?? "N/A"}`, " Headers:");
|
|
1646
|
+
if (message.req.headerKeys) {
|
|
1647
|
+
Object.entries(message.req.headerKeys).forEach(([key, values]) => {
|
|
1648
|
+
formattedTestCase.push(` ${key}: ${values}`);
|
|
1458
1649
|
});
|
|
1459
1650
|
}
|
|
1460
|
-
if (message.
|
|
1461
|
-
formattedTestCase.push(` Body: ${message.
|
|
1651
|
+
if (message.req.body) {
|
|
1652
|
+
formattedTestCase.push(` Body: ${message.req.body}`);
|
|
1462
1653
|
}
|
|
1463
1654
|
}
|
|
1464
|
-
if (message.
|
|
1465
|
-
formattedTestCase.push(" Response:", ` Status Code: ${message.
|
|
1466
|
-
if (message.
|
|
1467
|
-
Object.entries(message.
|
|
1468
|
-
formattedTestCase.push(` ${key}: ${values
|
|
1655
|
+
if (message.res) {
|
|
1656
|
+
formattedTestCase.push(" Response:", ` Status Code: ${message.res.statusCode ?? "N/A"}`, " Headers:");
|
|
1657
|
+
if (message.res.headerKeys) {
|
|
1658
|
+
Object.entries(message.res.headerKeys).forEach(([key, values]) => {
|
|
1659
|
+
formattedTestCase.push(` ${key}: ${values}`);
|
|
1469
1660
|
});
|
|
1470
1661
|
}
|
|
1471
|
-
if (message.
|
|
1472
|
-
formattedTestCase.push(` Body: ${message.
|
|
1662
|
+
if (message.res.body) {
|
|
1663
|
+
formattedTestCase.push(` Body: ${message.res.body}`);
|
|
1473
1664
|
}
|
|
1474
1665
|
}
|
|
1475
1666
|
});
|
|
1476
|
-
formattedTestCase.push("Assertions:", ...data.assertions.map((assertion) => `
|
|
1667
|
+
formattedTestCase.push("Assertions:", ...(data.assertions || []).map((assertion) => ` Type: ${assertion.assertionType}, Expected: ${assertion.expected.toString()}, Indexes: ${assertion.messageIndexes && assertion.messageIndexes.length > 0 ? assertion.messageIndexes.join(", ") : "None"}`), "---");
|
|
1477
1668
|
return {
|
|
1478
1669
|
content: [{ type: "text", text: formattedTestCase.join("\n") }],
|
|
1479
1670
|
};
|
|
1480
1671
|
});
|
|
1481
|
-
server.tool(getToolName("
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1672
|
+
server.tool(getToolName("get_requests"), "Get a list of HTTP requests captured by Impart. Supports extensive filtering and pagination. Examples:\n\n" +
|
|
1673
|
+
"• Recent requests (default): {} (gets last day, 25 results)\n" +
|
|
1674
|
+
'• Last 24 hours: {"from": "-24h", "max_results": 50}\n' +
|
|
1675
|
+
'• Last week: {"from": "-7d", "max_results": 100}\n' +
|
|
1676
|
+
'• Filter by API spec: {"spec_id": ["spec-abc123"], "max_results": 25}\n' +
|
|
1677
|
+
'• Error requests only: {"status_code": ["400", "404", "500"], "from": "-1d"}\n' +
|
|
1678
|
+
'• Security events: {"tag": ["attack", "blocked", "suspicious"], "from": "-24h"}\n' +
|
|
1679
|
+
'• Specific IP activity: {"ip": ["192.168.1.100"], "from": "-24h", "sort_order": "desc"}\n' +
|
|
1680
|
+
'• Multiple filters: {"status_code": ["200"], "tag": ["allowed"], "from": "-1h", "max_results": 10}\n' +
|
|
1681
|
+
'• Time range: {"from": "-7d", "until": "-1d", "sort_order": "desc", "max_results": 50}\n' +
|
|
1682
|
+
'• Next page: {"max_results": 25, "next": "eyJ0aW1lc3RhbXAiOi4uLn0="}', {
|
|
1683
|
+
spec_id: zod_1.z.array(zod_1.z.string()).optional().nullable().describe('Filter by spec IDs (e.g., ["spec-123", "spec-456"])'),
|
|
1684
|
+
collection_id: zod_1.z
|
|
1685
|
+
.array(zod_1.z.string())
|
|
1488
1686
|
.optional()
|
|
1489
1687
|
.nullable()
|
|
1490
|
-
.describe(
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
.
|
|
1688
|
+
.describe('Filter by collection IDs (e.g., ["collection-123"])'),
|
|
1689
|
+
endpoint_id: zod_1.z.array(zod_1.z.string()).optional().nullable().describe('Filter by endpoint IDs (e.g., ["endpoint-123"])'),
|
|
1690
|
+
tag: zod_1.z
|
|
1691
|
+
.array(zod_1.z.string())
|
|
1692
|
+
.optional()
|
|
1494
1693
|
.nullable()
|
|
1495
|
-
.describe(
|
|
1496
|
-
|
|
1497
|
-
.array(zod_1.z.
|
|
1498
|
-
request: zod_1.z
|
|
1499
|
-
.object({
|
|
1500
|
-
method: zod_1.z.string().optional(),
|
|
1501
|
-
url: zod_1.z.string().optional(),
|
|
1502
|
-
headers: zod_1.z.record(zod_1.z.array(zod_1.z.string())).optional(),
|
|
1503
|
-
body: zod_1.z.string().optional(),
|
|
1504
|
-
})
|
|
1505
|
-
.optional(),
|
|
1506
|
-
response: zod_1.z
|
|
1507
|
-
.object({
|
|
1508
|
-
status_code: zod_1.z.number().optional(),
|
|
1509
|
-
headers: zod_1.z.record(zod_1.z.array(zod_1.z.string())).optional(),
|
|
1510
|
-
body: zod_1.z.string().optional(),
|
|
1511
|
-
})
|
|
1512
|
-
.optional(),
|
|
1513
|
-
}))
|
|
1514
|
-
.describe("The test case messages"),
|
|
1515
|
-
assertions: zod_1.z
|
|
1516
|
-
.array(zod_1.z.object({
|
|
1517
|
-
message_index: zod_1.z.number(),
|
|
1518
|
-
message_definition: zod_1.z.object({
|
|
1519
|
-
line: zod_1.z.number(),
|
|
1520
|
-
column: zod_1.z.number(),
|
|
1521
|
-
}),
|
|
1522
|
-
passed: zod_1.z.boolean(),
|
|
1523
|
-
}))
|
|
1694
|
+
.describe('Filter by tag names (e.g., ["attack", "blocked", "allowed"])'),
|
|
1695
|
+
ip: zod_1.z
|
|
1696
|
+
.array(zod_1.z.string())
|
|
1524
1697
|
.optional()
|
|
1525
1698
|
.nullable()
|
|
1526
|
-
.describe(
|
|
1527
|
-
|
|
1699
|
+
.describe('Filter by IP addresses (e.g., ["192.168.1.1", "10.0.0.1"])'),
|
|
1700
|
+
external_request_id: zod_1.z
|
|
1528
1701
|
.array(zod_1.z.string())
|
|
1529
1702
|
.optional()
|
|
1530
1703
|
.nullable()
|
|
1531
|
-
.describe(
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1704
|
+
.describe('Filter by external request IDs (e.g., ["req-123"])'),
|
|
1705
|
+
rule_id: zod_1.z
|
|
1706
|
+
.array(zod_1.z.string())
|
|
1707
|
+
.optional()
|
|
1708
|
+
.nullable()
|
|
1709
|
+
.describe('Filter by rule IDs that processed the request (e.g., ["rule-123"])'),
|
|
1710
|
+
status_code: zod_1.z
|
|
1711
|
+
.array(zod_1.z.string())
|
|
1712
|
+
.optional()
|
|
1713
|
+
.nullable()
|
|
1714
|
+
.describe('Filter by HTTP status codes (e.g., ["200", "404", "500"])'),
|
|
1715
|
+
from: zod_1.z
|
|
1716
|
+
.string()
|
|
1717
|
+
.default("-1d")
|
|
1718
|
+
.optional()
|
|
1719
|
+
.nullable()
|
|
1720
|
+
.describe("Start time (relative like '-1d', '-24h', '-7d' or absolute timestamp)"),
|
|
1721
|
+
until: zod_1.z
|
|
1722
|
+
.string()
|
|
1723
|
+
.default("-0h")
|
|
1724
|
+
.optional()
|
|
1725
|
+
.nullable()
|
|
1726
|
+
.describe("End time (relative like '-0h', '-1h' or absolute timestamp)"),
|
|
1727
|
+
max_results: zod_1.z
|
|
1728
|
+
.number()
|
|
1729
|
+
.default(25)
|
|
1730
|
+
.optional()
|
|
1731
|
+
.nullable()
|
|
1732
|
+
.describe("Maximum number of results to return (1-1000, e.g., 10, 25, 50). Default 25 for performance"),
|
|
1733
|
+
sort_order: zod_1.z
|
|
1734
|
+
.enum(["asc", "desc"])
|
|
1735
|
+
.optional()
|
|
1736
|
+
.describe("Sort order for results: 'asc' (oldest first) or 'desc' (newest first)"),
|
|
1737
|
+
next: zod_1.z
|
|
1738
|
+
.string()
|
|
1739
|
+
.optional()
|
|
1740
|
+
.nullable()
|
|
1741
|
+
.describe("Pagination token for next page of results (provided in previous response)"),
|
|
1742
|
+
requests_source: zod_1.z
|
|
1743
|
+
.enum(["pg", "clickhouse"])
|
|
1744
|
+
.default("pg")
|
|
1745
|
+
.optional()
|
|
1746
|
+
.describe("Data source: 'pg' (PostgreSQL, default) or 'clickhouse'"),
|
|
1747
|
+
}, async ({ spec_id, collection_id, endpoint_id, tag, ip, external_request_id, rule_id, status_code, from, until, max_results, sort_order, next, requests_source, }) => {
|
|
1748
|
+
// Validate time format parameters
|
|
1749
|
+
if (from) {
|
|
1750
|
+
const fromCheck = validateTimeFormat(from);
|
|
1751
|
+
if (!fromCheck.valid) {
|
|
1752
|
+
return { content: [{ type: "text", text: fromCheck.error }] };
|
|
1753
|
+
}
|
|
1539
1754
|
}
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
if (description !== null && description !== undefined) {
|
|
1546
|
-
payload["description"] = description;
|
|
1755
|
+
if (until) {
|
|
1756
|
+
const untilCheck = validateTimeFormat(until);
|
|
1757
|
+
if (!untilCheck.valid) {
|
|
1758
|
+
return { content: [{ type: "text", text: untilCheck.error }] };
|
|
1759
|
+
}
|
|
1547
1760
|
}
|
|
1548
|
-
|
|
1549
|
-
|
|
1761
|
+
// Validate max_results parameter
|
|
1762
|
+
const paginationCheck = validatePagination(undefined, max_results);
|
|
1763
|
+
if (!paginationCheck.valid) {
|
|
1764
|
+
return { content: [{ type: "text", text: paginationCheck.error }] };
|
|
1550
1765
|
}
|
|
1551
|
-
|
|
1552
|
-
|
|
1766
|
+
const queryParams = {};
|
|
1767
|
+
// Add array parameters
|
|
1768
|
+
if (spec_id !== null && spec_id !== undefined) {
|
|
1769
|
+
queryParams["spec_id"] = spec_id;
|
|
1553
1770
|
}
|
|
1554
|
-
if (
|
|
1555
|
-
|
|
1771
|
+
if (collection_id !== null && collection_id !== undefined) {
|
|
1772
|
+
queryParams["collection_id"] = collection_id;
|
|
1773
|
+
}
|
|
1774
|
+
if (endpoint_id !== null && endpoint_id !== undefined) {
|
|
1775
|
+
queryParams["endpoint_id"] = endpoint_id;
|
|
1776
|
+
}
|
|
1777
|
+
if (tag !== null && tag !== undefined) {
|
|
1778
|
+
queryParams["tag"] = tag;
|
|
1779
|
+
}
|
|
1780
|
+
if (ip !== null && ip !== undefined) {
|
|
1781
|
+
queryParams["ip"] = ip;
|
|
1782
|
+
}
|
|
1783
|
+
if (external_request_id !== null && external_request_id !== undefined) {
|
|
1784
|
+
queryParams["external_request_id"] = external_request_id;
|
|
1785
|
+
}
|
|
1786
|
+
if (rule_id !== null && rule_id !== undefined) {
|
|
1787
|
+
queryParams["rule_id"] = rule_id;
|
|
1556
1788
|
}
|
|
1557
|
-
|
|
1789
|
+
if (status_code !== null && status_code !== undefined) {
|
|
1790
|
+
queryParams["status_code"] = status_code;
|
|
1791
|
+
}
|
|
1792
|
+
// Add single value parameters
|
|
1793
|
+
if (from !== null && from !== undefined) {
|
|
1794
|
+
queryParams["from"] = from;
|
|
1795
|
+
}
|
|
1796
|
+
if (until !== null && until !== undefined) {
|
|
1797
|
+
queryParams["until"] = until;
|
|
1798
|
+
}
|
|
1799
|
+
if (max_results !== null && max_results !== undefined) {
|
|
1800
|
+
queryParams["max_results"] = max_results;
|
|
1801
|
+
}
|
|
1802
|
+
if (sort_order !== null && sort_order !== undefined) {
|
|
1803
|
+
queryParams["sort_order"] = sort_order;
|
|
1804
|
+
}
|
|
1805
|
+
if (next !== null && next !== undefined) {
|
|
1806
|
+
queryParams["next"] = next;
|
|
1807
|
+
}
|
|
1808
|
+
if (requests_source !== null && requests_source !== undefined) {
|
|
1809
|
+
queryParams["requests_source"] = requests_source;
|
|
1810
|
+
}
|
|
1811
|
+
const data = await makeImpartRequest("requests", queryParams);
|
|
1558
1812
|
if (!data) {
|
|
1813
|
+
const lastError = makeImpartRequest.lastError || "Unknown error";
|
|
1559
1814
|
return {
|
|
1560
|
-
content: [{ type: "text", text:
|
|
1815
|
+
content: [{ type: "text", text: `Unable to fetch requests: ${lastError}` }],
|
|
1561
1816
|
};
|
|
1562
1817
|
}
|
|
1563
|
-
const
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
`Name: ${data.name}`,
|
|
1567
|
-
`Description: ${data.description ?? "N/A"}`,
|
|
1568
|
-
`Required: ${data.required}`,
|
|
1569
|
-
`Labels: ${data.labels.join(", ")}`,
|
|
1570
|
-
`Messages Count: ${data.messages.length}`,
|
|
1571
|
-
`Assertions Count: ${data.assertions.length}`,
|
|
1572
|
-
`Updated By: ${data.updated_by}`,
|
|
1573
|
-
`Updated At: ${data.updated_at}`,
|
|
1574
|
-
"---",
|
|
1575
|
-
].join("\n");
|
|
1576
|
-
return {
|
|
1577
|
-
content: [{ type: "text", text: formattedTestCase }],
|
|
1578
|
-
};
|
|
1579
|
-
});
|
|
1580
|
-
server.tool(getToolName("delete_rules_test_case"), "Delete a rules test case", {
|
|
1581
|
-
rules_test_case_id: zod_1.z
|
|
1582
|
-
.string()
|
|
1583
|
-
.describe("The unique identifier of the rules test case"),
|
|
1584
|
-
}, async ({ rules_test_case_id }) => {
|
|
1585
|
-
if (!rules_test_case_id) {
|
|
1818
|
+
const requests = data.items || [];
|
|
1819
|
+
const meta = data.meta || {};
|
|
1820
|
+
if (requests.length === 0) {
|
|
1586
1821
|
return {
|
|
1587
|
-
content: [
|
|
1588
|
-
{ type: "text", text: "Error: rules_test_case_id is required." },
|
|
1589
|
-
],
|
|
1822
|
+
content: [{ type: "text", text: "No requests found matching the specified criteria." }],
|
|
1590
1823
|
};
|
|
1591
1824
|
}
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
}
|
|
1825
|
+
// Limit output for performance and readability
|
|
1826
|
+
const maxDisplay = Math.min(requests.length, 20);
|
|
1827
|
+
const showingLimited = requests.length > maxDisplay;
|
|
1828
|
+
const result = [
|
|
1829
|
+
`Found ${requests.length} requests (Total available: ${meta.total || "unknown"})`,
|
|
1830
|
+
showingLimited
|
|
1831
|
+
? `Showing first ${maxDisplay} requests for performance. Use pagination or filters to get specific results.`
|
|
1832
|
+
: "",
|
|
1833
|
+
"=".repeat(60),
|
|
1834
|
+
"",
|
|
1835
|
+
];
|
|
1836
|
+
// Show condensed format for better readability
|
|
1837
|
+
requests.slice(0, maxDisplay).forEach((request, index) => {
|
|
1838
|
+
const clientIp = request.client_ip?.address || "unknown";
|
|
1839
|
+
const tags = request.tags && request.tags.length > 0
|
|
1840
|
+
? request.tags.length > 3
|
|
1841
|
+
? `${request.tags.slice(0, 3).join(", ")} (+${request.tags.length - 3} more)`
|
|
1842
|
+
: request.tags.join(", ")
|
|
1843
|
+
: "None";
|
|
1844
|
+
result.push(`${index + 1}. ${request.method} ${request.path} (${request.status_code})`, ` ID: ${request.id} | IP: ${clientIp} | ${request.timestamp}`, ` Tags: ${tags}`, ` Rule: ${request.rule_id || "None"}`, "");
|
|
1845
|
+
});
|
|
1846
|
+
// Add pagination info
|
|
1847
|
+
if (data.next) {
|
|
1848
|
+
result.push(`Next page token: ${data.next}`);
|
|
1849
|
+
result.push("Use this token in the 'next' parameter to get the next page of results.");
|
|
1597
1850
|
}
|
|
1598
1851
|
return {
|
|
1599
|
-
content: [{ type: "text", text: "
|
|
1852
|
+
content: [{ type: "text", text: result.join("\n") }],
|
|
1600
1853
|
};
|
|
1601
1854
|
});
|
|
1602
|
-
server.tool(getToolName("
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
body: zod_1.z.string().optional(),
|
|
1618
|
-
})
|
|
1619
|
-
.optional(),
|
|
1620
|
-
}))
|
|
1621
|
-
.nullable()
|
|
1622
|
-
.describe("The test case messages to run"),
|
|
1623
|
-
}, async ({ messages }) => {
|
|
1624
|
-
if (!messages) {
|
|
1855
|
+
server.tool(getToolName("get_request_details"), "Get detailed information about a specific HTTP request captured by Impart. Provides complete request metadata, client information, and processing details. Examples:\n\n" +
|
|
1856
|
+
'• Basic usage: {"request_id": "123e4567-e89b-12d3-a456-426614174000"}\n' +
|
|
1857
|
+
'• With data source: {"request_id": "123e4567-e89b-12d3-a456-426614174000", "requests_source": "clickhouse"}\n' +
|
|
1858
|
+
'• PostgreSQL source: {"request_id": "abc12345-678d-90ef-1234-567890abcdef", "requests_source": "pg"}\n\n' +
|
|
1859
|
+
"Note: Use request IDs from the get_requests tool or your Impart dashboard.", {
|
|
1860
|
+
request_id: zod_1.z
|
|
1861
|
+
.string()
|
|
1862
|
+
.describe("The unique identifier of the request (UUID format, e.g., '123e4567-e89b-12d3-a456-426614174000')"),
|
|
1863
|
+
requests_source: zod_1.z
|
|
1864
|
+
.enum(["pg", "clickhouse"])
|
|
1865
|
+
.default("pg")
|
|
1866
|
+
.optional()
|
|
1867
|
+
.describe("Data source: 'pg' (PostgreSQL, default) or 'clickhouse'"),
|
|
1868
|
+
}, async ({ request_id, requests_source }) => {
|
|
1869
|
+
if (!request_id) {
|
|
1625
1870
|
return {
|
|
1626
|
-
content: [
|
|
1871
|
+
content: [
|
|
1872
|
+
{
|
|
1873
|
+
type: "text",
|
|
1874
|
+
text: "Error: request_id is required. Please provide a valid UUID for the request you want to retrieve details for.",
|
|
1875
|
+
},
|
|
1876
|
+
],
|
|
1627
1877
|
};
|
|
1628
1878
|
}
|
|
1629
|
-
const
|
|
1630
|
-
|
|
1879
|
+
const queryParams = {};
|
|
1880
|
+
if (requests_source !== null && requests_source !== undefined) {
|
|
1881
|
+
queryParams["requests_source"] = requests_source;
|
|
1882
|
+
}
|
|
1883
|
+
const data = await makeImpartRequest(`requests/${request_id}`, queryParams);
|
|
1631
1884
|
if (!data) {
|
|
1632
1885
|
return {
|
|
1633
|
-
content: [
|
|
1886
|
+
content: [
|
|
1887
|
+
{
|
|
1888
|
+
type: "text",
|
|
1889
|
+
text: `Unable to fetch request details for ID '${request_id}': Request not found or unknown error occurred.`,
|
|
1890
|
+
},
|
|
1891
|
+
],
|
|
1634
1892
|
};
|
|
1635
1893
|
}
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
"
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1894
|
+
// Format the detailed request information
|
|
1895
|
+
const result = [
|
|
1896
|
+
"Request Details",
|
|
1897
|
+
"=".repeat(50),
|
|
1898
|
+
"",
|
|
1899
|
+
`ID: ${data.id}`,
|
|
1900
|
+
`Method: ${data.method}`,
|
|
1901
|
+
`Path: ${data.path}`,
|
|
1902
|
+
`Status Code: ${data.status_code}`,
|
|
1903
|
+
`Timestamp: ${data.timestamp}`,
|
|
1904
|
+
"",
|
|
1905
|
+
"Client Information:",
|
|
1906
|
+
` IP Address: ${data.client_ip?.address || "N/A"}`,
|
|
1907
|
+
` IP Version: ${data.client_ip?.version || "N/A"}`,
|
|
1908
|
+
"",
|
|
1909
|
+
"Request Metadata:",
|
|
1910
|
+
` Spec ID: ${data.spec_id}`,
|
|
1911
|
+
` Endpoint ID: ${data.endpoint_id}`,
|
|
1912
|
+
` External Request ID: ${data.external_request_id}`,
|
|
1913
|
+
` Rule ID: ${data.rule_id || "N/A (no rule processed this request)"}`,
|
|
1914
|
+
"",
|
|
1915
|
+
"Tags:",
|
|
1916
|
+
data.tags && data.tags.length > 0 ? data.tags.map((tag) => ` • ${tag}`).join("\n") : " No tags applied",
|
|
1917
|
+
"",
|
|
1918
|
+
];
|
|
1919
|
+
// Add external links if available
|
|
1920
|
+
if (data.external_links && Object.keys(data.external_links).length > 0) {
|
|
1921
|
+
result.push("External Links:");
|
|
1922
|
+
Object.entries(data.external_links).forEach(([key, value]) => {
|
|
1923
|
+
// Properly serialize objects and arrays
|
|
1924
|
+
const serializedValue = typeof value === "object" ? JSON.stringify(value) : String(value);
|
|
1925
|
+
result.push(` ${key}: ${serializedValue}`);
|
|
1926
|
+
});
|
|
1927
|
+
result.push("");
|
|
1928
|
+
}
|
|
1929
|
+
// Add data source info
|
|
1930
|
+
result.push(`Data Source: ${requests_source || "pg"} (${requests_source === "clickhouse" ? "ClickHouse" : "PostgreSQL"})`);
|
|
1645
1931
|
return {
|
|
1646
|
-
content: [{ type: "text", text:
|
|
1932
|
+
content: [{ type: "text", text: result.join("\n") }],
|
|
1647
1933
|
};
|
|
1648
1934
|
});
|
|
1649
1935
|
async function main() {
|