Screenshot from the article

Building an AI-Powered Document Processing System with MCP, ECS, and S3

How I built a serverless architecture that lets AI models intelligently fetch and summarize documents from S3 using the Model Context Protocol

The Problem That Started It All

You know that feeling when you have tons of documents scattered across S3 buckets and you wish you could just ask an AI:

“Hey, can you summarize that report from last week?”

Well, that’s exactly what got me started on this project.

I wanted to bridge the gap between AI models (like Amazon Bedrock) and my document storage in S3 — without building yet another custom API that would be a pain to maintain.

That’s where the Model Context Protocol (MCP) came into play.

What is MCP and Why Should You Care?

The Model Context Protocol is basically a standardized way for AI models to interact with external data sources.

Think of it as a universal translator between AI models and your data. Instead of building custom integrations for every AI service, you build one MCP server and any MCP-compatible AI can talk to it.

It’s like having a waiter at a restaurant who speaks multiple languages — your AI orders in “AI speak”, and the MCP server translates that into “S3 speak” to fetch your documents.

The Architecture: Serverless All The Way Down

Here’s what I ended up building:

S3 Bucket → Lambda Function → ECS Fargate → API Gateway → AI Model
     ↓           ↓              ↓             ↓
CloudWatch ← SNS Alerts ← Load Balancer ← Authentication

Let’s break it down:

1. S3 Bucket — The Document Vault

This is where all the magic starts. I configured the S3 bucket with:

  • Server-side encryption (because security matters)
  • Versioning enabled (because mistakes happen)
  • Event notifications that trigger our Lambda function

2. Lambda Function — The Trigger Happy Component

Whenever someone uploads a file to S3, this Lambda function wakes up and says:

“Hey ECS, we got a new file to process!”

It’s like having a doorbell for your S3 bucket.

import boto3
import json
import os
def lambda_handler(event, context):
    """
    Lambda function triggered when a new file is uploaded to S3.
    Updates the ECS service to ensure it's running to process the new file.
    """
    try:
        cluster = os.environ.get('ECS_CLUSTER')
        service = os.environ.get('ECS_SERVICE')
        if not all([cluster, service]):
            raise ValueError("Missing required environment variables")
        ecs = boto3.client('ecs')
        ecs.update_service(
            cluster=cluster,
            service=service,
            forceNewDeployment=True
        )
        return {
            "statusCode": 200,
            "body": json.dumps("Successfully triggered ECS service update"),
        }
    except Exception as e:
        print(f"Error: {str(e)}")
        raise e

3. ECS Fargate — The Workhorse

This is where the actual MCP server runs. I chose Fargate because:

  • No servers to manage (serverless FTW!)
  • Auto-scaling when things get busy
  • Pay only for what you use

The MCP server is a Flask app that implements the MCP protocol, with endpoints like:

  • /health — Monitoring
  • /summarize/<filename> — The main event

4. API Gateway — The Bouncer

Handles authentication, rate limiting, and makes sure no random internet person is summarizing my documents.

Infrastructure as Code — Because Manual is Painful

I used Terraform for everything. Clicking around the AWS console gets old fast.

Infrastructure modules include:

  • ECR repository for Docker images
  • ECS cluster and service definitions
  • S3 bucket with proper policies
  • API Gateway with authentication
  • CloudWatch logging and monitoring
  • SNS for alerts

ECS Task Definition in Terraform:

resource "aws_ecs_task_definition" "mcp_task" {
  family                   = "${var.project_name}-${var.environment}-task"
  network_mode             = "awsvpc"
  requires_compatibilities = ["FARGATE"]
  cpu                      = var.ecs_task_cpu
  memory                   = var.ecs_task_memory
  execution_role_arn       = aws_iam_role.ecs_task_execution_role.arn
  task_role_arn            = aws_iam_role.ecs_task_role.arn
  container_definitions = jsonencode([
    {
      name      = "mcp-server"
      image     = var.container_image
      essential = true
      portMappings = [
        {
          containerPort = var.container_port
          hostPort      = var.container_port
          protocol      = "tcp"
        }
      ]
      environment = [
        { name = "AWS_REGION",      value = data.aws_region.current.name },
        { name = "S3_BUCKET_NAME",  value = var.s3_bucket_name }
      ]
      logConfiguration = {
        logDriver = "awslogs"
        options = {
          awslogs-group         = aws_cloudwatch_log_group.mcp_logs.name
          awslogs-region        = data.aws_region.current.name
          awslogs-stream-prefix = "ecs"
        }
      }
    }
  ])
}

