datanotes 2.0.1 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (181) hide show
  1. package/NOTICE +9 -0
  2. package/README.md +207 -28
  3. package/SECURITY.md +92 -45
  4. package/dist/THIRD-PARTY-NOTICES.txt +682 -0
  5. package/dist/api/catalog.d.cts +69 -0
  6. package/dist/api/catalog.d.ts +50 -2
  7. package/dist/api/cli-options.d.cts +39 -0
  8. package/dist/api/cli-options.d.ts +39 -0
  9. package/dist/api/client.d.cts +89 -0
  10. package/dist/api/client.d.ts +19 -30
  11. package/dist/api/docs.d.cts +211 -0
  12. package/dist/api/docs.d.ts +211 -0
  13. package/dist/api/guidance.d.cts +81 -0
  14. package/dist/api/guidance.d.ts +81 -0
  15. package/dist/api/index.cjs +6610 -2247
  16. package/dist/api/index.d.cts +13 -0
  17. package/dist/api/index.d.ts +10 -7
  18. package/dist/api/index.js +6610 -2247
  19. package/dist/api/local.cjs +118 -6
  20. package/dist/api/local.d.cts +95 -0
  21. package/dist/api/local.d.ts +70 -6
  22. package/dist/api/local.js +104 -6
  23. package/dist/api/mcp.cjs +17384 -0
  24. package/dist/api/mcp.d.cts +108 -0
  25. package/dist/api/mcp.d.ts +108 -0
  26. package/dist/api/mcp.js +17334 -0
  27. package/dist/api/otlp.d.cts +65 -0
  28. package/dist/api/redact.d.cts +17 -0
  29. package/dist/api/redact.d.ts +17 -0
  30. package/dist/api/requests.d.cts +26 -0
  31. package/dist/api/requests.d.ts +14 -16
  32. package/dist/api/results.d.cts +16 -0
  33. package/dist/api/results.d.ts +2 -1
  34. package/dist/api/tokens.d.cts +7 -0
  35. package/dist/datanotes.cjs +62511 -28069
  36. package/dist/docs/administration/configuration.md +59 -0
  37. package/dist/docs/administration/docker.md +80 -0
  38. package/dist/docs/administration/git-sync.md +71 -0
  39. package/dist/docs/administration/limits.md +69 -0
  40. package/dist/docs/administration/run-the-service.md +219 -0
  41. package/dist/docs/administration/status.md +61 -0
  42. package/dist/docs/administration/use-with-obsidian.md +75 -0
  43. package/dist/docs/api/operations/add_column.md +49 -0
  44. package/dist/docs/api/operations/add_constraint.md +55 -0
  45. package/dist/docs/api/operations/adopt_rows.md +50 -0
  46. package/dist/docs/api/operations/align_relations.md +41 -0
  47. package/dist/docs/api/operations/alter_column.md +46 -0
  48. package/dist/docs/api/operations/alter_table.md +67 -0
  49. package/dist/docs/api/operations/batch.md +48 -0
  50. package/dist/docs/api/operations/changes_stream.md +28 -0
  51. package/dist/docs/api/operations/check_table.md +29 -0
  52. package/dist/docs/api/operations/clone_table.md +48 -0
  53. package/dist/docs/api/operations/comment_on.md +42 -0
  54. package/dist/docs/api/operations/configure_database.md +52 -0
  55. package/dist/docs/api/operations/create_table.md +78 -0
  56. package/dist/docs/api/operations/create_view.md +54 -0
  57. package/dist/docs/api/operations/create_webhook.md +37 -0
  58. package/dist/docs/api/operations/define_base.md +52 -0
  59. package/dist/docs/api/operations/delete_rows.md +43 -0
  60. package/dist/docs/api/operations/delete_webhook.md +31 -0
  61. package/dist/docs/api/operations/describe_database.md +30 -0
  62. package/dist/docs/api/operations/describe_history.md +40 -0
  63. package/dist/docs/api/operations/describe_semantic_index.md +24 -0
  64. package/dist/docs/api/operations/describe_table.md +38 -0
  65. package/dist/docs/api/operations/docs.md +13 -0
  66. package/dist/docs/api/operations/drop_column.md +41 -0
  67. package/dist/docs/api/operations/drop_constraint.md +39 -0
  68. package/dist/docs/api/operations/drop_table.md +43 -0
  69. package/dist/docs/api/operations/drop_view.md +38 -0
  70. package/dist/docs/api/operations/engine_status.md +23 -0
  71. package/dist/docs/api/operations/graph_neighbors.md +32 -0
  72. package/dist/docs/api/operations/graph_path.md +32 -0
  73. package/dist/docs/api/operations/graph_traverse.md +43 -0
  74. package/dist/docs/api/operations/identify_session.md +24 -0
  75. package/dist/docs/api/operations/infer_table.md +43 -0
  76. package/dist/docs/api/operations/insert_row.md +48 -0
  77. package/dist/docs/api/operations/list_changes.md +35 -0
  78. package/dist/docs/api/operations/list_folder.md +28 -0
  79. package/dist/docs/api/operations/list_link_targets.md +44 -0
  80. package/dist/docs/api/operations/list_migrations.md +35 -0
  81. package/dist/docs/api/operations/list_tables.md +32 -0
  82. package/dist/docs/api/operations/list_webhooks.md +23 -0
  83. package/dist/docs/api/operations/manual_index.md +14 -0
  84. package/dist/docs/api/operations/manual_page.md +22 -0
  85. package/dist/docs/api/operations/merge_rows.md +44 -0
  86. package/dist/docs/api/operations/move_rows.md +52 -0
  87. package/dist/docs/api/operations/openapi.md +13 -0
  88. package/dist/docs/api/operations/openapi_post.md +20 -0
  89. package/dist/docs/api/operations/read_note.md +29 -0
  90. package/dist/docs/api/operations/recompute_columns.md +44 -0
  91. package/dist/docs/api/operations/record_tool_call.md +36 -0
  92. package/dist/docs/api/operations/rename_column.md +44 -0
  93. package/dist/docs/api/operations/rename_value.md +45 -0
  94. package/dist/docs/api/operations/restore_database.md +51 -0
  95. package/dist/docs/api/operations/restore_table.md +46 -0
  96. package/dist/docs/api/operations/search_notes.md +28 -0
  97. package/dist/docs/api/operations/select_rows.md +61 -0
  98. package/dist/docs/api/operations/semantic_search.md +34 -0
  99. package/dist/docs/api/operations/semantic_search_docs.md +26 -0
  100. package/dist/docs/api/operations/set_table_bases.md +44 -0
  101. package/dist/docs/api/operations/status.md +13 -0
  102. package/dist/docs/api/operations/stop_engine.md +23 -0
  103. package/dist/docs/api/operations/sync_semantic_index.md +33 -0
  104. package/dist/docs/api/operations/table_changes.md +31 -0
  105. package/dist/docs/api/operations/tags/auxiliary.md +14 -0
  106. package/dist/docs/api/operations/tags/change-feed.md +8 -0
  107. package/dist/docs/api/operations/tags/history.md +17 -0
  108. package/dist/docs/api/operations/tags/queries.md +15 -0
  109. package/dist/docs/api/operations/tags/rows-dml.md +16 -0
  110. package/dist/docs/api/operations/tags/schema-ddl.md +22 -0
  111. package/dist/docs/api/operations/tags/service.md +15 -0
  112. package/dist/docs/api/operations/tags/tokens.md +13 -0
  113. package/dist/docs/api/operations/tokens_create.md +34 -0
  114. package/dist/docs/api/operations/tokens_delete.md +29 -0
  115. package/dist/docs/api/operations/tokens_list.md +21 -0
  116. package/dist/docs/api/operations/tokens_me.md +20 -0
  117. package/dist/docs/api/operations/tokens_rotate.md +29 -0
  118. package/dist/docs/api/operations/tokens_update.md +34 -0
  119. package/dist/docs/api/operations/undo_migration.md +46 -0
  120. package/dist/docs/api/operations/update_row.md +60 -0
  121. package/dist/docs/api/operations/update_rows.md +46 -0
  122. package/dist/docs/api/operations/vacuum_table.md +37 -0
  123. package/dist/docs/api.md +135 -0
  124. package/dist/docs/develop/build-an-app.md +320 -0
  125. package/dist/docs/develop/build-on-datanotes.md +138 -0
  126. package/dist/docs/develop/change-feed-and-webhooks.md +70 -0
  127. package/dist/docs/develop/client.md +116 -0
  128. package/dist/docs/develop/concurrency.md +153 -0
  129. package/dist/docs/develop/http-api.md +79 -0
  130. package/dist/docs/develop/telemetry.md +86 -0
  131. package/dist/docs/guides/agents/docs-offline.md +99 -0
  132. package/dist/docs/guides/agents/how-agents-use-datanotes.md +124 -0
  133. package/dist/docs/guides/agents/prompts.md +495 -0
  134. package/dist/docs/guides/agents/reading-errors.md +112 -0
  135. package/dist/docs/guides/data-quality/check-constraints.md +379 -0
  136. package/dist/docs/guides/data-quality/check-table.md +63 -0
  137. package/dist/docs/guides/data-quality/hand-edits.md +507 -0
  138. package/dist/docs/guides/data-quality/sync-between-devices.md +51 -0
  139. package/dist/docs/guides/data-quality/validation.md +90 -0
  140. package/dist/docs/guides/database-objects/bases.md +480 -0
  141. package/dist/docs/guides/database-objects/catalogs-and-schemas.md +897 -0
  142. package/dist/docs/guides/database-objects/columns.md +1332 -0
  143. package/dist/docs/guides/database-objects/relations.md +1454 -0
  144. package/dist/docs/guides/database-objects/tables.md +1404 -0
  145. package/dist/docs/guides/database-objects/views.md +1003 -0
  146. package/dist/docs/guides/history/back-up.md +195 -0
  147. package/dist/docs/guides/history/change-data-feed.md +247 -0
  148. package/dist/docs/guides/history/journal-and-undo.md +262 -0
  149. package/dist/docs/guides/history/restore.md +341 -0
  150. package/dist/docs/guides/history/table-history.md +657 -0
  151. package/dist/docs/guides/history/vacuum.md +35 -0
  152. package/dist/docs/guides/read-and-write/automatic-links.md +768 -0
  153. package/dist/docs/guides/read-and-write/batch.md +139 -0
  154. package/dist/docs/guides/read-and-write/import-notes.md +896 -0
  155. package/dist/docs/guides/read-and-write/joins-and-aggregates.md +251 -0
  156. package/dist/docs/guides/read-and-write/read-and-filter.md +536 -0
  157. package/dist/docs/guides/read-and-write/write-rows.md +573 -0
  158. package/dist/docs/guides/search/link-graph.md +146 -0
  159. package/dist/docs/guides/search/semantic.md +133 -0
  160. package/dist/docs/guides/search/text.md +142 -0
  161. package/dist/docs/guides/security/security-model.md +110 -0
  162. package/dist/docs/guides/security/tokens.md +60 -0
  163. package/dist/docs/guides/security/who-did-what.md +42 -0
  164. package/dist/docs/index.md +113 -0
  165. package/dist/docs/llms.txt +33 -0
  166. package/dist/docs/reference/cli.md +136 -0
  167. package/dist/docs/reference/compatibility.md +63 -0
  168. package/dist/docs/reference/errors.md +72 -0
  169. package/dist/docs/reference/glossary.md +161 -0
  170. package/dist/docs/reference/operations/auxiliary.md +116 -0
  171. package/dist/docs/reference/operations/history.md +168 -0
  172. package/dist/docs/reference/operations/queries.md +169 -0
  173. package/dist/docs/reference/operations/rows.md +185 -0
  174. package/dist/docs/reference/operations/schema.md +316 -0
  175. package/dist/docs/reference/operations.md +96 -0
  176. package/dist/docs/reference/settings.md +31 -0
  177. package/dist/docs/release-notes.md +67 -0
  178. package/dist/docs/start/connect-agents.md +204 -0
  179. package/dist/docs/start/how-it-works.md +317 -0
  180. package/dist/docs/start/quick-start.md +126 -0
  181. package/package.json +58 -14
