Skip to main content
Glama
flumpiey

linkwarden-mcp

linkwarden-mcp

MCP server for self-hosted Linkwarden: ask your AI about bookmarks, collections, and tags.

License: MIT Python MCP CI PyPI

What is Linkwarden?

Linkwarden is a self-hosted, open-source bookmark manager. You collect, organize, annotate, and preserve webpages in one place, with full-page archives so content stays readable after the original page disappears. It also supports collaboration and public sharing.

This project wires the Linkwarden HTTP API into the Model Context Protocol so Cursor, Claude, VS Code Copilot, and other MCP hosts can query your live library in natural language.

Useful Linkwarden links:

Related MCP server: ai-bookmark-mcp

What this server does

Default is read-only. You get:

  • 18 read tools - discovery (list_resources), six core reads (search, get, preserved content, collections, tags, overview), and eleven triage/hygiene workflows

  • Task tools (opt-in) - intent-shaped writes such as save_link, smart_save_link, organise_links, create_collection, apply_triage_plan (register when matching write scopes are set)

  • Delete tools (opt-in) - delete_links, delete_tags, merge_tags, delete_collection (register only under delete scopes; never implied by write)

  • Hard denylist - tokens, session, auth, user admin (except GET /api/v1/users/me), migration, and whole-instance preservation stay blocked even when writes are on

Transport is stdio. No HTTP server. No global install required if you use uv / uvx.

Branding / icons

