Skip to main content
Glama
o-gent

linkwarden-mcp

by o-gent
README.md
# linkwarden-mcp

An [MCP](https://modelcontextprotocol.io) server that wraps the
[Linkwarden](https://linkwarden.app) bookmark API in a small set of token-frugal
tools. Tool responses are compact plain text (`#id  name :: url`) so listing
collections or links costs as few tokens as possible.

## Tools

| Tool | What it does |
| --- | --- |
| `list_collections` | List collections as `#id  name (count)`. |
| `list_links` | List links as `#id  name :: url`; optional `collection_id`, `limit`. |
| `find_link` | Check if a URL already exists (tracking params stripped first). |
| `add_link` | Add a link: strips tracking params, skips duplicates, then moves it into a collection. |
| `move_link` | Move an existing link to another collection. |
| `delete_link` | Delete a link by id. |
| `create_collection` | Create a new collection. |

### Behaviour baked in from experience

- **Tracking params are stripped** on add (`utm_*`, `fbclid`, `gclid`, `gbraid`,
  `gad_source`, `gad_campaignid`, `rcm`, …); meaningful params like `variant` and
  `model` are kept.
- **Two-step create.** Linkwarden ignores `collectionId` on link creation, so
  `add_link` POSTs the link then PUTs it into the target collection.
- **Correct PUT shape.** Updates always send `id`, `url`, `collection.{id,ownerId}`
  and `tags`, which the API requires.
- **Full-list dedup.** Linkwarden caps page size at ~50, so dedup walks every page
  via cursor pagination rather than trusting a single `limit`.

## Configuration

Configuration comes from environment variables — no secret is ever hardcoded:

| Variable | Required | Default | Notes |
| --- | --- | --- | --- |
| `LINKWARDEN_URL` | no | `http://links.lan` | Base URL, no `/api/v1`. |
| `LINKWARDEN_TOKEN` | **yes** | — | API token (Settings → Access Tokens). Secret. |
| `LINKWARDEN_OWNER_ID` | no | `1` | Owner id used when moving links. |

**How the token is stored:** in production it is supplied by the MCP host via the
server config's `env` block (see below) — it is not read from disk. For local
development you may instead copy `.env.example` to `.env` and fill in the token;
`.env` is git-ignored and loaded automatically. Host-provided env vars take
precedence over `.env`.

## Run

```bash
uv sync
uv run linkwarden-mcp   # serves over stdio
```

## Register with an MCP host

Claude Code:

```bash
claude mcp add linkwarden -e LINKWARDEN_URL=http://links.lan -e LINKWARDEN_TOKEN=your-token -- uv --directory C:/Users/olive/Github/linkwarden-mcp run linkwarden-mcp
```

Or Claude Desktop (`claude_desktop_config.json`):

```json
{
  "mcpServers": {
    "linkwarden": {
      "command": "uv",
      "args": ["--directory", "C:/Users/olive/Github/linkwarden-mcp", "run", "linkwarden-mcp"],
      "env": {
        "LINKWARDEN_URL": "http://links.lan",
        "LINKWARDEN_TOKEN": "your-token"
      }
    }
  }
}
```

## Development

```bash
uv run ruff check .   # lint
uv run ruff format .  # format
uv run ty check       # type check
```

TDQS

A3.7/5.0

Scored across 7 tools

Disambiguation5/5

Each tool has a clear, distinct purpose: add, delete, find, move links; create and list collections; list links. There is no overlap or ambiguity.

Naming Consistency5/5

All tool names follow a consistent verb_noun pattern in snake_case (e.g., add_link, list_collections), making it easy for an agent to infer functionality.

Tool Count5/5

With 7 tools, the set is well-scoped. It covers core operations for a link management service without being too sparse or bloated.

Completeness4/5

The set covers primary CRUD operations for links (add, delete, move, find) and collection management (create, list). Missing update operations for links/collections and deletion of collections are minor gaps.

Maintenance

ActivityStale
ResponsivenessNo issues