Guide · AWS MediaConvert · Video Transcoding
AWS MediaConvert for MCP Servers — Video Transcoding Jobs, Queues, and IAM Roles
AWS MediaConvert is a file-based video transcoding service that converts media files from one format to another at scale — no infrastructure to provision or manage. For MCP server developers, MediaConvert solves the "transcode user-uploaded video before delivery" problem: an MCP tool receives a raw video uploaded to S3, creates a MediaConvert job to produce an HLS adaptive stream and MP4 preview, then delivers the outputs to a CloudFront-backed S3 bucket — all without running a single FFmpeg process on your own server. The service handles codec complexity (H.264, H.265, VP9, AV1), container formats (MP4, TS, WebM, MXF), captions (SCC, SRT, TTML), and quality normalization. Critical MediaConvert decisions: choosing on-demand queues (pay-per-minute, burst capacity) vs reserved queues (fixed hourly rate, predictable throughput for sustained workloads), structuring the job settings JSON correctly, and configuring the IAM role that MediaConvert assumes to read from your input S3 bucket and write to your output bucket.
TL;DR
Create a MediaConvert job by calling the endpoint-specific API (retrieve your account endpoint first with describe-endpoints) with a settings JSON that includes at minimum one Input (with S3 URI) and one OutputGroup (with container, codec, and destination). Attach an IAM role that trusts mediaconvert.amazonaws.com and grants s3:GetObject on your input bucket and s3:PutObject on your output bucket. Use the default on-demand queue for burst workloads. Subscribe to EventBridge MediaConvert Job State Change events to detect COMPLETE or ERROR status — do not poll get-job in a tight loop. See the S3 trigger workflow guide for a complete Lambda-based automation pattern.
Getting the account-specific endpoint
MediaConvert uses a per-account, per-region API endpoint that is different from the generic regional endpoint. Every API call — including job creation — must go to this account-specific endpoint, not the generic mediaconvert.us-east-1.amazonaws.com. You retrieve the endpoint once and cache it: it does not change between calls, but you must fetch it before your first job creation.
# Retrieve the account-specific MediaConvert endpoint
aws mediaconvert describe-endpoints \
--region us-east-1 \
--query 'Endpoints[0].Url' \
--output text
# Returns something like:
# https://abc123def456.mediaconvert.us-east-1.amazonaws.com
# All subsequent calls use this endpoint via --endpoint-url:
aws mediaconvert list-queues \
--endpoint-url https://abc123def456.mediaconvert.us-east-1.amazonaws.com \
--region us-east-1
In an MCP tool implementation, call describe-endpoints once at startup and store the result. The endpoint URL format is https://<account-hash>.mediaconvert.<region>.amazonaws.com. If you call the generic endpoint directly for job creation, you receive a 404 Not Found — not an authorization error — which is a common source of confusion during initial setup. In the AWS SDK for Python (boto3), pass endpoint_url when creating the MediaConvert client; in the JavaScript SDK v3, use endpoint in the client config.
IAM role for MediaConvert
MediaConvert needs an IAM role to access your S3 input and output buckets. This role is attached to each job at creation time via the Role parameter. MediaConvert assumes the role during job execution — it must have a trust policy allowing mediaconvert.amazonaws.com to assume it, plus permissions to read from the input bucket and write to the output bucket. Without the correct trust policy, job creation succeeds but the job immediately fails with INVALID_INPUT_FILE_ACCESS.
# Trust policy — allows MediaConvert to assume this role
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": { "Service": "mediaconvert.amazonaws.com" },
"Action": "sts:AssumeRole"
}]
}
# Permission policy — S3 read input, write output
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": ["s3:GetObject", "s3:GetObjectAcl"],
"Resource": "arn:aws:s3:::my-video-input/*"
},
{
"Effect": "Allow",
"Action": ["s3:PutObject", "s3:PutObjectAcl"],
"Resource": "arn:aws:s3:::my-video-output/*"
},
{
"Effect": "Allow",
"Action": ["s3:ListBucket"],
"Resource": [
"arn:aws:s3:::my-video-input",
"arn:aws:s3:::my-video-output"
]
}
]
}
# Create the role:
aws iam create-role \
--role-name MediaConvertJobRole \
--assume-role-policy-document file://trust-policy.json
aws iam put-role-policy \
--role-name MediaConvertJobRole \
--policy-name MediaConvertS3Access \
--policy-document file://permissions-policy.json
If your input and output buckets use KMS-managed encryption, add kms:Decrypt on the input key and kms:GenerateDataKey on the output key to the role. If you write outputs to a CloudFront-origin bucket with block-public-access enabled, no additional ACL permissions are needed — set PutObjectAcl grant to bucket-owner-full-control via the job settings, not via IAM.
Creating a MediaConvert job
A MediaConvert job defines one or more inputs (video sources) and one or more output groups (collections of related outputs). A minimal job has one input pointing to an S3 URI and one output group producing either an HLS stream, an MP4 file, or a thumbnail. The settings JSON can be complex, but for most MCP server use cases you need: the input S3 URI, an output group type (FILE_GROUP_SETTINGS for single-file outputs like MP4, HLS_GROUP_SETTINGS for adaptive streams), codec settings (bitrate, resolution), and the output destination S3 prefix.
# Minimal job: transcode MP4 to H.264 MP4 + thumbnail
ENDPOINT="https://abc123def456.mediaconvert.us-east-1.amazonaws.com"
ROLE_ARN="arn:aws:iam::123456789012:role/MediaConvertJobRole"
aws mediaconvert create-job \
--endpoint-url "$ENDPOINT" \
--region us-east-1 \
--role "$ROLE_ARN" \
--settings '{
"Inputs": [{
"FileInput": "s3://my-video-input/uploads/raw-video.mp4",
"AudioSelectors": {
"Audio Selector 1": { "DefaultSelection": "DEFAULT" }
},
"VideoSelector": {}
}],
"OutputGroups": [{
"Name": "File Group",
"OutputGroupSettings": {
"Type": "FILE_GROUP_SETTINGS",
"FileGroupSettings": {
"Destination": "s3://my-video-output/processed/"
}
},
"Outputs": [{
"ContainerSettings": {
"Container": "MP4",
"Mp4Settings": {}
},
"VideoDescription": {
"CodecSettings": {
"Codec": "H_264",
"H264Settings": {
"Bitrate": 5000000,
"RateControlMode": "CBR",
"CodecProfile": "HIGH",
"CodecLevel": "AUTO"
}
},
"Width": 1920,
"Height": 1080
},
"AudioDescriptions": [{
"AudioSourceName": "Audio Selector 1",
"CodecSettings": {
"Codec": "AAC",
"AacSettings": {
"Bitrate": 96000,
"CodingMode": "CODING_MODE_2_0",
"SampleRate": 48000
}
}
}],
"NameModifier": "_1080p"
}]
}]
}'
# The response includes the job ID:
# { "Job": { "Id": "1696300000000-abc123", "Status": "SUBMITTED", ... } }
The job ID format is <unix-epoch-ms>-<random-suffix>. Status transitions are: SUBMITTED → PROGRESSING → COMPLETE (or ERROR). Use the job ID to query status: aws mediaconvert get-job --id 1696300000000-abc123 --endpoint-url "$ENDPOINT". For production workloads, use EventBridge to receive completion notifications rather than polling — see the MediaConvert CloudWatch monitoring guide.
Queue types: on-demand vs reserved
MediaConvert queues control job parallelism and pricing. The default on-demand queue processes jobs at a per-minute rate with AWS managing capacity — ideal for burst workloads where you process a few videos per hour. Reserved queues provision dedicated transcoding capacity at a fixed hourly rate regardless of usage — ideal when you process more than ~6 hours of video per day and want predictable costs and throughput guarantees.
# Submit a job to the default on-demand queue (no queue ARN needed)
aws mediaconvert create-job \
--endpoint-url "$ENDPOINT" \
--role "$ROLE_ARN" \
--queue "arn:aws:mediaconvert:us-east-1:123456789012:queues/Default" \
--settings '...'
# Create a custom on-demand queue for a specific workload
aws mediaconvert create-queue \
--endpoint-url "$ENDPOINT" \
--name "high-priority-video" \
--description "Priority queue for user-facing video processing"
# Returns the queue ARN
# Create a reserved queue (requires a reservation plan — monthly commit)
aws mediaconvert create-queue \
--endpoint-url "$ENDPOINT" \
--name "batch-transcoding" \
--pricing-plan RESERVED \
--reservation-plan-settings '{
"Commitment": "ONE_YEAR",
"RenewalType": "AUTO_RENEW",
"ReservedSlots": 1
}'
# List queues and their current status
aws mediaconvert list-queues \
--endpoint-url "$ENDPOINT" \
--query 'Queues[*].{Name:Name,Status:Status,Type:Type,ARN:Arn}'
On-demand queues have no concurrency guarantee — jobs queue behind each other and AWS allocates capacity from a shared pool. For time-sensitive MCP tools (user is waiting for a processed video), create a dedicated on-demand queue and submit high-priority jobs there, keeping background batch jobs in a separate queue. Reserved queues are billed at ~$0.22/hour per reserved transcode slot regardless of whether jobs are running — only purchase if your utilization rate is consistently high.
Job templates for reusable settings
Manually constructing the full settings JSON for every job is error-prone. MediaConvert job templates capture a complete set of output group and codec settings that can be applied to any input. Instead of embedding output settings in each create-job call, reference a template by ARN — only provide the input and destination overrides per job. See the job templates guide for creation and parameterization patterns.
# Create a job using an existing template
aws mediaconvert create-job \
--endpoint-url "$ENDPOINT" \
--role "$ROLE_ARN" \
--job-template "arn:aws:mediaconvert:us-east-1:123456789012:jobTemplates/web-delivery" \
--settings '{
"Inputs": [{
"FileInput": "s3://my-video-input/uploads/raw-video.mp4",
"AudioSelectors": {
"Audio Selector 1": { "DefaultSelection": "DEFAULT" }
},
"VideoSelector": {}
}]
}'
# Template provides all OutputGroups; only Input needs to be specified
When you reference a job template, the settings you provide in create-job are merged with the template settings — inputs are taken from your call, output groups come from the template. This is the recommended pattern for MCP tools that always produce the same output formats (e.g., always produce 1080p MP4 + HLS + thumbnail) but vary only in the source file and destination path.
Failure modes reference
| Failure | Symptom | Fix |
|---|---|---|
| 404 on job creation | create-job returns HTTP 404 Not Found | You are calling the generic regional endpoint instead of the account-specific endpoint — run describe-endpoints first and pass that URL via --endpoint-url |
| Job fails immediately with INVALID_INPUT_FILE_ACCESS | Job goes SUBMITTED → ERROR within seconds | IAM role trust policy is missing or does not list mediaconvert.amazonaws.com as principal; check role trust policy and re-attach to job |
| S3 read access denied | Job fails with error code 3450 or mentions "Access Denied" on input URI | Role lacks s3:GetObject on the input bucket/prefix; verify role policy and bucket policy do not conflict |
| S3 write access denied | Job progresses but fails at output write phase | Role lacks s3:PutObject on the output bucket; also check if bucket has a policy requiring specific ACL grants |
| Invalid settings JSON | create-job returns ValidationException with field path | Settings JSON schema is strict — use the MediaConvert console to build settings interactively, then export as JSON; missing required fields like AudioSelectors on input are common causes |
| Queue capacity exceeded | Job stays in SUBMITTED for hours | On-demand queue is at capacity — create a second queue or upgrade to a reserved queue if this happens consistently; alternatively increase concurrency limit on the queue |
| Output files missing audio track | Output video plays silently | Input has audio but AudioDescriptions array in output is empty; audio must be explicitly wired from AudioSelectors in input to AudioDescriptions in each output |