Skip to content

laurynas-biveinis/org-mcp

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

401 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

org-mcp

https://github.com/laurynas-biveinis/org-mcp/actions/workflows/elisp-test.yml/badge.svg https://github.com/laurynas-biveinis/org-mcp/actions/workflows/super-linter.yml/badge.svg https://melpa.org/packages/org-mcp-badge.svg https://stable.melpa.org/packages/org-mcp-badge.svg

Overview

org-mcp is an Emacs package that implements a Model Context Protocol (MCP) server for Org-mode. It enables AI assistants and other MCP clients to interact with your Org files through a structured API.

Installation

From MELPA or MELPA Stable:

M-x package-install RET org-mcp RET

See NEWS for the changelog.

Usage

WARNING: some of the tools in this package give LLMs WRITE access to your Org files, and, once in a thousand invocations, LLMs will try to delete everything, because they are like that. Backups and automatic versioning on every change are strongly advised.

Configuring allowed files

Once you read and internalized the warning above, set the allowed Org file set, using absolute paths:

(setq org-mcp-allowed-files '("/path/to/foo.org" "/path/to/bar.org"))

Registering with an MCP Client

After mcp-server-lib has been properly installed (including M-x mcp-server-lib-install), register org-mcp with your MCP client:

claude mcp add -s user -t stdio org-mcp -- ~/.emacs.d/emacs-mcp-stdio.sh --server-id=org-mcp --init-function=org-mcp-enable --stop-function=org-mcp-disable

Before using the MCP server, you must start it in Emacs with M-x mcp-server-lib-start. Stop it with M-x mcp-server-lib-stop when done.

Available MCP Resources

Note: File paths in URIs use minimal encoding (only # characters are encoded). Avoid using % characters in Org file names.

org://{filename}

  • Description: Access the raw content of an allowed Org file
  • URI Pattern: org://{filename} where filename is the absolute path to the file
  • Configuration: Files must be explicitly allowed via org-mcp-allowed-files using absolute paths
  • Returns: Plain text content of the Org file
  • Buffer behavior: Reads the file from disk; unsaved changes in an Emacs buffer visiting the file are not reflected.

org-outline://{filename}

  • Description: Get the hierarchical structure of an Org file
  • URI Pattern: org-outline://{filename} where filename is the absolute path to the file
  • Configuration: Files must be explicitly allowed via org-mcp-allowed-files using absolute paths
  • Returns: JSON representation of the document structure with headings and their levels
  • Buffer behavior: Reads the file from disk; unsaved changes in an Emacs buffer visiting the file are not reflected.

Example:

# Access via MCP:
URI: org-outline:///home/user/org/projects.org
Returns: JSON structure like:
{
  "headings": [
    {
      "title": "Project Alpha",
      "level": 1,
      "children": [
        {"title": "Requirements", "level": 2, "children": []},
        {"title": "Implementation", "level": 2, "children": []}
      ]
    }
  ]
}

org-headline://{filename}#{path}

  • Description: Access the content of a specific headline by its path
  • URI Pattern: org-headline://{filename}#{path} where:
    • filename is the absolute path (with # encoded as %23)
    • path is URL-encoded headline titles separated by /
    • Headlines containing # must be encoded as %23 in the path
    • Trailing / on filename and empty # fragments are stripped (FILE, FILE/, FILE#, and FILE/# all resolve identically)
  • Configuration: Files must be explicitly allowed via org-mcp-allowed-files using absolute paths
  • Returns: Plain text content of the specified headline section including all subheadings
  • Buffer behavior: Reads the file from disk; unsaved changes in an Emacs buffer visiting the file are not reflected.

Example:

# Access a headline:
URI: org-headline:///home/user/org/projects.org#Project%20Alpha/Requirements
Returns: Content of "Requirements" under "Project Alpha"

# Headline with # character (must be encoded as %23):
URI: org-headline:///home/user/org/projects.org#Issue%20%2342
Returns: Content of "Issue #42" headline

# Access entire file (no fragment):
URI: org-headline:///home/user/org/projects.org
Returns: Full content of the file

# File with # in the name (must be encoded as %23):
URI: org-headline:///home/user/org/file%231.org#Headline
Returns: Content of "Headline" from file#1.org

# Both file and headline with # (all encoded):
URI: org-headline:///home/user/org/file%231.org#Task%20%235
Returns: Content of "Task #5" from file#1.org

Encoding limitations: File paths use minimal encoding (only #%23) for readability. Files with % characters in their names should be avoided, as they may cause decoding issues. For such files, rename them or use org-id:// URIs instead. Headline paths use full URL encoding.

org-id URI Format

  • Description: Access Org node content by its unique ID property
  • URI Pattern: org-id://{uuid} where uuid is the value of an ID property
  • Configuration: The file containing the ID must be in org-mcp-allowed-files
  • Returns: Plain text content of the headline with the specified ID, including all subheadings
  • Buffer behavior: Reads the file from disk; unsaved changes in an Emacs buffer visiting the file are not reflected.

Example:

# Org file with ID property:
* Project Meeting Notes
:PROPERTIES:
:ID: 550e8400-e29b-41d4-a716-446655440000
:END:
Meeting content here...

Access via MCP:

  • URI: org-id://550e8400-e29b-41d4-a716-446655440000
  • Returns: Content of “Project Meeting Notes” section

Available MCP Tools

Note: All write tools will create Org IDs for any touched nodes that did not have them originally. The IDs will be returned in the tool response.

Note: Full semantics for each tool live in its MCP description string, surfaced by clients via tools/list; the summaries below mirror those descriptions.

org-get-todo-config

  • Description: Get TODO keyword configuration for understanding task states
  • Parameters: None
  • Returns: JSON object with sequences and semantics

Example response:

{
  "sequences": [
    {
      "type": "sequence",
      "keywords": ["TODO", "NEXT", "|", "DONE", "CANCELLED"]
    }
  ],
  "semantics": [
    {"state": "TODO", "isFinal": false, "sequenceType": "sequence"},
    {"state": "NEXT", "isFinal": false, "sequenceType": "sequence"},
    {"state": "DONE", "isFinal": true, "sequenceType": "sequence"},
    {"state": "CANCELLED", "isFinal": true, "sequenceType": "sequence"}
  ]
}

org-get-tag-config

  • Description: Get tag configuration as literal Elisp variable values
  • Parameters: None
  • Returns: JSON object with literal Elisp strings for all tag-related variables

Example return value:

{
  "org-use-tag-inheritance": "t",
  "org-tags-exclude-from-inheritance": "(\"urgent\")",
  "org-tag-alist": "((\"work\" . 119) (\"urgent\" . 117) (:startgroup) (\"@office\" . 111) (\"@home\" . 104) (\"@errand\" . 101) (:endgroup) (:startgrouptag) (\"project\") (:grouptags) (\"proj_a\") (\"proj_b\") (:endgrouptag))",
  "org-tag-persistent-alist": "nil"
}

org-get-allowed-files

  • Description: Get the list of Org files accessible through the org-mcp server
  • Parameters: None
  • Returns: JSON object with files array containing absolute paths of allowed Org files

Use cases:

  • Discovery: “What Org files can I access through MCP?”
  • URI Construction: “I need to build an org-headline:// URI - what’s the exact path?”
  • Access Troubleshooting: “Why is my file access failing?”
  • Configuration Verification: “Did my org-mcp-allowed-files setting work correctly?”

Example response:

{
  "files": [
    "/home/user/org/tasks.org",
    "/home/user/org/projects.org",
    "/home/user/notes/daily.org"
  ]
}

Empty configuration returns:

{
  "files": []
}

org-get-agenda

  • Description: Get the same plain-text view Emacs shows for a standard Org org-agenda-list in daily, weekly, or monthly span, aligned with org-agenda-day-view, org-agenda-week-view, and org-agenda-month-view.
  • Parameters:
    • view (string, required): day, week, or month (case-insensitive)
    • date (string, optional): Any string org-read-date accepts; omit to use today (an empty or whitespace-only string is rejected). Fixes which day / week / calendar month the agenda covers. How unrecognized input is treated follows your installed Org version.
  • Scope: The agenda is built only from org-mcp-allowed-files entries that exist on disk. It does not pull in other org-agenda-files the user may have configured in Emacs.
  • Returns: JSON with view, date (your string or the literal today if omitted), start_day (the first day the agenda actually covers, as YYYY-MM-DD; resolved, for a month snapped to the first of the calendar month, and for a week aligned per your org-agenda-start-on-weekday — the reference day itself when that is nil), and agenda (full agenda buffer text, including headers and lines for scheduled and deadline items from the allowed files).

org-update-todo-state

  • Description: Update the TODO state of a specific headline
  • Parameters:
    • uri (string, required): URI of the headline (supports org-headline:// or org-id://)
    • current_state (string, required): Current TODO state (empty string “” for no state) - must match actual state
    • new_state (string, required): New TODO state (must be valid in org-todo-keywords)
  • Returns: Success status with previous and new states, and ID-based URI of the updated headline
  • Buffer behavior: Modifies the file on disk; fails if an Emacs buffer visiting the file has unsaved changes; ask the user to save the buffer and retry.

Example:

# Request:
{
  "uri": "org-headline:///home/user/org/projects.org/Project%20Alpha",
  "current_state": "TODO",
  "new_state": "IN-PROGRESS"
}

# Success response:
{
  "success": true,
  "previous_state": "TODO",
  "new_state": "IN-PROGRESS",
  "uri": "org-id://554A22F6-E29F-4759-8AD2-E7CA225C6397"
}

# State mismatch error:
{
  "error": "State mismatch: expected TODO, found IN-PROGRESS"
}

org-rename-headline

  • Description: Rename the title of an existing headline while preserving its TODO state, tags, and properties
  • Parameters:
    • uri (string, required): URI of the headline (supports org-headline:// or org-id://)
    • current_title (string, required): Current headline title (without TODO state or tags) - must match actual title
    • new_title (string, required): New headline title (without TODO state or tags)
  • Returns: Success status with previous and new titles
  • Buffer behavior: Modifies the file on disk; fails if an Emacs buffer visiting the file has unsaved changes; ask the user to save the buffer and retry.

Example:

# Request:
{
  "uri": "org-headline:///home/user/org/projects.org/Original%20Task",
  "current_title": "Original Task",
  "new_title": "Updated Task Name"
}

# Success response:
{
  "success": true,
  "previous_title": "Original Task",
  "new_title": "Updated Task Name",
  "uri": "org-id://550e8400-e29b-41d4-a716-446655440002"
}

# Title mismatch error:
{
  "error": "Title mismatch: expected 'Original Task', found 'Different Task'"
}

org-add-todo

  • Description: Add a new TODO item to an Org file
  • Parameters:
    • title (string, required): The headline text
    • todo_state (string, required): TODO state from org-todo-keywords
    • tags (string or array, required): Tags to add (e.g., “urgent” or [“work”, “urgent”])
    • body (string, optional): Body text content to add under the heading
    • parent_uri (string, required): URI of parent item. Use org-headline://filename.org/ for top-level items in a file (also accepted: org-headline://filename.org, org-headline://filename.org#, org-headline://filename.org/# — not recommended).
    • after_uri (string, optional): URI of sibling to insert after; omit to append as last child. Cannot be combined with a top-level parent_uri or with position, and cannot reference parent_uri itself. See the MCP tool description for accepted formats.
    • position (string, optional): Where to place the new item: "start" or "end" (default "end"). At top level, "start" inserts after leading #-prefixed lines and drawers, before the first existing heading. See the MCP tool description for full placement rules and the mutex with after_uri.
  • Returns: Object with success status, new item URI, file name, and title
  • Buffer behavior: Modifies the file on disk; fails if an Emacs buffer visiting the file has unsaved changes; ask the user to save the buffer and retry.

Example:

# Request:
{
  "title": "Implement new feature",
  "todo_state": "TODO",
  "tags": ["work", "urgent"],
  "body": "This feature needs to be completed by end of week.",
  "parent_uri": "org-headline:///home/user/org/projects.org/"
}

# Success response:
{
  "success": true,
  "uri": "org-id://550e8400-e29b-41d4-a716-446655440001",
  "file": "projects.org",
  "title": "Implement new feature"
}

org-edit-body

  • Description: Edit body content of an Org node using partial string replacement
  • Parameters:
    • resource_uri (string, required): URI of the node to edit (supports org-headline:// or org-id://)
    • old_body (string, required): Substring to search for within the node’s body (must be unique unless replace_all is true). Use empty string “” to add content to an empty node
    • new_body (string, required): Replacement text
    • replace_all (boolean, optional): Replace all occurrences (default: false)
  • Returns: Success status with ID-based URI of the updated node
  • Special behavior: When old_body is an empty string (“”), the tool will only work if the node has no body content, allowing you to add initial content to empty nodes
  • Buffer behavior: Modifies the file on disk; fails if an Emacs buffer visiting the file has unsaved changes; ask the user to save the buffer and retry.

Example:

# Request:
{
  "resource_uri": "org-id://abc-123",
  "old_body": "This is a placeholder.",
  "new_body": "Implementation started - using Strategy pattern."
}

# Success response:
{
  "success": true,
  "uri": "org-id://abc-123"
}

# Adding content to empty node:
{
  "resource_uri": "org-id://new-task",
  "old_body": "",
  "new_body": "Initial task description."
}

org-archive-subtree

  • Description: Archive an Org headline subtree to its configured archive location
  • Parameters:
    • uri (string, required): URI of the headline to archive (supports org-headline:// or org-id://)
  • Returns: Success status, the absolute archive file path, and the archived headline’s org-id:// URI. The archive file path equals the source file’s own path when the archive location is in-file (empty file part before ::)
  • URI resolvability: The returned org-id:// URI identifies the archived headline but is resolvable via resources/read only if the archive file is itself a member of org-mcp-allowed-files; otherwise it is informational only
  • Archive location: Honors the headline’s ARCHIVE property, then the file’s #+ARCHIVE: setting, then the global org-archive-location, in that order. The subtree is always moved to that archive file regardless of org-archive-default-command.
  • Buffer behavior: Modifies the source and archive files on disk; fails if an Emacs buffer visiting either has unsaved changes; ask the user to save the buffer and retry.

Example:

# Request (headline lacks an ID; the tool mints one):
{
  "uri": "org-headline:///home/user/org/projects.org#Project%20Alpha/Old%20Task"
}

# Success response (returns the freshly minted org-id:// URI):
{
  "success": true,
  "archive_file": "/home/user/org/projects.org_archive",
  "uri": "org-id://550e8400-e29b-41d4-a716-446655440003"
}

Workaround Tools Duplicating Resource Templates

Note: The following tools are temporary workarounds that duplicate the resource template functionality as tools. They exist because Claude Code currently doesn’t discover resource templates.

org-read-file

  • Description: Read complete raw content of an Org file
  • Parameters:
    • file (string, required): Absolute path to an Org file
  • Returns: Plain text content of the entire Org file
  • Configuration: File must be in org-mcp-allowed-files
  • Buffer behavior: Reads the file from disk; unsaved changes in an Emacs buffer visiting the file are not reflected.

org-read-outline

  • Description: Get hierarchical structure of an Org file as JSON outline
  • Parameters:
    • file (string, required): Absolute path to an Org file
  • Returns: JSON object with hierarchical outline structure
  • Configuration: File must be in org-mcp-allowed-files
  • Buffer behavior: Reads the file from disk; unsaved changes in an Emacs buffer visiting the file are not reflected.

org-read-headline

  • Description: Read specific Org headline by hierarchical path
  • Parameters:
    • file (string, required): Absolute path to an Org file
    • headline_path (string, required): Non-empty slash-separated path to headline. Only slashes within headline titles must be URL-encoded as %2F to distinguish them from path separators. Other characters (spaces, #, etc.) do not need encoding. To read entire files, use org-read-file instead
  • Returns: Plain text content of the headline and its subtree
  • Configuration: File must be in org-mcp-allowed-files
  • Buffer behavior: Reads the file from disk; unsaved changes in an Emacs buffer visiting the file are not reflected.

org-read-by-id

  • Description: Read Org headline by its unique ID property
  • Parameters:
    • uuid (string, required): UUID from headline’s ID property
  • Returns: Plain text content of the headline and its subtree
  • Configuration: File containing the ID must be in org-mcp-allowed-files
  • Note: More stable than path-based access since IDs don’t change when headlines are renamed or moved
  • Buffer behavior: Reads the file from disk; unsaved changes in an Emacs buffer visiting the file are not reflected.

org-grep

  • Description: Search for a literal substring across one or all allowed Org files
  • Parameters:
    • pattern (string, required): Literal substring to search for. Must be non-empty and single-line (no newlines). Not a regex.
    • file (string, optional): Absolute path to an allowed Org file. When omitted, all org-mcp-allowed-files are searched.
    • case_sensitive (boolean, optional, default false): When true, the match is case-sensitive.
  • Returns: JSON object with a groups array. Each group represents a contiguous run of matching lines within one section:
    • file: absolute path of the source file
    • headline_path: array of headline title strings tracing the path to the containing section (empty array for content before the first heading)
    • uri: resource URI — org-id:// when the section has an ID, org-headline:// otherwise. Pass directly to resources/read. To use the read tools instead, extract the uuid (for org-read-by-id) or the file and fragment path (for org-read-headline) from the URI.
    • matches: array of {line, text} objects — line is the 1-based line number, text is the full line content
  • Group rules: Groups appear in document order, per file in org-mcp-allowed-files order. A new group starts whenever the containing section changes. One match per source line.
  • Configuration: Searched files must be in org-mcp-allowed-files. Returns {"groups": []} when no files are configured and no file is given.
  • Buffer behavior: Reads files from disk; unsaved changes in Emacs buffers are not reflected.

License

This project is licensed under the GNU General Public License v3.0 (GPLv3) - see the LICENSE file for details.

About

Emacs Org-mode integration with Model Context Protocol (MCP) for AI-assisted task management

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors