Skip to main content

Documentation Functions

SereneDB ships its own documentation inside the server binary, so the reference material always matches the running version and is available with no network access. Two tables and two table functions expose it:

ObjectDescription
sdb_docs.docsOne row per documentation page or section.
sdb_docs.objectsOne row per documented object, such as a function or a data type.
sdb_docs.search(query[, max_hits])Full-text search, ranked with BM25.
sdb_docs.object(name[, kind])The documentation of an object, looked up by name.

sdb_docs.docs and sdb_docs.objects are not catalog tables. They are read straight out of the index compiled into the binary, so they do not appear in information_schema, and nothing can be created in, altered in or granted on sdb_docs. The schema name itself is reserved: CREATE SCHEMA sdb_docs raises an error.

The same tables and functions work in psql, DBeaver, Grafana and anything else that speaks SQL, and in serened shell and serened psql, which also render the documentation with the .docs command. Every surface runs the same search code over the same index, so a query returns the same pages in the same order wherever it runs.

Documentation paths​

Every row is addressed by a path. A page indexed as a whole is addressed by its file name, and a page split into sections is addressed by its file name followed by the chain of headings that leads to the section:

Text
sql/indexes/index.md#Indexes
sql/functions/timestamp.md#Timestamp_Functions#Scalar_Timestamp_Functions#date_trunc(part,_timestamp)

Spaces in a heading become underscores and a literal # in a heading is escaped as \#, so the unescaped # characters count the heading depth, which sdb_docs.docs also returns as depth. Paths returned by sdb_docs.search(), sdb_docs.object() and sdb_docs.objects address rows of sdb_docs.docs:

sql
SELECT content FROM sdb_docs.docs WHERE path = 'sql/indexes/index.md#Indexes';

A condition on path with =, IN or starts_with() reads only the matching rows out of the index. Any other condition reads the whole corpus first, which is cheap for the titles and noticeably slower once content or markdown is selected.

The object catalog​

sdb_docs.objects presents the documentation as a catalog of objects rather than a set of pages. Each row names one documented thing and says what kind of thing it is, which is what makes a name lookup unambiguous: create is documented as a client method and as part of several statements, but it is not a SQL function, and the catalog says so.

KindContents
functionScalar, aggregate, window and table functions.
statementSQL statements such as CREATE TABLE.
tokenizerThe dictionary templates with a page under CREATE TEXT SEARCH DICTIONARY: keyword, pipeline, sql and union. Analysis templates such as split_text are functions.
typeData types, with their aliases.
settingConfiguration options.
index_typeIndex access methods: inverted and art.
commandDot commands of the shell such as .timer, with the bare word as an alias.
SQL
SELECT kind, count(*) FROM sdb_docs.objects GROUP BY kind ORDER BY count(*) DESC;

Objects are read out of the documentation two ways. A heading whose text is a call signature — #### ts_phrase(text, ...) — becomes a row on its own. So does a row of a reference table on one of the pages that carry them, whose first column is headed Function, Aggregate, Name or Index — which is how the function tables, the data type table, the configuration reference and the index list are indexed. A function documented both ways yields a single row, keeping its own section's path. A table that also carries an Aliases or Alias column fills in the aliases column, so alternative spellings resolve to the same object:

sql
SELECT name, signature, aliases
FROM sdb_docs.objects
WHERE kind = 'type' AND name = 'BIGINT';

The catalog is read out once, when the documentation index is built, and ships inside that index, so querying sdb_docs.objects costs about as much as reading a small table.

Because a single page can document one function in both a heading and a table, and a single table can list several overloads, a name repeats. The row is identified by (kind, name, path, signature); group by name when you want one line per object.

Reference​

sdb_docs.docs​

The whole corpus, one row per page or section. Query it like any table; the paths section says which conditions read only matching rows.

ColumnDescriptionType
pathThe documentation path.TEXT
titleThe page or heading title.TEXT
breadcrumbThe chain of headings above this row.TEXT
depthThe number of headings in path: 0 for a whole page, 1 for the title row of a split page.INTEGER
contentThe Markdown source as plain Markdown: site components are removed and callouts such as :::note become block quotes that open with their label.TEXT
content_textThe same body with Markdown markup removed.TEXT
markdowncontent opening with the row's title as a heading, once: a body that already starts with one keeps it. This is what .docs renders.TEXT

