mcp-server-kubernetes 4.0.1 → 4.0.3
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 +1 -0
- package/dist/config/server-config.d.ts +1 -1
- package/dist/config/server-config.js +1 -1
- package/dist/index.d.ts +21 -2
- package/dist/security/transport.d.ts +1 -0
- package/dist/security/transport.js +17 -0
- package/dist/tools/kubectl-create.d.ts +25 -2
- package/dist/tools/kubectl-create.js +87 -27
- package/dist/tools/kubectl-get.js +2 -2
- package/dist/tools/kubectl-logs.js +5 -5
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
[](https://github.com/Flux159/mcp-server-kubernetes/pulls)
|
|
11
11
|
[](https://github.com/Flux159/mcp-server-kubernetes/commits/main)
|
|
12
12
|
[](https://archestra.ai/mcp-catalog/flux159__mcp-server-kubernetes)
|
|
13
|
+
[](https://hvtracker.net/agents/kubernetes-mcp-server/)
|
|
13
14
|
[](https://deepwiki.com/Flux159/mcp-server-kubernetes)
|
|
14
15
|
|
|
15
16
|
<p align="center">
|
package/dist/index.d.ts
CHANGED
|
@@ -616,7 +616,7 @@ declare const allTools: ({
|
|
|
616
616
|
};
|
|
617
617
|
readonly filename: {
|
|
618
618
|
readonly type: "string";
|
|
619
|
-
readonly description: "Path to a YAML file to create resources from";
|
|
619
|
+
readonly description: "Path to a YAML file to create resources from. The path is read on the machine running the MCP server, so it is rejected when the server runs over a remote (SSE/Streamable HTTP) transport; use 'manifest' to pass the file's contents instead.";
|
|
620
620
|
};
|
|
621
621
|
readonly resourceType: {
|
|
622
622
|
readonly type: "string";
|
|
@@ -643,7 +643,26 @@ declare const allTools: ({
|
|
|
643
643
|
readonly items: {
|
|
644
644
|
readonly type: "string";
|
|
645
645
|
};
|
|
646
|
-
readonly description: "Path to file for creating configmap (e.g. [\"key1=/path/to/file1\", \"key2=/path/to/file2\"])";
|
|
646
|
+
readonly description: "Path to file for creating configmap/secret (e.g. [\"key1=/path/to/file1\", \"key2=/path/to/file2\"]). The path is read on the machine running the MCP server, so it is rejected when the server runs over a remote (SSE/Streamable HTTP) transport; use \"fromFileContent\" to pass file contents directly instead.";
|
|
647
|
+
};
|
|
648
|
+
readonly fromFileContent: {
|
|
649
|
+
readonly type: "array";
|
|
650
|
+
readonly items: {
|
|
651
|
+
readonly type: "object";
|
|
652
|
+
readonly properties: {
|
|
653
|
+
readonly key: {
|
|
654
|
+
readonly type: "string";
|
|
655
|
+
readonly description: "Key to store the content under in the configmap/secret";
|
|
656
|
+
};
|
|
657
|
+
readonly content: {
|
|
658
|
+
readonly type: "string";
|
|
659
|
+
readonly description: "The file content to store";
|
|
660
|
+
};
|
|
661
|
+
};
|
|
662
|
+
readonly required: readonly ["key", "content"];
|
|
663
|
+
readonly additionalProperties: false;
|
|
664
|
+
};
|
|
665
|
+
readonly description: "Inline file contents for creating a configmap/secret, provided by the client instead of a server-side path (e.g. [{\"key\": \"app.conf\", \"content\": \"...\"}]). Safe on all transports; use this instead of \"fromFile\" on remote (SSE/Streamable HTTP) servers.";
|
|
647
666
|
};
|
|
648
667
|
readonly secretType: {
|
|
649
668
|
readonly type: "string";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function isRemoteTransport(): boolean;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// Transport-mode detection for security decisions.
|
|
2
|
+
//
|
|
3
|
+
// The server selects its transport at startup based on these env vars (see
|
|
4
|
+
// src/index.ts): if either is set the server listens over HTTP (SSE or
|
|
5
|
+
// Streamable HTTP) and serves remote clients; otherwise it uses stdio and is
|
|
6
|
+
// driven by a local process that already runs as the operator.
|
|
7
|
+
//
|
|
8
|
+
// Some tool inputs are only safe under the stdio trust model. A server-side
|
|
9
|
+
// filesystem path (e.g. `--from-file` / `-f`) is read on the machine running
|
|
10
|
+
// the server: over stdio that machine is the operator's own, but over HTTP it
|
|
11
|
+
// is the server host, so any client that can reach the endpoint could read
|
|
12
|
+
// arbitrary files (kubeconfig, service-account token, /proc/self/environ).
|
|
13
|
+
// Guards use this helper to reject path-based reads on remote transports.
|
|
14
|
+
export function isRemoteTransport() {
|
|
15
|
+
return Boolean(process.env.ENABLE_UNSAFE_SSE_TRANSPORT ||
|
|
16
|
+
process.env.ENABLE_UNSAFE_STREAMABLE_HTTP_TRANSPORT);
|
|
17
|
+
}
|
|
@@ -27,7 +27,7 @@ export declare const kubectlCreateSchema: {
|
|
|
27
27
|
};
|
|
28
28
|
readonly filename: {
|
|
29
29
|
readonly type: "string";
|
|
30
|
-
readonly description: "Path to a YAML file to create resources from";
|
|
30
|
+
readonly description: "Path to a YAML file to create resources from. The path is read on the machine running the MCP server, so it is rejected when the server runs over a remote (SSE/Streamable HTTP) transport; use 'manifest' to pass the file's contents instead.";
|
|
31
31
|
};
|
|
32
32
|
readonly resourceType: {
|
|
33
33
|
readonly type: "string";
|
|
@@ -54,7 +54,26 @@ export declare const kubectlCreateSchema: {
|
|
|
54
54
|
readonly items: {
|
|
55
55
|
readonly type: "string";
|
|
56
56
|
};
|
|
57
|
-
readonly description: "Path to file for creating configmap (e.g. [\"key1=/path/to/file1\", \"key2=/path/to/file2\"])";
|
|
57
|
+
readonly description: "Path to file for creating configmap/secret (e.g. [\"key1=/path/to/file1\", \"key2=/path/to/file2\"]). The path is read on the machine running the MCP server, so it is rejected when the server runs over a remote (SSE/Streamable HTTP) transport; use \"fromFileContent\" to pass file contents directly instead.";
|
|
58
|
+
};
|
|
59
|
+
readonly fromFileContent: {
|
|
60
|
+
readonly type: "array";
|
|
61
|
+
readonly items: {
|
|
62
|
+
readonly type: "object";
|
|
63
|
+
readonly properties: {
|
|
64
|
+
readonly key: {
|
|
65
|
+
readonly type: "string";
|
|
66
|
+
readonly description: "Key to store the content under in the configmap/secret";
|
|
67
|
+
};
|
|
68
|
+
readonly content: {
|
|
69
|
+
readonly type: "string";
|
|
70
|
+
readonly description: "The file content to store";
|
|
71
|
+
};
|
|
72
|
+
};
|
|
73
|
+
readonly required: readonly ["key", "content"];
|
|
74
|
+
readonly additionalProperties: false;
|
|
75
|
+
};
|
|
76
|
+
readonly description: "Inline file contents for creating a configmap/secret, provided by the client instead of a server-side path (e.g. [{\"key\": \"app.conf\", \"content\": \"...\"}]). Safe on all transports; use this instead of \"fromFile\" on remote (SSE/Streamable HTTP) servers.";
|
|
58
77
|
};
|
|
59
78
|
readonly secretType: {
|
|
60
79
|
readonly type: "string";
|
|
@@ -136,6 +155,10 @@ export declare function kubectlCreate(k8sManager: KubernetesManager, input: {
|
|
|
136
155
|
namespace?: string;
|
|
137
156
|
fromLiteral?: string[];
|
|
138
157
|
fromFile?: string[];
|
|
158
|
+
fromFileContent?: {
|
|
159
|
+
key: string;
|
|
160
|
+
content: string;
|
|
161
|
+
}[];
|
|
139
162
|
secretType?: "generic" | "docker-registry" | "tls";
|
|
140
163
|
serviceType?: "clusterip" | "nodeport" | "loadbalancer" | "externalname";
|
|
141
164
|
tcpPort?: string[];
|
|
@@ -5,6 +5,7 @@ import * as path from "path";
|
|
|
5
5
|
import * as os from "os";
|
|
6
6
|
import { getSpawnMaxBuffer } from "../config/max-buffer.js";
|
|
7
7
|
import { contextParameter, namespaceParameter, dryRunParameter } from "../models/common-parameters.js";
|
|
8
|
+
import { isRemoteTransport } from "../security/transport.js";
|
|
8
9
|
export const kubectlCreateSchema = {
|
|
9
10
|
name: "kubectl_create",
|
|
10
11
|
description: "Create Kubernetes resources using various methods (from file or using subcommands)",
|
|
@@ -42,7 +43,7 @@ export const kubectlCreateSchema = {
|
|
|
42
43
|
},
|
|
43
44
|
filename: {
|
|
44
45
|
type: "string",
|
|
45
|
-
description: "Path to a YAML file to create resources from",
|
|
46
|
+
description: "Path to a YAML file to create resources from. The path is read on the machine running the MCP server, so it is rejected when the server runs over a remote (SSE/Streamable HTTP) transport; use 'manifest' to pass the file's contents instead.",
|
|
46
47
|
},
|
|
47
48
|
// Resource type to create (determines which subcommand to use)
|
|
48
49
|
resourceType: {
|
|
@@ -64,7 +65,26 @@ export const kubectlCreateSchema = {
|
|
|
64
65
|
fromFile: {
|
|
65
66
|
type: "array",
|
|
66
67
|
items: { type: "string" },
|
|
67
|
-
description: 'Path to file for creating configmap (e.g. ["key1=/path/to/file1", "key2=/path/to/file2"])',
|
|
68
|
+
description: 'Path to file for creating configmap/secret (e.g. ["key1=/path/to/file1", "key2=/path/to/file2"]). The path is read on the machine running the MCP server, so it is rejected when the server runs over a remote (SSE/Streamable HTTP) transport; use "fromFileContent" to pass file contents directly instead.',
|
|
69
|
+
},
|
|
70
|
+
fromFileContent: {
|
|
71
|
+
type: "array",
|
|
72
|
+
items: {
|
|
73
|
+
type: "object",
|
|
74
|
+
properties: {
|
|
75
|
+
key: {
|
|
76
|
+
type: "string",
|
|
77
|
+
description: "Key to store the content under in the configmap/secret",
|
|
78
|
+
},
|
|
79
|
+
content: {
|
|
80
|
+
type: "string",
|
|
81
|
+
description: "The file content to store",
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
required: ["key", "content"],
|
|
85
|
+
additionalProperties: false,
|
|
86
|
+
},
|
|
87
|
+
description: 'Inline file contents for creating a configmap/secret, provided by the client instead of a server-side path (e.g. [{"key": "app.conf", "content": "..."}]). Safe on all transports; use this instead of "fromFile" on remote (SSE/Streamable HTTP) servers.',
|
|
68
88
|
},
|
|
69
89
|
// Namespace specific parameters
|
|
70
90
|
// No special parameters for namespace, just name is needed
|
|
@@ -143,6 +163,21 @@ export async function kubectlCreate(k8sManager, input) {
|
|
|
143
163
|
input.resourceType !== "namespace") {
|
|
144
164
|
throw new McpError(ErrorCode.InvalidRequest, `Name is required when creating a ${input.resourceType}`);
|
|
145
165
|
}
|
|
166
|
+
// Reject server-side filesystem reads on remote transports. Over SSE /
|
|
167
|
+
// Streamable HTTP the path resolves on the MCP server host, not the
|
|
168
|
+
// client, so `filename` (-f) and `fromFile` (--from-file) would let any
|
|
169
|
+
// client that can reach the endpoint read arbitrary server files
|
|
170
|
+
// (kubeconfig, service-account token, /proc/self/environ, etc.). See
|
|
171
|
+
// GHSA-m67f-jxm9-cvx8. Clients on these transports must pass content
|
|
172
|
+
// inline via `manifest` or `fromFileContent` instead.
|
|
173
|
+
if (isRemoteTransport()) {
|
|
174
|
+
if (input.filename) {
|
|
175
|
+
throw new McpError(ErrorCode.InvalidRequest, "The 'filename' parameter reads a file from the MCP server's filesystem and is disabled on remote (SSE/Streamable HTTP) transports. Pass the file contents via 'manifest' instead.");
|
|
176
|
+
}
|
|
177
|
+
if (input.fromFile && input.fromFile.length > 0) {
|
|
178
|
+
throw new McpError(ErrorCode.InvalidRequest, "The 'fromFile' parameter reads files from the MCP server's filesystem and is disabled on remote (SSE/Streamable HTTP) transports. Pass the file contents via 'fromFileContent' instead.");
|
|
179
|
+
}
|
|
180
|
+
}
|
|
146
181
|
// Set up common parameters
|
|
147
182
|
const namespace = input.namespace || "default";
|
|
148
183
|
const dryRun = input.dryRun || false;
|
|
@@ -151,15 +186,33 @@ export async function kubectlCreate(k8sManager, input) {
|
|
|
151
186
|
const context = input.context || "";
|
|
152
187
|
const command = "kubectl";
|
|
153
188
|
const args = ["create"];
|
|
154
|
-
|
|
189
|
+
const tempFiles = [];
|
|
190
|
+
// Write client-provided content to a private temp file and return its
|
|
191
|
+
// path. The file is tracked for cleanup after kubectl runs.
|
|
192
|
+
const writeTempFile = (contents, label, ext = "") => {
|
|
193
|
+
const tmpDir = os.tmpdir();
|
|
194
|
+
const tempFile = path.join(tmpDir, `create-${label}-${Date.now()}-${tempFiles.length}${ext}`);
|
|
195
|
+
fs.writeFileSync(tempFile, contents, { mode: 0o600 });
|
|
196
|
+
tempFiles.push(tempFile);
|
|
197
|
+
return tempFile;
|
|
198
|
+
};
|
|
199
|
+
// Emit `--from-file=<key>=<tempfile>` args for inline content supplied by
|
|
200
|
+
// the client. Each entry's content is written to a server-side temp file
|
|
201
|
+
// (never a client-controlled path), so this is safe on all transports.
|
|
202
|
+
const pushFromFileContent = (entries) => {
|
|
203
|
+
entries.forEach((entry) => {
|
|
204
|
+
if (!entry.key) {
|
|
205
|
+
throw new McpError(ErrorCode.InvalidRequest, "Each fromFileContent entry requires a non-empty 'key'");
|
|
206
|
+
}
|
|
207
|
+
const tempFile = writeTempFile(entry.content ?? "", "fromfile");
|
|
208
|
+
args.push(`--from-file=${entry.key}=${tempFile}`);
|
|
209
|
+
});
|
|
210
|
+
};
|
|
155
211
|
// Process manifest content if provided (file-based creation)
|
|
156
212
|
if (input.manifest || input.filename) {
|
|
157
213
|
if (input.manifest) {
|
|
158
214
|
// Create temporary file for the manifest
|
|
159
|
-
|
|
160
|
-
tempFile = path.join(tmpDir, `create-manifest-${Date.now()}.yaml`);
|
|
161
|
-
fs.writeFileSync(tempFile, input.manifest);
|
|
162
|
-
args.push("-f", tempFile);
|
|
215
|
+
args.push("-f", writeTempFile(input.manifest, "manifest", ".yaml"));
|
|
163
216
|
}
|
|
164
217
|
else if (input.filename) {
|
|
165
218
|
args.push("-f", input.filename);
|
|
@@ -179,12 +232,17 @@ export async function kubectlCreate(k8sManager, input) {
|
|
|
179
232
|
args.push(`--from-literal=${literal}`);
|
|
180
233
|
});
|
|
181
234
|
}
|
|
182
|
-
// Add --from-file arguments
|
|
235
|
+
// Add --from-file arguments (server-side paths; blocked on remote
|
|
236
|
+
// transports by the guard above)
|
|
183
237
|
if (input.fromFile && input.fromFile.length > 0) {
|
|
184
238
|
input.fromFile.forEach((file) => {
|
|
185
239
|
args.push(`--from-file=${file}`);
|
|
186
240
|
});
|
|
187
241
|
}
|
|
242
|
+
// Add inline file contents (safe on all transports)
|
|
243
|
+
if (input.fromFileContent && input.fromFileContent.length > 0) {
|
|
244
|
+
pushFromFileContent(input.fromFileContent);
|
|
245
|
+
}
|
|
188
246
|
break;
|
|
189
247
|
case "secret":
|
|
190
248
|
if (!input.secretType) {
|
|
@@ -197,12 +255,17 @@ export async function kubectlCreate(k8sManager, input) {
|
|
|
197
255
|
args.push(`--from-literal=${literal}`);
|
|
198
256
|
});
|
|
199
257
|
}
|
|
200
|
-
// Add --from-file arguments
|
|
258
|
+
// Add --from-file arguments (server-side paths; blocked on remote
|
|
259
|
+
// transports by the guard above)
|
|
201
260
|
if (input.fromFile && input.fromFile.length > 0) {
|
|
202
261
|
input.fromFile.forEach((file) => {
|
|
203
262
|
args.push(`--from-file=${file}`);
|
|
204
263
|
});
|
|
205
264
|
}
|
|
265
|
+
// Add inline file contents (safe on all transports)
|
|
266
|
+
if (input.fromFileContent && input.fromFileContent.length > 0) {
|
|
267
|
+
pushFromFileContent(input.fromFileContent);
|
|
268
|
+
}
|
|
206
269
|
break;
|
|
207
270
|
case "service":
|
|
208
271
|
if (!input.serviceType) {
|
|
@@ -292,6 +355,17 @@ export async function kubectlCreate(k8sManager, input) {
|
|
|
292
355
|
if (context) {
|
|
293
356
|
args.push("--context", context);
|
|
294
357
|
}
|
|
358
|
+
// Remove any temp files created for inline content.
|
|
359
|
+
const cleanupTempFiles = () => {
|
|
360
|
+
tempFiles.forEach((tempFile) => {
|
|
361
|
+
try {
|
|
362
|
+
fs.unlinkSync(tempFile);
|
|
363
|
+
}
|
|
364
|
+
catch (err) {
|
|
365
|
+
console.warn(`Failed to delete temporary file ${tempFile}: ${err}`);
|
|
366
|
+
}
|
|
367
|
+
});
|
|
368
|
+
};
|
|
295
369
|
// Execute the command
|
|
296
370
|
try {
|
|
297
371
|
const result = execFileSyncSafe(command, args, {
|
|
@@ -299,15 +373,8 @@ export async function kubectlCreate(k8sManager, input) {
|
|
|
299
373
|
maxBuffer: getSpawnMaxBuffer(),
|
|
300
374
|
env: { ...process.env, KUBECONFIG: process.env.KUBECONFIG },
|
|
301
375
|
});
|
|
302
|
-
// Clean up temp
|
|
303
|
-
|
|
304
|
-
try {
|
|
305
|
-
fs.unlinkSync(tempFile);
|
|
306
|
-
}
|
|
307
|
-
catch (err) {
|
|
308
|
-
console.warn(`Failed to delete temporary file ${tempFile}: ${err}`);
|
|
309
|
-
}
|
|
310
|
-
}
|
|
376
|
+
// Clean up temp files if created
|
|
377
|
+
cleanupTempFiles();
|
|
311
378
|
return {
|
|
312
379
|
content: [
|
|
313
380
|
{
|
|
@@ -318,15 +385,8 @@ export async function kubectlCreate(k8sManager, input) {
|
|
|
318
385
|
};
|
|
319
386
|
}
|
|
320
387
|
catch (error) {
|
|
321
|
-
// Clean up temp
|
|
322
|
-
|
|
323
|
-
try {
|
|
324
|
-
fs.unlinkSync(tempFile);
|
|
325
|
-
}
|
|
326
|
-
catch (err) {
|
|
327
|
-
console.warn(`Failed to delete temporary file ${tempFile}: ${err}`);
|
|
328
|
-
}
|
|
329
|
-
}
|
|
388
|
+
// Clean up temp files if created, even if command failed
|
|
389
|
+
cleanupTempFiles();
|
|
330
390
|
throw new McpError(ErrorCode.InternalError, `Failed to create resource: ${error.message}`);
|
|
331
391
|
}
|
|
332
392
|
}
|
|
@@ -113,10 +113,10 @@ export async function kubectlGet(k8sManager, input) {
|
|
|
113
113
|
}
|
|
114
114
|
else if (output === "custom") {
|
|
115
115
|
if (resourceType === "events") {
|
|
116
|
-
args.push("-o", "
|
|
116
|
+
args.push("-o", "custom-columns=LASTSEEN:.lastTimestamp,TYPE:.type,REASON:.reason,OBJECT:.involvedObject.name,MESSAGE:.message");
|
|
117
117
|
}
|
|
118
118
|
else {
|
|
119
|
-
args.push("-o", "
|
|
119
|
+
args.push("-o", "custom-columns=NAME:.metadata.name,NAMESPACE:.metadata.namespace,STATUS:.status.phase,AGE:.metadata.creationTimestamp");
|
|
120
120
|
}
|
|
121
121
|
}
|
|
122
122
|
// Execute the command
|
|
@@ -63,7 +63,7 @@ export const kubectlLogsSchema = {
|
|
|
63
63
|
};
|
|
64
64
|
export async function kubectlLogs(k8sManager, input) {
|
|
65
65
|
try {
|
|
66
|
-
const resourceType = input.resourceType.toLowerCase();
|
|
66
|
+
const resourceType = (input.resourceType ?? "pod").toLowerCase();
|
|
67
67
|
const name = input.name;
|
|
68
68
|
const namespace = input.namespace || "default";
|
|
69
69
|
const context = input.context || "";
|
|
@@ -108,7 +108,7 @@ export async function kubectlLogs(k8sManager, input) {
|
|
|
108
108
|
"deployment",
|
|
109
109
|
name,
|
|
110
110
|
"-o",
|
|
111
|
-
"jsonpath=
|
|
111
|
+
"jsonpath={.spec.selector.matchLabels}",
|
|
112
112
|
];
|
|
113
113
|
}
|
|
114
114
|
else if (resourceType === "job") {
|
|
@@ -124,7 +124,7 @@ export async function kubectlLogs(k8sManager, input) {
|
|
|
124
124
|
"jobs",
|
|
125
125
|
"--selector=job-name=" + name,
|
|
126
126
|
"-o",
|
|
127
|
-
"jsonpath=
|
|
127
|
+
"jsonpath={.items[*].metadata.name}",
|
|
128
128
|
];
|
|
129
129
|
try {
|
|
130
130
|
const jobs = execFileSyncSafe(command, jobsArgs, {
|
|
@@ -182,7 +182,7 @@ export async function kubectlLogs(k8sManager, input) {
|
|
|
182
182
|
maxBuffer: getSpawnMaxBuffer(),
|
|
183
183
|
env: { ...process.env, KUBECONFIG: process.env.KUBECONFIG },
|
|
184
184
|
}).trim();
|
|
185
|
-
const selector = JSON.parse(selectorJson
|
|
185
|
+
const selector = JSON.parse(selectorJson);
|
|
186
186
|
// Convert to label selector format
|
|
187
187
|
const labelSelector = Object.entries(selector)
|
|
188
188
|
.map(([key, value]) => `${key}=${value}`)
|
|
@@ -258,7 +258,7 @@ async function getLabelSelectorLogs(labelSelector, namespace, input) {
|
|
|
258
258
|
"pods",
|
|
259
259
|
`--selector=${labelSelector}`,
|
|
260
260
|
"-o",
|
|
261
|
-
"jsonpath=
|
|
261
|
+
"jsonpath={.items[*].metadata.name}",
|
|
262
262
|
];
|
|
263
263
|
const pods = execFileSyncSafe(command, podsArgs, {
|
|
264
264
|
encoding: "utf8",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mcp-server-kubernetes",
|
|
3
|
-
"version": "4.0.
|
|
3
|
+
"version": "4.0.3",
|
|
4
4
|
"description": "MCP server for interacting with Kubernetes clusters via kubectl",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -57,6 +57,6 @@
|
|
|
57
57
|
"@types/node": "22.9.3",
|
|
58
58
|
"shx": "0.3.4",
|
|
59
59
|
"typescript": "5.6.2",
|
|
60
|
-
"vitest": "2.
|
|
60
|
+
"vitest": "3.2.6"
|
|
61
61
|
}
|
|
62
62
|
}
|