MCP server
Applies to monthlet 0.13.1 and later
monthlet can act as an MCP server, so an agent outside the app — Claude Code, for example — can read and write the notes in your vault through monthlet itself rather than touching the files behind its back. This page lists what is published and what each tool guarantees. For the steps inside the app, see Help in the app.
What it is
When you turn it on, monthlet listens for MCP connections and offers a fixed set of tools: searching and listing notes, reading a note as Markdown, reading spreadsheet cells and chart values, and — separately opt-in — creating and rewriting notes, and moving and renaming notes and folders.
The app has to be running. Agents connect only while monthlet is open, and the vault they see is the one you have open. Switching vaults switches what is published.
Everything goes through monthlet, which stays the single writer of your files. That is what keeps the note history, the search index, and the open editor in step with what an agent does.
Setting it up
- In monthlet, open Settings → Connectors → Publish to external agents and turn on Publish.
- Under Setup, press Copy command and run what you copied in a terminal. It registers monthlet with Claude Code.
- To let agents write as well, turn on Allow writing in the same place. It is off by default.
- To let agents move and rename notes and folders too, turn on Allow moving and renaming below it. It is off by default and works only while writing is allowed.
The command looks like this. Copy the real one from the settings screen — the path depends on where the app is installed.
claude mcp add --scope user monthlet -- \
/Applications/monthlet.app/Contents/Resources/bin/monthlet-mcpThis is the newer of two ways to connect, and it needs monthlet 0.13.3 or later, which is where the command started being bundled. You can also tell from the settings screen: if Setup offers both Copy command and Direct HTTP (with token), you have it. If there is only one button, use the HTTP method below instead.
- Running this command requires Node.js on your machine. monthlet bundles the script, not a runtime to execute it with.
- What gets registered is only the path to that command. Your token is not written into the agent’s config — the command reads the token and the port from monthlet each time it runs.
- Because of that, regenerating the token does not require registering again, and neither does the port changing when the default one is taken.
--scope userregisters monthlet once for your user, rather than inside a single project’s config file.
Connecting over HTTP instead
Clients that cannot launch a command, or that only speak streamable HTTP, can connect to the endpoint directly. Copy this one from Direct HTTP (with token) on the same screen — it carries your token, and the port differs when the default one is taken.
claude mcp add --scope user --transport http monthlet \
http://127.0.0.1:27180/mcp --header "Authorization: Bearer <token>"- Here
--scope usermatters for a second reason: the token is saved in your user settings, not in a project file that could be committed. - The token is written into the client’s config, so regenerating it from the settings screen means registering again. The previous token stops working immediately.
- Any MCP client that speaks streamable HTTP can connect with the same endpoint and header.
Tools
Reading
| Tool | What it does | Main arguments | Returns |
|---|---|---|---|
search_notes | Full-text search over notes and spreadsheets: titles, bodies, and tags. | query (required), tags, includeArchived, limit (20, up to 199 from monthlet 1.1.0) | Matches by relevance in `results`, each with path, title, kind, updated date, tags, and a short snippet. From monthlet 1.1.0, also `truncated` (true when more matches exist beyond `limit`) and the `limit` that was applied. If `truncated` is true, raise `limit` or narrow the query. |
grep_notes | Searches note bodies for an exact string or a regular expression, the way grep does. It searches the same Markdown that read_note returns. Spreadsheets are not covered. monthlet 1.1.0 and later. | pattern (required), regex, ignoreCase, path (a folder, or a glob such as dev/**/*.html), outputMode (lines or files), context (0–5), includeArchived, limit (100, up to 500) | Matching lines with path, line number, and text, or match counts per note with `files`. Also `totalMatches` and `totalFiles` counted regardless of `limit`, `truncated`, and `skipped` (notes that could not be read). Check that `skipped` is 0 before concluding there is no match. |
list_notes | Lists notes and spreadsheets, most recently updated first. | folder, includeExcerpt, includeArchived, limit (200) | Path, folder, title, kind (note or sheet), updated date, tags, pinned, archived, and the first 160 characters when asked for. |
read_note | Reads a note body as Markdown. Spreadsheets are not covered — use read_sheet_range. | path (required), offset (first line, from 1), limit (number of lines) | Path, title, tags, updated date, the body as Markdown, and its `version`. From monthlet 1.1.0, the body starts with a frontmatter block between `---` lines: `type` from OKF (Open Knowledge Format; `Note` unless the note sets its own), followed by the note’s own keys (anything besides title, tags, pinned, archived, and the dates monthlet keeps). Also `startLine`, `endLine`, `totalLines`, and `partial` (whether only part of the note was returned). When only part is returned, there is no `version`. `offset` and `limit` are in monthlet 1.1.1 and later. |
read_sheet_range | Reads cells from a spreadsheet in the vault. | path (required over this connection), range (A1 notation), sheet_id | The cell values. Without a range, an overview of the used area. |
read_chart_data | Reads the numbers behind a chart in a note. | path (required), id | Labels and series values per chart. For charts bound to a sheet, the source path and range as well. |
list_folders | Returns the folder tree of the vault, empty folders included. Use it to see the structure before choosing where to create or move a note. monthlet 1.1.1 and later. | path (start folder), depth (1 = direct children) | For each folder: path, name, `documents` (notes and spreadsheets directly in it), `totalDocuments` (everything below it), and its subfolders. Up to 2,000 folders; folders cut off by `depth` or the limit are marked `truncated`. |
list_files | Lists the files in a folder, whatever notes they belong to: attachments such as images, PDFs, video, and audio by default. To get only the files attached to one note, use `list_attachments`. monthlet 1.1.1 and later. | path, recursive (true), include (files, notes, or all) | Path, kind (note, image, video, audio, or other), and size of each file. |
get_backlinks | Lists the notes that link to a note. Notes in cloud storage that have not been downloaded yet are not included. monthlet 1.1.1 and later. | path (required) | The path and title of each linking note. |
list_attachments | Lists the files attached to one note: those referenced in the body (`where: body`) and those only in the attachments panel (`where: panel`). To list the files in a folder regardless of notes, use `list_files`. monthlet 1.1.1 and later. | path (required) | For each attachment: `path` (pass it as is to the tool in `readWith`), the reference as written in the note, `where`, `kind` (image, pdf, text, video, audio, or other), `exists`, `size`, and `readWith` — the tool that reads its content (`view_image`, `read_pdf`, or `read_file`; empty when it cannot be read). |
view_image | Returns an image in the vault (PNG, JPEG, GIF, or WebP) so the agent can see it. monthlet 1.1.1 and later. | path (required; the path from list_attachments works as is) | The image as MCP image content. |
read_pdf | Extracts the text of a PDF in the vault. Figures and layout are not included, and a scanned PDF with no text layer returns an error. monthlet 1.1.1 and later. | path (required) | The text of the PDF. |
read_file | Reads a plain-text file in the vault, such as .md, .txt, .csv, or .json. Use `read_note` for notes and `read_pdf` for PDFs. monthlet 1.1.1 and later. | path (required) | The content of the file. |
Writing
These appear only while Allow writing is on. Paths are relative to the vault, the same ones the reading tools return.
| Tool | What it does | Main arguments | Returns |
|---|---|---|---|
create_note | Creates a note from Markdown. Missing folders are created along the way. From monthlet 1.1.0, a frontmatter block at the start sets the note’s tags and any other keys; the title comes from the `title` argument. From monthlet 1.1.1, `[[Note name]]` in the body becomes an internal link. | title (required), markdown (required), folder, tags (monthlet 1.1.1 and later) | The path and title of the new note. A number is appended when the title is already taken. From monthlet 1.1.1, also its tags and `unresolvedWikilinks` — the `[[...]]` names that matched no note and were left as text. |
update_note | Replaces the body of a note with Markdown. Pass the `version` that `read_note` returned. From monthlet 1.1.0, a frontmatter block at the start is saved as the note’s frontmatter: leave it out to keep the current one, or send an empty `---` `---` block to remove it. From monthlet 1.1.1, `[[Note name]]` in the body becomes an internal link. | path (required), markdown (required), version (required from 1.0.2) | The path, the title, the placeholders that survived, any that were dropped, and the new `version`. `ignoredFrontmatterKeys` lists keys monthlet manages itself, such as title and tags, which frontmatter cannot change here. From monthlet 1.1.1, also `unresolvedWikilinks`. |
edit_note | Replaces part of a note’s body (monthlet 1.1.0 and later). The text `old_string` in the body that `read_note` returned becomes `new_string`; everything else stays as it was. Pass the `version` that `read_note` returned. | path (required), old_string (required), new_string (required; "" to delete), replace_all, version (required) | The same as `update_note`, plus `replacements`, the number of places replaced. From monthlet 1.1.1, `[[Note name]]` in `new_string` becomes an internal link, and `unresolvedWikilinks` lists the names that matched no note. |
create_folder | Creates an empty folder, intermediate levels included. | path (required) | The folder that was created. |
create_sheet | Creates a spreadsheet from rows of text. Formulas and formatting are not set at creation. | title (required), rows (required), folder | The path and title of the new spreadsheet. |
update_tags | Adds or removes tags on a note or spreadsheet without touching the body. Tags that differ only in case or full-width/half-width form count as the same tag, and a leading # is optional. monthlet 1.1.1 and later. | path (required), add, remove, version | The path and the tags it now has. When the `version` you passed still matches, also the new `version`, so you can go on to `update_note` without reading again. |
add_attachment | Adds an attachment to a note. Pass the content as base64 to save it next to the note (a number is appended instead of overwriting an existing file), or pass a file already in the vault to refer to it without copying. Paths outside the vault are not accepted; send the content instead. Only images, video, audio, PDF, and text files (txt, md, csv, tsv, json, log), up to 20 MB. monthlet 1.1.1 and later. | path (required), data and filename, or file; placement (body or panel; by default images, video, and audio go at the end of the body and other files in the attachments panel), version | The path of the attachment, the reference written into the note, where it was placed, and whether it was copied. When it went to the attachments panel and the `version` you passed still matches, also the new `version`. |
Moving and renaming
These appear only while both Allow writing and Allow moving and renaming are on (monthlet 1.1.0 and later). Internal links in other notes are updated to the new location. A note open in monthlet with unsaved changes is not moved or renamed, and neither is a folder that contains one. Deleting is not offered.
| Tool | What it does | Main arguments | Returns |
|---|---|---|---|
move_note | Moves a note to another folder. The filename stays the same. | path (required), to_folder (required; "" or . for the top of the vault) | The new path. A number is appended when the destination already has a file with that name. |
move_folder | Moves a folder, with everything in it, under another folder. A folder cannot be moved into itself or one of its subfolders. | path (required), to_folder (required; "" or . for the top of the vault) | The new path. |
rename_note | Changes the title of a note. The filename follows the title. | path (required), new_title (required) | The new title and path. |
rename_folder | Renames a folder where it is. The paths of the notes inside change with it. To put it under another folder, use move_folder. | path (required), new_name (required) | The new path. |
What the tools guarantee
- Markdown is the contract.
read_notehands you Markdown andupdate_noteandedit_notetake Markdown back. Nothing else about the file format is part of the interface. - Elements that Markdown cannot express are kept as placeholders. Charts, embeds, and calculation blocks come through as fenced
monthlet-blockcode blocks carrying an id. Leave the line in place and the element survives the round trip; remove the line and the element goes with it. Anything removed that way is reported back asdroppedBlockswith its id and a label, so a rewrite never drops a chart silently. When you need the numbers rather than the placeholder, callread_chart_data. update_notereplaces the whole body. Send the full text, not a patch. The title is not part of it and does not change.- Nested bulleted and numbered lists make the round trip (monthlet 1.1.1 and later). Indent child items under their parent, up to five levels. Earlier versions flattened them. Nested checklists are still flattened.
edit_notechanges only what it matches (monthlet 1.1.0 and later).old_stringhas to match the body exactly, spaces and line breaks included. When it appears more than once, add the surrounding text until it is unique, or setreplace_all. Only the blocks it touches are rebuilt from Markdown, so the rest of the note — nested lists, raw HTML, placeholders — is left exactly as it was. A match that cannot be found, or one that is not unique, writes nothing.- Frontmatter makes the round trip (monthlet 1.1.0 and later). A note’s own frontmatter keys come out of
read_noteat the start of the body and go back in throughupdate_note. Keys monthlet manages itself, such as title and tags, are not changed byupdate_note; it reports them asignoredFrontmatterKeys. Thetype: Notethatread_noteadds to a note without atypeis not stored when you write it back. - A write is refused in three cases: the note is open in monthlet with unsaved changes; no
versionis passed; or the note has changed since thatversionwas read — by a person, a sync, or another agent. The last two clear as soon as you read the note again withread_noteand rebuild your edit on that.update_noteandedit_notereturn the newversion, so writing again right after your own write does not need a fresh read. - Read the whole note before writing (monthlet 1.1.1 and later). When
read_notereturns only part of a note because ofoffsetorlimit, it returns noversion, so neitherupdate_notenoredit_notecan write from that read. Read the whole note first; otherwiseupdate_note, which replaces the whole body, would drop the lines you did not read. versionarrived in monthlet 1.0.2. Earlier versions checked for changes without it and could miss a write by another agent that had read the same note.- After
add_attachmentputs a file in the body, read the note again beforeupdate_note(monthlet 1.1.1 and later). The body you read earlier does not contain the new attachment, so writing it back would remove it;update_noterefuses until you read the note again.update_tagsandadd_attachmentare also refused while the note has unsaved changes in monthlet. - The previous version is kept. Every change to a note’s body goes into its change history in the app, so a person can look at what an agent did and restore the earlier version. Changing tags and adding a file to the attachments panel (monthlet 1.1.1 and later) leave the body as it is and are not recorded there.
- Turning writing, or moving and renaming, off takes effect immediately. A client that is already connected may keep showing the old tool list until it reconnects, but every call is checked against the current setting, so anything you have not allowed will not run.
- Paths can change. Opening a note whose filename disagrees with its title renames the file to match the title. Treat paths as current, not permanent: list or search again rather than reusing a path you cached a while ago.
Not exposed, on purpose
These exist in the app but are not offered over the connection.
- Deleting and merging notes or foldersThese are operations the app confirms with you before running, and a connection has nobody at the screen to ask. Moving and renaming were in this list until monthlet 1.1.0; they now have their own switch (see above).
- Editing cells in an existing spreadsheetSheet edits are applied against the sheet you have open, in the part of the app that draws it. Creating a new spreadsheet is available; changing one in place is not.
- Anything that moves the app’s interfaceOpening a note or a search panel has no meaning for an agent working outside the window.
- Fetching URLs, saving skills, and looking up in-app helpThese serve the chat inside monthlet. An outside agent already has its own.
Security
- Off by default. Nothing listens until you turn it on. Reading, writing, and moving and renaming are separate switches — turning it on does not let anything write, and allowing writing does not let anything move or rename.
- Local only. The server binds to 127.0.0.1, so it is not reachable from your network.
- Every request carries a token. A
Authorization: Bearerheader is required even locally, and requests that arrive with anOriginheader are refused outright, which keeps web pages in a browser from connecting. - One vault, while the app runs. What is published is the vault you have open, and only for as long as monthlet is running.
- Note contents are untrusted input for the agent that reads them. A note can contain text that reads like an instruction, and an agent that acts on it is acting on whatever happened to be written down. Handling that is the connecting agent’s responsibility — monthlet hands over the content as it is.
Compatibility
- Tool names and their inputs and outputs are kept backward compatible. Fields may be added; existing ones keep their meaning.
- When something has to change in a way that breaks callers, it arrives as a new tool name rather than a quiet change to an existing one.
- One exception so far: in monthlet 1.0.2,
update_notestarted requiringversion. Without it, two agents that read the same note could overwrite each other’s changes without noticing, and a new tool name would have kept the old, unsafe one available. A call withoutversionis refused with a message to read the note first; it never writes. - The
monthlet-blocksJSON inside a note file is not part of the published interface. It changes as the app changes, so go through the tools rather than reading it.