Skip to main content
Glama
dagistankaradeniz

quillink-mcp

README.md
# quillink-mcp

An [MCP](https://modelcontextprotocol.io) server exposing **read-only** access to your [Quillink](https://note-taking-app-prod.web.app) notes, folders, and tags to AI tools like Claude Desktop, Claude Code, and other MCP-compatible clients.

Requires a **Pro** Quillink plan (API Access is a Pro-only feature).

Not published to PyPI -- build and run it locally from source.

## Build

```bash
git clone https://github.com/dagistankaradeniz/gcp-note-taking-mcp.git
cd gcp-note-taking-mcp
python3 -m venv .venv
.venv/bin/pip install -e .
```

This installs the `quillink-mcp` command into `.venv/bin/`. Note the full
path to that binary (e.g. `/Users/you/gcp-note-taking-mcp/.venv/bin/quillink-mcp`)
-- MCP clients launch it directly, not through an activated shell, so
they need the absolute path.

**macOS:** if you clone the repo under `~/Documents`, `~/Desktop`, or
`~/Downloads`, Claude Desktop may fail to launch the server with
"Server disconnected" -- macOS privacy protection (TCC) blocks its child
processes from reading those folders. Either grant Claude Desktop access
to the folder (System Settings -> Privacy & Security -> Files and
Folders), or build the venv outside the protected folders:

```bash
python3 -m venv ~/.local/share/quillink-mcp-venv
~/.local/share/quillink-mcp-venv/bin/pip install /path/to/gcp-note-taking-mcp
# then point your MCP client at ~/.local/share/quillink-mcp-venv/bin/quillink-mcp
```

## Authenticate

Two options:

**Option A — OAuth device login (recommended for interactive use):**

```bash
.venv/bin/quillink-mcp login
```

Opens a device code + verification URL; approve it in your browser. The token is stored in your OS keyring (or `~/.config/quillink-mcp/credential` as a fallback).

Requires a registered OAuth client id for this tool -- if `login` fails with an "Unknown client_id" error, set `QUILLINK_CLIENT_ID` to a registered client id (see Configuration below), or use a PAT instead.

**Option B — Personal Access Token:**

Create one in Quillink under **Settings → Developer → Tokens** (scopes: `notes:read`, `folders:read`, `tags:read`, `organizations:read`), then set it as an environment variable:

```bash
export QUILLINK_TOKEN=qlk_pat_...
```

`QUILLINK_TOKEN` always takes priority over a stored OAuth login.

## Use with Claude Desktop

Add to your Claude Desktop config (`~/Library/Application Support/Claude/claude_desktop_config.json` on macOS):

```json
{
  "mcpServers": {
    "quillink": {
      "command": "/absolute/path/to/gcp-note-taking-mcp/.venv/bin/quillink-mcp"
    }
  }
}
```

If using a PAT instead of `login`, pass it as an env var in the config:

```json
{
  "mcpServers": {
    "quillink": {
      "command": "/absolute/path/to/gcp-note-taking-mcp/.venv/bin/quillink-mcp",
      "env": { "QUILLINK_TOKEN": "qlk_pat_..." }
    }
  }
}
```

Restart Claude Desktop after editing the config.

## Use with other MCP clients

Any client that can launch a local stdio MCP server works the same way: point it at the absolute path to `.venv/bin/quillink-mcp` (with `QUILLINK_TOKEN` set, or after running `quillink-mcp login` once so the credential is already stored). This includes Claude Code (`claude mcp add quillink -- /absolute/path/to/.venv/bin/quillink-mcp`) and any OpenAI-compatible agent runtime that supports the MCP stdio transport.

## Tools

All read-only — this server cannot create, edit, or delete anything.

| Tool | Description |
|---|---|
| `list_notes` | List notes, filterable by folder, status, tags (any, or all with `tag_match`), pinned |
| `get_note` | Get a single note by id |
| `search_notes` | Full-text search over title + body, optionally within tags |
| `get_note_stats` | Note count, storage used, pinned count |
| `get_note_backlinks` | Notes that link to a given note |
| `get_related_notes` | Tag/link-similarity "you might want to link these" suggestions for a note (Pro plan) |
| `get_note_graph` | Local link graph (1 or 2 hops) centered on a note |
| `get_global_note_graph` | Whole-account link graph, paginated (Pro plan) |
| `get_graph_analysis` | Community detection + centrality ranking over the whole-account graph (Pro plan) |
| `export_note` | A single note as a standalone Markdown file |
| `export_notes` | Bulk export of active notes as NDJSON or concatenated Markdown, for backups/scripting |
| `list_attachments` | List a note's file attachments |
| `get_attachment_download_url` | A short-lived signed URL to download one attachment's file bytes |
| `list_note_recipients` | Who a note has been shared with |
| `list_shared_notes` | Notes shared with you by others |
| `list_folders` | List notebooks/folders |
| `get_folder` | Get a single folder by id |
| `list_tags` | List all tags in use |
| `list_tag_counts` | Every tag with how many notes and canvases carry it |
| `get_organization` | Your Team-plan organization's name, plan tier, seats, alerts |
| `list_organization_members` | Your organization's members (plain members see only their own entry) |

Vault notes are never accessible here — they're end-to-end encrypted client-side, so no server (including this one) can read them. Organization tools 404 for a caller who isn't on a Team-plan organization.

## Security notes

- **Read-only.** The server only requests read scopes and has no tools that create, change or delete anything.
- **Note content is untrusted input.** A note -- especially one shared with you or imported -- can contain text written to steer an AI assistant ("prompt injection"), e.g. asking it to send your notes somewhere through another tool. The server tells clients to treat note content as data, not instructions, but that's a request to the model, not a guarantee. Keep tool approval on in your MCP client when other tools (email, web, file write) are enabled alongside this one.
- **HTTPS only.** `QUILLINK_API_BASE` must be `https://` (plain `http://` is allowed for `localhost` only).
- **Token storage.** The OS keychain, or a `0600` file under `~/.config/quillink-mcp/` when no keychain is available.

## Configuration (environment variables)

| Variable | Purpose |
|---|---|
| `QUILLINK_TOKEN` | A Personal Access Token — bypasses OAuth login entirely |
| `QUILLINK_API_BASE` | Override the API base URL (default: production) |
| `QUILLINK_CLIENT_ID` | Override the OAuth client id used by `login` |
| `QUILLINK_WORKSPACE` | Use a named workspace's api-base/client-id/credential instead (see below) |

## Workspaces (multiple environments/accounts)

This server is a single long-running process, launched once per MCP client config entry — there's no per-call "switch workspace" the way the CLI has `--workspace`. To use several environments or accounts, register **one server entry per workspace**, each pinned to a different `QUILLINK_WORKSPACE`:

```bash
# One-time: define the workspaces with the CLI (gcp-note-taking-cli)
quillink workspace add staging --api-base https://staging.example.com
quillink workspace login staging

# Point this server at it
QUILLINK_WORKSPACE=staging .venv/bin/quillink-mcp login
```

Then in your MCP client config, add a second server entry (e.g. `quillink-staging`) alongside your existing one, with `"env": {"QUILLINK_WORKSPACE": "staging"}` — both can be active at once.

Workspace metadata (api_base/client_id, no secrets) lives in `~/.config/quillink/workspaces.json` — the same file `gcp-note-taking-cli`'s `quillink workspace add/use` manages, so a workspace defined once via the CLI is immediately visible here. This server keeps its own separate keyring entry per workspace for the actual token, though, so you still need to `login` (or set `QUILLINK_TOKEN`) once per workspace here too. `QUILLINK_TOKEN`/`QUILLINK_API_BASE`/`QUILLINK_CLIENT_ID` set directly always take precedence over `QUILLINK_WORKSPACE`.

## Running it directly (for testing)

```bash
.venv/bin/quillink-mcp login
.venv/bin/quillink-mcp   # runs the server on stdio -- an MCP client normally launches this for you
```

TDQS

A3.5/5.0

Scored across 21 tools

Disambiguation4/5

Most tools are clearly scoped by resource and operation, but the graph family (get_note_backlinks, get_related_notes, get_note_graph, get_global_note_graph, get_graph_analysis) and the tag pair (list_tags vs list_tag_counts) have overlapping boundaries that could cause misselection without careful reading. The descriptions mostly disambiguate them, so only minor confusion remains.

Naming Consistency5/5

All names use snake_case with a consistent verb_noun shape (get_*, list_*, search_*, export_*). No mixed conventions or vague verbs; prefixes consistently signal the operation type.

Tool Count3/5

21 tools is on the heavy side for a note-app integration, especially with separate local/global graph tools and overlapping tag-listing variants. The count is not extreme, but consolidation would improve scoping.

Completeness3/5

Read coverage is broad across notes, folders, tags, sharing, graphs, export, attachments, and organization data. However, there are no create, update, delete, move, or share operations, leaving common note lifecycle workflows as dead ends.

Maintenance

ActivityMaintained
ResponsivenessNo issues