Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -294,7 +294,7 @@ pkill -HUP kubernetes-mcp-server

### MCP Prompts

The server supports MCP prompts for workflow templates. Define custom prompts in `config.toml`:
1. The server supports MCP prompts for workflow templates. Define custom prompts in `config.toml`:

```toml
[[prompts]]
Expand All @@ -311,6 +311,8 @@ role = "user"
content = "Help me with {{resource_name}}"
```

2. Toolset prompts implemented by toolset developers (can be disabled with disable_toolset_prompts = true)

See docs/PROMPTS.md for detailed documentation.

## 🛠️ Tools and Functionalities <a id="tools-and-functionalities"></a>
Expand Down
64 changes: 63 additions & 1 deletion docs/PROMPTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,4 +59,66 @@ Use `{{argument_name}}` placeholders in message content. The template engine rep

## Configuration File Location

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.
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.

## Toolset Prompts

Toolsets can provide built-in prompts by implementing the `GetPrompts()` method. This allows toolset developers to ship workflow templates alongside their tools.

### Implementing Toolset Prompts

```go
func (t *MyToolset) GetPrompts(o internalk8s.Openshift) []api.ServerPrompt {
return []api.ServerPrompt{
{
Prompt: api.Prompt{
Name: "my-workflow",
Description: "Custom workflow for my toolset",
Arguments: []api.PromptArgument{
{
Name: "namespace",
Description: "Target namespace",
Required: true,
},
},
},
Handler: func(params api.PromptHandlerParams) (*api.PromptCallResult, error) {
args := params.GetArguments()
namespace := args["namespace"]

// Build messages dynamically based on arguments
messages := []api.PromptMessage{
{
Role: "user",
Content: api.PromptContent{
Type: "text",
Text: fmt.Sprintf("Help me with namespace: %s", namespace),
},
},
}

return api.NewPromptCallResult("Workflow description", messages, nil), nil
},
},
}
}
```

### Disabling Toolset Prompts

Add `disable_toolset_prompts = true` in your `config.toml` to use only config-defined prompts:

```toml
disable_toolset_prompts = true
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@nader-ziada will this be a global option, or a per-toolset option?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

global option currently

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe I included this in #556

Disabling Toolset Prompts

Administrators can disable all toolset-defined prompts via configuration, using only config-defined prompts:

based on what Nader initially implemented in #510

IMO maybe we SHOULD NOT provide this option at all (prompts don't have any involuntary impact on the LLM behavior or consume context-window tokens)

OR if you consider this of value, provide a consistent implementation with what we have for tools (EnabledTools / DisabledTools).

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for the sake of simplicity, will remove the flag completely


[[prompts]]
name = "my-custom-prompt"
# ... rest of config
```

### Prompt Merging

When both toolset and config prompts exist:
- Config-defined prompts **override** toolset prompts with the same name
- This allows administrators to customize built-in workflows
- Prompts with unique names from both sources are available
3 changes: 3 additions & 0 deletions pkg/api/toolsets.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ type Toolset interface {
// Will be used to generate documentation and help text.
GetDescription() string
GetTools(o Openshift) []ServerTool
// GetPrompts returns the prompts provided by this toolset.
// Returns nil if the toolset doesn't provide any prompts.
GetPrompts(o Openshift) []ServerPrompt
}

type ToolCallRequest interface {
Expand Down
3 changes: 3 additions & 0 deletions pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,9 @@ type StaticConfig struct {
promptsDefined bool // Internal: tracks if prompts were defined in config
promptsMetadata toml.MetaData // Internal: metadata for prompts decoding

// When true, disable toolset-defined prompts (only use config-defined prompts)
DisableToolsetPrompts bool `toml:"disable_toolset_prompts,omitempty"`

// Server instructions to be provided by the MCP server to the MCP client
// This can be used to provide specific instructions on how the client should use the server
ServerInstructions string `toml:"server_instructions,omitempty"`
Expand Down
27 changes: 24 additions & 3 deletions pkg/mcp/mcp.go
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,24 @@ func (s *Server) reloadToolsets() error {
// Track previously enabled prompts
previousPrompts := s.enabledPrompts

// Build and register prompts from all toolsets
applicablePrompts := make([]api.ServerPrompt, 0)
s.enabledPrompts = make([]string, 0)

// Load embedded toolset prompts (unless disabled)
if !s.configuration.DisableToolsetPrompts {
for _, toolset := range s.configuration.Toolsets() {
prompts := toolset.GetPrompts(s.p)
if prompts == nil {
continue
}
for _, prompt := range prompts {
applicablePrompts = append(applicablePrompts, prompt)
s.enabledPrompts = append(s.enabledPrompts, prompt.Prompt.Name)
}
}
}

// Load config prompts into registry
prompts.Clear()
if s.configuration.HasPrompts() {
Expand All @@ -180,9 +198,12 @@ func (s *Server) reloadToolsets() error {
// Get prompts from registry
configPrompts := prompts.ConfigPrompts()

// Merge: config prompts override embedded prompts with same name
applicablePrompts = mergePrompts(applicablePrompts, configPrompts)

// Update enabled prompts list
s.enabledPrompts = make([]string, 0)
for _, prompt := range configPrompts {
for _, prompt := range applicablePrompts {
s.enabledPrompts = append(s.enabledPrompts, prompt.Prompt.Name)
}

Expand All @@ -195,8 +216,8 @@ func (s *Server) reloadToolsets() error {
}
s.server.RemovePrompts(promptsToRemove...)

// Register all config prompts
for _, prompt := range configPrompts {
// Register all applicable prompts
for _, prompt := range applicablePrompts {
mcpPrompt, promptHandler, err := ServerPromptToGoSdkPrompt(s, prompt)
if err != nil {
return fmt.Errorf("failed to convert prompt %s: %v", prompt.Prompt.Name, err)
Expand Down
Loading