Large language models (LLMs) are revolutionizing the way we build software. But as the scope of intelligent applications grows, so does the need for structured, contextual communication between LLMs and real-world data, services, and business logic.

This is where the Model Context Protocol (MCP) comes in — a lightweight but powerful standard for exposing structured context and functional APIs to LLMs. Think of it as the REST for AI-native applications.

In this article, you’ll learn how to:

  • Understand what MCP is and why it matters
  • Build a custom MCP Server in TypeScript
  • Host it in a Docker container
  • Deploy it to Microsoft Azure using the Azure Developer CLI (azd)
  • Extend the server with your own tools and data

We’ll use the excellent powergentic/azd-mcp-ts open-source template, from Powergentic.ai, as our base — a production-friendly scaffold for building MCP-compatible services.

Whether you’re building internal tools, AI copilots, or advanced chat workflows — this article will help you build the bridge between your data and the model.

What Is the Model Context Protocol (MCP)?

The Model Context Protocol (MCP) is an open, standardized way for large language models (LLMs) to interact with structured data, tools, and workflows provided by an external server. Instead of throwing everything into a giant text prompt, MCP gives LLMs well-defined interfaces — making it easier to build more powerful, predictable, and maintainable AI-driven applications.

MCP is used by clients like Claude Desktop, in-app LLM agents, and even automated orchestration systems. It enables composability between tools, safe access to structured data, and stronger interaction patterns between LLMs and apps.

In simple terms, MCP is like an API for LLMs, but purpose-built for their unique needs.

Why Was MCP Created?

Traditional LLM prompting relies on stuffing context (like documents, data, and instructions) into a single, unstructured input. As your app grows, this becomes brittle and hard to scale.

MCP solves this by:

  • Separating content from control – your app manages what data/tools are exposed, and the LLM simply consumes them
  • Encouraging composability – you can build reusable interfaces to structured information and actions
  • Improving safety and auditability – every tool call and resource read is trackable

This model is already influencing advanced LLM platforms like Claude Desktop, multi-agent frameworks, and autonomous agents.

MCP Core Primitives

At the heart of MCP are three primitive building blocks that your server can implement:

🔹 Primitive 📋 Description 🧑‍💻 Analogy
Resource Read-only, contextual data the model can query. Think files, config, schemas, user profiles, etc. GET endpoint (REST)
Tool Actionable functions that the model can invoke. They may trigger side effects, compute values, or call external services. POST endpoint (REST)
Prompt Reusable message templates that shape how the LLM responds. These can be invoked by the user or triggered programmatically. Slash command / macro

These primitives are designed to support LLM-native use cases, where understanding, decision-making, and interaction are central to the app’s functionality.

How MCP Works at Runtime

Here’s what a typical MCP interaction looks like:

  1. A client (like Claude Desktop or a custom frontend) connects to your MCP server via Server-Sent Events (SSE).
  2. The server advertises what resources, tools, and prompts it supports.
  3. The LLM (or user) requests a resource like greeting://alice, or calls a tool like tools://calculate-bmi.
  4. Your server returns the requested data or executes the tool, streaming the response back to the client.

This approach gives you:

  • Real-time communication via SSE
  • Declarative descriptions of what your server offers
  • A clear separation of roles between server logic and LLM usage

Benefits of Using MCP

Here are several of the benefits of using Model Context Protocol (MCP) servers with your LLM / AI Agent solution:

  • Better control over what data and actions an LLM can access
  • 🧱 Modular server design using tools, resources, and prompts
  • 🛡️ Safer and more auditable than arbitrary code generation
  • 🔄 Composable across clients — use the same MCP server in Claude Desktop, your internal chatbots, or custom LLM agents
  • 🌐 Language-agnostic — servers can be written in Python, TypeScript, or any language with an SDK

Think of it like building an API for your LLM — but way more tailored for how language models consume information and execute tasks.


Why Use TypeScript to Build an MCP Server?

TypeScript is a natural choice for building MCP servers. Here’s why:

  • Strong typing makes your tool/resource definitions safer and easier to maintain.
  • Fast iteration with familiar tooling (Node.js, npm, etc.)
  • 🧩 The official @modelcontextprotocol/sdk is built for modern TypeScript workflows.

