Thank you to Sergey Tihon for another year of F# Advent and for adding me to the calendar. Be sure to catch the other articles.
May you know the Truth and the Reason for the Christmas Season !
This is my second year participating in the F# Advent . Last year I wrote about My Journey towards Functional Programming and F# .
My friend and colleague Matt at TwoPointDev during his .Net Conf 2025 talk Smatterings of F# said the F# and Functional Programming has been the “itch” he needed to scratch to keep programming fun for him. I can relate to that.
With all of the buzz around AI agents, I wanted to explore how F# could be used to build AI agents using the new Microsoft Agent Framework and the Model Context Protocol (MCP). I’m also interested in Bitcoin (see my articles on Bitcoin ).
This is a work in progress, but I’ll share how I’m learning about all 3 to create an AI agent workflow to handle my Capital Gains tax reporting. Capital Gains taxes are hard to track with Bitcoin transactions and cost basis.
You’re functionally mind might be cringing at the indeterminic usage of AI agents. I agree with you and many of the agents I outline in my Readme will probably move to functions. Remember this was a learning experiment and I have a lot more work to see if this would actually be useful.
All the examples I found are in C#, Python and JavaScript. Using GitHub Copilot makes it easy to convert the C# samples into F#. With AI, it makes it even easier to move to F# or you could leave the boilerplate code in from the samples in C# and write your code in F#. It’s great to have a choice with F# being in .Net!
My repo has the code and I will continue to update it as I have time and the itch is scratched.
MCP is a protocol for defining the context in which an AI model operates. It allows developers to define the inputs, outputs, and behavior of an AI model in a standardized way. This makes it easier to integrate AI models into applications and workflows. Read the summary I wrote for our Omnitech Blog Post and my personal post .
I’m thinking as MCP as the functions (pure or impure) that will bring determinism to the AI agents.
Check out my previous blog post for F# code sample of an MCP client.
The Microsoft Agent Framework is a new framework for building AI agents that can interact with users and other systems. It is built on top of the Model Context Protocol (MCP), which is a standard for defining the context in which an AI model operates.
The README in my repo defines the different agents that I think will be useful for the workflow.
I am still having problems getting the orchestration agent to pass along the information correctly. Maybe I’ve overstepped what the current version of the Microsoft Agent Framework can do.
See the https://github.com/aligneddev/BitcoinCostBasisAi/blob/main/BitcoinCostBasis.Orchestration/AgentWorkflow.fs was modified from my starting point with additions.

