container app - deploying and setting up mcp server and calling it remotely from your vscode agent

To create a container app, we need to setup our mcp server code, then deploy to the server in Azure. Finally we will test it using our vscode. 

 The following are the code we using for todo task. 



from mcp.server.fastmcp import FastMCP
from mcp.server.transport_security import TransportSecuritySettings
from task_store import store

mcp = FastMCP("TasksMCP", stateless_http=True,
              transport_security=TransportSecuritySettings(enable_dns_rebinding_protection=False)
              )


@mcp.tool()
async def list_tasks() -> list[dict]:
    """List all tasks with their ID, title, description, and completion status."""
    return store.get_all()


@mcp.tool()
async def get_task(task_id: int) -> dict | None:
    """Get a single task by its numeric ID.

    Args:
        task_id: The numeric ID of the task to retrieve.
    """
    return store.get_by_id(task_id)


@mcp.tool()
async def create_task(title: str, description: str) -> dict:
    """Create a new task with the given title and description. Returns the created task.

    Args:
        title: A short title for the task.
        description: A detailed description of what the task involves.
    """
    return store.create(title, description)


@mcp.tool()
async def toggle_task_complete(task_id: int) -> str:
    """Toggle a task's completion status between complete and incomplete.

    Args:
        task_id: The numeric ID of the task to toggle.
    """
    task = store.toggle_complete(task_id)
    if task:
        status = "complete" if task["is_complete"] else "incomplete"
        return f"Task {task['id']} is now {status}."
    return f"Task with ID {task_id} not found."


@mcp.tool()
async def delete_task(task_id: int) -> str:
    """Delete a task by its numeric ID.

    Args:
        task_id: The numeric ID of the task to delete.
    """
    if store.delete(task_id):
        return f"Task {task_id} deleted."
    return f"Task with ID {task_id} not found."

And this is what our main.py looks like 

from contextlib import AsyncExitStack, asynccontextmanager

from fastapi import FastAPI
from fastapi.responses import JSONResponse

from mcp_server import mcp

@asynccontextmanager
async def lifespan(app: FastAPI):
    async with AsyncExitStack() as stack:
        await stack.enter_async_context(mcp.session_manager.run())
        yield

app = FastAPI(lifespan=lifespan)

@app.get("/health")
async def health():
    return JSONResponse({"status": "healthy"})

app.mount("/", mcp.streamable_http_app())

And this is the task_store.py

from dataclasses import dataclass, field
from datetime import datetime, timezone


@dataclass
class TaskItem:
    id: int
    title: str
    description: str
    is_complete: bool = False
    created_at: datetime = field(default_factory=lambda: datetime.now(timezone.utc))

    def to_dict(self) -> dict:
        return {
            "id": self.id,
            "title": self.title,
            "description": self.description,
            "is_complete": self.is_complete,
            "created_at": self.created_at.isoformat(),
        }


class TaskStore:
    def __init__(self):
        self._tasks: list[TaskItem] = [
            TaskItem(1, "Buy groceries", "Milk, eggs, bread"),
            TaskItem(2, "Write docs", "Draft the MCP tutorial", True),
        ]
        self._next_id = 3

    def get_all(self) -> list[dict]:
        return [t.to_dict() for t in self._tasks]

    def get_by_id(self, task_id: int) -> dict | None:
        task = next((t for t in self._tasks if t.id == task_id), None)
        return task.to_dict() if task else None

    def create(self, title: str, description: str) -> dict:
        task = TaskItem(self._next_id, title, description)
        self._next_id += 1
        self._tasks.append(task)
        return task.to_dict()

    def toggle_complete(self, task_id: int) -> dict | None:
        task = next((t for t in self._tasks if t.id == task_id), None)
        if task is None:
            return None
        task.is_complete = not task.is_complete
        return task.to_dict()

    def delete(self, task_id: int) -> bool:
        task = next((t for t in self._tasks if t.id == task_id), None)
        if task is None:
            return False
        self._tasks.remove(task)
        return True


# For demonstration only — not thread-safe.
store = TaskStore()

To get it deploy (we are using powershell) you need to run the following code. 

$RESOURCE_GROUP="my-mcp-rg"
$LOCATION="australiaeast"
$ENVIRONMENT_NAME="mcp-env"
$APP_NAME="tasks-mcp-server-py"

-- create container app environment

az group create --name $RESOURCE_GROUP --location $LOCATION

az containerapp env create --name $ENVIRONMENT_NAME  
--resource-group $RESOURCE_GROUP --location $LOCATION

-- deploy container app

az containerapp up --name $APP_NAME
--resource-group $RESOURCE_GROUP --environment $ENVIRONMENT_NAME
--source . --ingress external --target-port 8080

az containerapp ingress cors enable --name $APP_NAME
--resource-group $RESOURCE_GROUP --allowed-origins
"*" --allowed-methods "GET,POST,DELETE,OPTIONS" --allowed-headers "*"

verify the deployment

$APP_URL=$(az containerapp show --name $APP_NAME
--resource-group $RESOURCE_GROUP --query
"properties.configuration.ingress.fqdn" -o tsv)

Please replace with your endpoint

$APP_URL=https://<endpoint>
curl https://$APP_URL/health

az containerapp update --name $APP_NAME
--resource-group $RESOURCE_GROUP --min-replicas 1

Please check to ensure the container app has been deployed successfully,

And you should get the following outputs :- 

And ensuring the endpoint is healthy too



Finally let's setup and test out environment. Press Ctrl + Alt + P) and look for "Add MCP server" and select 2nd option - HTTP or SSE event)


And then provide the full URL of your server from Azure and next provide server id to it


And ensure you have selected 'workspace'


Please open .vscode/mcp.json and start the server and it should look like this :-



And then open your chat prompt and issue the following prompt (you can choose your own). As you can see we have created the following tasks:-



And finally let's list down all our task so far 









Comments

Popular posts from this blog

Windows SSH: Permissions for 'private-key' are too open

NodeJS: Error: spawn EINVAL in window for node version 20.20 and 18.20