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