Skip to content

Commit 1d70e69

Browse files
committed
implement with prompt type
1 parent 767c1db commit 1d70e69

File tree

14 files changed

+1096
-972
lines changed

14 files changed

+1096
-972
lines changed

pkg/api/prompts.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package api
2+
3+
import (
4+
internalk8s "github.com/containers/kubernetes-mcp-server/pkg/kubernetes"
5+
)
6+
7+
// ServerPrompt represents a prompt that can be provided to the MCP server
8+
type ServerPrompt struct {
9+
Name string
10+
Description string
11+
Arguments []PromptArgument
12+
GetMessages func(arguments map[string]string) []PromptMessage
13+
}
14+
15+
// PromptArgument defines an argument that can be passed to a prompt
16+
type PromptArgument struct {
17+
Name string
18+
Description string
19+
Required bool
20+
}
21+
22+
// PromptMessage represents a message in a prompt
23+
type PromptMessage struct {
24+
Role string // "user" or "assistant"
25+
Content string
26+
}
27+
28+
// PromptSet groups related prompts together
29+
type PromptSet interface {
30+
// GetName returns the name of the prompt set
31+
GetName() string
32+
// GetDescription returns a description of what this prompt set provides
33+
GetDescription() string
34+
// GetPrompts returns all prompts in this set
35+
GetPrompts(o internalk8s.Openshift) []ServerPrompt
36+
}

