← All posts

Humla Ships an MCP Server: Your Meeting Notes, Inside Claude Code and Codex

The alt-tab that built this

I was halfway through a migration, in Claude Code, and the thing blocking me was not a code question. It was: what did we actually decide about the rollback plan?

I knew the answer existed. I could picture the meeting. And so began the ritual, which I had performed roughly nine hundred times without ever once thinking of it as a problem: stop, alt-tab, search my notes, find the passage, copy it, tab back, paste, resume, lose the thread I'd been holding, spend forty seconds getting it back.

Nine hundred times. Then one time I noticed.

That's the whole origin story. It's not visionary, it's just that the answer lived in a different app than the question, and MCP exists precisely to make that stop being true. So Humla speaks it now.

(Disclosure, since I make the thing: I'd love it if you installed Humla. It's free and MIT-licensed and I want the download. Everything below about what it doesn't do is more useful to you than the parts about what it does, so I've put quite a lot of effort into that half.)

The short answer

  • What it is: a first-party MCP server that ships inside Humla.app, exposing six read-only tools over your own notes.
  • How to get it: Settings → General → Integrations, flip the switch, paste the one-line command it gives you.
  • What it costs: nothing. No API key, no per-query cost, because search is keyword-based and runs against a local SQLite file.
  • What it can't do: it can't change a note, can't delete one, can't reach a recording. It also can't do semantic search, which is a genuine tradeoff and gets its own section below because I'd rather you heard it from me.

What shipped

Six tools. The names are deliberately boring, because they're the same ones Humla's own chat feature uses internally and roughly what the wider ecosystem has settled on:

ToolWhat it does
search_notesKeyword search across your notes, with excerpts
get_noteRead a note in full - your typed notes plus the AI summary. Up to 10 at once
get_transcriptRead one meeting's speaker-labelled transcript
list_notesSkim what exists, most recent first, with ids and speaker names
list_foldersThe folders you file notes in
list_clientsThe client each note is tagged with

The bit that turned out to matter most, and I did not predict this, is what search_notes actually reaches. Humla indexes three kinds of text per meeting: what you typed during the call, the AI summary, and the full spoken transcript. It searches all three together, and every excerpt comes back tagged [body], [summary] or [transcript].

Which means the agent can tell what someone SAID from what got written down about it afterwards. "Did anyone actually say that, or did I just summarise it that way?" is now a question with an answer, and let me tell you, having built the summariser, that is a question I ask more often than is comfortable.

Results narrow by folder, client, who spoke (not merely who got mentioned), language, and a date window, either relative like "the last 30 days" or absolute like "2026-06-01 to 2026-06-30".

Turning it on

It's off by default, and that's not box-ticking. This hands an outside agent the contents of real meetings with real people in them. An app update must never quietly open that door, and if I'd shipped it on by default I'd deserve every word of the post someone would have written about me.

In Humla: Settings → General → Integrations. Flip the switch and the panel hands you a ready-to-paste line for each client, with the real binary path on your install, already quoted.

For Claude Code:

claude mcp add --scope user humla -- "/Applications/Humla.app/Contents/MacOS/humla-mcp"

For Codex, in ~/.codex/config.toml:

[mcp_servers.humla]
command = "/Applications/Humla.app/Contents/MacOS/humla-mcp"

(The quotes around that path are not decoration, which is exactly why the app puts them there for you rather than trusting either of us to remember. An .app path contains a space. An unquoted one silently becomes two arguments. And the error you get back talks about a missing binary rather than about the space, so the one clue you need is the one clue nobody prints.)

Anything that speaks MCP over stdio can run that binary directly. Flipping the switch back off takes effect on the next tool call, not at the client's next restart, because the server re-reads the setting every single time instead of caching it at startup. Off should mean off within seconds, not whenever you next remember to quit Claude Code.

No token, no port, no network

There are three ways to build one of these, and the choice barely shows up in the feature list. It shows up entirely in your privacy posture.

ShapeHow it authenticatesWorks offline?Where your notes travel
Hosted/remote MCP (an endpoint you OAuth into)Sign-in, or a bearer token in a config fileNoThrough the vendor's server, to the client
Local HTTP server in the appA token on a loopback portOnly while the app runsLocal, but reachable by anything on the machine
Humla: a local stdio binaryFilesystem permissions on your own home directoryYesNowhere - it reads a file on your disk

humla-mcp is a second binary built from the same crate as the app and shipped inside the bundle. Your MCP client launches it directly and talks to it over stdin and stdout. It opens ~/Library/Application Support/no.humla.app/notes.sqlite, which is the same database the app uses rather than a copy of it, and answers questions from that.

Consequences worth spelling out, because "it's local" gets said about a lot of things that aren't:

  • No port is listening. There is nothing for another local process, or a webpage doing DNS-rebinding tricks, to connect to. There's no door, so there's no lock to pick.
  • No token exists, so no token can leak out of a config file into a screenshot, a dotfiles repo, or a support ticket. The authorization is the file permissions on your own user directory, which is already the thing protecting those notes.
  • It works with Humla closed, which is the common case. Humla isn't a menu-bar daemon squatting in your tray, and your notes don't stop existing when you quit it.
  • It works on a plane. No key, no call, nothing to meter, same local-first posture as the rest of the app.

It's also honest about the workspace boundary. In a team workspace the server reads that workspace; in Personal it reads Personal. Which one is resolved on Humla's side and cannot be passed in as a tool argument, so a client can't ask its way from your personal notes into a shared workspace, or the other direction. I wanted that decision made by the app and not by whatever the model felt like typing.

Read-only, and no audio, on purpose

