Skip to main content
Glama
README.md
# notes-mcp

A [Model Context Protocol](https://modelcontextprotocol.io) server for [Nextcloud Notes](https://github.com/nextcloud/notes) — exposes notes, categories and app settings to Claude and any MCP-compatible client.

## How it works

The server speaks one API: the Notes REST API at `/index.php/apps/notes/api/v1`.

Unlike most Nextcloud apps this is not an OCS endpoint — it returns bare JSON with conventional HTTP status codes, and note bodies travel inline in the `content` field. There is no WebDAV leg, so no path arithmetic and no file locking to contend with.

Details and the behaviours that are not in the published API docs are in [ENDPOINTS.md](ENDPOINTS.md).

## Tools exposed (12)

- **Notes:** `list_notes`, `get_note`, `create_note`, `update_note`, `append_to_note`, `delete_note`
- **Categories:** `list_categories`, `set_note_category`, `rename_category`
- **Settings:** `get_settings`, `update_settings`
- **Other:** `ping`

Every tool declares MCP annotations (`readOnlyHint`, `destructiveHint`, `idempotentHint`, `openWorldHint`), so clients can distinguish a read from an irreversible delete without parsing descriptions.

### Concurrency

`get_note` returns the note's `etag`. Passing it back to `update_note` makes the write conditional: if the note changed on the server in the meantime, the write is refused and the error carries the server's current copy of the note, so a caller can merge and retry without a second round-trip. `append_to_note` does this internally.

### Categories

A category is a folder under the notes folder, named by each note's `category` field and nested with `/`. Two consequences shape the tools:

- The server's `category` filter is an exact string comparison, so `list_notes` takes `recursive` to include subcategories such as `work/clients` under `work`.
- There is no category endpoint and no server-side rename. `list_categories` derives the list from the notes themselves, so a category holding no notes does not appear. `rename_category` rewrites every affected note individually and reports the per-note outcome, because the operation is not atomic.

## Install

There is no published npm package. Install the release tarball, which puts the `notes-mcp` command on your `PATH`:

```bash
# Download notes-mcp-<version>.tgz from the latest release, then:
npm install -g ./notes-mcp-<version>.tgz
```

The asset is attached to each [release](https://github.com/megamaced/nc_notes-mcp/releases/latest).

To build it yourself instead, either pack the same tarball:

```bash
corepack pnpm install
corepack pnpm pack:tarball
npm install -g ./notes-mcp-<version>.tgz
```

or skip the global install and point the client at the built entry point:

```bash
corepack pnpm install
corepack pnpm build
```

## Configuration

Add to your MCP client config (Claude Code shown). After a global install:

```json
{
  "mcpServers": {
    "notes": {
      "command": "notes-mcp",
      "args": [],
      "env": {
        "NEXTCLOUD_URL": "https://your-nextcloud.example.com",
        "NEXTCLOUD_USER": "your-username",
        "NEXTCLOUD_APP_PASSWORD": "xxxx-xxxx-xxxx-xxxx-xxxx"
      }
    }
  }
}
```

Or, running from the build directory, with an absolute path to `dist/index.js`:

```json
{
  "mcpServers": {
    "notes": {
      "command": "node",
      "args": ["/absolute/path/to/notes-mcp/dist/index.js"],
      "env": {
        "NEXTCLOUD_URL": "https://your-nextcloud.example.com",
        "NEXTCLOUD_USER": "your-username",
        "NEXTCLOUD_APP_PASSWORD": "xxxx-xxxx-xxxx-xxxx-xxxx"
      }
    }
  }
}
```

**Generate the app-password** in Nextcloud under Settings > Security > Devices & sessions > "Create new app password". The MCP server only needs an app-password, never your real account password — and you can revoke it without affecting your main login.

## Development

```bash
corepack pnpm install
corepack pnpm dev        # stdio MCP server, point mcp inspector at it
corepack pnpm test       # deterministic unit tests, no Nextcloud required
corepack pnpm lint
corepack pnpm typecheck
corepack pnpm build      # tsc -> dist/
```

Required env vars: `NEXTCLOUD_URL`, `NEXTCLOUD_USER`, `NEXTCLOUD_APP_PASSWORD`.

Optional:

| Variable | Default | Purpose |
| --- | --- | --- |
| `NEXTCLOUD_TIMEOUT_MS` | `60000` | Per-request deadline. Must be a whole number of milliseconds, at most 2147483647. |
| `NEXTCLOUD_MAX_RESPONSE_BYTES` | `10485760` | Largest response body buffered. Raise it for very large notes. |
| `DEBUG` | unset | Log each request to stderr. |

## Disclosure

This project was 100% written by AI (Claude), including all source code, tests, CI configuration, and documentation.

## License

MIT — see [LICENSE](LICENSE).

## Related

- [Nextcloud Notes](https://github.com/nextcloud/notes) and its [API reference](https://github.com/nextcloud/notes/blob/main/docs/api/v1.md)
- [nc_collectives-mcp](https://github.com/megamaced/nc_collectives-mcp) — the same approach for Nextcloud Collectives
- [Model Context Protocol](https://modelcontextprotocol.io)

TDQS

A4/5.0

Scored across 12 tools

Disambiguation5/5

Each tool has a clearly distinct purpose: ping/get_settings read connectivity and settings; list_notes/get_note retrieve; create/update/append/delete modify; category tools manage categorization. The append vs update distinction is explicitly clarified in descriptions, and set_note_category vs rename_category are well-separated.

Naming Consistency4/5

Most tools follow a consistent verb_noun snake_case pattern (get_note, create_note, update_note, delete_note, list_notes, etc.). Minor deviations like 'ping' and 'get_settings' break the resource_verb pattern slightly but remain readable and predictable.

Tool Count5/5

12 tools is well-scoped for a notes server covering CRUD, settings, and category management. Each tool earns its place with no redundancy; the count sits comfortably in the ideal 3-15 range.

Completeness4/5

Full note lifecycle (create/read/update/append/delete) and category management are covered, plus settings and connectivity checks. Minor gap: no restore_note despite delete going to trash, and list_categories excludes empty categories, but these are documented limitations rather than dead ends.

Maintenance

ActivityMaintained
ResponsivenessResponsive