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.
From MELPA or MELPA Stable:
M-x package-install RET org-mcp RET
**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.
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"))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-disableBefore 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.
Note: File paths in URIs use minimal encoding (only # characters are encoded). Avoid using % characters in Org file names.
- 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-filesusing absolute paths - Returns: Plain text content of the Org file
- 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-filesusing absolute paths - Returns: JSON representation of the document structure with headings and their levels
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": []}
]
}
]
}
- Description: Access the content of a specific headline by its path
- URI Pattern:
org-headline://{filename}#{path}where:filenameis the absolute path (with # encoded as %23)pathis URL-encoded headline titles separated by/- Headlines containing # must be encoded as %23 in the path
- Configuration: Files must be explicitly allowed via
org-mcp-allowed-filesusing absolute paths - Returns: Plain text content of the specified headline section including all subheadings
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.
- 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
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
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.
- Description: Get TODO keyword configuration for understanding task states
- Parameters: None
- Returns: JSON object with
sequencesandsemantics
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"}
]
}- 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"
}- Description: Get the list of Org files accessible through the org-mcp server
- Parameters: None
- Returns: JSON object with
filesarray 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": []
}- Description: Update the TODO state of a specific headline
- Parameters:
uri(string, required): URI of the headline (supportsorg-headline://ororg-id://)currentState(string, required): Current TODO state (empty string “” for no state) - must match actual statenewState(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
Example:
# Request:
{
"uri": "org-headline:///home/user/org/projects.org/Project%20Alpha",
"currentState": "TODO",
"newState": "IN-PROGRESS"
}
# Success response:
{
"success": true,
"previousState": "TODO",
"newState": "IN-PROGRESS",
"uri": "org-id://554A22F6-E29F-4759-8AD2-E7CA225C6397"
}
# State mismatch error:
{
"error": "State mismatch: expected TODO, found IN-PROGRESS"
}- Description: Rename the title of an existing headline while preserving its TODO state, tags, and properties
- Parameters:
uri(string, required): URI of the headline (supportsorg-headline://ororg-id://)currentTitle(string, required): Current headline title (without TODO state or tags) - must match actual titlenewTitle(string, required): New headline title (without TODO state or tags)
- Returns: Success status with previous and new titles
Example:
# Request:
{
"uri": "org-headline:///home/user/org/projects.org/Original%20Task",
"currentTitle": "Original Task",
"newTitle": "Updated Task Name"
}
# Success response:
{
"success": true,
"previousTitle": "Original Task",
"newTitle": "Updated Task Name",
"uri": "org-id://550e8400-e29b-41d4-a716-446655440002"
}
# Title mismatch error:
{
"error": "Title mismatch: expected 'Original Task', found 'Different Task'"
}- Description: Add a new TODO item to an Org file
- Parameters:
title(string, required): The headline texttodoState(string, required): TODO state fromorg-todo-keywordstags(string or array, required): Tags to add (e.g., “urgent” or [“work”, “urgent”])body(string, optional): Body text content to add under the headingparentUri(string, required): URI of parent item. Useorg-headline://filename.org/for top-level items in a fileafterUri(string, optional): URI of sibling to insert after. If not given, append as last child of parent
- Returns: Object with success status, new item URI, file name, and title
Example:
# Request:
{
"title": "Implement new feature",
"todoState": "TODO",
"tags": ["work", "urgent"],
"body": "This feature needs to be completed by end of week.",
"parentUri": "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"
}- Description: Edit body content of an Org node using partial string replacement
- Parameters:
resourceUri(string, required): URI of the node to edit (supportsorg-headline://ororg-id://)oldBody(string, required): Substring to search for within the node’s body (must be unique unless replaceAll is true). Use empty string “” to add content to an empty nodenewBody(string, required): Replacement textreplaceAll(boolean, optional): Replace all occurrences (default: false)
- Returns: Success status with ID-based URI of the updated node
- Special behavior: When
oldBodyis an empty string (“”), the tool will only work if the node has no body content, allowing you to add initial content to empty nodes
Example:
# Request:
{
"resourceUri": "org-id://abc-123",
"oldBody": "This is a placeholder.",
"newBody": "Implementation started - using Strategy pattern."
}
# Success response:
{
"success": true,
"uri": "org-id://abc-123"
}
# Adding content to empty node:
{
"resourceUri": "org-id://new-task",
"oldBody": "",
"newBody": "Initial task description."
}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.
- 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
- 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
- Description: Read specific Org headline by hierarchical path
- Parameters:
file(string, required): Absolute path to an Org fileheadlinePath(string, required): Non-empty slash-separated path to headline. Only slashes within headline titles must be URL-encoded as%2Fto distinguish them from path separators. Other characters (spaces,#, etc.) do not need encoding. To read entire files, useorg-read-fileinstead
- Returns: Plain text content of the headline and its subtree
- Configuration: File must be in
org-mcp-allowed-files
- 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
This project is licensed under the GNU General Public License v3.0 (GPLv3) - see the LICENSE file for details.