Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/microsoft/mcp-for-beginners/llms.txt

Use this file to discover all available pages before exploring further.

Overview

While MCP’s standard transports (stdio and HTTP streaming) serve most use cases, enterprise environments often require specialized transport mechanisms for improved scalability and cloud-native integration. This guide covers custom transport implementations using Azure Event Grid and Azure Event Hubs.

Azure Event Grid

Serverless, event-driven routing — ideal for loosely coupled MCP architectures

Azure Event Hubs

High-throughput, real-time streaming — ideal for high-frequency MCP interactions
Specification reference: This guide reflects MCP Specification 2025-11-25 transport requirements.

Transport requirements

Message Protocol:
  format: "JSON-RPC 2.0 with MCP extensions"
  bidirectional: "Full duplex communication required"
  ordering: "Message ordering must be preserved per session"

Transport Layer:
  reliability: "Transport MUST handle connection failures gracefully"
  security: "Transport MUST support secure communication"
  identification: "Each session MUST have unique identifier"

Azure Event Grid transport

Event Grid provides serverless event routing — ideal for distributing MCP requests across multiple processing nodes.

Architecture

MCP Client ──► Azure Event Grid ──► MCP Server Function

              ◄─────┘ (response via separate topic)
using Azure.Messaging.EventGrid;

public class EventGridMcpTransport : IMcpTransport
{
    private readonly EventGridPublisherClient _publisher;
    private readonly string _clientId;

    public EventGridMcpTransport(
        string topicEndpoint, string accessKey, string clientId)
    {
        _publisher = new EventGridPublisherClient(
            new Uri(topicEndpoint),
            new AzureKeyCredential(accessKey));
        _clientId = clientId;
    }

    public async Task SendMessageAsync(McpMessage message)
    {
        var eventGridEvent = new EventGridEvent(
            subject:      $"mcp/{_clientId}",
            eventType:    "MCP.MessageReceived",
            dataVersion:  "1.0",
            data:         JsonSerializer.Serialize(message))
        {
            Id        = Guid.NewGuid().ToString(),
            EventTime = DateTimeOffset.UtcNow
        };

        await _publisher.SendEventAsync(eventGridEvent);
    }
}

// Azure Function receiver
[FunctionName("McpEventGridReceiver")]
public async Task<IActionResult> HandleEventGridMessage(
    [EventGridTrigger] EventGridEvent eventGridEvent,
    ILogger log)
{
    var mcpMessage = JsonSerializer.Deserialize<McpMessage>(
        eventGridEvent.Data.ToString());

    var response = await _mcpServer.ProcessMessageAsync(mcpMessage);
    await _transport.SendMessageAsync(response);

    return new OkResult();
}

Azure Event Hubs transport

Event Hubs provides high-throughput, low-latency streaming — ideal for scenarios with many concurrent MCP sessions.
using Azure.Messaging.EventHubs;
using Azure.Messaging.EventHubs.Producer;
using Azure.Messaging.EventHubs.Consumer;

public class EventHubsMcpTransport : IMcpTransport, IDisposable
{
    private readonly EventHubProducerClient _producer;
    private readonly EventHubConsumerClient _consumer;

    public EventHubsMcpTransport(
        string connectionString,
        string eventHubName,
        string consumerGroup = "$Default")
    {
        _producer = new EventHubProducerClient(connectionString, eventHubName);
        _consumer = new EventHubConsumerClient(
            consumerGroup, connectionString, eventHubName);
    }

    public async Task SendMessageAsync(McpMessage message)
    {
        var messageBody = JsonSerializer.Serialize(message);
        var eventData   = new EventData(Encoding.UTF8.GetBytes(messageBody));

        eventData.Properties.Add("MessageType", message.Method ?? "response");
        eventData.Properties.Add("MessageId",   message.Id);
        eventData.Properties.Add("Timestamp",   DateTimeOffset.UtcNow);

        await _producer.SendAsync(new[] { eventData });
    }

    public async Task StartReceivingAsync(Func<McpMessage, Task> messageHandler)
    {
        await foreach (PartitionEvent partitionEvent in
            _consumer.ReadEventsAsync(_cancellationTokenSource.Token))
        {
            var messageBody = Encoding.UTF8.GetString(
                partitionEvent.Data.EventBody.ToArray());
            var mcpMessage = JsonSerializer.Deserialize<McpMessage>(messageBody);
            await messageHandler(mcpMessage);
        }
    }

    public void Dispose()
    {
        _cancellationTokenSource?.Cancel();
        _producer?.DisposeAsync().AsTask().Wait();
        _consumer?.DisposeAsync().AsTask().Wait();
    }
}

Advanced patterns

Message batching for throughput

public class BatchingEventGridTransport : IMcpTransport
{
    private readonly List<McpMessage> _messageBuffer = new();
    private const int MaxBatchSize = 100;

    public async Task SendMessageAsync(McpMessage message)
    {
        lock (_messageBuffer)
        {
            _messageBuffer.Add(message);
            if (_messageBuffer.Count >= MaxBatchSize)
                _ = Task.Run(FlushMessages);
        }
    }

    private async Task FlushMessages()
    {
        List<McpMessage> toSend;
        lock (_messageBuffer)
        {
            toSend = new List<McpMessage>(_messageBuffer);
            _messageBuffer.Clear();
        }
        if (toSend.Any())
        {
            var events = toSend.Select(CreateEventGridEvent);
            await _publisher.SendEventsAsync(events);
        }
    }
}

Hybrid transport routing

public class HybridMcpTransport : IMcpTransport
{
    private readonly IMcpTransport _realtimeTransport; // Event Hubs
    private readonly IMcpTransport _batchTransport;    // Event Grid
    private readonly IMcpTransport _fallbackTransport; // HTTP Streaming

    public async Task SendMessageAsync(McpMessage message)
    {
        var transport = message.Method switch
        {
            "tools/call" when IsRealtime(message) => _realtimeTransport,
            "resources/read" when IsBatch(message) => _batchTransport,
            _ => _fallbackTransport
        };
        await transport.SendMessageAsync(message);
    }
}

Best practices

Use managed identity

Avoid connection strings in production. Use DefaultAzureCredential for authentication.

Implement idempotency

Message processing must be idempotent to safely handle Event Hubs at-least-once delivery.

Add dead-letter queues

Configure dead-letter topics for messages that fail processing to prevent data loss.

Monitor with Application Insights

Add telemetry to all send/receive paths to track latency, throughput, and errors.

Build docs developers (and LLMs) love