pkg/config/config.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ type StaticConfig struct {
3131
// When true, disable tools annotated with destructiveHint=true
3232
DisableDestructive bool `toml:"disable_destructive,omitempty"`
3333
Toolsets []string `toml:"toolsets,omitempty"`
34+
Promptsets []string `toml:"promptsets,omitempty"`
3435
EnabledTools []string `toml:"enabled_tools,omitempty"`
3536
DisabledTools []string `toml:"disabled_tools,omitempty"`
3637

pkg/mcp/mcp.go

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import (
1717
"github.com/containers/kubernetes-mcp-server/pkg/config"
1818
internalk8s "github.com/containers/kubernetes-mcp-server/pkg/kubernetes"
1919
"github.com/containers/kubernetes-mcp-server/pkg/output"
20+
"github.com/containers/kubernetes-mcp-server/pkg/promptsets"
2021
"github.com/containers/kubernetes-mcp-server/pkg/toolsets"
2122
"github.com/containers/kubernetes-mcp-server/pkg/version"
2223
)
@@ -29,6 +30,7 @@ type Configuration struct {
2930
*config.StaticConfig
3031
listOutput output.Output
3132
toolsets []api.Toolset
33+
promptsets []api.PromptSet
3234
}
3335

3436
func (c *Configuration) Toolsets() []api.Toolset {
@@ -40,6 +42,23 @@ func (c *Configuration) Toolsets() []api.Toolset {
4042
return c.toolsets
4143
}
4244

45+
func (c *Configuration) Promptsets() []api.PromptSet {
46+
if c.promptsets == nil {
47+
// Default to core if no promptsets configured
48+
promptsetNames := c.StaticConfig.Promptsets
49+
if len(promptsetNames) == 0 {
50+
promptsetNames = []string{"core"}
51+
}
52+
for _, promptset := range promptsetNames {
53+
ps := promptsets.PromptSetFromString(promptset)
54+
if ps != nil {
55+
c.promptsets = append(c.promptsets, ps)
56+
}
57+
}
58+
}
59+
return c.promptsets
60+
}
61+
4362
func (c *Configuration) ListOutput() output.Output {
4463
if c.listOutput == nil {
4564
c.listOutput = output.FromString(c.StaticConfig.ListOutput)
@@ -148,11 +167,37 @@ func (s *Server) reloadKubernetesClusterProvider() error {
148167

149168
s.server.SetTools(m3labsServerTools...)
150169

170+
// Register prompts
171+
if err := s.registerPrompts(p); err != nil {
172+
klog.Warningf("Failed to register prompts: %v", err)
173+
// Don't fail the whole reload if prompts fail
174+
}
175+
151176
// start new watch
152177
s.p.WatchTargets(s.reloadKubernetesClusterProvider)
153178
return nil
154179
}
155180

181+
// registerPrompts loads and registers all prompts with the MCP server
182+
func (s *Server) registerPrompts(p internalk8s.Provider) error {
183+
allPrompts := make([]api.ServerPrompt, 0)
184+
for _, ps := range s.configuration.Promptsets() {
185+
prompts := ps.GetPrompts(p)
186+
allPrompts = append(allPrompts, prompts...)
187+
klog.V(5).Infof("Loaded %d prompts from promptset '%s'", len(prompts), ps.GetName())
188+
}
189+
190+
m3labsPrompts, err := ServerPromptToM3LabsPrompt(allPrompts)
191+
if err != nil {
192+
return fmt.Errorf("failed to convert prompts: %v", err)
193+
}
194+
195+
s.server.SetPrompts(m3labsPrompts...)
196+
klog.V(3).Infof("Registered %d prompts", len(m3labsPrompts))
197+
198+
return nil
199+
}
200+
156201
func (s *Server) ServeStdio() error {
157202
return server.ServeStdio(s.server)
158203
}

pkg/mcp/mcp_test.go

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,20 @@ func TestWatchKubeConfig(t *testing.T) {
2323
// Given
2424
withTimeout, cancel := context.WithTimeout(c.ctx, 5*time.Second)
2525
defer cancel()
26-
var notification *mcp.JSONRPCNotification
26+
var toolsNotification *mcp.JSONRPCNotification
27+
var promptsNotification *mcp.JSONRPCNotification
2728
c.mcpClient.OnNotification(func(n mcp.JSONRPCNotification) {
28-
notification = &n
29+
if n.Method == "notifications/tools/list_changed" {
30+
toolsNotification = &n
31+
}
32+
if n.Method == "notifications/prompts/list_changed" {
33+
promptsNotification = &n
34+
}
2935
})
3036
// When
3137
f, _ := os.OpenFile(filepath.Join(c.tempDir, "config"), os.O_APPEND|os.O_WRONLY, 0644)
3238
_, _ = f.WriteString("\n")
33-
for notification == nil {
39+
for toolsNotification == nil || promptsNotification == nil {
3440
select {
3541
case <-withTimeout.Done():
3642
default:
@@ -39,11 +45,19 @@ func TestWatchKubeConfig(t *testing.T) {
3945
}
4046
// Then
4147
t.Run("WatchKubeConfig notifies tools change", func(t *testing.T) {
42-
if notification == nil {
43-
t.Fatalf("WatchKubeConfig did not notify")
48+
if toolsNotification == nil {
49+
t.Fatalf("WatchKubeConfig did not notify tools change")
50+
}
51+
if toolsNotification.Method != "notifications/tools/list_changed" {
52+
t.Fatalf("WatchKubeConfig did not notify tools change, got %s", toolsNotification.Method)
53+
}
54+
})
55+
t.Run("WatchKubeConfig notifies prompts change", func(t *testing.T) {
56+
if promptsNotification == nil {
57+
t.Fatalf("WatchKubeConfig did not notify prompts change")
4458
}
45-
if notification.Method != "notifications/tools/list_changed" {
46-
t.Fatalf("WatchKubeConfig did not notify tools change, got %s", notification.Method)
59+
if promptsNotification.Method != "notifications/prompts/list_changed" {
60+
t.Fatalf("WatchKubeConfig did not notify prompts change, got %s", promptsNotification.Method)
4761
}
4862
})
4963
})

pkg/mcp/modules.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package mcp
22

3+
import _ "github.com/containers/kubernetes-mcp-server/pkg/promptsets/core"
34
import _ "github.com/containers/kubernetes-mcp-server/pkg/toolsets/config"
45
import _ "github.com/containers/kubernetes-mcp-server/pkg/toolsets/core"
56
import _ "github.com/containers/kubernetes-mcp-server/pkg/toolsets/helm"

pkg/mcp/prompts.go

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
package mcp
2+
3+
import (
4+
"context"
5+
6+
"github.com/mark3labs/mcp-go/mcp"
7+
"github.com/mark3labs/mcp-go/server"
8+
9+
"github.com/containers/kubernetes-mcp-server/pkg/api"
10+
)
11+
12+
// ServerPromptToM3LabsPrompt converts our internal ServerPrompt to mcp-go ServerPrompt format
13+
func ServerPromptToM3LabsPrompt(prompts []api.ServerPrompt) ([]server.ServerPrompt, error) {
14+
m3labsPrompts := make([]server.ServerPrompt, 0, len(prompts))
15+
16+
for _, prompt := range prompts {
17+
// Convert arguments
18+
arguments := make([]mcp.PromptArgument, 0, len(prompt.Arguments))
19+
for _, arg := range prompt.Arguments {
20+
arguments = append(arguments, mcp.PromptArgument{
21+
Name: arg.Name,
22+
Description: arg.Description,
23+
Required: arg.Required,
24+
})
25+
}
26+
27+
// Create the prompt handler
28+
handler := createPromptHandler(prompt)
29+
30+
m3labsPrompts = append(m3labsPrompts, server.ServerPrompt{
31+
Prompt: mcp.Prompt{
32+
Name: prompt.Name,
33+
Description: prompt.Description,
34+
Arguments: arguments,
35+
},
36+
Handler: handler,
37+
})
38+
}
39+
40+
return m3labsPrompts, nil
41+
}
42+
43+
// createPromptHandler creates a handler function for a prompt
44+
func createPromptHandler(prompt api.ServerPrompt) server.PromptHandlerFunc {
45+
return func(ctx context.Context, request mcp.GetPromptRequest) (*mcp.GetPromptResult, error) {
46+
// Get arguments from the request (already a map[string]string)
47+
arguments := request.Params.Arguments
48+
if arguments == nil {
49+
arguments = make(map[string]string)
50+
}
51+
52+
// Get messages from the prompt
53+
promptMessages := prompt.GetMessages(arguments)
54+
55+
// Convert to mcp-go format
56+
messages := make([]mcp.PromptMessage, 0, len(promptMessages))
57+
for _, msg := range promptMessages {
58+
messages = append(messages, mcp.PromptMessage{
59+
Role: mcp.Role(msg.Role),
60+
Content: mcp.TextContent{
61+
Type: "text",
62+
Text: msg.Content,
63+
},
64+
})
65+
}
66+
67+
return &mcp.GetPromptResult{
68+
Description: prompt.Description,
69+
Messages: messages,
70+
}, nil
71+
}
72+
}

0 commit comments

Comments
 (0)