Skip to content
Open
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
28 changes: 28 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,34 @@ uvx kubernetes-mcp-server@latest --help
| `--toolsets` | Comma-separated list of toolsets to enable. Check the [🛠️ Tools and Functionalities](#tools-and-functionalities) section for more information. |
| `--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. |

#### Custom Prompts Configuration

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.

**Configuration file example:**

```toml
# Define prompts inline in your config.toml
[[prompts]]
name = "my-custom-prompt"
description = "A custom troubleshooting workflow"

[[prompts.arguments]]
name = "resource_name"
required = true

[[prompts.messages]]
role = "user"
content = "Help me troubleshoot {{resource_name}}"

[[prompts.messages]]
role = "assistant"
content = "I'll investigate {{resource_name}} for you."

# Optionally disable built-in embedded prompts
disable_embedded_prompts = false
```

## 🛠️ Tools and Functionalities <a id="tools-and-functionalities"></a>

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.
Expand Down
196 changes: 196 additions & 0 deletions docs/PROMPTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
# MCP Prompts Support

The Kubernetes MCP Server supports [MCP Prompts](https://modelcontextprotocol.io/docs/concepts/prompts), which provide pre-defined workflow templates and guidance to AI assistants.

## What are MCP Prompts?

MCP Prompts are pre-defined templates that guide AI assistants through specific workflows. They combine:
- **Structured guidance**: Step-by-step instructions for common tasks
- **Parameterization**: Arguments that customize the prompt for specific contexts
- **Conversation templates**: Pre-formatted messages that guide the interaction

## Available Built-in Prompts

The server provides these prompts in the `core` toolset:

1. **troubleshoot-pod** - Debug failing or crashed pods
2. **deploy-application** - Deploy new applications
3. **scale-deployment** - Scale deployments safely
4. **investigate-cluster-health** - Check overall cluster health
5. **debug-networking** - Debug connectivity issues
6. **review-resource-usage** - Analyze resource consumption

## Creating Custom Prompts

Define custom prompts in your `config.toml` file - no code changes or recompilation needed!

### Basic Example

```toml
[[prompts]]
name = "check-pod-logs"
description = "Quick way to check pod logs"

[[prompts.arguments]]
name = "pod_name"
description = "Name of the pod"
required = true

[[prompts.arguments]]
name = "namespace"
description = "Namespace of the pod"
required = false

[[prompts.messages]]
role = "user"
content = "Show me the logs for pod {{pod_name}} in {{namespace}}"

[[prompts.messages]]
role = "assistant"
content = "I'll retrieve and analyze the logs for you."
```

### Complex Example with Multi-Step Workflow

```toml
[[prompts]]
name = "troubleshoot-deployment"
description = "Comprehensive deployment troubleshooting"

[[prompts.arguments]]
name = "deployment_name"
required = true

[[prompts.arguments]]
name = "namespace"
required = true

[[prompts.messages]]
role = "user"
content = """
My deployment {{deployment_name}} in {{namespace}} is having issues.
Can you investigate?
"""

[[prompts.messages]]
role = "assistant"
content = """
I'll troubleshoot deployment {{deployment_name}}. Let me:

1. **Check Deployment Status**
- Review current vs desired replicas
- Check rollout status

2. **Investigate Pods**
- List pod states
- Review pod events and logs

3. **Analyze Resources**
- Check CPU/memory limits
- Verify quotas

Starting investigation...
"""
```

### Argument Substitution

Use `{{argument_name}}` in message content to insert values:

```toml
[[prompts.messages]]
role = "user"
content = "Check {{resource_type}} named {{resource_name}}"
```

### Overriding Built-in Prompts

Replace built-in prompts by using the same name:

```toml
[[prompts]]
name = "troubleshoot-pod" # This overrides the built-in version
description = "Our custom pod troubleshooting process"

[[prompts.arguments]]
name = "pod_name"
required = true

[[prompts.messages]]
role = "user"
content = "Pod {{pod_name}} needs help"

[[prompts.messages]]
role = "assistant"
content = "Using our custom troubleshooting workflow..."
```

### Disabling Built-in Prompts

Use only your custom prompts:

```toml
# Disable all built-in prompts
disable_embedded_prompts = true

# Then define your own
[[prompts]]
name = "my-prompt"
# ...
```

## For Toolset Developers

If you're creating a custom toolset, define prompts directly in Go code (similar to how tools are defined):

```go
// pkg/toolsets/yourtoolset/prompts.go
package yourtoolset

import (
"fmt"
"github.com/containers/kubernetes-mcp-server/pkg/api"
)

func (t *Toolset) GetPrompts(_ internalk8s.Openshift) []api.ServerPrompt {
return []api.ServerPrompt{
{
Prompt: api.Prompt{
Name: "your-prompt",
Description: "What it does",
Arguments: []api.PromptArgument{
{
Name: "arg1",
Description: "First argument",
Required: true,
},
},
},
Handler: func(params api.PromptHandlerParams) (*api.PromptCallResult, error) {
args := params.GetArguments()
arg1, _ := args["arg1"]

messages := []api.PromptMessage{
{
Role: "user",
Content: api.PromptContent{
Type: "text",
Text: fmt.Sprintf("Message with %s", arg1),
},
},
{
Role: "assistant",
Content: api.PromptContent{
Type: "text",
Text: "Response template",
},
},
}

return api.NewPromptCallResult("What it does", messages, nil), nil
},
},
}
}
```

134 changes: 134 additions & 0 deletions pkg/api/prompt_loader.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
package api

import (
"fmt"
"strings"

"sigs.k8s.io/yaml"
)

// PromptDefinition represents a prompt definition loaded from config
type PromptDefinition struct {
Name string `yaml:"name"`
Description string `yaml:"description"`
Arguments []PromptArgumentDef `yaml:"arguments,omitempty"`
Messages []PromptMessageTemplate `yaml:"messages"`
}

// PromptArgumentDef represents an argument definition
type PromptArgumentDef struct {
Name string `yaml:"name"`
Description string `yaml:"description"`
Required bool `yaml:"required"`
}

// PromptMessageTemplate represents a message template
type PromptMessageTemplate struct {
Role string `yaml:"role"`
Content string `yaml:"content"`
}

// PromptLoader loads prompt definitions from TOML config
type PromptLoader struct {
definitions []PromptDefinition
}

// NewPromptLoader creates a new prompt loader
func NewPromptLoader() *PromptLoader {
return &PromptLoader{
definitions: make([]PromptDefinition, 0),
}
}

// GetServerPrompts converts loaded definitions to ServerPrompt instances
func (l *PromptLoader) GetServerPrompts() []ServerPrompt {
prompts := make([]ServerPrompt, 0, len(l.definitions))
for _, def := range l.definitions {
prompts = append(prompts, l.convertToServerPrompt(def))
}
return prompts
}

// convertToServerPrompt converts a PromptDefinition to a ServerPrompt
func (l *PromptLoader) convertToServerPrompt(def PromptDefinition) ServerPrompt {
arguments := make([]PromptArgument, 0, len(def.Arguments))
for _, arg := range def.Arguments {
arguments = append(arguments, PromptArgument(arg))
}

return ServerPrompt{
Prompt: Prompt{
Name: def.Name,
Description: def.Description,
Arguments: arguments,
},
Handler: l.createHandler(def),
}
}

// createHandler creates a prompt handler function for a prompt definition
func (l *PromptLoader) createHandler(def PromptDefinition) PromptHandlerFunc {
return func(params PromptHandlerParams) (*PromptCallResult, error) {
args := params.GetArguments()

// Validate required arguments
for _, argDef := range def.Arguments {
if argDef.Required {
if _, exists := args[argDef.Name]; !exists {
return nil, fmt.Errorf("required argument '%s' is missing", argDef.Name)
}
}
}

// Render messages with argument substitution
messages := make([]PromptMessage, 0, len(def.Messages))
for _, msgTemplate := range def.Messages {
content := l.substituteArguments(msgTemplate.Content, args)
messages = append(messages, PromptMessage{
Role: msgTemplate.Role,
Content: PromptContent{
Type: "text",
Text: content,
},
})
}

return NewPromptCallResult(def.Description, messages, nil), nil
}
}

// substituteArguments replaces {{argument}} placeholders in content with actual values
func (l *PromptLoader) substituteArguments(content string, args map[string]string) string {
result := content
for key, value := range args {
placeholder := fmt.Sprintf("{{%s}}", key)
result = strings.ReplaceAll(result, placeholder, value)
}
return result
}

// LoadFromConfig loads prompts from TOML config structures
func (l *PromptLoader) LoadFromConfig(configs interface{}) error {
// Type assertion to handle the config package types
// We use interface{} here to avoid circular dependency with config package
var defs []PromptDefinition

// Use reflection or type switching to convert config types to PromptDefinition
// This is a simple implementation that works with the expected structure
data, err := convertToYAML(configs)
if err != nil {
return fmt.Errorf("failed to convert config to YAML: %w", err)
}

if err := yaml.Unmarshal(data, &defs); err != nil {
return fmt.Errorf("failed to parse prompt config: %w", err)
}

l.definitions = append(l.definitions, defs...)
return nil
}

// convertToYAML converts config structs to YAML bytes for uniform processing
func convertToYAML(v interface{}) ([]byte, error) {
return yaml.Marshal(v)
}
Loading