# How to Build a Fastio MCP Client in Elixir

Building a Fastio MCP client in Elixir uses OTP to maintain highly resilient, concurrent agent workspaces that self-heal on connection drops. By combining the Model Context Protocol with the Fastio server, developers can connect Elixir applications to multiple intelligent file management tools. This guide covers setting up an SSE client, managing capability negotiation, using built-in workspace intelligence, and using OTP supervision trees for fault-tolerant agent execution.


Source: https://fast.io/resources/build-fastio-mcp-client-elixir/
Last reviewed: 2026-02-24

## What is the Model Context Protocol?

The Model Context Protocol (MCP) is an open-source standard designed to standardize the integration of AI applications with external systems, data sources, and tools. Before MCP, developers had to write custom API wrappers for every new service an agent needed to access. Now, developers can build a single MCP client that negotiates capabilities over JSON-RPC 2.0 and executes remote functions easily. 

For developer teams, choosing the right file sharing method directly affects project timelines and client satisfaction. Fastio operates a fully compliant MCP server that exposes multiple specific tools for workspace manipulation. Your AI agents can perform complex tasks like uploading files, creating data rooms, running semantic searches, and granting specific access permissions through a unified protocol without writing custom REST clients.

When you implement an MCP client, your application connects to the server, announces the features it supports, and receives a structured manifest of available tools and prompts. This capability negotiation makes the integration dynamic. If Fastio adds a new tool tomorrow, your client can discover and use it automatically without requiring any code changes on your end. The protocol supports bi-directional communication, allowing the server to push notifications and updates to your agents in real-time.

This approach changes how AI interacts with storage. Instead of pulling files locally to process them, the agent communicates with the storage environment natively. It treats the workspace as its operating system.

## Why Elixir is the Perfect Fit for MCP Clients

Elixir operates on the Erlang VM (BEAM), an environment designed for highly concurrent, fault-tolerant telecommunications systems. This pedigree makes it well-suited for building reliable MCP clients that maintain persistent connections to external servers over long periods.

According to Hexdocs, Elixir processes are lightweight enough to run tens or hundreds of thousands simultaneously. Unlike operating system threads, these processes start at just a few kilobytes of memory and communicate exclusively through isolated message passing. You can run thousands of independent agent sessions on a single machine without risking a shared state deadlock or exhausting system memory.

For AI agents that often run asynchronously and require persistent state, this model is highly effective. Each agent can maintain its own isolated connection to the Fastio server.

**Key advantages of Elixir for MCP:**
- **Supervision Trees**: Elixir applications use OTP supervision trees to guarantee high resilience and process uptime. If a network blip causes your Fastio SSE connection to drop, the supervisor automatically restarts the client process from a known good state.
- **Actor Model Concurrency**: Each agent can maintain its own isolated connection state, complete with capability lists and session tokens.
- **Garbage Collection**: Because memory is garbage collected on a per-process basis, large JSON payloads from the Fastio server will not cause application-wide pauses or stop-the-world events.
- **Binary Pattern Matching**: Parsing incoming JSON-RPC 2.0 payloads from the Fastio server is fast and expressive in Elixir. This allows you to route complex nested maps easily.

## The Fastio MCP Server Architecture

Fastio is an intelligent workspace, not just basic storage. Intelligence is native to the platform. Files are auto-indexed, searchable by meaning, and queryable through chat immediately upon upload.

The Fastio MCP server allows external applications to programmatically control this intelligence. It exposes exactly multiple MCP tools via Streamable HTTP and Server-Sent Events (SSE). Every UI capability you see in the Fastio web dashboard has a corresponding agent tool available over the protocol. Agents and humans share the same workspaces, tools, and intelligence.

For example, Fastio includes built-in RAG (Retrieval-Augmented Generation). You can toggle Intelligence Mode on a workspace, and your files are auto-indexed without the need to set up a separate vector database. Your agent can invoke the `search_workspace` tool to retrieve semantic matches with full citations. 

