@zerry_jin/k8s-doctor-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.
@@ -0,0 +1,337 @@
1
+ /**
2
+ * K8s Doctor type definitions
3
+ *
4
+ * @author zerry
5
+ */
6
+ /**
7
+ * Diagnostic severity
8
+ * Represents urgency of Kubernetes issues
9
+ */
10
+ export type Severity = 'critical' | 'high' | 'medium' | 'low' | 'info';
11
+ /**
12
+ * Pod status
13
+ * Possible pod states in K8s
14
+ */
15
+ export type PodPhase = 'Pending' | 'Running' | 'Succeeded' | 'Failed' | 'Unknown';
16
+ /**
17
+ * Diagnostic issue
18
+ *
19
+ * Contains each detected problem and solution.
20
+ * Not just showing status but explaining "why this is a problem"
21
+ */
22
+ export interface DiagnosticIssue {
23
+ /** Issue type (e.g., "CrashLoopBackOff", "ImagePullBackOff") */
24
+ type: string;
25
+ /** Severity */
26
+ severity: Severity;
27
+ /** Human-readable description */
28
+ message: string;
29
+ /** Root cause analysis */
30
+ rootCause: string;
31
+ /** Suggested solution (includes specific kubectl commands) */
32
+ solution: string;
33
+ /** Related resource (pod name, namespace, etc.) */
34
+ resource?: {
35
+ kind: string;
36
+ name: string;
37
+ namespace: string;
38
+ };
39
+ /** Related log lines */
40
+ relevantLogs?: string[];
41
+ /** Related events */
42
+ relatedEvents?: K8sEvent[];
43
+ /** Discovery timestamp */
44
+ timestamp: string;
45
+ }
46
+ /**
47
+ * Pod diagnostics result
48
+ *
49
+ * Comprehensive diagnostics combining pod status, resource usage, events, etc.
50
+ */
51
+ export interface PodDiagnostics {
52
+ /** Pod basic information */
53
+ podInfo: {
54
+ name: string;
55
+ namespace: string;
56
+ phase: PodPhase;
57
+ startTime?: string;
58
+ nodeName?: string;
59
+ hostIP?: string;
60
+ podIP?: string;
61
+ };
62
+ /** Container status */
63
+ containers: ContainerStatus[];
64
+ /** Detected issues */
65
+ issues: DiagnosticIssue[];
66
+ /** Resource usage */
67
+ resources: ResourceUsage;
68
+ /** Recent events (problem-related) */
69
+ events: K8sEvent[];
70
+ /** Diagnosis summary */
71
+ summary: string;
72
+ /** Health score (0-100) */
73
+ healthScore: number;
74
+ }
75
+ /**
76
+ * Container status
77
+ */
78
+ export interface ContainerStatus {
79
+ /** Container name */
80
+ name: string;
81
+ /** Ready status */
82
+ ready: boolean;
83
+ /** Restart count */
84
+ restartCount: number;
85
+ /** Current state */
86
+ state: {
87
+ running?: {
88
+ startedAt: string;
89
+ };
90
+ waiting?: {
91
+ reason: string;
92
+ message?: string;
93
+ };
94
+ terminated?: {
95
+ reason: string;
96
+ exitCode: number;
97
+ message?: string;
98
+ };
99
+ };
100
+ /** Last state (if restarted) */
101
+ lastState?: {
102
+ terminated?: {
103
+ reason: string;
104
+ exitCode: number;
105
+ finishedAt: string;
106
+ };
107
+ };
108
+ /** Image */
109
+ image: string;
110
+ /** Image pull status */
111
+ imageID?: string;
112
+ }
113
+ /**
114
+ * Resource usage
115
+ *
116
+ * CPU and memory usage with threshold comparison results
117
+ */
118
+ export interface ResourceUsage {
119
+ /** CPU usage */
120
+ cpu: {
121
+ /** Current usage (millicores) */
122
+ current?: number;
123
+ /** Requested amount (requests) */
124
+ requested?: number;
125
+ /** Limit amount (limits) */
126
+ limit?: number;
127
+ /** Usage percentage (%) */
128
+ usagePercent?: number;
129
+ /** Threshold exceeded */
130
+ isThrottled: boolean;
131
+ };
132
+ /** Memory usage */
133
+ memory: {
134
+ /** Current usage (bytes) */
135
+ current?: number;
136
+ /** Requested amount (requests) */
137
+ requested?: number;
138
+ /** Limit amount (limits) */
139
+ limit?: number;
140
+ /** Usage percentage (%) */
141
+ usagePercent?: number;
142
+ /** OOM risk */
143
+ isOOMRisk: boolean;
144
+ };
145
+ /** Disk usage */
146
+ disk?: {
147
+ current?: number;
148
+ limit?: number;
149
+ usagePercent?: number;
150
+ };
151
+ }
152
+ /**
153
+ * K8s event
154
+ *
155
+ * Events shown in kubectl describe
156
+ */
157
+ export interface K8sEvent {
158
+ /** Event type (Normal/Warning) */
159
+ type: string;
160
+ /** Reason (e.g., "Failed", "BackOff") */
161
+ reason: string;
162
+ /** Message */
163
+ message: string;
164
+ /** Occurrence count */
165
+ count: number;
166
+ /** First occurrence timestamp */
167
+ firstTimestamp: string;
168
+ /** Last occurrence timestamp */
169
+ lastTimestamp: string;
170
+ /** Source component */
171
+ source?: string;
172
+ }
173
+ /**
174
+ * Log analysis result
175
+ *
176
+ * Not just log output, but analyzed error patterns
177
+ */
178
+ export interface LogAnalysis {
179
+ /** Total line count */
180
+ totalLines: number;
181
+ /** Error lines */
182
+ errorLines: LogEntry[];
183
+ /** Warning lines */
184
+ warningLines: LogEntry[];
185
+ /** Detected error patterns */
186
+ patterns: ErrorPattern[];
187
+ /** Repeated errors (same error multiple times) */
188
+ repeatedErrors: RepeatedError[];
189
+ /** Log analysis summary */
190
+ summary: string;
191
+ /** Recommendations */
192
+ recommendations: string[];
193
+ }
194
+ /**
195
+ * Log entry
196
+ */
197
+ export interface LogEntry {
198
+ /** Line number */
199
+ lineNumber: number;
200
+ /** Log content */
201
+ content: string;
202
+ /** Timestamp (if parseable) */
203
+ timestamp?: string;
204
+ /** Log level */
205
+ level?: 'ERROR' | 'WARN' | 'INFO' | 'DEBUG';
206
+ }
207
+ /**
208
+ * Error pattern
209
+ *
210
+ * Patterns of commonly occurring errors with solutions
211
+ */
212
+ export interface ErrorPattern {
213
+ /** Pattern name */
214
+ name: string;
215
+ /** Matched log lines */
216
+ matchedLines: number[];
217
+ /** Pattern description */
218
+ description: string;
219
+ /** Possible causes */
220
+ possibleCauses: string[];
221
+ /** Solutions */
222
+ solutions: string[];
223
+ /** Severity */
224
+ severity: Severity;
225
+ }
226
+ /**
227
+ * Repeated error
228
+ */
229
+ export interface RepeatedError {
230
+ /** Error message (normalized) */
231
+ message: string;
232
+ /** Occurrence count */
233
+ count: number;
234
+ /** First occurrence line */
235
+ firstLine: number;
236
+ /** Last occurrence line */
237
+ lastLine: number;
238
+ /** Is pattern */
239
+ isPattern: boolean;
240
+ }
241
+ /**
242
+ * Network diagnostics result
243
+ */
244
+ export interface NetworkDiagnostics {
245
+ /** Service connectivity */
246
+ serviceConnectivity: ServiceConnectivity[];
247
+ /** DNS issues */
248
+ dnsIssues: DiagnosticIssue[];
249
+ /** Network policy issues */
250
+ networkPolicyIssues: DiagnosticIssue[];
251
+ /** Ingress issues */
252
+ ingressIssues: DiagnosticIssue[];
253
+ /** Diagnosis summary */
254
+ summary: string;
255
+ }
256
+ /**
257
+ * Service connectivity
258
+ */
259
+ export interface ServiceConnectivity {
260
+ /** Service name */
261
+ serviceName: string;
262
+ /** Namespace */
263
+ namespace: string;
264
+ /** Reachable status */
265
+ isReachable: boolean;
266
+ /** Endpoint count */
267
+ endpointCount: number;
268
+ /** Issues */
269
+ issues: string[];
270
+ }
271
+ /**
272
+ * Storage diagnostics result
273
+ */
274
+ export interface StorageDiagnostics {
275
+ /** PVC status */
276
+ pvcStatus: PVCStatus[];
277
+ /** Volume mount issues */
278
+ mountIssues: DiagnosticIssue[];
279
+ /** Capacity issues */
280
+ capacityIssues: DiagnosticIssue[];
281
+ /** Diagnosis summary */
282
+ summary: string;
283
+ }
284
+ /**
285
+ * PVC status
286
+ */
287
+ export interface PVCStatus {
288
+ /** PVC name */
289
+ name: string;
290
+ /** Namespace */
291
+ namespace: string;
292
+ /** Phase (Bound/Pending/Lost) */
293
+ phase: string;
294
+ /** Requested capacity */
295
+ requestedCapacity: string;
296
+ /** Actual capacity */
297
+ actualCapacity?: string;
298
+ /** Storage class */
299
+ storageClass?: string;
300
+ /** Issues */
301
+ issues: string[];
302
+ }
303
+ /**
304
+ * Cluster health diagnosis
305
+ */
306
+ export interface ClusterHealth {
307
+ /** Overall health score (0-100) */
308
+ overallScore: number;
309
+ /** Node health */
310
+ nodeHealth: {
311
+ total: number;
312
+ ready: number;
313
+ notReady: number;
314
+ issues: DiagnosticIssue[];
315
+ };
316
+ /** Pod health */
317
+ podHealth: {
318
+ total: number;
319
+ running: number;
320
+ pending: number;
321
+ failed: number;
322
+ crashLooping: number;
323
+ issues: DiagnosticIssue[];
324
+ };
325
+ /** Resource utilization */
326
+ resourceUtilization: {
327
+ cpu: number;
328
+ memory: number;
329
+ storage: number;
330
+ };
331
+ /** Critical issues */
332
+ criticalIssues: DiagnosticIssue[];
333
+ /** Recommendations */
334
+ recommendations: string[];
335
+ /** Diagnosis summary */
336
+ summary: string;
337
+ }
package/dist/types.js ADDED
@@ -0,0 +1,6 @@
1
+ /**
2
+ * K8s Doctor type definitions
3
+ *
4
+ * @author zerry
5
+ */
6
+ export {};
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Simple in-memory cache with TTL
3
+ *
4
+ * @author zerry
5
+ */
6
+ /**
7
+ * Simple memory cache with TTL support
8
+ */
9
+ export declare class MemoryCache<T> {
10
+ private cache;
11
+ private defaultTTL;
12
+ /**
13
+ * @param defaultTTL Default time-to-live in milliseconds (default: 30 seconds)
14
+ */
15
+ constructor(defaultTTL?: number);
16
+ /**
17
+ * Get value from cache
18
+ *
19
+ * @param key Cache key
20
+ * @returns Cached value or undefined if not found or expired
21
+ */
22
+ get(key: string): T | undefined;
23
+ /**
24
+ * Set value in cache
25
+ *
26
+ * @param key Cache key
27
+ * @param value Value to cache
28
+ * @param ttl Time-to-live in milliseconds (optional, uses default if not provided)
29
+ */
30
+ set(key: string, value: T, ttl?: number): void;
31
+ /**
32
+ * Delete value from cache
33
+ *
34
+ * @param key Cache key
35
+ */
36
+ delete(key: string): void;
37
+ /**
38
+ * Clear all cache entries
39
+ */
40
+ clear(): void;
41
+ /**
42
+ * Get cache size
43
+ */
44
+ size(): number;
45
+ /**
46
+ * Clean up expired entries
47
+ */
48
+ cleanup(): void;
49
+ }
50
+ /**
51
+ * Get or compute cached value
52
+ *
53
+ * @param cache Cache instance
54
+ * @param key Cache key
55
+ * @param computeFn Function to compute value if not cached
56
+ * @param ttl Optional TTL override
57
+ * @returns Cached or computed value
58
+ */
59
+ export declare function getOrCompute<T>(cache: MemoryCache<T>, key: string, computeFn: () => Promise<T>, ttl?: number): Promise<T>;
@@ -0,0 +1,99 @@
1
+ /**
2
+ * Simple in-memory cache with TTL
3
+ *
4
+ * @author zerry
5
+ */
6
+ /**
7
+ * Simple memory cache with TTL support
8
+ */
9
+ export class MemoryCache {
10
+ cache = new Map();
11
+ defaultTTL;
12
+ /**
13
+ * @param defaultTTL Default time-to-live in milliseconds (default: 30 seconds)
14
+ */
15
+ constructor(defaultTTL = 30000) {
16
+ this.defaultTTL = defaultTTL;
17
+ }
18
+ /**
19
+ * Get value from cache
20
+ *
21
+ * @param key Cache key
22
+ * @returns Cached value or undefined if not found or expired
23
+ */
24
+ get(key) {
25
+ const entry = this.cache.get(key);
26
+ if (!entry) {
27
+ return undefined;
28
+ }
29
+ // Check if expired
30
+ if (Date.now() > entry.expiresAt) {
31
+ this.cache.delete(key);
32
+ return undefined;
33
+ }
34
+ return entry.value;
35
+ }
36
+ /**
37
+ * Set value in cache
38
+ *
39
+ * @param key Cache key
40
+ * @param value Value to cache
41
+ * @param ttl Time-to-live in milliseconds (optional, uses default if not provided)
42
+ */
43
+ set(key, value, ttl) {
44
+ const expiresAt = Date.now() + (ttl ?? this.defaultTTL);
45
+ this.cache.set(key, { value, expiresAt });
46
+ }
47
+ /**
48
+ * Delete value from cache
49
+ *
50
+ * @param key Cache key
51
+ */
52
+ delete(key) {
53
+ this.cache.delete(key);
54
+ }
55
+ /**
56
+ * Clear all cache entries
57
+ */
58
+ clear() {
59
+ this.cache.clear();
60
+ }
61
+ /**
62
+ * Get cache size
63
+ */
64
+ size() {
65
+ return this.cache.size;
66
+ }
67
+ /**
68
+ * Clean up expired entries
69
+ */
70
+ cleanup() {
71
+ const now = Date.now();
72
+ for (const [key, entry] of this.cache.entries()) {
73
+ if (now > entry.expiresAt) {
74
+ this.cache.delete(key);
75
+ }
76
+ }
77
+ }
78
+ }
79
+ /**
80
+ * Get or compute cached value
81
+ *
82
+ * @param cache Cache instance
83
+ * @param key Cache key
84
+ * @param computeFn Function to compute value if not cached
85
+ * @param ttl Optional TTL override
86
+ * @returns Cached or computed value
87
+ */
88
+ export async function getOrCompute(cache, key, computeFn, ttl) {
89
+ // Try to get from cache first
90
+ const cached = cache.get(key);
91
+ if (cached !== undefined) {
92
+ return cached;
93
+ }
94
+ // Compute value
95
+ const value = await computeFn();
96
+ // Cache it
97
+ cache.set(key, value, ttl);
98
+ return value;
99
+ }
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Result formatting utilities
3
+ *
4
+ * @author zerry
5
+ */
6
+ import type { DiagnosticIssue, Severity } from '../types.js';
7
+ /**
8
+ * Convert bytes to human-readable format
9
+ *
10
+ * 1024 -> "1 KiB"
11
+ * 1048576 -> "1 MiB"
12
+ */
13
+ export declare function formatBytes(bytes: number): string;
14
+ /**
15
+ * Convert millicores to human-readable format
16
+ *
17
+ * 1000 -> "1 core"
18
+ * 500 -> "0.5 cores"
19
+ */
20
+ export declare function formatCPU(millicores: number): string;
21
+ /**
22
+ * Return emoji based on severity
23
+ */
24
+ export declare function getSeverityEmoji(severity: Severity): string;
25
+ /**
26
+ * Convert health score to emoji
27
+ */
28
+ export declare function getHealthEmoji(score: number): string;
29
+ /**
30
+ * Convert timestamp to relative time
31
+ *
32
+ * "2 hours ago", "5 minutes ago" λ“±
33
+ */
34
+ export declare function timeAgo(timestamp: string): string;
35
+ /**
36
+ * Format issue list as markdown
37
+ */
38
+ export declare function formatIssues(issues: DiagnosticIssue[]): string;
39
+ /**
40
+ * Output diagnosis results as clean table
41
+ */
42
+ export declare function createTable(headers: string[], rows: string[][]): string;
43
+ /**
44
+ * Display percent as progress bar
45
+ *
46
+ * 85% -> "β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–‘β–‘ 85%"
47
+ */
48
+ export declare function progressBar(percent: number, width?: number): string;
@@ -0,0 +1,129 @@
1
+ /**
2
+ * Result formatting utilities
3
+ *
4
+ * @author zerry
5
+ */
6
+ /**
7
+ * Convert bytes to human-readable format
8
+ *
9
+ * 1024 -> "1 KiB"
10
+ * 1048576 -> "1 MiB"
11
+ */
12
+ export function formatBytes(bytes) {
13
+ if (bytes === 0)
14
+ return '0 B';
15
+ const k = 1024;
16
+ const sizes = ['B', 'KiB', 'MiB', 'GiB', 'TiB'];
17
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
18
+ return `${(bytes / Math.pow(k, i)).toFixed(2)} ${sizes[i]}`;
19
+ }
20
+ /**
21
+ * Convert millicores to human-readable format
22
+ *
23
+ * 1000 -> "1 core"
24
+ * 500 -> "0.5 cores"
25
+ */
26
+ export function formatCPU(millicores) {
27
+ const cores = millicores / 1000;
28
+ return `${cores.toFixed(2)} ${cores === 1 ? 'core' : 'cores'}`;
29
+ }
30
+ /**
31
+ * Return emoji based on severity
32
+ */
33
+ export function getSeverityEmoji(severity) {
34
+ const emojis = {
35
+ critical: 'πŸ”΄',
36
+ high: '🟠',
37
+ medium: '🟑',
38
+ low: 'πŸ”΅',
39
+ info: 'βšͺ',
40
+ };
41
+ return emojis[severity] || 'βšͺ';
42
+ }
43
+ /**
44
+ * Convert health score to emoji
45
+ */
46
+ export function getHealthEmoji(score) {
47
+ if (score >= 90)
48
+ return 'πŸ’š';
49
+ if (score >= 70)
50
+ return 'πŸ’›';
51
+ if (score >= 50)
52
+ return '🧑';
53
+ return '❀️';
54
+ }
55
+ /**
56
+ * Convert timestamp to relative time
57
+ *
58
+ * "2 hours ago", "5 minutes ago" λ“±
59
+ */
60
+ export function timeAgo(timestamp) {
61
+ const now = new Date();
62
+ const past = new Date(timestamp);
63
+ const diffMs = now.getTime() - past.getTime();
64
+ const diffSec = Math.floor(diffMs / 1000);
65
+ const diffMin = Math.floor(diffSec / 60);
66
+ const diffHour = Math.floor(diffMin / 60);
67
+ const diffDay = Math.floor(diffHour / 24);
68
+ if (diffDay > 0)
69
+ return `${diffDay}days ago`;
70
+ if (diffHour > 0)
71
+ return `${diffHour}hours ago`;
72
+ if (diffMin > 0)
73
+ return `${diffMin}minutes ago`;
74
+ return `${diffSec}seconds ago`;
75
+ }
76
+ /**
77
+ * Format issue list as markdown
78
+ */
79
+ export function formatIssues(issues) {
80
+ if (issues.length === 0) {
81
+ return 'βœ… No issues found!';
82
+ }
83
+ let result = `## πŸ” Detected Issues (${issues.length})\n\n`;
84
+ // Severityλ³„λ‘œ μ •λ ¬
85
+ const sorted = [...issues].sort((a, b) => {
86
+ const severityOrder = { critical: 0, high: 1, medium: 2, low: 3, info: 4 };
87
+ return severityOrder[a.severity] - severityOrder[b.severity];
88
+ });
89
+ for (const issue of sorted) {
90
+ result += `### ${getSeverityEmoji(issue.severity)} ${issue.type}\n\n`;
91
+ result += `**Severity**: ${issue.severity.toUpperCase()}\n\n`;
92
+ result += `**Problem**: ${issue.message}\n\n`;
93
+ result += `**Root Cause**: ${issue.rootCause}\n\n`;
94
+ result += `**Solution**:\n${issue.solution}\n\n`;
95
+ if (issue.relevantLogs && issue.relevantLogs.length > 0) {
96
+ result += `**Related Logs**:\n\`\`\`\n${issue.relevantLogs.join('\n')}\n\`\`\`\n\n`;
97
+ }
98
+ if (issue.resource) {
99
+ result += `**Resource**: ${issue.resource.kind}/${issue.resource.name} (ns: ${issue.resource.namespace})\n\n`;
100
+ }
101
+ result += '---\n\n';
102
+ }
103
+ return result;
104
+ }
105
+ /**
106
+ * Output diagnosis results as clean table
107
+ */
108
+ export function createTable(headers, rows) {
109
+ const colWidths = headers.map((h, i) => {
110
+ const maxRowWidth = Math.max(...rows.map(r => (r[i] || '').length));
111
+ return Math.max(h.length, maxRowWidth);
112
+ });
113
+ let table = '| ' + headers.map((h, i) => h.padEnd(colWidths[i])).join(' | ') + ' |\n';
114
+ table += '| ' + colWidths.map(w => '-'.repeat(w)).join(' | ') + ' |\n';
115
+ for (const row of rows) {
116
+ table += '| ' + row.map((cell, i) => (cell || '').padEnd(colWidths[i])).join(' | ') + ' |\n';
117
+ }
118
+ return table;
119
+ }
120
+ /**
121
+ * Display percent as progress bar
122
+ *
123
+ * 85% -> "β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–‘β–‘ 85%"
124
+ */
125
+ export function progressBar(percent, width = 10) {
126
+ const filled = Math.round((percent / 100) * width);
127
+ const empty = width - filled;
128
+ return 'β–ˆ'.repeat(filled) + 'β–‘'.repeat(empty) + ` ${percent.toFixed(1)}%`;
129
+ }
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Kubernetes client initialization
3
+ *
4
+ * @author zerry
5
+ */
6
+ import * as k8s from '@kubernetes/client-node';
7
+ /**
8
+ * Load K8s configuration
9
+ *
10
+ * Automatically finds and loads kubeconfig.
11
+ * Works in-cluster as well
12
+ */
13
+ export declare function loadK8sConfig(): k8s.KubeConfig;
14
+ /**
15
+ * Create K8s API clients
16
+ *
17
+ * Creates frequently used API clients at once
18
+ */
19
+ export declare function createK8sClients(kc: k8s.KubeConfig): {
20
+ core: k8s.CoreV1Api;
21
+ apps: k8s.AppsV1Api;
22
+ batch: k8s.BatchV1Api;
23
+ networking: k8s.NetworkingV1Api;
24
+ storage: k8s.StorageV1Api;
25
+ log: k8s.Log;
26
+ metrics: k8s.Metrics;
27
+ };
28
+ /**
29
+ * Validate namespace
30
+ *
31
+ * Check if namespace actually exists
32
+ */
33
+ export declare function validateNamespace(coreApi: k8s.CoreV1Api, namespace: string): Promise<boolean>;
34
+ /**
35
+ * Check if pod exists
36
+ */
37
+ export declare function podExists(coreApi: k8s.CoreV1Api, namespace: string, podName: string): Promise<boolean>;