# Microsoft Agent Framework - Production Patterns

> **Scope:** universal
> **Layer:** 0 (always load)
> **Keywords:** middleware, a2a, mcp, caching, production, observability
> **Load When:** always

**Ref:** `setup.md` — Core setup | `workflows.md` — Orchestration

Middleware, A2A protocol, MCP integration, caching, and observability for production agents.

---

## Middleware Pipeline

Three middleware types intercept agent behavior at different layers:

| Type | Intercepts | Use Case |
|------|-----------|----------|
| **Agent Run** | Full agent invocation | Logging, auth, rate limiting |
| **Function Calling** | Individual tool calls | Error handling, validation, auditing |
| **IChatClient** | Raw LLM calls | Telemetry, caching, content filtering |

### Agent Run Middleware

```csharp
async Task<AgentResponse> LoggingMiddleware(
    IEnumerable<ChatMessage> messages,
    AgentSession? session,
    AgentRunOptions? options,
    AIAgent innerAgent,
    CancellationToken cancellationToken)
{
    Console.WriteLine($"Input messages: {messages.Count()}");
    var response = await innerAgent.RunAsync(messages, session, options, cancellationToken);
    Console.WriteLine($"Output messages: {response.Messages.Count}");
    return response;
}
```

### Function Calling Middleware (Error Handling)

```csharp
async ValueTask<object?> ErrorHandlingMiddleware(
    AIAgent agent,
    FunctionInvocationContext context,
    Func<FunctionInvocationContext, CancellationToken, ValueTask<object?>> next,
    CancellationToken cancellationToken)
{
    try
    {
        Console.WriteLine($"Calling: {context.Function.Name}");
        var result = await next(context, cancellationToken);
        Console.WriteLine($"Result: {result}");
        return result;
    }
    catch (Exception ex)
    {
        // Return error message the agent can understand
        return $"Error calling {context.Function.Name}: {ex.Message}. Try a different approach.";
    }
}
```

### IChatClient Middleware

```csharp
async Task<ChatResponse> TelemetryMiddleware(
    IEnumerable<ChatMessage> messages,
    ChatOptions? options,
    IChatClient innerChatClient,
    CancellationToken cancellationToken)
{
    var sw = Stopwatch.StartNew();
    var response = await innerChatClient.GetResponseAsync(messages, options, cancellationToken);
    sw.Stop();

    Console.WriteLine($"LLM call: {sw.ElapsedMilliseconds}ms, Tokens: {response.Usage?.TotalTokenCount}");
    return response;
}
```

### Registering Middleware

```csharp
// On an existing agent
var middlewareAgent = originalAgent
    .AsBuilder()
        .Use(runFunc: LoggingMiddleware, runStreamingFunc: LoggingStreamingMiddleware)
        .Use(ErrorHandlingMiddleware)  // Function calling
    .Build();

// On the chat client (before agent creation)
var chatClient = new AzureOpenAIClient(new Uri(endpoint), new AzureCliCredential())
    .GetChatClient(deploymentName)
    .AsIChatClient()
    .AsBuilder()
        .Use(getResponseFunc: TelemetryMiddleware, getStreamingResponseFunc: null)
    .Build();

// Via factory method on AsAIAgent
var agent = new AzureOpenAIClient(new Uri(endpoint), new AzureCliCredential())
    .GetChatClient(deploymentName)
    .AsAIAgent("You are a helpful assistant.", clientFactory: (chatClient) => chatClient
        .AsBuilder()
            .Use(getResponseFunc: TelemetryMiddleware, getStreamingResponseFunc: null)
        .Build());
```

**Important:** If only non-streaming middleware is provided, streaming calls run in non-streaming mode.

---

## A2A Protocol (Agent-to-Agent)

Expose agents as interoperable web services using the standardized A2A protocol.

### Packages

```xml
<PackageReference Include="Microsoft.Agents.AI.Hosting.A2A.AspNetCore" Version="1.0.0-preview.*" />
```

### Expose Agent via A2A

```csharp
// Program.cs
var builder = WebApplication.CreateBuilder(args);

IChatClient chatClient = new AzureOpenAIClient(
        new Uri(endpoint), new DefaultAzureCredential())
    .GetChatClient(deploymentName)
    .AsIChatClient();
builder.Services.AddSingleton(chatClient);

// Register agents
var mathAgent = builder.AddAIAgent("math", instructions: "You are a math expert.");
var historyAgent = builder.AddAIAgent("history", instructions: "You are a history expert.");

var app = builder.Build();

// Expose via A2A protocol
app.MapA2A(mathAgent, path: "/a2a/math", agentCard: new()
{
    Name = "Math Agent",
    Description = "Expert in mathematics and calculations.",
    Version = "1.0"
});

app.MapA2A(historyAgent, path: "/a2a/history", agentCard: new()
{
    Name = "History Agent",
    Description = "Expert in world history.",
    Version = "1.0"
});

app.Run();
```

### Agent Card Discovery

```
GET /a2a/math/v1/card    → Returns agent metadata (name, description, capabilities)
POST /a2a/math/v1/message:stream  → Send message, get streaming response
```

### A2A Key Features

| Feature | Description |
|---------|-------------|
| Agent Discovery | Standardized Agent Cards with metadata |
| Cross-Framework | .NET agent can talk to Python agent and vice versa |
| Context Persistence | `contextId` maintains conversation across messages |
| Streaming | Server-Sent Events for real-time responses |
| Long-Running Tasks | Async task support for extended operations |

---

## MCP Integration (Model Context Protocol)

Connect agents to external tools and data sources via MCP servers.

### Package

```bash
dotnet add package ModelContextProtocol --prerelease
```

### Use MCP Tools in an Agent

```csharp
using ModelContextProtocol;
using ModelContextProtocol.Client;

// Connect to an MCP server (e.g., GitHub)
await using var mcpClient = await McpClientFactory.CreateAsync(
    new StdioClientTransport(new()
    {
        Name = "GitHubServer",
        Command = "npx",
        Arguments = ["-y", "--verbose", "@modelcontextprotocol/server-github"],
    }));

// Get available tools
var mcpTools = await mcpClient.ListToolsAsync();

// Create agent with MCP tools
AIAgent agent = chatClient.AsAIAgent(
    instructions: "You answer questions about GitHub repositories.",
    tools: [.. mcpTools.Cast<AITool>()]);

Console.WriteLine(await agent.RunAsync(
    "Summarize the last 4 commits to microsoft/agent-framework"));
```

### Popular MCP Servers

| Server | Purpose |
|--------|---------|
| `@modelcontextprotocol/server-github` | GitHub repos, issues, PRs |
| `@modelcontextprotocol/server-filesystem` | File system operations |
| `@modelcontextprotocol/server-sqlite` | SQLite database access |
| Azure MCP Server | Azure resource management |

---

## Caching Patterns for AI

### Semantic Caching (Redis)

Cache LLM responses by semantic similarity — avoid repeated inference costs.

```csharp
public async Task<string?> GetCachedResponseAsync(string question)
{
    var questionEmbedding = await _embedding.GenerateAsync(question);

    var results = await _redis.ExecuteAsync("FT.SEARCH",
        "idx:semantic_cache",
        $"*=>[KNN 1 @embedding $vec AS score]",
        "PARAMS", "2", "vec", questionEmbedding.ToByteArray(),
        "SORTBY", "score", "DIALECT", "2");

    if (results.Score < 0.95)  // 95% similar
        return results.Response;

    return null;  // Cache miss
}
```

**Cost savings:** Embedding call ~$0.0001/1K tokens vs inference ~$0.01-0.03/1K tokens = **100-300x savings**.

### Hybrid Cache (L1 + L2)

```csharp
// Program.cs
builder.Services.AddHybridCache(options =>
{
    options.DefaultEntryOptions = new HybridCacheEntryOptions
    {
        Expiration = TimeSpan.FromMinutes(10),
        LocalCacheExpiration = TimeSpan.FromMinutes(1)
    };
});

// Usage — tool with caching
[Description("Gets weather for a location")]
public async Task<WeatherResponse> GetWeatherAsync(
    string location, string date, CancellationToken ct)
{
    return await _cache.GetOrCreateAsync(
        $"weather:{location}:{date}",
        async token => await FetchFromApiAsync(location, date, token),
        cancellationToken: ct);
}
```

| Level | Location | Speed | Scope |
|-------|----------|-------|-------|
| L1 | Process memory | Fastest | Per instance |
| L2 | Redis | Very fast | Distributed |

### MCP Tool Routing Cache

Store tool description embeddings in Redis. On user query, find relevant tools by vector similarity — reduces tokens sent to LLM.

---

## Observability

### OpenTelemetry Integration

```csharp
// Enable on individual agents
agent.WithOpenTelemetry();

// Program.cs — full setup
builder.Services.AddOpenTelemetry()
    .WithTracing(tracing =>
    {
        tracing.AddSource("Experimental.Microsoft.Extensions.AI.*");
        tracing.AddSource(builder.Environment.ApplicationName);
        tracing.AddAspNetCoreInstrumentation();
        tracing.AddHttpClientInstrumentation();
    })
    .WithMetrics(metrics =>
    {
        metrics.AddMeter("Experimental.Microsoft.Extensions.AI.*");
        metrics.AddAspNetCoreInstrumentation();
        metrics.AddHttpClientInstrumentation();
        metrics.AddRuntimeInstrumentation();
    });

// Enable sensitive data in development
builder.AddAzureChatCompletionsClient("chat", settings =>
{
    settings.EnableSensitiveTelemetryData = builder.Environment.IsDevelopment();
});
```

### Aspire Dashboard

Visualizes agent traces including:
- Full conversation flow between agents
- Tool call cycles (Model → Tool → Model)
- Token usage and latency per call
- Error traces and retry patterns

```csharp
// AppHost/Program.cs
var redis = builder.AddAzureRedisEnterprise("cache");
var api = builder.AddProject<Projects.Api>().WithReference(redis);
```

### What to Monitor

| Metric | Purpose | Alert Threshold |
|--------|---------|----------------|
| Token usage per request | Cost control | > 10K tokens/request |
| Latency per agent call | Performance | > 5s for simple tasks |
| Tool call failure rate | Reliability | > 5% failures |
| Cache hit rate | Cost optimization | < 50% (tune strategy) |
| Model drift | Quality | Evaluation score drop > 10% |

---

## Error Handling Strategy

### Centralized via Middleware

```csharp
// Function calling middleware catches tool errors
async ValueTask<object?> ResilientToolMiddleware(
    AIAgent agent,
    FunctionInvocationContext context,
    Func<FunctionInvocationContext, CancellationToken, ValueTask<object?>> next,
    CancellationToken cancellationToken)
{
    try
    {
        return await next(context, cancellationToken);
    }
    catch (HttpRequestException ex) when (ex.StatusCode == System.Net.HttpStatusCode.TooManyRequests)
    {
        // Rate limit — return friendly message so agent can retry
        return "Service is temporarily busy. Please try again in a moment.";
    }
    catch (TimeoutException)
    {
        return $"Tool '{context.Function.Name}' timed out. Try a simpler query.";
    }
    catch (Exception ex)
    {
        // Log and return error context to agent
        return $"Error in {context.Function.Name}: {ex.Message}";
    }
}
```

### HAX Framework Principles for Production

| Principle | Implementation |
|-----------|---------------|
| Progressive disclosure | Start simple, add detail on request |
| Transparent agency | Log and expose agent decisions |
| Failure visibility | Never silently swallow errors |
| Intervention opportunities | HITL checkpoints when confidence is low |

---

## Security Checklist

- [ ] API keys in Key Vault (never in appsettings)
- [ ] Managed Identity for Azure services
- [ ] Input validation middleware (PII detection, injection prevention)
- [ ] Output filtering (guardrails for harmful content)
- [ ] Typed message edges for data isolation between agents
- [ ] Rate limiting middleware on public-facing agents
- [ ] A2A endpoints behind authentication

---

## References

- [Agent Middleware](https://learn.microsoft.com/agent-framework/user-guide/agents/agent-middleware)
- [A2A Integration](https://learn.microsoft.com/agent-framework/user-guide/hosting/agent-to-agent-integration)
- [Using MCP Tools](https://learn.microsoft.com/agent-framework/user-guide/model-context-protocol/using-mcp-tools)
- [MCP C# SDK](https://github.com/modelcontextprotocol/csharp-sdk)
- [Build MCP Server in C#](https://devblogs.microsoft.com/dotnet/build-a-model-context-protocol-mcp-server-in-csharp/)
- `setup.md` — Core setup
- `workflows.md` — Orchestration patterns

---

*MORPH-SPEC by Polymorphism Tech*
