Skip to content

Commit c8bb280

Browse files
committed
add toolset prompts infrastructure
Enable toolset implementers to define MCP prompts programmatically as part of their toolset definition. - Add GetPrompts() method to Toolset interface - Collect prompts from all enabled toolsets - Merge config and toolset prompts (config takes precedence) - Add disable_toolset_prompts configuration option - Update all existing toolsets to implement GetPrompts() Signed-off-by: Nader Ziada <[email protected]>
1 parent 4f0ed3e commit c8bb280

File tree

12 files changed

+575
-5
lines changed

12 files changed

+575
-5
lines changed

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -294,7 +294,7 @@ pkill -HUP kubernetes-mcp-server
294294

295295
### MCP Prompts
296296

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

299299
```toml
300300
[[prompts]]
@@ -311,6 +311,8 @@ role = "user"
311311
content = "Help me with {{resource_name}}"
312312
```
313313

314+
2. Toolset prompts implemented by toolset developers (can be disabled with disable_toolset_prompts = true)
315+
314316
See docs/PROMPTS.md for detailed documentation.
315317

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

docs/PROMPTS.md

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,4 +59,66 @@ Use `{{argument_name}}` placeholders in message content. The template engine rep
5959

6060
## Configuration File Location
6161

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.
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.
63+
64+
## Toolset Prompts
65+
66+
Toolsets can provide built-in prompts by implementing the `GetPrompts()` method. This allows toolset developers to ship workflow templates alongside their tools.
67+
68+
### Implementing Toolset Prompts
69+
70+
```go
71+
func (t *MyToolset) GetPrompts(o internalk8s.Openshift) []api.ServerPrompt {
72+
return []api.ServerPrompt{
73+
{
74+
Prompt: api.Prompt{
75+
Name: "my-workflow",
76+
Description: "Custom workflow for my toolset",
77+
Arguments: []api.PromptArgument{
78+
{
79+
Name: "namespace",
80+
Description: "Target namespace",
81+
Required: true,
82+
},
83+
},
84+
},
85+
Handler: func(params api.PromptHandlerParams) (*api.PromptCallResult, error) {
86+
args := params.GetArguments()
87+
namespace := args["namespace"]
88+
89+
// Build messages dynamically based on arguments
90+
messages := []api.PromptMessage{
91+
{
92+
Role: "user",
93+
Content: api.PromptContent{
94+
Type: "text",
95+
Text: fmt.Sprintf("Help me with namespace: %s", namespace),
96+
},
97+
},
98+
}
99+
100+
return api.NewPromptCallResult("Workflow description", messages, nil), nil
101+
},
102+
},
103+
}
104+
}
105+
```
106+
107+
### Disabling Toolset Prompts
108+
109+
Add `disable_toolset_prompts = true` in your `config.toml` to use only config-defined prompts:
110+
111+
```toml
112+
disable_toolset_prompts = true
113+
114+
[[prompts]]
115+
name = "my-custom-prompt"
116+
# ... rest of config
117+
```
118+
119+
### Prompt Merging
120+
121+
When both toolset and config prompts exist:
122+
- Config-defined prompts **override** toolset prompts with the same name
123+
- This allows administrators to customize built-in workflows
124+
- Prompts with unique names from both sources are available

pkg/api/toolsets.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,9 @@ type Toolset interface {
4343
// Will be used to generate documentation and help text.
4444
GetDescription() string
4545
GetTools(o Openshift) []ServerTool
46+
// GetPrompts returns the prompts provided by this toolset.
47+
// Returns nil if the toolset doesn't provide any prompts.
48+
GetPrompts(o Openshift) []ServerPrompt
4649
}
4750

4851
type ToolCallRequest interface {

pkg/config/config.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,9 @@ type StaticConfig struct {
8181
promptsDefined bool // Internal: tracks if prompts were defined in config
8282
promptsMetadata toml.MetaData // Internal: metadata for prompts decoding
8383

84+
// When true, disable toolset-defined prompts (only use config-defined prompts)
85+
DisableToolsetPrompts bool `toml:"disable_toolset_prompts,omitempty"`
86+
8487
// Server instructions to be provided by the MCP server to the MCP client
8588
// This can be used to provide specific instructions on how the client should use the server
8689
ServerInstructions string `toml:"server_instructions,omitempty"`

pkg/mcp/mcp.go

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,24 @@ func (s *Server) reloadToolsets() error {
167167
// Track previously enabled prompts
168168
previousPrompts := s.enabledPrompts
169169

170+
// Build and register prompts from all toolsets
171+
applicablePrompts := make([]api.ServerPrompt, 0)
172+
s.enabledPrompts = make([]string, 0)
173+
174+
// Load embedded toolset prompts (unless disabled)
175+
if !s.configuration.DisableToolsetPrompts {
176+
for _, toolset := range s.configuration.Toolsets() {
177+
prompts := toolset.GetPrompts(s.p)
178+
if prompts == nil {
179+
continue
180+
}
181+
for _, prompt := range prompts {
182+
applicablePrompts = append(applicablePrompts, prompt)
183+
s.enabledPrompts = append(s.enabledPrompts, prompt.Prompt.Name)
184+
}
185+
}
186+
}
187+
170188
// Load config prompts into registry
171189
prompts.Clear()
172190
if s.configuration.HasPrompts() {
@@ -180,9 +198,12 @@ func (s *Server) reloadToolsets() error {
180198
// Get prompts from registry
181199
configPrompts := prompts.ConfigPrompts()
182200

201+
// Merge: config prompts override embedded prompts with same name
202+
applicablePrompts = mergePrompts(applicablePrompts, configPrompts)
203+
183204
// Update enabled prompts list
184205
s.enabledPrompts = make([]string, 0)
185-
for _, prompt := range configPrompts {
206+
for _, prompt := range applicablePrompts {
186207
s.enabledPrompts = append(s.enabledPrompts, prompt.Prompt.Name)
187208
}
188209

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

198-
// Register all config prompts
199-
for _, prompt := range configPrompts {
219+
// Register all applicable prompts
220+
for _, prompt := range applicablePrompts {
200221
mcpPrompt, promptHandler, err := ServerPromptToGoSdkPrompt(s, prompt)
201222
if err != nil {
202223
return fmt.Errorf("failed to convert prompt %s: %v", prompt.Prompt.Name, err)

0 commit comments

Comments
 (0)