Back to Blog
MCP, SSE, and Agents Oh MY! Building AI Experiences with .NET 10

MCP, SSE, and Agents Oh MY! Building AI Experiences with .NET 10

By Steve Ruben
AIAI Agents.NETMCPServer-Sent Events

"Can you add three unlimited data plans to my cart?"

I typed this into Claude Desktop, and within seconds, my dashboard lit up with real-time notifications. Items appeared in the cart. Inventory updated. Claude responded: "I've added 3 unlimited data plans to your cart. Your total is $147/month. Ready to checkout?"

This wasn't a parlor trick. Claude was actually interacting with a live e-commerce system, browsing products, managing a shopping cart, and processing real Stripe payments. All while broadcasting events to a React dashboard in real-time.

This is an exploration, not a blueprint. I built this demo for NeuroCore Technologies to see what happens when you wire together three technologies that rarely talk to each other: Anthropic's Model Context Protocol (MCP), Microsoft's Agent Framework, and Server-Sent Events in .NET 10. It's not about building the "right" architecture, it's about sparking ideas for what's possible when AI agents, real-time systems, and business logic collide.

The individual pieces are useful on their own. But seeing them work together? That's what gets your brain going in new directions.

The "Holy Crap" Moment

The first time I watched Claude complete an entire shopping transaction, I had that rare developer moment where you think: "Wait, this actually works?"

Claude wasn't just pretending to shop. It was using real APIs through MCP, the cart was updating in Cosmos DB, Stripe was generating actual checkout URLs, and every action was broadcasting events that multiple dashboard clients were receiving simultaneously through Server-Sent Events.

When I asked the AI agent "What are our top-selling products?", it queried the database, analyzed the data, and streamed back insights in natural language. Not canned responses, actual analysis of real data.

This is the kind of integration that makes you rethink what's possible.

Watch It All Come Together

In the demo below, you'll see this flow in real-time. It starts in Claude Desktop searching for products, then cuts to the admin portal where new products are added to the catalog. Back in Claude Desktop, the MCP immediately surfaces those new products from the API. Claude creates an order and returns a live Stripe checkout link. Click it, and you see a fully populated Stripe payment page with everything Claude just added to the cart.

Then we flip back to the admin portal (which has been open the whole time next to Claude Desktop) and ask the AI agent about what was just ordered. It queries the database and streams back the analysis.

Throughout the whole video, keep an eye on the admin portal. Every time Claude invokes an MCP tool on behalf of the user, you see the actual API calls and results streaming in real-time via Server-Sent Events. You're watching Claude interact with a real system, not simulating anything.

The Architecture in 60 Seconds

PhoTelCo/
├── Domain/          # Pure business logic (Product, Customer, Order)
├── Application/     # Services (ProductService, CartService)
├── Infrastructure/  # Cosmos DB, Stripe, SSE implementation
├── McpApi/          # REST API with SSE endpoints
├── McpClient/       # MCP server for Claude Desktop
└── AppHost/         # .NET Aspire orchestration

The key principle: MCP tools are thin wrappers. They delegate to your service layer, not contain business logic. This keeps your architecture clean and testable.

Three Technologies Working in Harmony

1. Model Context Protocol: Giving Claude Hands

MCP is Anthropic's answer to a fundamental problem: how do you let an LLM actually do things instead of just talking about them?

Think of it as REST APIs designed specifically for AI. You define tools (like search_products or add_to_cart) with clear parameters and descriptions, and Claude figures out when and how to use them.

Here's what a simple MCP tool looks like in C#:

[McpServerTool]
public class ProductTools
{
    private readonly ProductService _productService;
    private readonly EventBroadcastService _eventBroadcaster;

