Diving Deep: From Zero to a MCP *Client* in Flutter

25 Jul 2025

image of dash as a genie by Gemini 2.5 Pro

Introduction

As usual I'm late to the party. Just as I only got to Android at 2.1 or Flutter just before 1.0, I have only over the last couple of months gone from being a LLM doubter to, if not a fervent believer, then at the very least a user of LLMs as a key part of my daily software development toolset.

But for me, its not just about using a tool, to really understand it I need to havea reason to work with it for first principles and get "under the hood". Previously that meant working on a git client to really get tot grips with the true beauty of how the core principles of how git works.

Yet over the last couple of months, while I have found using LLMs not only productive but in some cases indespensible in working on a very old, embedded C++ code base, I have not have a project, a need, to dig and learn about LLMs.

Luckily this has changed with my very recent interest in trying to run LLMs locally for to see if its possible to use local LLMs for serious, professional software development needs and discovering the while the VSCode forks like Windsurf that talk to cloud based LLMs were not great, the alternatives for using local LLMs either in VSCode or in separate cli tools was down right dreadful!

So it was the recent post by Filip in the Flutter forum on the availability of "official" MCP server support in Dart (albeit in master channel only) that tipped the scales and had me finally rolling up my sleeves and diving in to learn the details of running local LLMs, MCP and what it takes to do agentic work with local LLMs with Dart.

But as the saying goes, we need to learn to walk before we can run. Since I had decided to learn how it all worked vs just following the great, as usual, docs on how to setup an existing MCP client with the Dart MCP server, I needed to build my own MCP client before I could get around to actually trying out the Dart MCP server.

So buckle up and follow along on my TIL deep dive into using MCP with Dart!

So what is MCP and Why Does it Matter?

Before we get to the details of MCP in Dart, lets first cover over of MCP, just in case you are not yet yourself immersed in the world of LLMs as of mid-2025.

For those unfamiliar, MCP is a standardized protocol for communication between LLM clients and servers. It provides a structured way to exchange messages, including prompts, completions, and metadata, offering consistency and interoperability across different LLM implementations. The really important bit, at least as I see it for MCP vs say just APIs is that its self documenting and in this case its important for the machines and not us humans! Thats because the core value prop from MCP is that models can make use of MCP servers and the services they provide "on the fly" discovering for themselves what tools or services are available and how to use them, neat!

Well, thats a sufficient of an intro to MCP for now and I'll dive into the technical details of using MCP soon enough.

So now Dart!

With the intro to MCP out of the way, lets get on to how Dart fits in. As I mentioned at the start of this article, my goal as to make use of the brand new Dart MCP server but do so as a learning experience of what makes MCP tick. Though as also mentioned I have been rather underwhelmed by the available tools I found for using local LLMs so part of the reasoning here was to potentially make some bespoke tools for myself as well. Its not that the existing tools are completely unusable or bad, but the communities fixation with building most of them with Python is really offputting for someone used to the creature comforts of Dart ecosystem (or even Java/Kotlin) and the whole disaster that is different python versions, multiple inconsistent package management tools, managing "virtual environments" and Linux distros then trying to do it all themselves with their own package management leaves a truely bad taste in my mouth, somewhat reminesent of the excesses of "modern" JS & TS development and its horrors of incompatible package management and competing tooling ecosystems. But I digress! Suffice to say being able to build standalone, cross platform, AOT compiled tools (hello also Go and Rust!) was a temptation to hard to resist along with the aforementioned educational incentives of this adventure.

So now that you know my goal was to build a MCP client in Dart (eventually Flutter), where to start? Well luckily not from scatch! as the kind Dart team had already released the dart_mcp package that looked like it had all the bits and pieces I would need. But instead of diving into the deep end of trying to use the Dart SDKs MCP server, I decided a better place to start would be a super simple MCP server of my own, yes abit more reinventing the wheel in the service of education! Again here the Dart teams excellent work shines through and could just make use of the extensive examples, in my case tools_server.dart gave me a great starting point to use and then easily add to the concat tool a slightly more realistic and useful listfilestool.

Finally a Dart MCP Client

With that out the way, I could finally get to the star of todays, show: what may well be the worlds first Dart MCP client. Well of course thats not quite true as I began again by using the example tools_client.dart as my starting point, but to my mind, even the quick hack I have ended up with is if not the first Dart MCP client, then certainly I think the first that has support for both local LLMs and tools!

Did someone say local LLMs?

While I've mentioned it already a few times, using local LLMs was not only a driver behind this work but also something specific to a particular open source tool that in contrast to other, I have been very happy with: ollama!

I don't want to digress again and go into the details of ollama, suffice it to say its a fantastic client/server architecture tool over the top of llama.cpp (I hope I got that right!) that not only gives a super easy way for people to run all sorts of open LLMs locally, but does so via a very nice REST API.

Coming back to Dart, I again here was very fortunate not to have to start from scratch because even while implementing using a REST API in Dart is not exactly rocket science, it was very nice to find not only an existing (or 3!) Dart package for the API but also a decent if basic Flutter app built on top of that API to serve as an example of how to use the package 🎉 .

With those foundations, I was well on my way to building my proof of concept MVP app.

What about the tools?

The last missing piece to the puzzle now was wiring up the code to talk to our local models via ollamas API to my little test MCP server. This bit is at the heart of what it means for an application to be an MCP client really, giving the user access to the LLM and giving the LLM access to the suite of MCP servers and the tools that they make available for the models to "use".

Having limited time, here I did what is at the heart of all this, I called on a LLM (or 2) to write some glue code Dart and a bit more Dart for some Flutter widgets for a minimal UI to test it all out.

And so I give you "Blue Djinn"! my brand new local LLM focused MCP client:

screenshot of blue djinn application

And yes, not exactly the height of either UI design or usability, but it is a (very hacky) working demonstration of exposing a local tool via a MCP server to a locally running model via ollama.

As always you can find the Blue Djinn source code repo on Github.

But wait, there's more!

Yes this is only the beginning, while just like Dr Frankenstein, I'm very impressed with the new monster I have created and brought to life, much still remains to be done!

For one, the project is missing the eponymous logo of the Blue Djinn project. But for this I won't leave you in suspense and am happy to unveil the mascot that came about from surpisingly few prompts back and forths with Gemini 2.5 and that is featured at the start of this article.

So please join me in the next exciting installment, where I'll cover adding a crucial feature that I have grown used to in the "agentic" software development tools I have been using: indexing and embeddings.

Until then, I wish you all a happy and safe today and tomorrow in these very uncertain times.