Skip to content

org-mcp v1.0.0

Latest

Choose a tag to compare

@laurynas-biveinis laurynas-biveinis released this 20 Aug 08:04
· 1 commit to main since this release
b02d20d

Incompatible Changes

org-rename-headline renamed to org-edit-headline

The tool now also edits a headline's local tags via add_tags and remove_tags in addition to its title, and returns the updated node (title, todo, priority, tags, scheduled, deadline, uri) instead of the previous title pair. new_title is now optional; supply at least one of new_title, add_tags, remove_tags.

Raised the minimum Emacs version from 27.1 to 28.2

org-read-headline and org-read-by-id now return JSON

Both tools, and the corresponding org-headline:// (with a headline path) and org-id:// resource reads, return a JSON object of headline_path (the ancestor chain from the outermost ancestor down to the read headline, in org-grep's node format) and content (the subtree text previously returned bare). Whole-file org-headline:// reads still return raw file content. The org-id:// resource now declares mime type application/json.

Now requires mcp-server-lib 0.4.0

The MCP initialize serverInfo now reports org-mcp's own name and version instead of the library's defaults.

New Features

Added org-get-agenda tool for day/week/month agenda views

Returns the plain-text Org agenda for a day, week, or month span from an optional reference date, built from the allowed files. Thanks to Collins Conover (@PC-LoadLetter).

Added org-grep tool for searching across Org files

Search for a literal substring across one or all allowed files. Results are grouped by containing section. Each group's headline_path traces the section's ancestors as objects carrying title, todo, priority, local tags, scheduled, deadline, and a resource uri (readable via resources/read).

Richer node metadata in org-read-outline

Each outline node now also carries the headline's todo state, priority, local tags, scheduled, deadline, and resource uri alongside title, level, and children.

Added org-archive-subtree tool for archiving subtrees

