← Back to list

Extending AWS DevOps Agent with Slack Report Delivery Using MCP

How we built a custom MCP server to give our AI agent the ability to push operational reports directly to Slack.

Faizan Shah · 2026-06-07 06:59 · 0 claps · 6.0 min read
#ai-agent #slackbot #weekly-report #aws-devops-agent #aws
Open on Medium ↗
Wiki topics: AGT · AI Agents ☁️ · DevOps & Cloud

Extending AWS DevOps Agent with Slack Report Delivery Using MCP

How we built a custom MCP server to give our AI agent the ability to push operational reports directly to Slack.

The Problem

We use AWS DevOps Agent heavily for infrastructure monitoring — ECS metrics, billing summaries, incident reviews, service health checks, grafana and newrelic. The agent is great at compiling this data into well-structured reports when you ask it in chat.

But here’s the gap: the reports stay in the chat window.

Our team lives in Slack. Weekly reports, incident summaries, and on-call handoffs all happen there. Manually copying agent output into Slack channels every week? That doesn’t scale. We needed the agent to deliver reports directly where the team actually reads them.

AWS DevOps Agent doesn’t support Slack (or email) delivery out of the box. So we built it ourselves.

The Idea: MCP as the Extension Layer

The Model Context Protocol (MCP) is a standard for giving AI agents access to external tools. Instead of hacking webhooks or writing Lambda glue code, we could expose a proper tool that the agent natively understands.

The plan:

  1. Build an MCP server with a send_report_to_slack tool
  2. Host it privately on ECS (no public internet exposure)
  3. Create a skill in the DevOps Agent that knows when and how to use this tool
  4. Wire it all together with a Jenkins CI/CD pipeline

Architecture Overview

The full architecture keeps everything inside the private VPC. The DevOps Agent calls the MCP server through a private Application Load Balancer (no public internet exposure), and the MCP server pushes formatted reports to Slack via an incoming webhook.

Request Flow

Here’s what happens when someone asks for a report:

  1. An EventBridge Schedular initiates a chat to generate weekly report (create-chat)
  2. The agent’s skill invokes the MCP tool via POST /mcp/
  3. MCP server formats the markdown and fires the Slack webhook
  4. Slack returns 200 OK
  5. MCP server confirms delivery back to the agent
  6. Agent makes sure, report is delivered

Building the MCP Server

The MCP server is a single Python file. It uses the mcp library's Streamable HTTP transport over Starlette, which gives us a standard HTTP endpoint the agent can call.

The Tool Definition

@server.list_tools()
async def list_tools() -> list[Tool]:
    return [
        Tool(
            name="send_report_to_slack",
            description=(
                "Send a markdown-formatted report to the configured Slack channel. "
                "Use for weekly metrics reports, incident summaries, or any "
                "structured report delivery."
            ),
            inputSchema={
                "type": "object",
                "properties": {
                    "title": {
                        "type": "string",
                        "description": "Report title"
                    },
                    "content": {
                        "type": "string",
                        "description": "Full report content in markdown format"
                    },
                    "channel_id": {
                        "type": "string",
                        "description": "Optional Slack channel ID"
                    }
                },
                "required": ["title", "content"]
            }
        )
    ]

The agent sees this tool description and knows exactly when to use it — whenever a user asks for a report to be delivered.

Markdown → Slack Block Kit

Slack doesn’t render standard markdown. We convert it to Block Kit format:

def format_report_as_slack_blocks(title: str, content: str) -> list[dict]:
    blocks = [
        {
            "type": "header",
            "text": {"type": "plain_text", "text": f"📊 {title}", "emoji": True}
        },
        {"type": "divider"}
    ]

    sections = re.split(r'(?=^## )', content, flags=re.MULTILINE)

    for section in sections:
        if section.strip():
            slack_text = convert_markdown_to_slack(section.strip())
            for chunk in chunk_text(slack_text, 2900):
                blocks.append({
                    "type": "section",
                    "text": {"type": "mrkdwn", "text": chunk}
                })

    return blocks

Each markdown ## heading becomes a separate Block Kit section, and we chunk text at 2900 characters (Slack's limit is 3000) to avoid truncation.

Authentication

Simple API key auth. The agent passes an X-API-Key header, validated before any MCP request is processed:

class AuthenticatedMCPApp:
    """ASGI app wrapper that validates API key before forwarding to MCP."""
    async def __call__(self, scope, receive, send):
        request = Request(scope, receive)
        error = verify_api_key(request)
        if error:
            await error(scope, receive, send)
            return
        await self._session_mgr.handle_request(scope, receive, send)

Hosting: ECS Behind a Private Load Balancer

We deliberately chose not to expose this to the internet. The MCP server handles internal operational data — it should only be reachable from the agent’s network space.

Infrastructure Setup

  • ECS Fargate — runs the containerized MCP server
  • Private ALB — internal-only, not internet-facing
  • ACM Certificate — TLS termination on the ALB using a private CA
  • Private Hosted Zone — reports-mcp.staging.acme.internal resolves only within the VPC
  • Security Groups — inbound restricted to the agent’s CIDR range