Nothing an agent does through this server can alter or destroy a meeting record. There is no create_note, no append_to_note, no delete. Not "not yet." Not there.

And no tool returns or even references an audio file. Humla's rule, spelled out on the privacy page, is that the Keep recorded audio setting is the single absolute gate on recordings, and an MCP tool does not get to be the one exception sitting above it. If you have audio retention off, which is the default, there's nothing on disk to reach anyway. If you have it on, this server still can't see it.

Now the subtler reason for read-only, which is the part I actually lost sleep over.

Think about what a transcript IS. It's other people's speech, verbatim, and the agent reading it is quite possibly holding shell access and file-editing tools in the same session. Someone in a meeting saying "just delete the whole staging database, honestly" is a sentence that is about to be read by something with a terminal.

So every result carrying note content is prefixed with a line framing it as reference material and telling the model to ignore any instructions that appear inside it. If a client says something that reads like a command, it stays a quote.

I want to be straight about the strength of that: it's a mitigation, not a guarantee. No prompt-level defence is, and anyone selling you one as a guarantee is selling. What makes me comfortable shipping it is that it's paired with a structural defence instead of standing alone. Even a completely hijacked agent can only read, and only inside the workspace you're already in. The prompt is the fence; read-only is the fact that there's nothing on the other side of it worth stealing a tractor for.

The honest tradeoffs

Four, and the first one will bite you before the others do.

1. Search is keyword-only. Humla's own chat pane uses hybrid retrieval, full-text search plus embeddings, so it finds things you phrased differently. The MCP server deliberately doesn't, because embedding a query would drag in an API key, a per-query cost, and a Keychain prompt fired by a process your MCP client spawned invisibly in the background. Picture that: you ask Claude Code a question and macOS pops a password dialog for a binary you don't remember launching. Absolutely not.

The trade is that an agentic client can just do what the embeddings were compensating for. It can search five times, spell the term three different ways, list_notes and skim, then search again. Repeated querying plus reading beats one good vector query far more often than I expected it to, and I say that as someone who built the embedding half and was mildly annoyed to find out it wasn't needed here.

Where it genuinely hurts is across languages. A lexical index doesn't return worse results for a query in the wrong language, it returns NOTHING, which is a much nastier failure because it looks exactly like "there's no such meeting." So every result carries a lang: field and every relevant tool takes a language filter, which lets the agent discover what languages your library actually contains by looking instead of guessing.

2. It's read-only. An agent can't file a note into a folder for you or append its findings to a meeting. Writes would have to go through the app's own command layer to fire sync and refresh an open window, because a process scribbling into the database behind the app's back would leave the UI showing a stale copy and the change would never reach the cloud. That's a real gap. It's also a deliberate one, and see the previous section for why I'm not in a hurry.

3. Mac only. Same wall as the rest of Humla: macOS app, Apple Silicon recommended. This is an MCP server for people whose notes are already on a Mac. No Windows, no Linux, no mobile, and no plan I can honestly promise you.

4. A teammate's brand-new note may not be searchable yet. In a shared workspace, a note that just synced is readable by id and shows up in list_notes immediately, but the keyword index search_notes needs is built by the app. So a note that landed while Humla was closed may not surface in a search until the app next runs. It's on the fix list, and I'd rather tell you than have you conclude the search is broken.

Why first-party matters here

For a long stretch Granola shipped no MCP server, and the community filled the hole itself. Bencockin/granola-mcp reads the access token the Granola desktop app stores locally and calls the same private endpoints the app uses, and it's one of several like it. Its own README says plainly that Granola publishes no public REST API. The integration exists because someone reverse-engineered it.

That's not a knock on the people who wrote those. It's a decent hack, it clearly met a real need, and I'd probably have written one too. It's a knock on the situation, which goes like this: a notetaker doesn't ship this, someone reverse-engineers the local cache or the stored token, and now the app's internal storage schema is a de facto public API maintained by strangers who find out it changed when their users' installs break.

Shipping it first-party inverts the whole thing. search_notes and get_note become the contract I'm on the hook for, my database schema stays free to move, and nobody has to go digging through Application Support for a token.

A dated caveat, stated plainly, because this blog runs on a rule about competitor facts. Granola has since shipped an official hosted MCP server, a remote endpoint at mcp.granola.ai/mcp using browser OAuth, so "nobody's done this" is not the claim and it isn't the one I'm making. My claim is architectural: theirs is a hosted endpoint your agent signs into over the network, which is the right shape for a cloud product, and ours is a local binary reading a file on your disk, which is the right shape for a local-first one. Details as of August 2026 - check their docs before treating my specifics as settled. The Bencockin repository I did read directly.

What to actually ask it

The server tells the client at connection time when to reach for it: whenever the answer depends on something that happened in a meeting rather than something in the code or on the web. In practice:

  • "What did we agree with Acme about the renewal?"
  • "Did I promise anyone that export feature? Which meeting, and what exactly did I say?"
  • "Summarise every meeting tagged to this client in June."
  • "Before I write this migration, what did the team decide about the rollback plan?"

That last one is where we came in, and it's the one that changed my week. The rollback decision was made out loud, in a meeting, and never written into an issue by anybody. It had been sitting in the transcript the entire time. Which is when it landed properly: the alt-tab was never the problem. The problem was that my notes and my work had never once been in the same room, and I'd somehow filed that under the shape of the world rather than under things I could fix.

Try it

MCP support is in Humla now, free and open source, on your Mac. If you want the full picture of how retrieval works, both the half the MCP server uses and the embedding half it pointedly doesn't, there's a technical write-up, and the chat feature is the in-app version of the same idea.