AWS CodePipeline MCP Server

by cuongdev
Verified
This file is a merged representation of the entire codebase, combined into a single document by Repomix. ================================================================ File Summary ================================================================ Purpose: -------- This file contains a packed representation of the entire repository's contents. It is designed to be easily consumable by AI systems for analysis, code review, or other automated processes. File Format: ------------ The content is organized as follows: 1. This summary section 2. Repository information 3. Directory structure 4. Multiple file entries, each consisting of: a. A separator line (================) b. The file path (File: path/to/file) c. Another separator line d. The full contents of the file e. A blank line Usage Guidelines: ----------------- - This file should be treated as read-only. Any changes should be made to the original repository files, not this packed version. - When processing this file, use the file path to distinguish between different files in the repository. - Be aware that this file may contain sensitive information. Handle it with the same level of security as you would the original repository. Notes: ------ - Some files may have been excluded based on .gitignore rules and Repomix's configuration - Binary files are not included in this packed representation. Please refer to the Repository Structure section for a complete list of file paths, including binary files - Files matching patterns in .gitignore are excluded - Files matching default ignore patterns are excluded Additional Info: ---------------- ================================================================ Directory Structure ================================================================ src/ config/ server-config.ts controllers/ codepipeline.controller.ts routes/ codepipeline.routes.ts services/ codepipeline.service.ts tools/ approve_action.ts create_pipeline_webhook.ts get_pipeline_details.ts get_pipeline_execution_logs.ts get_pipeline_metrics.ts get_pipeline_state.ts list_pipeline_executions.ts list_pipelines.ts retry_stage.ts stop_pipeline_execution.ts tag_pipeline_resource.ts trigger_pipeline.ts types/ codepipeline.ts modelcontextprotocol-sdk.d.ts utils/ env.ts index.ts mcp-server.ts types.ts .env.example .gitignore ARCHITECTURE.md package.json README.md tsconfig.json ================================================================ Files ================================================================ ================ File: src/config/server-config.ts ================ interface ServerDescription { name: string; version: string; displayName?: string; description?: string; publisher?: string; homepage?: string; license?: string; repository?: string; } export const serverConfig: ServerDescription = { name: "aws-codepipeline-mcp-server", version: "1.0.0", displayName: "AWS CodePipeline MCP Server", description: "MCP server for interacting with AWS CodePipeline services", publisher: "Cuong T Nguyen", homepage: "https://cuong.asia", license: "MIT", repository: "https://github.com/cuongdev/mcp-codepipeline-server" }; ================ File: src/controllers/codepipeline.controller.ts ================ import { Request, Response } from 'express'; import { CodePipelineService } from '../services/codepipeline.service.js'; import { ApprovalRequest, RetryStageRequest, TriggerPipelineRequest } from '../types/codepipeline.js'; export class CodePipelineController { private codePipelineService: CodePipelineService; constructor() { this.codePipelineService = new CodePipelineService(); } /** * List all pipelines */ listPipelines = async (req: Request, res: Response): Promise<void> => { try { const pipelines = await this.codePipelineService.listPipelines(); res.status(200).json({ pipelines }); } catch (error) { console.error('Error in listPipelines controller:', error); res.status(500).json({ error: 'Failed to list pipelines' }); } }; /** * Get pipeline state */ getPipelineState = async (req: Request, res: Response): Promise<void> => { try { const { pipelineName } = req.params; if (!pipelineName) { res.status(400).json({ error: 'Pipeline name is required' }); return; } const pipelineState = await this.codePipelineService.getPipelineState(pipelineName); res.status(200).json({ pipelineState }); } catch (error) { console.error('Error in getPipelineState controller:', error); res.status(500).json({ error: 'Failed to get pipeline state' }); } }; /** * List pipeline executions */ listPipelineExecutions = async (req: Request, res: Response): Promise<void> => { try { const { pipelineName } = req.params; if (!pipelineName) { res.status(400).json({ error: 'Pipeline name is required' }); return; } const executions = await this.codePipelineService.listPipelineExecutions(pipelineName); res.status(200).json({ executions }); } catch (error) { console.error('Error in listPipelineExecutions controller:', error); res.status(500).json({ error: 'Failed to list pipeline executions' }); } }; /** * Approve a manual approval action */ approveAction = async (req: Request, res: Response): Promise<void> => { try { const approvalRequest: ApprovalRequest = req.body; const { approved, comments } = req.body; if (!approvalRequest.pipelineName || !approvalRequest.stageName || !approvalRequest.actionName || !approvalRequest.token) { res.status(400).json({ error: 'Missing required approval parameters' }); return; } await this.codePipelineService.approveAction( approvalRequest, approved === true, comments || '' ); res.status(200).json({ message: `Action ${approved ? 'approved' : 'rejected'} successfully` }); } catch (error) { console.error('Error in approveAction controller:', error); res.status(500).json({ error: 'Failed to process approval' }); } }; /** * Retry a failed stage */ retryStage = async (req: Request, res: Response): Promise<void> => { try { const retryRequest: RetryStageRequest = req.body; if (!retryRequest.pipelineName || !retryRequest.stageName || !retryRequest.pipelineExecutionId) { res.status(400).json({ error: 'Missing required retry parameters' }); return; } await this.codePipelineService.retryStageExecution(retryRequest); res.status(200).json({ message: 'Stage retry initiated successfully' }); } catch (error) { console.error('Error in retryStage controller:', error); res.status(500).json({ error: 'Failed to retry stage' }); } }; /** * Trigger a pipeline execution */ triggerPipeline = async (req: Request, res: Response): Promise<void> => { try { const triggerRequest: TriggerPipelineRequest = req.body; if (!triggerRequest.pipelineName) { res.status(400).json({ error: 'Pipeline name is required' }); return; } const executionId = await this.codePipelineService.startPipelineExecution(triggerRequest); res.status(200).json({ message: 'Pipeline triggered successfully', executionId }); } catch (error) { console.error('Error in triggerPipeline controller:', error); res.status(500).json({ error: 'Failed to trigger pipeline' }); } }; /** * Get pipeline execution logs */ getPipelineExecutionLogs = async (req: Request, res: Response): Promise<void> => { try { const { pipelineName, executionId } = req.params; if (!pipelineName || !executionId) { res.status(400).json({ error: 'Pipeline name and execution ID are required' }); return; } const logs = await this.codePipelineService.getPipelineExecutionLogs(pipelineName, executionId); res.status(200).json({ logs }); } catch (error) { console.error('Error in getPipelineExecutionLogs controller:', error); res.status(500).json({ error: 'Failed to get pipeline execution logs' }); } }; /** * Stop pipeline execution */ stopPipelineExecution = async (req: Request, res: Response): Promise<void> => { try { const { pipelineName, executionId } = req.params; const { reason } = req.body; if (!pipelineName || !executionId) { res.status(400).json({ error: 'Pipeline name and execution ID are required' }); return; } await this.codePipelineService.stopPipelineExecution(pipelineName, executionId, reason); res.status(200).json({ message: 'Pipeline execution stopped successfully' }); } catch (error) { console.error('Error in stopPipelineExecution controller:', error); res.status(500).json({ error: 'Failed to stop pipeline execution' }); } }; } ================ File: src/routes/codepipeline.routes.ts ================ import { Router } from 'express'; import { CodePipelineController } from '../controllers/codepipeline.controller.js'; const router = Router(); const codePipelineController = new CodePipelineController(); // List all pipelines router.get('/pipelines', codePipelineController.listPipelines); // Get pipeline state router.get('/pipelines/:pipelineName/state', codePipelineController.getPipelineState); // List pipeline executions router.get('/pipelines/:pipelineName/executions', codePipelineController.listPipelineExecutions); // Approve or reject a manual approval action router.post('/pipelines/approve', codePipelineController.approveAction); // Retry a failed stage router.post('/pipelines/retry-stage', codePipelineController.retryStage); // Trigger a pipeline execution router.post('/pipelines/trigger', codePipelineController.triggerPipeline); // Get pipeline execution logs router.get('/pipelines/:pipelineName/executions/:executionId/logs', codePipelineController.getPipelineExecutionLogs); // Stop pipeline execution router.post('/pipelines/:pipelineName/executions/:executionId/stop', codePipelineController.stopPipelineExecution); export default router; ================ File: src/services/codepipeline.service.ts ================ import AWS from 'aws-sdk'; import { PipelineSummary, PipelineState, PipelineExecution, ApprovalRequest, RetryStageRequest, TriggerPipelineRequest, StageState } from '../types/codepipeline.js'; import { getEnv } from '../utils/env.js'; export class CodePipelineService { private codepipeline: AWS.CodePipeline; constructor() { // Get AWS configuration from environment variables const region = getEnv('AWS_REGION', 'us-west-2'); // Default to us-west-2 if not provided const accessKeyId = getEnv('AWS_ACCESS_KEY_ID'); const secretAccessKey = getEnv('AWS_SECRET_ACCESS_KEY'); // Configure AWS SDK const awsConfig: AWS.ConfigurationOptions = { region }; // Add credentials if provided if (accessKeyId && secretAccessKey) { awsConfig.credentials = new AWS.Credentials({ accessKeyId, secretAccessKey }); } // Update AWS SDK configuration AWS.config.update(awsConfig); console.log(`AWS CodePipeline service initialized with region: ${region}`); this.codepipeline = new AWS.CodePipeline(awsConfig); } /** * List all pipelines */ async listPipelines(): Promise<PipelineSummary[]> { try { const response = await this.codepipeline.listPipelines().promise(); return response.pipelines?.map(pipeline => ({ name: pipeline.name || '', version: pipeline.version || 0, created: pipeline.created?.toISOString() || '', updated: pipeline.updated?.toISOString() || '' })) || []; } catch (error) { console.error('Error listing pipelines:', error); throw error; } } /** * Get pipeline state */ async getPipelineState(pipelineName: string): Promise<PipelineState> { try { const response = await this.codepipeline.getPipelineState({ name: pipelineName }).promise(); // Convert AWS.CodePipeline.StageState[] to our StageState[] const stageStates: StageState[] = response.stageStates?.map(stage => { // Convert TransitionState const inboundTransitionState = stage.inboundTransitionState ? { enabled: stage.inboundTransitionState.enabled === undefined ? false : stage.inboundTransitionState.enabled, lastChangedBy: stage.inboundTransitionState.lastChangedBy, lastChangedAt: stage.inboundTransitionState.lastChangedAt, disabledReason: stage.inboundTransitionState.disabledReason } : undefined; // Convert StageExecution const latestExecution = stage.latestExecution ? { pipelineExecutionId: stage.latestExecution.pipelineExecutionId || '', status: stage.latestExecution.status || '' // AWS SDK types may have different property names, we're mapping to our interface } : undefined; // Convert ActionStates const actionStates = (stage.actionStates || []).map(action => { // Convert ActionExecution const latestExecution = action.latestExecution ? { status: action.latestExecution.status || '', summary: action.latestExecution.summary, lastStatusChange: action.latestExecution.lastStatusChange || new Date().toISOString(), token: action.latestExecution.token, externalExecutionId: action.latestExecution.externalExecutionId, externalExecutionUrl: action.latestExecution.externalExecutionUrl, errorDetails: action.latestExecution.errorDetails ? { code: action.latestExecution.errorDetails.code || '', message: action.latestExecution.errorDetails.message || '' } : undefined } : undefined; // Convert ActionRevision const currentRevision = action.currentRevision ? { revisionId: action.currentRevision.revisionId || '', revisionChangeId: action.currentRevision.revisionChangeId || '', created: action.currentRevision.created || new Date().toISOString() } : undefined; return { actionName: action.actionName || '', currentRevision, latestExecution, entityUrl: action.entityUrl }; }); return { stageName: stage.stageName || '', inboundTransitionState, actionStates, latestExecution }; }) || []; return { pipelineName: response.pipelineName || '', pipelineVersion: response.pipelineVersion || 0, stageStates: stageStates, created: response.created?.toISOString() || '', updated: response.updated?.toISOString() || '' }; } catch (error) { console.error(`Error getting pipeline state for ${pipelineName}:`, error); throw error; } } /** * Get pipeline executions */ async listPipelineExecutions(pipelineName: string): Promise<PipelineExecution[]> { try { const response = await this.codepipeline.listPipelineExecutions({ pipelineName }).promise(); return response.pipelineExecutionSummaries?.map(execution => ({ pipelineExecutionId: execution.pipelineExecutionId || '', status: execution.status || '', artifactRevisions: execution.sourceRevisions?.map(revision => ({ name: revision.actionName || '', revisionId: revision.revisionId || '', revisionChangeIdentifier: revision.revisionUrl || '', // Fixed property name revisionSummary: revision.revisionSummary || '', created: new Date().toISOString(), // Fixed missing property revisionUrl: revision.revisionUrl || '' })) || [] })) || []; } catch (error) { console.error(`Error listing pipeline executions for ${pipelineName}:`, error); throw error; } } /** * Approve or reject a manual approval action */ async approveAction(request: ApprovalRequest, approved: boolean, comments: string = ''): Promise<void> { try { await this.codepipeline.putApprovalResult({ pipelineName: request.pipelineName, stageName: request.stageName, actionName: request.actionName, token: request.token, result: { status: approved ? 'Approved' : 'Rejected', summary: comments } }).promise(); } catch (error) { console.error(`Error ${approved ? 'approving' : 'rejecting'} action:`, error); throw error; } } /** * Retry a failed stage */ async retryStageExecution(request: RetryStageRequest): Promise<void> { try { await this.codepipeline.retryStageExecution({ pipelineName: request.pipelineName, stageName: request.stageName, pipelineExecutionId: request.pipelineExecutionId, retryMode: 'FAILED_ACTIONS' }).promise(); } catch (error) { console.error(`Error retrying stage execution:`, error); throw error; } } /** * Trigger a pipeline execution */ async startPipelineExecution(request: TriggerPipelineRequest): Promise<string> { try { const response = await this.codepipeline.startPipelineExecution({ name: request.pipelineName }).promise(); return response.pipelineExecutionId || ''; } catch (error) { console.error(`Error starting pipeline execution:`, error); throw error; } } /** * Get pipeline execution logs */ async getPipelineExecutionLogs(pipelineName: string, executionId: string): Promise<AWS.CodePipeline.GetPipelineExecutionOutput> { try { return await this.codepipeline.getPipelineExecution({ pipelineName, pipelineExecutionId: executionId }).promise(); } catch (error) { console.error(`Error getting pipeline execution logs:`, error); throw error; } } /** * Stop pipeline execution */ async stopPipelineExecution(pipelineName: string, executionId: string, reason: string = 'Stopped by user'): Promise<void> { try { await this.codepipeline.stopPipelineExecution({ pipelineName, pipelineExecutionId: executionId, reason, abandon: false }).promise(); } catch (error) { console.error(`Error stopping pipeline execution:`, error); throw error; } } } ================ File: src/tools/approve_action.ts ================ import { CodePipelineManager } from "../types.js"; export const approveActionSchema = { name: "approve_action", description: "Approve or reject a manual approval action", inputSchema: { type: "object", properties: { pipelineName: { type: "string", description: "Name of the pipeline" }, stageName: { type: "string", description: "Name of the stage" }, actionName: { type: "string", description: "Name of the action" }, token: { type: "string", description: "Approval token" }, approved: { type: "boolean", description: "Boolean indicating approval or rejection" }, comments: { type: "string", description: "Optional comments" } }, required: ["pipelineName", "stageName", "actionName", "token", "approved"], }, } as const; export async function approveAction( codePipelineManager: CodePipelineManager, input: { pipelineName: string; stageName: string; actionName: string; token: string; approved: boolean; comments?: string; } ) { const { pipelineName, stageName, actionName, token, approved, comments } = input; const codepipeline = codePipelineManager.getCodePipeline(); await codepipeline.putApprovalResult({ pipelineName, stageName, actionName, token, result: { status: approved ? 'Approved' : 'Rejected', summary: comments || '' } }).promise(); return { content: [ { type: "text", text: JSON.stringify({ message: `Action ${approved ? 'approved' : 'rejected'} successfully` }, null, 2), }, ], }; } ================ File: src/tools/create_pipeline_webhook.ts ================ import { CodePipelineManager } from "../types.js"; import AWS from 'aws-sdk'; export const createPipelineWebhookSchema = { name: "create_pipeline_webhook", description: "Create a webhook for a pipeline to enable automatic triggering", inputSchema: { type: "object", properties: { pipelineName: { type: "string", description: "Name of the pipeline" }, webhookName: { type: "string", description: "Name for the webhook" }, targetAction: { type: "string", description: "The name of the action in the pipeline that processes the webhook" }, authentication: { type: "string", description: "Authentication method for the webhook", enum: ["GITHUB_HMAC", "IP", "UNAUTHENTICATED"] }, authenticationConfiguration: { type: "object", description: "Authentication configuration based on the authentication type", properties: { SecretToken: { type: "string", description: "Secret token for GITHUB_HMAC authentication" }, AllowedIpRange: { type: "string", description: "Allowed IP range for IP authentication" } } }, filters: { type: "array", description: "Event filters for the webhook", items: { type: "object", properties: { jsonPath: { type: "string", description: "JSON path to filter events" }, matchEquals: { type: "string", description: "Value to match in the JSON path" } }, required: ["jsonPath"] } } }, required: ["pipelineName", "webhookName", "targetAction", "authentication"], }, } as const; export async function createPipelineWebhook( codePipelineManager: CodePipelineManager, input: { pipelineName: string; webhookName: string; targetAction: string; authentication: string; authenticationConfiguration?: { SecretToken?: string; AllowedIpRange?: string; }; filters?: Array<{ jsonPath: string; matchEquals?: string; }>; } ) { const { pipelineName, webhookName, targetAction, authentication, authenticationConfiguration = {}, filters = [] } = input; const codepipeline = codePipelineManager.getCodePipeline(); // Create the webhook const response = await codepipeline.putWebhook({ webhook: { name: webhookName, targetPipeline: pipelineName, targetAction: targetAction, filters: filters.map(filter => ({ jsonPath: filter.jsonPath, matchEquals: filter.matchEquals })), authentication, authenticationConfiguration } }).promise(); // Register the webhook await codepipeline.registerWebhookWithThirdParty({ webhookName }).promise(); // Extract webhook details safely const webhookDetails = { name: webhookName, url: response.webhook ? String(response.webhook.url) : undefined, targetPipeline: pipelineName, targetAction: targetAction }; return { content: [ { type: "text", text: JSON.stringify({ message: "Pipeline webhook created and registered successfully", webhookDetails }, null, 2), }, ], }; } ================ File: src/tools/get_pipeline_details.ts ================ import { CodePipelineManager } from "../types.js"; export const getPipelineDetailsSchema = { name: "get_pipeline_details", description: "Get the full definition of a specific pipeline", inputSchema: { type: "object", properties: { pipelineName: { type: "string", description: "Name of the pipeline" } }, required: ["pipelineName"], }, } as const; export async function getPipelineDetails( codePipelineManager: CodePipelineManager, input: { pipelineName: string } ) { const { pipelineName } = input; const codepipeline = codePipelineManager.getCodePipeline(); const response = await codepipeline.getPipeline({ name: pipelineName }).promise(); // Format the pipeline details for better readability const pipelineDetails = { pipeline: { name: response.pipeline?.name || '', roleArn: response.pipeline?.roleArn || '', artifactStore: response.pipeline?.artifactStore, stages: response.pipeline?.stages?.map(stage => ({ name: stage.name, actions: stage.actions?.map(action => ({ name: action.name, actionTypeId: action.actionTypeId, runOrder: action.runOrder, configuration: action.configuration, outputArtifacts: action.outputArtifacts, inputArtifacts: action.inputArtifacts, region: action.region, namespace: action.namespace })) })), version: response.pipeline?.version || 0, metadata: { created: response.metadata?.created?.toISOString() || '', updated: response.metadata?.updated?.toISOString() || '', pipelineArn: response.metadata?.pipelineArn || '' } } }; return { content: [ { type: "text", text: JSON.stringify(pipelineDetails, null, 2), }, ], }; } ================ File: src/tools/get_pipeline_execution_logs.ts ================ import { CodePipelineManager } from "../types.js"; import AWS from 'aws-sdk'; export const getPipelineExecutionLogsSchema = { name: "get_pipeline_execution_logs", description: "Get logs for a pipeline execution", inputSchema: { type: "object", properties: { pipelineName: { type: "string", description: "Name of the pipeline" }, executionId: { type: "string", description: "Execution ID" } }, required: ["pipelineName", "executionId"], }, } as const; export async function getPipelineExecutionLogs( codePipelineManager: CodePipelineManager, input: { pipelineName: string; executionId: string; } ) { const { pipelineName, executionId } = input; const codepipeline = codePipelineManager.getCodePipeline(); const response = await codepipeline.getPipelineExecution({ pipelineName, pipelineExecutionId: executionId }).promise(); // Format the response for better readability // Extract and format the execution details const logs = { pipelineName: response.pipelineExecution?.pipelineName || pipelineName, pipelineVersion: response.pipelineExecution?.pipelineVersion || '1', pipelineExecution: { pipelineExecutionId: response.pipelineExecution?.pipelineExecutionId, status: response.pipelineExecution?.status, artifactRevisions: response.pipelineExecution?.artifactRevisions?.map((revision: AWS.CodePipeline.ArtifactRevision) => ({ name: revision.name, revisionId: revision.revisionId, revisionSummary: revision.revisionSummary, revisionUrl: revision.revisionUrl })) } }; return { content: [ { type: "text", text: JSON.stringify({ logs }, null, 2), }, ], }; } ================ File: src/tools/get_pipeline_metrics.ts ================ import { CodePipelineManager } from "../types.js"; import AWS from 'aws-sdk'; export const getPipelineMetricsSchema = { name: "get_pipeline_metrics", description: "Get performance metrics for a pipeline", inputSchema: { type: "object", properties: { pipelineName: { type: "string", description: "Name of the pipeline" }, period: { type: "number", description: "Time period in seconds for the metrics (default: 86400 - 1 day)", default: 86400 }, startTime: { type: "string", description: "Start time for metrics in ISO format (default: 1 week ago)", format: "date-time" }, endTime: { type: "string", description: "End time for metrics in ISO format (default: now)", format: "date-time" } }, required: ["pipelineName"], }, } as const; export async function getPipelineMetrics( codePipelineManager: CodePipelineManager, input: { pipelineName: string; period?: number; startTime?: string; endTime?: string; } ) { const { pipelineName, period = 86400 } = input; // Set default time range if not provided const endTime = input.endTime ? new Date(input.endTime) : new Date(); const startTime = input.startTime ? new Date(input.startTime) : new Date(endTime.getTime() - 7 * 24 * 60 * 60 * 1000); // 1 week ago // Create CloudWatch client const cloudwatch = new AWS.CloudWatch({ region: codePipelineManager.getCodePipeline().config.region }); // Get execution success/failure metrics const successMetric = await cloudwatch.getMetricStatistics({ Namespace: 'AWS/CodePipeline', MetricName: 'SucceededPipeline', Dimensions: [{ Name: 'PipelineName', Value: pipelineName }], StartTime: startTime, EndTime: endTime, Period: period, Statistics: ['Sum', 'Average', 'Maximum'] }).promise(); const failedMetric = await cloudwatch.getMetricStatistics({ Namespace: 'AWS/CodePipeline', MetricName: 'FailedPipeline', Dimensions: [{ Name: 'PipelineName', Value: pipelineName }], StartTime: startTime, EndTime: endTime, Period: period, Statistics: ['Sum', 'Average', 'Maximum'] }).promise(); // Get execution time metrics const executionTimeMetric = await cloudwatch.getMetricStatistics({ Namespace: 'AWS/CodePipeline', MetricName: 'PipelineExecutionTime', Dimensions: [{ Name: 'PipelineName', Value: pipelineName }], StartTime: startTime, EndTime: endTime, Period: period, Statistics: ['Average', 'Minimum', 'Maximum'] }).promise(); // Calculate success rate const totalSuccessful = successMetric.Datapoints?.reduce((sum, point) => sum + (point.Sum || 0), 0) || 0; const totalFailed = failedMetric.Datapoints?.reduce((sum, point) => sum + (point.Sum || 0), 0) || 0; const totalExecutions = totalSuccessful + totalFailed; const successRate = totalExecutions > 0 ? (totalSuccessful / totalExecutions) * 100 : 0; // Format execution time data const executionTimeData = executionTimeMetric.Datapoints?.map(point => ({ timestamp: point.Timestamp?.toISOString(), average: point.Average, minimum: point.Minimum, maximum: point.Maximum })) || []; // Get pipeline executions for the period const codepipeline = codePipelineManager.getCodePipeline(); const pipelineExecutions = await codepipeline.listPipelineExecutions({ pipelineName, maxResults: 20 // Limit to recent executions }).promise(); // Calculate average stage duration const stageMetrics: Record<string, { count: number, totalDuration: number }> = {}; // We would need to fetch each execution detail to get accurate stage timing // This is a simplified version using the available data for (const execution of pipelineExecutions.pipelineExecutionSummaries || []) { if (execution.startTime && execution.status === 'Succeeded') { const executionDetail = await codepipeline.getPipelineExecution({ pipelineName, pipelineExecutionId: execution.pipelineExecutionId || '' }).promise(); // Get pipeline state to analyze stage timing const pipelineState = await codepipeline.getPipelineState({ name: pipelineName }).promise(); // Process stage information for (const stage of pipelineState.stageStates || []) { if (stage.latestExecution?.status === 'Succeeded' && stage.stageName && stage.actionStates && stage.actionStates.length > 0) { // Find earliest and latest action timestamps let earliestTime: Date | undefined; let latestTime: Date | undefined; for (const action of stage.actionStates) { if (action.latestExecution?.lastStatusChange) { const timestamp = new Date(action.latestExecution.lastStatusChange); if (!earliestTime || timestamp < earliestTime) { earliestTime = timestamp; } if (!latestTime || timestamp > latestTime) { latestTime = timestamp; } } } // Calculate duration if we have both timestamps if (earliestTime && latestTime) { const stageName = stage.stageName; const duration = (latestTime.getTime() - earliestTime.getTime()) / 1000; // in seconds if (!stageMetrics[stageName]) { stageMetrics[stageName] = { count: 0, totalDuration: 0 }; } stageMetrics[stageName].count += 1; stageMetrics[stageName].totalDuration += duration; } } } } } // Calculate average duration for each stage const stageDurations = Object.entries(stageMetrics).map(([stageName, metrics]) => ({ stageName, averageDuration: metrics.count > 0 ? metrics.totalDuration / metrics.count : 0, executionCount: metrics.count })); // Prepare the metrics result const metrics = { pipelineName, timeRange: { startTime: startTime.toISOString(), endTime: endTime.toISOString(), periodSeconds: period }, executionStats: { totalExecutions, successfulExecutions: totalSuccessful, failedExecutions: totalFailed, successRate: successRate.toFixed(2) + '%' }, executionTime: { average: executionTimeMetric.Datapoints?.length ? executionTimeMetric.Datapoints.reduce((sum, point) => sum + (point.Average || 0), 0) / executionTimeMetric.Datapoints.length : 0, minimum: Math.min(...(executionTimeMetric.Datapoints?.map(point => point.Minimum || 0) || [0])), maximum: Math.max(...(executionTimeMetric.Datapoints?.map(point => point.Maximum || 0) || [0])), dataPoints: executionTimeData }, stagePerformance: stageDurations }; return { content: [ { type: "text", text: JSON.stringify(metrics, null, 2), }, ], }; } ================ File: src/tools/get_pipeline_state.ts ================ import { CodePipelineManager } from "../types.js"; import AWS from 'aws-sdk'; export const getPipelineStateSchema = { name: "get_pipeline_state", description: "Get the state of a specific pipeline", inputSchema: { type: "object", properties: { pipelineName: { type: "string", description: "Name of the pipeline" } }, required: ["pipelineName"], }, } as const; export async function getPipelineState(codePipelineManager: CodePipelineManager, input: { pipelineName: string }) { const { pipelineName } = input; const codepipeline = codePipelineManager.getCodePipeline(); const response = await codepipeline.getPipelineState({ name: pipelineName }).promise(); // Convert AWS.CodePipeline.StageState[] to our StageState[] const stageStates = response.stageStates?.map((stage: AWS.CodePipeline.StageState) => { // Convert TransitionState const inboundTransitionState = stage.inboundTransitionState ? { enabled: stage.inboundTransitionState.enabled === undefined ? false : stage.inboundTransitionState.enabled, lastChangedBy: stage.inboundTransitionState.lastChangedBy, lastChangedAt: stage.inboundTransitionState.lastChangedAt, disabledReason: stage.inboundTransitionState.disabledReason } : undefined; // Convert StageExecution const latestExecution = stage.latestExecution ? { pipelineExecutionId: stage.latestExecution.pipelineExecutionId || '', status: stage.latestExecution.status || '' } : undefined; // Convert ActionStates const actionStates = (stage.actionStates || []).map((action: AWS.CodePipeline.ActionState) => { // Convert ActionExecution const latestExecution = action.latestExecution ? { status: action.latestExecution.status || '', summary: action.latestExecution.summary, lastStatusChange: action.latestExecution.lastStatusChange || new Date().toISOString(), token: action.latestExecution.token, externalExecutionId: action.latestExecution.externalExecutionId, externalExecutionUrl: action.latestExecution.externalExecutionUrl, errorDetails: action.latestExecution.errorDetails ? { code: action.latestExecution.errorDetails.code || '', message: action.latestExecution.errorDetails.message || '' } : undefined } : undefined; // Convert ActionRevision const currentRevision = action.currentRevision ? { revisionId: action.currentRevision.revisionId || '', revisionChangeId: action.currentRevision.revisionChangeId || '', created: action.currentRevision.created || new Date().toISOString() } : undefined; return { actionName: action.actionName || '', currentRevision, latestExecution, entityUrl: action.entityUrl }; }); return { stageName: stage.stageName || '', inboundTransitionState, actionStates, latestExecution }; }) || []; const pipelineState = { pipelineName: response.pipelineName || '', pipelineVersion: response.pipelineVersion || 0, stageStates: stageStates, created: response.created?.toISOString() || '', updated: response.updated?.toISOString() || '' }; return { content: [ { type: "text", text: JSON.stringify({ pipelineState }, null, 2), }, ], }; } ================ File: src/tools/list_pipeline_executions.ts ================ import { CodePipelineManager } from "../types.js"; import AWS from 'aws-sdk'; export const listPipelineExecutionsSchema = { name: "list_pipeline_executions", description: "List executions for a specific pipeline", inputSchema: { type: "object", properties: { pipelineName: { type: "string", description: "Name of the pipeline" } }, required: ["pipelineName"], }, } as const; export async function listPipelineExecutions(codePipelineManager: CodePipelineManager, input: { pipelineName: string }) { const { pipelineName } = input; const codepipeline = codePipelineManager.getCodePipeline(); const response = await codepipeline.listPipelineExecutions({ pipelineName }).promise(); const executions = response.pipelineExecutionSummaries?.map((execution: AWS.CodePipeline.PipelineExecutionSummary) => ({ pipelineExecutionId: execution.pipelineExecutionId || '', status: execution.status || '', startTime: execution.startTime?.toISOString() || '', lastUpdateTime: execution.lastUpdateTime?.toISOString() || '', sourceRevisions: execution.sourceRevisions?.map((revision: AWS.CodePipeline.SourceRevision) => ({ name: revision.actionName || '', revisionId: revision.revisionId || '', revisionUrl: revision.revisionUrl || '', revisionSummary: revision.revisionSummary || '' })) || [] })) || []; return { content: [ { type: "text", text: JSON.stringify({ executions }, null, 2), }, ], }; } ================ File: src/tools/list_pipelines.ts ================ import { CodePipelineManager } from "../types.js"; export const listPipelinesSchema = { name: "list_pipelines", description: "List all CodePipeline pipelines", inputSchema: { type: "object", properties: {}, required: [], }, } as const; export async function listPipelines(codePipelineManager: CodePipelineManager) { const codepipeline = codePipelineManager.getCodePipeline(); const response = await codepipeline.listPipelines().promise(); const pipelines = response.pipelines?.map((pipeline: AWS.CodePipeline.PipelineSummary) => ({ name: pipeline.name || '', version: pipeline.version || 0, created: pipeline.created?.toISOString() || '', updated: pipeline.updated?.toISOString() || '' })) || []; return { content: [ { type: "text", text: JSON.stringify({ pipelines }, null, 2), }, ], }; } ================ File: src/tools/retry_stage.ts ================ import { CodePipelineManager } from "../types.js"; export const retryStageSchema = { name: "retry_stage", description: "Retry a failed stage", inputSchema: { type: "object", properties: { pipelineName: { type: "string", description: "Name of the pipeline" }, stageName: { type: "string", description: "Name of the stage" }, pipelineExecutionId: { type: "string", description: "Execution ID" } }, required: ["pipelineName", "stageName", "pipelineExecutionId"], }, } as const; export async function retryStage( codePipelineManager: CodePipelineManager, input: { pipelineName: string; stageName: string; pipelineExecutionId: string; } ) { const { pipelineName, stageName, pipelineExecutionId } = input; const codepipeline = codePipelineManager.getCodePipeline(); await codepipeline.retryStageExecution({ pipelineName, stageName, pipelineExecutionId, retryMode: 'FAILED_ACTIONS' }).promise(); return { content: [ { type: "text", text: JSON.stringify({ message: "Stage retry initiated successfully" }, null, 2), }, ], }; } ================ File: src/tools/stop_pipeline_execution.ts ================ import { CodePipelineManager } from "../types.js"; export const stopPipelineExecutionSchema = { name: "stop_pipeline_execution", description: "Stop a pipeline execution", inputSchema: { type: "object", properties: { pipelineName: { type: "string", description: "Name of the pipeline" }, executionId: { type: "string", description: "Execution ID" }, reason: { type: "string", description: "Optional reason for stopping" } }, required: ["pipelineName", "executionId"], }, } as const; export async function stopPipelineExecution( codePipelineManager: CodePipelineManager, input: { pipelineName: string; executionId: string; reason?: string; } ) { const { pipelineName, executionId, reason } = input; const codepipeline = codePipelineManager.getCodePipeline(); await codepipeline.stopPipelineExecution({ pipelineName, pipelineExecutionId: executionId, reason: reason || 'Stopped by user', abandon: false }).promise(); return { content: [ { type: "text", text: JSON.stringify({ message: "Pipeline execution stopped successfully" }, null, 2), }, ], }; } ================ File: src/tools/tag_pipeline_resource.ts ================ import { CodePipelineManager } from "../types.js"; export const tagPipelineResourceSchema = { name: "tag_pipeline_resource", description: "Add or update tags for a pipeline resource", inputSchema: { type: "object", properties: { pipelineName: { type: "string", description: "Name of the pipeline" }, tags: { type: "array", description: "List of tags to add or update", items: { type: "object", properties: { key: { type: "string", description: "Tag key" }, value: { type: "string", description: "Tag value" } }, required: ["key", "value"] } } }, required: ["pipelineName", "tags"], }, } as const; export async function tagPipelineResource( codePipelineManager: CodePipelineManager, input: { pipelineName: string; tags: Array<{ key: string; value: string }>; } ) { const { pipelineName, tags } = input; const codepipeline = codePipelineManager.getCodePipeline(); // First, get the pipeline ARN const pipelineResponse = await codepipeline.getPipeline({ name: pipelineName }).promise(); const resourceArn = pipelineResponse.metadata?.pipelineArn; if (!resourceArn) { throw new Error(`Could not find ARN for pipeline: ${pipelineName}`); } // Tag the resource await codepipeline.tagResource({ resourceArn, tags: tags.map(tag => ({ key: tag.key, value: tag.value })) }).promise(); return { content: [ { type: "text", text: JSON.stringify({ message: "Pipeline resource tagged successfully", resourceArn, tags }, null, 2), }, ], }; } ================ File: src/tools/trigger_pipeline.ts ================ import { CodePipelineManager } from "../types.js"; export const triggerPipelineSchema = { name: "trigger_pipeline", description: "Trigger a pipeline execution", inputSchema: { type: "object", properties: { pipelineName: { type: "string", description: "Name of the pipeline" } }, required: ["pipelineName"], }, } as const; export async function triggerPipeline( codePipelineManager: CodePipelineManager, input: { pipelineName: string; } ) { const { pipelineName } = input; const codepipeline = codePipelineManager.getCodePipeline(); const response = await codepipeline.startPipelineExecution({ name: pipelineName }).promise(); const executionId = response.pipelineExecutionId || ''; return { content: [ { type: "text", text: JSON.stringify({ message: "Pipeline triggered successfully", executionId }, null, 2), }, ], }; } ================ File: src/types/codepipeline.ts ================ export interface PipelineSummary { name: string; version: number; created: string; updated: string; } export interface StageExecution { pipelineExecutionId: string; status: string; } export interface ActionRevision { revisionId: string; revisionChangeId: string; created: string | Date; } export interface ActionExecutionError { code: string; message: string; } export interface ActionExecution { status: string; summary?: string; lastStatusChange: string | Date; token?: string; externalExecutionId?: string; externalExecutionUrl?: string; errorDetails?: ActionExecutionError; } export interface ActionState { actionName: string; currentRevision?: ActionRevision; latestExecution?: ActionExecution; entityUrl?: string; } export interface TransitionState { enabled: boolean; lastChangedBy?: string; lastChangedAt?: string | Date; disabledReason?: string; } export interface StageExecution { pipelineExecutionId: string; status: string; startTime?: Date; lastUpdateTime?: Date; } export interface StageState { stageName: string; inboundTransitionState?: TransitionState; actionStates: ActionState[]; latestExecution?: StageExecution; } export interface PipelineState { pipelineName: string; pipelineVersion: number; stageStates: StageState[]; created: string; updated: string; } export interface PipelineExecution { pipelineExecutionId: string; status: string; artifactRevisions?: { name: string; revisionId: string; revisionChangeIdentifier: string; revisionSummary: string; created: string; revisionUrl: string; }[]; } export interface ApprovalRequest { pipelineName: string; stageName: string; actionName: string; token: string; } export interface RetryStageRequest { pipelineName: string; stageName: string; pipelineExecutionId: string; } export interface TriggerPipelineRequest { pipelineName: string; } ================ File: src/types/modelcontextprotocol-sdk.d.ts ================ declare module '@modelcontextprotocol/sdk' { export interface ParamDefinition { type: string; description: string; required?: boolean; enum?: string[]; properties?: Record<string, ParamDefinition>; items?: ParamDefinition; } export interface ToolDefinition { name: string; description: string; parameters: Record<string, ParamDefinition | string>; } export interface Tool { name: string; parameters: Record<string, any>; } export interface ErrorDetail { message: string; code: string; details?: any; } export interface SuccessResult { result: any; } export interface ErrorResult { error: ErrorDetail; } export interface MCPServerConfig { serverName: string; serverVersion: string; serverDescription: string; toolDefinitions: ToolDefinition[]; } export class MCPRouter { constructor(server: MCPServer); router: any; } export class MCPServer { constructor(config: MCPServerConfig); protected handleTool(tool: Tool): Promise<SuccessResult | ErrorResult>; } } ================ File: src/utils/env.ts ================ import dotenv from 'dotenv'; import { resolve } from 'path'; import { existsSync } from 'fs'; /** * Load environment variables from .env file */ export function loadEnv(): void { const envPath = resolve(process.cwd(), '.env'); if (existsSync(envPath)) { console.log(`Loading environment variables from ${envPath}`); const result = dotenv.config({ path: envPath }); if (result.error) { console.error('Error loading .env file:', result.error); } else { console.log('Environment variables loaded successfully'); } } else { console.warn(`No .env file found at ${envPath}`); } } /** * Get an environment variable with a fallback value */ export function getEnv(key: string, defaultValue?: string): string { return process.env[key] || defaultValue || ''; } ================ File: src/index.ts ================ #!/usr/bin/env node import { Server } from "@modelcontextprotocol/sdk/server/index.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { loadEnv, getEnv } from './utils/env.js'; import { CodePipelineManager } from "./types.js"; import { serverConfig } from "./config/server-config.js"; // Import tool schemas and handlers import { listPipelines, listPipelinesSchema } from "./tools/list_pipelines.js"; import { getPipelineState, getPipelineStateSchema } from "./tools/get_pipeline_state.js"; import { listPipelineExecutions, listPipelineExecutionsSchema } from "./tools/list_pipeline_executions.js"; import { approveAction, approveActionSchema } from "./tools/approve_action.js"; import { retryStage, retryStageSchema } from "./tools/retry_stage.js"; import { triggerPipeline, triggerPipelineSchema } from "./tools/trigger_pipeline.js"; import { getPipelineExecutionLogs, getPipelineExecutionLogsSchema } from "./tools/get_pipeline_execution_logs.js"; import { stopPipelineExecution, stopPipelineExecutionSchema } from "./tools/stop_pipeline_execution.js"; // Import new tool schemas and handlers import { getPipelineDetails, getPipelineDetailsSchema } from "./tools/get_pipeline_details.js"; import { tagPipelineResource, tagPipelineResourceSchema } from "./tools/tag_pipeline_resource.js"; import { createPipelineWebhook, createPipelineWebhookSchema } from "./tools/create_pipeline_webhook.js"; import { getPipelineMetrics, getPipelineMetricsSchema } from "./tools/get_pipeline_metrics.js"; import { ListResourcesRequestSchema, ReadResourceRequestSchema, ListToolsRequestSchema, CallToolRequestSchema, ErrorCode, McpError, } from "@modelcontextprotocol/sdk/types.js"; // Load environment variables loadEnv(); // Log configuration for debugging console.log('----- AWS CodePipeline MCP Server Configuration -----'); console.log('PORT:', getEnv('PORT', '3000')); console.log('AWS_REGION:', getEnv('AWS_REGION')); console.log('AWS_ACCESS_KEY_ID:', getEnv('AWS_ACCESS_KEY_ID') ? '***' : 'undefined'); console.log('AWS_SECRET_ACCESS_KEY:', getEnv('AWS_SECRET_ACCESS_KEY') ? '***' : 'undefined'); console.log('-----------------------------------------------------'); // Initialize CodePipeline manager const codePipelineManager = new CodePipelineManager(); // Create server instance const server = new Server( { name: serverConfig.name, version: serverConfig.version, }, { capabilities: { tools: { listChanged: true }, resources: { listChanged: false }, prompts: { listChanged: false } }, instructions: "AWS CodePipeline MCP Server for interacting with AWS CodePipeline services" } ); // Set up tools handler server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools: [ listPipelinesSchema, getPipelineStateSchema, listPipelineExecutionsSchema, approveActionSchema, retryStageSchema, triggerPipelineSchema, getPipelineExecutionLogsSchema, stopPipelineExecutionSchema, // Add new tool schemas getPipelineDetailsSchema, tagPipelineResourceSchema, createPipelineWebhookSchema, getPipelineMetricsSchema, ], }; }); // Set up tool call handler server.setRequestHandler(CallToolRequestSchema, async (request: { params: { name: string; _meta?: any; arguments?: Record<string, any> }; method: string }) => { try { const { name, arguments: input = {} } = request.params; switch (name) { case "list_pipelines": { return await listPipelines(codePipelineManager); } case "get_pipeline_state": { return await getPipelineState(codePipelineManager, input as { pipelineName: string }); } case "list_pipeline_executions": { return await listPipelineExecutions(codePipelineManager, input as { pipelineName: string }); } case "approve_action": { return await approveAction(codePipelineManager, input as { pipelineName: string; stageName: string; actionName: string; token: string; approved: boolean; comments?: string; }); } case "retry_stage": { return await retryStage(codePipelineManager, input as { pipelineName: string; stageName: string; pipelineExecutionId: string; }); } case "trigger_pipeline": { return await triggerPipeline(codePipelineManager, input as { pipelineName: string; }); } case "get_pipeline_execution_logs": { return await getPipelineExecutionLogs(codePipelineManager, input as { pipelineName: string; executionId: string; }); } case "stop_pipeline_execution": { return await stopPipelineExecution(codePipelineManager, input as { pipelineName: string; executionId: string; reason?: string; }); } // Add handlers for new tools case "get_pipeline_details": { return await getPipelineDetails(codePipelineManager, input as { pipelineName: string; }); } case "tag_pipeline_resource": { return await tagPipelineResource(codePipelineManager, input as { pipelineName: string; tags: Array<{ key: string; value: string }>; }); } case "create_pipeline_webhook": { return await createPipelineWebhook(codePipelineManager, input as { pipelineName: string; webhookName: string; targetAction: string; authentication: string; authenticationConfiguration?: { SecretToken?: string; AllowedIpRange?: string; }; filters?: Array<{ jsonPath: string; matchEquals?: string; }>; }); } case "get_pipeline_metrics": { return await getPipelineMetrics(codePipelineManager, input as { pipelineName: string; period?: number; startTime?: string; endTime?: string; }); } default: throw new McpError(ErrorCode.InvalidRequest, `Unknown tool: ${name}`); } } catch (error) { if (error instanceof McpError) throw error; throw new McpError( ErrorCode.InternalError, `Tool execution failed: ${error}` ); } }); // Resources handlers (empty for now, can be implemented if needed) server.setRequestHandler(ListResourcesRequestSchema, async () => ({ resources: [] })); server.setRequestHandler(ReadResourceRequestSchema, async () => { throw new McpError(ErrorCode.InvalidRequest, "No resources available"); }); // Create transport and start server const transport = new StdioServerTransport(); // Connect the server to the transport server.connect(transport); // Import and initialize the HTTP server import express from 'express'; import bodyParser from 'body-parser'; import cors from 'cors'; import codePipelineRoutes from './routes/codepipeline.routes.js'; const app = express(); const PORT = parseInt(getEnv('PORT', '3000')); // Configure middleware app.use(cors()); app.use(bodyParser.json()); app.use(bodyParser.urlencoded({ extended: true })); // Configure routes app.use('/api', codePipelineRoutes); // Health check route app.get('/health', (req, res) => { res.status(200).json({ status: 'ok' }); }); // Start the HTTP server app.listen(PORT, () => { console.log(`HTTP server listening on port ${PORT}`); }); ["SIGINT", "SIGTERM"].forEach((signal) => { process.on(signal, async () => { console.log(`Received ${signal}, shutting down...`); await server.close(); process.exit(0); }); }); // Log server start console.log("AWS CodePipeline MCP Server started successfully"); console.log(`Server running with AWS region: ${getEnv('AWS_REGION')}`); ================ File: src/mcp-server.ts ================ import express, { Request, Response } from 'express'; import bodyParser from 'body-parser'; import cors from 'cors'; import { Server as BaseMCPServer } from "@modelcontextprotocol/sdk/server/index.js"; import codePipelineRoutes from './routes/codepipeline.routes'; // Define types that match the SDK expectations interface Tool { name: string; parameters: any; } interface ParamDefinition { type: string; description: string; required?: boolean; } interface ToolDefinition { name: string; description: string; parameters: Record<string, ParamDefinition>; } interface SuccessResult { status: 'success'; result: any; } interface ErrorResult { status: 'error'; error: { code: string; message: string; }; } // Define the MCPRouter class since it's not directly exported class MCPRouter { router: express.Router; constructor(server: any) { this.router = express.Router(); this.setupRoutes(server); } private setupRoutes(server: any): void { this.router.post('/mcp', (req, res) => { // Handle MCP requests res.json({ status: 'success' }); }); } } // Define the tool definitions following MCP standard const toolDefinitions: ToolDefinition[] = [ { name: 'listPipelines', description: 'List all CodePipeline pipelines', parameters: {} }, { name: 'getPipelineState', description: 'Get the state of a specific pipeline', parameters: { pipelineName: { type: 'string', description: 'Name of the pipeline' } as ParamDefinition } }, { name: 'listPipelineExecutions', description: 'List executions for a specific pipeline', parameters: { pipelineName: { type: 'string', description: 'Name of the pipeline' } as ParamDefinition } }, { name: 'approveAction', description: 'Approve or reject a manual approval action', parameters: { pipelineName: { type: 'string', description: 'Name of the pipeline' } as ParamDefinition, stageName: { type: 'string', description: 'Name of the stage' } as ParamDefinition, actionName: { type: 'string', description: 'Name of the action' } as ParamDefinition, token: { type: 'string', description: 'Approval token' } as ParamDefinition, approved: { type: 'boolean', description: 'Boolean indicating approval or rejection' } as ParamDefinition, comments: { type: 'string', description: 'Optional comments', required: false } as ParamDefinition } }, { name: 'retryStage', description: 'Retry a failed stage', parameters: { pipelineName: { type: 'string', description: 'Name of the pipeline' } as ParamDefinition, stageName: { type: 'string', description: 'Name of the stage' } as ParamDefinition, pipelineExecutionId: { type: 'string', description: 'Execution ID' } as ParamDefinition } }, { name: 'triggerPipeline', description: 'Trigger a pipeline execution', parameters: { pipelineName: { type: 'string', description: 'Name of the pipeline' } as ParamDefinition } }, { name: 'getPipelineExecutionLogs', description: 'Get logs for a pipeline execution', parameters: { pipelineName: { type: 'string', description: 'Name of the pipeline' } as ParamDefinition, executionId: { type: 'string', description: 'Execution ID' } as ParamDefinition } }, { name: 'stopPipelineExecution', description: 'Stop a pipeline execution', parameters: { pipelineName: { type: 'string', description: 'Name of the pipeline' } as ParamDefinition, executionId: { type: 'string', description: 'Execution ID' } as ParamDefinition, reason: { type: 'string', description: 'Optional reason for stopping', required: false } as ParamDefinition } } ]; export class MCPServer extends BaseMCPServer { private app: express.Application; private port: number; private mcpRouter: MCPRouter; constructor(port: number = 3000) { // Initialize the MCP server with tool definitions super({ name: 'AWS CodePipeline MCP Server', version: '1.0.0', description: 'MCP server for AWS CodePipeline integration', tools: toolDefinitions }); this.app = express(); this.port = port; this.mcpRouter = new MCPRouter(this); this.configureMiddleware(); this.configureRoutes(); } private configureMiddleware(): void { this.app.use(cors()); this.app.use(bodyParser.json()); this.app.use(bodyParser.urlencoded({ extended: true })); } private configureRoutes(): void { // Mount the MCP routes this.app.use('/', this.mcpRouter.router); // API routes this.app.use('/api', codePipelineRoutes); // Health check route this.app.get('/health', (req: Request, res: Response) => { res.status(200).json({ status: 'ok' }); }); } // Override the MCP handleTool method to implement tool execution protected async handleTool(tool: Tool): Promise<SuccessResult | ErrorResult> { try { // Import the controller directly const { CodePipelineController } = require('./controllers/codepipeline.controller'); const controller = new CodePipelineController(); // Process tool based on name switch (tool.name) { case 'listPipelines': { const result = await controller.listPipelines(); return { status: 'success', result }; } case 'getPipelineState': { const params = tool.parameters as { pipelineName: string }; const result = await controller.getPipelineState(params.pipelineName); return { status: 'success', result }; } case 'listPipelineExecutions': { const params = tool.parameters as { pipelineName: string }; const result = await controller.listPipelineExecutions(params.pipelineName); return { status: 'success', result }; } case 'approveAction': { const params = tool.parameters as { pipelineName: string; stageName: string; actionName: string; token: string; approved: boolean; comments?: string }; const result = await controller.approveAction( params.pipelineName, params.stageName, params.actionName, params.token, params.approved, params.comments ); return { status: 'success', result }; } case 'retryStage': { const params = tool.parameters as { pipelineName: string; stageName: string; pipelineExecutionId: string }; const result = await controller.retryStage( params.pipelineName, params.stageName, params.pipelineExecutionId ); return { status: 'success', result }; } case 'triggerPipeline': { const params = tool.parameters as { pipelineName: string }; const result = await controller.triggerPipeline(params.pipelineName); return { status: 'success', result }; } case 'getPipelineExecutionLogs': { const params = tool.parameters as { pipelineName: string; executionId: string }; const result = await controller.getPipelineExecutionLogs( params.pipelineName, params.executionId ); return { status: 'success', result }; } case 'stopPipelineExecution': { const params = tool.parameters as { pipelineName: string; executionId: string; reason?: string }; const result = await controller.stopPipelineExecution( params.pipelineName, params.executionId, params.reason || 'Stopped by user' ); return { status: 'success', result }; } default: return { status: 'error', error: { code: 'UNKNOWN_TOOL', message: `Unknown tool: ${tool.name}` } }; } } catch (error: any) { console.error('Error handling tool:', error); return { status: 'error', error: { code: 'INTERNAL_SERVER_ERROR', message: error.message || 'Internal server error' } }; } } public start(): void { this.app.listen(this.port, () => { console.log(`MCP server running on port ${this.port}`); console.log(`Server description available at http://localhost:${this.port}/`); console.log(`Health check available at http://localhost:${this.port}/health`); console.log(`MCP tools endpoint available at http://localhost:${this.port}/tools`); }); } } ================ File: src/types.ts ================ import AWS from 'aws-sdk'; import { getEnv } from './utils/env.js'; export class CodePipelineManager { private codepipeline: AWS.CodePipeline; constructor() { // Get AWS configuration from environment variables const region = getEnv('AWS_REGION', 'us-west-2'); // Default to us-west-2 if not provided const accessKeyId = getEnv('AWS_ACCESS_KEY_ID'); const secretAccessKey = getEnv('AWS_SECRET_ACCESS_KEY'); // Configure AWS SDK const awsConfig: AWS.ConfigurationOptions = { region }; // Add credentials if provided if (accessKeyId && secretAccessKey) { awsConfig.credentials = new AWS.Credentials({ accessKeyId, secretAccessKey }); } // Update AWS SDK configuration AWS.config.update(awsConfig); console.log(`AWS CodePipeline manager initialized with region: ${region}`); this.codepipeline = new AWS.CodePipeline(awsConfig); } getCodePipeline(): AWS.CodePipeline { return this.codepipeline; } } // Types for the API responses export interface PipelineSummary { name: string; version: number; created: string; updated: string; } export interface TransitionState { enabled: boolean; lastChangedBy?: string; lastChangedAt?: Date | string; disabledReason?: string; } export interface StageExecution { pipelineExecutionId: string; status: string; } export interface ErrorDetails { code: string; message: string; } export interface ActionExecution { status: string; summary?: string; lastStatusChange: Date | string; token?: string; externalExecutionId?: string; externalExecutionUrl?: string; errorDetails?: ErrorDetails; } export interface ActionRevision { revisionId: string; revisionChangeId: string; created: Date | string; } export interface ActionState { actionName: string; currentRevision?: ActionRevision; latestExecution?: ActionExecution; entityUrl?: string; } export interface StageState { stageName: string; inboundTransitionState?: TransitionState; actionStates: ActionState[]; latestExecution?: StageExecution; } export interface PipelineState { pipelineName: string; pipelineVersion: number; stageStates: StageState[]; created: string; updated: string; } export interface ArtifactRevision { name: string; revisionId: string; revisionChangeIdentifier: string; revisionSummary: string; created: string; revisionUrl: string; } export interface PipelineExecution { pipelineExecutionId: string; status: string; artifactRevisions: ArtifactRevision[]; } export interface ApprovalRequest { pipelineName: string; stageName: string; actionName: string; token: string; } export interface RetryStageRequest { pipelineName: string; stageName: string; pipelineExecutionId: string; } export interface TriggerPipelineRequest { pipelineName: string; } ================ File: .env.example ================ AWS_REGION=us-east-1 AWS_ACCESS_KEY_ID=your_access_key_id AWS_SECRET_ACCESS_KEY=your_secret_access_key PORT=3000 ================ File: .gitignore ================ # Dependency directories node_modules/ jspm_packages/ # Build outputs dist/ build/ out/ *.tsbuildinfo # Environment variables and secrets .env .env.local .env.development.local .env.test.local .env.production.local *.pem *.key *.cert *.crt *.p12 .aws/ aws-credentials.json # Logs logs *.log npm-debug.log* yarn-debug.log* yarn-error.log* lerna-debug.log* .pnpm-debug.log* # IDE specific files .idea/ .vscode/ *.swp *.swo .DS_Store .project .classpath .settings/ *.sublime-workspace *.sublime-project # Testing coverage/ .nyc_output/ # Temporary files tmp/ temp/ *.tmp *.bak # Misc .cache/ .npm .eslintcache .stylelintcache .yarn/cache .yarn/unplugged .yarn/build-state.yml .yarn/install-state.gz .pnp.* # AWS specific .aws-sam/ samconfig.toml cloudformation-template-*.json # MCP specific mcp_config.json ================ File: ARCHITECTURE.md ================ # MCP Server for AWS CodePipeline - Beginner's Guide ## 1. Project Overview ### What is an MCP Server? A Model Context Protocol (MCP) server creates a bridge between Cascade (the AI assistant in Windsurf IDE) and external services like AWS. Think of it as a translator that converts natural language requests into API calls to specific services. **Example**: When you ask Cascade "List all my CodePipeline pipelines," the MCP server translates this into an AWS API call and returns the results. ### Goals - Create a Model Context Protocol (MCP) server that allows Cascade to interact with AWS CodePipeline - Enable natural language control of pipeline operations through Windsurf - Provide a secure and efficient bridge between the Windsurf IDE and AWS services ### What You'll Build - A server that can list and manage AWS CodePipeline resources - Functionality to view pipeline states, executions, and details - Capability to trigger pipeline executions, approvals, and other operations - Proper authentication and error handling mechanisms ### Key Technologies - **TypeScript**: Used for type-safe development (don't worry if you're new to it!) - **Node.js**: Runtime environment for JavaScript - **Express**: Simple HTTP server framework - **AWS SDK**: Library to interact with AWS services - **Model Context Protocol SDK**: Framework for MCP implementation - **ES Modules**: Modern JavaScript module system ### Visual Overview ``` User → Windsurf → Cascade → MCP Server → AWS CodePipeline ↑ | | | └─────────────── Results ────────────────┘ ``` ## 2. Getting Started: Step by Step ### Setting Up Your Project 1. **Create a new project folder** ```bash mkdir my-mcp-server cd my-mcp-server ``` 2. **Initialize Node.js project** ```bash npm init -y ``` 3. **Install dependencies** ```bash npm install @modelcontextprotocol/sdk express aws-sdk cors body-parser dotenv npm install --save-dev typescript @types/express @types/node @types/cors @types/body-parser ``` 4. **Create TypeScript configuration** Create a file named `tsconfig.json`: ```json { "compilerOptions": { "target": "ES2020", "module": "NodeNext", "moduleResolution": "NodeNext", "esModuleInterop": true, "outDir": "./dist", "strict": true }, "include": ["src/**/*"] } ``` 5. **Add scripts to package.json** Update your `package.json` to include: ```json "scripts": { "build": "tsc", "start": "node dist/index.js", "dev": "ts-node src/index.ts" } ``` ### Folder Structure Overview Create this folder structure step by step: ``` my-mcp-server/ ├── src/ │ ├── config/ # Server configuration │ ├── controllers/ # HTTP request handlers │ ├── routes/ # API routes │ ├── services/ # Business logic & AWS API calls │ ├── utils/ # Helper functions │ ├── types/ # TypeScript interfaces │ ├── index.ts # Entry point │ └── mcp-server.ts # MCP implementation ├── .env # Environment variables ├── .gitignore # Git ignore rules └── package.json # Project config ``` **Pro Tip**: Don't worry about creating all files at once. We'll build them one by one as we progress. ### How the Files Work Together ``` ┌─────────────┐ ┌──────────────┐ ┌────────────┐ ┌─────────┐ │ index.ts │────▶│ mcp-server.ts│────▶│ controllers │────▶│services │────▶ AWS └─────────────┘ └──────────────┘ └────────────┘ └─────────┘ ▲ ▲ ▲ ▲ │ │ │ │ └─ Starts both ◀────┘ │ │ servers Uses interfaces ────┘ │ from types/ Calls methods ───┘ ``` ## 3. Building Your First MCP Server Components ### Core Concepts Made Simple Let's break down each key component with simple explanations and examples: ### Step 1: Define Your Environment Helper Create `src/utils/env.ts` to load environment variables: ```typescript // src/utils/env.ts import dotenv from 'dotenv'; import path from 'path'; export function loadEnv(): void { // Load .env file if it exists dotenv.config(); console.log('Environment variables loaded'); } // Helper to get environment variables export function getEnv(key: string, defaultValue: string = ''): string { return process.env[key] || defaultValue; } ``` ### Step 2: Create Server Configuration Create `src/config/server-config.ts` to define your server's metadata: ```typescript // src/config/server-config.ts export const serverConfig = { name: "my-aws-mcp-server", version: "1.0.0", displayName: "My AWS MCP Server", description: "MCP server for interacting with AWS services", publisher: "Your Name", license: "MIT" }; ``` ### Step 3: Define Your AWS Service Create `src/services/aws-service.ts` to handle AWS SDK calls: ```typescript // src/services/aws-service.ts import AWS from 'aws-sdk'; import { getEnv } from '../utils/env.js'; export class AWSService { private awsService: AWS.Service; constructor() { const region = getEnv('AWS_REGION', 'us-west-2'); // Initialize the specific AWS SDK service you need // For example, for S3: this.awsService = new AWS.S3({ region }); console.log(`AWS service initialized with region: ${region}`); } // Add methods for each AWS operation // Example for S3 listBuckets: async listItems() { try { // Replace this with your specific AWS service call const result = await this.awsService.listBuckets().promise(); return result; } catch (error) { console.error('Error listing items:', error); throw error; } } } ``` ### Step 4: Create a Controller Create `src/controllers/aws-controller.ts` to handle HTTP requests: ```typescript // src/controllers/aws-controller.ts import { Request, Response } from 'express'; import { AWSService } from '../services/aws-service.js'; export class AWSController { private awsService: AWSService; constructor() { this.awsService = new AWSService(); } // Example controller method listItems = async (req: Request, res: Response): Promise<void> => { try { const items = await this.awsService.listItems(); res.status(200).json({ items }); } catch (error) { console.error('Error in listItems controller:', error); res.status(500).json({ error: 'Failed to list items' }); } }; } ``` ### Step 5: Define Routes Create `src/routes/aws-routes.ts` to define API endpoints: ```typescript // src/routes/aws-routes.ts import { Router } from 'express'; import { AWSController } from '../controllers/aws-controller.js'; const router = Router(); const awsController = new AWSController(); // Define routes router.get('/items', awsController.listItems); export default router; ``` ### Understanding MCP Interfaces Here are the key interfaces you'll use, simplified: #### Tool Interface ```typescript // This is what Cascade sends to your MCP server interface Tool { name: string; // The name of the tool (e.g., "list_buckets") parameters: { // The parameters the user provided [key: string]: any // E.g., { "region": "us-west-2" } }; } ``` #### Tool Definition Interface ```typescript // This tells Cascade what tools your MCP server provides interface ToolDefinition { name: string; // Tool name (e.g., "list_buckets") description: string; // Human-readable description parameters: { // What parameters this tool accepts type: "object", properties: { // Define each parameter paramName: { type: "string", // Parameter type description: "What this parameter does" } // More parameters... } } } ``` ## 4. Creating the MCP Server Implementation ### Step 6: Implement the MCP Server Create `src/mcp-server.ts` - this is the heart of your MCP implementation: ```typescript // src/mcp-server.ts import { Server as BaseMCPServer } from "@modelcontextprotocol/sdk/server/index.js"; // Define your tools const toolDefinitions = [ { name: "list_items", // Tool name for Cascade to call description: "List all items", // Human-readable description parameters: { type: "object", properties: {} } }, // Add more tool definitions here ]; // Create the MCP server class export class MCPServer extends BaseMCPServer { constructor() { super(); this.registerTools(); } private registerTools() { // Register each tool with its handler this.router.register("list_items", this.handleTool.bind(this)); // Register more tools here } // Handle tool execution async handleTool(tool) { try { // Call your API server const response = await fetch(`http://localhost:3000/api/${tool.name.replace('_', '/')}`, { method: "GET", headers: { 'Content-Type': 'application/json' } }); const data = await response.json(); // Return success response return { status: 'success', result: data }; } catch (error) { console.error(`Error executing tool ${tool.name}:`, error); // Return error response return { status: 'error', error: { code: 'EXECUTION_ERROR', message: `Failed to execute tool: ${error.message}` } }; } } // Return tool definitions to the MCP framework getToolDefinitions() { return toolDefinitions; } } ``` ### Step 7: Create the Main Entry Point Create `src/index.ts` to start both servers: ```typescript // src/index.ts import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { loadEnv, getEnv } from './utils/env.js'; import express from 'express'; import bodyParser from 'body-parser'; import cors from 'cors'; import { MCPServer } from './mcp-server.js'; import awsRoutes from './routes/aws-routes.js'; // Load environment variables loadEnv(); // Create and start the MCP server const server = new MCPServer(); const transport = new StdioServerTransport(); server.connect(transport); // Create and start the HTTP server const app = express(); const PORT = parseInt(getEnv('PORT', '3000')); // Configure middleware app.use(cors()); app.use(bodyParser.json()); // Configure routes app.use('/api', awsRoutes); // Health check route app.get('/health', (req, res) => { res.status(200).json({ status: 'ok' }); }); // Start the HTTP server app.listen(PORT, () => { console.log(`HTTP server listening on port ${PORT}`); }); console.log("MCP Server started successfully"); ``` ### How the Processing Flow Works (Made Simple) ``` 1. User: "List my AWS items" ↓ 2. Cascade understands and calls your tool "list_items" ↓ 3. MCP Server receives the request through StdioServerTransport ↓ 4. MCPServer.handleTool() makes an HTTP request to your Express server ↓ 5. Express routes the request to your controller ↓ 6. Controller calls your service ↓ 7. Service makes AWS API call ↓ 8. Results travel back up the chain to the user ``` **Key Point**: Your MCP server actually has TWO servers running: 1. The MCP protocol server (communicates with Cascade) 2. An HTTP server (handles API requests from the MCP server) ## 5. Final Steps and Troubleshooting ### Step 8: Configure Environment Variables Create a `.env` file in your project root: ``` PORT=3000 AWS_REGION=us-west-2 AWS_ACCESS_KEY_ID=your_access_key_here AWS_SECRET_ACCESS_KEY=your_secret_key_here ``` ### Step 9: Create a .gitignore File Create a `.gitignore` file to prevent sensitive information from being committed: ``` node_modules/ dist/ .env *.log .DS_Store ``` ### Step 10: Build and Run Your Server ```bash # Build your TypeScript code npm run build # Start your server npm start ``` ### Step 11: Configure Windsurf Update your Windsurf MCP configuration (typically in `~/.codeium/windsurf/mcp_config.json`): ```json { "mcpServers": { "your-service-name": { "command": "npx", "args": [ "-y", "path/to/your-mcp-server/dist/index.js" ], "env": { "AWS_REGION": "your-region", "AWS_ACCESS_KEY_ID": "your-access-key", "AWS_SECRET_ACCESS_KEY": "your-secret-key" } } } } ``` ### Adding New AWS Operations (Simple Steps) 1. **Add a service method**: Create a new method in your service class 2. **Add a controller method**: Create a handler in your controller 3. **Add a route**: Connect the controller to an API endpoint 4. **Add a tool definition**: Tell Cascade about your new capability 5. **Register the tool handler**: Connect the tool to your implementation ### Common Issues and Solutions #### 1. "Cannot find module" errors **Problem**: TypeScript can't find imported modules **Solution**: - Make sure you're using `.js` extensions in imports (ES modules requirement) - Check that the module is installed in package.json #### 2. AWS SDK errors **Problem**: AWS operations fail with authentication errors **Solution**: - Verify your AWS credentials in the .env file - Check that the region is correct - Ensure proper IAM permissions for the AWS operations #### 3. MCP Server not communicating with Windsurf **Problem**: Cascade can't access your tools **Solution**: - Check the path to your index.js file in mcp_config.json - Verify that you're returning proper tool definitions - Make sure both your Express and MCP servers are running #### 4. "TypeError: Cannot read property of undefined" **Problem**: Trying to access properties that don't exist **Solution**: - Use optional chaining (`?.`) when accessing nested properties - Add null checks before accessing properties - Add console.log statements to debug object structures ### What Next? Once you have your basic MCP server working: 1. **Expand functionality**: Add more AWS operations 2. **Refine error handling**: Provide detailed error messages 3. **Add validation**: Validate inputs before processing 4. **Implement testing**: Add unit and integration tests 5. **Improve documentation**: Add comments and API docs --- By following this beginner-friendly guide, you can create your own MCP server for any AWS service. Start with something simple, get it working, and then gradually expand its capabilities. Good luck with your MCP development journey! ================ File: package.json ================ { "name": "mcp-codepipeline-server", "version": "1.0.0", "description": "MCP server for AWS CodePipeline integration", "type": "module", "main": "dist/index.js", "scripts": { "build": "tsc", "start": "node dist/index.js", "dev": "tsc -w & nodemon dist/index.js", "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": [ "aws", "codepipeline", "mcp", "server" ], "author": "Cuong T Nguyen", "license": "ISC", "devDependencies": { "@types/cors": "^2.8.17", "@types/express": "^5.0.0", "@types/node": "^22.13.10", "nodemon": "^3.1.0", "typescript": "^5.8.2" }, "dependencies": { "@modelcontextprotocol/sdk": "^1.7.0", "aws-sdk": "^2.1692.0", "body-parser": "^1.20.3", "cors": "^2.8.5", "dotenv": "^16.4.7", "express": "^4.21.2" } } ================ File: README.md ================ # AWS CodePipeline MCP Server This is a Model Context Protocol (MCP) server that integrates with AWS CodePipeline, allowing you to manage your pipelines through Windsurf and Cascade. The server provides a standardized interface for interacting with AWS CodePipeline services. **Author:** Cuong T Nguyen ## Features - List all pipelines - Get pipeline state and detailed pipeline definitions - List pipeline executions - Approve or reject manual approval actions - Retry failed stages - Trigger pipeline executions - View pipeline execution logs - Stop pipeline executions - Tag pipeline resources - Create webhooks for automatic pipeline triggering - Get pipeline performance metrics ## Prerequisites - Node.js (v14 or later) - AWS account with CodePipeline access - AWS credentials with permissions for CodePipeline, CloudWatch, and IAM (for tagging) - Windsurf IDE with Cascade AI assistant ## Installation 1. Clone this repository: ```bash git clone https://github.com/cuongdev/mcp-codepipeline-server.git cd mcp-codepipeline-server ``` 2. Install dependencies: ```bash npm install ``` 3. Create a `.env` file based on the `.env.example` template: ```bash cp .env.example .env ``` 4. Update the `.env` file with your AWS credentials and configuration: ``` AWS_REGION=us-east-1 AWS_ACCESS_KEY_ID=your_access_key_id AWS_SECRET_ACCESS_KEY=your_secret_access_key PORT=3000 ``` > **Note**: For security, never commit your `.env` file to version control. ## Usage ### Build the project ```bash npm run build ``` ### Start the server ```bash npm start ``` For development with auto-restart: ```bash npm run dev ``` ## Integration with Windsurf This MCP server is designed to work with Windsurf, allowing Cascade to interact with AWS CodePipeline through natural language requests. ### Setup Steps 1. Make sure the server is running: ```bash npm start ``` 2. Add the server configuration to your Windsurf MCP config file at `~/.codeium/windsurf/mcp_config.json`: ```json { "mcpServers": { "codepipeline": { "command": "npx", "args": [ "-y", "path/to/mcp-codepipeline-server/dist/index.js" ], "env": { "AWS_REGION": "us-east-1", "AWS_ACCESS_KEY_ID": "your_access_key_id", "AWS_SECRET_ACCESS_KEY": "your_secret_access_key" } } } } ``` 3. Create the directory if it doesn't exist: ```bash mkdir -p ~/.codeium/windsurf touch ~/.codeium/windsurf/mcp_config.json ``` 4. Restart Windsurf to load the new MCP server configuration ### Using with Cascade Once configured, you can interact with AWS CodePipeline using natural language in Windsurf. For example: - "List all my CodePipeline pipelines" - "Show me the current state of my 'production-deploy' pipeline" - "Trigger the 'test-build' pipeline" - "Get metrics for my 'data-processing' pipeline" - "Create a webhook for my 'frontend-deploy' pipeline" Cascade will translate these requests into the appropriate MCP tool calls. ## MCP Tools ### Core Pipeline Management | Tool Name | Description | Parameters | |-----------|-------------|------------| | `list_pipelines` | List all CodePipeline pipelines | None | | `get_pipeline_state` | Get the state of a specific pipeline | `pipelineName`: Name of the pipeline | | `list_pipeline_executions` | List executions for a specific pipeline | `pipelineName`: Name of the pipeline | | `trigger_pipeline` | Trigger a pipeline execution | `pipelineName`: Name of the pipeline | | `stop_pipeline_execution` | Stop a pipeline execution | `pipelineName`: Name of the pipeline<br>`executionId`: Execution ID<br>`reason`: Optional reason for stopping | ### Pipeline Details and Metrics | Tool Name | Description | Parameters | |-----------|-------------|------------| | `get_pipeline_details` | Get the full definition of a pipeline | `pipelineName`: Name of the pipeline | | `get_pipeline_execution_logs` | Get logs for a pipeline execution | `pipelineName`: Name of the pipeline<br>`executionId`: Execution ID | | `get_pipeline_metrics` | Get performance metrics for a pipeline | `pipelineName`: Name of the pipeline<br>`period`: Optional metric period in seconds<br>`startTime`: Optional start time for metrics<br>`endTime`: Optional end time for metrics | ### Pipeline Actions and Integrations | Tool Name | Description | Parameters | |-----------|-------------|------------| | `approve_action` | Approve or reject a manual approval action | `pipelineName`: Name of the pipeline<br>`stageName`: Name of the stage<br>`actionName`: Name of the action<br>`token`: Approval token<br>`approved`: Boolean indicating approval or rejection<br>`comments`: Optional comments | | `retry_stage` | Retry a failed stage | `pipelineName`: Name of the pipeline<br>`stageName`: Name of the stage<br>`pipelineExecutionId`: Execution ID | | `tag_pipeline_resource` | Add or update tags for a pipeline resource | `pipelineName`: Name of the pipeline<br>`tags`: Array of key-value pairs for tagging | | `create_pipeline_webhook` | Create a webhook for a pipeline | `pipelineName`: Name of the pipeline<br>`webhookName`: Name for the webhook<br>`targetAction`: Target action for the webhook<br>`authentication`: Authentication type<br>`authenticationConfiguration`: Optional auth config<br>`filters`: Optional event filters | ## Troubleshooting ### Common Issues 1. **Connection refused error**: - Ensure the server is running on the specified port - Check if the port is blocked by a firewall 2. **AWS credential errors**: - Verify your AWS credentials in the `.env` file - Ensure your IAM user has the necessary permissions 3. **Windsurf not detecting the MCP server**: - Check the `mcp_config.json` file format - Ensure the server URL is correct - Restart Windsurf after making changes ### Logs The server logs information to the console. Check these logs for troubleshooting: ```bash # Run with more verbose logging DEBUG=* npm start ``` ## Examples ### Creating a Webhook for GitHub Integration ```json { "pipelineName": "my-pipeline", "webhookName": "github-webhook", "targetAction": "Source", "authentication": "GITHUB_HMAC", "authenticationConfiguration": { "SecretToken": "my-secret-token" }, "filters": [ { "jsonPath": "$.ref", "matchEquals": "refs/heads/main" } ] } ``` ### Getting Pipeline Metrics ```json { "pipelineName": "my-pipeline", "period": 86400, "startTime": "2025-03-10T00:00:00Z", "endTime": "2025-03-17T23:59:59Z" } ``` ## License ISC ================ File: tsconfig.json ================ { "compilerOptions": { "target": "ES2020", "module": "NodeNext", "moduleResolution": "NodeNext", "outDir": "./dist", "rootDir": "./src", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, "resolveJsonModule": true, "allowJs": true, "typeRoots": ["./node_modules/@types", "./src/types"] }, "include": ["src/**/*"], "exclude": [ "node_modules", "**/*.test.ts", "src/controllers/**/*", "src/routes/**/*", "src/services/**/*", "src/mcp-server.ts" ] } ================================================================ End of Codebase ================================================================