itglue-mcp 1.0.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/LICENSE +21 -0
- package/README.md +382 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +513 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/api-client.d.ts +76 -0
- package/dist/lib/api-client.d.ts.map +1 -0
- package/dist/lib/api-client.js +189 -0
- package/dist/lib/api-client.js.map +1 -0
- package/dist/lib/audit-logger.d.ts +51 -0
- package/dist/lib/audit-logger.d.ts.map +1 -0
- package/dist/lib/audit-logger.js +119 -0
- package/dist/lib/audit-logger.js.map +1 -0
- package/dist/lib/cache.d.ts +52 -0
- package/dist/lib/cache.d.ts.map +1 -0
- package/dist/lib/cache.js +115 -0
- package/dist/lib/cache.js.map +1 -0
- package/dist/lib/error-handler.d.ts +25 -0
- package/dist/lib/error-handler.d.ts.map +1 -0
- package/dist/lib/error-handler.js +99 -0
- package/dist/lib/error-handler.js.map +1 -0
- package/dist/lib/rate-limiter.d.ts +45 -0
- package/dist/lib/rate-limiter.d.ts.map +1 -0
- package/dist/lib/rate-limiter.js +124 -0
- package/dist/lib/rate-limiter.js.map +1 -0
- package/dist/tools/auxiliary/audit/compliance-check.d.ts +54 -0
- package/dist/tools/auxiliary/audit/compliance-check.d.ts.map +1 -0
- package/dist/tools/auxiliary/audit/compliance-check.js +303 -0
- package/dist/tools/auxiliary/audit/compliance-check.js.map +1 -0
- package/dist/tools/auxiliary/health/health-check.d.ts +36 -0
- package/dist/tools/auxiliary/health/health-check.d.ts.map +1 -0
- package/dist/tools/auxiliary/health/health-check.js +287 -0
- package/dist/tools/auxiliary/health/health-check.js.map +1 -0
- package/dist/tools/auxiliary/reporting/organization-report.d.ts +41 -0
- package/dist/tools/auxiliary/reporting/organization-report.d.ts.map +1 -0
- package/dist/tools/auxiliary/reporting/organization-report.js +297 -0
- package/dist/tools/auxiliary/reporting/organization-report.js.map +1 -0
- package/dist/tools/auxiliary/staleness/staleness-detector.d.ts +82 -0
- package/dist/tools/auxiliary/staleness/staleness-detector.d.ts.map +1 -0
- package/dist/tools/auxiliary/staleness/staleness-detector.js +238 -0
- package/dist/tools/auxiliary/staleness/staleness-detector.js.map +1 -0
- package/dist/tools/auxiliary/validation/data-validator.d.ts +46 -0
- package/dist/tools/auxiliary/validation/data-validator.d.ts.map +1 -0
- package/dist/tools/auxiliary/validation/data-validator.js +296 -0
- package/dist/tools/auxiliary/validation/data-validator.js.map +1 -0
- package/dist/tools/configurations.d.ts +48 -0
- package/dist/tools/configurations.d.ts.map +1 -0
- package/dist/tools/configurations.js +89 -0
- package/dist/tools/configurations.js.map +1 -0
- package/dist/tools/documents.d.ts +42 -0
- package/dist/tools/documents.d.ts.map +1 -0
- package/dist/tools/documents.js +79 -0
- package/dist/tools/documents.js.map +1 -0
- package/dist/tools/flexible-assets.d.ts +79 -0
- package/dist/tools/flexible-assets.d.ts.map +1 -0
- package/dist/tools/flexible-assets.js +136 -0
- package/dist/tools/flexible-assets.js.map +1 -0
- package/dist/tools/organizations.d.ts +68 -0
- package/dist/tools/organizations.d.ts.map +1 -0
- package/dist/tools/organizations.js +123 -0
- package/dist/tools/organizations.js.map +1 -0
- package/dist/tools/passwords.d.ts +86 -0
- package/dist/tools/passwords.d.ts.map +1 -0
- package/dist/tools/passwords.js +165 -0
- package/dist/tools/passwords.js.map +1 -0
- package/dist/tools/search.d.ts +34 -0
- package/dist/tools/search.d.ts.map +1 -0
- package/dist/tools/search.js +79 -0
- package/dist/tools/search.js.map +1 -0
- package/dist/types/itglue.d.ts +129 -0
- package/dist/types/itglue.d.ts.map +1 -0
- package/dist/types/itglue.js +10 -0
- package/dist/types/itglue.js.map +1 -0
- package/dist/types/mcp.d.ts +33 -0
- package/dist/types/mcp.d.ts.map +1 -0
- package/dist/types/mcp.js +11 -0
- package/dist/types/mcp.js.map +1 -0
- package/package.json +64 -0
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error Handler for ITGlue API Errors
|
|
3
|
+
* Transforms API errors into user-friendly messages
|
|
4
|
+
*/
|
|
5
|
+
export class ITGlueAPIError extends Error {
|
|
6
|
+
statusCode;
|
|
7
|
+
isRetryable;
|
|
8
|
+
details;
|
|
9
|
+
constructor(message, statusCode, isRetryable = false, details) {
|
|
10
|
+
super(message);
|
|
11
|
+
this.name = 'ITGlueAPIError';
|
|
12
|
+
this.statusCode = statusCode;
|
|
13
|
+
this.isRetryable = isRetryable;
|
|
14
|
+
this.details = details;
|
|
15
|
+
// Maintains proper stack trace
|
|
16
|
+
Error.captureStackTrace(this, this.constructor);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Transform Axios error into ITGlueAPIError
|
|
21
|
+
*/
|
|
22
|
+
export function handleApiError(error) {
|
|
23
|
+
if (error instanceof ITGlueAPIError) {
|
|
24
|
+
return error;
|
|
25
|
+
}
|
|
26
|
+
// Handle Axios errors
|
|
27
|
+
if (isAxiosError(error)) {
|
|
28
|
+
const statusCode = error.response?.status || 0;
|
|
29
|
+
const responseData = error.response?.data;
|
|
30
|
+
// Extract error details from ITGlue response
|
|
31
|
+
let message = 'An unknown error occurred';
|
|
32
|
+
let details;
|
|
33
|
+
if (responseData && Array.isArray(responseData.errors) && responseData.errors.length > 0) {
|
|
34
|
+
const firstError = responseData.errors[0];
|
|
35
|
+
message = firstError.title || message;
|
|
36
|
+
details = firstError.detail;
|
|
37
|
+
}
|
|
38
|
+
switch (statusCode) {
|
|
39
|
+
case 401:
|
|
40
|
+
return new ITGlueAPIError('Authentication failed. Please check your ITGLUE_API_KEY environment variable.', 401, false, details || 'Invalid or missing API key');
|
|
41
|
+
case 403:
|
|
42
|
+
return new ITGlueAPIError('Access denied. Your API key does not have permission to access this resource.', 403, false, details);
|
|
43
|
+
case 404:
|
|
44
|
+
return new ITGlueAPIError('Resource not found. The requested resource does not exist or you do not have access to it.', 404, false, details);
|
|
45
|
+
case 422:
|
|
46
|
+
return new ITGlueAPIError('Invalid request parameters.', 422, false, details || 'Check your request parameters');
|
|
47
|
+
case 429:
|
|
48
|
+
const retryAfter = error.response?.headers['retry-after'];
|
|
49
|
+
return new ITGlueAPIError('Rate limit exceeded. The request will be automatically retried.', 429, true, retryAfter ? `Retry after ${retryAfter} seconds` : undefined);
|
|
50
|
+
case 500:
|
|
51
|
+
case 502:
|
|
52
|
+
case 503:
|
|
53
|
+
case 504:
|
|
54
|
+
return new ITGlueAPIError('ITGlue server error. The request will be automatically retried.', statusCode, true, details);
|
|
55
|
+
default:
|
|
56
|
+
if (statusCode >= 500) {
|
|
57
|
+
return new ITGlueAPIError('ITGlue server error. The request will be automatically retried.', statusCode, true, details);
|
|
58
|
+
}
|
|
59
|
+
return new ITGlueAPIError(message || `Request failed with status ${statusCode}`, statusCode, false, details);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
// Handle network errors
|
|
63
|
+
if (error instanceof Error) {
|
|
64
|
+
if (error.message.includes('ENOTFOUND') || error.message.includes('ECONNREFUSED')) {
|
|
65
|
+
return new ITGlueAPIError('Network error: Unable to connect to ITGlue API. Check your internet connection and ITGLUE_REGION setting.', 0, true, error.message);
|
|
66
|
+
}
|
|
67
|
+
if (error.message.includes('ETIMEDOUT')) {
|
|
68
|
+
return new ITGlueAPIError('Request timeout: ITGlue API did not respond in time.', 0, true, error.message);
|
|
69
|
+
}
|
|
70
|
+
return new ITGlueAPIError('Request failed: ' + error.message, 0, false, error.message);
|
|
71
|
+
}
|
|
72
|
+
return new ITGlueAPIError('An unknown error occurred', 0, false);
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Type guard for Axios errors
|
|
76
|
+
*/
|
|
77
|
+
function isAxiosError(error) {
|
|
78
|
+
return (typeof error === 'object' &&
|
|
79
|
+
error !== null &&
|
|
80
|
+
'isAxiosError' in error &&
|
|
81
|
+
error.isAxiosError === true);
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Format error for MCP tool response
|
|
85
|
+
*/
|
|
86
|
+
export function formatErrorResponse(error) {
|
|
87
|
+
let errorMessage = `Error: ${error.message}`;
|
|
88
|
+
if (error.details) {
|
|
89
|
+
errorMessage += `\n\nDetails: ${error.details}`;
|
|
90
|
+
}
|
|
91
|
+
if (error.statusCode) {
|
|
92
|
+
errorMessage += `\n\nStatus Code: ${error.statusCode}`;
|
|
93
|
+
}
|
|
94
|
+
return {
|
|
95
|
+
content: [{ type: 'text', text: errorMessage }],
|
|
96
|
+
isError: true,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
//# sourceMappingURL=error-handler.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"error-handler.js","sourceRoot":"","sources":["../../src/lib/error-handler.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Queue-Based Rate Limiter
|
|
3
|
+
* Ensures we stay within ITGlue's rate limits (3000 req/5min = 10 req/sec)
|
|
4
|
+
* Conservative limit: 8 req/sec with 20% safety margin
|
|
5
|
+
*/
|
|
6
|
+
import { AuditLogger } from './audit-logger.js';
|
|
7
|
+
export declare class RateLimiter {
|
|
8
|
+
private readonly requestsPerSecond;
|
|
9
|
+
private readonly requestQueue;
|
|
10
|
+
private readonly requestTimes;
|
|
11
|
+
private isProcessing;
|
|
12
|
+
private readonly logger;
|
|
13
|
+
constructor(requestsPerSecond?: number, logger?: AuditLogger);
|
|
14
|
+
/**
|
|
15
|
+
* Enqueue a request to be executed with rate limiting
|
|
16
|
+
*/
|
|
17
|
+
enqueue<T>(fn: () => Promise<T>): Promise<T>;
|
|
18
|
+
/**
|
|
19
|
+
* Process queued requests with rate limiting
|
|
20
|
+
*/
|
|
21
|
+
private processQueue;
|
|
22
|
+
/**
|
|
23
|
+
* Wait until a request slot is available
|
|
24
|
+
* Uses sliding window algorithm
|
|
25
|
+
*/
|
|
26
|
+
private waitForSlot;
|
|
27
|
+
/**
|
|
28
|
+
* Sleep for specified milliseconds
|
|
29
|
+
*/
|
|
30
|
+
private sleep;
|
|
31
|
+
/**
|
|
32
|
+
* Get current rate limiting stats
|
|
33
|
+
*/
|
|
34
|
+
getStats(): {
|
|
35
|
+
queueSize: number;
|
|
36
|
+
requestsInLastSecond: number;
|
|
37
|
+
maxRequestsPerSecond: number;
|
|
38
|
+
utilization: number;
|
|
39
|
+
};
|
|
40
|
+
/**
|
|
41
|
+
* Clear the queue (useful for testing or shutdown)
|
|
42
|
+
*/
|
|
43
|
+
clear(): void;
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=rate-limiter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rate-limiter.d.ts","sourceRoot":"","sources":["../../src/lib/rate-limiter.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAQhD,qBAAa,WAAW;IACtB,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAS;IAC3C,OAAO,CAAC,QAAQ,CAAC,YAAY,CAA4B;IACzD,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAgB;IAC7C,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAc;gBAEzB,iBAAiB,SAAI,EAAE,MAAM,CAAC,EAAE,WAAW;IAKvD;;OAEG;IACG,OAAO,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;IAgBlD;;OAEG;YACW,YAAY;IAgC1B;;;OAGG;YACW,WAAW;IA6BzB;;OAEG;IACH,OAAO,CAAC,KAAK;IAIb;;OAEG;IACH,QAAQ;;;;;;IAaR;;OAEG;IACH,KAAK,IAAI,IAAI;CAYd"}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Queue-Based Rate Limiter
|
|
3
|
+
* Ensures we stay within ITGlue's rate limits (3000 req/5min = 10 req/sec)
|
|
4
|
+
* Conservative limit: 8 req/sec with 20% safety margin
|
|
5
|
+
*/
|
|
6
|
+
import { AuditLogger } from './audit-logger.js';
|
|
7
|
+
export class RateLimiter {
|
|
8
|
+
requestsPerSecond;
|
|
9
|
+
requestQueue = [];
|
|
10
|
+
requestTimes = []; // Track request times in last second
|
|
11
|
+
isProcessing = false;
|
|
12
|
+
logger;
|
|
13
|
+
constructor(requestsPerSecond = 8, logger) {
|
|
14
|
+
this.requestsPerSecond = requestsPerSecond;
|
|
15
|
+
this.logger = logger || new AuditLogger();
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Enqueue a request to be executed with rate limiting
|
|
19
|
+
*/
|
|
20
|
+
async enqueue(fn) {
|
|
21
|
+
return new Promise((resolve, reject) => {
|
|
22
|
+
this.requestQueue.push({ fn, resolve, reject });
|
|
23
|
+
if (this.requestQueue.length > 1) {
|
|
24
|
+
this.logger.debug('Request queued', {
|
|
25
|
+
queueSize: this.requestQueue.length,
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
if (!this.isProcessing) {
|
|
29
|
+
this.processQueue();
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Process queued requests with rate limiting
|
|
35
|
+
*/
|
|
36
|
+
async processQueue() {
|
|
37
|
+
if (this.isProcessing) {
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
this.isProcessing = true;
|
|
41
|
+
while (this.requestQueue.length > 0) {
|
|
42
|
+
// Wait for available slot
|
|
43
|
+
await this.waitForSlot();
|
|
44
|
+
const request = this.requestQueue.shift();
|
|
45
|
+
if (!request) {
|
|
46
|
+
break;
|
|
47
|
+
}
|
|
48
|
+
// Track request time
|
|
49
|
+
const now = Date.now();
|
|
50
|
+
this.requestTimes.push(now);
|
|
51
|
+
// Execute request
|
|
52
|
+
try {
|
|
53
|
+
const result = await request.fn();
|
|
54
|
+
request.resolve(result);
|
|
55
|
+
}
|
|
56
|
+
catch (error) {
|
|
57
|
+
request.reject(error);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
this.isProcessing = false;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Wait until a request slot is available
|
|
64
|
+
* Uses sliding window algorithm
|
|
65
|
+
*/
|
|
66
|
+
async waitForSlot() {
|
|
67
|
+
const now = Date.now();
|
|
68
|
+
const oneSecondAgo = now - 1000;
|
|
69
|
+
// Remove requests older than 1 second
|
|
70
|
+
while (this.requestTimes.length > 0 && this.requestTimes[0] < oneSecondAgo) {
|
|
71
|
+
this.requestTimes.shift();
|
|
72
|
+
}
|
|
73
|
+
// If we're at the limit, wait
|
|
74
|
+
if (this.requestTimes.length >= this.requestsPerSecond) {
|
|
75
|
+
const oldestRequestTime = this.requestTimes[0];
|
|
76
|
+
const waitTime = oldestRequestTime + 1000 - now;
|
|
77
|
+
if (waitTime > 0) {
|
|
78
|
+
this.logger.debug('Rate limit throttling', {
|
|
79
|
+
waitTimeMs: waitTime,
|
|
80
|
+
currentRate: this.requestTimes.length,
|
|
81
|
+
maxRate: this.requestsPerSecond,
|
|
82
|
+
});
|
|
83
|
+
await this.sleep(waitTime);
|
|
84
|
+
// Recursively check again after waiting
|
|
85
|
+
return this.waitForSlot();
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Sleep for specified milliseconds
|
|
91
|
+
*/
|
|
92
|
+
sleep(ms) {
|
|
93
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Get current rate limiting stats
|
|
97
|
+
*/
|
|
98
|
+
getStats() {
|
|
99
|
+
const now = Date.now();
|
|
100
|
+
const oneSecondAgo = now - 1000;
|
|
101
|
+
const recentRequests = this.requestTimes.filter(time => time > oneSecondAgo);
|
|
102
|
+
return {
|
|
103
|
+
queueSize: this.requestQueue.length,
|
|
104
|
+
requestsInLastSecond: recentRequests.length,
|
|
105
|
+
maxRequestsPerSecond: this.requestsPerSecond,
|
|
106
|
+
utilization: (recentRequests.length / this.requestsPerSecond) * 100,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Clear the queue (useful for testing or shutdown)
|
|
111
|
+
*/
|
|
112
|
+
clear() {
|
|
113
|
+
// Reject all pending requests
|
|
114
|
+
while (this.requestQueue.length > 0) {
|
|
115
|
+
const request = this.requestQueue.shift();
|
|
116
|
+
if (request) {
|
|
117
|
+
request.reject(new Error('Rate limiter cleared'));
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
this.requestTimes.length = 0;
|
|
121
|
+
this.isProcessing = false;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
//# sourceMappingURL=rate-limiter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rate-limiter.js","sourceRoot":"","sources":["../../src/lib/rate-limiter.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compliance Check Tools
|
|
3
|
+
* Verify ITGlue data meets organizational compliance standards
|
|
4
|
+
*/
|
|
5
|
+
import { z } from 'zod';
|
|
6
|
+
import type { APIClient } from '../../../lib/api-client.js';
|
|
7
|
+
export declare const ComplianceCheckSchema: z.ZodObject<{
|
|
8
|
+
organization_id: z.ZodString;
|
|
9
|
+
checks: z.ZodDefault<z.ZodArray<z.ZodEnum<["passwords", "configurations", "documentation", "warranties"]>, "many">>;
|
|
10
|
+
}, "strip", z.ZodTypeAny, {
|
|
11
|
+
organization_id: string;
|
|
12
|
+
checks: ("configurations" | "passwords" | "documentation" | "warranties")[];
|
|
13
|
+
}, {
|
|
14
|
+
organization_id: string;
|
|
15
|
+
checks?: ("configurations" | "passwords" | "documentation" | "warranties")[] | undefined;
|
|
16
|
+
}>;
|
|
17
|
+
export type ComplianceCheckArgs = z.infer<typeof ComplianceCheckSchema>;
|
|
18
|
+
interface ComplianceIssue {
|
|
19
|
+
severity: 'low' | 'medium' | 'high' | 'critical';
|
|
20
|
+
category: string;
|
|
21
|
+
issue: string;
|
|
22
|
+
resource_type: string;
|
|
23
|
+
resource_id?: string;
|
|
24
|
+
recommendation: string;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Perform comprehensive compliance checks on ITGlue data
|
|
28
|
+
*/
|
|
29
|
+
export declare function performComplianceCheck(client: APIClient, args: ComplianceCheckArgs): Promise<{
|
|
30
|
+
content: Array<{
|
|
31
|
+
type: "text";
|
|
32
|
+
text: string;
|
|
33
|
+
}>;
|
|
34
|
+
isError: true;
|
|
35
|
+
} | {
|
|
36
|
+
content: {
|
|
37
|
+
type: "text";
|
|
38
|
+
text: string;
|
|
39
|
+
}[];
|
|
40
|
+
data: {
|
|
41
|
+
organization_id: string;
|
|
42
|
+
checks_performed: ("configurations" | "passwords" | "documentation" | "warranties")[];
|
|
43
|
+
total_issues: number;
|
|
44
|
+
issues_by_severity: {
|
|
45
|
+
critical: number;
|
|
46
|
+
high: number;
|
|
47
|
+
medium: number;
|
|
48
|
+
low: number;
|
|
49
|
+
};
|
|
50
|
+
issues: ComplianceIssue[];
|
|
51
|
+
};
|
|
52
|
+
}>;
|
|
53
|
+
export {};
|
|
54
|
+
//# sourceMappingURL=compliance-check.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compliance-check.d.ts","sourceRoot":"","sources":["../../../../src/tools/auxiliary/audit/compliance-check.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,4BAA4B,CAAC;AAI5D,eAAO,MAAM,qBAAqB;;;;;;;;;EAMhC,CAAC;AAEH,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAC;AAExE,UAAU,eAAe;IACvB,QAAQ,EAAE,KAAK,GAAG,QAAQ,GAAG,MAAM,GAAG,UAAU,CAAC;IACjD,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,cAAc,EAAE,MAAM,CAAC;CACxB;AAED;;GAEG;AACH,wBAAsB,sBAAsB,CAAC,MAAM,EAAE,SAAS,EAAE,IAAI,EAAE,mBAAmB;;;;;;;;;;;;;;;;;;;;;;;GAkDxF"}
|
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compliance Check Tools
|
|
3
|
+
* Verify ITGlue data meets organizational compliance standards
|
|
4
|
+
*/
|
|
5
|
+
import { z } from 'zod';
|
|
6
|
+
import { handleApiError, formatErrorResponse } from '../../../lib/error-handler.js';
|
|
7
|
+
export const ComplianceCheckSchema = z.object({
|
|
8
|
+
organization_id: z.string().describe('Organization ID to check compliance for'),
|
|
9
|
+
checks: z
|
|
10
|
+
.array(z.enum(['passwords', 'configurations', 'documentation', 'warranties']))
|
|
11
|
+
.default(['passwords', 'configurations', 'documentation'])
|
|
12
|
+
.describe('Compliance checks to perform'),
|
|
13
|
+
});
|
|
14
|
+
/**
|
|
15
|
+
* Perform comprehensive compliance checks on ITGlue data
|
|
16
|
+
*/
|
|
17
|
+
export async function performComplianceCheck(client, args) {
|
|
18
|
+
try {
|
|
19
|
+
const issues = [];
|
|
20
|
+
const checks = args.checks;
|
|
21
|
+
// Password compliance checks
|
|
22
|
+
if (checks.includes('passwords')) {
|
|
23
|
+
const passwordIssues = await checkPasswordCompliance(client, args.organization_id);
|
|
24
|
+
issues.push(...passwordIssues);
|
|
25
|
+
}
|
|
26
|
+
// Configuration compliance checks
|
|
27
|
+
if (checks.includes('configurations')) {
|
|
28
|
+
const configIssues = await checkConfigurationCompliance(client, args.organization_id);
|
|
29
|
+
issues.push(...configIssues);
|
|
30
|
+
}
|
|
31
|
+
// Documentation compliance checks
|
|
32
|
+
if (checks.includes('documentation')) {
|
|
33
|
+
const docIssues = await checkDocumentationCompliance(client, args.organization_id);
|
|
34
|
+
issues.push(...docIssues);
|
|
35
|
+
}
|
|
36
|
+
// Warranty compliance checks
|
|
37
|
+
if (checks.includes('warranties')) {
|
|
38
|
+
const warrantyIssues = await checkWarrantyCompliance(client, args.organization_id);
|
|
39
|
+
issues.push(...warrantyIssues);
|
|
40
|
+
}
|
|
41
|
+
const resultText = formatComplianceReport(issues, args.organization_id, checks);
|
|
42
|
+
return {
|
|
43
|
+
content: [{ type: 'text', text: resultText }],
|
|
44
|
+
data: {
|
|
45
|
+
organization_id: args.organization_id,
|
|
46
|
+
checks_performed: checks,
|
|
47
|
+
total_issues: issues.length,
|
|
48
|
+
issues_by_severity: {
|
|
49
|
+
critical: issues.filter(i => i.severity === 'critical').length,
|
|
50
|
+
high: issues.filter(i => i.severity === 'high').length,
|
|
51
|
+
medium: issues.filter(i => i.severity === 'medium').length,
|
|
52
|
+
low: issues.filter(i => i.severity === 'low').length,
|
|
53
|
+
},
|
|
54
|
+
issues,
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
catch (error) {
|
|
59
|
+
const apiError = handleApiError(error);
|
|
60
|
+
return formatErrorResponse(apiError);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Check password-related compliance issues
|
|
65
|
+
*/
|
|
66
|
+
async function checkPasswordCompliance(client, organizationId) {
|
|
67
|
+
const issues = [];
|
|
68
|
+
try {
|
|
69
|
+
const response = await client.get('/passwords', {
|
|
70
|
+
'filter[organization_id]': organizationId,
|
|
71
|
+
'page[size]': 100,
|
|
72
|
+
}, {
|
|
73
|
+
skipCache: true,
|
|
74
|
+
organizationId,
|
|
75
|
+
});
|
|
76
|
+
const passwords = Array.isArray(response.data) ? response.data : [response.data];
|
|
77
|
+
for (const pwd of passwords) {
|
|
78
|
+
// Check for missing category
|
|
79
|
+
if (!pwd.attributes['password-category-id'] && !pwd.attributes['password-category-name']) {
|
|
80
|
+
issues.push({
|
|
81
|
+
severity: 'medium',
|
|
82
|
+
category: 'Password Management',
|
|
83
|
+
issue: `Password "${pwd.attributes.name}" has no category assigned`,
|
|
84
|
+
resource_type: 'password',
|
|
85
|
+
resource_id: pwd.id,
|
|
86
|
+
recommendation: 'Assign a password category for better organization and compliance tracking',
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
// Check for missing username
|
|
90
|
+
if (!pwd.attributes.username) {
|
|
91
|
+
issues.push({
|
|
92
|
+
severity: 'low',
|
|
93
|
+
category: 'Password Management',
|
|
94
|
+
issue: `Password "${pwd.attributes.name}" has no username`,
|
|
95
|
+
resource_type: 'password',
|
|
96
|
+
resource_id: pwd.id,
|
|
97
|
+
recommendation: 'Add username for complete credential documentation',
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
// Check for very old passwords (>365 days without update)
|
|
101
|
+
const updatedAt = new Date(pwd.attributes['updated-at']);
|
|
102
|
+
const daysSinceUpdate = Math.floor((Date.now() - updatedAt.getTime()) / (1000 * 60 * 60 * 24));
|
|
103
|
+
if (daysSinceUpdate > 365) {
|
|
104
|
+
issues.push({
|
|
105
|
+
severity: 'high',
|
|
106
|
+
category: 'Password Management',
|
|
107
|
+
issue: `Password "${pwd.attributes.name}" not updated in ${daysSinceUpdate} days`,
|
|
108
|
+
resource_type: 'password',
|
|
109
|
+
resource_id: pwd.id,
|
|
110
|
+
recommendation: 'Review and update password, verify it is still current and secure',
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
catch (error) {
|
|
116
|
+
// If passwords can't be fetched, note it but don't fail the whole check
|
|
117
|
+
issues.push({
|
|
118
|
+
severity: 'high',
|
|
119
|
+
category: 'Password Management',
|
|
120
|
+
issue: 'Unable to fetch passwords for compliance check',
|
|
121
|
+
resource_type: 'password',
|
|
122
|
+
recommendation: 'Verify API permissions include password access',
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
return issues;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Check configuration-related compliance issues
|
|
129
|
+
*/
|
|
130
|
+
async function checkConfigurationCompliance(client, organizationId) {
|
|
131
|
+
const issues = [];
|
|
132
|
+
try {
|
|
133
|
+
const response = await client.get('/configurations', {
|
|
134
|
+
'filter[organization_id]': organizationId,
|
|
135
|
+
'page[size]': 100,
|
|
136
|
+
}, {
|
|
137
|
+
cacheTTL: 60,
|
|
138
|
+
organizationId,
|
|
139
|
+
});
|
|
140
|
+
const configs = Array.isArray(response.data) ? response.data : [response.data];
|
|
141
|
+
for (const config of configs) {
|
|
142
|
+
// Check for missing serial number on physical assets
|
|
143
|
+
const isPhysical = ['Server', 'Workstation', 'Laptop', 'Desktop'].some(type => config.attributes['configuration-type-name']?.includes(type));
|
|
144
|
+
if (isPhysical && !config.attributes['serial-number']) {
|
|
145
|
+
issues.push({
|
|
146
|
+
severity: 'medium',
|
|
147
|
+
category: 'Asset Management',
|
|
148
|
+
issue: `Configuration "${config.attributes.name}" missing serial number`,
|
|
149
|
+
resource_type: 'configuration',
|
|
150
|
+
resource_id: config.id,
|
|
151
|
+
recommendation: 'Add serial number for asset tracking and warranty management',
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
// Check for missing manufacturer/model
|
|
155
|
+
if (isPhysical && (!config.attributes['manufacturer-name'] || !config.attributes['model-number'])) {
|
|
156
|
+
issues.push({
|
|
157
|
+
severity: 'low',
|
|
158
|
+
category: 'Asset Management',
|
|
159
|
+
issue: `Configuration "${config.attributes.name}" missing manufacturer or model information`,
|
|
160
|
+
resource_type: 'configuration',
|
|
161
|
+
resource_id: config.id,
|
|
162
|
+
recommendation: 'Add manufacturer and model for accurate asset documentation',
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
catch (error) {
|
|
168
|
+
issues.push({
|
|
169
|
+
severity: 'high',
|
|
170
|
+
category: 'Asset Management',
|
|
171
|
+
issue: 'Unable to fetch configurations for compliance check',
|
|
172
|
+
resource_type: 'configuration',
|
|
173
|
+
recommendation: 'Verify API permissions and connectivity',
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
return issues;
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Check documentation-related compliance issues
|
|
180
|
+
*/
|
|
181
|
+
async function checkDocumentationCompliance(client, organizationId) {
|
|
182
|
+
const issues = [];
|
|
183
|
+
try {
|
|
184
|
+
const response = await client.get('/documents', {
|
|
185
|
+
'filter[organization_id]': organizationId,
|
|
186
|
+
'page[size]': 10,
|
|
187
|
+
}, {
|
|
188
|
+
cacheTTL: 60,
|
|
189
|
+
organizationId,
|
|
190
|
+
});
|
|
191
|
+
const documents = Array.isArray(response.data) ? response.data : [response.data];
|
|
192
|
+
if (documents.length === 0) {
|
|
193
|
+
issues.push({
|
|
194
|
+
severity: 'medium',
|
|
195
|
+
category: 'Documentation',
|
|
196
|
+
issue: 'No documents found for organization',
|
|
197
|
+
resource_type: 'document',
|
|
198
|
+
recommendation: 'Upload essential documentation (network diagrams, SOPs, vendor contacts)',
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
catch (error) {
|
|
203
|
+
// Non-critical if documents can't be checked
|
|
204
|
+
}
|
|
205
|
+
return issues;
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Check warranty-related compliance issues
|
|
209
|
+
*/
|
|
210
|
+
async function checkWarrantyCompliance(client, organizationId) {
|
|
211
|
+
const issues = [];
|
|
212
|
+
try {
|
|
213
|
+
const response = await client.get('/configurations', {
|
|
214
|
+
'filter[organization_id]': organizationId,
|
|
215
|
+
'page[size]': 100,
|
|
216
|
+
}, {
|
|
217
|
+
cacheTTL: 60,
|
|
218
|
+
organizationId,
|
|
219
|
+
});
|
|
220
|
+
const configs = Array.isArray(response.data) ? response.data : [response.data];
|
|
221
|
+
for (const config of configs) {
|
|
222
|
+
const warrantyExpires = config.attributes['warranty-expires-at'];
|
|
223
|
+
if (warrantyExpires) {
|
|
224
|
+
const expiryDate = new Date(warrantyExpires);
|
|
225
|
+
const daysUntilExpiry = Math.floor((expiryDate.getTime() - Date.now()) / (1000 * 60 * 60 * 24));
|
|
226
|
+
if (daysUntilExpiry < 0) {
|
|
227
|
+
issues.push({
|
|
228
|
+
severity: 'medium',
|
|
229
|
+
category: 'Warranty Management',
|
|
230
|
+
issue: `Configuration "${config.attributes.name}" warranty expired ${Math.abs(daysUntilExpiry)} days ago`,
|
|
231
|
+
resource_type: 'configuration',
|
|
232
|
+
resource_id: config.id,
|
|
233
|
+
recommendation: 'Review asset status and consider renewal or replacement',
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
else if (daysUntilExpiry <= 90) {
|
|
237
|
+
issues.push({
|
|
238
|
+
severity: 'low',
|
|
239
|
+
category: 'Warranty Management',
|
|
240
|
+
issue: `Configuration "${config.attributes.name}" warranty expires in ${daysUntilExpiry} days`,
|
|
241
|
+
resource_type: 'configuration',
|
|
242
|
+
resource_id: config.id,
|
|
243
|
+
recommendation: 'Plan for warranty renewal or replacement',
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
catch (error) {
|
|
250
|
+
// Non-critical
|
|
251
|
+
}
|
|
252
|
+
return issues;
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* Format compliance report
|
|
256
|
+
*/
|
|
257
|
+
function formatComplianceReport(issues, organizationId, checks) {
|
|
258
|
+
const lines = [
|
|
259
|
+
`# Compliance Check Report`,
|
|
260
|
+
`**Organization ID**: ${organizationId}`,
|
|
261
|
+
`**Checks Performed**: ${checks.join(', ')}`,
|
|
262
|
+
`**Total Issues Found**: ${issues.length}`,
|
|
263
|
+
'',
|
|
264
|
+
];
|
|
265
|
+
if (issues.length === 0) {
|
|
266
|
+
lines.push('✅ **No compliance issues found!**');
|
|
267
|
+
lines.push('');
|
|
268
|
+
lines.push('All checked areas meet compliance standards.');
|
|
269
|
+
return lines.join('\n');
|
|
270
|
+
}
|
|
271
|
+
// Group by severity
|
|
272
|
+
const bySeverity = {
|
|
273
|
+
critical: issues.filter(i => i.severity === 'critical'),
|
|
274
|
+
high: issues.filter(i => i.severity === 'high'),
|
|
275
|
+
medium: issues.filter(i => i.severity === 'medium'),
|
|
276
|
+
low: issues.filter(i => i.severity === 'low'),
|
|
277
|
+
};
|
|
278
|
+
lines.push('## Issues by Severity');
|
|
279
|
+
lines.push('');
|
|
280
|
+
lines.push(`- 🔴 **Critical**: ${bySeverity.critical.length}`);
|
|
281
|
+
lines.push(`- 🟠 **High**: ${bySeverity.high.length}`);
|
|
282
|
+
lines.push(`- 🟡 **Medium**: ${bySeverity.medium.length}`);
|
|
283
|
+
lines.push(`- 🟢 **Low**: ${bySeverity.low.length}`);
|
|
284
|
+
lines.push('');
|
|
285
|
+
// Detail each severity level
|
|
286
|
+
for (const [severity, severityIssues] of Object.entries(bySeverity)) {
|
|
287
|
+
if (severityIssues.length === 0)
|
|
288
|
+
continue;
|
|
289
|
+
const emoji = { critical: '🔴', high: '🟠', medium: '🟡', low: '🟢' }[severity];
|
|
290
|
+
lines.push(`## ${emoji} ${severity.toUpperCase()} Priority Issues`);
|
|
291
|
+
lines.push('');
|
|
292
|
+
for (const issue of severityIssues) {
|
|
293
|
+
lines.push(`### ${issue.category}: ${issue.issue}`);
|
|
294
|
+
if (issue.resource_id) {
|
|
295
|
+
lines.push(`**Resource**: ${issue.resource_type} (ID: ${issue.resource_id})`);
|
|
296
|
+
}
|
|
297
|
+
lines.push(`**Recommendation**: ${issue.recommendation}`);
|
|
298
|
+
lines.push('');
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
return lines.join('\n');
|
|
302
|
+
}
|
|
303
|
+
//# sourceMappingURL=compliance-check.js.map
|