datanotes 3.2.0 → 3.3.1
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.
- package/README.md +1 -0
- package/SECURITY.md +12 -0
- package/dist/api/client.d.cts +2 -0
- package/dist/api/client.d.ts +2 -0
- package/dist/api/docs.d.cts +14 -4
- package/dist/api/docs.d.ts +14 -4
- package/dist/api/extension-files.d.cts +23 -0
- package/dist/api/extension-files.d.ts +23 -0
- package/dist/api/extension-host.d.cts +108 -0
- package/dist/api/extension-host.d.ts +108 -0
- package/dist/api/extension-manifest.d.cts +154 -0
- package/dist/api/extension-manifest.d.ts +154 -0
- package/dist/api/extension-plan.d.cts +174 -0
- package/dist/api/extension-plan.d.ts +174 -0
- package/dist/api/extension-record.d.cts +65 -0
- package/dist/api/extension-record.d.ts +65 -0
- package/dist/api/extension-sdk.d.cts +65 -0
- package/dist/api/extension-sdk.d.ts +65 -0
- package/dist/api/extensions.cjs +19660 -0
- package/dist/api/extensions.d.cts +11 -0
- package/dist/api/extensions.d.ts +11 -0
- package/dist/api/extensions.js +19597 -0
- package/dist/api/index.cjs +1568 -375
- package/dist/api/index.js +1568 -375
- package/dist/api/mcp.cjs +1238 -27
- package/dist/api/mcp.js +1238 -27
- package/dist/datanotes.cjs +8365 -4335
- package/dist/docs/administration/extensions.md +213 -0
- package/dist/docs/api/operations/alter_table.md +4 -3
- package/dist/docs/api/operations/extensions_delete.md +29 -0
- package/dist/docs/api/operations/extensions_list.md +20 -0
- package/dist/docs/api/operations/extensions_record.md +44 -0
- package/dist/docs/api/operations/tags/extensions.md +9 -0
- package/dist/docs/api.md +9 -1
- package/dist/docs/develop/write-an-extension.md +369 -0
- package/dist/docs/guides/data-quality/check-constraints.md +10 -10
- package/dist/docs/guides/data-quality/hand-edits.md +8 -8
- package/dist/docs/guides/database-objects/bases.md +11 -11
- package/dist/docs/guides/database-objects/catalogs-and-schemas.md +30 -10
- package/dist/docs/guides/database-objects/columns.md +38 -38
- package/dist/docs/guides/database-objects/relations.md +28 -28
- package/dist/docs/guides/database-objects/tables.md +167 -43
- package/dist/docs/guides/database-objects/views.md +44 -44
- package/dist/docs/guides/history/change-data-feed.md +34 -34
- package/dist/docs/guides/history/journal-and-undo.md +12 -12
- package/dist/docs/guides/history/restore.md +20 -20
- package/dist/docs/guides/history/table-history.md +31 -31
- package/dist/docs/guides/read-and-write/automatic-links.md +15 -15
- package/dist/docs/guides/read-and-write/import-notes.md +20 -20
- package/dist/docs/guides/read-and-write/joins-and-aggregates.md +7 -7
- package/dist/docs/guides/read-and-write/read-and-filter.md +10 -10
- package/dist/docs/guides/read-and-write/write-rows.md +10 -10
- package/dist/docs/guides/search/text.md +1 -1
- package/dist/docs/guides/security/security-model.md +12 -0
- package/dist/docs/reference/cli.md +32 -1
- package/dist/docs/reference/glossary.md +8 -0
- package/dist/docs/reference/operations/auxiliary.md +1 -1
- package/dist/docs/reference/operations/rows.md +1 -1
- package/dist/docs/reference/operations/schema.md +3 -3
- package/dist/docs/reference/operations.md +4 -4
- package/dist/docs/release-notes.md +49 -0
- package/dist/docs/start/connect-agents.md +11 -0
- package/package.json +11 -1
package/README.md
CHANGED
|
@@ -15,6 +15,7 @@ Datanotes is the open-source database engine for Markdown: it runs a folder of n
|
|
|
15
15
|
- **Agents can't write garbage.** Every write is checked against its table: types, links, unique values and CHECK constraints. A refused write changes nothing. Its answer says which value broke which rule and how to fix it.
|
|
16
16
|
- **Every change can be undone.** The engine journals every write it makes. It records who made it: the agent, its session and its token. `undo_migration` takes any change back.
|
|
17
17
|
- **It's still just Markdown.** A row is a note, and its front matter keys are the columns. Read it, edit it, grep it or commit it to Git. The engine reads your hand edits and checks them too.
|
|
18
|
+
- **Extensions.** Install tables and MCP tools from a git repository, pinned and confirmed (`datanotes extension install`).
|
|
18
19
|
- **Local first.** Datanotes is Apache-2.0 and runs on your machine. Nothing leaves it unless you turn that feature on.
|
|
19
20
|
|
|
20
21
|
## When not to use it
|
package/SECURITY.md
CHANGED
|
@@ -65,6 +65,18 @@ The owner creates a named token with `POST /datanotes/tokens/create/`, and the e
|
|
|
65
65
|
|
|
66
66
|
A named token can’t create webhooks, manage tokens or read the engine’s status, and without `maintain` it can’t undo.
|
|
67
67
|
|
|
68
|
+
### Extensions
|
|
69
|
+
|
|
70
|
+
An extension adds tables and Model Context Protocol (MCP) tools from a git repository. Its code is trusted code that runs inside the MCP server's process, not in a sandbox. Install only extensions you trust, and read the plan first.
|
|
71
|
+
|
|
72
|
+
- **Pinned code**: the installer takes a version tag or a full commit id, and refuses a branch. It fetches the tag itself, even when a branch has the same name. It records the commit and the SHA-256 hashes of the manifest and the entry file. The server refuses cached code that differs.
|
|
73
|
+
- **No install scripts**: the installer runs no script, no build and no `npm install`. The entry file may import only Node built-ins. The installer reads the imports in the file. Code that builds a module name at run time escapes this check.
|
|
74
|
+
- **One token for each extension**: `ext-<name>` writes only the tables the manifest lists. It cannot change the schema or undo. The installer changes the schema with the owner's token, after you confirm the plan.
|
|
75
|
+
- **An invoker check**: before a tool that writes runs, the server checks the calling agent's token. That token must be able to write the tool's tables. A read-only client cannot write through an extension.
|
|
76
|
+
- **The secret stays on one machine**: the token file sits under the data home with mode 0600. The registry in the engine's store holds no secret.
|
|
77
|
+
|
|
78
|
+
Known limits: extension code is not isolated from the MCP server's process. Its token reads every table until read patterns ship. Migrations are declarative and run no code. [Install extensions](https://datanotes.dev/administration/extensions/) has the details.
|
|
79
|
+
|
|
68
80
|
### Who made each write
|
|
69
81
|
|
|
70
82
|
The engine journals every write with its actor. The client sends the agent’s name, model and session, so they’re self-reported. The server sets the credential, `auth` (`owner`, or the named token’s name), and no client can claim another one.
|
package/dist/api/client.d.cts
CHANGED
|
@@ -82,6 +82,8 @@ export interface RecordedToolCall {
|
|
|
82
82
|
takes_summary?: boolean;
|
|
83
83
|
}
|
|
84
84
|
export declare const fetchTransport: Transport;
|
|
85
|
+
/** `s` without its trailing "/" characters. A loop, not `/\/+$/`: that regex is quadratic on a long run of "/" not at the end. */
|
|
86
|
+
export declare function trimTrailingSlashes(s: string): string;
|
|
85
87
|
export declare class DatanotesClient {
|
|
86
88
|
private readonly o;
|
|
87
89
|
readonly url: string;
|
package/dist/api/client.d.ts
CHANGED
|
@@ -82,6 +82,8 @@ export interface RecordedToolCall {
|
|
|
82
82
|
takes_summary?: boolean;
|
|
83
83
|
}
|
|
84
84
|
export declare const fetchTransport: Transport;
|
|
85
|
+
/** `s` without its trailing "/" characters. A loop, not `/\/+$/`: that regex is quadratic on a long run of "/" not at the end. */
|
|
86
|
+
export declare function trimTrailingSlashes(s: string): string;
|
|
85
87
|
export declare class DatanotesClient {
|
|
86
88
|
private readonly o;
|
|
87
89
|
readonly url: string;
|
package/dist/api/docs.d.cts
CHANGED
|
@@ -131,14 +131,24 @@ export interface DocsQuery {
|
|
|
131
131
|
* word. Words that say nothing (`+the`) require nothing.
|
|
132
132
|
*/
|
|
133
133
|
export declare function parseQuery(query: string): DocsQuery;
|
|
134
|
-
/**
|
|
134
|
+
/** A folder of extra pages shown under a prefix of the docs (an installed extension's `docs/`). */
|
|
135
|
+
export interface DocsMount {
|
|
136
|
+
/** The path its pages appear under: "extensions/tasks". */
|
|
137
|
+
prefix: string;
|
|
138
|
+
/** The folder of .md pages. */
|
|
139
|
+
dir: string;
|
|
140
|
+
}
|
|
141
|
+
/** The docs in a folder (null: there are none, and every call says how to get them), and the mounts added to them. */
|
|
135
142
|
export declare class DocsLibrary {
|
|
136
143
|
readonly dir: string | null;
|
|
144
|
+
readonly mounts: readonly DocsMount[];
|
|
137
145
|
private loaded;
|
|
146
|
+
/** The keys of the pages that come from mounts: the docs' version leaves them out. */
|
|
147
|
+
private readonly mounted;
|
|
138
148
|
private index;
|
|
139
149
|
private hash;
|
|
140
150
|
private sectionList;
|
|
141
|
-
constructor(dir: string | null);
|
|
151
|
+
constructor(dir: string | null, mounts?: readonly DocsMount[]);
|
|
142
152
|
private missing;
|
|
143
153
|
private all;
|
|
144
154
|
/** The pages, sorted by path; only those under `prefix` (`guides/`) when it is given. */
|
|
@@ -206,6 +216,6 @@ export declare class DocsLibrary {
|
|
|
206
216
|
export declare const RRF_K = 60;
|
|
207
217
|
/** How many sections of each ranking hybrid search fuses. */
|
|
208
218
|
export declare const FUSION_DEPTH = 50;
|
|
209
|
-
/** The docs of a folder (default: `findDocsDir()`), loaded once per folder. */
|
|
210
|
-
export declare function openDocs(dir?: string | null): DocsLibrary;
|
|
219
|
+
/** The docs of a folder (default: `findDocsDir()`) with its mounts, loaded once per folder and mounts. */
|
|
220
|
+
export declare function openDocs(dir?: string | null, mounts?: readonly DocsMount[]): DocsLibrary;
|
|
211
221
|
export {};
|
package/dist/api/docs.d.ts
CHANGED
|
@@ -131,14 +131,24 @@ export interface DocsQuery {
|
|
|
131
131
|
* word. Words that say nothing (`+the`) require nothing.
|
|
132
132
|
*/
|
|
133
133
|
export declare function parseQuery(query: string): DocsQuery;
|
|
134
|
-
/**
|
|
134
|
+
/** A folder of extra pages shown under a prefix of the docs (an installed extension's `docs/`). */
|
|
135
|
+
export interface DocsMount {
|
|
136
|
+
/** The path its pages appear under: "extensions/tasks". */
|
|
137
|
+
prefix: string;
|
|
138
|
+
/** The folder of .md pages. */
|
|
139
|
+
dir: string;
|
|
140
|
+
}
|
|
141
|
+
/** The docs in a folder (null: there are none, and every call says how to get them), and the mounts added to them. */
|
|
135
142
|
export declare class DocsLibrary {
|
|
136
143
|
readonly dir: string | null;
|
|
144
|
+
readonly mounts: readonly DocsMount[];
|
|
137
145
|
private loaded;
|
|
146
|
+
/** The keys of the pages that come from mounts: the docs' version leaves them out. */
|
|
147
|
+
private readonly mounted;
|
|
138
148
|
private index;
|
|
139
149
|
private hash;
|
|
140
150
|
private sectionList;
|
|
141
|
-
constructor(dir: string | null);
|
|
151
|
+
constructor(dir: string | null, mounts?: readonly DocsMount[]);
|
|
142
152
|
private missing;
|
|
143
153
|
private all;
|
|
144
154
|
/** The pages, sorted by path; only those under `prefix` (`guides/`) when it is given. */
|
|
@@ -206,6 +216,6 @@ export declare class DocsLibrary {
|
|
|
206
216
|
export declare const RRF_K = 60;
|
|
207
217
|
/** How many sections of each ranking hybrid search fuses. */
|
|
208
218
|
export declare const FUSION_DEPTH = 50;
|
|
209
|
-
/** The docs of a folder (default: `findDocsDir()`), loaded once per folder. */
|
|
210
|
-
export declare function openDocs(dir?: string | null): DocsLibrary;
|
|
219
|
+
/** The docs of a folder (default: `findDocsDir()`) with its mounts, loaded once per folder and mounts. */
|
|
220
|
+
export declare function openDocs(dir?: string | null, mounts?: readonly DocsMount[]): DocsLibrary;
|
|
211
221
|
export {};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where an extension's files live on this machine (<data home>/extensions): the immutable code of each
|
|
3
|
+
* installed commit and the secret of the extension's token. The installation record itself is in the
|
|
4
|
+
* engine's store. No Node types in the exported signatures: the package's consumer check compiles
|
|
5
|
+
* without them.
|
|
6
|
+
*/
|
|
7
|
+
export declare const EXTENSIONS_FOLDER = "extensions";
|
|
8
|
+
/** <data home>/extensions. */
|
|
9
|
+
export declare function extensionsHome(env?: Record<string, string | undefined>): string;
|
|
10
|
+
/** The folder holding an immutable copy of an extension's published files at one commit. */
|
|
11
|
+
export declare function codeDir(home: string, name: string, commit: string): string;
|
|
12
|
+
/** The file holding an extension's token for one database. */
|
|
13
|
+
export declare function tokenPath(home: string, databaseId: string, name: string): string;
|
|
14
|
+
/** SHA-256 of the raw bytes, hex. */
|
|
15
|
+
export declare function sha256Hex(data: string | Uint8Array): string;
|
|
16
|
+
/** SHA-256 of a file's bytes, hex. */
|
|
17
|
+
export declare function fileSha256(file: string): string;
|
|
18
|
+
/** Write a token file (the token and a newline, mode 0600), creating its folders. */
|
|
19
|
+
export declare function writeTokenFile(file: string, token: string): void;
|
|
20
|
+
/** The token in a file, trimmed; null when the file is missing or empty. */
|
|
21
|
+
export declare function readTokenFile(file: string): string | null;
|
|
22
|
+
/** Delete a token file; a missing one is fine. */
|
|
23
|
+
export declare function removeTokenFile(file: string): void;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where an extension's files live on this machine (<data home>/extensions): the immutable code of each
|
|
3
|
+
* installed commit and the secret of the extension's token. The installation record itself is in the
|
|
4
|
+
* engine's store. No Node types in the exported signatures: the package's consumer check compiles
|
|
5
|
+
* without them.
|
|
6
|
+
*/
|
|
7
|
+
export declare const EXTENSIONS_FOLDER = "extensions";
|
|
8
|
+
/** <data home>/extensions. */
|
|
9
|
+
export declare function extensionsHome(env?: Record<string, string | undefined>): string;
|
|
10
|
+
/** The folder holding an immutable copy of an extension's published files at one commit. */
|
|
11
|
+
export declare function codeDir(home: string, name: string, commit: string): string;
|
|
12
|
+
/** The file holding an extension's token for one database. */
|
|
13
|
+
export declare function tokenPath(home: string, databaseId: string, name: string): string;
|
|
14
|
+
/** SHA-256 of the raw bytes, hex. */
|
|
15
|
+
export declare function sha256Hex(data: string | Uint8Array): string;
|
|
16
|
+
/** SHA-256 of a file's bytes, hex. */
|
|
17
|
+
export declare function fileSha256(file: string): string;
|
|
18
|
+
/** Write a token file (the token and a newline, mode 0600), creating its folders. */
|
|
19
|
+
export declare function writeTokenFile(file: string, token: string): void;
|
|
20
|
+
/** The token in a file, trimmed; null when the file is missing or empty. */
|
|
21
|
+
export declare function readTokenFile(file: string): string | null;
|
|
22
|
+
/** Delete a token file; a missing one is fine. */
|
|
23
|
+
export declare function removeTokenFile(file: string): void;
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What an MCP server needs to offer the extensions installed in a database: read the registry,
|
|
3
|
+
* verify and load each extension's code, build the context its tool handlers get (the extension's
|
|
4
|
+
* own token, never the agent's), check arguments and the client's write permission, and answer
|
|
5
|
+
* refusals with offline docs lines (3.0.9 of the extensions plan). `datanotes mcp` and DatanotesOS
|
|
6
|
+
* both use it through `datanotes/extensions`. The engine never runs extension code.
|
|
7
|
+
*/
|
|
8
|
+
import { type Transport } from "./client.cjs";
|
|
9
|
+
import { type DocsLibrary, type DocsMount } from "./docs.cjs";
|
|
10
|
+
import { type ExtensionManifest } from "./extension-manifest.cjs";
|
|
11
|
+
import { type InstalledExtension } from "./extension-record.cjs";
|
|
12
|
+
import { type ExtensionActor } from "./extension-sdk.cjs";
|
|
13
|
+
/** The log of the MCP server: a constant body, the variable parts as attributes. */
|
|
14
|
+
export type HostLog = (level: "DEBUG" | "INFO" | "WARN" | "ERROR", body: string, attributes?: Record<string, string | number | boolean>) => void;
|
|
15
|
+
export interface LoadOptions {
|
|
16
|
+
engineUrl: string;
|
|
17
|
+
/** Any token the engine accepts: reads the registry, the engine's version and time zone. */
|
|
18
|
+
token: string;
|
|
19
|
+
env?: Record<string, string | undefined>;
|
|
20
|
+
transport?: Transport;
|
|
21
|
+
/** Tool names the server already offers. */
|
|
22
|
+
reserved?: readonly string[];
|
|
23
|
+
/** Only the read-only tools (datanotes mcp --profile read). */
|
|
24
|
+
readOnlyOnly?: boolean;
|
|
25
|
+
log?: HostLog;
|
|
26
|
+
}
|
|
27
|
+
/** A tool as MCP lists it. */
|
|
28
|
+
export interface ExtensionToolDefinition {
|
|
29
|
+
name: string;
|
|
30
|
+
description: string;
|
|
31
|
+
inputSchema: Record<string, unknown>;
|
|
32
|
+
annotations: Record<string, unknown>;
|
|
33
|
+
_meta: {
|
|
34
|
+
"datanotes/extension": string;
|
|
35
|
+
"datanotes/tables": string[];
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
/** Who calls a tool: the agent as the MCP server knows it, and the token its client connected with. */
|
|
39
|
+
export interface ExtensionCaller {
|
|
40
|
+
actor: ExtensionActor;
|
|
41
|
+
clientToken: string;
|
|
42
|
+
}
|
|
43
|
+
export interface LoadedExtension {
|
|
44
|
+
readonly name: string;
|
|
45
|
+
readonly version: string;
|
|
46
|
+
/** "ext-<name>": the token's name, and the `served_by` of its tool calls. */
|
|
47
|
+
readonly servedBy: string;
|
|
48
|
+
readonly tools: ExtensionToolDefinition[];
|
|
49
|
+
readonly instructions: string;
|
|
50
|
+
/** Whether this server offers the tool: with readOnlyOnly, only the read-only ones. */
|
|
51
|
+
has(tool: string): boolean;
|
|
52
|
+
call(tool: string, args: Record<string, unknown>, caller: ExtensionCaller): Promise<{
|
|
53
|
+
ok: boolean;
|
|
54
|
+
value: unknown;
|
|
55
|
+
}>;
|
|
56
|
+
}
|
|
57
|
+
export type SkipCode = "api" | "code_missing" | "code_changed" | "engine" | "token_missing" | "tool_taken" | "entry" | "handler";
|
|
58
|
+
export interface SkippedExtension {
|
|
59
|
+
name: string;
|
|
60
|
+
version: string;
|
|
61
|
+
code: SkipCode;
|
|
62
|
+
reason: string;
|
|
63
|
+
}
|
|
64
|
+
export interface ExtensionLoad {
|
|
65
|
+
databaseId: string | null;
|
|
66
|
+
loaded: LoadedExtension[];
|
|
67
|
+
skipped: SkippedExtension[];
|
|
68
|
+
docsMounts: DocsMount[];
|
|
69
|
+
}
|
|
70
|
+
/** Does a qualified table name match a write pattern? (The rule of the engine's named tokens.) */
|
|
71
|
+
export declare function tableAllowed(full: string, patterns: readonly string[]): boolean;
|
|
72
|
+
/**
|
|
73
|
+
* The actor recorded for an engine call of an extension: the agent's, the tool it called, and the
|
|
74
|
+
* client's token name in the harness ("claude-code via phone").
|
|
75
|
+
*/
|
|
76
|
+
export declare function engineActor(actor: ExtensionActor, tool: string, clientName: string | null): ExtensionActor & {
|
|
77
|
+
tool: string;
|
|
78
|
+
};
|
|
79
|
+
/** Where an extension was installed from, as the commands print it: the git URL with its ref, or the folder. */
|
|
80
|
+
export declare function sourceLabel(r: InstalledExtension): string;
|
|
81
|
+
/**
|
|
82
|
+
* The file URL to import an extension's entry from. `pathToFileURL` writes `~` as `%7E`; Node decodes it,
|
|
83
|
+
* but other loaders (vite, under vitest) do not, and a Windows 8.3 short path (`C:\Users\RUNNER~1\...`)
|
|
84
|
+
* then fails to load. `~` is unreserved in a URL, so the literal form names the same file.
|
|
85
|
+
*/
|
|
86
|
+
export declare function entryUrl(file: string): string;
|
|
87
|
+
/** The problems of an entry file's default export against the manifest (empty: it fits). */
|
|
88
|
+
export declare function validateModule(mod: unknown, manifest: ExtensionManifest): string[];
|
|
89
|
+
/** The docs library of the package with the extensions' pages mounted under `extensions/<name>/`. */
|
|
90
|
+
export declare function extensionDocs(mounts: readonly DocsMount[], dir?: string | null): DocsLibrary;
|
|
91
|
+
/** What the engine knows of a client's token: its name and the tables it may write ("all": the owner). */
|
|
92
|
+
export interface ClientToken {
|
|
93
|
+
name: string;
|
|
94
|
+
write: readonly string[] | "all";
|
|
95
|
+
}
|
|
96
|
+
/** Asks the engine who a client's token is, and remembers the answer for a while. */
|
|
97
|
+
export declare class ClientTokens {
|
|
98
|
+
private readonly engineUrl;
|
|
99
|
+
private readonly transport?;
|
|
100
|
+
private readonly ttlMs;
|
|
101
|
+
private readonly clock;
|
|
102
|
+
private readonly cache;
|
|
103
|
+
constructor(engineUrl: string, transport?: Transport | undefined, ttlMs?: number, clock?: () => number);
|
|
104
|
+
/** The token's name and write patterns; null when the engine refuses the token. */
|
|
105
|
+
describe(token: string): Promise<ClientToken | null>;
|
|
106
|
+
}
|
|
107
|
+
/** Load the extensions installed in the database the engine serves; one that cannot be loaded is skipped with its reason. */
|
|
108
|
+
export declare function loadExtensions(o: LoadOptions): Promise<ExtensionLoad>;
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What an MCP server needs to offer the extensions installed in a database: read the registry,
|
|
3
|
+
* verify and load each extension's code, build the context its tool handlers get (the extension's
|
|
4
|
+
* own token, never the agent's), check arguments and the client's write permission, and answer
|
|
5
|
+
* refusals with offline docs lines (3.0.9 of the extensions plan). `datanotes mcp` and DatanotesOS
|
|
6
|
+
* both use it through `datanotes/extensions`. The engine never runs extension code.
|
|
7
|
+
*/
|
|
8
|
+
import { type Transport } from "./client.js";
|
|
9
|
+
import { type DocsLibrary, type DocsMount } from "./docs.js";
|
|
10
|
+
import { type ExtensionManifest } from "./extension-manifest.js";
|
|
11
|
+
import { type InstalledExtension } from "./extension-record.js";
|
|
12
|
+
import { type ExtensionActor } from "./extension-sdk.js";
|
|
13
|
+
/** The log of the MCP server: a constant body, the variable parts as attributes. */
|
|
14
|
+
export type HostLog = (level: "DEBUG" | "INFO" | "WARN" | "ERROR", body: string, attributes?: Record<string, string | number | boolean>) => void;
|
|
15
|
+
export interface LoadOptions {
|
|
16
|
+
engineUrl: string;
|
|
17
|
+
/** Any token the engine accepts: reads the registry, the engine's version and time zone. */
|
|
18
|
+
token: string;
|
|
19
|
+
env?: Record<string, string | undefined>;
|
|
20
|
+
transport?: Transport;
|
|
21
|
+
/** Tool names the server already offers. */
|
|
22
|
+
reserved?: readonly string[];
|
|
23
|
+
/** Only the read-only tools (datanotes mcp --profile read). */
|
|
24
|
+
readOnlyOnly?: boolean;
|
|
25
|
+
log?: HostLog;
|
|
26
|
+
}
|
|
27
|
+
/** A tool as MCP lists it. */
|
|
28
|
+
export interface ExtensionToolDefinition {
|
|
29
|
+
name: string;
|
|
30
|
+
description: string;
|
|
31
|
+
inputSchema: Record<string, unknown>;
|
|
32
|
+
annotations: Record<string, unknown>;
|
|
33
|
+
_meta: {
|
|
34
|
+
"datanotes/extension": string;
|
|
35
|
+
"datanotes/tables": string[];
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
/** Who calls a tool: the agent as the MCP server knows it, and the token its client connected with. */
|
|
39
|
+
export interface ExtensionCaller {
|
|
40
|
+
actor: ExtensionActor;
|
|
41
|
+
clientToken: string;
|
|
42
|
+
}
|
|
43
|
+
export interface LoadedExtension {
|
|
44
|
+
readonly name: string;
|
|
45
|
+
readonly version: string;
|
|
46
|
+
/** "ext-<name>": the token's name, and the `served_by` of its tool calls. */
|
|
47
|
+
readonly servedBy: string;
|
|
48
|
+
readonly tools: ExtensionToolDefinition[];
|
|
49
|
+
readonly instructions: string;
|
|
50
|
+
/** Whether this server offers the tool: with readOnlyOnly, only the read-only ones. */
|
|
51
|
+
has(tool: string): boolean;
|
|
52
|
+
call(tool: string, args: Record<string, unknown>, caller: ExtensionCaller): Promise<{
|
|
53
|
+
ok: boolean;
|
|
54
|
+
value: unknown;
|
|
55
|
+
}>;
|
|
56
|
+
}
|
|
57
|
+
export type SkipCode = "api" | "code_missing" | "code_changed" | "engine" | "token_missing" | "tool_taken" | "entry" | "handler";
|
|
58
|
+
export interface SkippedExtension {
|
|
59
|
+
name: string;
|
|
60
|
+
version: string;
|
|
61
|
+
code: SkipCode;
|
|
62
|
+
reason: string;
|
|
63
|
+
}
|
|
64
|
+
export interface ExtensionLoad {
|
|
65
|
+
databaseId: string | null;
|
|
66
|
+
loaded: LoadedExtension[];
|
|
67
|
+
skipped: SkippedExtension[];
|
|
68
|
+
docsMounts: DocsMount[];
|
|
69
|
+
}
|
|
70
|
+
/** Does a qualified table name match a write pattern? (The rule of the engine's named tokens.) */
|
|
71
|
+
export declare function tableAllowed(full: string, patterns: readonly string[]): boolean;
|
|
72
|
+
/**
|
|
73
|
+
* The actor recorded for an engine call of an extension: the agent's, the tool it called, and the
|
|
74
|
+
* client's token name in the harness ("claude-code via phone").
|
|
75
|
+
*/
|
|
76
|
+
export declare function engineActor(actor: ExtensionActor, tool: string, clientName: string | null): ExtensionActor & {
|
|
77
|
+
tool: string;
|
|
78
|
+
};
|
|
79
|
+
/** Where an extension was installed from, as the commands print it: the git URL with its ref, or the folder. */
|
|
80
|
+
export declare function sourceLabel(r: InstalledExtension): string;
|
|
81
|
+
/**
|
|
82
|
+
* The file URL to import an extension's entry from. `pathToFileURL` writes `~` as `%7E`; Node decodes it,
|
|
83
|
+
* but other loaders (vite, under vitest) do not, and a Windows 8.3 short path (`C:\Users\RUNNER~1\...`)
|
|
84
|
+
* then fails to load. `~` is unreserved in a URL, so the literal form names the same file.
|
|
85
|
+
*/
|
|
86
|
+
export declare function entryUrl(file: string): string;
|
|
87
|
+
/** The problems of an entry file's default export against the manifest (empty: it fits). */
|
|
88
|
+
export declare function validateModule(mod: unknown, manifest: ExtensionManifest): string[];
|
|
89
|
+
/** The docs library of the package with the extensions' pages mounted under `extensions/<name>/`. */
|
|
90
|
+
export declare function extensionDocs(mounts: readonly DocsMount[], dir?: string | null): DocsLibrary;
|
|
91
|
+
/** What the engine knows of a client's token: its name and the tables it may write ("all": the owner). */
|
|
92
|
+
export interface ClientToken {
|
|
93
|
+
name: string;
|
|
94
|
+
write: readonly string[] | "all";
|
|
95
|
+
}
|
|
96
|
+
/** Asks the engine who a client's token is, and remembers the answer for a while. */
|
|
97
|
+
export declare class ClientTokens {
|
|
98
|
+
private readonly engineUrl;
|
|
99
|
+
private readonly transport?;
|
|
100
|
+
private readonly ttlMs;
|
|
101
|
+
private readonly clock;
|
|
102
|
+
private readonly cache;
|
|
103
|
+
constructor(engineUrl: string, transport?: Transport | undefined, ttlMs?: number, clock?: () => number);
|
|
104
|
+
/** The token's name and write patterns; null when the engine refuses the token. */
|
|
105
|
+
describe(token: string): Promise<ClientToken | null>;
|
|
106
|
+
}
|
|
107
|
+
/** Load the extensions installed in the database the engine serves; one that cannot be loaded is skipped with its reason. */
|
|
108
|
+
export declare function loadExtensions(o: LoadOptions): Promise<ExtensionLoad>;
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The manifest of a Datanotes extension (`datanotes-extension.json`, format version 1): parsing, validation
|
|
3
|
+
* with errors that teach (every problem at once, each as `<path>: <text>`), version comparison and the
|
|
4
|
+
* binding of table roles to real tables. The installer reads only this file before the user confirms, so
|
|
5
|
+
* everything an extension can do is declared here. No dependency but ./guidance.js.
|
|
6
|
+
*/
|
|
7
|
+
/** The manifest's file name at the root of the extension's repository. */
|
|
8
|
+
export declare const MANIFEST_FILE = "datanotes-extension.json";
|
|
9
|
+
/** Docs pages and headings that the errors of extensions point to (the pages are in docs/). */
|
|
10
|
+
export declare const EXT_DOCS: {
|
|
11
|
+
readonly install: "https://datanotes.dev/administration/extensions.md#install-an-extension";
|
|
12
|
+
readonly tables: "https://datanotes.dev/administration/extensions.md#choose-the-tables-an-extension-uses";
|
|
13
|
+
readonly permissions: "https://datanotes.dev/administration/extensions.md#what-an-extension-can-do";
|
|
14
|
+
readonly update: "https://datanotes.dev/administration/extensions.md#update-an-extension";
|
|
15
|
+
readonly rollback: "https://datanotes.dev/administration/extensions.md#undo-an-install-or-an-update";
|
|
16
|
+
readonly remove: "https://datanotes.dev/administration/extensions.md#remove-an-extension";
|
|
17
|
+
readonly skipped: "https://datanotes.dev/administration/extensions.md#when-the-mcp-server-skips-an-extension";
|
|
18
|
+
readonly where: "https://datanotes.dev/administration/extensions.md#where-extensions-live";
|
|
19
|
+
readonly layout: "https://datanotes.dev/develop/write-an-extension.md#lay-out-the-repository";
|
|
20
|
+
readonly manifest: "https://datanotes.dev/develop/write-an-extension.md#write-the-manifest";
|
|
21
|
+
readonly declare: "https://datanotes.dev/develop/write-an-extension.md#declare-the-tables";
|
|
22
|
+
readonly tools: "https://datanotes.dev/develop/write-an-extension.md#write-the-tools";
|
|
23
|
+
readonly migrations: "https://datanotes.dev/develop/write-an-extension.md#migrate-the-schema-between-versions";
|
|
24
|
+
readonly publish: "https://datanotes.dev/develop/write-an-extension.md#check-and-publish-an-extension";
|
|
25
|
+
};
|
|
26
|
+
/** An extension's name (its token is `ext-<name>`). */
|
|
27
|
+
export declare const EXTENSION_NAME: RegExp;
|
|
28
|
+
/** A role, a table, a schema, a column, a base. */
|
|
29
|
+
export declare const IDENT: RegExp;
|
|
30
|
+
/** A tool's name. */
|
|
31
|
+
export declare const TOOL_NAME: RegExp;
|
|
32
|
+
/** The column types of the engine (a test checks the list against src/core). */
|
|
33
|
+
export declare const COLUMN_TYPES: readonly string[];
|
|
34
|
+
/** The attributes of a column spec in the engine (src/core/table-loader.ts; a test checks the list). */
|
|
35
|
+
export declare const COLUMN_ATTRS: readonly string[];
|
|
36
|
+
/** The operations a batch takes (src/server/rest.ts; a test checks the list). */
|
|
37
|
+
export declare const BATCH_OPERATIONS: readonly string[];
|
|
38
|
+
/** The operations that write rows. */
|
|
39
|
+
export declare const DATA_OPERATIONS: readonly string[];
|
|
40
|
+
/** An extension's own error: what is wrong, how to fix it, where to read more. */
|
|
41
|
+
export declare class ExtensionError extends Error {
|
|
42
|
+
readonly hint: string;
|
|
43
|
+
readonly docs: string;
|
|
44
|
+
readonly code: string;
|
|
45
|
+
readonly name: string;
|
|
46
|
+
constructor(message: string, hint: string, docs: string, code?: string);
|
|
47
|
+
}
|
|
48
|
+
/** A manifest that is not valid: every problem, in the order of the walk. */
|
|
49
|
+
export declare class ExtensionManifestError extends ExtensionError {
|
|
50
|
+
readonly problems: string[];
|
|
51
|
+
readonly name = "ExtensionManifestError";
|
|
52
|
+
constructor(problems: string[], file?: string);
|
|
53
|
+
}
|
|
54
|
+
/** Whether a table the extension depends on must have the column. */
|
|
55
|
+
export type ColumnNeed = "required" | "optional";
|
|
56
|
+
/** A column spec as the engine's operations take it, plus `need`. */
|
|
57
|
+
export interface ManifestColumn {
|
|
58
|
+
name: string;
|
|
59
|
+
type: string;
|
|
60
|
+
description: string;
|
|
61
|
+
need: ColumnNeed;
|
|
62
|
+
[attribute: string]: unknown;
|
|
63
|
+
}
|
|
64
|
+
export interface ManifestConstraint {
|
|
65
|
+
name: string;
|
|
66
|
+
check: Record<string, unknown>;
|
|
67
|
+
hint: string;
|
|
68
|
+
}
|
|
69
|
+
export interface ManifestTable {
|
|
70
|
+
owned: boolean;
|
|
71
|
+
/** The table's name when created; the role when missing. */
|
|
72
|
+
name?: string;
|
|
73
|
+
schema?: string;
|
|
74
|
+
names?: string[];
|
|
75
|
+
description: string;
|
|
76
|
+
aliases?: string[];
|
|
77
|
+
filename?: string;
|
|
78
|
+
columns: ManifestColumn[];
|
|
79
|
+
constraints?: ManifestConstraint[];
|
|
80
|
+
instructions?: string;
|
|
81
|
+
bases?: string[];
|
|
82
|
+
}
|
|
83
|
+
export interface ManifestBase {
|
|
84
|
+
description: string;
|
|
85
|
+
columns: ManifestColumn[];
|
|
86
|
+
}
|
|
87
|
+
export interface ManifestView {
|
|
88
|
+
description: string;
|
|
89
|
+
query: Record<string, unknown>;
|
|
90
|
+
aliases?: string[];
|
|
91
|
+
labels?: Record<string, unknown>;
|
|
92
|
+
}
|
|
93
|
+
export interface ManifestTool {
|
|
94
|
+
name: string;
|
|
95
|
+
description: string;
|
|
96
|
+
inputSchema: Record<string, unknown>;
|
|
97
|
+
annotations: Record<string, unknown>;
|
|
98
|
+
/** Roles the tool reads or writes. */
|
|
99
|
+
tables: string[];
|
|
100
|
+
/** Roles the tool writes. */
|
|
101
|
+
writes: string[];
|
|
102
|
+
}
|
|
103
|
+
export interface ManifestMigration {
|
|
104
|
+
version: string;
|
|
105
|
+
description: string;
|
|
106
|
+
operations: ({
|
|
107
|
+
op: string;
|
|
108
|
+
} & Record<string, unknown>)[];
|
|
109
|
+
}
|
|
110
|
+
export interface ExtensionManifest {
|
|
111
|
+
manifest_version: 1;
|
|
112
|
+
name: string;
|
|
113
|
+
version: string;
|
|
114
|
+
description: string;
|
|
115
|
+
datanotes: string;
|
|
116
|
+
api: 1;
|
|
117
|
+
entry: string;
|
|
118
|
+
default_schema: string;
|
|
119
|
+
instructions?: string;
|
|
120
|
+
tables: Record<string, ManifestTable>;
|
|
121
|
+
/** The keys of `tables`, in order. */
|
|
122
|
+
roles: string[];
|
|
123
|
+
bases: Record<string, ManifestBase>;
|
|
124
|
+
views: Record<string, ManifestView>;
|
|
125
|
+
permissions: {
|
|
126
|
+
write: string[];
|
|
127
|
+
};
|
|
128
|
+
tools: ManifestTool[];
|
|
129
|
+
migrations: ManifestMigration[];
|
|
130
|
+
}
|
|
131
|
+
export interface SemVer {
|
|
132
|
+
major: number;
|
|
133
|
+
minor: number;
|
|
134
|
+
patch: number;
|
|
135
|
+
pre: string | null;
|
|
136
|
+
}
|
|
137
|
+
/** MAJOR.MINOR.PATCH[-pre] (no leading `v`); null when it is not a version. */
|
|
138
|
+
export declare function parseVersion(v: string): SemVer | null;
|
|
139
|
+
/** Negative when a is older than b, 0 when equal, positive when newer. Throws for text that is not a version. */
|
|
140
|
+
export declare function compareVersions(a: string, b: string): number;
|
|
141
|
+
/** Whether an engine version meets a manifest's `">=X.Y.Z"`. A source run ("dev", "") meets every range. */
|
|
142
|
+
export declare function satisfiesEngine(range: string, engineVersion: string): boolean;
|
|
143
|
+
/** A string that is exactly a role: `@task`. */
|
|
144
|
+
export declare const ROLE_REF: RegExp;
|
|
145
|
+
/** A role inside a text: `@{task}`. */
|
|
146
|
+
export declare const ROLE_TEXT: RegExp;
|
|
147
|
+
/** A deep copy of `value` with every `@role` string and every `@{role}` in a string replaced by the role's table. Keys stay. */
|
|
148
|
+
export declare function bindRoles<T>(value: T, tables: Readonly<Record<string, string>>): T;
|
|
149
|
+
/** The roles a value names, in order of first appearance (both forms). */
|
|
150
|
+
export declare function rolesNamed(value: unknown): string[];
|
|
151
|
+
/** Read a manifest's text (a leading BOM is fine) and validate it. */
|
|
152
|
+
export declare function parseManifest(text: string, file?: string): ExtensionManifest;
|
|
153
|
+
/** Check a parsed manifest, collecting every problem; returns the typed manifest (a deep copy) or throws ExtensionManifestError. */
|
|
154
|
+
export declare function validateManifest(raw: unknown, file?: string): ExtensionManifest;
|