Skip to content
Draft
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
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ require (
github.com/go-jose/go-jose/v4 v4.1.3
github.com/google/jsonschema-go v0.3.0
github.com/mark3labs/mcp-go v0.43.0
github.com/modelcontextprotocol/go-sdk v1.1.0
github.com/pkg/errors v0.9.1
github.com/spf13/afero v1.15.0
github.com/spf13/cobra v1.10.1
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,8 @@ github.com/moby/spdystream v0.5.0 h1:7r0J1Si3QO/kjRitvSLVVFUjxMEb/YLj6S9FF62JBCU
github.com/moby/spdystream v0.5.0/go.mod h1:xBAYlnt/ay+11ShkdFKNAG7LsyK/tmNBVvVOwrfMgdI=
github.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ=
github.com/moby/term v0.5.2/go.mod h1:d3djjFCrjnB+fl8NJux+EJzu0msscUP+f8it8hPkFLc=
github.com/modelcontextprotocol/go-sdk v1.1.0 h1:Qjayg53dnKC4UZ+792W21e4BpwEZBzwgRW6LrjLWSwA=
github.com/modelcontextprotocol/go-sdk v1.1.0/go.mod h1:6fM3LCm3yV7pAs8isnKLn07oKtB0MP9LHd3DfAcKw10=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
Expand Down
4 changes: 2 additions & 2 deletions pkg/http/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@ func Serve(ctx context.Context, mcpServer *mcp.Server, staticConfig *config.Stat
Handler: wrappedMux,
}

sseServer := mcpServer.ServeSse(staticConfig.SSEBaseURL, httpServer)
streamableHttpServer := mcpServer.ServeHTTP(httpServer)
sseServer := mcpServer.ServeSse()
streamableHttpServer := mcpServer.ServeHTTP()
mux.Handle(sseEndpoint, sseServer)
mux.Handle(sseMessageEndpoint, sseServer)
mux.Handle(mcpEndpoint, streamableHttpServer)
Expand Down
3 changes: 2 additions & 1 deletion pkg/kubernetes-mcp-server/cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -345,7 +345,8 @@ func (m *MCPServerOptions) Run() error {
return internalhttp.Serve(ctx, mcpServer, m.StaticConfig, oidcProvider, httpClient)
}

if err := mcpServer.ServeStdio(); err != nil && !errors.Is(err, context.Canceled) {
ctx := context.Background()
if err := mcpServer.ServeStdio(ctx); err != nil && !errors.Is(err, context.Canceled) {
return err
}

Expand Down
2 changes: 1 addition & 1 deletion pkg/mcp/common_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,7 @@ func (s *BaseMcpSuite) InitMcpClient(options ...transport.StreamableHTTPCOption)
var err error
s.mcpServer, err = NewServer(Configuration{StaticConfig: s.Cfg})
s.Require().NoError(err, "Expected no error creating MCP server")
s.McpClient = test.NewMcpClient(s.T(), s.mcpServer.ServeHTTP(nil), options...)
s.McpClient = test.NewMcpClient(s.T(), s.mcpServer.ServeHTTP(), options...)
}

// EnvTestInOpenShift sets up the kubernetes environment to seem to be running OpenShift
Expand Down
109 changes: 109 additions & 0 deletions pkg/mcp/gosdk.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
package mcp

import (
"context"
"encoding/json"
"errors"
"fmt"

"github.com/containers/kubernetes-mcp-server/pkg/api"
"github.com/modelcontextprotocol/go-sdk/mcp"
"k8s.io/utils/ptr"
)

func ServerToolToGoSdkTool(s *Server, tool api.ServerTool) (*mcp.Tool, mcp.ToolHandler, error) {
goSdkTool := &mcp.Tool{
Name: tool.Tool.Name,
Description: tool.Tool.Description,
Title: tool.Tool.Annotations.Title,
Annotations: &mcp.ToolAnnotations{
Title: tool.Tool.Annotations.Title,
ReadOnlyHint: ptr.Deref(tool.Tool.Annotations.ReadOnlyHint, false),
DestructiveHint: tool.Tool.Annotations.DestructiveHint,
IdempotentHint: ptr.Deref(tool.Tool.Annotations.IdempotentHint, false),
OpenWorldHint: tool.Tool.Annotations.OpenWorldHint,
},
}
if tool.Tool.InputSchema != nil {
schema, err := json.Marshal(tool.Tool.InputSchema)
if err != nil {
return nil, nil, fmt.Errorf("failed to marshal tool input schema for tool %s: %v", tool.Tool.Name, err)
}
// TODO: temporary fix to append an empty properties object (some client have trouble parsing a schema without properties)
// As opposed, Gemini had trouble for a while when properties was present but empty.
// https://github.com/containers/kubernetes-mcp-server/issues/340
if string(schema) == `{"type":"object"}` {
schema = []byte(`{"type":"object","properties":{}}`)
}

var fixedSchema map[string]interface{}
if err := json.Unmarshal(schema, &fixedSchema); err != nil {
return nil, nil, fmt.Errorf("failed to unmarshal tool input schema for tool %s: %v", tool.Tool.Name, err)
}

goSdkTool.InputSchema = fixedSchema
}
goSdkHandler := func(ctx context.Context, request *mcp.CallToolRequest) (*mcp.CallToolResult, error) {
toolCallRequest, err := GoSdkToolCallRequestToToolCallRequest(request)
if err != nil {
return nil, fmt.Errorf("%v for tool %s", err, tool.Tool.Name)
}
// get the correct derived Kubernetes client for the target specified in the request
cluster := toolCallRequest.GetString(s.p.GetTargetParameterName(), s.p.GetDefaultTarget())
k, err := s.p.GetDerivedKubernetes(ctx, cluster)
if err != nil {
return nil, err
}

result, err := tool.Handler(api.ToolHandlerParams{
Context: ctx,
Kubernetes: k,
ToolCallRequest: toolCallRequest,
ListOutput: s.configuration.ListOutput(),
})
if err != nil {
return nil, err
}
return NewTextResult(result.Content, result.Error), nil
}
return goSdkTool, goSdkHandler, nil
}

type ToolCallRequest struct {
Name string
arguments map[string]any
}

var _ api.ToolCallRequest = (*ToolCallRequest)(nil)

func GoSdkToolCallRequestToToolCallRequest(request *mcp.CallToolRequest) (*ToolCallRequest, error) {
toolCallParams, ok := request.GetParams().(*mcp.CallToolParamsRaw)
if !ok {
return nil, errors.New("invalid tool call parameters for tool call request")
}
return GoSdkToolCallParamsToToolCallRequest(toolCallParams)
}

func GoSdkToolCallParamsToToolCallRequest(toolCallParams *mcp.CallToolParamsRaw) (*ToolCallRequest, error) {
var arguments map[string]any
if err := json.Unmarshal(toolCallParams.Arguments, &arguments); err != nil {
return nil, fmt.Errorf("failed to unmarshal tool call arguments: %v", err)
}
return &ToolCallRequest{
Name: toolCallParams.Name,
arguments: arguments,
}, nil
}

func (ToolCallRequest *ToolCallRequest) GetArguments() map[string]any {
return ToolCallRequest.arguments
}

func (ToolCallRequest *ToolCallRequest) GetString(key, defaultValue string) string {
if value, ok := ToolCallRequest.arguments[key]; ok {
if strValue, ok := value.(string); ok {
return strValue
}
}
return defaultValue
}
63 changes: 0 additions & 63 deletions pkg/mcp/m3labs.go

This file was deleted.

Loading
Loading