Read one row by its path:

SQL
SELECT content FROM sdb_docs.docs WHERE path = 'sql/indexes/index.md#Indexes';

List the rows under a prefix. A directory prefix lists the pages beneath it and a page or section path lists the sections beneath that; add depth <= 1 to list only pages, meaning whole pages and the title rows of split pages:

SQL
SELECT path, title FROM sdb_docs.docs WHERE starts_with(path, 'sql/functions/search/') ORDER BY path;

Render a row for a terminal with sdb_md_to_ansi(), described below:

SQL
SELECT sdb_md_to_ansi(markdown, 100, false, path)FROM sdb_docs.docs WHERE path = 'sql/indexes/index.md#Indexes#Index_Types';

sdb_docs.objects​

One row per documented object. name is the bare identifier, signature is the full form as documented, and path addresses the documentation for that object. Overloads appear as separate rows sharing a name.

SQL
SELECT name, signature, summaryFROM sdb_docs.objectsWHERE kind = 'function' AND name = 'date_trunc'ORDER BY path;
ColumnDescriptionType
kindThe kind of object, from the table above.TEXT
nameThe bare identifier, for example date_trunc or DECIMAL.TEXT
signatureThe documented form, for example date_trunc(part, timestamp).TEXT
summaryA single-line description, or NULL when the documentation has no lead paragraph.TEXT
aliasesAlternative names, for data types and settings that have them.TEXT
pathThe documentation path, a row of sdb_docs.docs.TEXT
pageThe file part of path.TEXT
categoryFor functions, the documentation page they are grouped under.TEXT
breadcrumbThe chain of headings above the object.TEXT

Filter it with an ordinary WHERE clause:

SQL
SELECT name, summary FROM sdb_docs.objects WHERE kind = 'tokenizer' ORDER BY name;
SQL
SELECT name, aliases FROM sdb_docs.objects WHERE kind = 'type' AND aliases IS NOT NULL ORDER BY name;

sdb_docs.search(query[, max_hits])​

Full-text search over titles, breadcrumbs and body text, ranked with BM25. Returns path, title, breadcrumb, a snippet of up to 400 characters of the body text, ending in … when it is cut, and the score, best match first. Without max_hits, or with max_hits set to NULL, every match is returned.

query takes plain text: keywords, a question or a pasted error message. Every word is matched against every field. Rows that contain the exact sentence rank first, and each word also reaches its other forms at a lower weight, so index finds indexes. When query is exactly the name or alias of a cataloged object, that object comes first. A query with no words at all, such as @@, matches its characters literally.

Lucene syntax, the same syntax to_tsquery accepts, takes over when the query uses it:

SyntaxExampleMatches
"phrase""inverted index"The words adjacent, in order
+required+vacuum indexRows that must contain vacuum
prefix*tokeniz*tokenizer, tokenizing, tokenize
term~Nvacum~1Within N edits, so typos still match
AND OR ( )(bm25 OR tfidf) AND rankBoolean combinations
path:path:"configuration/limits.md"Rows at that exact path

A query that uses Lucene syntax but does not parse is searched as plain words instead.

Exclusion (-term or NOT) is rejected: the documentation is searched as three separate fields, and an exclusion would only apply to whichever field it matched in, which silently returns rows it was meant to remove.

SQL
SELECT path, title, breadcrumb, score FROM sdb_docs.search('inverted index options', 5);

sdb_docs.object(name[, kind])​

Finds the objects of the catalog documented under name, matching the name or one of its aliases without regard to case, so INT8 finds BIGINT. One name can belong to several kinds at once, a function and a data type for example, and every one of them is returned. When no object carries the name, the documentation rows whose title is name or name applied to arguments are returned instead.

With kind, the lookup is restricted to one kind of object, for when a name is both a function and a data type and only one of them is wanted. It then never falls back to titles, so a name with no object of that kind returns no rows: SELECT path, title FROM sdb_docs.object('uuid', 'type').

SQL
SELECT path, title, breadcrumb FROM sdb_docs.object('date_trunc');
ColumnDescriptionType
pathThe documentation path, a row of sdb_docs.docs.TEXT
titleThe object's signature, or the title of a row matched by its title.TEXT
breadcrumbThe chain of headings above the object.TEXT
kindThe kind of object, or NULL for a row matched by its title.TEXT

sdb_md_to_ansi(markdown, width, color, base_path)​

Renders Markdown for a terminal: Markdown structure becomes layout rather than syntax, prose is wrapped to width, SQL examples are highlighted, and tables are drawn with their columns aligned. Pass a row's markdown and path to render documentation, or any Markdown held in your own tables.

ArgumentDescription
markdownThe Markdown to render.
widthColumn budget for wrapping. 0 means do not wrap, which is what a client that wraps for itself should pass.
colortrue emits ANSI escape sequences; false emits plain text.
base_pathThe documentation path the Markdown came from, used to resolve relative links.

width, color and base_path accept NULL and fall back to 80, colored, and no link resolution. A negative width is an error.

Code blocks are indented rather than wrapped, so a long SQL line stays intact and the terminal soft-wraps it. Links keep their label, and a link to another documentation page is followed by the resolved path so it can be looked up in sdb_docs.docs; links to anywhere else show their label alone. Nested lists keep their indentation, and table cells keep their formatting but show a link's label alone so the columns stay narrow.

SQL
SELECT sdb_md_to_ansi('See [precision](../../sql/functions/datepart.md).', 72, false,                      'sql/functions/timestamp.md');

Reading documentation in psql​

content and rendered output contain newlines, which psql boxes into its aligned output by default. Switch to unaligned, untabulated output to read it:

sql
\pset format unaligned
\pset tuples_only on
\pset pager always
SELECT sdb_md_to_ansi(markdown, 100, true, path)
FROM sdb_docs.docs WHERE path = 'sql/indexes/index.md#Indexes';

Pipe the result through a pager that understands escape sequences, such as less -R, when color is true.

Plain output for other clients​

DBeaver, Grafana and the HTTP API do not interpret ANSI escape sequences, so pass color => false there. What those clients gain from sdb_md_to_ansi() is the layout: wrapped prose, aligned tables and no Markdown syntax. Pass width => 0 when the client wraps for itself.

Documentation for AI agents over MCP​

An HTTP listener with ?api=mcp serves this documentation to AI agents over the Model Context Protocol. The endpoint is POST /_mcp. It speaks JSON-RPC 2.0 and accepts protocol versions 2025-06-18, 2025-03-26 and 2024-11-05.

bash
serened ./data --listen 'postgres://127.0.0.1:7890,http://127.0.0.1:8080?api=mcp'

--listen takes every endpoint in one comma-separated value. A second --listen replaces the first, so keep the PostgreSQL endpoint in the same list.

Point your agent at http://127.0.0.1:8080/_mcp. Auth works like every other HTTP API: Basic auth against the catalog roles. Every request needs the header, from the local machine too, and the endpoint answers 401 without it. A role without a password only works from the local machine. To register the endpoint with Claude Code:

bash
claude mcp add --transport http serenedb http://127.0.0.1:8080/_mcp \
--header "Authorization: Basic $(printf 'postgres:' | base64)"

The first five tools read the tables and functions above, and check_sql checks your own SQL against the server:

ToolArgumentsReturns
search_docsquery, optional limit (default 5, at most 10)Ranked sections with a path and a snippet, for keywords, a question or pasted text
read_docpathOne page or section as Markdown. Links in it become paths read_doc takes back, and a very long page is cut after listing its sections
list_docsoptional prefixpath - title lines: the pages under a directory or the sections under a page
list_objectsoptional kindOne line per object with its signature, kind and summary
describe_objectname, optional kindEverything documented under a name or an alias: the section about it, or its summary when a reference table row documents it. A function or setting the docs miss comes from the server's own catalog
check_sqlsqlThe plan of one statement or the server's error with its hint

check_sql only plans the statement with EXPLAIN and never runs it. It refuses more than one statement and EXPLAIN ANALYZE. Tables resolve in the database the listener serves, so the plan shows whether an inverted index serves a predicate.

Check that the endpoint answers:

bash
curl -s -u postgres: http://127.0.0.1:8080/_mcp -H 'Content-Type: application/json' \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'