The Agent Factory is a nice use of F# functions. Here are some exercerpts.
namespace BitcoinCostBasis
open Azure.AI.OpenAI
open OllamaSharp
open OpenAI
open System
open OpenAI.Chat
open Microsoft.Agents.AI
open Azure.Identity
open Microsoft.Extensions.AI
open ModelContextProtocol.Client
open ModelContextProtocol.Protocol
module Entry =
type AzureConfiguration =
{ AzureOpenAiEndpoint: string
AzureOpenAiKey: string
ModelDeploymentName: string}
type AgentFactory(configuration) =
// System-level guardrails prepended to every agent's instructions.
let systemGuardrail =
"SYSTEM GUARDRAILS:\n- Do not invent facts. If you are unsure, ask for sources or clarifying information.\n- State uncertainty explicitly and avoid fabricating legal or tax advice.\n- Prefer asking clarifying questions over guessing an answer.\n- When returning factual claims, if possible request or cite the source."
member private this.MergeInstructions (instructions: string) =
sprintf "%s\n\n%s" systemGuardrail instructions
member this.CreateAzureOpenAiAgentWithTools (agentName: string) (description: string) (instructions: string) (tools: AITool[]) =
let client: AzureOpenAIClient = this.CreateAzureOpenAiClient()
let chatClient: ChatClient = client.GetChatClient(configuration.ModelDeploymentName)
let agentInstr = this.MergeInstructions(instructions)
let agent: AIAgent = chatClient.CreateAIAgent(agentInstr, agentName, description, tools)
agent
member this.CreateAzureOpenAiAgent (agentName: string) (description: string) (instructions: string) =
let client: AzureOpenAIClient = this.CreateAzureOpenAiClient()
let chatClient: ChatClient = client.GetChatClient(configuration.ModelDeploymentName)
let agentInstr = this.MergeInstructions instructions
let agent: AIAgent = chatClient.CreateAIAgent(agentInstr, agentName, description)
agent
member this.CreateAzureOpenAiOrchestrationAgent (orchestrationAgentName:string) (instructions: string) =
let client: AzureOpenAIClient = this.CreateAzureOpenAiClient()
let chatClient: ChatClient = client.GetChatClient(configuration.ModelDeploymentName)
let agentInstr = this.MergeInstructions(instructions)
let agent: AIAgent = chatClient.CreateAIAgent(agentInstr, orchestrationAgentName)
agent
member this.CreateLocalOrchestrationAgent ollamaBaseUri ollamaModelName orchestrationAgentName instructions =
let agentInstr = this.MergeInstructions(instructions)
this.CreateLocalOrchestrationAgentollamaBaseUri ollamaBaseUri ollamaModelName orchestrationAgentName agentInstr
member this.createPodmanMcpClient name imageName =
//await using var mcpClient = await McpClientFactory.CreateAsync(new StdioClientTransport(new()
//{
// Name = "BitcoinHistoricalPriceMcp",
// Command = "podman",
// Arguments = [ "run", "--rm", "-i", "bitcoin-historical-mcp" ]
//}));
let mutable mcpClientObj : obj = null
try
// Replace the inner options type name if different in your project (e.g. StdioClientTransportOptions).
// This attempts to mirror the original intent in F#:
let transport =
StdioClientTransport(
StdioClientTransportOptions(Name = name, Command = "podman", Arguments = [| "run"; "--rm"; "-i"; imageName |])
)
// Create and return the McpClient synchronously.
McpClient.CreateAsync(transport).GetAwaiter().GetResult()
finally
// If McpClient implements IAsyncDisposable, call DisposeAsync synchronously.
match mcpClientObj with
| null -> ()
| client ->
match client with
| :? System.IAsyncDisposable as iad -> iad.DisposeAsync().AsTask().GetAwaiter().GetResult()
| :? IDisposable as d -> d.Dispose()
| _ -> ()
I like using F# for these small factory functions to create the agents and hide away some details.
This is what it looks like when running the application in the CLI and the handoff of the question happens between the agents.

In Program.fs the F# flows well into setting up the orchestration agent and starting the workflow. Take a look there for the full code, but here’s a snippet of setting up the orchestration.
let x: (AIAgent seq) = [ bitcoinTaxSpecialistAgent ]
AgentWorkflowBuilder.CreateHandoffBuilderWith(orchestratorAgent)
.WithHandoffs(orchestratorAgent, [ bitcoinStatisticalAgent
bitcoinTaxSpecialistAgent
bitcoinTransactionAccountantAgent
fillTaxFormAgent
verifyFilledTaxFormAgent])
.WithHandoffs([ bitcoinStatisticalAgent
bitcoinTaxSpecialistAgent
bitcoinTransactionAccountantAgent
fillTaxFormAgent
verifyFilledTaxFormAgent ],
orchestratorAgent)
.Build()
It’s definined the HandoffBuilder (from Microsoft Agent Framework) then setup the handoff pairs. It looks just nice in F# and a C# developer could read it easily.
My TODO list has a lot of ideas to explore and I don’t think I’m going to have it working before it’s time to enter taxes!
Don’t build your business on this sample, there is so much more to do in this project before it could do that. There’s also several things I should move to pure functions instead of agents. Maybe someday we won’t have to pay taxes on Bitcoin purchases?
Yes, since F# is DotNet, you can use F# for AI systems. I’ve explored using F# to work with tools that only have samples in C#, Python and Javascript. I shared a sample F# MCP and how it brings some some determinism into AI agent workflow.
With AI assistance, it’s easy to convert the code to F#. In this AI world, there isn’t a lot of reason you can’t use F#, but make sure your work within your team and don’t cause problems doing this (start with your side projects first). After all, this interesting presentation made me see that most of the world is actually functional programming, it just happens to be in spreadsheets and they don’t recognize it 😉.
May you have a blessed Christmas Season and a Happy New Year!
Check out my Resources Page for referrals that would help me.