Other powerful capabilities include URL Import, which pulls files directly from Google Drive, OneDrive, Box, or Dropbox without routing through local I/O. This is useful for serverless agents that lack a local filesystem. The Fastio MCP server also gives you granular permissions at the organization, workspace, share, folder, and file level, plus file version history for reviewing or restoring prior content after an unwanted overwrite. Agents can monitor changes through activity polling or Fastio's WebSocket events feed. Fastio's [MCP server integration guide for developers](/resources/fastio-mcp-server-integration-developers/) covers the connection and authentication handshake behind these capabilities.

## Setting Up Your Elixir Project

To get started building your MCP client, we need to initialize a standard Mix project and pull in a few dependencies to handle HTTP connections, Server-Sent Events, and JSON parsing.

Run the following command in your terminal to create the scaffolding:
```bash
mix new fastio_mcp_client --sup
cd fastio_mcp_client
```

The `--sup` flag generates a supervised application, which is a necessary requirement for maintaining our resilient connection. Next, open your `mix.exs` file and add the required dependencies inside the `deps` function:

```elixir
defp deps do
  [
    {:req, "~> 0.5.0"},
    {:jason, "~> 1.4"},
    {:websockex, "~> 0.4.3"} # Optional, if using WebSockets instead of SSE
  ]
end
```

We will rely heavily on the `Req` library because it natively supports Server-Sent Events (SSE) out of the box. This makes it easy to subscribe to the Fastio MCP stream. `Jason` will handle our JSON encoding and decoding for the JSON-RPC 2.0 payloads. After updating your dependencies, run `mix deps.get` to download and compile them.

Before we write our connection module, ensure you have your Fastio API key ready. You can generate one from the Fastio dashboard under your workspace settings.

## Implementing the SSE Client with GenServer

Building an MCP client requires maintaining state and handling asynchronous network events. The perfect tool for this in Elixir is a `GenServer`. Here is how you can implement a basic GenServer to manage an SSE connection to the Fastio MCP server.

First, we define our module and initialize the connection process. We will use `Req` to attach an asynchronous stream to our server endpoint.

```elixir
defmodule FastioMcpClient.Connection do
  use GenServer
  require Logger

# Client API

def start_link(opts) do
    GenServer.start_link(__MODULE__, opts, name: __MODULE__)
  end

def send_request(method, params) do
    GenServer.call(__MODULE__, {:send_request, method, params})
  end

# Server Callbacks

@impl true
  def init(opts) do
    api_key = Keyword.fetch!(opts, :api_key)
    url = "/storage-for-agents/"
    
    # Start the async SSE stream
    req = Req.new(url: url, headers: [{"Authorization", "Bearer #{api_key}"}])
    {:ok, ref} = Req.get(req, into: fn request, response -> 
      send(self(), {:sse_chunk, response})
      {:cont, request, response}
    end, async: true)

# Our state holds the request reference, a message ID counter, and the POST endpoint
    {:ok, %{ref: ref, request_id: 1, session_id: nil, available_tools: []}}
  end
end
```

In this setup, the `init/multiple` callback immediately fires off an asynchronous GET request to the Fastio SSE endpoint. The `Req` library streams chunks of data back to our GenServer by sending `{:sse_chunk, response}` messages continuously. We initialize our state map to track the current message ID. This is required by the JSON-RPC 2.0 specification for matching responses to requests.

## Handling JSON-RPC 2.0 Messages and Capability Negotiation

Once the connection is established, the Fastio server will stream JSON-RPC 2.0 messages down the pipe. We need to handle these messages by implementing the `handle_info/multiple` callback in our GenServer.

The Model Context Protocol uses Server-Sent Events to push data. When the connection opens, the server will send an initialization message containing a unique POST endpoint. We must capture this endpoint. It is where we will send all our client's JSON-RPC requests.

