Skip to content

Commit 9561131

Browse files
Saranya-jenaHarness
authored andcommitted
chore: [ML-1425]: Added a create_follow_up_prompt tool to generate prompt events based on the action_data (#217)
* 1a33cd chore: [ML-1425]: Added readme * d77cf7 chore: [ML-1397]: updated prompt * bd7e7b chore: [ML-1425]: Added a create_follow_up_prompt tool to generate prompt events based on the action_data * f2ce07 chore: [ML-1425]: Added a create_follow_up_prompt tool to generate prompt events based on the action_data
1 parent abeb350 commit 9561131

File tree

5 files changed

+124
-3
lines changed

5 files changed

+124
-3
lines changed

README.md

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -648,6 +648,52 @@ Environment variables are prefixed with `HARNESS_`:
648648

649649
The server uses a Harness API key for authentication. This can be set via the `HARNESS_API_KEY` environment variable.
650650

651+
### Using the create_follow_up_prompt Tool to generate actionable prompt events
652+
653+
The `create_follow_up_prompt` tool allows you to generate actionable prompt events that appear as buttons in the UI. These buttons can navigate users to specific pages within the Harness platform.
654+
655+
Here's how to use it:
656+
657+
```json
658+
{
659+
"actions": [
660+
{
661+
"text": "Button Text",
662+
"action": "OPEN_ENTITY_NEW_TAB",
663+
"data": {
664+
"pageName": "PAGE_NAME",
665+
"metadata": {
666+
"<KEY>": "<VALUE>"
667+
}
668+
}
669+
}
670+
]
671+
}
672+
```
673+
674+
#### Parameters:
675+
676+
- `text`: The text to display on the button
677+
- `action`: The action to perform (currently supports `OPEN_ENTITY_NEW_TAB`)
678+
- `data`: Contains navigation information
679+
- `pageName`: The page to navigate to (e.g., `ExecutionPipelineView`, `PipelineStudio`, etc.)
680+
- `metadata`: Key-value pairs needed for the target page (e.g., `{"executionId": "abc123", "pipelineId": "xyz789"}`)
681+
682+
#### Example:
683+
684+
```go
685+
actionData := `{"actions": [{"text": "View Pipeline", "action": "OPEN_ENTITY_NEW_TAB", "data": {"pageName": "PipelineStudio", "metadata": {"id": "pipeline-id"}}}]}`
686+
```
687+
688+
#### Alternative: Quick Prompts
689+
690+
If you provide an array of strings instead of the actions object, these strings will be added to the message box as quick prompts that users can click on:
691+
692+
```go
693+
// Add quick prompts to the message box
694+
quickPrompts := `["Show me pipeline details", "List recent executions", "Analyze performance"]`
695+
```
696+
651697
## Notes for Local Testing
652698

653699
There might be certain tools that are not added in external mode or vice-versa, for local testing, following changes in `pkg/harness/tools.go` need to be done in mcp-server code to enable that certain tool in both internal and external modes.

pkg/harness/event/types/simpleaction.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,11 @@ const (
88
)
99

1010
type ActionWrapper struct {
11-
Prompts []string `json:"prompts"`
11+
Prompts interface{} `json:"prompts"`
1212
}
1313

1414
// NewActionEvent uses strings and makes simple selectable actions in the UI (always displayed last)
15-
func NewActionEvent(actions []string, opts ...event.CustomEventOption) event.CustomEvent {
15+
func NewActionEvent(actions interface{}, opts ...event.CustomEventOption) event.CustomEvent {
1616
allOpts := append([]event.CustomEventOption{event.WithDisplayOrder(ActionDisplayOrder)}, opts...)
1717
wrapper := ActionWrapper{Prompts: actions}
1818
return event.NewCustomEvent("prompt", wrapper, allOpts...)

pkg/harness/prompts/prompts.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,9 @@ func RegisterPrompts(mcpServer *server.MCPServer) {
2929
p.NewPrompt().SetName("pipeline_summarizer").
3030
SetDescription("Summarize a Harness pipeline's structure, purpose, and behavior.").
3131
SetResultDescription("Comprehensive pipeline summary with key details.").
32-
SetText(`{"standard": "I need you to summarise the pipeline with the input pipeline identifier.\n\n1. **What to do?**\n - Fetch any required metadata or definitions for the pipeline.\n - Analyze its configuration and structure.\n - Make the necessary tool calls to get the pipeline related details.\n - Produce a concise, accurate summary of the pipeline's design and behavior.\n\n2. **Tools to call to get a complete overview of pipeline**\n - get_pipeline\n - get_pipeline_summary\n - list_pipelines\n - get_environment\n - get_service\n - list_settings (with category as NOTIFICATIONS)\n - get_secret\n - list_triggers\n\n3. **Must-have details in the output** (acceptance criteria):\n - **Purpose and Objective**: What this pipeline is designed to accomplish (e.g. \"Builds and deploys a Node.js microservice to staging and production.\")\n - **High-Level Architecture**: Major components and phases (build, test, security scanning, deployment).\n - **Environment Flow**: How the execution moves through environments.\n - **Key Technologies**: Languages, frameworks, deployment targets, and tools used.\n - **Trigger Conditions**: What events start the pipeline (Git commits, manual triggers, schedules).\n - **Approval Gates**: Any manual approvals required, and who must sign off.\n - **Dependencies**: External dependencies such as environments, infrastructures, connectors, services, other pipelines this one relies on, etc with their ids if available.\n - **Success Criteria**: What defines a successful run.\n\n4. **Output format**\n Return the following data ONLY in a markdown format, DO NOT use JSON literals:\n {\n \"purpose\": string,\n \"architecture\": string,\n \"environment\": string,\n \"technologies\": string[],\n \"triggers\": string[],\n \"approvals\": string[],\n \"dependencies\": string[],\n \"success_criteria\": string,\n \"past_execution_details\": string[]\n }"}`).
32+
SetText(`{
33+
"standard": "I need you to summarise the pipeline with the input pipeline identifier.\n 1. What to do?\n - Fetch any required metadata or definitions for the pipeline.\n - Analyze its configuration and structure.\n - Make the necessary tool calls to get the pipeline related details.\n - Before making any tool call, for all other tool calls you MUST send a message on what tool you are going to call and why.\n - Produce a concise, accurate summary of the pipeline's design and behavior.\n 2. Tools to call to get a complete overview of pipeline:\n - get_pipeline\n - get_pipeline_summary\n - list_pipelines\n - get_environment\n - get_service\n - list_settings (with category as NOTIFICATIONS)\n - get_secret\n - list_triggers\n - list_executions\n - create_follow_up_prompt\n 3. Must-have details in the summary (acceptance criteria):\n - Purpose and Objective: What this pipeline is designed to accomplish (e.g. 'Builds and deploys a Node.js microservice to staging and production.')\n - High-Level Architecture: Major components and phases (build, test, security scanning, deployment).\n - Environment Flow: How the execution moves through environments.\n - Key Technologies: Languages, frameworks, deployment targets, and tools used.\n - Trigger Conditions: What events start the pipeline (Git commits, manual triggers, schedules).\n - Approval Gates: Any manual approvals required, and who must sign off.\n - Dependencies: External dependencies such as environments, infrastructures, connectors, services, other pipelines this one relies on, etc with their ids if available.\n - Success Criteria: What defines a successful run.\n 4. Instructions for calling create_follow_up_prompt tool:\n Follow these steps in order:\n a. Make a tool call to list_executions to fetch the execution ID of the latest execution of the given pipeline\n b. Make a tool call to create_follow_up_prompt with these parameters:\n - action_data: The following JSON data stringified:\n {\"actions\": [\n {\n \"text\": \"View Latest Execution\",\n \"action\": \"OPEN_ENTITY_NEW_TAB\",\n \"data\": {\n \"pageName\": \"ExecutionPipelineView\",\n \"metadata\": {\n \"executionId\": \"executionId-value\",\n \"pipelineId\": \"pipeline-id\"\n }\n }\n },\n {\n \"text\": \"View Pipeline\",\n \"action\": \"OPEN_ENTITY_NEW_TAB\",\n \"data\": {\n \"pageName\": \"PipelineStudio\",\n \"metadata\": {\n \"id\": \"pipeline-id\"\n }\n }\n }\n ]}\n - Replace \"executionId-value\" with the actual execution ID from the list_executions response\n - Replace \"pipeline-id\" with the pipeline identifier\n - If there are no executions available, MUST use this JSON data instead(with only option view pipeline):\n {\"actions\": [\n {\n \"text\": \"View Pipeline\",\n \"action\": \"OPEN_ENTITY_NEW_TAB\",\n \"data\": {\n \"pageName\": \"PIPELINE_STUDIO\",\n \"metadata\": {\n \"id\": \"pipeline-id\"\n }\n }\n }\n ]}\n\n 5. Output format:\n Return the following summary data ONLY in a markdown format, DO NOT use JSON literals:\n {\n \"### Purpose\": string,\n \"### Architecture\": string,\n \"### Environment\": string,\n \"### Technologies\": string[],\n \"### Triggers\": string[],\n \"### Approvals\": string[],\n \"### Dependencies\": string[],\n \"### Success Criteria\": string,\n \"### Past Execution Details\": string[]\n } \n Return the tool response exactly as received, in markdown format. DO NOT use JSON literals. The pipeline summary should the final response."
34+
}`).
3335
Build())
3436

3537
p.AddPrompts(prompts, mcpServer)

pkg/harness/tools/pipelines.go

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,13 @@ import (
44
"context"
55
"encoding/json"
66
"fmt"
7+
"strings"
78

89
"github.com/harness/harness-mcp/client"
910
"github.com/harness/harness-mcp/client/dto"
1011
"github.com/harness/harness-mcp/cmd/harness-mcp-server/config"
1112
"github.com/harness/harness-mcp/pkg/harness/common"
13+
"github.com/harness/harness-mcp/pkg/harness/event/types"
1214
"github.com/mark3labs/mcp-go/mcp"
1315
"github.com/mark3labs/mcp-go/server"
1416
)
@@ -390,6 +392,76 @@ func GetPipelineSummaryTool(config *config.Config, client *client.PipelineServic
390392
}
391393
}
392394

395+
type ActionData struct {
396+
Actions []struct {
397+
Text string `json:"text"`
398+
Action string `json:"action"`
399+
Data struct {
400+
PageName string `json:"pageName"`
401+
Metadata map[string]string `json:"metadata"`
402+
} `json:"data"`
403+
} `json:"actions"`
404+
}
405+
406+
func CreateFollowUpPromptTool(config *config.Config, client *client.PipelineService) (tool mcp.Tool, handler server.ToolHandlerFunc) {
407+
return mcp.NewTool("create_follow_up_prompt",
408+
mcp.WithDescription("Creates a follow up prompt event with the specified data."),
409+
mcp.WithString("action_data",
410+
mcp.Required(),
411+
mcp.Description("A JSON string in one of these formats: 1) An array of action objects: {\"actions\": [{\"text\": \"Button Text\", \"action\": \"OPEN_ENTITY_NEW_TAB\", \"data\": {\"pageName\": \"PAGE_NAME\", \"metadata\": {\"<KEY>\": \"<VALUE>\"}}}]} OR 2) just a single string action without any array: \"Action1\""),
412+
),
413+
),
414+
func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
415+
actionDataStr, err := RequiredParam[string](request, "action_data")
416+
if err != nil {
417+
return mcp.NewToolResultError(err.Error()), nil
418+
}
419+
420+
// Parse action data into actionStrings (array of JSON strings)
421+
var actionStrings []string
422+
var actionObject interface{}
423+
424+
// Check if the input is a plain text message (not JSON)
425+
if !strings.HasPrefix(strings.TrimSpace(actionDataStr), "{") && !strings.HasPrefix(strings.TrimSpace(actionDataStr), "[") {
426+
actionStrings = append(actionStrings, actionDataStr)
427+
actionObject = actionStrings
428+
} else {
429+
// Try to parse as JSON
430+
var actionString string
431+
// First try if it's already a JSON string
432+
if err := json.Unmarshal([]byte(actionDataStr), &actionString); err == nil {
433+
actionStrings = append(actionStrings, actionString)
434+
actionObject = actionStrings
435+
} else {
436+
// Try if it's a wrapper with "actions" field
437+
var actionDataWrapper ActionData
438+
439+
if err := json.Unmarshal([]byte(actionDataStr), &actionDataWrapper); err == nil {
440+
// Convert each action to a stringified JSON
441+
actionObject = actionDataWrapper.Actions
442+
} else {
443+
return mcp.NewToolResultError(fmt.Sprintf("Failed to parse action data: %v", err)), nil
444+
}
445+
446+
}
447+
}
448+
449+
// Create a custom event with the stringified action data
450+
promptEvent := types.NewActionEvent(actionObject)
451+
452+
// Create an embedded resource from the event
453+
resource, err := promptEvent.CreateEmbeddedResource()
454+
if err != nil {
455+
return mcp.NewToolResultError(fmt.Sprintf("Failed to create embedded resource: %v", err)), nil
456+
}
457+
458+
// Return the resource as the tool result
459+
return &mcp.CallToolResult{
460+
Content: []mcp.Content{resource},
461+
}, nil
462+
}
463+
}
464+
393465
func ListTriggersTool(config *config.Config, client *client.PipelineService) (tool mcp.Tool, handler server.ToolHandlerFunc) {
394466
return mcp.NewTool("list_triggers",
395467
mcp.WithDescription("List triggers in a Harness pipeline."),

pkg/modules/core.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,7 @@ func RegisterPipelines(config *config.Config, tsg *toolsets.ToolsetGroup) error
198198
toolsets.NewServerTool(tools.ListInputSetsTool(config, pipelineClient)),
199199
toolsets.NewServerTool(tools.GetPipelineSummaryTool(config, pipelineClient)),
200200
toolsets.NewServerTool(tools.ListTriggersTool(config, pipelineClient)),
201+
toolsets.NewServerTool(tools.CreateFollowUpPromptTool(config, pipelineClient)),
201202
)
202203

203204
// Add toolset to the group

0 commit comments

Comments
 (0)