diff --git a/pkg/github/issues.go b/pkg/github/issues.go index ea068ed00..b4c64c8de 100644 --- a/pkg/github/issues.go +++ b/pkg/github/issues.go @@ -931,3 +931,42 @@ func parseISOTimestamp(timestamp string) (time.Time, error) { // Return error with supported formats return time.Time{}, fmt.Errorf("invalid ISO 8601 timestamp: %s (supported formats: YYYY-MM-DDThh:mm:ssZ or YYYY-MM-DD)", timestamp) } + +func AssignCodingAgentPrompt(t translations.TranslationHelperFunc) (tool mcp.Prompt, handler server.PromptHandlerFunc) { + return mcp.NewPrompt("AssignCodingAgent", + mcp.WithPromptDescription(t("PROMPT_ASSIGN_CODING_AGENT_DESCRIPTION", "Assign GitHub Coding Agent to multiple tasks in a GitHub repository.")), + mcp.WithArgument("repo", mcp.ArgumentDescription("The repository to assign tasks in (owner/repo)."), mcp.RequiredArgument()), + ), func(ctx context.Context, request mcp.GetPromptRequest) (*mcp.GetPromptResult, error) { + repo := request.Params.Arguments["repo"] + + messages := []mcp.PromptMessage{ + { + Role: "system", + Content: mcp.NewTextContent("You are a personal assistant for GitHub the Copilot GitHub Coding Agent. Your task is to help the user assign tasks to the Coding Agent based on their open GitHub issues. You can use `assign_copilot_to_issue` tool to assign the Coding Agent to issues that are suitable for autonomous work, and `search_issues` tool to find issues that match the user's criteria. You can also use `list_issues` to get a list of issues in the repository."), + }, + { + Role: "user", + Content: mcp.NewTextContent(fmt.Sprintf("Please go and get a list of the most recent 10 issues from the %s GitHub repository", repo)), + }, + { + Role: "assistant", + Content: mcp.NewTextContent(fmt.Sprintf("Sure! I will get a list of the 10 most recent issues for the repo %s.", repo)), + }, + { + Role: "user", + Content: mcp.NewTextContent("For each issue, please check if it is a clearly defined coding task with acceptance criteria and a low to medium complexity to identify issues that are suitable for an AI Coding Agent to work on. Then assign each of the identified issues to Copilot."), + }, + { + Role: "assistant", + Content: mcp.NewTextContent("Certainly! Let me carefully check which ones are clearly scoped issues that are good to assign to the coding agent, and I will summarize and assign them now."), + }, + { + Role: "user", + Content: mcp.NewTextContent("Great, if you are unsure if an issue is good to assign, ask me first, rather than assigning copilot. If you are certain the issue is clear and suitable you can assign it to Copilot without asking."), + }, + } + return &mcp.GetPromptResult{ + Messages: messages, + }, nil + } +} diff --git a/pkg/github/tools.go b/pkg/github/tools.go index ba540d227..5b970698c 100644 --- a/pkg/github/tools.go +++ b/pkg/github/tools.go @@ -59,7 +59,7 @@ func DefaultToolsetGroup(readOnly bool, getClient GetClientFn, getGQLClient GetG toolsets.NewServerTool(AddIssueComment(getClient, t)), toolsets.NewServerTool(UpdateIssue(getClient, t)), toolsets.NewServerTool(AssignCopilotToIssue(getGQLClient, t)), - ) + ).AddPrompts(toolsets.NewServerPrompt(AssignCodingAgentPrompt(t))) users := toolsets.NewToolset("users", "GitHub User related tools"). AddReadTools( toolsets.NewServerTool(SearchUsers(getClient, t)), diff --git a/pkg/toolsets/toolsets.go b/pkg/toolsets/toolsets.go index ad444c050..5d503b742 100644 --- a/pkg/toolsets/toolsets.go +++ b/pkg/toolsets/toolsets.go @@ -40,12 +40,25 @@ func NewServerResourceTemplate(resourceTemplate mcp.ResourceTemplate, handler se } } +func NewServerPrompt(prompt mcp.Prompt, handler server.PromptHandlerFunc) ServerPrompt { + return ServerPrompt{ + Prompt: prompt, + Handler: handler, + } +} + // ServerResourceTemplate represents a resource template that can be registered with the MCP server. type ServerResourceTemplate struct { resourceTemplate mcp.ResourceTemplate handler server.ResourceTemplateHandlerFunc } +// ServerPrompt represents a prompt that can be registered with the MCP server. +type ServerPrompt struct { + Prompt mcp.Prompt + Handler server.PromptHandlerFunc +} + // Toolset represents a collection of MCP functionality that can be enabled or disabled as a group. type Toolset struct { Name string @@ -57,6 +70,8 @@ type Toolset struct { // resources are not tools, but the community seems to be moving towards namespaces as a broader concept // and in order to have multiple servers running concurrently, we want to avoid overlapping resources too. resourceTemplates []ServerResourceTemplate + // prompts are also not tools but are namespaced similarly + prompts []ServerPrompt } func (t *Toolset) GetActiveTools() []server.ServerTool { @@ -95,6 +110,11 @@ func (t *Toolset) AddResourceTemplates(templates ...ServerResourceTemplate) *Too return t } +func (t *Toolset) AddPrompts(prompts ...ServerPrompt) *Toolset { + t.prompts = append(t.prompts, prompts...) + return t +} + func (t *Toolset) GetActiveResourceTemplates() []ServerResourceTemplate { if !t.Enabled { return nil @@ -115,6 +135,15 @@ func (t *Toolset) RegisterResourcesTemplates(s *server.MCPServer) { } } +func (t *Toolset) RegisterPrompts(s *server.MCPServer) { + if !t.Enabled { + return + } + for _, prompt := range t.prompts { + s.AddPrompt(prompt.Prompt, prompt.Handler) + } +} + func (t *Toolset) SetReadOnly() { // Set the toolset to read-only t.readOnly = true @@ -225,6 +254,7 @@ func (tg *ToolsetGroup) RegisterAll(s *server.MCPServer) { for _, toolset := range tg.Toolsets { toolset.RegisterTools(s) toolset.RegisterResourcesTemplates(s) + toolset.RegisterPrompts(s) } }