And with the powergentic/azd-mcp-ts template, you get a ready-to-run project scaffold that uses:

  • Express.js for HTTP + Server-Sent Events (SSE)
  • Docker for consistent builds
  • azd for seamless Azure deployment

At the time or writing this, the Model Context Protocol (MCP) and it’s SDKs include more examples and better documentation around using TypeScript and Node.js for building MCP servers. You can also check out the modelcontextprotocol/server project on GitHub for a ton of great MCP server examples.

Inside the powergentic/azd-mcp-ts Template

Let’s look at the powergentic/azd-mcp-ts project layout you can use as the base foundation for building your own MCP servers using TypeScript, Docker and Azure Container Apps (ACA):

azd-mcp-ts/
├── src/
│   └── mcpserver/
│       └── server.ts  # Main MCP server definition
├── infra/             # Infrastructure-as-code for Azure
├── Dockerfile         # Docker image for local + cloud use
├── azure.yaml         # azd metadata config
└── package.json

The heart of the server lives in src/mcpserver/server.ts, which uses the MCP SDK to expose a resource and wire up the transport layer.

Here’s a very simplified version of the server code (see the project for the full code):

import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
import express from "express";

const server = new McpServer({ name: "Demo", version: "1.0.0" });

server.resource(
  "greeting",
  new ResourceTemplate("greeting://{name}", { list: undefined }),
  async (uri, { name }) => ({
    contents: [{ uri: uri.href, text: `Hello, ${name}!` }]
  })
);

const app = express();
app.get("/sse", async (req, res) => {
  const transport = new SSEServerTransport("/messages", res);
  await server.connect(transport);
});

This defines a dynamic MCP resource that returns personalized greetings when the LLM queries greeting://your-name.


What Is SSE (Server-Send Events) and Why Does MCP Use It?

One of the most important technical underpinnings of the Model Context Protocol is its use of Server-Sent Events (SSE) for real-time, event-driven communication between your MCP server and clients (like Claude Desktop or an LLM app).

Let’s break down what SSE is, how it compares to alternatives, and why it’s such a natural fit for MCP.

What Are Server-Sent Events (SSE)?

SSE is a web technology that allows a server to push updates to a client over a single, long-lived HTTP connection. It’s part of the HTML5 standard and works over plain HTTP/1.1, making it widely supported and easy to implement.

Key properties:

  • One-way communication: server → client
  • Streamed as text/event-stream
  • Reconnection and heartbeat built in
  • Works great for streaming logs, updates, or – in the case of MCP – model responses

In contrast, traditional HTTP is request-response. SSE lets the server proactively send new information as it becomes available.

  1. The Client Connects

    The client (e.g. Claude Desktop) opens a persistent connection to the server’s /sse endpoint using HTTP and starts listening for events.

    GET /sse HTTP/1.1
    Accept: text/event-stream
    

    Your MCP server responds and begins streaming messages:

    Content-Type: text/event-stream
    
    event: resourceUpdate
    data: {"uri": "greeting://alice", "text": "Hello, Alice!"}
    
    event: toolResponse
    data: {"tool": "calculate-bmi", "result": "22.4"}
    
  2. Server Streams Responses

    As your server receives and handles requests — like reading a resource or executing a tool — it streams back events over the open SSE channel. These events are structured using the MCP message protocol.

    That might include:

    • Results from a tool invocation

    • Errors or status updates

    • Output from a long-running task

    • Progress updates during file processing

    • Content from a streaming model output

  3. Client Sends Request Separately

    The client sends requests (like calling a tool or reading a resource) via a separate /messages endpoint, typically using HTTP POST.

    This separation of concerns — read (SSE) vs write (POST) — helps keep the protocol simple and reliable. It’s also well-suited for environments like Azure Container Apps, which support HTTP-based communication out of the box.

Diagram: MCP Server-Send Events (SSE) Workflow
Diagram: MCP Server-Send Events (SSE) Workflow

Why SSE instead of WebSockets?

At first glance, you might wonder why the Model Context Protocol doesn’t use WebSockets, which are a more common choice for real-time communication in modern web apps. After all, WebSockets offer full-duplex (two-way) messaging and are popular in chat apps, multiplayer games, and collaborative tools.

