Repository: Flux159/mcp-server-kubernetes
Pull request #144: 139 - Support running as a pod in cluster
Author: login: ajit-pendse
Created: 2025-05-21T05:04:10Z | Merged: 2025-05-22T04:42:31Z

Resolves #139.

Linked issue #139: Support In-cluster configuration
It would be great if this can work with in-cluster configuration. This means, it will not have a `~/.kube/config` file but will have direct access to API server as the pod will be running inside the cluster to be managed.

Currently (As of 9 May 2025) this does not work and fails by generating API server URL like `https://undefined:undefined/api/v1/...`

Diff:
diff --git a/src/utils/kubernetes-manager.ts b/src/utils/kubernetes-manager.ts
index d14442c..bbc531d 100644
--- a/src/utils/kubernetes-manager.ts
+++ b/src/utils/kubernetes-manager.ts
@@ -1,4 +1,5 @@
 import * as k8s from "@kubernetes/client-node";
+import * as fs from "fs";
 import { ResourceTracker, PortForwardTracker, WatchTracker } from "../types.js";
 
 export class KubernetesManager {
@@ -12,27 +13,46 @@ export class KubernetesManager {
 
   constructor() {
     this.kc = new k8s.KubeConfig();
-    this.kc.loadFromDefault();
+    if (this.isRunningInCluster()) {
+      this.kc.loadFromCluster();
+    } else {
+      this.kc.loadFromDefault();
+    }
     this.k8sApi = this.kc.makeApiClient(k8s.CoreV1Api);
     this.k8sAppsApi = this.kc.makeApiClient(k8s.AppsV1Api);
     this.k8sBatchApi = this.kc.makeApiClient(k8s.BatchV1Api);
   }
 
+  /**
+   * A very simple test to check if the application is running inside a Kubernetes cluster
+   */
+  private isRunningInCluster(): boolean {
+    const serviceAccountPath =
+      "/var/run/secrets/kubernetes.io/serviceaccount/token";
+    try {
+      return fs.existsSync(serviceAccountPath);
+    } catch {
+      return false;
+    }
+  }
+
   /**
    * Set the current context to the desired context name.
-   * 
-   * @param contextName 
+   *
+   * @param contextName
    */
   public setCurrentContext(contextName: string) {
-
-
     // Get all available contexts
     const contexts = this.kc.getContexts();
-    const contextNames = contexts.map(context => context.name);
+    const contextNames = contexts.map((context) => context.name);
 
     // Check if the requested context exists
     if (!contextNames.includes(contextName)) {
-      throw new Error(`Context '${contextName}' not found. Available contexts: ${contextNames.join(', ')}`);
+      throw new Error(
+        `Context '${contextName}' not found. Available contexts: ${contextNames.join(
+          ", "
+        )}`
+      );
     }
     // Set the current context
     this.kc.setCurrentContext(contextName);
diff --git a/tests/kubernetes-manager.test.ts b/tests/kubernetes-manager.test.ts
new file mode 100644
index 0000000..c6a1238
--- /dev/null
+++ b/tests/kubernetes-manager.test.ts
@@ -0,0 +1,71 @@
+import { expect, describe, test, vi, beforeEach } from 'vitest';
+import * as fs from 'fs';
+import { KubernetesManager } from '../src/utils/kubernetes-manager.js';
+
+// Mock fs module
+vi.mock('fs', () => ({
+  existsSync: vi.fn()
+}));
+
+describe('KubernetesManager', () => {
+  let kubernetesManager: KubernetesManager;
+  
+  beforeEach(() => {
+    // Clear all mocks before each test
+    vi.clearAllMocks();
+    
+    // Create a new instance for each test with mocked constructor behavior
+    vi.spyOn(KubernetesManager.prototype, 'isRunningInCluster' as any).mockImplementation(() => false);
+    kubernetesManager = new KubernetesManager();
+    vi.restoreAllMocks();
+  });
+
+  describe('isRunningInCluster', () => {
+    test('should return true when service account token exists', () => {
+      // Mock fs.existsSync to return true
+      (fs.existsSync as any).mockReturnValueOnce(true);
+      
+      // Use any to access private method
+      const result = (kubernetesManager as any).isRunningInCluster();
+      
+      // Verify result
+      expect(result).toBe(true);
+      
+      // Verify fs.existsSync was called with correct path
+      expect(fs.existsSync).toHaveBeenCalledWith('/var/run/secrets/kubernetes.io/serviceaccount/token');
+      expect(fs.existsSync).toHaveBeenCalledTimes(1);
+    });
+
+    test('should return false when service account token does not exist', () => {
+      // Mock fs.existsSync to return false
+      (fs.existsSync as any).mockReturnValueOnce(false);
+      
+      // Use any to access private method
+      const result = (kubernetesManager as any).isRunningInCluster();
+      
+      // Verify result
+      expect(result).toBe(false);
+      
+      // Verify fs.existsSync was called with correct path
+      expect(fs.existsSync).toHaveBeenCalledWith('/var/run/secrets/kubernetes.io/serviceaccount/token');
+      expect(fs.existsSync).toHaveBeenCalledTimes(1);
+    });
+
+    test('should return false when fs.existsSync throws an error', () => {
+      // Mock fs.existsSync to throw an error
+      (fs.existsSync as any).mockImplementationOnce(() => {
+        throw new Error('Some filesystem error');
+      });
+      
+      // Use any to access private method
+      const result = (kubernetesManager as any).isRunningInCluster();
+      
+      // Verify result
+      expect(result).toBe(false);
+      
+      // Verify fs.existsSync was called with correct path
+      expect(fs.existsSync).toHaveBeenCalledWith('/var/run/secrets/kubernetes.io/serviceaccount/token');
+      expect(fs.existsSync).toHaveBeenCalledTimes(1);
+    });
+  });
+}); 
\ No newline at end of file


Reviews:
user: login: Flux159
body: LGTM, just remove the console logs
state: COMMENTED
author_association: OWNER
submitted_at: 2025-05-21T17:12:20Z
user: login: ajit-pendse
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-22T04:00:18Z
user: login: Flux159
body: Code LGTM & tests pass
state: APPROVED
author_association: OWNER
submitted_at: 2025-05-22T04:42:20Z

Review comments:
diff_hunk: @@ -12,27 +13,48 @@ export class KubernetesManager {
 
   constructor() {
     this.kc = new k8s.KubeConfig();
-    this.kc.loadFromDefault();
+    if (this.isRunningInCluster()) {
+      console.log("Running inside a Kubernetes cluster");
+      this.kc.loadFromCluster();
+    } else {
+      console.log("Loading default kube config file");
+      this.kc.loadFromDefault();
+    }
path: src/utils/kubernetes-manager.ts
user: login: Flux159
body: ```suggestion
    if (this.isRunningInCluster()) {
      this.kc.loadFromCluster();
    } else {
      this.kc.loadFromDefault();
    }
```

I think some clients had issues if you put stdout that isn't valid jsonrpc
created_at: 2025-05-21T17:11:51Z
author_association: OWNER
diff_hunk: @@ -12,27 +13,48 @@ export class KubernetesManager {
 
   constructor() {
     this.kc = new k8s.KubeConfig();
-    this.kc.loadFromDefault();
+    if (this.isRunningInCluster()) {
+      console.log("Running inside a Kubernetes cluster");
+      this.kc.loadFromCluster();
+    } else {
+      console.log("Loading default kube config file");
+      this.kc.loadFromDefault();
+    }
path: src/utils/kubernetes-manager.ts
user: login: ajit-pendse
body: Updated to remove console.log statements.
created_at: 2025-05-22T04:00:17Z
author_association: CONTRIBUTOR

PR comments:
user: login: rr-paras-patel
created_at: 2025-05-21T05:06:44Z
body: can you try this on your local one more time? we just merged one jumbo PR ?
author_association: COLLABORATOR
user: login: ajit-pendse
created_at: 2025-05-21T12:08:32Z
body: Integrated latest changes in the PR. Test cases for kubernetes-manager pass. Also tested on a live kubernetes cluster.
author_association: CONTRIBUTOR
user: login: Flux159
created_at: 2025-05-21T17:18:04Z
body: Oh I have a separate question - since we moved to using kubectl cli for a lot of commands, does kubectl also automatically look at the token in `/var/run/secrets/kubernetes.io/serviceaccount/token` if you run from a cluster & have it installed in that container? 

Right now this code change just adjusts the node-client, but if kubectl in the container needs some configuration change then that won't be done / server will probably error out. 
author_association: OWNER
user: login: ajit-pendse
created_at: 2025-05-22T03:56:07Z
body: Yes - `kubectl` looks for the same file and supports automatic detection for in-cluster use cases. Ref - https://kubernetes.io/docs/reference/kubectl/#in-cluster-authentication-and-namespace-overrides
author_association: CONTRIBUTOR
user: login: Flux159
created_at: 2025-05-22T04:49:14Z
body: Merged this in & it should be available in version 2.1.0 when it's published to NPM.

@ajit-pendse - Since we have kubectl installed in the [docker container for kubernetes-mcp-server](https://github.com/Flux159/mcp-server-kubernetes/blob/v2.1.0/Dockerfile), that means that we could just tell people to run the that container inside of their cluster now right? 

Since it's using a token inside of the container, in theory it wouldn't need the aws or gcp auth plugins in order to authenticate inside of EKS or GKE if it was run inside of the cluster?
author_association: OWNER
user: login: ajit-pendse
created_at: 2025-05-22T05:04:06Z
body: > Merged this in & it should be available in version 2.1.0 when it's published to NPM.
> 
> @ajit-pendse - Since we have kubectl installed in the [docker container for kubernetes-mcp-server](https://github.com/Flux159/mcp-server-kubernetes/blob/v2.1.0/Dockerfile), that means that we could just tell people to run the that container inside of their cluster now right?
> 
> Since it's using a token inside of the container, in theory it wouldn't need the aws or gcp auth plugins in order to authenticate inside of EKS or GKE if it was run inside of the cluster?

Yes, if this code is running inside a pod in the cluster, no additional authentication details or plugins are needed. Users can just run it as a pod in the cluster - which should work out of the box.

However, for RBAC enabled clusters, the identity used by the pod (be default, a service account named `default` in the same namespace  as the pod - but there are multiple ways to assign identity to a running pod) will need appropriate access provided to the resources to be managed by the mcp server. While this is out of scope for the mcp server, it would be a good idea to mention it in documentation.

Ref. - https://kubernetes.io/docs/reference/access-authn-authz/rbac/#role-and-clusterrole
author_association: CONTRIBUTOR
user: login: Flux159
created_at: 2025-05-22T05:13:05Z
body: > However, for RBAC enabled clusters, the identity used by the pod (be default, a service account named default in the same namespace as the pod - but there are multiple ways to assign identity to a running pod) will need appropriate access provided to the resources to be managed by the mcp server. While this is out of scope for the mcp server, it would be a good idea to mention it in documentation.

Ah okay, so the service account defined for a deployment/container for something like GKE would need permissions to administer the GKE cluster to be able to use all the kubectl commands - this makes sense. Will add to the readme in a followup. I think this also makes the docker container a lot more useful, so probably makes sense to add to the readme now.
author_association: OWNER