Guide · AWS CodePipeline · Pipeline Design

CodePipeline Stage Design for MCP Servers — Approval Gates, Variables, Parallel Actions

A CodePipeline stage is a logical partition of your delivery process — stages run sequentially, but actions within a stage can run in parallel. For MCP server pipelines, the typical layout is: Source stage → Build stage (CodeBuild produces the Docker image and imagedefinitions.json) → Test stage (integration tests run against a staging deployment) → Approval stage (SNS notification to Slack; human approves production) → Deploy-Prod stage (ECS rolling update or CodeDeploy blue-green). Getting the stage design right determines whether your pipeline is a friction-reducing delivery machine or a bottleneck. V2 pipeline variables allow you to pass values across stages — such as the image tag built in the Build stage through to the Deploy stage — without writing to an intermediate S3 file. Understanding action runOrder, namespace, and variable interpolation syntax avoids the most common stage configuration bugs.

TL;DR

Use V2 pipelines for stage variables. Export values from an action using its namespace (e.g., BuildVariables), then consume them in later actions with #{BuildVariables.IMAGE_TAG}. Set parallel test actions to the same runOrder so they run simultaneously. For production promotion gates, use a Manual Approval action with an SNS topic that fans out to Slack via Chatbot — this gives reviewers a one-click link to approve or reject. See the CodePipeline overview for artifact store setup and CodeBuild for buildspec configuration.

Action categories and types

Every CodePipeline action has a category that determines its role in the pipeline. Knowing which category fits each task prevents you from wiring the wrong action type and getting a confusing validation error.

CategoryProvidersPurpose for MCP pipelines
SourceCodeStarSourceConnection (GitHub/Bitbucket), S3, ECRDetect a push or image push and start the pipeline
BuildCodeBuildCompile, test unit, build Docker image, push to ECR, write imagedefinitions.json
TestCodeBuild, DeviceFarm, third-partyRun integration tests against a staging MCP deployment; different category keeps build and test metrics separate in CloudWatch
DeployECS, CodeDeploy, CloudFormation, Lambda, Elastic BeanstalkUpdate ECS service with new task definition; execute CDK/CloudFormation changeset
ApprovalManualGate production deployments; send SNS notification with approval URL
InvokeLambda, Step FunctionsCustom logic: tag the Git release, post a Slack message on successful deploy, run a smoke test

A common mistake is using a Build action for integration tests. While technically it works, using a Test category action keeps build and test results separate — CodePipeline displays them differently in the console, and CloudWatch Metrics are emitted under different namespaces. Use CodeBuild for both, but set the action category to Test for the integration test project.

Parallel actions with runOrder

Within a stage, actions assigned the same runOrder value run concurrently. Actions with higher runOrder values wait until all lower-runOrder actions in the same stage succeed. This lets you run unit tests, linting, and security scanning in parallel to reduce total pipeline time.

# Build stage with parallel unit test and security scan, then image push
{
  "name": "Build",
  "actions": [
    {
      "name": "UnitTest",
      "actionTypeId": {
        "category": "Build", "owner": "AWS",
        "provider": "CodeBuild", "version": "1"
      },
      "configuration": { "ProjectName": "mcp-server-unit-tests" },
      "inputArtifacts": [{ "name": "SourceOutput" }],
      "runOrder": 1
    },
    {
      "name": "SecurityScan",
      "actionTypeId": {
        "category": "Build", "owner": "AWS",
        "provider": "CodeBuild", "version": "1"
      },
      "configuration": { "ProjectName": "mcp-server-snyk-scan" },
      "inputArtifacts": [{ "name": "SourceOutput" }],
      "runOrder": 1
    },
    {
      "name": "BuildImage",
      "actionTypeId": {
        "category": "Build", "owner": "AWS",
        "provider": "CodeBuild", "version": "1"
      },
      "configuration": {
        "ProjectName": "mcp-server-build-push",
        "EnvironmentVariables": "[{\"name\":\"IMAGE_TAG\",\"value\":\"#{codepipeline.PipelineExecutionId}\",\"type\":\"PLAINTEXT\"}]"
      },
      "inputArtifacts": [{ "name": "SourceOutput" }],
      "outputArtifacts": [{ "name": "BuildOutput" }],
      "namespace": "BuildVars",
      "runOrder": 2
    }
  ]
}

