Skip to content

Commit 691a239

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 5623684 commit 691a239

File tree

12 files changed

+576
-5
lines changed

12 files changed

+576
-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
@@ -44,6 +44,9 @@ type Toolset interface {
4444
// Will be used to generate documentation and help text.
4545
GetDescription() string
4646
GetTools(o internalk8s.Openshift) []ServerTool
47+
// GetPrompts returns the prompts provided by this toolset.
48+
// Returns nil if the toolset doesn't provide any prompts.
49+
GetPrompts(o internalk8s.Openshift) []ServerPrompt
4750
}
4851

4952
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
// Internal: parsed provider configs (not exposed to TOML package)
8588
parsedClusterProviderConfigs map[string]configapi.Extended
8689
// Internal: parsed toolset configs (not exposed to TOML package)

pkg/mcp/mcp.go

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

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

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

@@ -194,8 +215,8 @@ func (s *Server) reloadToolsets() error {
194215
}
195216
s.server.RemovePrompts(promptsToRemove...)
196217

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

0 commit comments

Comments
 (0)