Tricorder MCP server: feedback your agent won't forget

Using Tricorder and its MCP server with coding agents.

A little over two months ago, we released the first version of Tricorder, a Haskell development tool built for developers of the modern era, where responsive and frictionless developer experience is king, and AI usage in coding is on the rise. Since then, we’ve shipped a steady stream of improvements based on user feedback. One of our most recent releases is tricorder-mcp, a Model Context Protocol (MCP) server that lets AI agents use Tricorder effectively on larger code bases.

While Tricorder is primarily developed for human use, it doubles as a bridge to GHCi for AI agents. This article showcases tricorder-mcp and how it makes agentic development with Tricorder more reliable.

For the uninitiated: what is Tricorder?

Tricorder is a CLI program that runs a lightweight daemon per project, each keeping a GHCi session warm in the background. Instead of having your agent cold-call cabal build and wait through a slow compile after every edit, it can query that daemon for the most up-to-date build state. Tricorder uses GHCi to recompile incrementally as files change, keeping type errors, warnings, test results, and eval comments available on demand. The daemon does the slow part once and keeps it warm; the agent just asks what’s true right now.

Tricorder exposes the build state through both a programmatic CLI interface and a more human-friendly TUI interface that you might just leave open in another terminal tab while working on your project.1

Context matters

We’ve offered a ready-to-use agent skill for some time now, and if you’ve tried it, you might agree it works really well. However, longer sessions reveal its limitations, as the agent forgets how to use it properly. This is especially the case when working in large codebases, where a lot of code needs to be brought into the context window for the agent to do its job effectively.

The reason is the dreaded context window: once the agent decides it needs a skill, it reads the skill definition once and reuses that original read definition for the rest of the session. It is not automatically re-read unless you prompt the agent to do so. That original read is just more content sitting in the context window, and like everything else there, it’s subject to compaction and context bloat. The agent never forgets the skill exists, but it can lose track of how to use it.

MCP tools don’t have this problem. An agent’s harness resends the full list of available tools—alongside skills and built-in tools—on every single turn, as concise descriptions the agent invokes directly with the right arguments. There’s no one-time read to lose to compaction, so tools stay just as reliable in a two-hour session as in a two-minute one.

In effect, the agent doesn’t actually know how to use the tricorder CLI program directly; it’s the MCP server exposing it as a tool that handles that, turn after turn, without the instructions ever degrading.2

Words have meaning

The skill works by telling the agent how to invoke tricorder: a page of prose describing subcommands, flags, and output formats that the agent has to read, remember, and reproduce correctly on every call. Forget --wait, or get a flag name slightly wrong, and the agent doesn’t fail loudly. It just gets a stale or subtly wrong answer, or gives up on Tricorder entirely and pulls out cabal directly, and carries on.

tricorder-mcp replaces that with typed tools: Status, TestResults, Restart, and others, each coupled with a structured schema. tricorder-mcp better surfaces the difference between “Tricorder found build errors” and “Tricorder itself failed to run”. The status, test-results, and eval-comments commands exit non-zero when they’re reporting errors or failing tests, and the server still returns that as a normal result rather than an error, so the agent doesn’t have to guess what a non-zero exit means.

Show and tell

To better illustrate how easy it is for an agent to use Tricorder with this MCP server, let’s use it in some actual work!

Picking a simple enough task straight off of Tricorder’s own issue board: the Tricorder daemon should quit after a period of inactivity. Currently, the daemon will just hang around after you’re done with it until you happen to remember to kill it. This is a bit of a problem, seeing as GHCi can gobble up a sizeable chunk of memory after a few :adds and :reloads.

I will use Claude Code for this demonstration. The full conversation is available in a public Gist.

Our initial prompt looks like this:

❯ Currently, the Tricorder daemon will remain running in the background forever
once started, until the user themselves calls `tricorder stop` or otherwise
kills the process. It would instead be preferable if the daemon would shut
down by itself after a certain amount of idle time.
...

I prompt Claude to make the Tricorder daemon shut down after a configurable period of inactivity. I also ask it to use the Tricorder MCP server to check the build and test status along the way, and I make sure to mention some edge-cases that it needs to address in its implementation.

Claude provides us an overview of its plan, and begins exploring.

