Skip to content

[API] Toolset-defined prompt templates #556

@manusa

Description

@manusa

Description

As a toolset implementer, I want to be able to define MCP prompts as part of my toolset definition, so that I can provide workflow templates that are bundled with the toolset's functionality.

Background

The Toolset interface in pkg/api/toolsets.go currently allows implementers to define tools via GetTools(). This issue extends the interface to also support defining prompts via a new GetPrompts() method.

This builds on the infrastructure established in #226, which adds MCP prompt support to the configuration module. The Prompt struct defined there will be extended to support both static and dynamic message generation.

Expected Behavior

Toolset implementers can define prompts by implementing the GetPrompts() method on their toolset:

type Toolset interface {
    GetName() string
    GetDescription() string
    GetTools(o internalk8s.Openshift) []ServerTool
    // GetPrompts returns the prompts provided by this toolset.
    // Returns nil or empty slice if the toolset doesn't provide any prompts.
    GetPrompts() []Prompt
}

Static vs Dynamic Messages

The Prompt struct supports two approaches for generating messages:

type Prompt struct {
    Name        string           `json:"name" toml:"name"`
    Title       string           `json:"title,omitempty" toml:"title,omitempty"`
    Description string           `json:"description,omitempty" toml:"description,omitempty"`
    Arguments   []PromptArgument `json:"arguments,omitempty" toml:"arguments,omitempty"`
    
    // Static messages - used for config-defined prompts with {{argument}} substitution
    Messages    []PromptMessage  `json:"messages,omitempty" toml:"messages,omitempty"`
    
    // Dynamic messages - used for toolset-defined prompts that need programmatic control
    // If set, takes precedence over Messages
    GetMessages func(arguments map[string]string) []PromptMessage `json:"-" toml:"-"`
}

When prompts/get is called:

  • If GetMessages is set, call it with the arguments
  • Otherwise, use Messages with {{argument}} substitution

Static Messages Example

For simple prompts with template substitution:

func (t *MyToolset) GetPrompts() []api.Prompt {
    return []api.Prompt{
        {
            Name:        "simple-workflow",
            Title:       "Simple Workflow",
            Description: "A simple workflow with template substitution",
            Arguments: []api.PromptArgument{
                {Name: "resource_name", Description: "Name of the resource", Required: true},
            },
            Messages: []api.PromptMessage{
                {Role: "user", Content: "Help me with {{resource_name}}"},
            },
        },
    }
}

Dynamic Messages Example

For complex prompts that need programmatic control (conditional sections, formatted output, etc.):

func (t *MyToolset) GetPrompts() []api.Prompt {
    return []api.Prompt{
        {
            Name:        "cluster-health-check",
            Title:       "Cluster Health Check",
            Description: "Comprehensive cluster health diagnostics",
            Arguments: []api.PromptArgument{
                {Name: "verbose", Description: "Enable detailed output", Required: false},
                {Name: "namespace", Description: "Limit to specific namespace", Required: false},
                {Name: "output_format", Description: "Output format: text or json", Required: false},
            },
            GetMessages: func(arguments map[string]string) []api.PromptMessage {
                verbose := arguments["verbose"] == "true"
                namespace := arguments["namespace"]
                outputFormat := arguments["output_format"]
                if outputFormat == "" {
                    outputFormat = "text"
                }
                
                // Build messages dynamically based on arguments
                content := buildHealthCheckInstructions(verbose, namespace, outputFormat)
                return []api.PromptMessage{
                    {Role: "user", Content: content},
                }
            },
        },
    }
}

The MCP server collects prompts from all enabled toolsets and exposes them via prompts/list and prompts/get.

Disabling Toolset Prompts

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

disable_toolset_prompts = true

Scope

In scope:

  • Add GetPrompts() []Prompt to the Toolset interface
  • Extend Prompt struct to support both Messages (static) and GetMessages (dynamic)
  • Update existing toolsets to return nil or empty slice from GetPrompts()
  • Update MCP server to collect and register prompts from enabled toolsets
  • Merge toolset prompts with config-defined prompts (config takes precedence for same name)
  • Add disable_toolset_prompts configuration option

Out of scope:

  • Built-in/embedded prompts shipped with the server (separate issue)
  • System prompts support

Related

Acceptance Criteria

  • Toolset interface includes GetPrompts() []Prompt method
  • Prompt struct supports both Messages and GetMessages
  • GetMessages takes precedence over Messages when both are set
  • Existing toolsets are updated to implement GetPrompts() (returning nil or empty)
  • MCP server collects prompts from all enabled toolsets
  • Config-defined prompts override toolset prompts with the same name
  • Prompts from disabled toolsets are not exposed
  • disable_toolset_prompts configuration option disables all toolset-defined prompts

Tests

  • Test toolset returning prompts with static messages
  • Test toolset returning prompts with dynamic messages (GetMessages)
  • Test GetMessages takes precedence over Messages
  • Test toolset returning nil/empty prompts
  • Test prompt collection from multiple toolsets
  • Test config prompts overriding toolset prompts with same name
  • Test prompts not exposed when toolset is disabled
  • Test disable_toolset_prompts configuration option

Metadata

Metadata

Assignees

Labels

enhancementNew feature or requestgoPull requests that update go code

Type

No type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions