Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -42,5 +42,6 @@ EXPOSE 8000
ENV TRANSPORT=sse \
PORT=8000

# Run the server (align with FastMCP default port 8000)
CMD ["python", "run_server.py", "--transport", "sse", "--port", "8000"]
# Run the server using environment variables
# Default binds to 127.0.0.1, set LISTEN=true to bind to 0.0.0.0
CMD ["python", "run_server.py", "--transport", "sse"]
43 changes: 40 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -182,19 +182,56 @@ docker pull rohitghumare64/kubectl-mcp-server:latest

### Running the image

The server inside the container listens on port **8000**. Bind any free host port to 8000 and mount your kubeconfig:
The server inside the container listens on port **8000** and binds to **127.0.0.1** by default. For external access, you need to set `LISTEN=true` to bind to all interfaces (0.0.0.0):

```bash
# Replace 8081 with any free port on your host
# Mount your local ~/.kube directory for cluster credentials
# For external access (bind to 0.0.0.0)
docker run -p 8081:8000 \
-e LISTEN=true \
-v $HOME/.kube:/root/.kube \
rohitghumare64/kubectl-mcp-server:latest

# For localhost only (default behavior)
docker run -p 8081:8000 \
-v $HOME/.kube:/root/.kube \
rohitghumare64/kubectl-mcp-server:latest
```

* `-p 8081:8000` maps host port 8081 β†’ container port 8000.
* `-v $HOME/.kube:/root/.kube` mounts your kubeconfig so the server can reach the cluster.
* `-e LISTEN=true` binds to 0.0.0.0 for external access.

### Environment Variables

You can customize the server behavior using environment variables:

```bash
# Bind to all interfaces (0.0.0.0) for external access
docker run -p 8000:8000 \
-e LISTEN=true \
-v $HOME/.kube:/root/.kube \
rohitghumare64/kubectl-mcp-server:latest

# Custom port with external access
docker run -p 9000:9000 \
-e PORT=9000 \
-e LISTEN=true \
-v $HOME/.kube:/root/.kube \
rohitghumare64/kubectl-mcp-server:latest

# Use different transport (stdio for direct integration)
docker run -e TRANSPORT=stdio \
-v $HOME/.kube:/root/.kube \
rohitghumare64/kubectl-mcp-server:latest
```

Available environment variables:
- `TRANSPORT`: `sse` (default) or `stdio`
- `PORT`: Port number (default: 8000)
- `LISTEN`:
- Default: `127.0.0.1` (localhost only)
- `true`: Bind to 0.0.0.0 (all interfaces)
- Any IP address: Bind to specific address

### Building a multi-architecture image (AMD64 & ARM64)

Expand Down
17 changes: 13 additions & 4 deletions kubectl_mcp_tool/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,11 @@ async def serve_stdio():
server = MCPServer("kubectl-mcp-tool")
await server.serve_stdio()

async def serve_sse(port: int):
async def serve_sse(port: int, host: str = "127.0.0.1"):
"""Serve the MCP server over SSE transport."""
logger.info(f"Starting standard MCP server with SSE transport on port {port}")
logger.info(f"Starting standard MCP server with SSE transport on {host}:{port}")
server = MCPServer("kubectl-mcp-tool")
await server.serve_sse(port)
await server.serve_sse(port, host)

def main():
"""Main entry point for the CLI."""
Expand All @@ -49,6 +49,15 @@ def main():
help="Transport to use (stdio or sse)")
serve_parser.add_argument("--port", type=int, default=8080,
help="Port to use for SSE transport")
# Handle LISTEN environment variable logic
listen_env = os.environ.get("LISTEN", "127.0.0.1")
if listen_env.lower() == "true":
listen_default = "0.0.0.0"
else:
listen_default = listen_env