But MCP has a different set of priorities — simplicity, compatibility, and reliability in cloud-native environments. For the types of interactions that LLMs require, Server-Sent Events (SSE) offers a better balance of performance and practicality.

Here’s a closer comparison between SSE and WebSockets:

Feature SSE WebSocket
Protocol HTTP/1.1 (text/event-stream) Custom TCP protocol
Direction One-way (server → client) Two-way
Complexity Simple More complex to manage
HTTP-compatible ✅ Yes ❌ Requires upgrade
Cloud support ✅ Yes ❌ Not always supported
Ideal for… Real-time updates, streaming Games, chat apps, full duplex scenarios

MCP’s use case is mostly server-push – streaming data and updates to LLM clients. So SSE is simpler, more compatible, and gets the job done.


Containerizing Your MCP Server with Docker

Before deploying to the cloud, we need a consistent runtime environment — enter Docker.

The powergentic/azd-mcp-ts template includes a preconfigured Dockerfile that packages the MCP server into a lean container. Here’s what it does at a high level:

# 1. Use a minimal Node.js base image
FROM node:20-slim

# 2. Set working directory
WORKDIR /app

# 3. Copy dependencies and install
COPY package*.json ./
RUN npm install --production

# 4. Copy source code
COPY . .

# 5. Expose port and run the server
EXPOSE 3000
CMD ["npm", "start"]

Here’s the docker commands to build and test the container locally:

docker build -t mcp-server .
docker run -p 3000:3000 mcp-server

Once built, this image can be:

  • ✅ Run locally for development
  • 🚀 Deployed to a Docker host; like Azure Container Apps or Kubernetes

Deploying MCP Server to Azure with the Azure Developer CLI (azd)

The Azure Developer CLI (azd) is a modern developer experience that simplifies deploying full-stack apps to Azure. It uses convention over configuration and supports Infra-as-Code out of the box.

With the powergentic/azd-mcp-ts Azure Developer CLI template, you can get started and deploy your own custom Model Context Protocol (MCP) server with just a few commands:

  1. Create a new folder for your azd project:

    mkdir mcp-server
    cd mcp-server
    
  2. Initialize the project from the template:

    azd init --template powergentic/azd-mcp-ts
    
  3. Login to Azure:

    azd auth login
    
  4. Deploy the MCP Server:

    azd up
    

    This step does the following:

    • Builds Docker image
    • Provisions Azure Container Registry, Azure Container Apps, Log Analytics, and a managed environment
    • Deploys your MCP server behind a public HTTPS endpoint

🎉 Your MCP Server is live and ready to connect with clients like Claude Desktop or a custom MCP client.


Extending the MCP Server with Tools and Prompts

With your server deployed, it’s time to make it your own.

Let’s add a new tool that calculates BMI (Body Mass Index):

Edit src/mcpserver/server.ts

import { z } from "zod";

// Add this after your greeting resource
server.tool(
  "calculate-bmi",
  {
    weightKg: z.number(),
    heightM: z.number()
  },
  async ({ weightKg, heightM }) => ({
    content: [{
      type: "text",
      text: `Your BMI is ${(weightKg / (heightM * heightM)).toFixed(2)}`
    }]
  })
);

This exposes a tool that can be called by an LLM or agent when integrated with your MCP server via:

tools://calculate-bmi

Wrapping Up: The Power of MCP + TypeScript + Azure

In this article, you learned how to:

✅ Understand what Model Context Protocol (MCP) is and why it matters
✅ Use TypeScript and the official SDK to define MCP resources and tools
✅ Build a lightweight MCP Server with real-time communication via SSE
✅ Package it into a Docker container
✅ Deploy it to Azure using the Azure Developer CLI (azd)
✅ Customize the server with your own logic, endpoints, and context

This will give you the foundation for building your own production-grade Model Context Protocol (MCP) server for extending your AI Agent with additional functionality!

Chris Pietschmann is a Microsoft MVP, HashiCorp Ambassador, and Microsoft Certified Trainer (MCT) with 20+ years of experience designing and building Cloud & Enterprise systems. He has worked with companies of all sizes from startups to large enterprises. He has a passion for technology and sharing what he learns with others to help enable them to learn faster and be more productive.
Microsoft MVP HashiCorp Ambassador

Discover more from Build5Nines

Subscribe now to keep reading and get access to the full archive.

Continue reading