    [Description("Search for products by keyword")]
    public async Task<McpToolResult> search_products(string searchTerm)
    {
        var products = await _productService.SearchProductsAsync(searchTerm);
        
        // Broadcast event for real-time dashboard updates
        await _eventBroadcaster.BroadcastAsync(new ActivityEvent
        {
            EventType = "ProductSearched",
            Data = new { searchTerm, resultCount = products.Count }
        });

        if (!products.Any())
            return McpToolResult.Success($"No products found matching '{searchTerm}'");

        var response = new StringBuilder();
        response.AppendLine($"Found {products.Count} products:");
        foreach (var product in products)
        {
            response.AppendLine($"\n📦 {product.Name}");
            response.AppendLine($"   Price: ${product.MonthlyFee}/month");
        }

        return McpToolResult.Success(response.ToString());
    }
}

The C# MCP SDK (currently in preview) makes this straightforward. You install it via NuGet, decorate your methods, and point Claude at your MCP server through claude_desktop_config.json:

{
  "mcpServers": {
    "photelco": {
      "command": "dotnet",
      "args": ["run", "--project", "C:/Source/PhoTelCo/PhoTelCo.McpClient"],
      "env": {
        "PHOTELCO_API_URL": "https://localhost:7178",
        "PHOTELCO_USAGE_KEY": "your-api-key-here"
      }
    }
  }
}

The key insight: Design your MCP tools like APIs, not like chat commands. Make them atomic, specific, and functional. Claude doesn't need tutorials, you give it a tool called add_multiple_to_cart that takes a list of items, and it figures out that "add three unlimited plans" maps to calling your tool with the right parameters. It's remarkably good at understanding function signatures and orchestrating them into workflows.

2. Server-Sent Events: The Real-Time Secret Weapon

Everyone knows WebSockets for real-time communication. But SSE? It's the underdog that should be getting way more attention.

Here's why SSE was perfect for this demo: it's just HTTP. No special protocols, no complex handshakes, no bidirectional overhead when you only need server-to-client streaming. And crucially, it's simpler to implement correctly.

The .NET 10 Game-Changer: Native SSE support via System.Net.ServerSentEvents eliminates all the manual header configuration and string formatting I used to do. The framework now provides typed SSE results with ServerSentEventsResult<T>.

The magic happens with multiple subscriber channels for true pub/sub:

using System.Net.ServerSentEvents;
using System.Threading.Channels;

public class EventBroadcastService
{
    private readonly List<Channel<ActivityEvent>> _subscribers = new();
    private readonly object _lock = new();

    public async Task BroadcastEventAsync(ActivityEvent activityEvent)
    {
        List<Channel<ActivityEvent>> currentSubscribers;
        lock (_lock)
        {
            currentSubscribers = new List<Channel<ActivityEvent>>(_subscribers);
        }

        foreach (var channel in currentSubscribers)
        {
            try
            {
                await channel.Writer.WriteAsync(activityEvent);
            }
            catch (ChannelClosedException)
            {
                // Subscriber disconnected, remove it
                lock (_lock) { _subscribers.Remove(channel); }
            }
        }
    }

    public async IAsyncEnumerable<SseItem<ActivityEvent>> SubscribeAsync(
        [EnumeratorCancellation] CancellationToken cancellationToken)
    {
        var subscriberChannel = Channel.CreateUnbounded<ActivityEvent>();

        lock (_lock) { _subscribers.Add(subscriberChannel); }

        try
        {
            await foreach (var evt in subscriberChannel.Reader.ReadAllAsync(cancellationToken))
            {
                // Return typed SSE items - .NET 10 handles formatting automatically
                yield return new SseItem<ActivityEvent>(evt, evt.EventType.ToString());
            }
        }
        finally
        {
            lock (_lock) { _subscribers.Remove(subscriberChannel); }
            subscriberChannel.Writer.Complete();
        }
    }
}

The endpoint is beautifully simple now with TypedResults.ServerSentEvents:

using Microsoft.AspNetCore.Http.HttpResults;
using System.Net.ServerSentEvents;

[HttpGet("events")]
public ServerSentEventsResult<ActivityEvent> GetEvents()
{
    // .NET 10 automatically configures headers and formatting!
    return TypedResults.ServerSentEvents(GetEventStream(HttpContext.RequestAborted));
}

