Skip to content

Commit f5cb093

Browse files
committed
feat(code): add evaluate_script tool for JavaScript script execution
Add a new MCP tool that enables LLMs to execute JavaScript scripts with direct access to Kubernetes clients. This allows complex operations that require multiple API calls, data transformation, filtering, or aggregation to be performed efficiently in a single tool call. Key features: - JavaScript execution via Goja (pure Go ES5.1+ engine) - Access to typed Kubernetes clients (CoreV1, AppsV1, BatchV1, etc.) - Transparent metadata flattening for standard K8s YAML/JSON structure - Case-insensitive method resolution (CoreV1/coreV1, Pods/pods, List/list) - SDK introspection support for API discovery - Configurable execution timeout (default 30s, max 5min) - Sandboxed environment with no file system or network access The tool is designed to be lenient and model-friendly: - Models can use familiar Kubernetes structure with metadata wrapper - Both uppercase and lowercase method names are supported - Automatic conversion handles the Go struct format requirements Signed-off-by: Marc Nuri <marc@marcnuri.com>
1 parent 3ed8048 commit f5cb093

23 files changed

Lines changed: 3945 additions & 4 deletions

README.md

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,7 @@ The following sets of tools are available (toolsets marked with ✓ in the Defau
262262

263263
| Toolset | Description | Default |
264264
|----------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------|
265+
| code | Execute JavaScript code with access to Kubernetes clients for advanced operations and data transformation (opt-in, security-sensitive) | |
265266
| config | View and manage the current local Kubernetes configuration (kubeconfig) ||
266267
| core | Most common tools for Kubernetes management (Pods, Generic Resources, Events, etc.) ||
267268
| helm | Tools for managing Helm charts and releases ||
@@ -279,6 +280,116 @@ In case multi-cluster support is enabled (default) and you have access to multip
279280

280281
<details>
281282

283+
<summary>code</summary>
284+
285+
- **evaluate_script** - Execute a JavaScript script with access to Kubernetes clients. Use this tool for complex operations that require multiple API calls, data transformation, filtering, or aggregation that would be inefficient with individual tool calls. The script runs in a sandboxed environment with access only to Kubernetes clients - no file system or network access.
286+
287+
288+
## JavaScript SDK
289+
290+
**Note:** Full ES5.1 syntax support, partial ES6. Synchronous execution only (no async/await or Promises).
291+
292+
### Globals
293+
- **k8s** - Kubernetes client (case-insensitive: coreV1, CoreV1, COREV1 all work)
294+
- **ctx** - Request context for cancellation
295+
- **namespace** - Default namespace
296+
297+
### k8s API Clients
298+
- k8s.coreV1() - pods, services, configMaps, secrets, namespaces, nodes, etc.
299+
- k8s.appsV1() - deployments, statefulSets, daemonSets, replicaSets
300+
- k8s.batchV1() - jobs, cronJobs
301+
- k8s.networkingV1() - ingresses, networkPolicies
302+
- k8s.rbacV1() - roles, roleBindings, clusterRoles, clusterRoleBindings
303+
- k8s.metricsV1beta1Client() - pod and node metrics (CPU/memory usage)
304+
- k8s.dynamicClient() - any resource by GVR
305+
- k8s.discoveryClient() - API discovery
306+
307+
### Examples
308+
309+
#### Combine multiple API calls with JavaScript
310+
```javascript
311+
// Get all deployments and their pod counts across namespaces
312+
const deps = k8s.appsV1().deployments("").list(ctx, {});
313+
const result = deps.items.flatMap(d => {
314+
const pods = k8s.coreV1().pods(d.metadata.namespace).list(ctx, {
315+
labelSelector: Object.entries(d.spec.selector.matchLabels || {})
316+
.map(([k,v]) => k+"="+v).join(",")
317+
});
318+
return [{
319+
deployment: d.metadata.name,
320+
namespace: d.metadata.namespace,
321+
replicas: d.status.readyReplicas + "/" + d.status.replicas,
322+
pods: pods.items.map(p => p.metadata.name)
323+
}];
324+
});
325+
JSON.stringify(result);
326+
```
327+
328+
#### Filter and aggregate
329+
```javascript
330+
const pods = k8s.coreV1().pods("").list(ctx, {});
331+
const unhealthy = pods.items.filter(p =>
332+
p.status.containerStatuses?.some(c => c.restartCount > 5)
333+
).map(p => ({
334+
name: p.metadata.name,
335+
ns: p.metadata.namespace,
336+
restarts: p.status.containerStatuses.reduce((s,c) => s + c.restartCount, 0)
337+
}));
338+
JSON.stringify(unhealthy);
339+
```
340+
341+
#### Create resources (using standard Kubernetes YAML/JSON structure)
342+
```javascript
343+
const pod = {
344+
apiVersion: "v1", kind: "Pod",
345+
metadata: { name: "my-pod", namespace: namespace },
346+
spec: { containers: [{ name: "nginx", image: "nginx:latest" }] }
347+
};
348+
k8s.coreV1().pods(namespace).create(ctx, pod, {}).metadata.name;
349+
```
350+
351+
#### API introspection
352+
```javascript
353+
// Discover available resources on coreV1
354+
const resources = []; for (const k in k8s.coreV1()) if (typeof k8s.coreV1()[k]==='function') resources.push(k);
355+
// resources: ["configMaps","namespaces","pods","secrets","services",...]
356+
357+
// Discover available operations on pods
358+
const ops = []; for (const k in k8s.coreV1().pods(namespace)) if (typeof k8s.coreV1().pods(namespace)[k]==='function') ops.push(k);
359+
// ops: ["create","delete","get","list","update","watch",...]
360+
```
361+
362+
#### Get pod metrics with resource quantities
363+
```javascript
364+
const metrics = k8s.metricsV1beta1Client();
365+
const podMetrics = metrics.podMetricses("").list(ctx, {});
366+
const result = podMetrics.items.map(function(pm) {
367+
return {
368+
name: pm.metadata.name,
369+
cpu: pm.containers[0].usage.cpu, // "100m"
370+
memory: pm.containers[0].usage.memory // "128Mi"
371+
};
372+
});
373+
JSON.stringify(result);
374+
```
375+
376+
#### Get pod logs
377+
```javascript
378+
const logBytes = k8s.coreV1().pods(namespace).getLogs("my-pod", {container: "main", tailLines: 100}).doRaw(ctx);
379+
var logs = ""; for (var i = 0; i < logBytes.length; i++) logs += String.fromCharCode(logBytes[i]);
380+
logs;
381+
```
382+
383+
### Return Value
384+
Last expression is returned. Use JSON.stringify() for objects.
385+
386+
- `script` (`string`) **(required)** - JavaScript code to execute. The last expression is returned as the result.
387+
- `timeout` (`integer`) - Execution timeout in milliseconds (default: 30000, max: 300000)
388+
389+
</details>
390+
391+
<details>
392+
282393
<summary>config</summary>
283394
284395
- **configuration_contexts_list** - List all available context names and associated server urls from the kubeconfig file

docs/configuration.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,7 @@ Toolsets group related tools together. Enable only the toolsets you need to redu
254254

255255
| Toolset | Description | Default |
256256
|----------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------|
257+
| code | Execute JavaScript code with access to Kubernetes clients for advanced operations and data transformation (opt-in, security-sensitive) | |
257258
| config | View and manage the current local Kubernetes configuration (kubeconfig) | ✓ |
258259
| core | Most common tools for Kubernetes management (Pods, Generic Resources, Events, etc.) | ✓ |
259260
| helm | Tools for managing Helm charts and releases | ✓ |

go.mod

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ go 1.25.6
55
require (
66
github.com/BurntSushi/toml v1.6.0
77
github.com/coreos/go-oidc/v3 v3.17.0
8+
github.com/dop251/goja v0.0.0-20260106131823-651366fbe6e3
89
github.com/fsnotify/fsnotify v1.9.0
910
github.com/go-jose/go-jose/v4 v4.1.3
1011
github.com/go-logr/logr v1.4.3
@@ -63,6 +64,7 @@ require (
6364
github.com/containerd/platforms v0.2.1 // indirect
6465
github.com/cyphar/filepath-securejoin v0.6.1 // indirect
6566
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
67+
github.com/dlclark/regexp2 v1.11.4 // indirect
6668
github.com/emicklei/go-restful/v3 v3.12.2 // indirect
6769
github.com/evanphx/json-patch v5.9.11+incompatible // indirect
6870
github.com/evanphx/json-patch/v5 v5.9.11 // indirect
@@ -75,10 +77,12 @@ require (
7577
github.com/go-openapi/jsonpointer v0.21.1 // indirect
7678
github.com/go-openapi/jsonreference v0.21.0 // indirect
7779
github.com/go-openapi/swag v0.23.1 // indirect
80+
github.com/go-sourcemap/sourcemap v2.1.3+incompatible // indirect
7881
github.com/gobwas/glob v0.2.3 // indirect
7982
github.com/google/btree v1.1.3 // indirect
8083
github.com/google/gnostic-models v0.7.0 // indirect
8184
github.com/google/go-cmp v0.7.0 // indirect
85+
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 // indirect
8286
github.com/google/uuid v1.6.0 // indirect
8387
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // indirect
8488
github.com/gosuri/uitable v0.0.4 // indirect

go.sum

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,14 +65,16 @@ github.com/distribution/distribution/v3 v3.0.0 h1:q4R8wemdRQDClzoNNStftB2ZAfqOiN
6565
github.com/distribution/distribution/v3 v3.0.0/go.mod h1:tRNuFoZsUdyRVegq8xGNeds4KLjwLCRin/tTo6i1DhU=
6666
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
6767
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
68-
github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI=
69-
github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
68+
github.com/dlclark/regexp2 v1.11.4 h1:rPYF9/LECdNymJufQKmri9gV604RvvABwgOA8un7yAo=
69+
github.com/dlclark/regexp2 v1.11.4/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
7070
github.com/docker/docker-credential-helpers v0.8.2 h1:bX3YxiGzFP5sOXWc3bTPEXdEaZSeVMrFgOr3T+zrFAo=
7171
github.com/docker/docker-credential-helpers v0.8.2/go.mod h1:P3ci7E3lwkZg6XiHdRKft1KckHiO9a2rNtyFbZ/ry9M=
7272
github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c h1:+pKlWGMw7gf6bQ+oDZB4KHQFypsfjYlq/C4rfL7D3g8=
7373
github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA=
7474
github.com/docker/go-metrics v0.0.1 h1:AgB/0SvBxihN0X8OR4SjsblXkbMvalQ8cjmtKQ2rQV8=
7575
github.com/docker/go-metrics v0.0.1/go.mod h1:cG1hvH2utMXtqgqqYE9plW6lDxS3/5ayHzueweSI3Vw=
76+
github.com/dop251/goja v0.0.0-20260106131823-651366fbe6e3 h1:bVp3yUzvSAJzu9GqID+Z96P+eu5TKnIMJSV4QaZMauM=
77+
github.com/dop251/goja v0.0.0-20260106131823-651366fbe6e3/go.mod h1:MxLav0peU43GgvwVgNbLAj1s/bSGboKkhuULvq/7hx4=
7678
github.com/emicklei/go-restful/v3 v3.12.2 h1:DhwDP0vY3k8ZzE0RunuJy8GhNpPL6zqLkDf9B/a0/xU=
7779
github.com/emicklei/go-restful/v3 v3.12.2/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
7880
github.com/evanphx/json-patch v5.9.11+incompatible h1:ixHHqfcGvxhWkniF1tWxBHA0yb4Z+d1UQi45df52xW8=
@@ -112,6 +114,8 @@ github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF
112114
github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4=
113115
github.com/go-openapi/swag v0.23.1 h1:lpsStH0n2ittzTnbaSloVZLuB5+fvSY/+hnagBjSNZU=
114116
github.com/go-openapi/swag v0.23.1/go.mod h1:STZs8TbRvEQQKUA+JZNAm3EWlgaOBGpyFDqQnDHMef0=
117+
github.com/go-sourcemap/sourcemap v2.1.3+incompatible h1:W1iEw64niKVGogNgBN3ePyLFfuisuzeidWPMPWmECqU=
118+
github.com/go-sourcemap/sourcemap v2.1.3+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg=
115119
github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
116120
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
117121
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=

internal/tools/update-readme/main.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
"github.com/containers/kubernetes-mcp-server/pkg/config"
1414
"github.com/containers/kubernetes-mcp-server/pkg/toolsets"
1515

16+
_ "github.com/containers/kubernetes-mcp-server/pkg/toolsets/code"
1617
_ "github.com/containers/kubernetes-mcp-server/pkg/toolsets/config"
1718
_ "github.com/containers/kubernetes-mcp-server/pkg/toolsets/core"
1819
_ "github.com/containers/kubernetes-mcp-server/pkg/toolsets/helm"

0 commit comments

Comments
 (0)