Beyond Localhost: Running an MCP Server for Kentico Xperience on AWS with Docker A developer built and deployed a hosted Model Context Protocol (MCP) server on AWS that lets an AI assistant create draft landing pages directly in a live Xperience by Kentico CMS, using ECS, RDS, Docker and an ALB. The proof-of-concept pipeline handles authentication and networking end-to-end so an AI can take a real CMS action without a human clicking through admin screens, though it remains an MVP limited to draft pages with a title, tag, headline and call-to-action button. The author notes Kentico's own KentiCopilot MCP servers are designed to run locally, whereas this project targets a cloud-hosted server any MCP client can reach securely. A few weeks ago I got curious about something: could I actually let an AI assistant create real content on a real website, safely, end-to-end, without a human clicking through admin screens? Not a mockup. Not a " wouldn't it be cool if " thought experiment. An actual working pipeline, in production, on AWS. This post is the full story of how I built that - the wins, the two-in-the-morning bugs, the moment I almost destroyed my own infrastructure by accident, and the final demo where it all clicked into place. If you work with Xperience by Kentico, MCP, or you're just curious what it takes to wire an AI up to a real CMS, this one's for you. I'd already built and shipped a Xperience by Kentico site Dancing Goat, the standard Kentico sample project on AWS - ECS, RDS, an ALB, the whole production setup. That project was done and live. Read more about previous article - https://medium.com/@pawansharma 25255/from-localhost-to-live-on-aws-deploying-kenticos-dancing-goat-with-docker-terraform-and-github-14f129734481 https://medium.com/@pawansharma 25255/from-localhost-to-live-on-aws-deploying-kenticos-dancing-goat-with-docker-terraform-and-github-14f129734481 But I kept coming back to a question: everyone's talking about " AI agents " doing real work, but what does that actually require under the hood, technically, for a real CMS? MCP Model Context Protocol kept coming up as the emerging standard for this - a way for AI assistants to discover and call "tools" that do real things in the real world, instead of just generating text. I didn't want to just read about it. I wanted to build one, badly, get it wrong a bunch of times, and come out the other side actually understanding the pieces. So I picked a concrete, scoped goal: let an AI create a draft landing page in my Kentico site. Small enough to be achievable. Real enough to force me through every layer - auth, networking, the CMS's own internals - that a "toy demo" would let me skip. Here's the honest pitch, no hype: right now, if a marketing person wants a new landing page, someone has to log into the CMS admin, click "New Page," type in the fields, pick a template, save. Every single time. It's not hard, but it's manual, and it doesn't scale, and it can't be delegated to an AI assistant working alongside a human. This project is a proof of concept for a different model: a marketing person or an AI acting on their behalf describes what they want in plain language, and it gets created directly in the CMS - safely, with proper authentication, without anyone hand-typing into admin forms. I want to be upfront about scope: this is an MVP, not a finished product. It creates draft pages with a title, a tag, and a simple headline and call-to-action button. It doesn't write full page copy, it doesn't handle every content type, and it has some rough edges I'll get into. But the hard part - the plumbing that lets an AI safely reach into a CMS and take a real action - works, end-to-end, in production. That's what this post is really about. Before I go further, an important note. Kentico now ships its own MCP servers as part of KentiCopilot : a Documentation MCP server https://docs.kentico.com/documentation/developers-and-admins/installation/mcp-server read-only, searches Kentico's docs , a Content Modeling MCP server, and a Management MCP server https://docs.kentico.com/documentation/developers-and-admins/api/management-api that can actually create and edit pages and content through the management API - still in preview as I write this. They're good, and for day-to-day development I'd use them. But they're designed to run next to a local Xperience instance, started by your IDE or AI client. I wanted something different: a hosted MCP server, running in the cloud, that any MCP client can reach securely, so an AI can create pages on a live site without anyone running a local project. That's what this post is about. It's a proof of concept, not a replacement for the official tooling. I built one tool create draft landing page that creates a real page in a real Kentico instance, and I put a secured, containerized path in front of it on AWS. Within that, there was a second fork: should the MCP server talk to Kentico directly using its content/page management APIs IContentItemManager , IWebPageManager , or should it call something else? I initially assumed I could embed those Kentico APIs straight into the standalone MCP server project. That turned out to be wrong - those APIs need to run inside a real, fully-initialized Xperience by Kentico application DI container, licensing, the works . A separate .NET console-style MCP server can't just import them and go. So the actual architecture became: The MCP server doesn't touch Kentico directly at all . It calls a plain internal HTTP endpoint I built inside the Dancing Goat application, and that endpoint is the one running inside the real Xperience by Kentico app, using the real IWebPageManager APIs. This distinction cost me some wasted time early on, but it's the right shape for anyone doing something similar. This is the part most tutorials skip. Here's what actually went wrong, in the order it happened: The "confused deputy" IAM trust policy issue. When I gave the AgentCore Gateway's IAM role a plain Service principal, AWS rejected it with "not authorized to assume the execution role." Turns out you need explicit Condition blocks aws:SourceAccount, aws:SourceArn - undocumented in the basic examples I found. Took real digging to track down. A silently-wrong Gateway target URL. Pointing the Gateway target at the bare API Gateway root URL no /mcp suffix caused it to enter a FAILED state with a 404 - not obvious from the error alone. An invisible page-template bug. Pages created through my code looked fine in the content tree, but threw InvalidOperationException the moment anyone tried to open them: "the web page does not have a page template assigned." There was no documented way to fix this through the public API. I ended up creating a page manually through the admin UI, then literally querying the database directly to diff the working page against the broken one, and found the difference sitting in one JSON column ContentItemCommonDataVisualBuilderTemplateConfiguration . That column wasn't exposed anywhere in the public C API — I had to set it through IInfoProvider directly. A near-disaster with Terraform. At one point, a CI pipeline ran terraform plan and showed "22 resources to destroy" - my entire MCP infrastructure: the Gateway, Cognito, API Gateway, the lot. My stomach dropped. It turned out the plan failed before it could apply a provider schema error saved me , and a later, successful run showed "0 destroyed" — nothing was actually lost. But it was a real reminder: always scope Terraform applies with target, and never assume a green checkmark in CI means "safe," read the actual plan output. PowerShell quoting and encoding gremlins. A UTF-8 BOM silently added by Out-File broke JSON parsing on the receiving end. Running commands in the wrong order pasting a whole block where later lines depended on earlier ones executing first produced confusing, unrelated-looking errors. A production-only 60-second hang. Everything worked locally. In production, calling the tool would just... hang, for exactly 60 seconds, then fail with a 504. It took pulling the actual application logs not the MCP server's logs - the Kentico app's logs to find the real cause: Kentico's Continuous Integration feature was trying to bulk-insert file-tracking metadata into the database as a side effect of page creation, and that insert was timing out. Disabling Continuous Integration fixed it instantly. AI billing. For the demo chat interface, I originally wired it up to call Claude's API directly — then hit "your credit balance is too low" the first time I actually tested it. For an MVP, I ended up building a free fallback that parses simple quoted phrases out of a sentence instead of calling an AI model at all, just to prove the pipeline without needing to set up billing. None of these were exotic. They were the normal, grinding kind of bugs that eat a whole afternoon - and I think that's worth writing down honestly, instead of pretending it was all smooth. Here's everything that ended up in the stack, at a glance: Amazon ECS Fargate - running both the Dancing Goat website itself and the new MCP server, as two separate services in the same cluster Amazon ECR - two repositories, one per container image Application Load Balancer ALB - public-facing, in front of Dancing Goat Network Load Balancer NLB - internal, in front of the MCP server API Gateway's VPC Link can only target an NLB or Cloud Map, not a bare ECS service Amazon API Gateway HTTP API - the internal hop between the Gateway and the NLB, secured with IAM/SigV4 VPC Link - bridges API Gateway into the private VPC Amazon Cognito - a user pool + app client issuing OAuth client-credentials tokens, used as the inbound authorizer for the Gateway Amazon Bedrock AgentCore Gateway - the actual front door that MCP clients connect to; validates the Cognito JWT, then signs and forwards requests on with IAM Amazon RDS SQL Server - the database behind Dancing Goat from the earlier, separate hosting project Amazon CloudWatch Logs - for both services; this is what ultimately cracked the production-only hang AWS Secrets Manager - holding the DB connection string Amazon S3 - used later to host the free, no-backend HTML demo page as a static website for chat IAM - roles and trust policies wiring all of the above together securely Everything's managed as Terraform, organized into small, focused modules rather than one giant file: modules/networking - VPC, subnets, NAT, security groups including a new dedicated one for the MCP server, allowing inbound 8080 only from within the VPC modules/ecr / modules/ecr mcp - one repo each for Dancing Goat and the MCP server modules/ecs / modules/ecs mcp - two ECS services sharing one cluster; I gave the MCP server its own module rather than reusing the existing one, since the existing one hardcoded an ALB dependency I didn't want here modules/nlb mcp - internal NLB + target group + listener for the MCP server modules/vpclink mcp - the VPC Link resource connecting API Gateway into the VPC modules/apigw mcp - the HTTP API Gateway itself, IAM-authorized, proxying through to the NLB modules/cognito mcp - the user pool, resource server, app client client-credentials flow , and hosted domain modules/agentcore gateway mcp - the Bedrock AgentCore Gateway resource and its target, plus the IAM role with that confused-deputy trust policy fix One practical lesson from the Terraform side: I bumped the AWS provider from ~ 5.0 to ~ 6.0 partway through needed for the newer AgentCore resource types , and from that point on, every apply got scoped with -target , specifically to avoid touching the existing, tightly-coupled module.ecs/module.rds resources by accident. Given the near-miss I described above, I'd treat that as a hard rule for anyone doing something similar, not just a nice-to-have. Phase 1 - MCP server, local only. Built with ModelContextProtocol.AspNetCore, one tool CreateDraftLandingPage , verified with the MCP Inspector's CLI mode its web UI had its own unrelated bug, so CLI became the standard way to test throughout . Phase 2 - containerize and deploy. Standard multi-stage Dockerfile, new ECR repo, new ECS service in the existing cluster, confirmed reachable over the internal network from a bastion host. Phase 3 - outbound auth Gateway → MCP server . Chose IAM/SigV4 over rolling my own OAuth validation inside the MCP server - built the NLB, VPC Link, and IAM-authorized API Gateway, confirmed the full chain with a SigV4-signed curl request. Phase 4 - inbound auth caller → Gateway . Added Cognito for JWT-based authorization on the Gateway itself, plus the AgentCore Gateway resource and target. Tested with a real Cognito client-credentials token, all the way through to a real tools/call response. Phase 5 - making the tool do something real. This was the biggest phase. Added an internal controller inside Dancing Goat itself, wired up real page creation via IWebPageManager , hit and fixed the invisible template-assignment bug, wired the MCP server's tool to call this new endpoint over HTTP, deployed both pieces to production, and finally root-caused and fixed the Continuous Integration hang that was silently blocking everything in production only. Phase 6 - making it demoable. Built a small single-file HTML chat page, first wired to Claude's API blocked by billing on a fresh account , then reworked into a free version that parses quoted phrases directly, so the whole thing could be demoed without any AI billing setup at all. Extended the tool further to add real page content a headline and a CTA button, using Kentico's Page Builder widget structure instead of just an empty templated shell. This section is where I'm dropping in the concrete artifacts — swap in your own screenshots from your run-through at each marked point. Building and pushing the MCP server image: docker build -t dancing-goat-mcp:v2 . aws ecr get-login-password --region ap-south-1 | docker login --username AWS --password-stdin