private async IAsyncEnumerable<SseItem<ActivityEvent>> GetEventStream(
    [EnumeratorCancellation] CancellationToken cancellationToken)
{
    // Send initial connection confirmation
    yield return new SseItem<ActivityEvent>(
        new ActivityEvent { EventType = "Connected", Data = new { message = "Connected" } },
        "Connected");

    // Stream all subsequent events
    await foreach (var sseItem in _eventBroadcastService.SubscribeAsync(cancellationToken))
    {
        yield return sseItem;
    }
}

What's amazing about .NET 10's native SSE:

  • No manual Response.Headers.Append(), it's handled automatically
  • No manual FlushAsync(), the framework does it for you
  • Typed events with SseItem<T> enable automatic JSON serialization
  • Named events (like "Connected", "ProductSearched") that clients can filter on
  • ServerSentEventsResult<T> return type gives you compile-time safety

Here's how it all flows: When Claude calls an MCP tool (which is just a thin wrapper telling Claude how to use your API and what the parameters mean), that triggers a call to your actual API. Your API does the work, broadcasts an event through the EventBroadcastService, and that event flows through the Channel system to every connected dashboard client instantly.

The typed events advantage: With .NET 10's named SSE events, your React frontend can filter events client-side:

const eventSource = new EventSource(`/api/admin/events?usageKey=${apiKey}`);

// Listen for specific event types
eventSource.addEventListener('ProductSearched', (e) => {
    const event = JSON.parse(e.data);
    console.log('Claude searched for:', event.data.searchTerm);
});

eventSource.addEventListener('CheckoutCompleted', (e) => {
    const event = JSON.parse(e.data);
    toast.success(`New sale: $${event.data.amount}`);
    refreshDashboard();
});

// Fallback for any unnamed events
eventSource.onmessage = (e) => {
    const event = JSON.parse(e.data);
    updateActivityFeed(event);
};

The result? You see Claude working. When it searches for products, your dashboard shows "Claude is searching." When checkout initiates, you see the Stripe URL generate. It's like watching a teammate work, not querying an API.

3. Server-Side AI Agents: Asking Business Questions

While Claude was handling customer interactions through MCP, I wanted the admin dashboard to answer business questions: "What are our top products by revenue?" or "Show me sales trends this month."

This is where server-side AI agents come in. Unlike MCP (which is consumed by Claude), this agent runs inside your .NET application using Azure OpenAI. The pattern is function calling: you give the AI tools it can use to fetch and analyze data.

Here's the agent service:

public class ManagementAgentService
{
    private readonly IChatClient _chatClient;
    private readonly CosmosDbRepository<Order> _orderRepo;
    
    public async Task<string> AnalyzeAsync(string query)
    {
        var chatOptions = new ChatOptions
        {
            Tools = new List<AITool>
            {
                AIFunctionFactory.Create(GetTopProducts)
            }
        };
        
        var messages = new List<ChatMessage>
        {
            new ChatMessage(ChatRole.System, 
                "You are a data analyst. Use tools to get data, then analyze and provide insights."),
            new ChatMessage(ChatRole.User, query)
        };
        
        var response = await _chatClient.CompleteAsync(messages, chatOptions);
        return response.Message.Content;
    }
    
    [Description("Get top selling products by revenue or quantity")]
    private async Task<object> GetTopProducts(int topN = 10, string metric = "revenue")
    {
        var orders = await _orderRepo.GetAllAsync();
        var items = orders.SelectMany(o => o.Items);
        
        return items
            .GroupBy(i => i.ProductName)
            .OrderByDescending(g => g.Sum(i => i.Price * i.Quantity))
            .Take(topN)
            .Select(g => new { Product = g.Key, Revenue = g.Sum(i => i.Price * i.Quantity) });
    }
}

