Skip to main content
Glama
ariedotme

obsidian-writer-mcp

by ariedotme
README.md
# obsidian-writer-mcp

MCP server for writing to Obsidian through the `obsidian-writer` HTTP service.

This project was originally created for personal use in my homelab. I later decided to publish it in case it is useful to other people building similar local-first Obsidian automation.

Related project: [`obsidian-writer`](https://github.com/ariedotme/obsidian-writer), the HTTP service this MCP server calls to write into Obsidian vaults.

## Configuration

The server reads environment variables from the process and from `.env` files. Existing process variables take precedence over `.env` values.

Load order:

1. `.env` next to `obsidian_writer_mcp.py`
2. `.env` in the current working directory

Create a local `.env` from the example:

```bash
cp .env.example .env
```

Variables:

- `OBSIDIAN_WRITER_BASE_URL`: base URL for the HTTP writer. Default: `http://obsidian-writer:3000`.
- `OBSIDIAN_DEFAULT_VAULT`: default vault used when a tool does not receive `vault`. Default: `default`.

Example:

```env
OBSIDIAN_WRITER_BASE_URL=http://obsidian-writer:3000
OBSIDIAN_DEFAULT_VAULT=my-vault
```

The `.env` file is ignored by Git; publish `.env.example` and keep local values private.

## Hermes registration

If this repository is mounted inside the Hermes container at `/srv/apps/obsidian-writer-mcp`, register it without hardcoded vault values; the MCP will read `.env` itself:

```bash
docker exec -it hermes hermes -p <profile> mcp remove obsidian-writer || true

docker exec -it hermes hermes -p <profile> mcp add obsidian-writer \
  --command sh \
  --args -lc 'exec uv run --with fastmcp --with httpx python /srv/apps/obsidian-writer-mcp/obsidian_writer_mcp.py'

docker exec -it hermes hermes -p <profile> mcp test obsidian-writer
```

If your `obsidian-writer` service is not reachable as `http://obsidian-writer:3000` from inside Hermes, change `OBSIDIAN_WRITER_BASE_URL` in `.env`.

## Tools

### Inbox

- `obsidian_append_inbox`: append one line to today's inbox file in the configured default vault.
- `obsidian_read_inbox`: read inbox items for a specific day. The `date` argument is optional; when omitted, `obsidian-writer` uses today's date.

### Lists

- `obsidian_list_lists`: list existing Obsidian checklist files.
- `obsidian_list_add`: add unchecked items to a named list.
- `obsidian_list_remove`: remove items from a named list.
- `obsidian_list_update`: update/rename items in a named list.

### Notes

- `obsidian_list_notes`: list existing notes.
- `obsidian_read_note`: read a note by slug.
- `obsidian_create_note`: create a new standalone Markdown note.
- `obsidian_append_note`: append Markdown content to an existing note.
- `obsidian_create_or_append_note`: create a note or append to an existing compatible note.

### Tasks

Task creation keeps its existing tool name: `obsidian_create_task`.

Task listing is available as `obsidian_list_tasks` and reads structured tasks through `GET /tasks`.

Creation sends structured JSON to `POST /tasks`; it does **not** format Obsidian Tasks Markdown itself. The canonical formatter is the `obsidian-writer` service.

Supported creation fields include:

- `vault`
- `title`
- `content` (legacy fallback for `title`)
- `status`: `todo`, `done`, `cancelled`
- `due`, `scheduled`, `start`, `done`, `cancelled`: dates as `YYYY-MM-DD`
- `priority`: `highest`, `high`, `medium`, `low`, `lowest`, `none`
- `recurrence`
- `tags`
- `source`
- `due_text` (legacy; ignored when `due` is provided)
- `depends_on` (accepted by the MCP for compatibility, but not sent yet)

Dependencies are reserved for phase 2 if/when `obsidian-writer` supports them.

Listing supports:

- `status`: `todo` (default), `done`, `cancelled`, or `all`
- `limit`: optional positive integer
- `vault`: optional vault override

## Examples

Read today's inbox:

```python
obsidian_read_inbox()
```

Read a specific inbox day:

```python
obsidian_read_inbox(date="2026-06-24")
```

Create a task using the configured default vault:

```python
obsidian_create_task(
    title="Pay electricity bill",
    due="2026-06-25",
    priority="high",
    tags=["home", "bills"],
    source="assistant",
)
```

List pending tasks:

```python
obsidian_list_tasks(status="todo", limit=20)
```

Create or append a note:

```python
obsidian_create_or_append_note(
    title="Project ideas",
    content="- Try a simpler publishing flow",
)
```

## Local development

Run the server over stdio:

```bash
uv run --with fastmcp --with httpx python obsidian_writer_mcp.py
```

The process will wait for MCP stdio messages; that is expected.

TDQS

A3.9/5.0

Scored across 13 tools

Disambiguation4/5

Most tools target distinct resources and actions (list operations vs note operations vs tasks vs inbox). The only potential confusion is among create_note, append_note, and create_or_append_note, but descriptions clarify their intended use cases.

Naming Consistency4/5

All tools use the obsidian_ prefix and snake_case. However, list operations use a noun-first pattern (list_add, list_remove) while others use verb-first (read_note, create_task), which is a minor structural inconsistency.

Tool Count5/5

13 tools is well within the ideal range for a domain-specific server focused on notes, lists, tasks, and inbox. Each tool has a clear role and contributes to the overall functionality.

Completeness3/5

Core workflows are covered (create/read/append notes, list management, tasks, inbox). Gaps include no delete for notes, no update/complete for tasks, and no explicit list creation, but these can often be worked around.

Maintenance

ActivityStale
ResponsivenessNo issues