Building a Meraki MCP Server, Part 3: Deploying to Azure with ACR and Container Apps

In Part 1 of this series, we built a working Cisco Meraki MCP server locally and connected it to Claude Desktop using stdio.

In Part 2, we containerized that application with Docker. We moved the MCP server from local stdio communication to HTTP, packaged the Python application into a reusable Linux container image, injected our Meraki credentials at runtime, exposed port 8000, and verified the /mcp endpoint.

At the end of Part 2, we had this:

Windows Workstation
Docker Desktop
meraki-mcp:local
Running Container
HTTP :8000
Meraki Dashboard API

That’s a major improvement over running Python directly, but we still have one big limitation:

The application is running on our computer.

If our workstation shuts down, the MCP server shuts down.

In Part 3, we’re going to move the workload into Azure.

We’ll create an Azure Container Registry, build and store the Meraki MCP image in the registry, deploy that image with Azure Container Apps, configure managed identity so the Container App can securely pull the private image, inject our Meraki API key as a runtime secret, configure our MCP environment variables, expose port 8000 through Azure’s HTTPS ingress, configure scaling, and verify that the MCP server is responding from Azure.

By the end of this article, our architecture will look like this:

Source Code
Azure Container Registry Build
Azure Container Registry
meraki-mcp:v1
Azure Container Apps
HTTPS
Meraki MCP Server
Meraki Dashboard API

This is the point in the series where the MCP server stops depending on our workstation.

It becomes a cloud-hosted service.

Where We Are in the Series

This is Part 3 of our six-part journey.

Part 1 — Build the MCP server locally and connect it to Claude Desktop.

Part 2 — Containerize the MCP server with Docker.

Part 3 — Store the image in Azure Container Registry and run it with Azure Container Apps.

Part 4 — Secure the remote MCP service using Microsoft Entra ID and Azure API Management.

Part 5 — Move the workload to Azure Kubernetes Service and learn how Kubernetes manages our containers.

Part 6 — Rebuild the platform using Terraform and automate deployments with Azure DevOps pipelines.

For Part 3, we’re concentrating on two Azure services:

Azure Container Registry

and

Azure Container Apps

Understanding the difference between them is important before we start building anything.

Azure Container Registry vs. Azure Container Apps

These two services perform completely different jobs.

Azure Container Registry, or ACR, stores container images.

Azure Container Apps, or ACA, runs container images.

Think back to Part 2:

Docker Image
docker run
Running Container

We’re essentially splitting those responsibilities across Azure services:

Azure Container Registry
stores image
Azure Container Apps
runs image

Another useful way to think about it:

ACR = Container image warehouse
Container Apps = Container runtime

ACR doesn’t execute our MCP server.

An image sitting in ACR isn’t a live container and isn’t actively running our application. It’s simply the packaged image stored in a registry.

Container Apps retrieves that image and creates running instances from it.

The architecture becomes:

Source Code
Build
ACR
Image
Container Apps
Running Container

Why Use Azure Container Registry?

We already built this image locally in Part 2:

meraki-mcp:local

Why can’t we simply tell Azure Container Apps to use it?

Because that image exists only in our local Docker environment.

Azure needs a registry from which it can retrieve the image.

That’s where ACR comes in.

Azure Container Registry gives us a private Azure-hosted location for images such as:

meraki-mcp:v1

As the application evolves, we might eventually have:

meraki-mcp:v1
meraki-mcp:v2
meraki-mcp:v3

And if we eventually create additional MCP servers, the same registry could contain multiple repositories:

meraki-mcp:v1
network-mcp:v1
backup-mcp:v1
directory-mcp:v1

We don’t necessarily need a separate registry for every application.

A registry can contain multiple repositories, and each repository can contain multiple image versions.

Repository vs. Image vs. Tag

Before creating ACR, let’s get three terms straight.

Suppose our image is:

acrmcpdemo.azurecr.io/meraki-mcp:v1

