Skip to content

Commit 062a8f3

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 ce62dec commit 062a8f3

21 files changed

Lines changed: 3944 additions & 3 deletions

README.md

Lines changed: 113 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ A powerful and flexible Kubernetes [Model Context Protocol (MCP)](https://blog.m
3535
- **List** Helm releases in all namespaces or in a specific namespace.
3636
- **Uninstall** a Helm release in the current or provided namespace.
3737
- **🔭 Observability**: Optional OpenTelemetry distributed tracing and metrics with custom sampling rates. Includes `/stats` endpoint for real-time statistics. See [OTEL.md](docs/OTEL.md).
38+
- **💻 Code Evaluation** _(opt-in with `--toolsets code`)_: Execute JavaScript scripts with direct access to Kubernetes clients for complex operations, data transformation, and aggregation in a single tool call.
3839

3940
Unlike other Kubernetes MCP server implementations, this **IS NOT** just a wrapper around `kubectl` or `helm` command-line tools.
4041
It is a **Go-based native implementation** that interacts directly with the Kubernetes API server.
@@ -431,6 +432,7 @@ The following sets of tools are available (toolsets marked with ✓ in the Defau
431432

432433
| Toolset | Description | Default |
433434
|----------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------|
435+
| code | Execute JavaScript code with access to Kubernetes clients for advanced operations and data transformation (opt-in, security-sensitive) | |
434436
| config | View and manage the current local Kubernetes configuration (kubeconfig) ||
435437
| core | Most common tools for Kubernetes management (Pods, Generic Resources, Events, etc.) ||
436438
| kcp | Manage kcp workspaces and multi-tenancy features | |
@@ -448,6 +450,116 @@ In case multi-cluster support is enabled (default) and you have access to multip
448450

449451
<details>
450452

453+
<summary>code</summary>
454+
455+
- **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.
456+
457+
458+
## JavaScript SDK
459+
460+
**Note:** Full ES5.1 syntax support, partial ES6. Synchronous execution only (no async/await or Promises).
461+
462+
### Globals
463+
- **k8s** - Kubernetes client (case-insensitive: coreV1, CoreV1, COREV1 all work)
464+
- **ctx** - Request context for cancellation
465+
- **namespace** - Default namespace
466+
467+
### k8s API Clients
468+
- k8s.coreV1() - pods, services, configMaps, secrets, namespaces, nodes, etc.
469+
- k8s.appsV1() - deployments, statefulSets, daemonSets, replicaSets
470+
- k8s.batchV1() - jobs, cronJobs
471+
- k8s.networkingV1() - ingresses, networkPolicies
472+
- k8s.rbacV1() - roles, roleBindings, clusterRoles, clusterRoleBindings
473+
- k8s.metricsV1beta1Client() - pod and node metrics (CPU/memory usage)
474+
- k8s.dynamicClient() - any resource by GVR
475+
- k8s.discoveryClient() - API discovery
476+
477+
### Examples
478+
479+
#### Combine multiple API calls with JavaScript
480+
```javascript
481+
// Get all deployments and their pod counts across namespaces
482+
const deps = k8s.appsV1().deployments("").list(ctx, {});
483+
const result = deps.items.flatMap(d => {
484+
const pods = k8s.coreV1().pods(d.metadata.namespace).list(ctx, {
485+
labelSelector: Object.entries(d.spec.selector.matchLabels || {})
486+
.map(([k,v]) => k+"="+v).join(",")
487+
});
488+
return [{
489+
deployment: d.metadata.name,
490+
namespace: d.metadata.namespace,
491+
replicas: d.status.readyReplicas + "/" + d.status.replicas,
492+
pods: pods.items.map(p => p.metadata.name)
493+
}];
494+
});
495+
JSON.stringify(result);
496+
```
497+
498+
#### Filter and aggregate
499+
```javascript
500+
const pods = k8s.coreV1().pods("").list(ctx, {});
501+
const unhealthy = pods.items.filter(p =>
502+
p.status.containerStatuses?.some(c => c.restartCount > 5)
503+
).map(p => ({
504+
name: p.metadata.name,
505+
ns: p.metadata.namespace,
506+
restarts: p.status.containerStatuses.reduce((s,c) => s + c.restartCount, 0)
507+
}));
508+
JSON.stringify(unhealthy);
509+
```
510+
511+
#### Create resources (using standard Kubernetes YAML/JSON structure)
512+
```javascript
513+
const pod = {
514+
apiVersion: "v1", kind: "Pod",
515+
metadata: { name: "my-pod", namespace: namespace },
516+
spec: { containers: [{ name: "nginx", image: "nginx:latest" }] }
517+
};
518+
k8s.coreV1().pods(namespace).create(ctx, pod, {}).metadata.name;
519+
```
520+
521+
#### API introspection
522+
```javascript
523+
// Discover available resources on coreV1
524+
const resources = []; for (const k in k8s.coreV1()) if (typeof k8s.coreV1()[k]==='function') resources.push(k);
525+
// resources: ["configMaps","namespaces","pods","secrets","services",...]
526+
527+
// Discover available operations on pods
528+
const ops = []; for (const k in k8s.coreV1().pods(namespace)) if (typeof k8s.coreV1().pods(namespace)[k]==='function') ops.push(k);
529+
// ops: ["create","delete","get","list","update","watch",...]
530+
```
531+
532+
#### Get pod metrics with resource quantities
533+
```javascript
534+
const metrics = k8s.metricsV1beta1Client();
535+
const podMetrics = metrics.podMetricses("").list(ctx, {});
536+
const result = podMetrics.items.map(function(pm) {
537+
return {
538+
name: pm.metadata.name,
539+
cpu: pm.containers[0].usage.cpu, // "100m"
540+
memory: pm.containers[0].usage.memory // "128Mi"
541+
};
542+
});
543+
JSON.stringify(result);
544+
```
545+
546+
#### Get pod logs
547+
```javascript
548+
const logBytes = k8s.coreV1().pods(namespace).getLogs("my-pod", {container: "main", tailLines: 100}).doRaw(ctx);
549+
var logs = ""; for (var i = 0; i < logBytes.length; i++) logs += String.fromCharCode(logBytes[i]);
550+
logs;
551+
```
552+
553+
### Return Value
554+
Last expression is returned. Use JSON.stringify() for objects.
555+
556+
- `script` (`string`) **(required)** - JavaScript code to execute. The last expression is returned as the result.
557+
- `timeout` (`integer`) - Execution timeout in milliseconds (default: 30000, max: 300000)
558+
559+
</details>
560+
561+
<details>
562+
451563
<summary>config</summary>
452564
453565
- **configuration_contexts_list** - List all available context names and associated server urls from the kubeconfig file
@@ -729,4 +841,4 @@ npx @modelcontextprotocol/inspector@latest $(pwd)/kubernetes-mcp-server
729841
730842
---
731843
732-
mcp-name: io.github.containers/kubernetes-mcp-server
844+
mcp-name: io.github.containers/kubernetes-mcp-server

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
@@ -66,6 +67,7 @@ require (
6667
github.com/containerd/platforms v0.2.1 // indirect
6768
github.com/cyphar/filepath-securejoin v0.6.1 // indirect
6869
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
70+
github.com/dlclark/regexp2 v1.11.4 // indirect
6971
github.com/emicklei/go-restful/v3 v3.12.2 // indirect
7072
github.com/evanphx/json-patch v5.9.11+incompatible // indirect
7173
github.com/evanphx/json-patch/v5 v5.9.11 // indirect
@@ -78,10 +80,12 @@ require (
7880
github.com/go-openapi/jsonpointer v0.21.1 // indirect
7981
github.com/go-openapi/jsonreference v0.21.0 // indirect
8082
github.com/go-openapi/swag v0.23.1 // indirect
83+
github.com/go-sourcemap/sourcemap v2.1.3+incompatible // indirect
8184
github.com/gobwas/glob v0.2.3 // indirect
8285
github.com/google/btree v1.1.3 // indirect
8386
github.com/google/gnostic-models v0.7.0 // indirect
8487
github.com/google/go-cmp v0.7.0 // indirect
88+
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 // indirect
8589
github.com/google/uuid v1.6.0 // indirect
8690
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // indirect
8791
github.com/gosuri/uitable v0.0.4 // indirect

go.sum

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,14 +69,16 @@ github.com/distribution/distribution/v3 v3.0.0 h1:q4R8wemdRQDClzoNNStftB2ZAfqOiN
6969
github.com/distribution/distribution/v3 v3.0.0/go.mod h1:tRNuFoZsUdyRVegq8xGNeds4KLjwLCRin/tTo6i1DhU=
7070
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
7171
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
72-
github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI=
73-
github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
72+
github.com/dlclark/regexp2 v1.11.4 h1:rPYF9/LECdNymJufQKmri9gV604RvvABwgOA8un7yAo=
73+
github.com/dlclark/regexp2 v1.11.4/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
7474
github.com/docker/docker-credential-helpers v0.8.2 h1:bX3YxiGzFP5sOXWc3bTPEXdEaZSeVMrFgOr3T+zrFAo=
7575
github.com/docker/docker-credential-helpers v0.8.2/go.mod h1:P3ci7E3lwkZg6XiHdRKft1KckHiO9a2rNtyFbZ/ry9M=
7676
github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c h1:+pKlWGMw7gf6bQ+oDZB4KHQFypsfjYlq/C4rfL7D3g8=
7777
github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA=
7878
github.com/docker/go-metrics v0.0.1 h1:AgB/0SvBxihN0X8OR4SjsblXkbMvalQ8cjmtKQ2rQV8=
7979
github.com/docker/go-metrics v0.0.1/go.mod h1:cG1hvH2utMXtqgqqYE9plW6lDxS3/5ayHzueweSI3Vw=
80+
github.com/dop251/goja v0.0.0-20260106131823-651366fbe6e3 h1:bVp3yUzvSAJzu9GqID+Z96P+eu5TKnIMJSV4QaZMauM=
81+
github.com/dop251/goja v0.0.0-20260106131823-651366fbe6e3/go.mod h1:MxLav0peU43GgvwVgNbLAj1s/bSGboKkhuULvq/7hx4=
8082
github.com/emicklei/go-restful/v3 v3.12.2 h1:DhwDP0vY3k8ZzE0RunuJy8GhNpPL6zqLkDf9B/a0/xU=
8183
github.com/emicklei/go-restful/v3 v3.12.2/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
8284
github.com/evanphx/json-patch v5.9.11+incompatible h1:ixHHqfcGvxhWkniF1tWxBHA0yb4Z+d1UQi45df52xW8=
@@ -116,6 +118,8 @@ github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF
116118
github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4=
117119
github.com/go-openapi/swag v0.23.1 h1:lpsStH0n2ittzTnbaSloVZLuB5+fvSY/+hnagBjSNZU=
118120
github.com/go-openapi/swag v0.23.1/go.mod h1:STZs8TbRvEQQKUA+JZNAm3EWlgaOBGpyFDqQnDHMef0=
121+
github.com/go-sourcemap/sourcemap v2.1.3+incompatible h1:W1iEw64niKVGogNgBN3ePyLFfuisuzeidWPMPWmECqU=
122+
github.com/go-sourcemap/sourcemap v2.1.3+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg=
119123
github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
120124
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
121125
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)