Archives a headline subtree to its configured location (the headline ARCHIVE property, the file's #+ARCHIVE:, or org-archive-location), and returns the archive file path and the headline's org-id:// URI. Thanks to Michael Cordell (@mcordell).

Send MCP initialize instructions

The server now emits an instructions string in the MCP initialize response, carrying the cross-cutting tool and resource guidance that was previously repeated in each description.

Added position parameter to org-add-todo

Optional position ("start" or "end", default "end") selects placement: first-child / last-child under a child parent_uri, or start-of-file / end-of-file under a top-level parent_uri. Cannot be combined with after_uri.

Added org-refile-headline tool for moving subtrees

Moves a headline and its subtree to a new parent or file, preserving its TODO state, tags, properties, ID, body, and children. Placement mirrors org-add-todo (after_uri / position); the existing ID is kept across files.

Added org-set-planning tool for SCHEDULED and DEADLINE

Sets, changes, or clears a headline's SCHEDULED and DEADLINE planning timestamps via org-schedule/org-deadline. Accepts relative dates and repeater/warning cookies, preserves an existing repeater/warning when re-scheduling without one, and returns the resulting timestamps and the headline's org-id:// URI.

Allowed a keyword-less headline in org-add-todo / org-update-todo-state

todo_state (add) and new_state (update) now accept an empty string to create or switch a headline to no TODO keyword. Any CLOSED stamp is left to Org's own settings.

Added org-find-tagged-ancestor tool for locating a tagged enclosing headline

Given a headline URI and a tag, returns the node metadata of the nearest self-or-ancestor headline that declares the tag as its own (local) tag, or null when none does. Inherited tags, #+FILETAGS, and org-tags-exclude-from-inheritance are not consulted.

Bug Fixes

Fixed org-get-agenda leaking buffers of files it opened

File buffers the agenda build had to open stayed alive on the global org-agenda-new-buffers list until the user's own org-agenda-exit killed them. The tool now kills the buffers it opened after each call (a buffer modified during the build is handed to org-agenda-new-buffers instead), and builds into a uniquely named private buffer.

Fixed in-file archiving losing the subtree when the file is open

When a buffer visited the file being archived within itself, org-archive-subtree pasted the archived copy into that buffer instead of the tool's own, and the tool's subsequent source write discarded the copy while reporting success. The in-file move now happens entirely in the tool's buffer.

Fixed file identity checks under aliased paths

The file-modifying tools compared file paths textually, so a buffer visiting an allowed file through a different path (a symlink anywhere in either path, or a hard link) was missed: the unsaved-changes guard did not block the write, and the buffer was left stale instead of refreshed afterwards. Buffers are now matched by truename or by inode and device numbers, as find-buffer-visiting does. Likewise, org-archive-subtree now recognizes an org-archive-location naming the source file through a different path as in-file archiving rather than a separate archive file.

Fixed org-edit-body on a drawer-only node before another heading

Adding a first paragraph with an empty old_body to a headline whose only content is drawers (:PROPERTIES:/:LOGBOOK:) failed with "Cannot use empty old_body with non-empty body" when the headline was followed by another heading, because the body region ran past the node into the next heading.

Fixed org-headline:// path resolution for non-ASCII titles

Resolving a headline by an org-headline:// URI or headline_path failed when a path segment held a non-ASCII (percent-encoded UTF-8) character, because the decoded bytes were not decoded as UTF-8 before matching the headline text. Affected every path-based read/modify tool; org-id:// resolution was unaffected.

Resolved relative #+SETUPFILE in Org-parsing read tools

org-read-outline, org-read-headline, org-read-by-id, org-grep, and org-find-tagged-ancestor, and the corresponding org-outline://, org-headline://, and org-id:// resource reads, resolved a relative #+SETUPFILE against the caller's default-directory rather than the Org file's own directory, so custom TODO keywords defined there could be missed.

Reported org-blocker-hook vetoes instead of a false success

org-update-todo-state and org-add-todo now error when a member of org-blocker-hook (e.g. a TODO-dependency or unchecked-checkbox blocker) silently vetoes the state change, rather than reporting success with the file unchanged. org-update-todo-state also returns the actual resulting state, so a repeater reset back to the not-done keyword is visible.

Stopped modifying tools from prompting to kill a modified buffer

A modifying tool run in a live Emacs could raise a stray modified-buffer kill prompt.

Fixed org-update-todo-state failing on no-state heading with current_state=""

string= treats nil and "" as distinct, so the documented "use empty string for no TODO state" path errored with "State mismatch" while the undocumented JSON null path silently worked. "" now matches a heading with no TODO state, and current_state rejects JSON null at the tool-boundary validator.

Stopped org-edit-body from returning the URI of a non-target heading

The returned org-id:// URI previously identified the parent's first child, or a strictly-deeper heading inside the new body.

Stopped org-edit-body from concatenating new content with the preceding line

With old_body="" on a heading whose body is empty in a file without a trailing newline, the inserted content previously ran onto the heading line (or onto the property drawer's :END:). Now the content lands on its own line, and org-edit-body always leaves the file ending in a newline.

Returned new-heading URI when org-add-todo body contains deeper headlines

body may contain headlines deeper than the new heading (legitimate child structure), but the returned org-id:// URI previously pointed to the deepest such headline instead of the newly added one.

Rejected after_uri with a top-level parent_uri in org-add-todo

A top-level parent_uri (no fragment) combined with after_uri is now rejected as a validation error.

Rejected after_uri equal to parent_uri in org-add-todo

Passing an after_uri whose :ID: resolves to parent_uri itself is now rejected as a validation error.

Restricted org-add-todo position="start" top-level anchor to level-1 headings

The anchor previously matched any-level heading at column 0; an orphan deeper heading before the first level-1 was silently reparented under the new heading.

Normalised redundant separators in org-headline:// URIs

org-mcp--split-headline-uri now collapses both an empty fragment (org-headline://FILE#) and a single trailing / on the file path to the same (FILE . nil) pair as the bare org-headline://FILE. All of org-headline://file.org, org-headline://file.org/, org-headline://file.org#, org-headline://file.org/#, org-headline://file.org#H and org-headline://file.org/#H now resolve to equivalent (FILE . HEADLINE) pairs.

Removed trailing blank line at end of file in org-add-todo with body

Adding a TODO with a non-empty body under a parent heading (either via after_uri against the parent's last child, or with position="end" when the parent is the file's last subtree) to a file that does not end with a trailing newline now produces a file ending in exactly one newline. Mid-file placements still leave a single blank line between the body and the next heading.

Treated empty body string as no-body in org-add-todo

Passing body="" (an empty string) now produces the same file as omitting body entirely.

Cached ID-fallback resolution to avoid repeated full-file scans

Successful ID-fallback scans now register the (id, file) pair in org-id-locations. Gated on org-id-track-globally.

Routed read-only org-id:// resolution through allowed-files fallback

Read-only org-id:// resolution (org-read-by-id and the org-id://{uuid} resource) now falls back to scanning allowed files when org-id-find-id-file misses, matching the modifying tools' behaviour.

Clarified org-id:// error when the resolved file is not allowed

The MCP error now reads ID '<uuid>' resolves to a file not in the allowed list. The resolved file path is intentionally not surfaced to avoid disclosing files outside org-mcp-allowed-files.

Rejected malformed org-id:// and org-headline:// URIs at the dispatch boundary

A URI whose suffix after the prefix carries no meaningful content (empty, whitespace-only, or NBSP-only) or itself contains another URI scheme separator (e.g. org-id://org-headline://foo) is now rejected with Invalid resource URI format at the validation boundary, instead of dispatching to a no-meaningful-content lookup that surfaced as Cannot find ID '<chars>' or a downstream file-access error.

Rejected unterminated file-header drawer across modifying tools

Every modifying MCP tool now rejects a file whose header block contains a :NAME: drawer opener with no matching :END:.

Recognised #+BEGIN_* and #+BEGIN: block pairs in the file-header walker

A named block (#+BEGIN_NAME ... #+END_NAME) or dynamic block (#+BEGIN: ... #+END:) at the start of an Org file is now consumed as a single bounded element. An unterminated block is rejected, mirroring unterminated drawers.

Rejected heading line inside file-header drawer

A heading line between a drawer opener and its :END: in the file's header block is now rejected as a validation error, matching Org's parser.

Strict :END: drawer terminator recognition

Only a line that consists of exactly :END: (optionally followed by spaces or tabs) counts as the drawer terminator. A line such as :END:typo does not close the drawer.

Strict drawer opener recognition in the file-header walker

Only a line that consists of exactly :NAME: (any drawer keyword, optionally followed by spaces or tabs) opens a drawer. A line such as :PROPERTIES: stray text at column 0 is treated as ordinary content terminating the file's header block, matching how Org's own parser classifies the line.

Leading whitespace accepted on file-header lines

Drawer openers (:NAME:), drawer closers (:END:), and #-prefixed lines (#+-keywords, # -comments, #hashtag paragraphs) are now accepted by the file-header walker with optional leading whitespace (spaces or tabs), matching how Org's parser already accepts indented forms. Headings themselves still require column 0 (Org rule).

Any drawer keyword recognised in the file-header walker

The file-header walker now recognises any well-formed drawer opener -- :PROPERTIES:, :LOGBOOK:, or any custom :NAME: keyword -- and consumes it through its :END:. A bare :END: with no preceding opener is still treated as ordinary content terminating the header.

Uniform type validation for wire-protocol string parameters

Every MCP tool now rejects non-string values for string-typed parameters with a uniform Field X must be a string, got: V (type: T) error.

Fixed spurious blank line in org-add-todo when parent is followed by content

When adding a child under a parent heading that has following content (a top-level heading or any parent-level sibling), the new heading now lands flush against the following content.

Fixed default placement for top-level org-add-todo

When called at top level (parent URI with no fragment) on a file that already contains top-level headings, the default placement now appends the new heading at end of file, as the tool's description documents.

Rejected empty or whitespace-only after_uri in org-add-todo

after_uri""and any whitespace-only value (regular spaces, tabs, NBSP) are now validation errors. Omit the JSON key entirely when not specifying a sibling. The bare prefixafter_uri"org-id://" (with no UUID after the scheme) is also rejected at the validation boundary.

Case-sensitive body matching in org-edit-body

The MCP tool org-edit-body now matches the old_body parameter case-sensitively.

Marked org-edit-bodys replace_all as optional in the JSON Schema

The published JSON Schema now reports replace_all as not required, matching the tool's documented default of false.

Fixed org-edit-body silently replacing all on replace_all=false

The JSON boolean false is now recognised for the replace_all parameter (e.g. {"replace_all": false}), so the multiple-occurrence safety guard fires on ambiguous matches as documented.