Blog
Mert Özen's blog posts on software development, backend, frontend, and DevOps.
Writing an MCP Client: McpClient, Connecting Tools to IChatClient and Agents, Elicitation
In C# you create an MCP client with McpClient.CreateAsync over a stdio or HTTP transport; since the McpClientTool returned by ListToolsAsync is an AIFunction, it can go straight into IChatClient and a MAF agent. We separate same-named tools from two servers with prefixes, pin tool definitions with fingerprints, and see real tool calls with a local qwen3:4b and how the SDK handles the server's confirmation request through MRTR rounds.
Controlling LLM Cost: DistributedCachingChatClient, Rate Limiting and Token Usage
We control LLM cost in three layers: DistributedCachingChatClient with Redis answers repeated questions with zero tokens, per-user rate limiting limits abuse, and a daily token budget kept with UsageDetails limits real spending. With real output we show what goes into the cache key, that entries are written without expiry by default, and why token accounting must sit inside the cache.
Taking Tool Calling to Production: FunctionInvokingChatClient Settings, DI Services and Approvals
In production, on whose behalf a tool runs comes from the session, not from the model's arguments, and irreversible actions wait for user approval through ApprovalRequiredAIFunction. With real runs we show an OrderTools class using scoped services, FunctionInvokingChatClient settings, separating tool errors from user errors, and an approval flow over two HTTP requests.
Persisting Chat History: Stateless APIs, ConversationId, EF Core and Chat Reducers
With a stateless provider your code keeps the chat history: we turn ChatMessage instances, including tool call and tool result contents, into JSON with AIJsonUtilities and store them in EF Core per user and conversation. We put the ownership check into the query, also support stateful providers through ConversationId, and compare the real output of MessageCountingChatReducer and SummarizingChatReducer.
Wiring IChatClient into ASP.NET Core: AddChatClient, Keyed Clients, Options and Secrets
We register IChatClient in ASP.NET Core as a singleton with AddKeyedChatClient, use keyed clients for a "fast" and a "smart" model, and choose provider and model through options validated at startup. With real logs we show the standard resilience handler's 10 s/30 s defaults regenerating an LLM call three times and ending in a 500, then tune timeouts and retries for LLMs.
The IChatClient Pipeline: ChatClientBuilder, Middleware Order and Your Own DelegatingChatClient
In ChatClientBuilder the first middleware added is outermost, and order changes behavior: with the cache outside function invocation the tool never runs, inside it the tool reads fresh data on every request. We write two DelegatingChatClient classes for Bookstore, a token budget and personal data masking, and show with a real run how forgetting the streaming override silently leaks data.
The .NET AI Stack: Where Microsoft.Extensions.AI, VectorData, Evaluation and Agent Framework Fit
An application takes Microsoft.Extensions.AI plus a provider package, while a library depends only on Abstractions; VectorData, DataIngestion, Evaluation and Agent Framework sit on top of or next to that IChatClient layer. We map the packages, run the aichatweb template against a local Ollama and fix three real errors that appear at the layer boundaries.