---
title: Tools
description: The three Qaf MCP tools, search, fetch and lookup_catalog, with every input, limit, default and output field, and the errors they return.
---

The Qaf MCP server has three tools. They only read the library: none of them changes anything or reads your account. Each result comes back twice, as structured content and as the same JSON in a text block, so apps that read either one get the full result.

Every tool takes `locale`, `en` (the default) or `ar`, which picks the language of book and author names. Passage text stays Arabic.

## `search`

Searches the library with hybrid semantic and keyword retrieval, then reranks the matches for relevance. Returns passages with their book, author, printed pages and a link to the page. API equivalent: [`POST /v1/search`](/reference/passages/search-passages).

| Input | Type | Limits and default | Description |
| --- | --- | --- | --- |
| `query` | string | Required. 1–2,000 characters after trimming. | What to search for, in Arabic or English. |
| `filters` | object | Optional. Set at least one field. | Restricts results by the passage's book and author, not by what the passage mentions. |
| `filters.authorIds` | integer[] | 1–20 IDs | Author IDs from `lookup_catalog` or a passage's `author.id`. |
| `filters.bookIds` | string[] | 1–20 UUIDs | Book IDs from `lookup_catalog` or a passage's `book.id`. |
| `filters.deathYearAH` | object | At least one of `gt`, `gte`, `lt`, `lte`, as integers | The author's death year in AH. All bounds must hold, and together they must allow at least one year. Leaves out authors with no recorded death year. |
| `filters.genres` | string[] | 1–20 slugs | [Genre slugs](/library#genres). A genre describes a book, not a topic. |
| `filterOperator` | `"AND"` or `"OR"` | Default `"AND"` | How the filter fields combine: `AND` requires every one, `OR` accepts any. The IDs within one list always combine with OR. |
| `limit` | integer | 1–15, default 5 | Passages to return. |
| `excludeIds` | string[] | Up to 200 UUIDs | Passages to skip. Pass the IDs you already have to get further passages for the same query. |
| `locale` | `"en"` or `"ar"` | Default `"en"` | Language of book and author names. |

**Output:** `{ "results": [passage, …] }`, best match first. Each passage has the fields described in [The Qaf library](/library#passages).

**Errors the app can correct:** an author or book ID that isn't in the catalog, or filters that no book can match, such as a genre with a death-year range none of its authors fall in. The tool explains the problem so the app can fix its call.

## `fetch`

Reads a passage by ID, with up to `radius` neighbouring passages on each side, in reading order. Neighbours carry their own IDs, so reading can continue from either end. API equivalent: [`GET /v1/passages/{id}`](/reference/passages/get-passage).

| Input | Type | Limits and default | Description |
| --- | --- | --- | --- |
| `id` | string | Required. A UUID. | A passage ID from `search` or `fetch`. |
| `radius` | integer | 0–5, default 0 | Neighbouring passages to include on each side. |
| `locale` | `"en"` or `"ar"` | Default `"en"` | Language of book and author names. |

**Output:** the passage's fields, plus `before` and `after`: arrays of passages in reading order. Neighbours come from the same edition, so a passage near the start or end of a book has fewer.

**Errors:** an ID with no passage: `No passage has ID <id>; use an ID returned by search or fetch.`

## `lookup_catalog`

Resolves an author or book name to catalog IDs for search filters. Returns up to five candidates, each with a match score. API equivalent: [`GET /v1/catalog/lookup`](/reference/catalog/lookup-catalog).

| Input | Type | Limits and default | Description |
| --- | --- | --- | --- |
| `type` | `"author"` or `"book"` | Required | Whether the name is an author or a book. |
| `query` | string | Required. 1–200 characters after trimming. | The name to resolve, in Arabic or English. |
| `locale` | `"en"` or `"ar"` | Default `"en"` | Language of the candidate labels. |

**Output:** `{ "candidates": [candidate, …] }`. Each candidate has a `type`:

| Field | Author | Book | Description |
| --- | --- | --- | --- |
| `id` | integer | UUID | The ID that `filters.authorIds` or `filters.bookIds` takes. |
| `label` | yes | yes | The author's name or the book's title, in `locale`. |
| `authorId`, `authorLabel` | | yes | The book's author. |
| `deathYearAH` | yes | yes | The author's death year in AH, or `null` when unknown. |
| `deathInexactLabel` | yes | yes | A label for an approximate or unknown death year, or `null`. |
| `categoryId` | | yes | The book's [genre ID](/library#genres). |
| `matchScore` | yes | yes | How closely the name matches the query, from 0 to 1. |

When several candidates are close, the app should choose by the question's context or ask you which one you mean.

## Errors

A tool that fails returns an error result instead of output:

- **Invalid input**, such as a `limit` of 20: the message names the field and the rule.
- **Input the app can correct**, such as an unknown author ID: the message explains what to change.
- **Anything else**, such as a call that runs past 30 seconds: `Qaf could not complete this request. Try again shortly.`
