> ## Documentation Index
> Fetch the complete documentation index at: https://linkly.ai/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Linkly AI MCP Tools Introduction

> Introduction to the tools Linkly AI provides for AI assistants

## Tools Overview

Linkly AI exposes **nine tools** to AI assistants via MCP (Model Context Protocol); the cloud gateway (`mcp.linkly.ai`, and `linkly mcp --remote`) adds two **cloud-only** tools — `library_search` and `library_link` — for eleven in total there. At the core is a progressive document access workflow:

```
search → grep or outline → read
```

Around it are three **discovery** helpers — `list_libraries` (list knowledge libraries), `explore` (overview of document collections), `find_paths` (locate folder paths by keyword to feed `search`'s `path_glob`) — plus `list` (enumerate a container: a folder's files, a library, or your notes) and the one **note** tool: `note_save` (create / edit a note).

<CardGroup cols={3}>
  <Card title="search" icon="magnifying-glass" iconType="duotone">
    Search documents and find relevant results
  </Card>

  <Card title="outline" icon="list-tree" iconType="duotone">
    View document outlines to understand structure
  </Card>

  <Card title="grep" icon="code" iconType="duotone">
    Find specific text patterns with regex matching
  </Card>

  <Card title="read" icon="book-open" iconType="duotone">
    Read document content for detailed information
  </Card>

  <Card title="list_libraries" icon="books" iconType="duotone">
    List knowledge libraries and their document counts
  </Card>

  <Card title="explore" icon="compass" iconType="duotone">
    Overview of document collection themes and structure
  </Card>

  <Card title="find_paths" icon="folder-magnifying-glass" iconType="duotone">
    Locate folder paths by keyword to feed `search`'s `path_glob`
  </Card>

  <Card title="list" icon="list" iconType="duotone">
    List the entries inside a container — a folder, a library, or your notes
  </Card>

  <Card title="note_save" icon="pen-to-square" iconType="duotone">
    Create or edit a note — **writes** (the only writer on a local connection)
  </Card>

  <Card title="library_search" icon="magnifying-glass-plus" iconType="duotone">
    Search the cloud library catalog, including libraries you haven't linked yet — **cloud gateway only**
  </Card>

  <Card title="library_link" icon="link" iconType="duotone">
    Link a cloud library so it becomes searchable, or unlink one you named (`action: "unlink"`) — **cloud gateway only, writes**
  </Card>
</CardGroup>

<Note>`library_search` and `library_link` exist only on the cloud gateway. `library_link` is the tool that writes there: it adds a cloud library to your account's linked set, or with `action: "unlink"` removes one you named. Everything else below reads only.</Note>

Every tool except `note_save` — and, on the cloud gateway, `library_link` — is **read-only**: they read your documents and never modify anything. `note_save` can only write into the notes folder, and there is **no delete tool**: deleting a note is something only you can do, in the app.

## Search

Searches indexed local documents and returns a list of the most relevant results.

### Parameters

| Parameter         | Type      | Required | Default    | Description                                                                                                                                                                                                                                                                                                                                                                   |
| ----------------- | --------- | -------- | ---------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `query`           | string    | Yes      | —          | Search keywords or phrase                                                                                                                                                                                                                                                                                                                                                     |
| `limit`           | number    | No       | 20         | Maximum number of results (1-50)                                                                                                                                                                                                                                                                                                                                              |
| `doc_types`       | string\[] | No       | All        | Filter by document type. Accepted values: `pdf`, `docx`, `doc`, `pptx`, `xlsx`, `csv`, `epub`, `rtf`, `md`, `txt`, `html`, `image`, `audio`, `video`                                                                                                                                                                                                                          |
| `library`         | string    | No       | —          | Restrict to a specific library. Local: a plain name or `local://<id>`; cloud: `cloud://<owner>/<slug>` (`--remote` only). Use `list_libraries` to see available libraries                                                                                                                                                                                                     |
| `path_glob`       | string    | No       | —          | Filter by file path. The pattern is **substring-matched** against the path — it may appear anywhere, no leading/trailing `*` needed. `*` matches any characters (including `/`), `?` a single character. Always case-sensitive. A full directory path (`/Users/me/notes/`) scopes to that directory. When the actual path is unknown, call `find_paths` first to discover it. |
| `scope`           | string    | No       | `folder`   | Search scope. `folder` (default) searches everything indexed, honouring the `library` / `path_glob` semantics above; `notes` searches only local notes and **ignores** `path_glob` and a local `library`; pairing it with a `cloud://` library is **rejected**, since notes have no cloud counterpart. Unknown values are rejected                                            |
| `tags`            | string\[] | No       | —          | Filter notes by tag, with **AND semantics** (a note must carry every listed tag). Tags are normalised (leading `#` stripped, ASCII lowercased). For OR, call once per tag and merge the results. Normally used together with `scope="notes"`                                                                                                                                  |
| `modified_after`  | string    | No       | —          | Inclusive lower bound on modification time. ISO 8601 UTC: bare date `2024-01-01` (treated as `00:00:00Z`) or full RFC 3339 `2024-01-01T00:00:00Z`                                                                                                                                                                                                                             |
| `modified_before` | string    | No       | —          | Inclusive upper bound on modification time. Same format as `modified_after`                                                                                                                                                                                                                                                                                                   |
| `time_sort`       | string    | No       | `default`  | Time-based reordering: `default` (preserves relevance order) / `newest` (most recent first) / `oldest` (earliest first). Reorders only after the candidate set is selected and deduplicated                                                                                                                                                                                   |
| `output_format`   | string    | No       | `markdown` | Set to `json` for structured JSON output                                                                                                                                                                                                                                                                                                                                      |