The Code That Makes It Tick

MCP server endpoint:

@app.route('/summarize/<filename>', methods=['GET'])
def summarize_file(filename):
    try:
        response = s3_client.get_object(Bucket=S3_BUCKET, Key=filename)
        content = response['Body'].read().decode('utf-8')
        
        return jsonify({
            "status": "success",
            "filename": filename,
            "content": content,
            "timestamp": time.time()
        })
    except Exception as e:
        logger.error(f"Error processing file {filename}: {str(e)}")
        return jsonify({"error": str(e)}), 500

CI/CD Pipeline

GitHub Actions handles:

  1. Build & test code
  2. Build & push Docker images to ECR
  3. Deploy infrastructure with Terraform
  4. Update ECS services
name: CI/CD Pipeline
on:
  push:
    branches: [ "main", "develop" ]
  pull_request:
    branches: [ "main", "develop" ]

(full pipeline code in repo)

Monitoring & Observability

  • CloudWatch Logs for everything
  • CloudWatch Metrics for API calls, processing times
  • SNS Alerts for failures
  • Health Checks for ECS services

Lessons Learned

  1. Start monitoring early
  2. Test for circular dependencies (S3 → Lambda → ECS)
  3. Mock external services
  4. Document APIs better

Real-World Use Cases

  • Legal contract analysis
  • Marketing document insights
  • Academic research workflows
  • Compliance reporting

Performance & Costs

  • Cold start: ~2–3s
  • Warm requests: ~200–500ms
  • Cost: ~$20–30/month moderate usage
  • Scaling: 100+ concurrent requests easily

Security

  • API Gateway auth
  • IAM least privilege
  • S3 restrictive policies
  • VPC isolation
  • Encryption at rest & transit

FAQ

**Q: Why use MCP instead of just building a REST API?**

A: MCP provides a standardized protocol that any compatible AI model can use. It’s like building to a standard instead of reinventing the wheel every time.

**Q: How much does this cost to run?**

A: For moderate usage (few hundred requests per day), you’re looking at $20–30/month. Most of that is ECS Fargate compute time.

**Q: Can this handle large files?**

A: Currently it’s designed for text files up to a few MB. For larger files, you’d want to implement streaming or chunking.

**Q: What about security?**

A: The setup includes API Gateway authentication, IAM roles, VPC isolation, and encrypted storage. It’s production-ready from a security standpoint.

**Q: How do I add support for different file types?**

A: You’d extend the MCP server to handle different MIME types and add appropriate parsers (PDF, DOCX, etc.).

**Q: Can I use this with OpenAI instead of Bedrock?**

A: Absolutely! The MCP server is AI-agnostic. You just need to point your AI client to the MCP endpoints.

**Q: What happens if ECS tasks crash?**

A: The service will automatically restart failed tasks, and the load balancer will route traffic away from unhealthy instances.

**Q: How do I monitor this in production?**

A: CloudWatch dashboards, SNS alerts, and the health check endpoints give you full visibility into system health.

**Q: Is this overkill for simple document processing?**

A: Maybe! But it’s designed to scale. If you just need to process a few files, a simple Lambda function might be enough.

**Q: Can I deploy this in multiple AWS regions?**

A: The Terraform code would need modifications for multi-region deployment, but the architecture supports it.

This project is open source on my GitHub. Fork it, break it, improve it!

🚀 Try it yourself: GitHub Repo 💬 Feedback? Connect with me

Written by Rajesh Sood

📢 Have questions or feedback? Drop a comment below or connect with me on Twitter/X@spysood!

Originally published on Medium.