```elixir
@impl true
def handle_info({:sse_chunk, chunk}, state) do
  # In a production system, you would properly parse the SSE format.
  # Here we assume a simplified extraction of the JSON payload string
  case parse_sse_event(chunk) do
    {:ok, "endpoint", endpoint_url} ->
      Logger.info("Received MCP POST endpoint: #{endpoint_url}")
      send_initialization(endpoint_url)
      {:noreply, %{state | session_id: endpoint_url}}
      
    {:ok, "message", payload} ->
      decoded = Jason.decode!(payload)
      new_state = handle_rpc_message(decoded, state)
      {:noreply, new_state}
      
    _ ->
      {:noreply, state}
  end
end
```

When our GenServer receives the endpoint, it triggers `send_initialization/multiple`, where we announce our capabilities. This includes telling the server whether we support resources, prompts, or tools. 

We also need a set of private functions to pattern match the incoming JSON-RPC methods:

```elixir
defp handle_rpc_message(%{"method" => "notifications/tools/list", "params" => params}, state) do
  tools = params["tools"] || []
  Logger.info("Discovered #{length(tools)} tools from Fastio.")
  %{state | available_tools: tools}
end

defp handle_rpc_message(%{"id" => id, "result" => result}, state) do
  # Handle successful response to a previous request
  Logger.info("Request #{id} succeeded.")
  state
end
```

The powerful pattern matching in Elixir makes routing these different message types easy and readable. You never need complex switch statements. You just define functions that match the shape of the map you expect.

## Executing Fastio's Consolidated MCP Toolset

With the connection established and initialization complete, your Elixir application can now execute any of the tools in Fastio's consolidated MCP toolset.

To execute a tool, your client sends a standard JSON-RPC request to the POST endpoint acquired during the SSE handshake. Here is an example of how you might invoke the `upload_file` tool to move an asset into a shared workspace, or the `search_workspace` tool to use Intelligence Mode.

```elixir
def invoke_tool(endpoint_url, tool_name, arguments) do
  payload = %{
    "jsonrpc" => "2.0",
    "id" => System.unique_integer([:positive]),
    "method" => "tools/call",
    "params" => %{
      "name" => tool_name.
      "arguments" => arguments
    }
  }
  
  # Use Req to send the POST request synchronously for the example
  # In a real app, you might want to handle this asynchronously
  Req.post!(endpoint_url, json: Jason.encode!(payload))
end
```

Because Fastio features an intelligent workspace, uploading a file through this tool immediately triggers the built-in RAG pipeline. The file is auto-indexed and its contents become available for semantic search via the `search_workspace` tool right away. 

If you wanted to search the workspace, you would structure your arguments like this:
```elixir
invoke_tool(
  state.session_id.
  "search_workspace", 
  %{"query" => "Q3 revenue projections", "workspace_id" => "wksp_123"}
)
```
The server will return a JSON-RPC response containing the semantic matches, which your Elixir client can parse and pass along to a local LLM or another part of your pipeline.

## Managing Agent State with Version History and Activity Feeds

When running multiple agents concurrently, data integrity becomes a real concern. If two agents attempt to modify the same file or data room simultaneously, you risk creating conflicts or overwriting data.

The Fastio MCP server addresses this with granular permissions, full file version history, and a realtime activity feed instead of locks. Your Elixir client can subscribe to a workspace's activity feed and react to changes as they happen, rather than blindly writing over another agent's edits. If a conflicting write does occur, every prior version of the file is still recoverable.

```elixir
def watch_activity(endpoint, workspace_id) do
  # Discover the appropriate activity action from the server's consolidated MCP tool list.
  response = invoke_activity_action(endpoint, workspace_id)

case response.body do
    %{"result" => %{"events" => events}} when events != [] ->
      Logger.info("New activity detected, re-checking file state before writing...")
      :changed
    _ ->
      :unchanged
  end
end
```