<Tip>If the vector model is still downloading, search will automatically fall back to keyword-only mode without affecting usability.</Tip>

**About time filtering and sorting:**

* When the user gives an explicit window ("last month", "in 2024", "in the last three months"), use `modified_after` / `modified_before`.
* When the user only says "recent", "latest", "earliest" without a fixed window, use `time_sort=newest` or `oldest`.
* The two can combine: "earliest in 2024" is `modified_after=2024-01-01` + `modified_before=2024-12-31` + `time_sort=oldest`.
* For relative dates ("last month"), first read the current UTC time from the `[meta] now=...` field at the end of any tool response, then compute the date — see [Response Metadata](#response-metadata) below.

### Response Fields

Each search result contains the following information:

| Field         | Description                                                  |
| ------------- | ------------------------------------------------------------ |
| `doc_id`      | Unique document identifier for subsequent outline/read calls |
| `title`       | Document title                                               |
| `path`        | File path                                                    |
| `relevance`   | Relevance score (0-1)                                        |
| `word_count`  | Document word count                                          |
| `total_lines` | Total number of lines in the document                        |
| `has_outline` | Whether an outline is available                              |
| `modified_at` | Last modified time                                           |
| `keywords`    | Extracted keyword list                                       |
| `snippet`     | Matching content snippet                                     |

### Usage Examples

```bash theme={null}
# CLI method
linkly search "project management best practices" --limit 10

# Filter by document type
linkly search "quarterly report" --type pdf,docx --json

# Search within a specific library
linkly search "deep learning" --library my-research --limit 10

# Filter by file path
linkly search "report" --path-glob "*2024*"

# Limit by time window (Q3 2024 quarterly reports)
linkly search "quarterly report" --modified-after 2024-07-01 --modified-before 2024-09-30

# Sort by time ("the latest", "the earliest" — when there's no fixed window)
linkly search "weekly retro" --time-sort newest --limit 5
```

## Outline

Retrieves the structured outline and metadata of one or more documents, helping to quickly understand document structure and locate target sections.

### Parameters

| Parameter       | Type      | Required | Default    | Description                                                                     |
| --------------- | --------- | -------- | ---------- | ------------------------------------------------------------------------------- |
| `doc_ids`       | string\[] | Yes      | —          | List of document IDs (from search results)                                      |
| `expand`        | string\[] | No       | Auto       | Node IDs to expand (e.g. `["2", "3.1"]`); omit to automatically show all levels |
| `output_format` | string    | No       | `markdown` | Set to `json` for structured JSON output                                        |

### When to Use Outline

| Scenario                           | Recommendation                                     |
| ---------------------------------- | -------------------------------------------------- |
| Document > 50 lines with outline   | View outline first, then read target sections      |
| Short document (\< 50 lines)       | Skip outline, `read` full text directly            |
| Document with `has_outline: false` | Use `grep` to find patterns or `read` page by page |

<Note>The outline feature works best with **bookmarked PDFs**, **Markdown**, **DOCX**, **DOC**, **PowerPoint (PPTX)**, **XLSX**, **CSV**, **EPUB**, and **RTF** documents. It is especially effective when reading lengthy documents and books. For spreadsheets the outline lists tables rather than headings: an XLSX workbook gets one entry per worksheet (with its row and column counts, header row, and a few preview rows), and a CSV file gets a single table summary entry. Outline support for plain text and unbookmarked PDFs will be added in future iterations.</Note>

### Usage Examples

```bash theme={null}
# View a single document's outline
linkly outline abc123

# View multiple documents at once
linkly outline id1 id2 id3

# JSON format output
linkly outline abc123 --json
```

## Grep

Locate specific lines within a single document by regex pattern. Best for documents with `has_outline=false` where outline is unavailable. Use after `search` to pinpoint exact positions of names, dates, terms, identifiers, or any pattern — then use `read` with offset to see full context. Works on all document types (PDF, Markdown, DOCX, DOC, PPTX, XLSX, CSV, EPUB, RTF, TXT, HTML). For searching across multiple documents, call grep once per document.

### Parameters

| Parameter          | Type    | Required | Default    | Description                                                                                                                                                                                                                          |
| ------------------ | ------- | -------- | ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `pattern`          | string  | Yes      | —          | Regular expression pattern to search for                                                                                                                                                                                             |
| `doc_id`           | string  | Yes      | —          | Document ID to search within (from search results)                                                                                                                                                                                   |
| `context`          | number  | No       | 3          | Lines of context before and after each match                                                                                                                                                                                         |
| `before`           | number  | No       | —          | Lines of context before each match (overrides `context`)                                                                                                                                                                             |
| `after`            | number  | No       | —          | Lines of context after each match (overrides `context`)                                                                                                                                                                              |
| `case_insensitive` | boolean | No       | false      | Case-insensitive matching                                                                                                                                                                                                            |
| `output_mode`      | string  | No       | `content`  | `content` (matching lines with context) or `count` (match count only, preview totals first)                                                                                                                                          |
| `limit`            | number  | No       | 20         | Maximum matching lines to return (max 100)                                                                                                                                                                                           |
| `offset`           | number  | No       | 0          | Number of matches to skip for pagination                                                                                                                                                                                             |
| `fuzzy_whitespace` | boolean | No       | Auto       | Whitespace-tolerant matching, for text extracted from PDFs where stray spaces and line breaks are everywhere. Omit it and the tool **decides automatically** (on for PDFs, off for other formats); pass `true` / `false` to force it |
| `output_format`    | string  | No       | `markdown` | Set to `json` for structured JSON output                                                                                                                                                                                             |

### When to Use Grep vs Outline

| Scenario                                          | Recommendation               |
| ------------------------------------------------- | ---------------------------- |
| Need to find a specific term, name, or date       | Use `grep` with the pattern  |
| Need to understand overall document structure     | Use `outline`                |
| Document has no outline (`has_outline: false`)    | Use `grep` to locate content |
| Looking for patterns (emails, IDs, numbers, etc.) | Use `grep` with regex        |

### Usage Examples

```bash theme={null}
# Find specific terms in a document
linkly grep "quarterly revenue" 456

# Case-insensitive search with context
linkly grep "error|warning" 1044 -C 3 -i

# Preview match count before reading
linkly grep "TODO" 591 --mode count
```

## Read

Reads document content with line number positioning and pagination, suitable for reading specific parts of long documents. The Read tool behaves consistently with the Claude AI SDK, ensuring optimal results across various Agentic AI models.

### Parameters

| Parameter       | Type   | Required | Default    | Description                                                                                                                      |
| --------------- | ------ | -------- | ---------- | -------------------------------------------------------------------------------------------------------------------------------- |
| `doc_id`        | string | Yes      | —          | Document ID (from search results)                                                                                                |
| `offset`        | number | No       | 1          | Starting line number (from 1)                                                                                                    |
| `limit`         | number | No       | 200        | Number of lines to read (max 500)                                                                                                |
| `image_text`    | string | No       | `abstract` | How much to attach for images referenced in the body — see [Reading documents with images](#reading-documents-with-images) below |
| `output_format` | string | No       | `markdown` | Set to `json` for structured JSON output                                                                                         |

### Reading documents with images

In many documents — Markdown notes and technical docs especially — the key information lives in the figures. `read` resolves the image references that appear **within the line range you are reading** into their corresponding indexed image documents and attaches them at the end of the result. `image_text` controls how much is attached:

| Value                | What gets attached                                    |
| -------------------- | ----------------------------------------------------- |
| `none`               | Just the mapping: line number, file, `doc_id`         |
| `abstract` (default) | Plus a one-line text excerpt and word count per image |
| `full`               | Plus the complete OCR text, inline                    |

`full` has a budget: 2,000 characters per image and 20,000 characters per call in total. Images that exceed the budget are automatically downgraded to `abstract`, along with guidance on how to read them individually.

<Tip>Use the default `abstract` to work out which figure matters, then `read` that one image's `doc_id` on its own. It's far cheaper than reaching for `full` up front.</Tip>

### Content Format

The `Read` tool returns content with line numbers for easy reference and positioning:

```
  1	# Project Requirements Document
  2
  3	## 1. Project Background
  4
  5	This project aims to build an efficient knowledge management system...
  6	Target users are enterprise R&D teams and individual knowledge workers.
```

### Pagination Strategy

For long documents, it is recommended to read in chunks:

```bash theme={null}
# Page 1: Lines 1-200
linkly read <DOC_ID> --offset 1 --limit 200

# Page 2: Lines 201-400
linkly read <DOC_ID> --offset 201 --limit 200

# Page 3: Lines 401-600
linkly read <DOC_ID> --offset 401 --limit 200
```

Combining with outlines yields even better results — use the outline to locate the line range of the target section, then use `read` to precisely retrieve the content within that range.

### Usage Examples

```bash theme={null}
# Read the beginning of a document
linkly read abc123

# Read a specific range
linkly read abc123 --offset 120 --limit 80

# JSON format (suitable for programmatic processing)
linkly read abc123 --json
```

## List Libraries

Lists the knowledge libraries that are searchable right now — local libraries and cloud libraries already linked — along with their descriptions and document counts. A cloud library you have not linked yet is not listed: use `library_search` to find it and `library_link` to link it.

### Parameters

No parameters required.

### Use Cases

* When the user asks "what libraries do I have?"
* Before using the `library` parameter in `search`, to verify a library name

```bash theme={null}
linkly list-libraries
```

## Search Libraries (library\_search)

<Note>Cloud gateway only — available on the `linkly-ai-cloud` MCP server and `linkly mcp --remote`. Local and LAN connections do not have this tool.</Note>

Searches the catalog of cloud knowledge libraries — including libraries you have **not** linked yet — by title, description or owner username. It finds *libraries*; to search *documents*, use `search`. To see what is already searchable, use `list_libraries`.

### Parameters

| Parameter       | Type   | Required | Default    | Description                                                                                                                                 |
| --------------- | ------ | -------- | ---------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
| `query`         | string | No       | —          | Keywords matched against title, description and owner username (case-insensitive substring). Omit to browse the catalog                     |
| `category`      | string | No       | All        | One of `ai-ml`, `programming`, `design`, `science`, `business`, `lifestyle`, `education`, `other`                                           |
| `owner`         | string | No       | All        | Exact owner username. The response starts with "You are signed in as @…" — pass that name to list your own libraries, private ones included |
| `limit`         | number | No       | 10         | Maximum entries per page (1–50)                                                                                                             |
| `offset`        | number | No       | 0          | Pagination offset; keep paging while `has_more` is true                                                                                     |
| `output_format` | string | No       | `markdown` | `markdown` or `json`                                                                                                                        |

### Response

Each entry carries the exact `cloud://<owner>/<slug>` to pass to other tools, plus title, description, owner, category, visibility, document / star / link counts, `is_owner`, `is_linked` (already searchable — no need to link again) and `can_link` (with `cannot_link_reason: invite_required` for a Showcase library you were not invited to). Public and Showcase libraries are always listed; Private libraries appear only to their owner and invited readers. Results are ordered by last update, newest first. The tool never links, stars or changes anything.

### Use Cases

* "Find a knowledge base about Rust and use it" → `library_search(query="Rust")` → pick a result → `library_link` → `search(query=…, library="cloud://…")`
* "Which cloud libraries do I own?" → `library_search(owner="<your username>")`

## Link Library (library\_link)

<Note>Cloud gateway only; one of the two tools that write (the other is `note_save`). The same tool unlinks with `action: "unlink"` — see below.</Note>

Links a cloud library to your account so it becomes searchable through MCP: it appears in `list_libraries` immediately and `search` / `explore` / `list` accept its `cloud://<owner>/<slug>` right away. Same rules as the **Link** button on the website.

### Parameters

| Parameter | Type   | Required | Description                                                                          |
| --------- | ------ | -------- | ------------------------------------------------------------------------------------ |
| `library` | string | Yes      | The full `cloud://<owner>/<slug>` reference, exactly as returned by `library_search` |
| `action`  | string | No       | `"link"` (default) or `"unlink"`; omit or send null to link                          |

### Rules

* **Public** libraries: anyone signed in can link. **Showcase** and **Private** libraries: only the owner and invited readers (the tool answers `invite_required`, or "not found" for a private library you cannot see).
* Linking the same library twice is harmless — it answers `already_linked` without using another Slot.
* Every link uses one **Slot** (Free: 1, Pro: 99). When the quota is full the tool answers `slot_exhausted` with the current count, the limit and an upgrade link. The assistant must not pick a library to unlink on its own: it lists your linked libraries and asks which one to release, then calls `library_link` with `action: "unlink"` and links again. You can also free a Slot on the website.

### Unlink (`action: "unlink"`)

Removes a cloud library from your account's linked set: it disappears from `list_libraries`, stops being searchable through MCP (for every client of your account), and its Slot is freed. Nothing else changes — your reader access, invitation and star stay, so the library can be linked again later. Same `library` parameter, exactly as listed by `list_libraries`.

* The assistant unlinks only a library **you named** — typically "replace A with B" when the Slot is full. If you have not named one, it lists your linked libraries and asks; it never picks one itself.
* Unlinking a library that is not linked is harmless — it answers `already_unlinked`. `not_found` means no such library is visible to your account.
* The response reports the Slots still used, so the assistant can link the library you actually wanted right after.

## Explore

Get a bird's-eye overview of all indexed documents or a specific library. Returns document type distribution, directory structure (with file counts and median word counts), and top keywords (with source attribution).

### Parameters

| Parameter | Type   | Required | Default | Description                                                                                                                                                   |
| --------- | ------ | -------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `library` | string | No       | —       | Restrict to a specific library. Local: a plain name or `local://<id>`; cloud: `cloud://<owner>/<slug>` (`--remote` only). Omit to explore all local documents |

### Use Cases

* The user wants to know what's in their knowledge base or document collection
* The user doesn't have a specific search topic and wants to discover available themes and directions
* The AI assistant needs to understand the scale and topic distribution to formulate effective search strategies

After exploring, use the keywords and directory names from the output as leads for subsequent `search` queries.

```bash theme={null}
# Explore all documents
linkly explore

# Explore a specific library
linkly explore --library my-research
```

## Find Paths (find\_paths)

Fuzzy-matches keywords against the **file path** field of indexed documents, aggregates matches at folder granularity, and returns the top folder candidates. It is positioned as a helper for `search`: when the user names a container ("in my Notion notes", "in my Dropbox papers folder") but you don't know its on-disk path, call `find_paths` first to discover the real path, then pass it as `path_glob` to `search`.

The actual folder name on disk often differs from the user's spoken name (e.g. an export might live under `Notion-Export-c58e430f...` rather than just `Notion`), so guessing a `path_glob` directly is fragile.

### Parameters

| Parameter       | Type      | Required | Default    | Description                                                                                                                                                                                                                                                                                                                            |
| --------------- | --------- | -------- | ---------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `patterns`      | string\[] | Yes      | —          | Array of keywords; each is wrapped internally as SQL `LIKE %keyword%` against the path. Multiple keywords are OR-matched, **so pass several variants in one call** (translation pairs, casings, real app/SDK identifiers when known), e.g. `["Notion", "notion", "notion-export"]`. Case-insensitive for ASCII; CJK matches literally. |
| `library`       | string    | No       | —          | Restrict to a specific library. Local: a plain name or `local://<id>`; cloud: `cloud://<owner>/<slug>` (`--remote` only). Use `list_libraries` to see available libraries                                                                                                                                                              |
| `limit`         | number    | No       | 10         | Maximum number of candidate folders (max 50)                                                                                                                                                                                                                                                                                           |
| `output_format` | string    | No       | `markdown` | Set to `json` for structured JSON output                                                                                                                                                                                                                                                                                               |

### Response Fields (JSON mode)

| Field         | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                       |
| ------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `total_files` | Total number of files aggregated into the returned candidates (before `limit` truncation)                                                                                                                                                                                                                                                                                                                                                                         |
| `truncated`   | Whether `limit` cut off the directory list (`true` means more candidates exist)                                                                                                                                                                                                                                                                                                                                                                                   |
| `directories` | Candidate folders, ordered by `file_count` descending. Each entry has `path` (the full absolute path), `path_glob` (the `path` quoted into a ready-to-use `path_glob` pattern: glob metacharacters `* ? [` in the folder name are escaped so it matches that folder literally — equals `path` when the name has no metacharacters; copy it verbatim into a follow-up `search` to scope to the whole folder), and `file_count` (matching files inside that folder) |

### Aggregation behaviour

* Files whose patterns only match the **filename segment** (no matching directory segment) are silently dropped — this is a "find folders" tool, not a "find files" tool. If a query yields zero candidate folders even though matching files exist, fall back to calling `search` directly.
* Each match is bucketed by the **shallowest** position of any pattern in the path, truncated at the next `/`. So `local:///Users/me/Documents/Notion-Export-abc/workspace/page.md` matched by `Notion` aggregates under `.../Documents/Notion-Export-abc`, regardless of how deep the file lives.

### When to use

* The user names a container with a fuzzy or cross-language word ("in my Notion notes", "in my Dropbox papers folder", "in my work backup") and you don't know the actual path
* Call before `search` to determine the right `path_glob`

### When not to use

* Pure content / topic queries ("find resumes", "find AI papers") — call `search` directly; its hybrid retrieval already covers title, filename, content, and path
* Filter by file type only ("all PDFs") — call `search` with `path_glob="*.pdf"` directly
* Vague queries with no container intent ("find recent stuff") — call `search`

### Usage example

```bash theme={null}
# User: "find shopping receipts in my Notion notes"
# Step 1: locate the real path
linkly find-paths --patterns Notion,notion --limit 5
# Suppose it returns .../Documents/Notion-Export-abc/workspace (1240 files)

# Step 2: search within that container
linkly search "shopping receipt" --path-glob "*Notion-Export*"
```

## List (list)

Lists the entries inside a container. It does **no full-text matching** — to find content by keyword or meaning, use `search`. Three container scopes are supported: `folder` (indexed files under a disk directory), `library` (one library's files), and `notes` (local card notes).

Tool boundaries: `explore` = global overview → `find_paths` = **find** a directory → `list` = **list** the files of a known container → `outline` / `read` = read content. Listing is a flat recursive sweep of the whole subtree — no directory tree is returned; drill down via the absolute paths in the entries, or via `find_paths`.

### Parameters

| Parameter         | Type      | Required              | Default   | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   |
| ----------------- | --------- | --------------------- | --------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `scope`           | string    | Yes                   | —         | The container to list. `folder`: indexed files under a disk directory (omit `path` to sweep all watched roots); `library`: one library's files (requires `library`); `notes`: local card notes. Unknown values are rejected. Careful: `search.scope` also has a value spelled `folder`, meaning "everything indexed" — a different concept; the two do not share values                                                                                                                                                                                                                                                                                                       |
| `library`         | string    | For `scope="library"` | —         | Which library to list. Local: a plain name or `local://<id>` (see `list_libraries`); cloud: `cloud://<owner>/<slug>` (`--remote` only)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |
| `path`            | string    | No                    | —         | Directory to list — an **address, not a pattern**: no globs; if you only know a fuzzy name, call `find_paths` first and copy its `path` field (**not** `path_glob`, whose escaping silently matches nothing here). Local (`folder` / local `library`): an absolute path — one that doesn't exist on disk or falls outside your watched folders is rejected with an error. When the library has a tag filter for Notes or Clips, paths inside that `Notes/` or `Clips/` directory are also accepted — they list only the documents the filter matched. Cloud library: a **relative directory prefix** (the form cloud `find_paths` returns); absolute paths are rejected there |
| `doc_types`       | string\[] | No                    | All       | Filter by document type (`folder` / `library` only): `pdf`, `docx`, `doc`, `pptx`, `xlsx`, `csv`, `epub`, `rtf`, `md`, `txt`, `html`, `image`, `audio`, `video`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               |
| `tags`            | string\[] | No                    | —         | Filter by tag, with **AND semantics** (`notes` only — other scopes reject it). A leading `#` is stripped and ASCII is lowercased, same as `search.tags`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       |
| `modified_after`  | string    | No                    | —         | Inclusive lower bound on modification time (`folder` / `library` only). ISO 8601 UTC: bare date `2024-01-01` or full RFC 3339                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |
| `modified_before` | string    | No                    | —         | Inclusive upper bound on modification time. Same format                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       |
| `sort`            | string    | No                    | `recent`  | `recent` (newest first — anchored on creation time for `notes`, modification time for `folder` / `library`, so a truncated page shows the **most recent** slice) / `oldest` (same anchor, earliest first) / `name` (filename A→Z). Every sort has a deterministic path tiebreaker, so pagination is stable                                                                                                                                                                                                                                                                                                                                                                    |
| `snippet`         | boolean   | No                    | Per scope | `notes`: `true` (roughly the first 200 characters of the body); `folder` / `library`: `false` (when enabled, the excerpt comes from the indexed abstract — no disk reads). While enabled, `limit` is capped at 50                                                                                                                                                                                                                                                                                                                                                                                                                                                             |
| `limit`           | number    | No                    | 50        | Maximum entries to return, capped at 200; the cap tightens to 50 while `snippet` is enabled                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   |
| `offset`          | number    | No                    | 0         | Skip the first N entries in sort order (for pagination); use `has_more` to decide whether to fetch the next page                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              |
| `output_format`   | string    | No                    | Per scope | `notes`: `json` (each entry is a CAS handle for `note_save`, which doesn't fit a compact line format); `folder` / `library`: `markdown`. Either can be requested explicitly                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   |

### Response Fields

**`folder` / `library` entries** carry: `doc_id`, `title`, absolute `path`, `doc_type`, `word_count`, `total_lines`, `has_outline`, `modified_at` (Unix milliseconds; the filesystem mtime), `keywords`, `snippet` (`null` unless snippets are enabled), and `skip_reason` — a non-null `skip_reason` means the content is not readable, so don't `read` / `grep` it. Use `total_lines` + `has_outline` to decide between `outline` and `read`. For local scopes `total` counts the whole filtered set; a cloud library may return `total: null` when the full count is unknown — either way, page with `offset` + `has_more`.

**`notes` entries** carry: `doc_id` (hand it to `read` / `grep` / `outline`), `note_id` and a live `version` (the two optimistic-locking credentials `note_save` needs when editing), `title`, absolute path, `created_at` / `modified_at` (Unix milliseconds), `tags`, source information, and the `snippet` attached by default. The response also includes `available_tags` — the 50 most frequently used tags across all your notes, ready to drop straight into the `tags` filter of the next call.

<Note>**README pointer**: when you list with an explicit `path` (scope `folder` or a local `library`) and a README-style file sits **directly** in that directory (not in a subdirectory), the response carries a top-level `readme` pointer. Cloud libraries never return one. When it's present and you need to understand what the folder is for, read that document first.</Note>

<Note>
  **Filesystem first** (notes): a note you just wrote shows up in the list immediately, but at that point its `doc_id` is `null` and `indexed` is `false` (word and line counts are still empty too) until indexing catches up. So "the note I just wrote is listed but not searchable" is expected behaviour, not a lost note.

  `title` can also be `null` — notes with machine-generated filenames have no usable title, so identify them by the excerpt, tags, and timestamps.
</Note>

### Cloud libraries

`folder` addresses **local** disk paths only. To list a cloud library, use `scope="library"` with `library="cloud://<owner>/<slug>"` (available with `--remote`), and pass `path` as a **relative** directory prefix — exactly the form cloud `find_paths` returns. The prefix applies across all of the library's sources; a prefix that doesn't exist can't be distinguished from an empty directory — on the first page (`offset=0`) both come back as `total: 0`, with a hint when a `path` was passed.

Cloud listing differs from local in a few ways: `sort="name"` is not supported, `skip_reason` is always `null`, snippets are capped at roughly 120 characters, and `total` may be `null` beyond the first page — paginate with `has_more`.

### Usage Examples

```bash theme={null}
# Most recent files across all watched roots
linkly list --scope folder --limit 10

# PDFs under one directory, modified this year
linkly list --scope folder --path /Users/me/Papers --type pdf --modified-after 2026-01-01

# One library's files
linkly list --scope library --library my-research

# Notes filtered by tag (default output: JSON with CAS handles)
linkly list --scope notes --tags project
```

## Save Note (note\_save)

Creates or edits a local Markdown card note. Together with `library_link` (cloud gateway only) this is one of **the two tools that write**, and it can only write into the notes folder. All YAML metadata is generated server-side, so callers don't need to deal with it.

### Parameters

| Parameter      | Type      | Required       | Description                                                                                                                                                                                                                                                                                                                              |
| -------------- | --------- | -------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `mode`         | string    | Yes            | `create` or `edit`                                                                                                                                                                                                                                                                                                                       |
| `content`      | string    | Yes            | Markdown body, **without YAML front matter**. When editing, pass the **complete** revised body                                                                                                                                                                                                                                           |
| `note_id`      | string    | Yes for `edit` | Note UUID, obtained from `list`                                                                                                                                                                                                                                                                                                          |
| `base_version` | string    | Yes for `edit` | The `version` you read, used for the optimistic-lock check                                                                                                                                                                                                                                                                               |
| `tags`         | string\[] | No             | Extra tags to **add** (the server appends the missing `#tokens` to the body). Cannot remove tags — delete the `#token` from `content` instead. Requires Desktop 0.11.0+; older Desktops require it on edit and replace the whole set                                                                                                     |
| `app_name`     | string    | No             | Display name of the **application** hosting the conversation (e.g. `ChatGPT`, `Cursor`), shown as the note's source badge in the app. It is the application name, not the model name — if only the model name is known, omit it. Over the cloud gateway on an OAuth connection the server fills this in automatically. Max 64 characters |

### Two rules you have to know

<AccordionGroup>
  <Accordion title="The body format is an allowlist, not free-form Markdown">
    This path only accepts the subset of Markdown that the editor's toolbar can produce: paragraphs and line breaks, bold, strikethrough, ordered and unordered lists, and plain text.

    **Headings, italics, blockquotes, code, links, tables, task lists, images, and raw HTML are rejected** with `NOTE_INVALID_INPUT`. Writing by hand in the app's editor is not subject to this restriction.

    Inline `#tags` in the body (outside code) **are** the note's tags — the body is the single source of truth, same as writing in the app's editor. Remove a tag by deleting its `#token`; the `tags` parameter can only add. Notes written by older versions with tags only in the YAML heal themselves: the first AI edit appends the missing `#tokens` to the body.
  </Accordion>

  <Accordion title="Editing must go through the optimistic-lock (CAS) loop">
    The correct edit sequence is:

    1. `list` (`scope="notes"`) to get `note_id` and `version`
    2. `read(doc_id)` to get the current **complete** body
    3. `note_save` with `mode="edit"`, `note_id`, `base_version` set to the version you just read, and the full revised body — keep the `#tag` tokens you want to keep, delete one to remove that tag

    If `base_version` is stale (the note changed in the meantime), you get `NOTE_VERSION_CONFLICT` along with the real version number — re-read, merge, and retry. **Do not blindly overwrite.**

    Every success response returns the note's effective `content` (the server may have appended `#tokens`) and its new `version` — base any follow-up edit on that returned content, never on what you sent. A note that hasn't been indexed yet has a `null` `doc_id`, so this is also how you edit a note you just created. **Never rewrite a whole note from its excerpt alone.**
  </Accordion>
</AccordionGroup>

<Warning>**There is no delete tool.** Notes can only be deleted by the user, in the app.</Warning>

## Response Metadata

Every successful tool response carries the current UTC time so callers can compute relative dates ("last month", "this year", "in the last 30 days") without relying on the model's training cutoff.

* **Markdown output**: a footer block at the end of the response, formatted as:

  ```
  ---
  [meta] now=2026-05-08T14:43:14Z
  ```

* **JSON output**: a top-level `_meta` object:

  ```json theme={null}
  { ..., "_meta": { "now": "2026-05-08T14:43:14Z" } }
  ```

Error responses (`isError: true`) **do not** include this metadata — the error body itself already conveys the cause, and adding a timestamp would only dilute the signal.

When the user uses a relative date, read `now` from the most recent tool response, compute the corresponding ISO 8601 date, and pass it to `search`'s `modified_after` / `modified_before`.

## Workflow Examples

### Complete Workflow: CLI Method

The following example demonstrates how to perform a complete document retrieval via CLI:

```bash theme={null}
# Step 1: Search for relevant documents
linkly search "microservice architecture design" --limit 5

# Step 2: View the target document's outline (assuming doc_id is abc123)
linkly outline abc123

# Step 3: Read the section of interest (assuming the target is at lines 80-150)
linkly read abc123 --offset 80 --limit 70
```

### Complete Workflow: MCP Method

When AI assistants call tools via the MCP protocol, the request format is as follows:

```json theme={null}
// Step 1: Search
{
  "method": "tools/call",
  "params": {
    "name": "search",
    "arguments": {
      "query": "microservice architecture design",
      "limit": 5
    }
  }
}

// Step 2: View outline
{
  "method": "tools/call",
  "params": {
    "name": "outline",
    "arguments": {
      "doc_ids": ["abc123"]
    }
  }
}

// Step 3: Read content
{
  "method": "tools/call",
  "params": {
    "name": "read",
    "arguments": {
      "doc_id": "abc123",
      "offset": 80,
      "limit": 70
    }
  }
}
```

## FAQ

<AccordionGroup>
  <Accordion title="What document formats are supported?">
    Linkly AI currently supports the following formats:

    | Format                | Extensions                                      | Outline Support |
    | --------------------- | ----------------------------------------------- | --------------- |
    | Markdown              | `.md`, `.mdx`                                   | Yes             |
    | Word                  | `.docx`                                         | Yes             |
    | Word 97-2003          | `.doc`                                          | Yes             |
    | RTF                   | `.rtf`                                          | Yes             |
    | PowerPoint            | `.pptx`                                         | Yes             |
    | EPUB                  | `.epub`                                         | Yes             |
    | Excel                 | `.xlsx`                                         | Yes             |
    | CSV                   | `.csv`                                          | Yes             |
    | PDF                   | `.pdf`                                          | Partial         |
    | Plain Text            | `.txt`                                          | No              |
    | HTML                  | `.html`                                         | Partial         |
    | Image (OCR)           | `.png`, `.jpg`, `.jpeg`, `.bmp`, `.webp`        | No              |
    | Audio (transcription) | `.mp3`, `.wav`, `.m4a`, `.flac`, `.aac`, `.ogg` | No              |
    | Video (transcription) | `.mp4`, `.mov`, `.mkv`, `.webm`                 | No              |

    **Excel and CSV files are parsed into GitHub-flavored Markdown tables and indexed by cell text**, so `search` and `grep` match the words inside individual cells. Outlines differ by format: an `.xlsx` workbook gets one entry per worksheet (with its row and column counts, header row, and a few preview rows), while a `.csv` file gets a single table summary entry. Only `.xlsx` and `.csv` are supported — the older `.xls` and the `.xlsm`, `.xlsb` and `.ods` variants are not. A CSV that is not UTF-8 is decoded only when its encoding can be determined from the file itself or from its neighbors in the same directory; when the encoding cannot be determined the file is skipped rather than indexed as garbled text. Both formats have a 16 MiB per-file limit.

    **Speech transcription for audio and video is off by default** — these files still get registered in the index (so filename search finds them), but to search *what was said inside them* you first need to turn on **Audio transcription** and **Video transcription** under **Settings → Index**. See [Index Settings](/docs/en/indexing) for details.
  </Accordion>

  <Accordion title="What if an outline is not available?">
    If a document has no available outline (`has_outline: false`), you can:

    1. Use the `read` tool directly to browse the document content page by page
    2. Read the beginning of the document first (default 200 lines) to get a general idea, then decide whether to continue reading
  </Accordion>

  <Accordion title="How to handle long documents?">
    Recommended workflow:

    1. First use `outline` to understand the document structure (if an outline is available)
    2. Based on the line ranges in the outline, use the `offset` and `limit` parameters of `read` to precisely read target sections
    3. Read up to 500 lines at a time, and paginate by adjusting `offset`
  </Accordion>

  <Accordion title="What is the default port for the MCP service?">
    The default port is **60606**. If that port is occupied, the application will automatically try other ports. You can check the actual port in use in Linkly AI Desktop's settings.
  </Accordion>

  <Accordion title="What if search results are inaccurate?">
    You can try:

    * Using more precise keywords
    * Using natural language descriptions (leveraging vector semantic matching)
    * Mixing keywords and synonyms, e.g. `"authentication auth login sign-in"`
    * Using `--type` to filter specific document types and narrow the search scope
  </Accordion>
</AccordionGroup>
