From e765d32a7a415099c71bdaaaeb5bb1ffebf8d136 Mon Sep 17 00:00:00 2001 From: Jonathan Norris Date: Wed, 10 Sep 2025 11:24:48 -0400 Subject: [PATCH] feat: add cleanup_feature tool and shared GitHub fetcher --- src/mcp/tools/featureTools.ts | 27 +++++++++++++++++++++++++++ src/mcp/tools/installTools.ts | 27 +++++---------------------- src/mcp/types.ts | 5 +++++ src/mcp/utils/github.ts | 27 +++++++++++++++++++++++++++ 4 files changed, 64 insertions(+), 22 deletions(-) create mode 100644 src/mcp/utils/github.ts diff --git a/src/mcp/tools/featureTools.ts b/src/mcp/tools/featureTools.ts index 81a55e17..d94b9261 100644 --- a/src/mcp/tools/featureTools.ts +++ b/src/mcp/tools/featureTools.ts @@ -37,6 +37,8 @@ import { IDevCycleApiClient } from '../api/interface' import { DevCycleMCPServerInstance } from '../server' import { handleZodiosValidationErrors } from '../utils/api' import { dashboardLinks } from '../utils/dashboardLinks' +import { fetchAiPromptsAndRules } from '../utils/github' +import { CleanupFeatureArgsSchema } from '../types' // Individual handler functions export async function listFeaturesHandler( @@ -678,4 +680,29 @@ export function registerFeatureTools( ) }, ) + + // Cleanup Feature Prompt tool: fetches the cleanup prompt from GitHub + serverInstance.registerToolWithErrorHandling( + 'cleanup_feature', + { + description: [ + 'Fetch the DevCycle Feature Cleanup prompt and return its markdown content.', + 'Use this to guide safe cleanup of a completed feature and its variables in codebases.', + 'Includes steps to analyze production state, complete the feature, and remove variables.', + ].join('\n'), + annotations: { + title: 'Cleanup Feature Prompt', + readOnlyHint: true, + }, + inputSchema: CleanupFeatureArgsSchema.shape, + }, + async (args: unknown) => { + // validate args + CleanupFeatureArgsSchema.parse(args) + return await fetchAiPromptsAndRules( + 'clean-up-prompts/clean-up.md', + 'Cleanup prompt not found at clean-up-prompts/clean-up.md.', + ) + }, + ) } diff --git a/src/mcp/tools/installTools.ts b/src/mcp/tools/installTools.ts index e0bf47cb..a926d065 100644 --- a/src/mcp/tools/installTools.ts +++ b/src/mcp/tools/installTools.ts @@ -1,8 +1,7 @@ -import axios from 'axios' import { z } from 'zod' -import type { IDevCycleApiClient } from '../api/interface' import type { DevCycleMCPServerInstance } from '../server' import { INSTALL_GUIDES } from './installGuides.generated' +import { fetchAiPromptsAndRules } from '../utils/github' const InstallGuideArgsSchema = z.object({ guide: z.enum(INSTALL_GUIDES), @@ -16,26 +15,10 @@ async function fetchInstallGuideHandler(args: InstallGuideArgs) { ? trimmedGuide : `${trimmedGuide}.md` const repoPath = `install-prompts/${fileName}` - const sourceUrl = `https://raw.githubusercontent.com/DevCycleHQ/AI-Prompts-And-Rules/main/${repoPath}` - - try { - const response = await axios.get(sourceUrl, { - responseType: 'text', - }) - return response.data as string - } catch (error: unknown) { - const status = axios.isAxiosError(error) - ? error.response?.status - : undefined - if (status === 404) { - throw new Error( - `Install guide "${fileName}" not found in install-prompts/. Check the filename (with or without .md).`, - ) - } - throw new Error( - 'Unable to fetch install guide from GitHub. Please retry.', - ) - } + return await fetchAiPromptsAndRules( + repoPath, + `Install guide "${fileName}" not found in install-prompts/. Check the filename (with or without .md).`, + ) } export function registerInstallTools( diff --git a/src/mcp/types.ts b/src/mcp/types.ts index b2a41d5b..6e61079b 100644 --- a/src/mcp/types.ts +++ b/src/mcp/types.ts @@ -468,6 +468,11 @@ export const GetFeatureAuditLogHistoryArgsSchema = z.object({ .describe('Action type to filter audit entries by'), }) +// Cleanup feature prompt tool args +export const CleanupFeatureArgsSchema = z.object({ + featureKey: z.string(), +}) + // Zod schema for DevCycle Audit Log Entity - matches the actual swagger specification export const AuditLogEntitySchema = z.object({ date: z diff --git a/src/mcp/utils/github.ts b/src/mcp/utils/github.ts new file mode 100644 index 00000000..e9108725 --- /dev/null +++ b/src/mcp/utils/github.ts @@ -0,0 +1,27 @@ +import axios from 'axios' + +export async function fetchAiPromptsAndRules( + relativePath: string, + notFoundMessage?: string, +): Promise { + const trimmedPath = relativePath.trim().replace(/^\/+|\/+$/g, '') + const sourceUrl = `https://raw.githubusercontent.com/DevCycleHQ/AI-Prompts-And-Rules/main/${trimmedPath}` + + try { + const response = await axios.get(sourceUrl, { + responseType: 'text', + }) + return response.data as string + } catch (error: unknown) { + const status = axios.isAxiosError(error) + ? error.response?.status + : undefined + if (status === 404) { + throw new Error( + notFoundMessage || + `Resource not found in AI-Prompts-And-Rules: "${trimmedPath}"`, + ) + } + throw new Error('Unable to fetch resource from GitHub. Please retry.') + } +}