serve_parser.add_argument("--listen", type=str, default=listen_default,
help="Host address to bind to for SSE transport. Use 0.0.0.0 to bind to all interfaces (or set LISTEN=true)")
serve_parser.add_argument("--cursor", action="store_true",
help="Enable Cursor compatibility mode")
serve_parser.add_argument("--debug", action="store_true",
Expand Down Expand Up @@ -88,7 +97,7 @@ def main():
if args.transport == "stdio":
asyncio.run(serve_stdio())
else:
asyncio.run(serve_sse(args.port))
asyncio.run(serve_sse(args.port, args.listen))
except Exception as e:
logger.error(f"Error starting standard MCP server: {e}")
if args.debug:
Expand Down
42 changes: 30 additions & 12 deletions kubectl_mcp_tool/mcp_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -842,20 +842,24 @@ async def serve_stdio(self):
# Continue with normal server startup
await self.server.run_stdio_async()

async def serve_sse(self, port: int):
async def serve_sse(self, port: int, host: str = "127.0.0.1"):
"""Serve the MCP server over SSE transport."""
logger.info(f"Starting MCP server with SSE transport on port {port}")
logger.info(f"Starting MCP server with SSE transport on {host}:{port}")

try:
# Newer versions of FastMCP expose a keyword argument for the port
await self.server.run_sse_async(port=port)
# Newer versions of FastMCP expose keyword arguments for port and host
await self.server.run_sse_async(port=port, host=host)
except TypeError:
# Fall back to the legacy signature that takes no parameters
logger.warning(
"FastMCP.run_sse_async() does not accept a 'port' parameter in this version. "
"Falling back to the default signature (using FastMCP's internal default port)."
)
await self.server.run_sse_async()
try:
# Try with just port parameter
await self.server.run_sse_async(port=port)
except TypeError:
# Fall back to the legacy signature that takes no parameters
logger.warning(
"FastMCP.run_sse_async() does not accept 'port' or 'host' parameters in this version. "
"Falling back to the default signature (using FastMCP's internal defaults)."
)
await self.server.run_sse_async()

if __name__ == "__main__":
import asyncio
Expand All @@ -878,6 +882,20 @@ async def serve_sse(self, port: int):
default=8080,
help="Port to use for SSE transport. Default: 8080.",
)
# Handle LISTEN environment variable logic
import os
listen_env = os.environ.get("LISTEN", "127.0.0.1")
if listen_env.lower() == "true":
listen_default = "0.0.0.0"
else:
listen_default = listen_env

parser.add_argument(
"--listen",
type=str,
default=listen_default,
help="Host address to bind to for SSE transport. Use 0.0.0.0 to bind to all interfaces. Default: 127.0.0.1 (or set LISTEN=true for 0.0.0.0).",
)
args = parser.parse_args()

server_name = "kubectl_mcp_server"
Expand All @@ -892,8 +910,8 @@ async def serve_sse(self, port: int):
logger.info(f"Starting {server_name} with stdio transport.")
loop.run_until_complete(mcp_server.serve_stdio())
elif args.transport == "sse":
logger.info(f"Starting {server_name} with SSE transport on port {args.port}.")
loop.run_until_complete(mcp_server.serve_sse(port=args.port))
logger.info(f"Starting {server_name} with SSE transport on {args.listen}:{args.port}.")
loop.run_until_complete(mcp_server.serve_sse(port=args.port, host=args.listen))
except KeyboardInterrupt:
logger.info("Server shutdown requested by user.")
except Exception as e:
Expand Down
21 changes: 17 additions & 4 deletions run_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,21 @@ def main():
parser.add_argument(
"--port",
type=int,
default=8080,
help="Port to use for SSE transport. Default: 8080.",
default=int(os.environ.get("PORT", 8080)),
help="Port to use for SSE transport. Default: 8080 (or PORT env var).",
)
# Handle LISTEN environment variable logic
listen_env = os.environ.get("LISTEN", "127.0.0.1")
if listen_env.lower() == "true":
listen_default = "0.0.0.0"
else:
listen_default = listen_env

parser.add_argument(
"--listen",
type=str,
default=listen_default,
help="Host address to bind to for SSE transport. Use 0.0.0.0 to bind to all interfaces. Default: 127.0.0.1 (or set LISTEN=true for 0.0.0.0).",
)
args = parser.parse_args()

Expand All @@ -48,8 +61,8 @@ def main():
logger.info(f"Starting {server_name} with stdio transport.")
loop.run_until_complete(mcp_server.serve_stdio())
elif args.transport == "sse":
logger.info(f"Starting {server_name} with SSE transport on port {args.port}.")
loop.run_until_complete(mcp_server.serve_sse(port=args.port))
logger.info(f"Starting {server_name} with SSE transport on {args.listen}:{args.port}.")
loop.run_until_complete(mcp_server.serve_sse(port=args.port, host=args.listen))
except KeyboardInterrupt:
logger.info("Server shutdown requested by user.")
except Exception as e:
Expand Down