This pattern, paired with Elixir's non-blocking processes, lets your multi-agent systems react to real changes and recover from conflicts using version history instead of racing to hold a lock.

## Resiliency and Self-Healing Workspaces

One of the main reasons to build your MCP client in Elixir is to take advantage of its self-healing capabilities. When dealing with remote network connections, failures are not just possible; they are guaranteed. Web sockets close, SSE streams timeout, server restarts happen, and API rate limits get hit.

By placing our `FastioMcpClient.Connection` under a Supervisor, we ensure that our client never stays down. We configure a supervision tree that watches over our agent connections.

```elixir
defmodule FastioMcpClient.Application do
  use Application

@impl true
  def start(_type, _args) do
    children = [
      {FastioMcpClient.Connection, [api_key: System.get_env("FASTIO_API_KEY")]}
    ]

opts = [strategy: :one_for_one, name: FastioMcpClient.Supervisor]
    Supervisor.start_link(children, opts)
  end
end
```

If the connection process crashes due to a network anomaly or an unhandled JSON parsing error, the supervisor catches the exit signal and spawns a fresh process right away. This new process runs its `init/multiple` callback, automatically re-establishes the SSE connection, and performs the MCP handshake again. 

This architecture fits well with the concept of ownership transfer in Fastio. An agent can create an organization, build workspaces, invite humans, and transfer ownership, all running continuously in the background. Thanks to Elixir's supervision trees, your agents will continue working around the clock without human intervention.

## Best Practices for Elixir MCP Clients

When finalizing your Fastio MCP client, there are a few best practices native to the Elixir ecosystem that you should adopt.

First, implement complete logging. The `Logger` module in Elixir is fast. Log every tool invocation and state change, especially when capability lists are updated by the server. Fastio provides a full audit log in its web interface, but having client-side logs is helpful for debugging agent behavior.

Second, respect rate limits. Ensure your client implements intelligent retries for HTTP 429 status codes. You can use Elixir's built-in timers or a library like `Retry` to handle backoffs smoothly. The Business Trial runs with Business-plan capabilities for 14 days and requires a credit card; see the [pricing page](/pricing/) for current details.

Finally, encapsulate your JSON-RPC payloads in structs. While raw maps are easy to use initially, defining a `defstruct` for your Requests, Responses, and Notifications will provide better type safety and documentation for your codebase as your agent capabilities grow.

## Frequently asked questions

### Is there an MCP client for Elixir?

Yes, there are open-source community libraries like Hermes MCP and MCPEx available for Elixir. Developers can also build custom clients easily using Elixir's Req library and GenServers to manage Server-Sent Events.

### How do I connect Elixir to Fastio?

You connect Elixir to Fastio by establishing a Server-Sent Events (SSE) connection to the Fastio MCP endpoint. Once connected, your application communicates using JSON-RPC 2.0 payloads to discover and invoke tools.

### Why should I use Elixir instead of Python for an MCP client?

Elixir provides superior concurrency and fault tolerance compared to Python. Its actor model allows you to manage thousands of isolated agent connections on a single machine, and OTP supervisors automatically restart dropped connections.

### Does the Fastio MCP server support WebSockets?

The Fastio MCP server primarily supports HTTP and Server-Sent Events (SSE) for its transport layer. This makes it compatible with most standard HTTP client libraries across all programming languages, including Elixir.

### What AI tools can my Elixir client access through Fastio?

Your Elixir client gains access to multiple specific tools. These include capabilities for uploading and sharing files, managing data rooms, tracking full file version history, and using Fastio's built-in Retrieval-Augmented Generation (RAG) system.

## About Fast.io

Fast.io provides shared workspaces where people and AI agents work on the same files, with built-in semantic search and citation-backed chat over what they hold. Agents reach it through a remote MCP server at https://mcp.fast.io/mcp, a REST API at https://api.fast.io/current/, and a command line client published on npm as @vividengine/fastio-cli.
