mirror of
https://github.com/kubeshark/kubeshark.git
synced 2026-02-14 18:09:51 +00:00
Address code review comments for MCP implementation
- Add 30s timeout to HTTP client to prevent hanging requests - Add scanner.Err() check after stdin processing loop - Close HTTP response bodies to prevent resource leaks - Add goroutine to wait on started process to prevent zombies - Simplify polling loop by removing ineffective context check - Advertise check_kubeshark_status in URL mode (was callable but hidden) - Update documentation to clarify URL mode only disables start/stop
This commit is contained in:
@@ -58,8 +58,8 @@ you can connect directly without needing kubectl/kubeconfig:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
In URL mode, cluster management tools (start/stop/check) are disabled since
|
In URL mode, destructive tools (start/stop) are disabled since Kubeshark is
|
||||||
Kubeshark is managed externally.
|
managed externally. The check_kubeshark_status tool remains available to confirm connectivity.
|
||||||
|
|
||||||
DESTRUCTIVE OPERATIONS:
|
DESTRUCTIVE OPERATIONS:
|
||||||
|
|
||||||
@@ -117,7 +117,7 @@ Multiple --set flags can be used for different settings.`,
|
|||||||
func init() {
|
func init() {
|
||||||
rootCmd.AddCommand(mcpCmd)
|
rootCmd.AddCommand(mcpCmd)
|
||||||
|
|
||||||
mcpCmd.Flags().StringVar(&mcpURL, "url", "", "Direct URL to Kubeshark (e.g., https://kubeshark.example.com). When set, connects directly without kubectl/proxy and disables start/stop/check tools.")
|
mcpCmd.Flags().StringVar(&mcpURL, "url", "", "Direct URL to Kubeshark (e.g., https://kubeshark.example.com). When set, connects directly without kubectl/proxy and disables start/stop tools.")
|
||||||
mcpCmd.Flags().StringVar(&mcpKubeconfig, "kubeconfig", "", "Path to kubeconfig file (e.g., /Users/me/.kube/config)")
|
mcpCmd.Flags().StringVar(&mcpKubeconfig, "kubeconfig", "", "Path to kubeconfig file (e.g., /Users/me/.kube/config)")
|
||||||
mcpCmd.Flags().BoolVar(&mcpListTools, "list-tools", false, "List available MCP tools and exit")
|
mcpCmd.Flags().BoolVar(&mcpListTools, "list-tools", false, "List available MCP tools and exit")
|
||||||
mcpCmd.Flags().BoolVar(&mcpConfig, "mcp-config", false, "Print MCP client configuration JSON and exit")
|
mcpCmd.Flags().BoolVar(&mcpConfig, "mcp-config", false, "Print MCP client configuration JSON and exit")
|
||||||
|
|||||||
@@ -166,7 +166,7 @@ func runMCPWithConfig(setFlags []string, directURL string, allowDestructive bool
|
|||||||
zerolog.SetGlobalLevel(zerolog.Disabled)
|
zerolog.SetGlobalLevel(zerolog.Disabled)
|
||||||
|
|
||||||
server := &mcpServer{
|
server := &mcpServer{
|
||||||
httpClient: &http.Client{},
|
httpClient: &http.Client{Timeout: 30 * time.Second},
|
||||||
stdin: os.Stdin,
|
stdin: os.Stdin,
|
||||||
stdout: os.Stdout,
|
stdout: os.Stdout,
|
||||||
setFlags: setFlags,
|
setFlags: setFlags,
|
||||||
@@ -250,9 +250,12 @@ func (s *mcpServer) ensureBackendConnection() string {
|
|||||||
return "Kubeshark is not running. Use the 'start_kubeshark' tool to start it first."
|
return "Kubeshark is not running. Use the 'start_kubeshark' tool to start it first."
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start proxy to frontend
|
// Start proxy to frontend and verify connectivity
|
||||||
frontURL := kubernetes.GetProxyOnPort(config.Config.Tap.Proxy.Front.Port)
|
frontURL := kubernetes.GetProxyOnPort(config.Config.Tap.Proxy.Front.Port)
|
||||||
response, err := http.Get(fmt.Sprintf("%s/", frontURL))
|
response, err := http.Get(fmt.Sprintf("%s/", frontURL))
|
||||||
|
if response != nil && response.Body != nil {
|
||||||
|
defer func() { _ = response.Body.Close() }()
|
||||||
|
}
|
||||||
if err != nil || response.StatusCode != 200 {
|
if err != nil || response.StatusCode != 200 {
|
||||||
startProxyReportErrorIfAny(
|
startProxyReportErrorIfAny(
|
||||||
kubernetesProvider,
|
kubernetesProvider,
|
||||||
@@ -345,6 +348,11 @@ func (s *mcpServer) run() {
|
|||||||
|
|
||||||
s.handleRequest(&req)
|
s.handleRequest(&req)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check for scanner errors (e.g., stdin closed, read errors)
|
||||||
|
if err := scanner.Err(); err != nil {
|
||||||
|
writeErrorToStderr("[kubeshark-mcp] Scanner error: %v", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *mcpServer) handleRequest(req *jsonRPCRequest) {
|
func (s *mcpServer) handleRequest(req *jsonRPCRequest) {
|
||||||
@@ -433,22 +441,20 @@ Use the MCP tools - do NOT use kubectl, helm, or curl directly.`
|
|||||||
func (s *mcpServer) handleListTools(req *jsonRPCRequest) {
|
func (s *mcpServer) handleListTools(req *jsonRPCRequest) {
|
||||||
var tools []mcpTool
|
var tools []mcpTool
|
||||||
|
|
||||||
// Add check_kubeshark_status if not in URL mode (safe, read-only)
|
// Add check_kubeshark_status - safe, read-only operation that works in both modes
|
||||||
if !s.urlMode {
|
tools = append(tools, mcpTool{
|
||||||
tools = append(tools, mcpTool{
|
Name: "check_kubeshark_status",
|
||||||
Name: "check_kubeshark_status",
|
Description: "Safe: Checks if Kubeshark is currently running and accessible. In URL mode, confirms connectivity to the remote instance. In local mode, checks cluster pods. This is a read-only operation.",
|
||||||
Description: "Safe: Checks if Kubeshark is currently running in the cluster. This is a read-only operation that does not modify anything.",
|
InputSchema: json.RawMessage(`{
|
||||||
InputSchema: json.RawMessage(`{
|
"type": "object",
|
||||||
"type": "object",
|
"properties": {
|
||||||
"properties": {
|
"release_namespace": {
|
||||||
"release_namespace": {
|
"type": "string",
|
||||||
"type": "string",
|
"description": "Namespace where Kubeshark is installed (default: 'default'). Only used in local mode."
|
||||||
"description": "Namespace where Kubeshark is installed (default: 'default')"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}`),
|
}
|
||||||
})
|
}`),
|
||||||
}
|
})
|
||||||
|
|
||||||
// Add destructive tools only if --allow-destructive flag was set (and not in URL mode)
|
// Add destructive tools only if --allow-destructive flag was set (and not in URL mode)
|
||||||
if !s.urlMode && s.allowDestructive {
|
if !s.urlMode && s.allowDestructive {
|
||||||
@@ -756,6 +762,11 @@ func (s *mcpServer) callStartKubeshark(args map[string]any) (string, bool) {
|
|||||||
return fmt.Sprintf("Failed to start Kubeshark: %v", err), true
|
return fmt.Sprintf("Failed to start Kubeshark: %v", err), true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Wait for the process in a goroutine to prevent zombie processes
|
||||||
|
go func() {
|
||||||
|
_ = cmd.Wait()
|
||||||
|
}()
|
||||||
|
|
||||||
logProgress("Kubeshark process started, waiting for pods to be ready...")
|
logProgress("Kubeshark process started, waiting for pods to be ready...")
|
||||||
|
|
||||||
// Wait for Kubeshark to be ready (poll for pods)
|
// Wait for Kubeshark to be ready (poll for pods)
|
||||||
@@ -786,13 +797,8 @@ func (s *mcpServer) callStartKubeshark(args map[string]any) (string, bool) {
|
|||||||
if ready {
|
if ready {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
select {
|
// Sleep 5 seconds before next check
|
||||||
case <-context.Background().Done():
|
time.Sleep(5 * time.Second)
|
||||||
return "Kubeshark start interrupted", true
|
|
||||||
default:
|
|
||||||
// Sleep 5 seconds before next check
|
|
||||||
<-time.After(5 * time.Second)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if !ready {
|
if !ready {
|
||||||
@@ -959,9 +965,12 @@ func establishProxyConnection(timeout time.Duration) (string, error) {
|
|||||||
return "", fmt.Errorf("not running (use start_kubeshark to start)")
|
return "", fmt.Errorf("not running (use start_kubeshark to start)")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start proxy to frontend
|
// Start proxy to frontend and verify connectivity
|
||||||
frontURL := kubernetes.GetProxyOnPort(config.Config.Tap.Proxy.Front.Port)
|
frontURL := kubernetes.GetProxyOnPort(config.Config.Tap.Proxy.Front.Port)
|
||||||
response, err := http.Get(fmt.Sprintf("%s/", frontURL))
|
response, err := http.Get(fmt.Sprintf("%s/", frontURL))
|
||||||
|
if response != nil && response.Body != nil {
|
||||||
|
defer func() { _ = response.Body.Close() }()
|
||||||
|
}
|
||||||
if err != nil || response.StatusCode != 200 {
|
if err != nil || response.StatusCode != 200 {
|
||||||
startProxyReportErrorIfAny(
|
startProxyReportErrorIfAny(
|
||||||
kubernetesProvider,
|
kubernetesProvider,
|
||||||
|
|||||||
Reference in New Issue
Block a user