Four surfaces (keep them in sync when the mark changes):

  1. stdio hosts (Cursor, Claude Desktop via mcp.json): serverInfo.icons from server_icons() — embedded data URI from src/linkwarden_mcp/assets/icon.png, plus HTTPS fallback docs/icon-512.png (https://raw.githubusercontent.com/flumpiey/linkwarden-mcp/main/docs/icon-512.png). website_url is https://linkwarden.app/.

  2. Cursor plugin: .cursor-plugin/plugin.json logodocs/linkwarden-icon.svg.

  3. Claude Desktop Extension: mcpb/icon.png (packed with npx @anthropic-ai/mcpb pack mcpb).

  4. Claude.ai remote connectors: Claude.ai ignores serverInfo.icons and uses the root-domain favicon of the connector URL. If you host a remote MCP later, serve docs/favicon.ico at the registrable domain root (e.g. https://acme.com/favicon.ico for https://mcp.acme.com/...).

server.json registry metadata also points its icons[0].src at the same raw docs/icon-512.png URL.

Requirements

  • Python ≥ 3.10 (pulled in automatically by uvx)

  • uv (provides uvx)

  • A reachable Linkwarden instance: LINKWARDEN_API_URL + LINKWARDEN_API_KEY

Access token

  1. Sign in to your Linkwarden instance (self-hosted or Cloud).

  2. Open Settings → Access Tokens (or go to /settings/access-tokens).

  3. Create a New Access Token, give it a name, and copy the value into LINKWARDEN_API_KEY.

  4. Set LINKWARDEN_API_URL to your instance base URL (usually without /api/v1; include /api/v1 only if your deployment requires it), e.g. https://links.example.com or local Docker http://127.0.0.1:3000.

linkwarden-mcp sends the token as Authorization: Bearer …. API overview: API Introduction.

Copy .env.example to .env for local runs — never commit .env. Prefer the Cursor plugin Configure UI for credentials, or a secret manager in production.

Quick start

Run the PyPI package with uvx:

uvx linkwarden-mcp

Paste a client config below, set LINKWARDEN_API_URL / LINKWARDEN_API_KEY, restart the host, then ask: “Find my unread bookmarks about Python” or “What's in my Dev collection?”

From a git clone (dev): uvx --from git+https://github.com/flumpiey/linkwarden-mcp linkwarden-mcp or uv run --directory /path/to/linkwarden-mcp linkwarden-mcp.

Installation

Configs below pull linkwarden-mcp from PyPI. Leave write-scope env vars unset for read-only.

Plugin (Configure UI for URL, key, and scopes): this repo is a Cursor plugin via .cursor-plugin/plugin.json + root mcp.json.

  1. Symlink or copy the clone to ~/.cursor/plugins/local/linkwarden-mcp (Windows: %USERPROFILE%\.cursor\plugins\local\linkwarden-mcp).

    • macOS / Linux: ln -s /path/to/linkwarden-mcp ~/.cursor/plugins/local/linkwarden-mcp

    • Windows: Cursor does not follow symlinks for local plugins. Use a junction or copy instead:

      mklink /J "%USERPROFILE%\.cursor\plugins\local\linkwarden-mcp" "E:\Development\linkwarden-mcp"

      or:

      robocopy "E:\Development\linkwarden-mcp" "%USERPROFILE%\.cursor\plugins\local\linkwarden-mcp" /E
  2. Reload the window.

  3. Open Plugins → Configure on linkwarden-mcp. Set Linkwarden API URL and Linkwarden API key. Leave Write scopes / Delete scopes empty for read-only, or paste a CSV such as links,collections.

  4. Confirm the linkwarden MCP server is enabled under Customize / MCP.

Marketplace listing is a separate submit at cursor.com/marketplace/publish.

Manual mcp.json: project .cursor/mcp.json or user-wide ~/.cursor/mcp.json. Root mcp.json is plugin wiring with ${…} placeholders only — never commit real secrets there.

From PyPI:

{
  "mcpServers": {
    "linkwarden": {
      "type": "stdio",
      "command": "uvx",
      "args": ["linkwarden-mcp"],
      "env": {
        "LINKWARDEN_API_URL": "https://links.example.com",
        "LINKWARDEN_API_KEY": "your-token"
      }
    }
  }
}

Local editable (dev):

{
  "mcpServers": {
    "linkwarden": {
      "type": "stdio",
      "command": "uv",
      "args": ["run", "--directory", "/path/to/linkwarden-mcp", "linkwarden-mcp"],
      "env": {
        "LINKWARDEN_API_URL": "https://links.example.com",
        "LINKWARDEN_API_KEY": "your-token"
      }
    }
  }
}

Optional scoped writes in the env block:

"LINKWARDEN_MCP_WRITE_SCOPES": "links,collections",
"LINKWARDEN_MCP_DELETE_SCOPES": "links"

Restart Cursor after saving. Confirm linkwarden under MCP settings.

Desktop Extension (.mcpb): download mcpb.mcpb from GitHub Releases. Use v0.1.5+ (needs uv on PATH). Launch is uv tool run --python 3.12 linkwarden-mcp. Do not put the PyPI package in mcpb/pyproject.toml dependencies — Claude Desktop syncs that file at install and can fail on system Python 3.13.

  1. Open Claude Desktop → Settings → Extensions.

  2. Open Advanced settingsInstall Extension…

  3. Select mcpb.mcpb. Review permissions, enter Linkwarden API URL and Linkwarden API key, then click Install.

  4. Leave Write scopes and Delete scopes empty for read-only.

  5. Restart Claude Desktop if tools do not appear.

Build your own bundle from a clone:

npx @anthropic-ai/mcpb pack mcpb

On Windows, double-click often does nothing and dragging the file into chat attaches it to the conversation instead of installing it. Use Install Extension… in Settings.

Manual claude_desktop_config.json fallback: edit the Claude Desktop config, then restart the app.

OS

Path

macOS

~/Library/Application Support/Claude/claude_desktop_config.json

Windows

%APPDATA%\Claude\claude_desktop_config.json

{
  "mcpServers": {
    "linkwarden": {
      "command": "uvx",
      "args": ["linkwarden-mcp"],
      "env": {
        "LINKWARDEN_API_URL": "https://links.example.com",
        "LINKWARDEN_API_KEY": "your-token"
      }
    }
  }
}

Local clone:

{
  "mcpServers": {
    "linkwarden": {
      "command": "uv",
      "args": ["run", "--directory", "/path/to/linkwarden-mcp", "linkwarden-mcp"],
      "env": {
        "LINKWARDEN_API_URL": "https://links.example.com",
        "LINKWARDEN_API_KEY": "your-token"
      }
    }
  }
}

Add via CLI:

claude mcp add linkwarden --env LINKWARDEN_API_URL=https://links.example.com --env LINKWARDEN_API_KEY=your-token -- uvx linkwarden-mcp

Or edit ~/.claude.json / project MCP config:

{
  "mcpServers": {
    "linkwarden": {
      "command": "uvx",
      "args": ["linkwarden-mcp"],
      "env": {
        "LINKWARDEN_API_URL": "https://links.example.com",
        "LINKWARDEN_API_KEY": "your-token"
      }
    }
  }
}

Create .vscode/mcp.json in the project root:

{
  "servers": {
    "linkwarden": {
      "type": "stdio",
      "command": "uvx",
      "args": ["linkwarden-mcp"],
      "env": {
        "LINKWARDEN_API_URL": "https://links.example.com",
        "LINKWARDEN_API_KEY": "your-token"
      }
    }
  }
}

Local editable:

{
  "servers": {
    "linkwarden": {
      "type": "stdio",
      "command": "uv",
      "args": ["run", "--directory", "/path/to/linkwarden-mcp", "linkwarden-mcp"],
      "env": {
        "LINKWARDEN_API_URL": "https://links.example.com",
        "LINKWARDEN_API_KEY": "your-token"
      }
    }
  }
}

Reload the window. Open Copilot Chat and confirm the linkwarden tools are available.

Edit ~/.codeium/windsurf/mcp_config.json (macOS/Linux) or the Windsurf MCP settings UI:

{
  "mcpServers": {
    "linkwarden": {
      "command": "uvx",
      "args": ["linkwarden-mcp"],
      "env": {
        "LINKWARDEN_API_URL": "https://links.example.com",
        "LINKWARDEN_API_KEY": "your-token"
      }
    }
  }
}

Restart Windsurf after saving.

Add under context_servers in Zed settings.json (Agent Panel → settings also works):

{
  "context_servers": {
    "linkwarden": {
      "command": "uvx",
      "args": ["linkwarden-mcp"],
      "env": {
        "LINKWARDEN_API_URL": "https://links.example.com",
        "LINKWARDEN_API_KEY": "your-token"
      }
    }
  }
}

Edit the Cline MCP settings file (cline_mcp_settings.json via the Cline MCP UI):

{
  "mcpServers": {
    "linkwarden": {
      "command": "uvx",
      "args": ["linkwarden-mcp"],
      "env": {
        "LINKWARDEN_API_URL": "https://links.example.com",
        "LINKWARDEN_API_KEY": "your-token"
      }
    }
  }
}

In .continue/config.yaml:

mcpServers:
  - name: linkwarden
    command: uvx
    args:
      - linkwarden-mcp
    env:
      LINKWARDEN_API_URL: https://links.example.com
      LINKWARDEN_API_KEY: your-token

Any host that can spawn a stdio MCP server:

Field

Value

Command

uvx

Args

linkwarden-mcp

Env

LINKWARDEN_API_URL, LINKWARDEN_API_KEY (+ optional write scopes)

uvx linkwarden-mcp

Dev from a clone: uv run --directory /path/to/linkwarden-mcp linkwarden-mcp.

npx only runs npm packages. This is a Python package; use uvx.

Environment

Variable

Required

Notes

LINKWARDEN_API_URL

yes

Base URL (include /api/v1 only if required; typical: https://links.example.com)

LINKWARDEN_API_KEY

yes

Access token from Settings → Access Tokens; sent as Authorization: Bearer; never logged

LINKWARDEN_MCP_WRITE_SCOPES

no

Comma-separated domains for create/update. Empty = no writes.

LINKWARDEN_MCP_DELETE_SCOPES

no

Comma-separated domains for delete only. Never implied by WRITE_SCOPES.

LINKWARDEN_MAX_BULK

no

Max records per bulk op (default 25)

TEST_LINKWARDEN_API_URL

integration only

Live sandbox URL for pytest -m integration

TEST_LINKWARDEN_API_KEY

integration only

Live sandbox token for pytest -m integration

Valid scopes: links, collections, tags, raw. No wildcards (*, all). raw expands effective scopes to all domain scopes (escape hatch).

Recommended (covers most bookmark workflows without every mutating tool):

"LINKWARDEN_MCP_WRITE_SCOPES": "links,collections",
"LINKWARDEN_MCP_DELETE_SCOPES": "links"

Default with no scopes: 18 tools. All three domain scopes in WRITE and DELETE: 31 tools.

Legacy LINKWARDEN_MCP_ALLOW_WRITES / ALLOW_WRITES / LINKWARDEN_MCP_WRITES hard-fail if set. Use the scoped vars instead.

MCP host env (.cursor/mcp.json or Cursor plugin Configure) must match process env / .env or scope behavior drifts.

See .env.example. Never commit .env. Prefer Cursor plugin Configure UI or a secret manager in production.

Write scopes and task tools

When a scope is listed in LINKWARDEN_MCP_WRITE_SCOPES, the server registers task tools for that domain. LINKWARDEN_MCP_DELETE_SCOPES enables delete/merge tools per domain. Call list_resources to inspect read_only, scope lists, and the live boundary string.

Tool

Scopes

Purpose

save_link

WRITE links

Save a URL into a collection (by name)

smart_save_link

WRITE links

Save with optional heuristic collection/tags

organise_links

WRITE links

Move or retag multiple links

update_link

WRITE links

Update link fields (read-modify-write)

queue_archive

WRITE links

Queue preservation (async; not immediate)

apply_triage_plan

WRITE links

Apply [{link_id, collection?, tags?}]; default dry_run=true

bulk_sort_by_rules

WRITE links

Match domain_pattern rules then organise; default dry_run=true

create_collection

WRITE collections

Create a collection (optional parent)

auto_tag_by_domain

WRITE links + tags

Apply domain→tag rules; default dry_run=true

delete_links

DELETE links

Delete multiple links

delete_tags

DELETE tags

Delete tags by id or name

merge_tags

DELETE tags

Merge tags into a new name (destructive)

delete_collection

DELETE collections

Delete a collection after user chooses delete/move/cancel for its links (elicitation or on_links)

Example with recommended scopes only:

"LINKWARDEN_MCP_WRITE_SCOPES": "links,collections",
"LINKWARDEN_MCP_DELETE_SCOPES": "links"

Denylist (always blocked): /api/v1/tokens, /api/v1/session, /api/v1/auth, /api/v1/users/** (except GET /api/v1/users/me), migration, and whole-instance preservation worker actions.

Tools

Read tools

Always registered (18 total).

Tool

Purpose

list_resources

Discovery; reports read_only + live write/delete scopes

search_links

Search by query, collection, tag, or pin status

get_link

Full metadata for one link

read_link_content

Preserved plain text (textContent or archive fallback)

list_collections

Collections with link counts

list_tags

Tags with link counts

get_library_overview

Totals, empty collections, unused tags

suggest_collection_for_url

Heuristic collection suggestions for a URL

suggest_tags_for_link

Suggest existing-library tags (never invents names)

find_unsorted_links

List unsorted links (default collection: Unorganized)

triage_links

Propose collection/tags for link ids (no writes)

find_duplicate_links

Group links with the same normalized URL

recommend_collection_for_links

Consensus collection for a batch of links

suggest_links_for_collection

Find links elsewhere that likely belong

analyze_collection_overlap

Compare two collections for shared domains/tags/URLs

suggest_collection_structure

Hygiene: empty, near-duplicate names, overcrowded

align_tags_with_similar_links

Tags used on similar-domain links

get_sorting_dashboard

One-shot triage: unsorted, duplicates, empty, largest

Write tools

Registered only when matching scopes are set (see table above). Prefer smart_save_link / triage tools over raw field edits when you are sorting an inbox.

Pattern

Requires

Notes

Link create/update/organise/archive

WRITE links

Includes workflow writers with dry_run defaults

Collection create

WRITE collections

Optional parent by name

Domain auto-tag

WRITE links + tags

Only existing tag names

Deletes / tag merge

matching DELETE scope

Destructive; confirm ids first

Agent Skill

Companion skill: skills/linkwarden-bookmarks/SKILL.md.

The Cursor plugin discovers this skill from skills/. Without the plugin, copy or symlink that folder into your agent skills path. It tells the model to call list_resources first, verify after writes, and which workflow tools to prefer.

Development

uv sync --extra dev
npm install          # installs lefthook + commitlint; registers git hooks
uv run linkwarden-mcp

Offline tests only (respx). No live Linkwarden required:

uv run ruff check src tests
uv run pytest

Git hooks (lefthook)

Hook / command

What it runs

pre-commit

ruff check + full pytest (mocked suite)

commit-msg

commitlint Conventional Commits

npm run pre-publish

ensure build/twine → ruff → pytest → sdist contents → python -m buildtwine checknpx @anthropic-ai/mcpb pack mcpb

Commit messages must follow Conventional Commits, e.g. feat(api): add delete_links tool.

Run the local publish gate before tagging a release:

npm run pre-publish

GitHub Actions matrix: Python 3.10 and 3.12.

Caveats

  • One process ↔ one LINKWARDEN_API_URL. Multi-instance routing is out of scope.

  • Multi-user / team disambiguation on a shared instance is unverified. Do not claim multi-tenant support until validated against a live shared library.

  • Collection/tag suggestions are heuristic and library-local; they do not invent new tag names.

  • Bulk mutating workflows default to dry_run=true; set dry_run=false only after you review the plan.

  • ChatGPT Apps need a hosted HTTP MCP endpoint. This package is stdio-only.

License

MIT. See LICENSE.

A
license - permissive license
-
quality - not tested
A
maintenance

Maintenance

Maintainers
Response time
0dRelease cycle
5Releases (12mo)
Commit activity

Related MCP Servers

View all related MCP servers

Related MCP Connectors

  • MCP server for AgentDocs (agentdocs.eu): read, search, write, comment on & share Markdown docs.

  • Official remote MCP server for Archivist AI TTRPG campaign memory: characters, sessions, and more.

  • Agent-native MCP server over the public saagarpatel.dev corpus. Read-only, stateless.

View all MCP Connectors

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/flumpiey/linkwarden-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server