The flow is straightforward: When you ask "What are the top 5 products?", the agent realizes it needs data, calls GetTopProducts to query Cosmos DB, gets back actual sales data, analyzes it, and streams back natural language insights.

The key difference from MCP: This runs server-side with Azure OpenAI as part of your application, not in Claude Desktop. It's your business intelligence layer talking to your own data, while MCP is Claude interacting with your customer-facing APIs.

The streaming response is what makes it feel natural. Users see the answer forming token by token instead of waiting through a long pause. It's conversational, not transactional.

What This Demo Really Shows

This isn't about building e-commerce (we have Shopify for that). It's about exploring what becomes possible when you connect systems in ways they weren't originally designed for.

Each piece works fine on its own. MCP lets Claude interact with your APIs. SSE gives you real-time updates. Agent Framework adds intelligence to server-side operations. Standard stuff.

But wire them together and you start asking different questions. What if customer service AIs could actually take actions instead of just answering questions? What if every business operation broadcasted events that both humans and AIs could see? What if your internal analytics talked back in plain English?

The stack is boring in a good way: .NET 10, Cosmos DB, Azure OpenAI, standard REST patterns, dependency injection, clean architecture. The interesting part isn't what you build with, it's how you connect the pieces and what that makes possible.

The Moments That Made Me Smile

Watching Claude bulk-add items: I said "add 5 basic plans and 3 unlimited plans" and watched it parse that into a multi-item cart operation, then saw 8 individual add events flow through the dashboard.

The first real Stripe checkout: Claude generated an actual Stripe payment URL. I clicked it. It worked. The whole flow, from AI interaction to real payment processing, was seamless.

Asking the agent "What's working?": Instead of writing SQL or building a report, I just asked. It queried the data, found that unlimited plans were outselling everything else 3:1, and suggested we feature them more prominently. That's the kind of insight that normally requires a BI tool and 20 minutes of dashboard clicking.

The reconnection handling: I killed the SSE connection, waited, and watched it automatically reconnect with exponential backoff. Such a simple pattern, but it makes the system feel robust instead of fragile.

What I Learned Building This

Events first, features second: I built features then added events as an afterthought. Bad idea. Once I flipped it and made every action broadcast an event from the start, I got real-time dashboards, audit logs, and debugging info basically for free.

Boring MCP tools work better: My first MCP tool tried to be conversational. It sucked. Simple, specific functions like search_products(keyword) and add_to_cart(items) let Claude do what it's good at: figuring out how to compose them.

The .NET 10 SSE improvements are real: System.Net.ServerSentEvents with typed results eliminated all the manual header configuration and flushing. The framework just handles it now.

Cache the expensive stuff: Added 1-hour caching for analytics queries. Response times dropped from 3 seconds to 50ms, Azure OpenAI costs dropped by 80%.

Where This Goes

This demo isn't about e-commerce. It's about what happens when you start thinking of AI as something that can do things, not just say things. Customer support AIs that actually fix issues. Real-time monitoring where AI notices patterns and takes action. Systems where the line between "human operation" and "AI operation" is just a logged event.

I built this in a few weeks to show a friend at NeuroCore what's possible with tools available today. The interesting question isn't "should we build e-commerce this way?" It's "what becomes possible when AI can actually interact with our systems?"

Try It Yourself

Want to explore these patterns? Start with one piece. Get Claude to call a single API through MCP. Set up SSE for one event type. Build one agent tool. See what clicks. The magic happens when you connect them in ways that solve problems you actually have.

What you'll need:

If you're building something similar or want to talk about AI integration patterns, find me on LinkedIn or at steveruben.com.


About NeuroCore Technologies: We're building AI solutions that bridge the gap between conversational interfaces and real business operations. Learn more at neurocoretech.com.

Get In Touch

I'm always interested in discussing innovative technology solutions, strategic partnerships, and opportunities to drive digital transformation. Let's connect and explore how we can create value together.

MCP, SSE, and Agents Oh MY! Building AI Experiences with .NET 10 - Steve Ruben