Skip to content

Commit aaac007

Browse files
committed
add configurable prompts support
Enable administrators to define custom MCP prompts in config.toml using template substitution with {{argument}} syntax. Features: - PromptLoader for loading prompts from TOML config - Core prompt types (ServerPrompt, Prompt, PromptArgument, PromptMessage) - Template argument substitution with {{variable}} syntax - Required argument validation - Integration with MCP server to register and serve config prompts Signed-off-by: Nader Ziada <[email protected]>
1 parent 724fa9f commit aaac007

File tree

15 files changed

+1146
-7
lines changed

15 files changed

+1146
-7
lines changed

README.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,27 @@ vim /etc/kubernetes-mcp-server/conf.d/99-local.toml
292292
pkill -HUP kubernetes-mcp-server
293293
```
294294

295+
### MCP Prompts
296+
297+
The server supports MCP prompts for workflow templates. Define custom prompts in `config.toml`:
298+
299+
```toml
300+
[[prompts]]
301+
name = "my-workflow"
302+
title = "my workflow"
303+
description = "Custom workflow"
304+
305+
[[prompts.arguments]]
306+
name = "resource_name"
307+
required = true
308+
309+
[[prompts.messages]]
310+
role = "user"
311+
content = "Help me with {{resource_name}}"
312+
```
313+
314+
See docs/PROMPTS.md for detailed documentation.
315+
295316
## 🛠️ Tools and Functionalities <a id="tools-and-functionalities"></a>
296317

297318
The Kubernetes MCP server supports enabling or disabling specific groups of tools and functionalities (tools, resources, prompts, and so on) via the `--toolsets` command-line flag or `toolsets` configuration option.

docs/PROMPTS.md

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
# MCP Prompts Support
2+
3+
The Kubernetes MCP Server supports [MCP Prompts](https://modelcontextprotocol.io/docs/concepts/prompts), which provide pre-defined workflow templates and guidance to AI assistants.
4+
5+
## What are MCP Prompts?
6+
7+
MCP Prompts are pre-defined templates that guide AI assistants through specific workflows. They combine:
8+
- **Structured guidance**: Step-by-step instructions for common tasks
9+
- **Parameterization**: Arguments that customize the prompt for specific contexts
10+
- **Conversation templates**: Pre-formatted messages that guide the interaction
11+
12+
## Creating Custom Prompts
13+
14+
Define custom prompts in your `config.toml` file - no code changes or recompilation needed!
15+
16+
### Example
17+
18+
```toml
19+
[[prompts]]
20+
name = "check-pod-logs"
21+
title = "Check Pod Logs"
22+
description = "Quick way to check pod logs"
23+
24+
[[prompts.arguments]]
25+
name = "pod_name"
26+
description = "Name of the pod"
27+
required = true
28+
29+
[[prompts.arguments]]
30+
name = "namespace"
31+
description = "Namespace of the pod"
32+
required = false
33+
34+
[[prompts.messages]]
35+
role = "user"
36+
content = "Show me the logs for pod {{pod_name}} in {{namespace}}"
37+
38+
[[prompts.messages]]
39+
role = "assistant"
40+
content = "I'll retrieve and analyze the logs for you."
41+
```
42+
43+
## Configuration Reference
44+
45+
### Prompt Fields
46+
- **name** (required): Unique identifier for the prompt
47+
- **title** (optional): Human-readable display name
48+
- **description** (required): Brief explanation of what the prompt does
49+
- **arguments** (optional): List of parameters the prompt accepts
50+
- **messages** (required): Conversation template with role/content pairs
51+
52+
### Argument Fields
53+
- **name** (required): Argument identifier
54+
- **description** (optional): Explanation of the argument's purpose
55+
- **required** (optional): Whether the argument must be provided (default: false)
56+
57+
### Argument Substitution
58+
Use `{{argument_name}}` placeholders in message content. The template engine replaces these with actual values when the prompt is called.
59+
60+
## Configuration File Location
61+
62+
Place your prompts in the `config.toml` file used by the MCP server. Specify the config file path using the `--config` flag when starting the server.

pkg/api/prompt_config.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package api
2+
3+
import (
4+
"context"
5+
6+
"github.com/BurntSushi/toml"
7+
"github.com/containers/kubernetes-mcp-server/pkg/config"
8+
)
9+
10+
// promptsParser parses prompts from TOML configuration
11+
func promptsParser(ctx context.Context, primitive toml.Primitive, md toml.MetaData) (interface{}, error) {
12+
var prompts []PromptDefinition
13+
if err := md.PrimitiveDecode(primitive, &prompts); err != nil {
14+
return nil, err
15+
}
16+
return prompts, nil
17+
}
18+
19+
func init() {
20+
// Register the prompts parser with the config package
21+
config.RegisterPromptsParser(promptsParser)
22+
}

pkg/api/prompt_loader.go

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
package api
2+
3+
import (
4+
"fmt"
5+
"strings"
6+
)
7+
8+
// PromptDefinition represents a prompt definition loaded from config
9+
type PromptDefinition struct {
10+
Name string `yaml:"name" toml:"name" json:"name"`
11+
Title string `yaml:"title,omitempty" toml:"title,omitempty" json:"title,omitempty"`
12+
Description string `yaml:"description" toml:"description" json:"description"`
13+
Arguments []PromptArgumentDef `yaml:"arguments,omitempty" toml:"arguments,omitempty" json:"arguments,omitempty"`
14+
Messages []PromptMessageTemplate `yaml:"messages" toml:"messages" json:"messages"`
15+
}
16+
17+
// PromptArgumentDef represents an argument definition
18+
type PromptArgumentDef struct {
19+
Name string `yaml:"name" toml:"name" json:"name"`
20+
Description string `yaml:"description" toml:"description" json:"description"`
21+
Required bool `yaml:"required" toml:"required" json:"required"`
22+
}
23+
24+
// PromptMessageTemplate represents a message template
25+
type PromptMessageTemplate struct {
26+
Role string `yaml:"role" toml:"role" json:"role"`
27+
Content string `yaml:"content" toml:"content" json:"content"`
28+
}
29+
30+
// PromptLoader loads prompt definitions from TOML config
31+
type PromptLoader struct {
32+
definitions []PromptDefinition
33+
}
34+
35+
// NewPromptLoader creates a new prompt loader
36+
func NewPromptLoader() *PromptLoader {
37+
return &PromptLoader{
38+
definitions: make([]PromptDefinition, 0),
39+
}
40+
}
41+
42+
// GetServerPrompts converts loaded definitions to ServerPrompt instances
43+
func (l *PromptLoader) GetServerPrompts() []ServerPrompt {
44+
prompts := make([]ServerPrompt, 0, len(l.definitions))
45+
for _, def := range l.definitions {
46+
prompts = append(prompts, l.convertToServerPrompt(def))
47+
}
48+
return prompts
49+
}
50+
51+
// convertToServerPrompt converts a PromptDefinition to a ServerPrompt
52+
func (l *PromptLoader) convertToServerPrompt(def PromptDefinition) ServerPrompt {
53+
arguments := make([]PromptArgument, 0, len(def.Arguments))
54+
for _, arg := range def.Arguments {
55+
arguments = append(arguments, PromptArgument(arg))
56+
}
57+
58+
return ServerPrompt{
59+
Prompt: Prompt{
60+
Name: def.Name,
61+
Title: def.Title,
62+
Description: def.Description,
63+
Arguments: arguments,
64+
},
65+
Handler: l.createHandler(def),
66+
}
67+
}
68+
69+
// createHandler creates a prompt handler function for a prompt definition
70+
func (l *PromptLoader) createHandler(def PromptDefinition) PromptHandlerFunc {
71+
return func(params PromptHandlerParams) (*PromptCallResult, error) {
72+
args := params.GetArguments()
73+
74+
// Validate required arguments
75+
for _, argDef := range def.Arguments {
76+
if argDef.Required {
77+
if _, exists := args[argDef.Name]; !exists {
78+
return nil, fmt.Errorf("required argument '%s' is missing", argDef.Name)
79+
}
80+
}
81+
}
82+
83+
// Render messages with argument substitution
84+
messages := make([]PromptMessage, 0, len(def.Messages))
85+
for _, msgTemplate := range def.Messages {
86+
content := l.substituteArguments(msgTemplate.Content, args)
87+
messages = append(messages, PromptMessage{
88+
Role: msgTemplate.Role,
89+
Content: PromptContent{
90+
Type: "text",
91+
Text: content,
92+
},
93+
})
94+
}
95+
96+
return NewPromptCallResult(def.Description, messages, nil), nil
97+
}
98+
}
99+
100+
// substituteArguments replaces {{argument}} placeholders in content with actual values
101+
func (l *PromptLoader) substituteArguments(content string, args map[string]string) string {
102+
result := content
103+
for key, value := range args {
104+
placeholder := fmt.Sprintf("{{%s}}", key)
105+
result = strings.ReplaceAll(result, placeholder, value)
106+
}
107+
return result
108+
}
109+
110+
// LoadFromParsedConfig loads prompts from already-parsed config data
111+
// The input should be a slice of PromptDefinition (stored as interface{} in config to maintain abstraction)
112+
func (l *PromptLoader) LoadFromParsedConfig(parsedPrompts interface{}) error {
113+
if parsedPrompts == nil {
114+
return nil
115+
}
116+
117+
// Type assert to []PromptDefinition
118+
// This works because the registered parser in pkg/api/prompt_config.go returns []PromptDefinition
119+
defs, ok := parsedPrompts.([]PromptDefinition)
120+
if !ok {
121+
return fmt.Errorf("unexpected type for parsed prompts: %T, expected []api.PromptDefinition", parsedPrompts)
122+
}
123+
124+
l.definitions = append(l.definitions, defs...)
125+
return nil
126+
}

pkg/api/prompts.go

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
package api
2+
3+
import (
4+
"context"
5+
6+
internalk8s "github.com/containers/kubernetes-mcp-server/pkg/kubernetes"
7+
)
8+
9+
// ServerPrompt represents a prompt that can be registered with the MCP server.
10+
// Prompts provide pre-defined workflow templates and guidance to AI assistants.
11+
type ServerPrompt struct {
12+
Prompt Prompt
13+
Handler PromptHandlerFunc
14+
ClusterAware *bool
15+
ArgumentSchema map[string]PromptArgument
16+
}
17+
18+
// IsClusterAware indicates whether the prompt can accept a "cluster" or "context" parameter
19+
// to operate on a specific Kubernetes cluster context.
20+
// Defaults to true if not explicitly set
21+
func (s *ServerPrompt) IsClusterAware() bool {
22+
if s.ClusterAware != nil {
23+
return *s.ClusterAware
24+
}
25+
return true
26+
}
27+
28+
// Prompt represents the metadata and content of an MCP prompt
29+
type Prompt struct {
30+
Name string `yaml:"name" json:"name"`
31+
Title string `yaml:"title,omitempty" json:"title,omitempty"`
32+
Description string `yaml:"description" json:"description,omitempty"`
33+
Arguments []PromptArgument `yaml:"arguments,omitempty" json:"arguments,omitempty"`
34+
}
35+
36+
// PromptArgument defines a parameter that can be passed to a prompt
37+
type PromptArgument struct {
38+
Name string `yaml:"name" json:"name"`
39+
Description string `yaml:"description" json:"description,omitempty"`
40+
Required bool `yaml:"required" json:"required"`
41+
}
42+
43+
// PromptMessage represents a single message in a prompt template
44+
type PromptMessage struct {
45+
Role string `yaml:"role" json:"role"`
46+
Content PromptContent `yaml:"content" json:"content"`
47+
}
48+
49+
// PromptContent represents the content of a prompt message
50+
type PromptContent struct {
51+
Type string `yaml:"type" json:"type"`
52+
Text string `yaml:"text,omitempty" json:"text,omitempty"`
53+
}
54+
55+
// PromptCallRequest interface for accessing prompt call arguments
56+
type PromptCallRequest interface {
57+
GetArguments() map[string]string
58+
}
59+
60+
// PromptCallResult represents the result of executing a prompt
61+
type PromptCallResult struct {
62+
Description string
63+
Messages []PromptMessage
64+
Error error
65+
}
66+
67+
// NewPromptCallResult creates a new PromptCallResult
68+
func NewPromptCallResult(description string, messages []PromptMessage, err error) *PromptCallResult {
69+
return &PromptCallResult{
70+
Description: description,
71+
Messages: messages,
72+
Error: err,
73+
}
74+
}
75+
76+
// PromptHandlerParams contains the parameters passed to a prompt handler
77+
type PromptHandlerParams struct {
78+
context.Context
79+
*internalk8s.Kubernetes
80+
PromptCallRequest
81+
}
82+
83+
// PromptHandlerFunc is a function that handles prompt execution
84+
type PromptHandlerFunc func(params PromptHandlerParams) (*PromptCallResult, error)

0 commit comments

Comments
 (0)