At runOrder: 1, UnitTest and SecurityScan run simultaneously. If either fails, runOrder: 2 (BuildImage) does not start — the stage fails immediately. This prevents building and pushing a Docker image for a commit that failed tests. If you need the image build to run even if tests fail (for debugging), separate them into different stages — a failing action in one stage blocks subsequent stages, but not other stages unless you configure stageName transitions.

V2 pipeline variables

V2 pipelines support variables that flow across stages. You declare variable namespaces on actions, export values from those actions, and consume them in downstream actions using the #{namespace.variable} interpolation syntax. This replaces the pattern of writing values to intermediate S3 files and reading them back.

# Export variables from CodeBuild action using namespace
{
  "name": "BuildImage",
  "configuration": {
    "ProjectName": "mcp-server-build-push"
  },
  "namespace": "BuildVars",   # <-- namespace declaration
  "outputArtifacts": [{ "name": "BuildOutput" }],
  "runOrder": 2
}

# In the CodeBuild buildspec, export variables via exported-variables
version: 0.2
phases:
  post_build:
    commands:
      - IMAGE_TAG=$CODEBUILD_RESOLVED_SOURCE_VERSION
      - IMAGE_URI="$ECR_REGISTRY/$ECR_REPO:$IMAGE_TAG"
      - echo "Exporting IMAGE_URI=$IMAGE_URI"
exported-variables:
  - IMAGE_URI
  - IMAGE_TAG

# Consume the variable in a downstream Deploy stage action
{
  "name": "DeployStaging",
  "configuration": {
    "ClusterName": "mcp-cluster-staging",
    "ServiceName": "mcp-server-staging",
    "FileName": "imagedefinitions.json"
  },
  "inputArtifacts": [{ "name": "BuildOutput" }]
  # imagedefinitions.json in BuildOutput already has the IMAGE_URI
}

# Or pass the variable to an Invoke action (Lambda function):
{
  "name": "PostDeploySlack",
  "actionTypeId": {
    "category": "Invoke", "owner": "AWS",
    "provider": "Lambda", "version": "1"
  },
  "configuration": {
    "FunctionName": "codepipeline-slack-notify",
    "UserParameters": "{\"imageUri\":\"#{BuildVars.IMAGE_URI}\",\"env\":\"staging\"}"
  }
}

Variables are limited to 2048 characters and cannot contain newlines. Only string values are supported — you cannot export a JSON object; serialize it as a string if needed. The codepipeline.* namespace provides built-in variables: codepipeline.PipelineExecutionId, codepipeline.PipelineName, codepipeline.PipelineArn, and codepipeline.SourceRevision (the Git commit SHA).

Manual Approval action for production gates

The Manual Approval action pauses pipeline execution and sends an SNS notification. A reviewer clicks the approval URL in the notification email (or Slack message via Chatbot) to approve or reject the deployment. Approved executions continue; rejected executions are marked Failed and the pipeline stops.

# Approval stage between staging and production deploy
{
  "name": "Approve",
  "actions": [
    {
      "name": "ApproveProductionDeploy",
      "actionTypeId": {
        "category": "Approval",
        "owner": "AWS",
        "provider": "Manual",
        "version": "1"
      },
      "configuration": {
        "NotificationArn": "arn:aws:sns:us-east-1:123456789012:mcp-pipeline-approvals",
        "CustomData": "Deploy #{BuildVars.IMAGE_TAG} to production. Review staging: https://staging.alivemcp.com",
        "ExternalEntityLink": "https://staging.alivemcp.com/health"
      },
      "runOrder": 1
    }
  ]
}

# SNS topic policy — allow CodePipeline to publish
{
  "Effect": "Allow",
  "Principal": { "Service": "codepipeline.amazonaws.com" },
  "Action": "SNS:Publish",
  "Resource": "arn:aws:sns:us-east-1:123456789012:mcp-pipeline-approvals"
}

# Wire SNS → AWS Chatbot → Slack for one-click approval from Slack
# The Chatbot Slack channel subscription sends a card with Approve/Reject buttons
# Recipients click directly in Slack; no need to log into the AWS console