● I'll start by loading the Tricorder MCP tools and checking the current build
  status, then explore the daemon/session architecture.

As Claude explores the repo to figure out how to achieve its goal, it runs Tricorder through the MCP server, observing the build process for any issues. It can view the build status itself, and as soon as the build is finished it can view test results as well.

  Read 1 file, called tricorder 5 times, ran 2 shell commands

After making a series of changes, Claude can then check in with Tricorder again to observe the build process. Since Tricorder is running in the background, the build is probably ready for Claude to check by the time it gets around to doing so. Claude doesn’t have to wait around for cabal build to finish.

● Good. Now let's build and check for type errors via Tricorder MCP.

  Read 1 file, called tricorder 5 times, ran 2 shell commands

Claude can perform various actions using Tricorder as it goes along:

  • Check test results:

    ● tricorder - test_results (MCP)(wait: true)
      └  atelier-core:test:atelier-test  passed (214) (410ms)
           test/Driver.hs
             Config
               extractNestedConfig
                 should use default value for non-object Values:              OK
                 should fetch top-level property:                             OK
                 ...
    
  • Inspect daemon logs:

    ● tricorder - log_contents (MCP)
      └  [INFO] Starting tricorder v0.2.1.0 (ac7251c)
         [INFO] Starting session 1
         [INFO] GHCi started (session #1): 0 diagnostics
         [INFO] Running 4 test suite(s)
         ...
    
  • View module sources:

    ● tricorder - source (MCP)(modules: ["Data.Time.Units"])
      └  {-# LANGUAGE DeriveDataTypeable #-}
         {-# LANGUAGE GeneralizedNewtypeDeriving #-}
         -- |This module defines types for many useful time periods, as well as
         -- mechanisms for converting between them.
         module Data.Time.Units(
                 TimeUnit(..)
               , Attosecond
               , Femtosecond
               , Picosecond
    
  • Restart the daemon entirely, if the need arises:

    ● tricorder - restart (MCP)
      └
    

A lot of these operations also support narrowing down the returned result to reduce the size of the context window filled by Tricorder’s output.

After a little bit of back and forth, checking the build along the way, Claude considers its task complete.

● Implementation is complete and verified (0 diagnostics, all 4 test suites
  passing including the new tests). Summary of changes:

  - Tricorder.Session.IdleTimeout (new): IdleTimeout newtype, defaults to 300s.
  - Session.Config / Session: added idle_timeout_seconds config field
    (default 300), threaded into Session and re-evaluated on every loadSession call
    (i.e. on config reload), per your request.
  ...

Further refining Claude’s solution from here is as simple as ever, but thoroughly sped up by Tricorder’s snappy build loop and output.

This is a simple example of how you can use Tricorder through the Tricorder MCP server with Claude or another coding agent. Admittedly, this task could’ve been achieved without Tricorder just as easily, or by using the Tricorder skill, but I hope this walkthrough demonstrates the various ways an agent can use Tricorder to optimise its work.

Try it out!

If you don’t have Tricorder itself already, check out Tricorder’s README on how to get started.

Want to try it out with an AI agent like Claude, Copilot, Codex, Cursor or Grok? Check the guide on using Tricorder with coding agents for a full suite of plugins that manage the MCP server binary for you, and can pick between the MCP server and the skill intelligently.

To use Tricorder’s MCP server directly without a plugin, see the official installation instructions for tricorder-mcp.

Have any feedback or suggestions for Tricorder or tricorder-mcp? Let us know either on the issue board or on Tricorder’s Discord channel!

The road ahead

Next up for Tricorder is further improving and refining the tool’s outputs. Much care has been given to the output of tricorder ui so far, for human usage, but there is still a fair amount of work left to improve the regular CLI interface in tricorder status, tricorder test-results, et al.

Keep up with Tricorder by starring the repo on GitHub or through the Discord channel.

  1. For more detail, you can watch the in-depth Tricorder presentation given at the Haskell Foundation’s “Haskell and AI” workshop

  2. I am grossly simplifying the potential uses for MCP servers in this article. They can be used for many different things, especially when they define resources and prompts on top of the tools we describe here. At full scope, it’s helpful to think of MCP servers as AI-oriented APIs over resources that have a well-defined set of operations you can perform.