
Security News
Ruby's Bundler 4.0.18 Extends Cooldown to bundle lock and bundle cache
The supply chain control that delays freshly published gems now covers lockfile generation and gem vendoring in Ruby projects.
linkwarden-mcp
Advanced tools
MCP server for Linkwarden bookmarks: read-first search and preserved content, with opt-in write, delete, and collection-delete tools.
MCP server for self-hosted Linkwarden: ask your AI about bookmarks, collections, and tags.
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:
Default is read-only. You get:
list_resources), six core reads (search, get, preserved content, collections, tags, overview), and eleven triage/hygiene workflowssave_link, smart_save_link, organise_links, create_collection, apply_triage_plan (register when matching write scopes are set)delete_links, delete_tags, merge_tags, delete_collection (register only under delete scopes; never implied by write)GET /api/v1/users/me), migration, and whole-instance preservation stay blocked even when writes are onTransport is stdio. No HTTP server. No global install required if you use uv / uvx.
Four surfaces (keep them in sync when the mark changes):
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/..cursor-plugin/plugin.json logo → docs/linkwarden-icon.svg.mcpb/icon.png (packed with npx @anthropic-ai/mcpb pack mcpb).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.
uvx)uvx)LINKWARDEN_API_URL + LINKWARDEN_API_KEY/settings/access-tokens).LINKWARDEN_API_KEY.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.
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.
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.
~/.cursor/plugins/local/linkwarden-mcp (Windows: %USERPROFILE%\.cursor\plugins\local\linkwarden-mcp).
ln -s /path/to/linkwarden-mcp ~/.cursor/plugins/local/linkwarden-mcpmklink /J "%USERPROFILE%\.cursor\plugins\local\linkwarden-mcp" "E:\Development\linkwarden-mcp"
or:
robocopy "E:\Development\linkwarden-mcp" "%USERPROFILE%\.cursor\plugins\local\linkwarden-mcp" /E
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.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.
mcpb.mcpb. Review permissions, enter Linkwarden API URL and Linkwarden API key, then click Install.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.
| 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.
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.
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 |
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 |
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.
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
| 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 build → twine check → npx @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.
LINKWARDEN_API_URL. Multi-instance routing is out of scope.dry_run=true; set dry_run=false only after you review the plan.MIT. See LICENSE.
FAQs
MCP server for Linkwarden bookmarks: read-first search and preserved content, with opt-in write, delete, and collection-delete tools.
We found that linkwarden-mcp demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
The supply chain control that delays freshly published gems now covers lockfile generation and gem vendoring in Ruby projects.

Security News
During a UK cyber test, a Mythos 5 agent used sockpuppets, social engineering, and prompt injection to try to get a maintainer to merge malware.

Company News
Socket is now in the AWS Security Hub Extended plan. Adopt it through AWS, apply committed spend, and block malicious open source packages.