diff --git a/pkg/mcp/mcp.go b/pkg/mcp/mcp.go index fe912028fe..e91dc54025 100644 --- a/pkg/mcp/mcp.go +++ b/pkg/mcp/mcp.go @@ -152,6 +152,10 @@ func New(options ...Option) *Server { i.AddResource(newHelpResource(s, "Envs Add Help", "help for 'config envs add'", "config", "envs", "add")) i.AddResource(newHelpResource(s, "Envs Remove Help", "help for 'config envs remove'", "config", "envs", "remove")) + // Prompts + // ------- + i.AddPrompt(funcWorkflowPrompt, funcWorkflowHandler) + s.impl = i return s diff --git a/pkg/mcp/prompts.go b/pkg/mcp/prompts.go new file mode 100644 index 0000000000..8cc5a940f1 --- /dev/null +++ b/pkg/mcp/prompts.go @@ -0,0 +1,53 @@ +package mcp + +import ( + "context" + "fmt" + + "github.com/modelcontextprotocol/go-sdk/mcp" +) + +var funcWorkflowPrompt = &mcp.Prompt{ + Name: "func-workflow", + Title: "Function Lifecycle Workflow", + Description: "Guides through the full Function lifecycle: create, build, and deploy.", + Arguments: []*mcp.PromptArgument{ + { + Name: "language", + Description: "Programming language for the Function (e.g. go, python, node).", + }, + }, +} + +func funcWorkflowHandler(_ context.Context, req *mcp.GetPromptRequest) (*mcp.GetPromptResult, error) { + lang := req.Params.Arguments["language"] + return &mcp.GetPromptResult{ + Description: "Step-by-step guide for the Function lifecycle.", + Messages: []*mcp.PromptMessage{ + { + Role: "user", + Content: &mcp.TextContent{Text: funcWorkflowText(lang)}, + }, + }, + }, nil +} + +func funcWorkflowText(language string) string { + langLine := "" + if language != "" { + langLine = fmt.Sprintf("\nUse language: %s\n", language) + } + return fmt.Sprintf(`Guide me through the full Function lifecycle.%s +Step 1 - Create: + Use the "create" tool to scaffold a new Function. + Check "func://languages" for available languages and "func://templates" for templates. + +Step 2 - Build: + Use the "build" tool to compile the Function into a container image. + +Step 3 - Deploy: + Use the "deploy" tool to deploy the Function to the cluster. + On first deploy, a container registry is required (e.g. docker.io/user). + +Use "func://function" to check current Function state at any point.`, langLine) +} diff --git a/pkg/mcp/prompts_test.go b/pkg/mcp/prompts_test.go new file mode 100644 index 0000000000..105fb5f5ea --- /dev/null +++ b/pkg/mcp/prompts_test.go @@ -0,0 +1,92 @@ +package mcp + +import ( + "strings" + "testing" + + "github.com/modelcontextprotocol/go-sdk/mcp" +) + +func TestPrompt_Listed(t *testing.T) { + client, _, err := newTestPair(t) + if err != nil { + t.Fatal(err) + } + + result, err := client.ListPrompts(t.Context(), nil) + if err != nil { + t.Fatal(err) + } + + var found bool + for _, p := range result.Prompts { + if p.Name == "func-workflow" { + found = true + if p.Description == "" { + t.Error("expected non-empty description") + } + break + } + } + if !found { + t.Fatal("func-workflow prompt not found in listing") + } +} + +func TestPrompt_Get(t *testing.T) { + client, _, err := newTestPair(t) + if err != nil { + t.Fatal(err) + } + + result, err := client.GetPrompt(t.Context(), &mcp.GetPromptParams{ + Name: "func-workflow", + }) + if err != nil { + t.Fatal(err) + } + + if len(result.Messages) != 1 { + t.Fatalf("expected 1 message, got %d", len(result.Messages)) + } + msg := result.Messages[0] + if msg.Role != "user" { + t.Fatalf("expected role 'user', got %q", msg.Role) + } + tc, ok := msg.Content.(*mcp.TextContent) + if !ok { + t.Fatalf("expected TextContent, got %T", msg.Content) + } + if !strings.Contains(tc.Text, "create") { + t.Error("expected prompt text to mention create") + } + if !strings.Contains(tc.Text, "deploy") { + t.Error("expected prompt text to mention deploy") + } +} + +func TestPrompt_GetWithLanguage(t *testing.T) { + client, _, err := newTestPair(t) + if err != nil { + t.Fatal(err) + } + + result, err := client.GetPrompt(t.Context(), &mcp.GetPromptParams{ + Name: "func-workflow", + Arguments: map[string]string{"language": "go"}, + }) + if err != nil { + t.Fatal(err) + } + + if len(result.Messages) != 1 { + t.Fatalf("expected 1 message, got %d", len(result.Messages)) + } + tc, ok := result.Messages[0].Content.(*mcp.TextContent) + if !ok { + t.Fatalf("expected TextContent, got %T", result.Messages[0].Content) + } + if !strings.Contains(tc.Text, "go") { + t.Error("expected prompt text to contain the language 'go'") + } +}