F#, Microsoft Agent Framework, MCP and Bitcoin for the F# Advent Calendar 2025

December 18, 2025    Development FSharp

F#, Microsoft Agent Framework, MCP and Bitcoin for the F# Advent Calendar 2025

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# .

What’s my “Itch?”

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.

Using F#

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.

Model Context Protocol (MCP) in F#

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.

Microsoft Agent Framework in F#

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.

Building the Agent Workflow

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.

Agent Workflow in F#

See the https://github.com/aligneddev/BitcoinCostBasisAi/blob/main/BitcoinCostBasis.Orchestration/AgentWorkflow.fs was modified from my starting point with additions.

Agent Workflow

Agent Factory

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.

Agent Handoff

This is what it looks like when running the application in the CLI and the handoff of the question happens between the agents.

Agent Handoff

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.

More to Explore

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?

Conclusion

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!