Skip to main content

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