@@ -0,0 +1,135 @@
1
+ # Datanotes HTTP API
2
+
3
+ > OpenAPI 3.1.0 description of the Datanotes 3.0.0 HTTP API: https://datanotes.dev/openapi.json
4
+
5
+ Database of Markdown notes: tables of notes, rows, queries, history, a change feed and the vault's notes.
6
+
7
+ **Operations**: every operation is `POST /datanotes/operations/<name>/`, its body the operation's arguments (the same names as the MCP tool of the same name). An unknown argument is refused (400) with the closest real one.
8
+
9
+ **Authentication**: every route but `GET /`, this document and `/datanotes/docs` needs `Authorization: Bearer <token>`: the owner's token (`httpServerToken` in the settings: everything) or a named token (`/datanotes/tokens/create/`: reads everything; writes the tables in its `write` patterns, changes the schema with `ddl`; the engine's administration stays the owner's: 403 otherwise).
10
+
11
+ **Actor**: send `X-Datanotes-Actor: {"harness": …, "model": …, "session": …}` (JSON) to record on whose behalf a write is made; the journal adds the credential (`auth`: `owner`, or the named token's name), which the client cannot claim.
12
+
13
+ **Errors**: `{error}` with 400 (bad input), 401 (missing or wrong token), 403 (a named token beyond its permissions), 404 (unknown table, view, migration or note), 409 (ambiguous table name), 500. A refused write answers 422 (409 for undo) with the operation's result, `ok: false`: nothing was written.
14
+
15
+ ## Servers
16
+
17
+ - `/`
18
+
19
+ ## Authentication
20
+
21
+ - `bearer`: http bearer. The owner's token or a named token.
22
+
23
+ ## Operations
24
+
25
+ ### Schema (DDL)
26
+
27
+ Operations that define the database: tables, columns, constraints, bases and views.
28
+
29
+ - [Create a table: `name`, in `catalog` and `schema` (default: the database's defaults); the definition](https://datanotes.dev/api/operations/create_table/): `POST /datanotes/operations/create_table/`
30
+ - [Drop a table. Refused while it has rows unless `mode: "archive"`, which removes the](https://datanotes.dev/api/operations/drop_table/): `POST /datanotes/operations/drop_table/`
31
+ - [Add a typed column. Sparse by default (written only on rows that have a value — use for](https://datanotes.dev/api/operations/add_column/): `POST /datanotes/operations/add_column/`
32
+ - [Rename a column in metastore.yaml and on every row. Refused if `to_column` is already a column](https://datanotes.dev/api/operations/rename_column/): `POST /datanotes/operations/rename_column/`
33
+ - [Remove a column from the schema and every row. Previous values are journaled; revert with](https://datanotes.dev/api/operations/drop_column/): `POST /datanotes/operations/drop_column/`
34
+ - [Change a column's attributes (type, min/max/step, values, required, default, label,](https://datanotes.dev/api/operations/alter_column/): `POST /datanotes/operations/alter_column/`
35
+ - [Alter a table: move it (`catalog`, `schema`), rename it (`name`), change](https://datanotes.dev/api/operations/alter_table/): `POST /datanotes/operations/alter_table/`
36
+ - [Create or replace a base: a named set of columns (same spec as `create_table`) that tables](https://datanotes.dev/api/operations/define_base/): `POST /datanotes/operations/define_base/`
37
+ - [Set the bases a table inherits from (`bases`, in order; `[]` removes them). Inherited](https://datanotes.dev/api/operations/set_table_bases/): `POST /datanotes/operations/set_table_bases/`
38
+ - [COMMENT ON CATALOG / SCHEMA: set the `description` of a catalog (`catalog` alone) or of one of its](https://datanotes.dev/api/operations/comment_on/): `POST /datanotes/operations/comment_on/`
39
+ - [Rename one value of an enum column (e.g. `high` → `very high`) in metastore.yaml and every](https://datanotes.dev/api/operations/rename_value/): `POST /datanotes/operations/rename_value/`
40
+ - [ALTER TABLE ADD CONSTRAINT `name` CHECK: `check` is a where filter every row must match,](https://datanotes.dev/api/operations/add_constraint/): `POST /datanotes/operations/add_constraint/`
41
+ - [Remove the CHECK constraint `name` from `table`.](https://datanotes.dev/api/operations/drop_constraint/): `POST /datanotes/operations/drop_constraint/`
42
+ - [Create (or `replace`) a view: a named select stored in metastore.yaml. Name it with the prefix](https://datanotes.dev/api/operations/create_view/): `POST /datanotes/operations/create_view/`
43
+ - [Remove a view from `metastore.yaml` (undo with `undo_migration`).](https://datanotes.dev/api/operations/drop_view/): `POST /datanotes/operations/drop_view/`
44
+ - [CREATE TABLE `target` CLONE `source`: same definition plus independent copies of its rows](https://datanotes.dev/api/operations/clone_table/): `POST /datanotes/operations/clone_table/`
45
+
46
+ ### Rows (DML)
47
+
48
+ Operations that write rows: insert, update, merge, delete, move, adopt and batch.
49
+
50
+ - [Insert a row (new note). Values are validated against the columns; the note path comes from](https://datanotes.dev/api/operations/insert_row/): `POST /datanotes/operations/insert_row/`
51
+ - [Update every row matching `where` (required: `{}` is every row). `where`: keys AND-ed; values are equality or operators](https://datanotes.dev/api/operations/update_rows/): `POST /datanotes/operations/update_rows/`
52
+ - [Update exactly ONE row, addressed by its `id` (the system UUID) or its `path`; refused when](https://datanotes.dev/api/operations/update_row/): `POST /datanotes/operations/update_row/`
53
+ - [Delete rows matching `where`. `mode`: "archive" (default, move to the table's archive](https://datanotes.dev/api/operations/delete_rows/): `POST /datanotes/operations/delete_rows/`
54
+ - [Move rows matching `where` (required: `{}` is every row) to another table (e.g. split a domain out of `log` into its own](https://datanotes.dev/api/operations/move_rows/): `POST /datanotes/operations/move_rows/`
55
+ - [Turn existing notes (not rows of any table yet) into rows of `table`, keeping their body and](https://datanotes.dev/api/operations/adopt_rows/): `POST /datanotes/operations/adopt_rows/`
56
+ - [Several writes that succeed or fail together. `operations`: `[{"op": ..., ...arguments}]` where](https://datanotes.dev/api/operations/batch/): `POST /datanotes/operations/batch/`
57
+ - [Add the reciprocal links missing on a table's `inverse` link columns (e.g. Alice lists Carol](https://datanotes.dev/api/operations/align_relations/): `POST /datanotes/operations/align_relations/`
58
+ - [MERGE (upsert) `rows` into `table` by the key columns `on` (e.g. `["occurred_at"]` or](https://datanotes.dev/api/operations/merge_rows/): `POST /datanotes/operations/merge_rows/`
59
+ - [Bring generated and identity columns up to date (all rows, or only `paths`), e.g. after](https://datanotes.dev/api/operations/recompute_columns/): `POST /datanotes/operations/recompute_columns/`
60
+
61
+ ### Queries
62
+
63
+ Operations that read rows and notes: select_rows, link targets, text and semantic search, the link graph.
64
+
65
+ - [Read a note or another file of the vault: `{path, content, front_matter, tags, modified_at,](https://datanotes.dev/api/operations/read_note/): `POST /datanotes/operations/read_note/`
66
+ - [The direct children of a vault folder: `{folder, folders, files}`, each a vault path, sorted;](https://datanotes.dev/api/operations/list_folder/): `POST /datanotes/operations/list_folder/`
67
+ - [Plain-text search: every word of `query` must occur, case- and accent-insensitive, in the](https://datanotes.dev/api/operations/search_notes/): `POST /datanotes/operations/search_notes/`
68
+ - [Query rows: `{table, total, rows: [{path, ...}]}`. `table` may be a view,](https://datanotes.dev/api/operations/select_rows/): `POST /datanotes/operations/select_rows/`
69
+ - [The rows a link column may point to, for one row: what an agent or a picker offers before writing](https://datanotes.dev/api/operations/list_link_targets/): `POST /datanotes/operations/list_link_targets/`
70
+ - [Find notes by meaning, not exact words: `{query, results: [{path, score, heading, snippet,](https://datanotes.dev/api/operations/semantic_search/): `POST /datanotes/operations/semantic_search/`
71
+ - [Direct neighbours of notes: what they link to (`out`) and what links to them (`in`,](https://datanotes.dev/api/operations/graph_neighbors/): `POST /datanotes/operations/graph_neighbors/`
72
+ - [Breadth-first walk over links from `from_nodes` (`{"paths": [...]}` and/or `{"table", "where"}`).](https://datanotes.dev/api/operations/graph_traverse/): `POST /datanotes/operations/graph_traverse/`
73
+ - [Shortest chain of links between two sets of notes (each `{"paths": [...]}` and/or](https://datanotes.dev/api/operations/graph_path/): `POST /datanotes/operations/graph_path/`
74
+
75
+ ### History
76
+
77
+ Operations on the journal, a table's versions, the change feed and webhooks: undo, time travel, restore and vacuum.
78
+
79
+ - [Revert a journaled table operation by its `migration_id`. Refused (nothing written) when any](https://datanotes.dev/api/operations/undo_migration/): `POST /datanotes/operations/undo_migration/`
80
+ - [The operation journal, newest first: `total` (the matching operations) and `migrations`, each with](https://datanotes.dev/api/operations/list_migrations/): `POST /datanotes/operations/list_migrations/`
81
+ - [DESCRIBE HISTORY: the table's versions, newest first — `version`, `timestamp`, `operation`](https://datanotes.dev/api/operations/describe_history/): `POST /datanotes/operations/describe_history/`
82
+ - [Change data feed between two versions (both included, end defaults to latest): one entry](https://datanotes.dev/api/operations/table_changes/): `POST /datanotes/operations/table_changes/`
83
+ - [Change feed across all tables: numbered row changes (`seq`), oldest first, from engine writes](https://datanotes.dev/api/operations/list_changes/): `POST /datanotes/operations/list_changes/`
84
+ - [Webhooks of the change feed: id, url, filter (tables, ops, columns, exclude_actor), `cursor` (last change delivered), `status` (ok, failing, disabled), `last_delivery`, `last_error`. Secrets are not shown.](https://datanotes.dev/api/operations/list_webhooks/): `POST /datanotes/operations/list_webhooks/`
85
+ - [Push row changes to a URL (a service that would rather be called than poll `list_changes`): batches of up to 100 events POSTed as `{webhook, events, last}`, signed with `X-Datanotes-Signature: sha256=<HMAC of the body>`, retried with backoff until a 2xx. Events are those of `list_changes` (hand edits with their `violations`; the hand edits the service has seen are recorded before it stops). Starts from now unless `since`. Returns the webhook with its `secret` — shown only here: give it to the receiver. Only on the user's explicit request: a webhook sends the database's changes (and, with `since`, its past ones) to that address.](https://datanotes.dev/api/operations/create_webhook/): `POST /datanotes/operations/create_webhook/`
86
+ - [Stop and remove a webhook (by `id` from list_webhooks).](https://datanotes.dev/api/operations/delete_webhook/): `POST /datanotes/operations/delete_webhook/`
87
+ - [RESTORE TABLE to a `version` or `timestamp`: rows and definition return to that state —](https://datanotes.dev/api/operations/restore_table/): `POST /datanotes/operations/restore_table/`
88
+ - [RESTORE DATABASE to a `timestamp` (each table as of its last version committed at or before it) or](https://datanotes.dev/api/operations/restore_database/): `POST /datanotes/operations/restore_database/`
89
+ - [Delete transaction-log history older than `retain_from_version` (default: keep only the](https://datanotes.dev/api/operations/vacuum_table/): `POST /datanotes/operations/vacuum_table/`
90
+
91
+ ### Auxiliary
92
+
93
+ Operations that describe and check the database, configure it and maintain its indexes.
94
+
95
+ - [List every table: `name`, `catalog`, `schema`, `qualified` (`personal.metrics.mood` — use it](https://datanotes.dev/api/operations/list_tables/): `POST /datanotes/operations/list_tables/`
96
+ - [Describe a table (`catalog.schema.table` or a shorter unambiguous name): `qualified`, `name`,](https://datanotes.dev/api/operations/describe_table/): `POST /datanotes/operations/describe_table/`
97
+ - [Describe the database: its folder (`root`), `metastore` (`metastore.yaml`: read it freely,](https://datanotes.dev/api/operations/describe_database/): `POST /datanotes/operations/describe_database/`
98
+ - [Configure the database (persisted in metastore.yaml `config:`): `root` (rename the database](https://datanotes.dev/api/operations/configure_database/): `POST /datanotes/operations/configure_database/`
99
+ - [Validate every row against the table (required, types, ranges, enums, links, unknown](https://datanotes.dev/api/operations/check_table/): `POST /datanotes/operations/check_table/`
100
+ - [Read-only schema inference from notes' front matter.](https://datanotes.dev/api/operations/infer_table/): `POST /datanotes/operations/infer_table/`
101
+ - [The semantic search index: `enabled`, `provider`, `model`, `files`, `passages`, `pending`,](https://datanotes.dev/api/operations/describe_semantic_index/): `POST /datanotes/operations/describe_semantic_index/`
102
+ - [Bring the semantic search index up to date: embed the notes changed since the last run (the](https://datanotes.dev/api/operations/sync_semantic_index/): `POST /datanotes/operations/sync_semantic_index/`
103
+
104
+ ### change feed
105
+
106
+ The change feed as Server-Sent Events, and the tool calls MCP servers report.
107
+
108
+ - [Record an agent's tool call in the change feed (`tool_call` events): what an MCP server reports after each call, its own tools included (`served_by`). Any token may call it.](https://datanotes.dev/api/operations/record_tool_call/): `POST /datanotes/tool-calls/record/`
109
+ - [The change feed as Server-Sent Events (id = sequence number). Resumes from Last-Event-ID or `since`.](https://datanotes.dev/api/operations/changes_stream/): `GET /datanotes/changes/stream`
110
+
111
+ ### tokens
112
+
113
+ Named tokens and their permissions; what MCP servers report about their agents' sessions.
114
+
115
+ - [Tell the engine the model and harness behind an agent session (what an MCP server reports): later requests carrying that session in `X-Datanotes-Actor` are journaled with it. Any token may call it.](https://datanotes.dev/api/operations/identify_session/): `POST /datanotes/sessions/identify/`
116
+ - [List the named tokens (owner only).](https://datanotes.dev/api/operations/tokens_list/): `POST /datanotes/tokens/`
117
+ - [The calling token's own entry (a named token; 400 for the owner).](https://datanotes.dev/api/operations/tokens_me/): `POST /datanotes/tokens/me/`
118
+ - [Create a named token (owner only). Returns the token once.](https://datanotes.dev/api/operations/tokens_create/): `POST /datanotes/tokens/create/`
119
+ - [Change a named token. A token may change its own description and autolink default; permissions are the owner's.](https://datanotes.dev/api/operations/tokens_update/): `POST /datanotes/tokens/update/`
120
+ - [Revoke a named token (owner only): it stops working at once.](https://datanotes.dev/api/operations/tokens_delete/): `POST /datanotes/tokens/delete/`
121
+ - [Give a named token a new value (owner only): the old one stops working.](https://datanotes.dev/api/operations/tokens_rotate/): `POST /datanotes/tokens/rotate/`
122
+
123
+ ### service
124
+
125
+ Status, stop, this document, the interactive console, the docs.
126
+
127
+ - [The sections of the docs of this version closest in meaning to a query: what `search_docs` fuses with its ranking by words (hybrid search).](https://datanotes.dev/api/operations/semantic_search_docs/): `POST /datanotes/manual/search/`
128
+ - [The engine's health at a glance (owner only; `datanotes status` prints it): engine, database, journal, git sync, change feed, webhooks, named tokens, hand edits breaking the rules in the last 24 h, logs.](https://datanotes.dev/api/operations/engine_status/): `POST /datanotes/status/`
129
+ - [Stop the engine gracefully (owner only; `datanotes stop` calls it): it answers at once, then finishes the requests under way, makes the last git sync and store backup commit and push, releases its lock and exits.](https://datanotes.dev/api/operations/stop_engine/): `POST /datanotes/stop/`
130
+ - [This document (as `GET /datanotes/openapi.json`, with a token).](https://datanotes.dev/api/operations/openapi_post/): `POST /datanotes/openapi/`
131
+ - [Status and version (no token needed; `authenticated` tells whether the token sent is accepted, `auth` whose it is).](https://datanotes.dev/api/operations/status/): `GET /`
132
+ - [This document (public: it describes the API, it holds no data).](https://datanotes.dev/api/operations/openapi/): `GET /datanotes/openapi.json`
133
+ - [This document in an interactive console (Swagger UI, served by the engine; public). Authorize with a token to try the routes.](https://datanotes.dev/api/operations/docs/): `GET /datanotes/docs`
134
+ - [The index of the docs of this version (every page of datanotes.dev as Markdown, shipped with the package): each page with its description and route.](https://datanotes.dev/api/operations/manual_index/): `GET /datanotes/manual/`
135
+ - [A page of the docs of this version as Markdown, or one of its sections with `section`, the anchor of its heading. The same text as the page with `.md` on datanotes.dev, offline.](https://datanotes.dev/api/operations/manual_page/): `GET /datanotes/manual/{page}.md`
@@ -0,0 +1,320 @@
1
+ # Build an app on Datanotes
2
+
3
+ > Write a small TypeScript app that reads and writes your film diary with its own token, and follows its changes.
4
+
5
+ This tutorial builds film night, a small app for the diary of classic films. The app is TypeScript on the [HTTP API](https://datanotes.dev/develop/http-api/), with the [JavaScript client](https://datanotes.dev/develop/client/) and its [own token](https://datanotes.dev/guides/security/tokens/), and every answer on this page comes from a real run against the engine. By the end, film night does three things:
6
+
7
+ * Reads the films you still want to watch
8
+ * Logs a viewing as one undoable change
9
+ * Prints a line each time a film becomes watched
10
+
11
+ ## Before you start
12
+
13
+ You need:
14
+
15
+ * **Node.js 24** or newer, which runs `.ts` files directly
16
+ * **An engine** that runs on your notes, with `npx datanotes serve --database your_database_folder`
17
+ * **The diary’s tables**: `film` and `watch` as in [Read and filter rows](https://datanotes.dev/guides/read-and-write/read-and-filter/), with the films of that page
18
+ * **The `datanotes` package** in the app’s folder: `npm install datanotes`, with `"type": "module"` in its `package.json`
19
+
20
+ The app reads a view of the films to watch, and the engine refuses a watched film without a rating. Create both once with the owner token:
21
+
22
+ ```ts
23
+ await owner.call("create_view", {
24
+ name: "vw_to_watch",
25
+ query: {
26
+ table: "film",
27
+ columns: ["title", "year", "minutes", "director"],
28
+ where: { status: "to watch" },
29
+ order_by: "minutes",
30
+ },
31
+ });
32
+ await owner.call("add_constraint", {
33
+ table: "film",
34
+ name: "rated_when_watched",
35
+ check: {
36
+ or: [{ status: { ne: "watched" } }, { rating: { exists: true } }],
37
+ },
38
+ });
39
+ ```
40
+
41
+ `owner` is a `DatanotesClient` with the owner token, as in step 1. [Store a query as a view](https://datanotes.dev/guides/database-objects/views/#store-a-query-as-a-view) and [Enforce rules with CHECK constraints](https://datanotes.dev/guides/data-quality/check-constraints/#enforce-rules-with-check-constraints) explain both operations.
42
+
43
+ ## Build film night
44
+
45
+ The app has four small files in `src/`: the client, the films, the change feed and the entry point.
46
+
47
+ 1. Create the app’s token with the owner token. The engine answers with the token, once, and keeps only its hash:
48
+
49
+ ```ts
50
+ import { DatanotesClient } from "datanotes/client";
51
+
52
+
53
+ const owner = new DatanotesClient({
54
+ url: "http://127.0.0.1:27150",
55
+ token: "your_owner_token",
56
+ });
57
+
58
+
59
+ const r = await owner.post<{ token: string }>("/tokens/create/", {
60
+ name: "film-night",
61
+ description: "Logs film nights and picks the next film",
62
+ write: ["main.public.watch", "main.public.film"],
63
+ });
64
+ console.log(r.token);
65
+ ```
66
+
67
+ The token can read every table, and write rows of `watch` and `film` only. It can’t change the schema, undo, or touch `actor`. The engine lists it like this:
68
+
69
+ ```json
70
+ {
71
+ "name": "film-night",
72
+ "description": "Logs film nights and picks the next film",
73
+ "write": ["main.public.watch", "main.public.film"],
74
+ "ddl": false
75
+ }
76
+ ```
77
+
78
+ Keep the token in an environment variable, `FILM_NIGHT_TOKEN`.
79
+
80
+ 2. Create the client in `src/db.ts`. Every other file imports it:
81
+
82
+ ```ts
83
+ import { DatanotesClient } from "datanotes/client";
84
+
85
+
86
+ export const db = new DatanotesClient({
87
+ url: process.env.DATANOTES_URL ?? "http://127.0.0.1:27150",
88
+ token: process.env.FILM_NIGHT_TOKEN ?? "",
89
+ actor: { harness: "film-night" },
90
+ });
91
+ ```
92
+
93
+ The engine records `film-night` as the `auth` of every write this token makes, whatever `actor` says.
94
+
95
+ 3. Read the films to watch in `src/films.ts`. You select a view like a table:
96
+
97
+ ```ts
98
+ import { db } from "./db.ts";
99
+
100
+
101
+ export interface Film {
102
+ path: string;
103
+ title: string;
104
+ minutes: number;
105
+ }
106
+
107
+
108
+ export async function filmsToWatch(): Promise<Film[]> {
109
+ const r = await db.call("select_rows", { table: "vw_to_watch" });
110
+ if (!r.ok) throw new Error(String(r.value));
111
+ return (r.value as { rows: Film[] }).rows;
112
+ }
113
+ ```
114
+
115
+ The view answers with the two films still to watch, shortest first. Each row includes its note’s `path`, which the next step uses:
116
+
117
+ ```json
118
+ [
119
+ {
120
+ "path": "main/public/film/Battleship Potemkin.md",
121
+ "title": "Battleship Potemkin",
122
+ "year": 1925,
123
+ "minutes": 75,
124
+ "director": "Sergei Eisenstein"
125
+ },
126
+ {
127
+ "path": "main/public/film/Metropolis.md",
128
+ "title": "Metropolis",
129
+ "year": 1927,
130
+ "minutes": 153,
131
+ "director": "Fritz Lang"
132
+ }
133
+ ]
134
+ ```
135
+
136
+ 4. Log a viewing, also in `src/films.ts`. A [`batch`](https://datanotes.dev/reference/operations/rows/#batch) inserts the `watch` row and updates the film together:
137
+
138
+ ```ts
139
+ export interface Viewing {
140
+ at: string;
141
+ where: "cinema" | "home" | "outdoor screening";
142
+ place: string;
143
+ rating?: number;
144
+ }
145
+
146
+
147
+ export function logViewing(film: Film, v: Viewing) {
148
+ const { at, where, place, rating } = v;
149
+ const watch = { film: `[[${film.title}]]`, occurred_at: at, where, place };
150
+ const seen = { status: "watched", rating, watched_on: at.slice(0, 10) };
151
+ return db.call("batch", {
152
+ summary: `Film night: ${film.title}`,
153
+ operations: [
154
+ { op: "insert_row", table: "watch", values: watch },
155
+ { op: "update_row", table: "film", path: film.path, set: seen },
156
+ ],
157
+ });
158
+ }
159
+ ```
160
+
161
+ The engine checks both operations before it writes either. When it refuses one, it writes nothing. The batch is one entry in the journal, so one undo reverts both.
162
+
163
+ 5. Follow the films you mark watched, in `src/feed.ts`. The app long-polls the [change feed](https://datanotes.dev/develop/change-feed-and-webhooks/#choose-how-to-follow-the-feed) with [`list_changes`](https://datanotes.dev/reference/operations/history/#list_changes), keeping only `film` changes to `status` that leave it `watched`:
164
+
165
+ ```ts
166
+ import { db } from "./db.ts";
167
+
168
+
169
+ interface Change {
170
+ row: { title: string; rating?: number };
171
+ }
172
+ type Page = { events: Change[]; last: number; head: number };
173
+
174
+
175
+ const watched = {
176
+ tables: ["film"],
177
+ columns: ["status"],
178
+ where: { status: "watched" },
179
+ };
180
+
181
+
182
+ async function changes(args: object): Promise<Page> {
183
+ const r = await db.call("list_changes", { ...watched, ...args });
184
+ if (!r.ok) throw new Error(String(r.value));
185
+ return r.value as Page;
186
+ }
187
+ ```
188
+
189
+ `followWatched` starts at `head`, the feed’s latest change, so it reports only what happens after the app starts. Each call waits up to 20s for a change, and `last` is where the next one starts:
190
+
191
+ ```ts
192
+ export async function followWatched(onWatched: (c: Change) => void) {
193
+ let since = (await changes({ limit: 1 })).head;
194
+ for (;;) {
195
+ const page = await changes({ since, wait_ms: 20_000 });
196
+ page.events.forEach(onWatched);
197
+ since = page.last;
198
+ }
199
+ }
200
+ ```
201
+
202
+ The feed suits an app better than a webhook here: a named token can read the feed, while only the owner creates webhooks. To pick up where it stopped after a restart, save `last` and start from it.
203
+
204
+ 6. Start the app in `src/main.ts`. It follows the feed and prints each film you mark watched:
205
+
206
+ ```ts
207
+ import { followWatched } from "./feed.ts";
208
+
209
+
210
+ await followWatched(({ row }) => {
211
+ console.log(`${row.title} is watched, rated ${row.rating ?? "-"}`);
212
+ });
213
+ ```
214
+
215
+ Run it and leave it running:
216
+
217
+ ```bash
218
+ node src/main.ts
219
+ ```
220
+
221
+ ## Let agents pick the film
222
+
223
+ The engine serves no tools of other programs. For agents to pick tonight’s film with one call, film night serves its own Model Context Protocol (MCP) server, with a `pick_film_for_tonight` tool next to the engine’s read tools and instructions of its own. Agents connect to it as to any MCP server. [Build your own MCP server](https://datanotes.dev/develop/build-on-datanotes/#build-your-own-mcp-server) writes that server for this diary, with `datanotes/mcp`.
224
+
225
+ ## Log tonight’s film
226
+
227
+ `src/tonight.ts` logs the evening with `logViewing`, and prints the batch’s journal entry:
228
+
229
+ ```ts
230
+ import { filmsToWatch, logViewing } from "./films.ts";
231
+
232
+
233
+ const films = await filmsToWatch();
234
+ console.log(films.map((f) => `${f.title}, ${f.minutes} min`));
235
+
236
+
237
+ const potemkin = films.find((f) => f.title === "Battleship Potemkin")!;
238
+ const r = await logViewing(potemkin, {
239
+ at: "2026-10-09T21:00:00",
240
+ where: "cinema",
241
+ place: "the film club downtown",
242
+ rating: 5,
243
+ });
244
+ if (!r.ok) console.log(r.value);
245
+ else console.log((r.value as { migration_id: string }).migration_id);
246
+ ```
247
+
248
+ The script prints the films to watch and the batch’s id:
249
+
250
+ ```text
251
+ [ 'Battleship Potemkin, 75 min', 'Metropolis, 153 min' ]
252
+ 20261009T215615-batch-film-night-battleship-potemkin
253
+ ```
254
+
255
+ A moment later, the running app prints the change it read from the feed:
256
+
257
+ ```text
258
+ Battleship Potemkin is watched, rated 5
259
+ ```
260
+
261
+ The diary now has a `watch` note, `main/public/watch/2026-10-09T2100.md`, and Metropolis is the only film left to watch.
262
+
263
+ To revert the whole evening, the owner calls `undo_migration` with the batch’s id. The app can’t: its token answers `token film-night may not call undo_migration (reserved to the owner)`.
264
+
265
+ ## Show the engine’s refusals
266
+
267
+ When the engine refuses a write, `ok` is `false` and `value` says why, in words a person can read. The app prints it as it is. This script logs Metropolis twice, first without a rating, then with a rating of 6:
268
+
269
+ ```ts
270
+ const metropolis = (await filmsToWatch())
271
+ .find((f) => f.title === "Metropolis")!;
272
+ const evening = {
273
+ at: "2026-10-10T20:30:00",
274
+ where: "home" as const,
275
+ place: "my living room",
276
+ };
277
+
278
+
279
+ for (const rating of [undefined, 6]) {
280
+ const r = await logViewing(metropolis, { ...evening, rating });
281
+ if (!r.ok) console.log(r.value);
282
+ }
283
+ ```
284
+
285
+ The first breaks the `rated_when_watched` CHECK, the second the column’s `max: 5`. In both cases the engine undoes the `watch` row of operation 0, and nothing stays written:
286
+
287
+ ```text
288
+ batch refused at operation 1 — the operations before it were undone, nothing is left written:
289
+ operation 1 (update_row): update_row refused — nothing was written:
290
+ invalid main/public/film/Metropolis.md: rated_when_watched violates CHECK rated_when_watched: {"or":[{"status":{"ne":"watched"}},{"rating":{"exists":true}}]}
291
+ batch refused at operation 1 — the operations before it were undone, nothing is left written:
292
+ operation 1 (update_row): update_row refused — nothing was written:
293
+ invalid main/public/film/Metropolis.md: rating must be <= 5
294
+ ```
295
+
296
+ A write outside the app’s permissions comes back the same way. The app may not write `actor`:
297
+
298
+ ```ts
299
+ const r = await db.call("update_rows", {
300
+ table: "actor",
301
+ where: { name: "Brigitte Helm" },
302
+ set: { born: "1906-03-17" },
303
+ });
304
+ if (!r.ok) console.log(r.value);
305
+ ```
306
+
307
+ The engine answers with 403, and the client turns it into the reason:
308
+
309
+ ```text
310
+ token film-night may not write main.public.actor
311
+ ```
312
+
313
+ ## Learn more about tokens, the client and the feed
314
+
315
+ These pages cover each part of film night in full:
316
+
317
+ * [Give each program its own token](https://datanotes.dev/guides/security/tokens/): every permission and route of named tokens
318
+ * [Build on Datanotes](https://datanotes.dev/develop/build-on-datanotes/): the ways to build on the engine, your own MCP server included
319
+ * [Call the engine from JavaScript](https://datanotes.dev/develop/client/): the client’s methods and errors
320
+ * [Follow every change with the change feed](https://datanotes.dev/guides/history/change-data-feed/#follow-every-change-with-the-change-feed): the feed’s filters, streaming and webhooks
@@ -0,0 +1,138 @@
1
+ # Build on Datanotes
2
+
3
+ > Datanotes is a foundation: reach the database through the default MCP server, through a program with its own token, or through an MCP server of your own that reuses the engine’s tools.
4
+
5
+ Datanotes is a foundation for your own tools: the engine holds your notes as typed tables, checks every write and journals it. You build on it in three ways, and you can mix them:
6
+
7
+ * **The default MCP server**: `datanotes mcp` gives an agent direct access to the database
8
+ * **A program with its own token**: a script, a service or an app calls the HTTP API, with only the permissions you give it
9
+ * **Your own MCP server**: your tools and your instructions, next to the engine’s tools you choose
10
+
11
+ Each way reaches the engine through its [HTTP API](https://datanotes.dev/develop/http-api/), so the same checks, permissions and journal apply to all three.
12
+
13
+ ## Give an agent direct access
14
+
15
+ `datanotes mcp` is the Model Context Protocol (MCP) server of Datanotes. It offers every [operation](https://datanotes.dev/reference/operations/) of the engine as a tool, with instructions that tell the agent how to find tables and write rows. Use it when the agent should work with the database itself: create tables, query them, write rows.
16
+
17
+ [Connect your agents](https://datanotes.dev/start/connect-agents/) shows how to add it to Claude, Cursor, Codex and other MCP clients. Give each agent its [own token](https://datanotes.dev/guides/security/tokens/) to limit what it may change.
18
+
19
+ ## Write a program with its own token
20
+
21
+ A program reads and writes through the HTTP API, from any language. In JavaScript and TypeScript, [`datanotes/client`](https://datanotes.dev/develop/client/) runs the same operations agents call. The program can also follow the change feed to react when a note changes.
22
+
23
+ Give the program its [own token](https://datanotes.dev/guides/security/tokens/). The engine then refuses what the program may not do, and the journal records the token’s name with every write. [Build an app on Datanotes](https://datanotes.dev/develop/build-an-app/) writes such a program step by step.
24
+
25
+ ## Build your own MCP server
26
+
27
+ An MCP server of your own decides what an agent sees: the engine’s tools it needs, tools of your own that know your data, and instructions in your words. Use it when agents should work through your rules, for example a film diary where the agent picks tonight’s film with one call instead of querying three tables.
28
+
29
+ `datanotes/mcp` gives your server the engine’s side, with no dependencies:
30
+
31
+ * **`engineTools(filter?)`**: the engine’s operations as MCP tool definitions, all of them or those you name in `only`, minus those in `except`
32
+ * **`callEngineTool(client, name, args, options?)`**: runs one through a `DatanotesClient` and answers its MCP result; a refusal comes back as an error result the agent can read, with a hint on how to fix it. A tool name or arguments that don’t match the tool’s schema are refused before the call reaches the engine, with the closest name and the tool’s arguments
33
+ * **`isEngineTool(name, filter?)`**: tells your server which calls go to the engine
34
+ * **`docsTools(filter?)`**, **`callDocsTool(name, args, options?)`** and **`isDocsTool(name, filter?)`**: the same for `list_docs`, `read_docs` and `search_docs`, which read the docs of the installed version from the package. With a `client` in the options, `search_docs` also ranks by meaning when the engine has semantic search on: see [hybrid search](https://datanotes.dev/guides/agents/docs-offline/#search-by-words-and-by-meaning)
35
+ * **`ENGINE_INSTRUCTIONS`**: the instructions of `datanotes mcp`, to start your own with; they point agents to `search_docs` and `read_docs`
36
+ * **`toolContent(ok, value)`**: turns a result of your own tools into MCP content
37
+ * **`offlineDocs(content)`**: drops the web address from the `Docs:` lines of an error result and keeps the `read_docs` call. Use it when your server serves the docs tools, so agents read the docs offline as with `datanotes mcp`
38
+
39
+ Your server brings its own MCP software development kit (SDK), and runs with its own token. This film night server offers three read tools of the engine, the docs tools and a tool of its own. It starts with the client and the tool:
40
+
41
+ ```ts
42
+ import { DatanotesClient } from "datanotes/client";
43
+ import { toolContent } from "datanotes/mcp";
44
+
45
+
46
+ const db = new DatanotesClient({
47
+ url: "http://127.0.0.1:27150",
48
+ token: process.env.FILM_NIGHT_TOKEN ?? "",
49
+ actor: { harness: "film-night" },
50
+ });
51
+
52
+
53
+ const pickFilm = {
54
+ name: "pick_film_for_tonight",
55
+ description: "Pick the longest film to watch that fits in max_minutes.",
56
+ inputSchema: {
57
+ type: "object" as const,
58
+ properties: { max_minutes: { type: "integer", minimum: 1 } },
59
+ },
60
+ };
61
+ ```
62
+
63
+ `pickFilm` is an MCP tool definition like the engine’s. The function behind it reads the films to watch with the client, and answers with `toolContent`:
64
+
65
+ ```ts
66
+ async function pick(args: Record<string, unknown>) {
67
+ const r = await db.call("select_rows", {
68
+ table: "film",
69
+ where: { status: "to watch" },
70
+ order_by: "minutes",
71
+ });
72
+ if (!r.ok) return toolContent(false, r.value);
73
+ const max = Number(args.max_minutes ?? Infinity);
74
+ const rows = (r.value as { rows: { title: string; minutes: number }[] }).rows;
75
+ const film = rows.filter((f) => f.minutes <= max).at(-1);
76
+ return toolContent(true, { film: film?.title ?? null });
77
+ }
78
+ ```
79
+
80
+ The server lists the engine’s tools it keeps next to its own, and sends each call to the engine or to `pick`. Its instructions start with the engine’s and add its own rule:
81
+
82
+ ```ts
83
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
84
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
85
+ import {
86
+ CallToolRequestSchema,
87
+ ListToolsRequestSchema,
88
+ } from "@modelcontextprotocol/sdk/types.js";
89
+ import {
90
+ callDocsTool, callEngineTool, docsTools, engineTools,
91
+ isDocsTool, isEngineTool, offlineDocs, ENGINE_INSTRUCTIONS,
92
+ } from "datanotes/mcp";
93
+
94
+
95
+ const reads = { only: ["list_tables", "describe_table", "select_rows"] };
96
+ const instructions = `${ENGINE_INSTRUCTIONS}\n\nTo suggest a film for ` +
97
+ "tonight, call pick_film_for_tonight.";
98
+ const server = new Server(
99
+ { name: "film-night", version: "1.0.0" },
100
+ { capabilities: { tools: {} }, instructions },
101
+ );
102
+ ```
103
+
104
+ The two handlers answer the agent’s `tools/list` and `tools/call` requests. A name outside the list answers an error, even when the engine has that operation. The server serves the docs tools, so `offlineDocs` points its errors to `read_docs` instead of a web address:
105
+
106
+ ```ts
107
+ server.setRequestHandler(ListToolsRequestSchema, () => ({
108
+ tools: [...engineTools(reads), ...docsTools(), pickFilm],
109
+ }));
110
+
111
+
112
+ server.setRequestHandler(CallToolRequestSchema, async (req) => {
113
+ const { name, arguments: args = {} } = req.params;
114
+ if (isEngineTool(name, reads)) {
115
+ return offlineDocs(await callEngineTool(db, name, args, reads));
116
+ }
117
+ if (isDocsTool(name)) {
118
+ return offlineDocs(await callDocsTool(name, args, { client: db }));
119
+ }
120
+ if (name === pickFilm.name) return pick(args);
121
+ return toolContent(false, `unknown tool: ${name}`);
122
+ });
123
+
124
+
125
+ await server.connect(new StdioServerTransport());
126
+ ```
127
+
128
+ An agent connected to this server sees seven tools: `list_tables`, `describe_table`, `select_rows`, `list_docs`, `read_docs`, `search_docs` and `pick_film_for_tonight`. With a [token](https://datanotes.dev/guides/security/tokens/) that only reads, the server can’t change your notes even if its code asks to. To log its tool calls on the engine’s change feed, the server reports them with [`tool-calls/record/`](https://datanotes.dev/guides/security/who-did-what/#report-what-an-agent-does).
129
+
130
+ ## Choose a way
131
+
132
+ Pick by who acts and what they need:
133
+
134
+ * **An agent that manages the database**: `datanotes mcp`, with the agent’s own token
135
+ * **A program that runs on its own**: the HTTP API or `datanotes/client`, with the program’s own token
136
+ * **Agents that work through your rules**: your own MCP server, with `datanotes/mcp`
137
+
138
+ The engine itself serves no tools of other programs: a program that offers tools to agents serves them from its own MCP server. An agent can connect to that server and to `datanotes mcp` at once.