Guide · AWS Systems Manager · Patch Management
AWS SSM Patch Manager for MCP Servers: OS and Dependency Patching
SSM Patch Manager keeps the OS packages on your MCP server instances up to date — automatically, on a schedule, with compliance reporting — without manual SSH sessions or custom scripting. Unpatched MCP server hosts are a liability: a known CVE in the OS or a system library like libssl is an exploitable path into the same machine that holds your downstream API keys. Patch Manager uses your existing SSM infrastructure (the SSM Agent + instance role) to run yum update or apt upgrade against an approved patch baseline, then reports compliance status to Systems Manager. Combine it with a Maintenance Window for scheduled patching and AliveMCP monitoring to confirm recovery, and you have an automated patching pipeline that requires zero SSH access and generates an audit trail per instance. For fleet-level operational tasks beyond patching, see SSM Run Command.
TL;DR
Instances need the AmazonSSMManagedInstanceCore policy and the SSM Agent. Tag instances with a Patch Group key to assign them to a patch baseline. Run aws ssm send-command --document-name AWS-RunPatchBaseline --parameters Operation=Scan to see what patches are missing. Use Operation=Install inside a Maintenance Window to apply patches. View compliance in the Systems Manager Patch Manager console. AWS provides default patch baselines for Amazon Linux 2, Amazon Linux 2023, Ubuntu, CentOS, and Windows; create a custom baseline if you need to approve specific package versions.
Why unpatched MCP server hosts are a specific risk
MCP servers operate at a trust boundary. They hold API keys for downstream services — web search APIs, code execution sandboxes, database credentials, payment processors — and execute calls to those services on behalf of the AI agent connecting to them. A compromised MCP server host can exfiltrate all those keys at rest (in environment variables, in /etc/mcp-server/env, in SSM Parameter Store cache files) and replay all tool calls with the attacker's chosen parameters.
The threat model for an unpatched MCP server host is therefore different from an unpatched generic web server: the blast radius of compromise is the union of all downstream API scopes, not just the MCP server's own data. OS-level CVEs — privilege escalation from www-data to root, remote code execution via the package manager toolchain, container escapes via kernel exploits — are the likely path. Patch Manager closes those paths systematically without requiring per-instance manual effort.
Patch baselines: default and custom
A patch baseline defines which patches Patch Manager installs. AWS provides a managed default baseline per OS; the defaults approve security patches automatically after a 7-day delay and require all Critical and Important severity patches to be installed. For most MCP server deployments, the default baseline is sufficient.
| OS | Default baseline name | Auto-approval delay | Default severity threshold |
|---|---|---|---|
| Amazon Linux 2023 | AWS-AmazonLinux2023DefaultPatchBaseline | 7 days | Critical + Important |
| Amazon Linux 2 | AWS-AmazonLinux2DefaultPatchBaseline | 7 days | Critical + Important |
| Ubuntu 22.04/24.04 | AWS-UbuntuDefaultPatchBaseline | 7 days | Critical + Important |
| CentOS 7/8 | AWS-CentOSDefaultPatchBaseline | 7 days | Critical + Important |
| Windows Server 2019/2022 | AWS-DefaultPatchBaseline | 7 days | Critical + Important |
If you need to pin a specific package version or exclude a package that breaks your MCP server, create a custom baseline:
import * as ssm from 'aws-cdk-lib/aws-ssm';
const mcpPatchBaseline = new ssm.CfnPatchBaseline(this, 'McpPatchBaseline', {
name: 'mcp-server-patch-baseline',
operatingSystem: 'AMAZON_LINUX_2023',
description: 'Patch baseline for MCP server fleet — approves security patches after 7-day delay; excludes openssl-devel (pinned manually)',
approvalRules: {
patchRules: [
{
patchFilterGroup: {
patchFilters: [
{ key: 'CLASSIFICATION', values: ['Security', 'Bugfix'] },
{ key: 'SEVERITY', values: ['Critical', 'Important'] },
],
},
approveAfterDays: 7,
enableNonSecurity: false,
},
],
},
// Reject packages that conflict with the pinned Node.js version
rejectedPatches: ['nodejs20', 'nodejs18'],
rejectedPatchesAction: 'BLOCK',
// Approve specific packages regardless of release date
approvedPatches: [],
globalFilters: {
patchFilters: [
// Exclude kernel updates (handled separately during dedicated window)
{ key: 'NAME', values: ['kernel*'] },
],
},
});
The BLOCK action on rejected patches prevents them from being installed even if they appear in the approval rules — it takes precedence. Use this for packages where a version upgrade would break your MCP server's runtime (e.g., a major Node.js version bump via the system package manager when you manage Node with nvm separately).
Patch groups: assign instances to baselines
Patch groups connect EC2 instances to a patch baseline via a tag. The tag key is always Patch Group (with a space). The tag value is arbitrary — use something meaningful like mcp-production.
import * as ec2 from 'aws-cdk-lib/aws-ec2';
// Tag your MCP server instances with the Patch Group
const mcpInstance = new ec2.Instance(this, 'McpServer', {
// ... other config ...
});
cdk.Tags.of(mcpInstance).add('Patch Group', 'mcp-production');
cdk.Tags.of(mcpInstance).add('Service', 'mcp-server');
cdk.Tags.of(mcpInstance).add('Env', 'production');
Then register the patch group with your custom baseline:
// Associate the patch group with the custom baseline
const patchGroupRegistration = new ssm.CfnPatchBaseline(this, 'McpPatchBaseline', {
// ... baseline config above ...
patchGroups: ['mcp-production'], // matches the tag value
});
If an instance's Patch Group tag does not match any registered patch group, Patch Manager uses the default AWS-managed baseline for the instance's OS. This is safe behavior — instances always have a baseline, they just may not have your custom one.
Running patch operations: scan vs. install
Patch Manager uses the AWS-RunPatchBaseline document with an Operation parameter. Scan reports which patches are missing without installing them. Install applies the patches.
Run a fleet-wide scan to check current patch compliance without changing anything:
aws ssm send-command \
--document-name "AWS-RunPatchBaseline" \
--targets '[{"Key":"tag:Service","Values":["mcp-server"]}]' \
--parameters '{"Operation":["Scan"]}' \
--max-concurrency "100%" \
--cloud-watch-output-config '{"CloudWatchLogGroupName":"/mcp/patch-scans","CloudWatchOutputEnabled":true}' \
--output text \
--query "Command.CommandId"
After the scan completes, view compliance per instance in the Systems Manager console under "Patch Manager → Compliance reporting". Each instance shows a Compliant or NonCompliant status with the count of missing patches by severity.
Run Install inside a Maintenance Window task (never directly with send-command at full concurrency on production — patching reboots instances):
import * as ssm from 'aws-cdk-lib/aws-ssm';
// Add a patch install task to an existing Maintenance Window
const patchInstallTask = new ssm.CfnMaintenanceWindowTask(this, 'PatchInstallTask', {
windowId: maintenanceWindow.ref,
taskArn: 'AWS-RunPatchBaseline',
taskType: 'RUN_COMMAND',
targets: [{ key: 'WindowTargetIds', values: [windowTargets.ref] }],
priority: 1,
maxConcurrency: '10%', // patch 10% of fleet at a time — slow, safe
maxErrors: '5%',
serviceRoleArn: maintenanceRole.roleArn,
taskInvocationParameters: {
maintenanceWindowRunCommandParameters: {
parameters: {
Operation: ['Install'],
RebootOption: ['RebootIfNeeded'],
},
cloudWatchOutputConfig: {
cloudWatchLogGroupName: '/mcp/patch-install',
cloudWatchOutputEnabled: true,
},
timeoutSeconds: 1800, // 30 minutes per instance for large patch sets
},
},
});
RebootIfNeeded is the safe default: it reboots the instance only if the installed patches require a reboot (kernel updates, glibc updates). Set NoReboot only if you have a specific reason to defer reboots — deferred reboots mean the patches are staged but not active, so the instance is still running the vulnerable binaries.
Compliance reporting and alerting
After each Scan or Install operation, Patch Manager writes compliance data to Systems Manager. You can query it with the CLI:
# List all non-compliant MCP server instances
aws ssm list-compliance-items \
--filters "Key=ComplianceType,Values=Patch,Type=EQUAL" \
"Key=Status,Values=NON_COMPLIANT,Type=EQUAL" \
--resource-types "ManagedInstance"
# Get patch details for a specific instance
aws ssm describe-instance-patches \
--instance-id i-0abc123def456789a \
--filters "Key=State,Values=Missing" \
--query "Patches[*].{Name:Title,Severity:Severity,State:State}" \
--output table
For automated alerting, create a CloudWatch Metric Filter on the /mcp/patch-scans log group that matches the pattern NONCOMPLIANT_PATCHES (included in the AWS-RunPatchBaseline output) and triggers an alarm when the count exceeds your threshold.
AliveMCP monitoring during patch operations
Patch installs that require a reboot create a predictable outage pattern: the instance goes offline for the reboot (60–180 seconds typically), then comes back online. AliveMCP's per-instance probe captures this exactly. With 10% concurrency on a 50-instance fleet, 5 instances reboot at a time — AliveMCP shows the expected 90% probe success rate during each patch wave, recovering to 100% between waves.
The deviation from this expected pattern that warrants attention: an instance that goes offline during patching and does not come back within 5 minutes. Causes include a failed kernel upgrade that leaves the instance in a rescue boot loop, a post-reboot service start failure due to a dependency that was updated (e.g., a glibc update that broke a compiled module), or an AMI that reboots into a different kernel than expected. Use Session Manager to access the instance out-of-band — no SSH needed — and inspect the boot logs.
Configure a Maintenance Window notification (SNS topic) to alert when the window's task execution fails beyond the MaxErrors threshold. Pair this with AliveMCP's instance-level alert so you catch both "patching failed" (window notification) and "server not responding" (AliveMCP probe) independently — they address different failure modes.
Failure modes
| Symptom | Cause | Fix |
|---|---|---|
| Scan shows instance as "Not configured" in Patch Manager | Instance not registered with SSM (Agent not running or instance profile missing); or instance launched before the Patch Group tag was added |
Verify in Systems Manager Fleet Manager; restart the SSM Agent; ensure the instance profile has AmazonSSMManagedInstanceCore |
| Patches installed but compliance still shows non-compliant after reboot | Patch Manager compliance data is not refreshed until the next Scan operation runs; Install updates the installed state but a separate Scan is needed to update compliance metadata |
Run a follow-up Scan after the Install Maintenance Window completes; or set up a daily Scan schedule to keep compliance data current |
| MCP server fails to start after kernel update | A native addon compiled against the old kernel headers; or a version pin in the service unit file specifying a Node.js path that changed after a package manager update | Use NoReboot for kernel patches and handle kernel upgrades in a separate dedicated window with longer recovery testing; verify native addons are rebuilt post-upgrade |
RejectedException — patch blocked by custom baseline |
A package in the rejectedPatches list was attempted; the BLOCK action causes the entire patch operation to fail on that instance |
Check Patch Manager compliance details for the blocked package name; update the baseline to use ALLOW_AS_DEPENDENCY if the rejection was overly broad, or fix the actual package conflict first |
| Instance reboots but does not re-register with SSM after patching | A network change during reboot (new network interface order, DHCP lease change) caused the SSM Agent to bind to a different IP; or the instance is in a private subnet and the VPC endpoint is in a different AZ from the instance | Check sudo systemctl status amazon-ssm-agent on the instance via the EC2 console's Instance Connect (if available); verify SSM VPC endpoints cover all AZs used by the instance subnets |