The approval token expires after 7 days — if no one approves or rejects within 7 days, the pipeline execution transitions to Failed. You can also approve and reject approvals programmatically using the put-approval-result API, which is useful for automated canary-based promotion: a Lambda function that checks your staging MCP server's health score can approve automatically if the score exceeds a threshold.

# Automated approval via Lambda (canary health check)
aws codepipeline put-approval-result \
  --pipeline-name mcp-server-pipeline \
  --stage-name Approve \
  --action-name ApproveProductionDeploy \
  --token "$APPROVAL_TOKEN" \
  --result "summary=Automated approval: staging health 98.5%,status=Approved"

# The token comes from the approval notification; Lambda receives it via SNS
# Extract: event['Records'][0]['Sns']['Message'] -> JSON -> token field

Deploy staging then production pattern

For MCP servers with both staging and production environments, the full pipeline structure runs Build → Deploy-Staging → Integration-Tests → Approve → Deploy-Prod. The staging deployment happens automatically on every push; the production deployment waits for human approval. This gives you a consistently deployed staging environment without manual effort.

# Full 5-stage pipeline for MCP server
Source → Build → DeployStaging → TestAndApprove → DeployProd

# DeployStaging stage: ECS rolling deploy to staging cluster
{
  "name": "DeployStaging",
  "actions": [{
    "name": "ECSUpdateStaging",
    "actionTypeId": {
      "category": "Deploy", "owner": "AWS",
      "provider": "ECS", "version": "1"
    },
    "configuration": {
      "ClusterName": "mcp-cluster-staging",
      "ServiceName": "mcp-server-staging",
      "FileName": "imagedefinitions.json",
      "DeploymentTimeout": "10"
    },
    "inputArtifacts": [{ "name": "BuildOutput" }]
  }]
}

# TestAndApprove stage: integration tests run in parallel with approval wait
# Parallel: integration test CodeBuild (runOrder 1) + Manual Approval (runOrder 1)
# Both must succeed for the stage to complete — tests pass AND human approves
{
  "name": "TestAndApprove",
  "actions": [
    {
      "name": "IntegrationTests",
      "actionTypeId": {
        "category": "Test", "owner": "AWS",
        "provider": "CodeBuild", "version": "1"
      },
      "configuration": { "ProjectName": "mcp-server-integration-tests" },
      "inputArtifacts": [{ "name": "BuildOutput" }],
      "runOrder": 1
    },
    {
      "name": "ApproveProduction",
      "actionTypeId": {
        "category": "Approval", "owner": "AWS",
        "provider": "Manual", "version": "1"
      },
      "configuration": {
        "NotificationArn": "arn:aws:sns:us-east-1:123456789012:mcp-approvals",
        "CustomData": "Image #{BuildVars.IMAGE_TAG} on staging — check health before approving"
      },
      "runOrder": 1
    }
  ]
}

Running the integration tests and the manual approval in parallel means the human reviewer can be doing their review while the automated tests are running. If the tests fail, the stage fails even if the human has already approved — the pipeline does not proceed to production with a failing test suite.

Failure modes reference

FailureSymptomFix
Variable interpolation not working#{namespace.var} appears literally in configPipeline must be V2 type; V1 pipelines do not support variable interpolation — set "pipelineType": "V2" in pipeline definition
Exported variable emptyCodeBuild action succeeds but downstream variable is empty stringVariable must be listed under exported-variables in buildspec AND be set as an environment variable in the build phase — variable must exist in the shell environment at the time the phase completes
Parallel actions not running simultaneouslyActions within a stage run one after anotherActions must have the same runOrder integer value to run in parallel; different values cause sequential execution
Approval notification not receivedApproval stage starts but no email or Slack message appearsSNS topic policy must allow codepipeline.amazonaws.com to publish; verify subscribers are confirmed (SNS email subscriptions require confirmation click)
Approval token expiredPipeline execution stuck in "InProgress" for days, then failsManual Approval tokens expire after 7 days; if approval is not acted on, the execution fails — ensure notification recipients are actively monitoring
Integration test CodeBuild can't reach stagingTest action fails: connection refused to staging endpointIntegration test CodeBuild project needs VPC configuration to access private staging ECS service, or staging endpoint must be publicly accessible with appropriate security group rules