Skip to content

Commit 7aacea6

Browse files
committed
feat: add MCP prompts support
Add MCP prompts support to enable pre-defined workflow templates and guidance for AI assistants. Includes: - New PromptLoader for loading prompts from toml config - Core prompt types (ServerPrompt, Prompt, PromptArgument, PromptMessage) - Template argument substitution with {{variable}} syntax - Required argument validation - built-in prompts for common Kubernetes workflows (troubleshooting, deployment, scaling, cluster health, networking, resource usage) - Integration with MCP server to register and serve prompts Signed-off-by: Nader Ziada <[email protected]>
1 parent 3a03479 commit 7aacea6

22 files changed

+1926
-7
lines changed

README.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,34 @@ uvx kubernetes-mcp-server@latest --help
196196
| `--toolsets` | Comma-separated list of toolsets to enable. Check the [🛠️ Tools and Functionalities](#tools-and-functionalities) section for more information. |
197197
| `--disable-multi-cluster` | If set, the MCP server will disable multi-cluster support and will only use the current context from the kubeconfig file. This is useful if you want to restrict the MCP server to a single cluster. |
198198

199+
#### Custom Prompts Configuration
200+
201+
The server supports defining custom [MCP prompts](https://modelcontextprotocol.io/docs/concepts/prompts) directly in your configuration file, allowing you to create custom workflows without recompiling. See [docs/PROMPTS.md](docs/PROMPTS.md) for detailed documentation.
202+
203+
**Configuration file example:**
204+
205+
```toml
206+
# Define prompts inline in your config.toml
207+
[[prompts]]
208+
name = "my-custom-prompt"
209+
description = "A custom troubleshooting workflow"
210+
211+
[[prompts.arguments]]
212+
name = "resource_name"
213+
required = true
214+
215+
[[prompts.messages]]
216+
role = "user"
217+
content = "Help me troubleshoot {{resource_name}}"
218+
219+
[[prompts.messages]]
220+
role = "assistant"
221+
content = "I'll investigate {{resource_name}} for you."
222+
223+
# Optionally disable built-in embedded prompts
224+
disable_embedded_prompts = false
225+
```
226+
199227
## 🛠️ Tools and Functionalities <a id="tools-and-functionalities"></a>
200228

201229
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: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
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+
## Available Built-in Prompts
13+
14+
The server provides these prompts in the `core` toolset:
15+
16+
1. **troubleshoot-pod** - Debug failing or crashed pods
17+
2. **deploy-application** - Deploy new applications
18+
3. **scale-deployment** - Scale deployments safely
19+
4. **investigate-cluster-health** - Check overall cluster health
20+
5. **debug-networking** - Debug connectivity issues
21+
6. **review-resource-usage** - Analyze resource consumption
22+
23+
## Creating Custom Prompts
24+
25+
Define custom prompts in your `config.toml` file - no code changes or recompilation needed!
26+
27+
### Basic Example
28+
29+
```toml
30+
[[prompts]]
31+
name = "check-pod-logs"
32+
description = "Quick way to check pod logs"
33+
34+
[[prompts.arguments]]
35+
name = "pod_name"
36+
description = "Name of the pod"
37+
required = true
38+
39+
[[prompts.arguments]]
40+
name = "namespace"
41+
description = "Namespace of the pod"
42+
required = false
43+
44+
[[prompts.messages]]
45+
role = "user"
46+
content = "Show me the logs for pod {{pod_name}} in {{namespace}}"
47+
48+
[[prompts.messages]]
49+
role = "assistant"
50+
content = "I'll retrieve and analyze the logs for you."
51+
```
52+
53+
### Complex Example with Multi-Step Workflow
54+
55+
```toml
56+
[[prompts]]
57+
name = "troubleshoot-deployment"
58+
description = "Comprehensive deployment troubleshooting"
59+
60+
[[prompts.arguments]]
61+
name = "deployment_name"
62+
required = true
63+
64+
[[prompts.arguments]]
65+
name = "namespace"
66+
required = true
67+
68+
[[prompts.messages]]
69+
role = "user"
70+
content = """
71+
My deployment {{deployment_name}} in {{namespace}} is having issues.
72+
Can you investigate?
73+
"""
74+
75+
[[prompts.messages]]
76+
role = "assistant"
77+
content = """
78+
I'll troubleshoot deployment {{deployment_name}}. Let me:
79+
80+
1. **Check Deployment Status**
81+
- Review current vs desired replicas
82+
- Check rollout status
83+
84+
2. **Investigate Pods**
85+
- List pod states
86+
- Review pod events and logs
87+
88+
3. **Analyze Resources**
89+
- Check CPU/memory limits
90+
- Verify quotas
91+
92+
Starting investigation...
93+
"""
94+
```
95+
96+
### Argument Substitution
97+
98+
Use `{{argument_name}}` in message content to insert values:
99+
100+
```toml
101+
[[prompts.messages]]
102+
role = "user"
103+
content = "Check {{resource_type}} named {{resource_name}}"
104+
```
105+
106+
### Overriding Built-in Prompts
107+
108+
Replace built-in prompts by using the same name:
109+
110+
```toml
111+
[[prompts]]
112+
name = "troubleshoot-pod" # This overrides the built-in version
113+
description = "Our custom pod troubleshooting process"
114+
115+
[[prompts.arguments]]
116+
name = "pod_name"
117+
required = true
118+
119+
[[prompts.messages]]
120+
role = "user"
121+
content = "Pod {{pod_name}} needs help"
122+
123+
[[prompts.messages]]
124+
role = "assistant"
125+
content = "Using our custom troubleshooting workflow..."
126+
```
127+
128+
### Disabling Built-in Prompts
129+
130+
Use only your custom prompts:
131+
132+
```toml
133+
# Disable all built-in prompts
134+
disable_embedded_prompts = true
135+
136+
# Then define your own
137+
[[prompts]]
138+
name = "my-prompt"
139+
# ...
140+
```
141+
142+
## For Toolset Developers
143+
144+
If you're creating a custom toolset, define prompts directly in Go code (similar to how tools are defined):
145+
146+
```go
147+
// pkg/toolsets/yourtoolset/prompts.go
148+
package yourtoolset
149+
150+
import (
151+
"fmt"
152+
"github.com/containers/kubernetes-mcp-server/pkg/api"
153+
)
154+
155+
func (t *Toolset) GetPrompts(_ internalk8s.Openshift) []api.ServerPrompt {
156+
return []api.ServerPrompt{
157+
{
158+
Prompt: api.Prompt{
159+
Name: "your-prompt",
160+
Description: "What it does",
161+
Arguments: []api.PromptArgument{
162+
{
163+
Name: "arg1",
164+
Description: "First argument",
165+
Required: true,
166+
},
167+
},
168+
},
169+
Handler: func(params api.PromptHandlerParams) (*api.PromptCallResult, error) {
170+
args := params.GetArguments()
171+
arg1, _ := args["arg1"]
172+
173+
messages := []api.PromptMessage{
174+
{
175+
Role: "user",
176+
Content: api.PromptContent{
177+
Type: "text",
178+
Text: fmt.Sprintf("Message with %s", arg1),
179+
},
180+
},
181+
{
182+
Role: "assistant",
183+
Content: api.PromptContent{
184+
Type: "text",
185+
Text: "Response template",
186+
},
187+
},
188+
}
189+
190+
return api.NewPromptCallResult("What it does", messages, nil), nil
191+
},
192+
},
193+
}
194+
}
195+
```
196+

pkg/api/prompt_loader.go

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
package api
2+
3+
import (
4+
"fmt"
5+
"strings"
6+
7+
"sigs.k8s.io/yaml"
8+
)
9+
10+
// PromptDefinition represents a prompt definition loaded from config
11+
type PromptDefinition struct {
12+
Name string `yaml:"name"`
13+
Description string `yaml:"description"`
14+
Arguments []PromptArgumentDef `yaml:"arguments,omitempty"`
15+
Messages []PromptMessageTemplate `yaml:"messages"`
16+
}
17+
18+
// PromptArgumentDef represents an argument definition
19+
type PromptArgumentDef struct {
20+
Name string `yaml:"name"`
21+
Description string `yaml:"description"`
22+
Required bool `yaml:"required"`
23+
}
24+
25+
// PromptMessageTemplate represents a message template
26+
type PromptMessageTemplate struct {
27+
Role string `yaml:"role"`
28+
Content string `yaml:"content"`
29+
}
30+
31+
// PromptLoader loads prompt definitions from TOML config
32+
type PromptLoader struct {
33+
definitions []PromptDefinition
34+
}
35+
36+
// NewPromptLoader creates a new prompt loader
37+
func NewPromptLoader() *PromptLoader {
38+
return &PromptLoader{
39+
definitions: make([]PromptDefinition, 0),
40+
}
41+
}
42+
43+
// GetServerPrompts converts loaded definitions to ServerPrompt instances
44+
func (l *PromptLoader) GetServerPrompts() []ServerPrompt {
45+
prompts := make([]ServerPrompt, 0, len(l.definitions))
46+
for _, def := range l.definitions {
47+
prompts = append(prompts, l.convertToServerPrompt(def))
48+
}
49+
return prompts
50+
}
51+
52+
// convertToServerPrompt converts a PromptDefinition to a ServerPrompt
53+
func (l *PromptLoader) convertToServerPrompt(def PromptDefinition) ServerPrompt {
54+
arguments := make([]PromptArgument, 0, len(def.Arguments))
55+
for _, arg := range def.Arguments {
56+
arguments = append(arguments, PromptArgument(arg))
57+
}
58+
59+
return ServerPrompt{
60+
Prompt: Prompt{
61+
Name: def.Name,
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+
// LoadFromConfig loads prompts from TOML config structures
111+
func (l *PromptLoader) LoadFromConfig(configs interface{}) error {
112+
// Type assertion to handle the config package types
113+
// We use interface{} here to avoid circular dependency with config package
114+
var defs []PromptDefinition
115+
116+
// Use reflection or type switching to convert config types to PromptDefinition
117+
// This is a simple implementation that works with the expected structure
118+
data, err := convertToYAML(configs)
119+
if err != nil {
120+
return fmt.Errorf("failed to convert config to YAML: %w", err)
121+
}
122+
123+
if err := yaml.Unmarshal(data, &defs); err != nil {
124+
return fmt.Errorf("failed to parse prompt config: %w", err)
125+
}
126+
127+
l.definitions = append(l.definitions, defs...)
128+
return nil
129+
}
130+
131+
// convertToYAML converts config structs to YAML bytes for uniform processing
132+
func convertToYAML(v interface{}) ([]byte, error) {
133+
return yaml.Marshal(v)
134+
}

0 commit comments

Comments
 (0)