From fdd856e515798fd75211aea125fff0d56b71cc79 Mon Sep 17 00:00:00 2001 From: Rajendra Baviskar Date: Tue, 21 Jul 2026 10:58:05 -0700 Subject: [PATCH 1/4] fix: register pipeline_error_analysis as alias for debug-pipeline-failure prompt ml-infra calls get_prompt("pipeline_error_analysis") but the MCP server only registered the prompt as "debug-pipeline-failure", causing the lookup to fail silently. Register the same handler under both names so the specialized pipeline troubleshooting instructions are actually fetched. Co-Authored-By: Claude Opus 4.6 AI-Session-Id: 1d177617-3d9e-4153-986e-34ff001b6bb4 AI-Tool: claude-code AI-Model: unknown --- src/prompts/debug-pipeline.ts | 64 ++++++++++++++++++++--------------- 1 file changed, 37 insertions(+), 27 deletions(-) diff --git a/src/prompts/debug-pipeline.ts b/src/prompts/debug-pipeline.ts index 2ccdd7909..e95d662ad 100644 --- a/src/prompts/debug-pipeline.ts +++ b/src/prompts/debug-pipeline.ts @@ -1,29 +1,26 @@ import * as z from "zod/v4"; import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -export function registerDebugPipelinePrompt(server: McpServer): void { - server.registerPrompt( - "debug-pipeline-failure", - { - description: "Analyze a failed pipeline execution and suggest fixes. Accepts an execution ID, pipeline ID, or Harness URL.", - argsSchema: { - executionId: z.string().describe("The failed execution ID, pipeline ID, or a Harness URL").optional(), - projectId: z.string().describe("Project identifier").optional(), - }, - }, - async ({ executionId, projectId }) => { - // Detect if the input looks like a URL - const isUrl = executionId?.startsWith("http"); - const idParam = isUrl - ? `url="${executionId}"` - : `execution_id="${executionId}"`; - - return { - messages: [{ - role: "user" as const, - content: { - type: "text" as const, - text: `Analyze this failed Harness pipeline execution and provide: +const promptConfig = { + description: "Analyze a failed pipeline execution and suggest fixes. Accepts an execution ID, pipeline ID, or Harness URL.", + argsSchema: { + executionId: z.string().describe("The failed execution ID, pipeline ID, or a Harness URL").optional(), + projectId: z.string().describe("Project identifier").optional(), + }, +}; + +function handleDebugPipeline({ executionId, projectId }: { executionId?: string; projectId?: string }) { + const isUrl = executionId?.startsWith("http"); + const idParam = isUrl + ? `url="${executionId}"` + : `execution_id="${executionId}"`; + + return { + messages: [{ + role: "user" as const, + content: { + type: "text" as const, + text: `Analyze this failed Harness pipeline execution and provide: 1. **Root cause** of the failure 2. **Which step failed** and why @@ -43,9 +40,22 @@ Then analyze the diagnostic payload: - Suggest the user either pass the missing values as \`inputs\` or use \`input_set_ids\` referencing a saved input set Provide actionable recommendations based on the combined evidence.`, - }, - }], - }; - }, + }, + }], + }; +} + +export function registerDebugPipelinePrompt(server: McpServer): void { + server.registerPrompt( + "debug-pipeline-failure", + promptConfig, + async (args) => handleDebugPipeline(args), + ); + + // Alias: ml-infra requests this name via get_prompt("pipeline_error_analysis") + server.registerPrompt( + "pipeline_error_analysis", + promptConfig, + async (args) => handleDebugPipeline(args), ); } From 2fd2a3597bbafaa98c0b58721087765ac64ec4bc Mon Sep 17 00:00:00 2001 From: Rajendra Baviskar Date: Tue, 21 Jul 2026 12:25:17 -0700 Subject: [PATCH 2/4] test: add prompt test for pipeline_error_analysis alias MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Verifies the alias appears in listPrompts(), returns identical content via getPrompt(), and correctly interpolates executionId/projectId/URL parameters — matching the coverage pattern from summarize-pipeline. Co-Authored-By: Claude Opus 4.6 AI-Session-Id: 1d177617-3d9e-4153-986e-34ff001b6bb4 AI-Tool: claude-code AI-Model: unknown --- tests/prompts/debug-pipeline.test.ts | 104 +++++++++++++++++++++++++++ 1 file changed, 104 insertions(+) create mode 100644 tests/prompts/debug-pipeline.test.ts diff --git a/tests/prompts/debug-pipeline.test.ts b/tests/prompts/debug-pipeline.test.ts new file mode 100644 index 000000000..592b83c4f --- /dev/null +++ b/tests/prompts/debug-pipeline.test.ts @@ -0,0 +1,104 @@ +import { describe, it, expect } from "vitest"; +import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { Client } from "@modelcontextprotocol/sdk/client/index.js"; +import { InMemoryTransport } from "@modelcontextprotocol/sdk/inMemory.js"; +import { registerDebugPipelinePrompt } from "../../src/prompts/debug-pipeline.js"; + +async function createTestClient(): Promise { + const server = new McpServer( + { name: "test-server", version: "0.0.1" }, + { capabilities: { prompts: {} } }, + ); + registerDebugPipelinePrompt(server); + + const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair(); + const client = new Client({ name: "test-client", version: "0.0.1" }); + + await Promise.all([ + client.connect(clientTransport), + server.connect(serverTransport), + ]); + + return client; +} + +describe("debug-pipeline-failure prompt", () => { + it("appears in the prompt list", async () => { + const client = await createTestClient(); + const { prompts } = await client.listPrompts(); + + const prompt = prompts.find((p) => p.name === "debug-pipeline-failure"); + expect(prompt).toBeDefined(); + expect(prompt!.description).toContain("Analyze a failed pipeline execution"); + }); + + it("registers pipeline_error_analysis as an alias", async () => { + const client = await createTestClient(); + const { prompts } = await client.listPrompts(); + + const alias = prompts.find((p) => p.name === "pipeline_error_analysis"); + expect(alias).toBeDefined(); + expect(alias!.description).toBe( + prompts.find((p) => p.name === "debug-pipeline-failure")!.description, + ); + }); + + it("pipeline_error_analysis returns the same prompt content", async () => { + const client = await createTestClient(); + + const original = await client.getPrompt({ + name: "debug-pipeline-failure", + arguments: { executionId: "exec-123" }, + }); + const alias = await client.getPrompt({ + name: "pipeline_error_analysis", + arguments: { executionId: "exec-123" }, + }); + + const originalText = (original.messages[0].content as { type: string; text: string }).text; + const aliasText = (alias.messages[0].content as { type: string; text: string }).text; + expect(aliasText).toBe(originalText); + }); + + it("interpolates executionId and projectId", async () => { + const client = await createTestClient(); + const result = await client.getPrompt({ + name: "pipeline_error_analysis", + arguments: { + executionId: "exec-abc-123", + projectId: "my-project", + }, + }); + + expect(result.messages).toHaveLength(1); + const text = (result.messages[0].content as { type: string; text: string }).text; + expect(text).toContain('execution_id="exec-abc-123"'); + expect(text).toContain('project_id="my-project"'); + }); + + it("detects URL input and uses url param", async () => { + const client = await createTestClient(); + const result = await client.getPrompt({ + name: "pipeline_error_analysis", + arguments: { + executionId: "https://app.harness.io/ng/#/account/abc/pipelines/exec123", + }, + }); + + const text = (result.messages[0].content as { type: string; text: string }).text; + expect(text).toContain('url="https://app.harness.io/ng/#/account/abc/pipelines/exec123"'); + expect(text).not.toContain("execution_id="); + }); + + it("references harness_diagnose with include_logs", async () => { + const client = await createTestClient(); + const result = await client.getPrompt({ + name: "pipeline_error_analysis", + arguments: { executionId: "exec123" }, + }); + + const text = (result.messages[0].content as { type: string; text: string }).text; + expect(text).toContain("harness_diagnose"); + expect(text).toContain("include_logs=true"); + }); +}); From 86d2cedd4a54cb392783791eff77fd005f73a448 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Tue, 21 Jul 2026 21:06:48 +0000 Subject: [PATCH 3/4] Give pipeline_error_analysis alias a distinguishing description The alias shared promptConfig with debug-pipeline-failure, causing identical descriptions in listPrompts(). Use a separate description that marks it as an alias while keeping the same handler and content. Co-authored-by: Rohan Gupta --- src/prompts/debug-pipeline.ts | 6 +++++- tests/prompts/debug-pipeline.test.ts | 7 +++---- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/prompts/debug-pipeline.ts b/src/prompts/debug-pipeline.ts index e95d662ad..42680c463 100644 --- a/src/prompts/debug-pipeline.ts +++ b/src/prompts/debug-pipeline.ts @@ -55,7 +55,11 @@ export function registerDebugPipelinePrompt(server: McpServer): void { // Alias: ml-infra requests this name via get_prompt("pipeline_error_analysis") server.registerPrompt( "pipeline_error_analysis", - promptConfig, + { + ...promptConfig, + description: + "Alias of debug-pipeline-failure — analyze a failed pipeline execution and suggest fixes. Accepts an execution ID, pipeline ID, or Harness URL.", + }, async (args) => handleDebugPipeline(args), ); } diff --git a/tests/prompts/debug-pipeline.test.ts b/tests/prompts/debug-pipeline.test.ts index 592b83c4f..cb8c4858d 100644 --- a/tests/prompts/debug-pipeline.test.ts +++ b/tests/prompts/debug-pipeline.test.ts @@ -32,15 +32,14 @@ describe("debug-pipeline-failure prompt", () => { expect(prompt!.description).toContain("Analyze a failed pipeline execution"); }); - it("registers pipeline_error_analysis as an alias", async () => { + it("registers pipeline_error_analysis as an alias with a distinguishing description", async () => { const client = await createTestClient(); const { prompts } = await client.listPrompts(); const alias = prompts.find((p) => p.name === "pipeline_error_analysis"); expect(alias).toBeDefined(); - expect(alias!.description).toBe( - prompts.find((p) => p.name === "debug-pipeline-failure")!.description, - ); + expect(alias!.description).toContain("Alias of debug-pipeline-failure"); + expect(alias!.description).toContain("analyze a failed pipeline execution"); }); it("pipeline_error_analysis returns the same prompt content", async () => { From 4c7132b4b9b847f0a6efaa1bb4c7a267a9b81133 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Tue, 21 Jul 2026 21:07:36 +0000 Subject: [PATCH 4/4] docs: document pipeline_error_analysis prompt alias in README Add the ml-infra-facing alias to the MCP Prompts table and clarify that the headline prompt count is modules, with aliases listed separately. Co-authored-by: Rohan Gupta --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 0ac4b6ab8..17f0c000e 100644 --- a/README.md +++ b/README.md @@ -1666,6 +1666,8 @@ Security exemption execute workflow: ## MCP Prompts +Prompt modules are listed below. Some modules register additional aliases for external integrations (for example, `pipeline_error_analysis` for `debug-pipeline-failure`); aliases appear in the table with their canonical name noted. + ### DevOps @@ -1673,6 +1675,7 @@ Security exemption execute workflow: | ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- | | `build-deploy-app` | End-to-end CI/CD workflow: scan a git repo, generate CI pipeline (build & push Docker image), discover or generate K8s manifests, create CD pipeline, and deploy — with auto-retry on CI failures (up to 5 attempts) and CD failures (up to 3 attempts with user permission). On exhausted retries, provides Harness UI deep links to all created resources for manual investigation. | `repoUrl` (required), `imageName` (required), `projectId` (optional), `namespace` (optional) | | `debug-pipeline-failure` | Analyze a failed execution: accepts an execution ID, pipeline ID, or Harness URL. Gets stage/step breakdown, failure details, delegate info, and failed step logs via `harness_diagnose`, then provides root cause analysis and suggested fixes. Automatically follows chained pipeline failures. | `executionId` (optional), `projectId` (optional) | +| `pipeline_error_analysis` | Alias for `debug-pipeline-failure` (used by ml-infra). Same behavior: analyze a failed execution via `harness_diagnose`, provide root cause analysis, and suggest fixes. Automatically follows chained pipeline failures. | `executionId` (optional), `projectId` (optional) | | `pipeline_summarizer` | Fetch and summarize ALL step logs from a pipeline execution. Uses `harness_diagnose` with `include_logs: true, include_all_step_logs: true` to get every step's log, then presents a table with Step Name, Status, Duration, and What Happened (log-based summary). Does NOT skip any steps. | `executionId` (optional), `projectId` (optional) | | `create-pipeline` | Generate a new pipeline YAML from natural language requirements, reviewing existing resources for context | `description` (required), `projectId` (optional) | | `create-agent` | Interactively build a Harness AI agent — check existing agents, gather requirements, generate agent YAML spec using the agent-pipeline schema, confirm with user, then create or update via `harness_create`/`harness_update` | `agent_name` (required), `task_description` (required), `org_id` (optional), `project_id` (optional) |