We can break that into:

acrmcpdemo.azurecr.io

Registry.

meraki-mcp

Repository.

v1

Tag.

Conceptually:

Registry
└── meraki-mcp
├── v1
├── v2
└── v3

The tag gives us a way to identify different versions of an image.

For this walkthrough, we’ll use:

meraki-mcp:v1

The Azure Resources We’re Building

For this article, we’ll use generic example resource names that you can replace with names appropriate for your environment.

Resource Group:
rg-mcp-demo-prod
Region:
East US 2
Azure Container Registry:
acrmcpdemo
Container Repository:
meraki-mcp
Image Tag:
v1
Container App:
ca-meraki-mcp-prod

Our basic resource structure will look like:

Resource Group
├── Azure Container Registry
│ └── meraki-mcp:v1
└── Container Apps Environment
└── Container App
└── Meraki MCP Container

Let’s build it.

Step 1: Create the Resource Group

Sign in to the Azure Portal.

Search for:

Resource groups

Select:

Create

Choose your Azure subscription.

For this example, enter:

Resource group:
rg-mcp-demo-prod

Region:

East US 2

Select:

Review + create

Then:

Create

The resource group will serve as the logical container for the Azure resources supporting our MCP deployment.

Step 2: Create Azure Container Registry

In the Azure Portal, search for:

Container registries

Select:

Create

Choose the subscription and resource group:

rg-mcp-demo-prod

Enter a registry name.

For this walkthrough:

acrmcpdemo

Important: Azure Container Registry names must be globally unique across Azure.

That means acrmcpdemo may already be taken by someone else.

If Azure reports that the name isn’t available, use your own unique variation and substitute that name throughout the commands in this article.

Select:

Region:
East US 2

For a lab or small deployment, select:

SKU:
Basic

Select:

Review + create

and then:

Create

Step 3: Understand What We Just Created

At this point, our registry exists.

But it doesn’t contain our application.

That’s an important distinction.

Creating:

acrmcpdemo

does not automatically move:

meraki-mcp:local

from Docker Desktop into Azure.

Right now we have two completely separate things:

LOCAL
Docker Desktop
└── meraki-mcp:local

and:

AZURE
Azure Container Registry
└── Empty

We need to build or push an image into the registry.

For this walkthrough, we’re going to build it directly in Azure.

Step 4: Open Azure Cloud Shell

From the Azure Portal, open Cloud Shell.

If prompted, select PowerShell.

We’re going to clone the source code into Cloud Shell and use Azure Container Registry’s remote build capability.

This also has a nice side benefit.

In Part 2, we encountered the Windows CRLF problem where entrypoint.sh could produce:

exec ./entrypoint.sh: no such file or directory

even though the file existed.

Cloning the repository inside the Linux environment used by Cloud Shell avoids the Windows checkout that caused that particular line-ending problem.

Step 5: Clone the Meraki MCP Project

In Cloud Shell, return to your home directory:

cd ~

Clone the project:

git clone https://github.com/CiscoDevNet/meraki-magic-mcp-community.git

Navigate into the repository:

cd ~/meraki-magic-mcp-community

Verify the files:

ls

Make sure you’re in the project directory containing the Dockerfile.

This is important because we’re about to use the current directory as our container build context.

Step 6: Build the Image Directly in Azure Container Registry

In Part 2, we built our image locally with:

docker build -t meraki-mcp:local .

This time, we’re going to let Azure perform the build.

Run:

az acr build --registry acrmcpdemo --image meraki-mcp:v1 .

If you chose a different globally unique registry name, substitute it here.

The command means:

az acr build

Build a container image using Azure Container Registry.

--registry acrmcpdemo

Use our ACR.

--image meraki-mcp:v1

Name the resulting image meraki-mcp and tag it v1.

And finally:

.

Use the current directory as the build context.

Conceptually:

Cloud Shell
Source Code
az acr build
ACR Build
Dockerfile
Container Image
meraki-mcp:v1
Azure Container Registry