The Dockerfile

Minimal and production-ready:

FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY slack_mcp_server.py .
ENV PORT=8000
EXPOSE 8000
CMD ["python", "slack_mcp_server.py"]

Secrets (SLACK_WEBHOOK_URL, API_KEY_VALUE) are injected via ECS task definition environment variables — never baked into the image.

The Slack App

We created a Slack app called reportingAgent with a single capability: an Incoming Webhook scoped to our ops channel.

Setup takes about 2 minutes:

  1. Go to api.slack.com/apps → Create New App
  2. Enable Incoming Webhooks
  3. Add webhook to your target channel
  4. Copy the webhook URL into your ECS task definition as SLACK_WEBHOOK_URL

That’s it. No bot tokens, no OAuth flows, no socket mode. An incoming webhook is the simplest way to push messages to a channel.

The Agent Skill

The skill is what ties everything together. It’s configured in the AWS DevOps Agent and does two things:

  1. Recognizes intent — when a user asks for a report to be delivered (not just generated), the skill activates
  2. Orchestrates the flow — the agent compiles the report data, formats it as markdown, then calls the MCP server’s send_report_to_slack tool

From the user’s perspective, the interaction looks like:

User: “Generate a weekly ECS metrics report for our services and send it to the ops channel”

Agent: compiles metrics, formats report, calls MCP tool

Agent: “✅ Report ‘Weekly ECS Metrics Report’ sent to Slack channel #ops-oncall”

The report shows up in Slack formatted with headers, tables, and context — not a raw text dump.

CI/CD: Jenkins Pipeline

We have a Jenkins pipeline that handles the full deployment lifecycle:

  1. Git Checkout — pull latest code
  2. Docker Build — build the container image
  3. Push to ECR — store in our private registry
  4. Update ECS Task — register new task definition
  5. Deploy Service — update ECS service with new task
  6. Health Check — hit GET /health on the private ALB to confirm the new version is live.

If the health check fails, the pipeline rolls back automatically.

Gotchas We Hit Along the Way

1. Trailing Slash Redirect (307)

Starlette’s Mount("/mcp", ...) redirects /mcp → /mcp/ with a 307. The agent was sending requests without the trailing slash and failing silently. Fix: handle both /mcp and /mcp/ paths explicitly in the route config.

2. TLS Certificate Trust

The private ALB uses an ACM Private CA certificate. Clients (including the agent) need the CA root cert in their trust store. On macOS, we added it to the System Keychain. In the container runtime, the CA bundle needed to be explicitly configured.

# Testing locally with the private CA cert
curl --cacert /path/to/private-ca.crt https://reports-mcp.staging.acme.internal/health

Scheduled Reports

Now Reports generation is automated as per schedule:

  • EventBridge Scheduler fires a cron rule (e.g., every Monday 9:00 AM UTC)
  • It sends a predefined message to the DevOps Agent chat
  • The agent compiles the weekly report and delivers it via the existing MCP → Slack pipeline

Same infrastructure, zero new code. Just a scheduled trigger upstream.

Key Takeaways

  1. MCP is the right abstraction for extending AI agents. Instead of building custom integrations, you expose tools the agent already knows how to discover and call.
  2. Keep it private. Not everything needs a public endpoint. A private ALB + VPC-only DNS keeps your operational tooling isolated.
  3. Slack webhooks are underrated. For one-way message delivery, they’re simpler than bot tokens and require zero maintenance.
  4. One tool, many reports. The send_report_to_slack tool is generic — it works for ECS metrics, billing, incidents, or anything else the agent can compile. The intelligence lives in the agent and the skill, not the delivery mechanism.
  5. Start manual, automate later. We validated the entire flow with on-demand reports before adding scheduling. Incremental delivery beats big-bang launches.

Try It Yourself

The MCP server is a single Python file with minimal dependencies:

mcp>=1.0.0
httpx>=0.27.0
starlette>=0.37.0
uvicorn>=0.29.0

You can run it locally in under a minute:

export SLACK_WEBHOOK_URL="https://hooks.slack.com/services/YOUR/WEBHOOK/URL"
export API_KEY_VALUE="your-secret-key"
python slack_mcp_server.py

Then point any MCP-compatible agent at http://localhost:8000/mcp/ with the appropriate API key header, and you've got Slack delivery.

If you’re building custom tooling for AI agents, MCP servers are worth exploring. They’re lightweight, protocol-native, and far less brittle than prompt-engineering your way to integrations.


메타데이터
post_id
37e82df89172
slug
extending-aws-devops-agent-with-slack-report-delivery-using-mcp-37e82df89172
url
https://medium.com/@faizanshah801/extending-aws-devops-agent-with-slack-report-delivery-using-mcp-37e82df89172
canonical_url
https://medium.com/@faizanshah801/extending-aws-devops-agent-with-slack-report-delivery-using-mcp-37e82df89172
author_url
https://medium.com/@faizanshah801
status
ok
fetched_at
2026-06-09 15:37:30