Unlike our Part 2 process, we don’t need to separately build the image locally and then push it.

az acr build uploads the source, performs the build in Azure, and stores the resulting image in ACR.

Step 7: Verify the Repository

Once the build finishes, don’t immediately move on to Container Apps.

First verify that the image actually exists.

Run:

az acr repository list --name acrmcpdemo --output table

You should see:

meraki-mcp

Now check the available tags:

az acr repository show-tags --name acrmcpdemo --repository meraki-mcp --output table

You should see:

v1

We now have:

Azure Container Registry
└── meraki-mcp
└── v1

This verification is important.

A Container App can’t run an image that doesn’t actually exist in the registry.

Step 8: Verify the Image in the Azure Portal

We can also verify this graphically.

In the Azure Portal, navigate to:

Container registries
→ acrmcpdemo
→ Repositories

You should see:

meraki-mcp

Select it.

You should then see:

v1

At this point, the image exists in Azure.

But remember:

Nothing is running.

ACR stores images.

It does not run containers.

We now need a runtime.

Step 9: Introducing Azure Container Apps

Azure Container Apps is going to be our first Azure runtime for the MCP server.

Container Apps allows us to run containers without directly managing the underlying virtual machines or Kubernetes cluster.

We’re essentially going to tell Azure:

Here is my image.
Run it.
Give it CPU and memory.
Inject my configuration.
Inject my secret.
Listen on port 8000.
Expose it through HTTPS.
Keep at least one instance running.

Azure manages the underlying compute infrastructure.

Conceptually:

Azure Container Registry
meraki-mcp:v1
Azure Container Apps
Running Container

Step 10: Create the Container App

In the Azure Portal, search for:

Container Apps

Select:

Create

Choose your subscription.

For the resource group, select:

rg-mcp-demo-prod

For the Container App name, use:

ca-meraki-mcp-prod

Region:

East US 2

You’ll also need a Container Apps Environment.

If you don’t already have an appropriate environment, create a new one.

You might use a name such as:

cae-mcp-demo-prod

Conceptually:

Container Apps Environment
Container App
Container

The environment provides the boundary and underlying infrastructure in which our Container App operates.

Step 11: Select the Container Image

During the Container App creation process, configure the container image.

Choose:

Image source:
Azure Container Registry

Registry:

acrmcpdemo

Repository:

meraki-mcp

Tag:

v1

The complete image reference will look like:

acrmcpdemo.azurecr.io/meraki-mcp:v1

We now have the relationship:

Azure Container Registry
acrmcpdemo
meraki-mcp:v1
Azure Container Apps
ca-meraki-mcp-prod

Step 12: Configure CPU and Memory

Container Apps allows us to assign CPU and memory resources to the running container.

For a lightweight MCP server, we don’t need to start with a large amount of compute.

Start conservatively and monitor actual utilization before increasing resources.

The important distinction here is:

ACR Image
Stored artifact

versus:

Container App Replica
Running workload
CPU + Memory

The registry stores the image.

The replica consumes compute resources while running the application.

Step 13: Configure Ingress

Our MCP server needs to receive HTTP traffic.

Enable:

Ingress

For this stage of the series, allow traffic from:

Anywhere

Set the target port to:

8000

Transport:

Auto

Leave insecure connections disabled so HTTPS is enforced.

Our traffic flow becomes:

Internet
HTTPS :443
Azure Container Apps Ingress
Target Port 8000
Container
Meraki MCP Server

Notice that users aren’t directly connecting to public port 8000.

Azure provides the public HTTPS endpoint and routes the request internally to port 8000 inside the application.

Step 14: Configure the MCP Environment Variables

Our application still needs the same runtime configuration we used with Docker.

The hosting platform has changed.

The application hasn’t.

Configure the appropriate environment variables:

MERAKI_ORG_ID=<YOUR ORGANIZATION ID>
READ_ONLY_MODE=true
MCP_TRANSPORT=http
MCP_HOST=0.0.0.0
MCP_PORT=8000
MCP_SERVER=dynamic

These should look familiar from Part 2.

We still need:

MCP_TRANSPORT=http

because this is now a network service.

We still need:

MCP_HOST=0.0.0.0

because the application needs to listen on the container’s available network interfaces.

And we still need:

MCP_PORT=8000

because that’s the port our Container App ingress will target.

Step 15: Create the Meraki API Key Secret

Our most sensitive environment value is:

MERAKI_API_KEY

Do not bake the API key into the container image.

Also avoid storing it as an ordinary plaintext environment variable.

Instead, create a Container App secret.

Use a generic secret name such as:

meraki-api-key

Enter your actual Meraki API key as the secret value.

Then create the environment variable:

MERAKI_API_KEY

and configure it to reference:

meraki-api-key

Conceptually:

Container App Secret
meraki-api-key
Environment Variable
MERAKI_API_KEY
Container
Python Application

The application can continue retrieving the value as an environment variable.

It doesn’t need to know how Azure stored the underlying secret.

Step 16: Why the Secret Isn’t Stored in ACR

This distinction is worth reinforcing.

Our container image contains the application and everything required to execute it.

It should not contain environment-specific credentials.

Think of the architecture like this:

Azure Container Registry
meraki-mcp:v1
Application
Python Runtime
Dependencies
Startup Instructions

Separately:

Azure Container Apps
Runtime Configuration
MERAKI_API_KEY
MERAKI_ORG_ID
READ_ONLY_MODE
MCP_TRANSPORT
MCP_HOST
MCP_PORT

When the Container App starts:

Container Image
+
Runtime Configuration
+
Secret
Running Application

This means we could theoretically use the exact same image in development and production while supplying different runtime configuration to each environment.

Step 17: Enable Managed Identity

We have another authentication problem to solve.

Our Container App needs to retrieve:

meraki-mcp:v1

from our private registry:

acrmcpdemo

Instead of creating and storing another username and password, we’ll use Azure Managed Identity.

Open the Container App and navigate to:

Identity

Enable:

System assigned

and save the change.

Azure creates an identity associated specifically with the Container App.

Conceptually:

Container App
System-Assigned Managed Identity
Azure RBAC
Azure Container Registry

This gives our application an Azure identity without requiring us to manually manage credentials for it.

But simply creating the identity doesn’t give it permission to do anything.

We still need authorization.

Step 18: Grant AcrPull

The Container App needs permission to pull images from our registry.

The Azure RBAC role specifically designed for this is:

AcrPull

The relationship we want is:

ca-meraki-mcp-prod
System-Assigned Managed Identity
AcrPull
acrmcpdemo
meraki-mcp:v1

AcrPull doesn’t make the Container App an administrator of the registry.

It grants the ability required to retrieve container images.

Step 19: Grant AcrPull in the Azure Portal

Navigate to:

Azure Portal
→ Container registries
→ acrmcpdemo
→ Access control (IAM)

Select:

Add
→ Add role assignment

Choose:

AcrPull

For the member type, select:

Managed identity

Select the managed identity associated with:

ca-meraki-mcp-prod

Complete the role assignment.

Azure RBAC changes aren’t always instantaneous.

Give the assignment a short amount of time to propagate before troubleshooting an image-pull failure.

Step 20: Grant AcrPull with Azure CLI

We can perform the same operation using Cloud Shell.

Run:

az role assignment create `
--assignee (az containerapp show -n ca-meraki-mcp-prod -g rg-mcp-demo-prod --query identity.principalId -o tsv) `
--role AcrPull `
--scope (az acr show -n acrmcpdemo --query id -o tsv)

Let’s break that apart.

This portion:

az containerapp show -n ca-meraki-mcp-prod -g rg-mcp-demo-prod --query identity.principalId -o tsv

retrieves the Container App’s managed identity principal ID.

This portion:

az acr show -n acrmcpdemo --query id -o tsv

retrieves the Azure resource ID of the registry.

And:

--role AcrPull

creates the required authorization relationship.

Step 21: Why AcrPull Matters So Much

This is one of those Azure dependencies that can produce symptoms that look unrelated to the actual problem.

Imagine:

The Container App exists.

The registry exists.

The image exists.

The image name is correct.

Ingress is configured.

The environment variables are configured.

But the application never becomes healthy.

The temptation is to start debugging Python or MCP.

But the problem could simply be:

Container App
tries to retrieve image
ACR
ACCESS DENIED

If the runtime can’t retrieve the image, the application never even gets a chance to start.

That’s why a useful troubleshooting order is:

Does the image exist?
Can the runtime pull it?
Can the container start?
Is the application process running?
Is it listening on port 8000?
Can ingress reach it?
Can the client communicate using MCP?

Troubleshooting from the infrastructure inward can save a tremendous amount of time.

Step 22: Redeploy the Image

After assigning AcrPull, give Azure a little time for the role assignment to propagate.

Then force the Container App to use our image:

az containerapp update `
-n ca-meraki-mcp-prod `
-g rg-mcp-demo-prod `
--image acrmcpdemo.azurecr.io/meraki-mcp:v1 `
--no-wait

Notice:

--no-wait

Instead of leaving Cloud Shell waiting for the deployment to finish, Azure accepts the request and returns control to us.

We can then inspect the deployment status separately.

Step 23: Understand Container App Revisions

Azure Container Apps uses revisions.

A revision is an immutable snapshot of a particular Container App configuration.

Conceptually:

Container App
├── Revision 1
│ └── Original configuration
├── Revision 2
│ └── Updated image/configuration
└── Revision 3
└── Newer configuration

Changes to revision-scoped settings can cause Azure to create a new revision.

This is an important shift away from the traditional server model.

We’re not thinking:

Keep modifying the same server forever.

We’re moving toward:

Define configuration
Deploy
Create new version
Replace old workload

We’ll see this model become even more prominent when we get to Kubernetes.

Step 24: Check the Container App Status

From Cloud Shell:

az containerapp show `
-n ca-meraki-mcp-prod `
-g rg-mcp-demo-prod

You can also inspect the revisions:

az containerapp revision list `
-n ca-meraki-mcp-prod `
-g rg-mcp-demo-prod `
-o table

We’re looking for an active, healthy revision.

If the Container App shows a failed state and no revision successfully provisions, don’t immediately assume the application code is broken.

First verify the image exists and the managed identity can retrieve it.

Step 25: Configure Minimum Replicas

Azure Container Apps can automatically scale workloads based on demand.

That includes scaling an application all the way down to:

0 replicas

This can be useful for reducing cost.

But it can also be confusing when you’re building an interactive MCP service and expect it to respond immediately.

For this walkthrough, we’ll keep at least one replica running.

In the Azure Portal, configure:

Minimum replicas:
1

Or use Cloud Shell:

az containerapp update `
-n ca-meraki-mcp-prod `
-g rg-mcp-demo-prod `
--min-replicas 1 `
--no-wait

Now Azure should maintain at least one running instance of the application.

Step 26: What Is a Replica?

This term will become extremely important when we get to Kubernetes.

A replica is a running instance of our containerized application.

We have one stored image:

ACR
└── meraki-mcp:v1

Container Apps might create:

Replica 1
└── running meraki-mcp:v1

If we configured the platform to run multiple instances, it could create:

Replica 1
└── meraki-mcp:v1
Replica 2
└── meraki-mcp:v1
Replica 3
└── meraki-mcp:v1

We still have only one image stored in ACR.

The platform is simply creating multiple running instances from that image.

This is the same fundamental concept we’ll later see with Kubernetes Pods.

Step 27: Get the Container App URL

Open:

Azure Portal
→ Container Apps
→ ca-meraki-mcp-prod

Azure provides an Application URL.

It will look similar to:

https://ca-meraki-mcp-prod.<generated-domain>.<region>.azurecontainerapps.io

Your generated domain will be different.

The actual MCP endpoint is:

/mcp

So the URL we ultimately care about becomes:

https://<YOUR-CONTAINER-APP-URL>/mcp

Step 28: Test the Root URL

From your workstation, run:

curl.exe -i https://<YOUR-CONTAINER-APP-URL>/

You may receive a 404.

As we learned in Part 2, a 404 isn’t automatically bad.

The root path:

/

isn’t our MCP endpoint.

What matters is where the response came from.

Step 29: Understand the Good 404 vs. the Bad 404

This distinction became particularly useful while troubleshooting the deployment.

In Part 2, our local container returned something like:

HTTP/1.1 404 Not Found
server: uvicorn

That was actually good.

The request traveled through Docker and reached our application.

The application simply didn’t have a route for:

/

Conceptually:

Request
Container
Uvicorn
Application
404

Azure can also generate its own platform-level unavailable response when there isn’t a healthy application replica behind the ingress endpoint.

Those two situations are completely different.

Don’t look only at:

404

Ask:

Who returned the 404?

Our application?

Or Azure’s platform?

Step 30: Test the Actual MCP Endpoint

Now test the route we actually care about:

curl.exe -i https://<YOUR-CONTAINER-APP-URL>/mcp

A response may look similar to:

HTTP/1.1 406 Not Acceptable
server: uvicorn
mcp-session-id: <session-id>
{"jsonrpc":"2.0","id":"server-error","error":{"code":-32600,"message":"Not Acceptable: Client must accept text/event-stream"}}

That might look like failure.

It’s actually an extremely useful result.

Remember Part 2:

A generic curl request isn’t a full MCP client.

The server is effectively telling us:

I received your request.
You reached the MCP endpoint.
But you're not speaking the protocol the way an MCP client should.

That’s exactly what we want to prove at this stage.

Step 31: Trace the Request

Our request path now looks like this:

Your Computer
HTTPS
Azure Container Apps
Public Ingress
Target Port 8000
Container App Replica
Linux Container
Uvicorn
FastMCP
/mcp

When the MCP server needs Meraki information:

FastMCP
MERAKI_API_KEY
HTTPS
Meraki Dashboard API

Meanwhile, the container itself came from:

Azure Container Registry
AcrPull
Managed Identity
Container Apps

Each component has a distinct responsibility.

Step 32: Our Complete Part 3 Architecture

We can now visualize the entire deployment.

BUILD PATH
GitHub Source
Azure Cloud Shell
az acr build
Azure Container Registry
Repository: meraki-mcp
Tag: v1

Then:

RUNTIME PATH
Azure Container Registry
AcrPull
System-Assigned Managed Identity
Azure Container Apps
Running Replica
Linux Container
MCP_TRANSPORT=http
MCP_HOST=0.0.0.0
MCP_PORT=8000
HTTPS Ingress
/mcp

And finally:

API PATH
MCP Server
MERAKI_API_KEY
Meraki Dashboard API

Step 33: Final Validation

Before calling Part 3 complete, verify each layer.

Verify ACR

Run:

az acr repository list --name acrmcpdemo --output table

You should see:

meraki-mcp

Then:

az acr repository show-tags `
--name acrmcpdemo `
--repository meraki-mcp `
--output table

You should see:

v1

Verify the Container Image

The Container App should reference:

acrmcpdemo.azurecr.io/meraki-mcp:v1

Verify Managed Identity

The Container App should have:

System assigned:
On

Verify ACR Authorization

The Container App’s managed identity should have:

AcrPull

on:

acrmcpdemo

Verify Ingress

Confirm:

Ingress:
Enabled
Traffic:
External
Target port:
8000
Insecure connections:
Disabled

Verify Runtime Configuration

Confirm:

READ_ONLY_MODE=true
MCP_TRANSPORT=http
MCP_HOST=0.0.0.0
MCP_PORT=8000
MCP_SERVER=dynamic

Confirm:

MERAKI_API_KEY

references a secret rather than exposing the API key as a normal value.

Verify Scaling

Confirm:

Minimum replicas:
1

Verify the Application

Finally:

curl.exe -i https://<YOUR-CONTAINER-APP-URL>/mcp

We want a response from the MCP application itself.

A protocol-related 406 response from Uvicorn/FastMCP confirms we’ve successfully reached the application.

Troubleshooting Part 3

Several problems can look very similar during this deployment.

Let’s walk through them in the order I’d troubleshoot them.

ACR Name Is Unavailable

Azure Container Registry names are globally unique.

If:

acrmcpdemo

is unavailable, choose a more distinctive name.

Remember to substitute your chosen registry name throughout the rest of the commands.

az acr build Can’t Find the Dockerfile

Check your current directory:

pwd

Then:

ls

Make sure you’re inside:

meraki-magic-mcp-community

before running:

az acr build --registry acrmcpdemo --image meraki-mcp:v1 .

Remember what the final period means:

.
=
current directory

If you’re in the wrong directory, Azure receives the wrong build context.

Container App Shows Failed

Don’t immediately start debugging the Python application.

First verify the repository:

az acr repository list --name acrmcpdemo --output table

Then verify the image tag:

az acr repository show-tags `
--name acrmcpdemo `
--repository meraki-mcp `
--output table

You need:

meraki-mcp

and:

v1

If the Container App references an image that doesn’t exist, the application can never start.

Image Exists but the Container App Still Won’t Start

Check:

Managed Identity

and:

AcrPull

The Container App’s identity needs permission to retrieve the image.

You can create the assignment with:

az role assignment create `
--assignee (az containerapp show -n ca-meraki-mcp-prod -g rg-mcp-demo-prod --query identity.principalId -o tsv) `
--role AcrPull `
--scope (az acr show -n acrmcpdemo --query id -o tsv)

Then allow a little time for RBAC propagation.

az containerapp update Appears to Hang

A deployment command waiting indefinitely doesn’t necessarily mean Azure CLI itself is broken.

The new revision may be unable to become healthy.

One possible cause is that the Container App can’t retrieve its image.

Check:

Image exists?

Then:

Managed identity enabled?

Then:

AcrPull assigned?

After correcting the problem, redeploy using:

az containerapp update `
-n ca-meraki-mcp-prod `
-g rg-mcp-demo-prod `
--image acrmcpdemo.azurecr.io/meraki-mcp:v1 `
--no-wait

Then inspect revisions separately:

az containerapp revision list `
-n ca-meraki-mcp-prod `
-g rg-mcp-demo-prod `
-o table

Azure Says the Container App Is Stopped or Doesn’t Exist

Check the scaling configuration.

If the minimum replica count is zero, the application may have scaled down.

For an interactive MCP service, set:

az containerapp update `
-n ca-meraki-mcp-prod `
-g rg-mcp-demo-prod `
--min-replicas 1 `
--no-wait

Then give Azure time to start a healthy replica.

Root URL Returns 404

Don’t use:

/

as your only application test.

Test:

/mcp

instead:

curl.exe -i https://<YOUR-CONTAINER-APP-URL>/mcp

Look for a response from:

server: uvicorn

rather than an Azure-generated unavailable page.

/mcp Returns 406 Not Acceptable

That’s expected from our basic curl test.

A generic HTTP request isn’t a complete MCP client request.

The important part is that:

/mcp

exists and FastMCP is processing the request.

At this stage:

406 from Uvicorn/FastMCP

is much better than:

Connection refused

or:

Connection timed out

or an Azure platform unavailable page.

A Useful Troubleshooting Sequence

Here’s the troubleshooting sequence I recommend for this deployment:

1. Container App fails
2. Verify ACR repository
3. Verify image tag
4. Verify Container App image reference
5. Verify managed identity
6. Verify AcrPull
7. Check revisions
8. Verify at least one replica is running
9. Verify ingress target port 8000
10. Test /mcp
11. Confirm response comes from Uvicorn

This sequence is valuable because it works from the infrastructure inward.

There’s no reason to troubleshoot Python if Azure can’t even retrieve the container image.

What We Built

Let’s compare our architecture across the first three parts.

Part 1:

Claude Desktop
stdio
Local Python MCP Server
Meraki Dashboard API

Part 2:

Windows Workstation
Docker
Linux Container
HTTP :8000
Meraki MCP Server
Meraki Dashboard API

And now Part 3:

Azure Container Registry
meraki-mcp:v1
Managed Identity + AcrPull
Azure Container Apps
Running Replica
HTTPS Ingress
/mcp
Meraki MCP Server
Meraki Dashboard API

We’ve separated the application into several clearly defined responsibilities:

Source Code
Defines the application
Dockerfile
Defines the container
Azure Container Registry
Stores the image
Managed Identity
Provides an Azure identity
AcrPull
Authorizes image retrieval
Azure Container Apps
Runs the image
Container App Secret
Protects the Meraki API key
Environment Variables
Configure the application
Ingress
Exposes the service over HTTPS
Meraki Dashboard API
Provides the underlying network data

That’s a dramatically different architecture from the Python process we started with in Part 1.

And our workstation is no longer responsible for keeping the MCP server alive.

But We Now Have a Security Problem

We’ve accomplished our main objective:

The MCP server is running in Azure.

But there’s an obvious issue.

For this stage of the build, we enabled:

External ingress

That makes the Container App publicly reachable.

Our Meraki API key is protected as a runtime secret.

That’s good.

But protecting the API key answers only this question:

How does the MCP server authenticate to Meraki?

We still need to answer another question:

How does a user authenticate to the MCP server?

Those are two completely different trust relationships.

TRUST RELATIONSHIP #1
MCP Server
Meraki Dashboard API

and:

TRUST RELATIONSHIP #2
MCP Client
MCP Server

We’ve addressed the first.

We haven’t fully addressed the second.

That’s exactly what we’ll tackle next.

Where We’re Going Next

Our current architecture is:

Internet
Azure Container Apps
Meraki MCP Server
Meraki Dashboard API

We now want to introduce identity and API governance.

Conceptually, we’re heading toward:

MCP Client
Microsoft Entra ID
OAuth Access Token
Azure API Management
Authentication
Authorization
API Policies
Azure Container Apps
Meraki MCP Server
Meraki Dashboard API

This is where the project starts moving from simply “hosting a container in Azure” toward an enterprise MCP architecture.

Coming Next: Part 4 — Securing the Meraki MCP Server with Microsoft Entra ID and Azure API Management

In Part 4, we’ll take the publicly reachable MCP server we just deployed and add the identity and security layers required to control who can actually use it.

We’ll work through:

Microsoft Entra ID app registrations

Resource applications and client applications

OAuth scopes

Application ID URIs

User authentication

Access tokens

Token audiences

Azure API Management

MCP authorization discovery

Protected Resource Metadata

JWT validation

API Management policies

Custom domains

Certificates

and the complete authenticated request path from an MCP client through Azure API Management to our Container App.

Our architecture will evolve from:

Internet
Container Apps
MCP Server

to:

Authenticated MCP Client
Microsoft Entra ID
OAuth Access Token
Azure API Management
Authentication + Authorization + Policy
Azure Container Apps
Meraki MCP Server
Meraki Dashboard API

At that point, we won’t just have a container running in Azure.

We’ll have the beginnings of a secure, governed MCP platform.

Next: Part 4 — Securing a Meraki MCP Server with Microsoft Entra ID and Azure API Management

Leave a comment