stateful-mcp 0.1.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/LICENSE +21 -0
- package/README.md +111 -0
- package/config/about/dictionary.md +8 -0
- package/config/about/event.md +8 -0
- package/config/about/filter.md +9 -0
- package/config/about/form.md +9 -0
- package/config/about/log.md +9 -0
- package/config/about/middleware.md +18 -0
- package/config/about/object.md +8 -0
- package/config/about.config.json +37 -0
- package/config/examples/dictionary.md +9 -0
- package/config/examples/event.md +10 -0
- package/config/examples/filter.md +13 -0
- package/config/examples/form.md +23 -0
- package/config/examples/log.md +14 -0
- package/config/examples/object.md +12 -0
- package/config/storage.config.json +39 -0
- package/config/tools.config.json +14 -0
- package/dist/adapters/engines/dataframe-bridge.py +89 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +37412 -0
- package/dist/src/adapters/engines/dataframe-engine.d.ts +33 -0
- package/dist/src/adapters/engines/dataframe-engine.d.ts.map +1 -0
- package/dist/src/adapters/engines/interfaces.d.ts +11 -0
- package/dist/src/adapters/engines/interfaces.d.ts.map +1 -0
- package/dist/src/adapters/engines/memory-query.d.ts +12 -0
- package/dist/src/adapters/engines/memory-query.d.ts.map +1 -0
- package/dist/src/adapters/engines/pg-query.d.ts +16 -0
- package/dist/src/adapters/engines/pg-query.d.ts.map +1 -0
- package/dist/src/adapters/engines/sqlite-query.d.ts +16 -0
- package/dist/src/adapters/engines/sqlite-query.d.ts.map +1 -0
- package/dist/src/adapters/storage/browser-repo.d.ts +116 -0
- package/dist/src/adapters/storage/browser-repo.d.ts.map +1 -0
- package/dist/src/adapters/storage/compliance.d.ts +13 -0
- package/dist/src/adapters/storage/compliance.d.ts.map +1 -0
- package/dist/src/adapters/storage/interfaces.d.ts +134 -0
- package/dist/src/adapters/storage/interfaces.d.ts.map +1 -0
- package/dist/src/adapters/storage/jsonl-repo.d.ts +178 -0
- package/dist/src/adapters/storage/jsonl-repo.d.ts.map +1 -0
- package/dist/src/adapters/storage/memory-repo.d.ts +129 -0
- package/dist/src/adapters/storage/memory-repo.d.ts.map +1 -0
- package/dist/src/adapters/storage/pg-repo.d.ts +38 -0
- package/dist/src/adapters/storage/pg-repo.d.ts.map +1 -0
- package/dist/src/adapters/storage/sqlite-repo.d.ts +91 -0
- package/dist/src/adapters/storage/sqlite-repo.d.ts.map +1 -0
- package/dist/src/adapters/validation/references.d.ts +6 -0
- package/dist/src/adapters/validation/references.d.ts.map +1 -0
- package/dist/src/adapters/validation/runner.d.ts +28 -0
- package/dist/src/adapters/validation/runner.d.ts.map +1 -0
- package/dist/src/config/loader.d.ts +33 -0
- package/dist/src/config/loader.d.ts.map +1 -0
- package/dist/src/config/loader.js +232 -0
- package/dist/src/config/pagination.d.ts +44 -0
- package/dist/src/config/pagination.d.ts.map +1 -0
- package/dist/src/config/types.d.ts +152 -0
- package/dist/src/config/types.d.ts.map +1 -0
- package/dist/src/config/types.js +1 -0
- package/dist/src/config/validator.d.ts +3 -0
- package/dist/src/config/validator.d.ts.map +1 -0
- package/dist/src/errors/format.d.ts +8 -0
- package/dist/src/errors/format.d.ts.map +1 -0
- package/dist/src/errors/types.d.ts +32 -0
- package/dist/src/errors/types.d.ts.map +1 -0
- package/dist/src/errors/types.js +44 -0
- package/dist/src/middleware/dictionary/interfaces.d.ts +43 -0
- package/dist/src/middleware/dictionary/interfaces.d.ts.map +1 -0
- package/dist/src/middleware/dictionary/resolver.d.ts +47 -0
- package/dist/src/middleware/dictionary/resolver.d.ts.map +1 -0
- package/dist/src/middleware/dictionary/store.d.ts +73 -0
- package/dist/src/middleware/dictionary/store.d.ts.map +1 -0
- package/dist/src/middleware/dictionary/types.d.ts +152 -0
- package/dist/src/middleware/dictionary/types.d.ts.map +1 -0
- package/dist/src/middleware/event/store.d.ts +59 -0
- package/dist/src/middleware/event/store.d.ts.map +1 -0
- package/dist/src/middleware/event/types.d.ts +48 -0
- package/dist/src/middleware/event/types.d.ts.map +1 -0
- package/dist/src/middleware/filter/store.d.ts +44 -0
- package/dist/src/middleware/filter/store.d.ts.map +1 -0
- package/dist/src/middleware/filter/types.d.ts +65 -0
- package/dist/src/middleware/filter/types.d.ts.map +1 -0
- package/dist/src/middleware/form/store.d.ts +43 -0
- package/dist/src/middleware/form/store.d.ts.map +1 -0
- package/dist/src/middleware/form/types.d.ts +21 -0
- package/dist/src/middleware/form/types.d.ts.map +1 -0
- package/dist/src/middleware/object/schema-walker.d.ts +4 -0
- package/dist/src/middleware/object/schema-walker.d.ts.map +1 -0
- package/dist/src/middleware/object/store.d.ts +62 -0
- package/dist/src/middleware/object/store.d.ts.map +1 -0
- package/dist/src/middleware/object/types.d.ts +19 -0
- package/dist/src/middleware/object/types.d.ts.map +1 -0
- package/dist/src/services/dictionary.d.ts +2 -0
- package/dist/src/services/dictionary.d.ts.map +1 -0
- package/dist/src/services/event.d.ts +2 -0
- package/dist/src/services/event.d.ts.map +1 -0
- package/dist/src/services/filter.d.ts +2 -0
- package/dist/src/services/filter.d.ts.map +1 -0
- package/dist/src/services/form.d.ts +2 -0
- package/dist/src/services/form.d.ts.map +1 -0
- package/dist/src/services/helper.d.ts +8 -0
- package/dist/src/services/helper.d.ts.map +1 -0
- package/dist/src/services/log.d.ts +2 -0
- package/dist/src/services/log.d.ts.map +1 -0
- package/dist/src/services/object.d.ts +2 -0
- package/dist/src/services/object.d.ts.map +1 -0
- package/dist/src/translation/compiler.d.ts +3 -0
- package/dist/src/translation/compiler.d.ts.map +1 -0
- package/dist/src/translation/pipeline.d.ts +3 -0
- package/dist/src/translation/pipeline.d.ts.map +1 -0
- package/dist/src/translation/resolver.d.ts +4 -0
- package/dist/src/translation/resolver.d.ts.map +1 -0
- package/dist/src/translation/types.d.ts +32 -0
- package/dist/src/translation/types.d.ts.map +1 -0
- package/dist/src/translation/validator.d.ts +3 -0
- package/dist/src/translation/validator.d.ts.map +1 -0
- package/docs/browser.md +96 -0
- package/docs/config.md +498 -0
- package/docs/dictionary.md +37 -0
- package/docs/event.md +35 -0
- package/docs/filter.md +32 -0
- package/docs/log.md +54 -0
- package/docs/object.md +29 -0
- package/docs/pipeline.md +116 -0
- package/docs/resource_types.md +138 -0
- package/docs/validation_examples.md +230 -0
- package/package.json +91 -0
package/docs/filter.md
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# Filter Service Reference Guide
|
|
2
|
+
|
|
3
|
+
The Filter Service provides an incremental, version-controlled builder for database query clauses. This allows client LLMs to construct and tweak complex SQL conditions across multiple turns.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 1. Decoupling Public Filters from Database Columns
|
|
8
|
+
|
|
9
|
+
A core design principle of the Filter Service is that **the client LLM should not know the internal database table structure or column names**. Instead, the LLM queries against logical, public-facing attribute definitions.
|
|
10
|
+
|
|
11
|
+
The **Translation Layer** maps these public queries into database column targets dynamically:
|
|
12
|
+
* Public attribute `email` maps to internal column `contact_email_address`.
|
|
13
|
+
* Public operator `is` maps to relational comparison `eq`.
|
|
14
|
+
* This decoupling allows database schemas to evolve (e.g. renaming columns, migrating tables) without breaking the LLM's client-facing tool configuration.
|
|
15
|
+
|
|
16
|
+
For full translation step mapping and compiler capabilities, refer to [docs/pipeline.md](file:///home/denny/lu/filter/docs/pipeline.md).
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## 2. Version-Controlled Query Lifecycle
|
|
21
|
+
|
|
22
|
+
Every mutation on a filter creates a new, immutable state node (commit) tracked in the session's DAG:
|
|
23
|
+
* **`filter_init`**: Starts a root node specifying target tool/table and optional default conditions.
|
|
24
|
+
* **`filter_add`**: Appends logical clauses (`AND`/`OR`) and returns a new version ID.
|
|
25
|
+
* **`filter_inspect`**: Displays the active rules and compiled SQL representation at a specific version.
|
|
26
|
+
* **`filter_compress`**: Flattens a linear chain of version nodes to reduce session storage overhead.
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## 3. Supported Dialects
|
|
31
|
+
* **SQLite**: Parameters are bound using index numbering (`?`).
|
|
32
|
+
* **PostgreSQL**: Parameters are compiled using position notation (`$1`, `$2`), and nested path syntax is compiled into JSONB arrow access operators (e.g. `metadata->>'status'`).
|
package/docs/log.md
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# Log Service Reference Guide
|
|
2
|
+
|
|
3
|
+
The Log Service is a read-only history walker over the versioned state created by the Filter and Object services. Where those services *build* checkpointed state, the Log Service *traverses* it — letting you page backward through a filter or object's ancestry (the parent-pointer DAG) to inspect how it evolved, one checkpoint at a time.
|
|
4
|
+
|
|
5
|
+
It is intentionally separate from the Event Service: the Event Service manages an append-only array of structured records, whereas the Log Service replays the lineage of a single filter or object and surfaces the forward/backward deltas between checkpoints.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 1. Stateful Traversal (not stateful mutation)
|
|
10
|
+
|
|
11
|
+
Unlike the other services, the Log Service holds **no state of its own**. It reads the session stores of the Filter and Object services (`filter_session_state` / `object_session_state`) that are already configured in `storage.config.json`. This means it can only walk history that still exists in the session — running `filter_gc` / `object_gc` will prune the very checkpoints the Log Service would otherwise traverse.
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## 2. Tools
|
|
16
|
+
|
|
17
|
+
### `log_open`
|
|
18
|
+
Begins a traversal from a starting filter or object ID/alias and returns the first page of history.
|
|
19
|
+
|
|
20
|
+
* **`type`**: `"filter"` or `"object"` — which store to walk.
|
|
21
|
+
* **`session_id`**: The session whose checkpoints you want to inspect.
|
|
22
|
+
* **`id_or_alias`**: The starting checkpoint (typically the current tip, or a named alias).
|
|
23
|
+
* **`limit`**: Page size (default `5`).
|
|
24
|
+
|
|
25
|
+
The response contains:
|
|
26
|
+
* **`entries`**: Ordered from the starting checkpoint back toward the root.
|
|
27
|
+
* For **filters**: each entry carries `{ id, parent_id, created_at, rules }` — `rules` is the *delta* (`add`/`remove`) recorded at that checkpoint.
|
|
28
|
+
* For **objects**: each entry carries `{ id, parent_id, created_at, delta }` — `delta` is the sparse set of keys that changed relative to the checkpoint's parent (computed by diffing the parent's data).
|
|
29
|
+
* **`next_page_token`**: A signed, stateless token to fetch the next page, or `null` when the root is reached.
|
|
30
|
+
* **`has_more`**: Convenience boolean mirroring whether `next_page_token` is present.
|
|
31
|
+
|
|
32
|
+
### `log_next`
|
|
33
|
+
Fetches the next page using a previously returned `next_page_token`. It takes a single argument, **`page_token`**, and returns the same `{ entries, next_page_token, has_more }` shape as `log_open`. Replaying tokens walks further back up the parent chain until `next_page_token` is `null`.
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## 3. The Page Token (stateless pagination)
|
|
38
|
+
|
|
39
|
+
Pagination is driven by a cryptographically signed token rather than server-side cursor state:
|
|
40
|
+
|
|
41
|
+
* The token wraps the current position (`type`, `sessionId`, `currentNodeId`, `pageSize`, optional `userId`).
|
|
42
|
+
* It is signed with HMAC-SHA256 using `LOG_SERVICE_SECRET` (set this env var for stable tokens across restarts; otherwise a random secret is generated on startup).
|
|
43
|
+
* On `log_next`, the signature is verified before the token is trusted. A tampered or forged token is rejected.
|
|
44
|
+
|
|
45
|
+
Because the token is self-contained, the Log Service remains stateless — no traversal session lives on the server between calls.
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## 4. Practical Notes
|
|
50
|
+
|
|
51
|
+
* **Direction**: History flows from the newest checkpoint (the one you pass to `log_open`) toward the oldest (the root with `parent_id: null`).
|
|
52
|
+
* **Filter deltas**: `rules` is the per-checkpoint change set, not the full accumulated filter. To see the fully projected filter at any checkpoint, use `filter_get_filter` on that `id`.
|
|
53
|
+
* **Object deltas**: `delta` shows only the keys that differ from the parent; the full object at any checkpoint can be retrieved with `object_get` / `object_inspect`.
|
|
54
|
+
* **No write path**: `log_open` / `log_next` are strictly read-only by design — they never create, mutate, or compress checkpoints.
|
package/docs/object.md
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Object Service Reference Guide
|
|
2
|
+
|
|
3
|
+
The Object Service manages structured JSON documents (e.g. order forms, medical prescriptions, patient SOAP notes) with stateful delta tracking and strict validation guards.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 1. Stateful Object Lifecycle
|
|
8
|
+
|
|
9
|
+
* **`object_init`**: Instantiates a new empty object or pre-populates it with initial data matching a registered schema.
|
|
10
|
+
* **`object_patch`**: Applies a sparse JSON patch delta on an existing object ID, returning a new immutable checkpoint.
|
|
11
|
+
* **`object_validate`**: Runs the object through the Ajv compiler and cross-field constraint validator, returning a structured list of errors and warnings.
|
|
12
|
+
* **`object_resolve`**: Finalizes the object. Resolves recursive lazy reference links (e.g. referencing other entities or variables) and returns the complete, resolved document.
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## 2. Validation & Schema Guards
|
|
17
|
+
|
|
18
|
+
* **JSON Schema validation**: Checked via Ajv at each checkpoint.
|
|
19
|
+
* **Cross-Field constraints**: Custom rules like ensuring `start_date < end_date` or verifying ranges.
|
|
20
|
+
* **Cycle detection**: Ensures lazy reference links do not form recursive loops.
|
|
21
|
+
* **Schema guard**: Comparing two object versions with differing schemas throws a `SCHEMA_MISMATCH` error.
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## 3. Template Loading (`from_saved`)
|
|
26
|
+
|
|
27
|
+
To reduce token overhead on repeated document creation, the Object Service supports loading saved templates:
|
|
28
|
+
* Templates can be saved to persistent storage.
|
|
29
|
+
* Call `object_from_saved(template_id)` to clone a pre-populated baseline state, allowing the LLM to only patch the specific fields that differ for the current transaction.
|
package/docs/pipeline.md
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# Query Compilation & Translation Pipeline
|
|
2
|
+
|
|
3
|
+
This document explains how user-defined query filters are translated and compiled into execution-ready database commands (SQLite/Postgres).
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 1. The Pipeline Flow
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
┌─────────────────┐
|
|
11
|
+
│ Filter State ID │ (VCS Commit Node)
|
|
12
|
+
└────────┬────────┘
|
|
13
|
+
│
|
|
14
|
+
▼
|
|
15
|
+
┌─────────────────┐
|
|
16
|
+
│ project() State │ (Topological project of all rules)
|
|
17
|
+
└────────┬────────┘
|
|
18
|
+
│
|
|
19
|
+
▼
|
|
20
|
+
┌─────────────────┐
|
|
21
|
+
│ translate() │ (Dynamic dialect field & operator mapping)
|
|
22
|
+
└────────┬────────┘
|
|
23
|
+
│
|
|
24
|
+
▼
|
|
25
|
+
┌─────────────────┐
|
|
26
|
+
│ compile() │ (SQL generator: Postgres / SQLite)
|
|
27
|
+
└─────────────────┘
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## 2. Dynamic Translation Mapping (`translation`)
|
|
33
|
+
|
|
34
|
+
The `translation` property on a tool defines how user-supplied field identifiers and operators are preprocessed before being passed to the SQL generator.
|
|
35
|
+
|
|
36
|
+
### Example Translation Schema
|
|
37
|
+
A translation map is configured using the `TableTranslation` schema structure under `properties`:
|
|
38
|
+
|
|
39
|
+
```json
|
|
40
|
+
{
|
|
41
|
+
"properties": {
|
|
42
|
+
"customer_name": {
|
|
43
|
+
"internal": "billing_name",
|
|
44
|
+
"allowed_operators": ["eq", "neq"]
|
|
45
|
+
},
|
|
46
|
+
"age": {
|
|
47
|
+
"internal": "birth_date",
|
|
48
|
+
"transform": {
|
|
49
|
+
"pipeline": [
|
|
50
|
+
{
|
|
51
|
+
"op": "date_diff",
|
|
52
|
+
"args": [{ "$init": "value" }, { "$fn": "now" }],
|
|
53
|
+
"return_var": "calculated_age"
|
|
54
|
+
}
|
|
55
|
+
]
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
* **`internal`**: Maps the public field name (e.g. `customer_name`) to its internal database column (e.g. `billing_name`).
|
|
63
|
+
* **`allowed_operators`**: Narrows down which operator families can be queried on the field (e.g. only allowing `eq` and `neq` on name fields).
|
|
64
|
+
* **`transform`**: Defines pipeline conversion steps using arithmetic, date, or nested JSON operations to transform values dynamically before compilation.
|
|
65
|
+
* **`$fn` Reference**: Represents dynamic system values evaluated at run time:
|
|
66
|
+
- `{ "$fn": "now" }`: Resolves to the current date string (`YYYY-MM-DD`). Compiles to `date('now')` in SQLite and `CURRENT_DATE` in PostgreSQL.
|
|
67
|
+
- `{ "$fn": "utc_time" }`: Resolves to the current UTC timestamp string (`ISO 8601`). Compiles to `datetime('now')` in SQLite and `CURRENT_TIMESTAMP` in PostgreSQL.
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
## 3. SQL Compilation Engines
|
|
72
|
+
|
|
73
|
+
The middleware compiles the final logical state into target database dialects:
|
|
74
|
+
|
|
75
|
+
### SQLite Engine
|
|
76
|
+
* Compiles logical operations (e.g. `like`, `geq`) to standard ANSI SQL parameters.
|
|
77
|
+
* Validates properties against the registered `table_schemas` in `tools.config.json` before execution.
|
|
78
|
+
|
|
79
|
+
### PostgreSQL Engine
|
|
80
|
+
* Generates dialect-safe parameterized SQL (`$1`, `$2`, etc.) to prevent SQL injection.
|
|
81
|
+
* Supports nested JSONB path queries (e.g. `meta->>'source'`) automatically.
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
## 4. Available Pipeline Operators (`OpName`)
|
|
86
|
+
|
|
87
|
+
The following operators can be used within translation pipelines. They are categorized by family:
|
|
88
|
+
|
|
89
|
+
### Arithmetic Operations
|
|
90
|
+
Used to modify or combine numeric field values.
|
|
91
|
+
* **`add`**: Sums two numbers. E.g. `{ "op": "add", "args": [{ "$init": "value" }, 10] }`
|
|
92
|
+
* **`sub`**: Subtracts second argument from first.
|
|
93
|
+
* **`mul`**: Multiplies two numbers.
|
|
94
|
+
* **`div`**: Divides first argument by second.
|
|
95
|
+
* **`mod`**: Modulo arithmetic (remainder).
|
|
96
|
+
* **`exp`**: Exponential power ($x^y$).
|
|
97
|
+
|
|
98
|
+
### Comparison Operations
|
|
99
|
+
Performs boolean comparisons.
|
|
100
|
+
* **`lt`** ($<$), **`leq`** ($\le$), **`eq`** ($=$), **`neq`** ($\ne$), **`geq`** ($\ge$), **`gt`** ($>$).
|
|
101
|
+
* E.g. `{ "op": "gt", "args": [{ "$init": "value" }, 100] }`
|
|
102
|
+
|
|
103
|
+
### Date Operations
|
|
104
|
+
Parses and extracts calendar details from ISO date strings.
|
|
105
|
+
* **`year`**: Extracts year. E.g. `{ "op": "year", "args": [{ "$init": "value" }] }` (returns e.g. `2026`).
|
|
106
|
+
* **`month`**: Extracts month index (`1` to `12`).
|
|
107
|
+
* **`day`**: Extracts calendar day of the month (`1` to `31`).
|
|
108
|
+
* **`quarter`**: Extracts quarter index (`1` to `4`).
|
|
109
|
+
* **`date_diff`**: Days between two dates. E.g. `{ "op": "date_diff", "args": [{ "$init": "value" }, { "$fn": "now" }] }`
|
|
110
|
+
|
|
111
|
+
### Nested Access & JSON Operations
|
|
112
|
+
Allows parsing and exploring nested JSON structures.
|
|
113
|
+
* **`json_parse`**: Deserializes a raw JSON string into a structured object.
|
|
114
|
+
* **`get`**: Accesses a property at a specific key path on an object.
|
|
115
|
+
* E.g. `{ "op": "get", "args": [{ "$var": "parsed_object" }, "metadata", "authors", 0] }`
|
|
116
|
+
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
# Resource Locators & Database Specifications
|
|
2
|
+
|
|
3
|
+
This document outlines the detailed specifications for `ResourceLocator` parameters and the database schema fields required when configuring SQLite or PostgreSQL storage backends.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 1. Resource Locator Types (`ResourceLocator`)
|
|
8
|
+
|
|
9
|
+
### 1.1 `file`
|
|
10
|
+
Loads JSON or text assets directly from the local filesystem.
|
|
11
|
+
* **Fields**:
|
|
12
|
+
* `path` (string, required): Filesystem path relative to the workspace root.
|
|
13
|
+
* **Example**:
|
|
14
|
+
```json
|
|
15
|
+
{ "_type": "file", "path": "config/pharmacy_dictionary.json" }
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
### 1.2 `remote_url`
|
|
19
|
+
Fetches configuration or constants over HTTP/HTTPS.
|
|
20
|
+
* **Fields**:
|
|
21
|
+
* `url` (string, required): The target endpoint.
|
|
22
|
+
* `headers` (object, optional): Map of headers (e.g. `Authorization`, `Accept`).
|
|
23
|
+
* `ttl_ms` (number, optional): Time-to-live caching interval in milliseconds.
|
|
24
|
+
* **Variable Substitutions**:
|
|
25
|
+
* **Boot-Time (Environment Variables)**: Any string prefixing `env:` (e.g. `env:DATABASE_URL`) anywhere in the configuration is substituted with the value of the environment variable at server startup.
|
|
26
|
+
* **Runtime (Contextual Variables)**: Any bracketed template placeholder `{variableName}` (e.g., `{userId}`, `{department}`, `{employeeId}`) is replaced dynamically at runtime on a per-request basis with the caller's contextual parameters. The parser replaces the brackets and variable names with URL-encoded parameters supplied by the client's request context. This replacement is supported in both the `url` property of `remote_url` and the `path` property of `file` locators. E.g., `"path": "config/{department}/constants.json"`.
|
|
27
|
+
* **Example**:
|
|
28
|
+
```json
|
|
29
|
+
{
|
|
30
|
+
"_type": "remote_url",
|
|
31
|
+
"url": "https://api.firm.internal/users/{userId}/constants",
|
|
32
|
+
"headers": { "Authorization": "Bearer token123" },
|
|
33
|
+
"ttl_ms": 600000
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### 1.3 `adapter`
|
|
38
|
+
Instantiates registered database storage repositories.
|
|
39
|
+
* **Fields**:
|
|
40
|
+
* `name` (string, required): Name of the adapter (`"sqlite"` or `"postgres"`).
|
|
41
|
+
* `options` (object, required): Connection parameters:
|
|
42
|
+
* `url` (string, required): Connection URL (e.g. `sqlite://data/filter_sessions.db` or `postgresql://user:pass@localhost:5432/dbname`).
|
|
43
|
+
* **Example**:
|
|
44
|
+
```json
|
|
45
|
+
{
|
|
46
|
+
"_type": "adapter",
|
|
47
|
+
"name": "sqlite",
|
|
48
|
+
"options": { "url": "sqlite://data/filter_sessions.db" }
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### 1.4 Behavior on Stateful vs. Read-Only Services
|
|
53
|
+
|
|
54
|
+
The middleware treats resource locators differently depending on whether the service requires write operations (stateful) or is read-only:
|
|
55
|
+
|
|
56
|
+
#### A. Read-Only & Configuration Services (Schemas, Dictionaries, Constants)
|
|
57
|
+
* **How it works**: These can be configured using `file` or `remote_url` locators. The middleware fetches or reads the entire asset at startup (or after TTL expires) and holds it in memory for instant lookups.
|
|
58
|
+
* **JSON/JSONL Support**: You can point a `file` locator to a static JSON ontology file for the Dictionary Service.
|
|
59
|
+
|
|
60
|
+
#### B. Stateful & Transactional Services (Filter conditions, Object states, Event logs)
|
|
61
|
+
* **How it works**: Stateful services require transactional mutation logic, branch merges, LCA graph traversals, and targeted garbage collection. Because of this complexity, they **require a database adapter** (`sqlite` or `postgres`).
|
|
62
|
+
* **Fallback Behavior**: If you configure a stateful service using a standard `.json` `file` or `remote_url` locator directly, the service falls back to its **in-memory storage engine**.
|
|
63
|
+
* **Special Case: JSONL Files (`.jsonl`)**: If a stateful service `file` locator points to a `.jsonl` file, the middleware treats it as a **built-in append-only file database**:
|
|
64
|
+
* **Schema**: Each line in the `.jsonl` file is a single JSON-serialized object matching the exact column keys defined in Section 2 (e.g. `commitId`, `parentCommitId`, `mutations`).
|
|
65
|
+
* **Startup Read**: On server initialization, the service reads the `.jsonl` file line-by-line to reconstruct the commit DAG in memory.
|
|
66
|
+
* **Mutations**: Any new checkpoint or commit appends a new serialized JSON line to the file.
|
|
67
|
+
* **GC/Compression**: When garbage collection or compression runs, the system rewrites the `.jsonl` file containing only the survived/flattened commits to prevent file size bloat.
|
|
68
|
+
* **Custom File/HTTP Backends**: To save stateful commits to other formats, you must write a custom storage adapter class (implementing the store interfaces in `src/adapters/storage/interfaces.ts`) and register it under a custom name via `registerAdapter`.
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
## 2. Database Schema Specifications
|
|
73
|
+
|
|
74
|
+
When utilizing `sqlite` or `postgres` storage adapters, the databases must implement the following structures. Columns storing arrays or objects must map to `TEXT` (JSON-serialized strings) in SQLite, and `JSONB` in PostgreSQL.
|
|
75
|
+
|
|
76
|
+
### 2.1 Filter VCS Tables
|
|
77
|
+
|
|
78
|
+
#### Table: `filter_conditions`
|
|
79
|
+
Stores logical condition nodes in the query builder DAG.
|
|
80
|
+
* **`filterId`** (VARCHAR/TEXT, Primary Key): Unique checkpoint node ID.
|
|
81
|
+
* **`parentFilterId`** (VARCHAR/TEXT, Nullable): Parent checkpoint ID.
|
|
82
|
+
* **`toolName`** (VARCHAR/TEXT): Target tool name.
|
|
83
|
+
* **`tableName`** (VARCHAR/TEXT): Target table name.
|
|
84
|
+
* **`rules`** (JSON/TEXT): Array of serialized conditions.
|
|
85
|
+
* **`schema_snapshot`** (JSON/TEXT, Nullable): JSON schema copy at checkpoint time.
|
|
86
|
+
* **`linearDepth`** (INTEGER): Distance from nearest branch or root.
|
|
87
|
+
* **`gcLock`** (BOOLEAN): If true, prevents pruning during GC.
|
|
88
|
+
* **`createdAt`** (TIMESTAMP/TEXT): Creation timestamp.
|
|
89
|
+
|
|
90
|
+
#### Table: `filter_aliases`
|
|
91
|
+
* **`sessionId`** (VARCHAR/TEXT): Active session.
|
|
92
|
+
* **`alias`** (VARCHAR/TEXT): Shorthand descriptor (e.g. `"main"`).
|
|
93
|
+
* **`targetId`** (VARCHAR/TEXT): Pointed commit node ID.
|
|
94
|
+
* *Composite Primary Key*: `(sessionId, alias)`
|
|
95
|
+
|
|
96
|
+
---
|
|
97
|
+
|
|
98
|
+
### 2.2 Object VCS Tables
|
|
99
|
+
|
|
100
|
+
#### Table: `object_states`
|
|
101
|
+
Stores structured document checkpoints.
|
|
102
|
+
* **`objectId`** (VARCHAR/TEXT, Primary Key): Unique object version ID.
|
|
103
|
+
* **`parentObjectId`** (VARCHAR/TEXT, Nullable): Parent object version ID.
|
|
104
|
+
* **`schemaName`** (VARCHAR/TEXT): Schema validator identifier.
|
|
105
|
+
* **`data`** (JSON/TEXT): Key-value properties of the object.
|
|
106
|
+
* **`linearDepth`** (INTEGER): Node chain depth.
|
|
107
|
+
* **`gcLock`** (BOOLEAN): Lock status.
|
|
108
|
+
* **`createdAt`** (TIMESTAMP/TEXT): Timestamp.
|
|
109
|
+
|
|
110
|
+
#### Table: `object_aliases`
|
|
111
|
+
* **`sessionId`** (VARCHAR/TEXT)
|
|
112
|
+
* **`alias`** (VARCHAR/TEXT)
|
|
113
|
+
* **`targetId`** (VARCHAR/TEXT)
|
|
114
|
+
* *Composite Primary Key*: `(sessionId, alias)`
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
### 2.3 Event VCS Tables
|
|
119
|
+
|
|
120
|
+
#### Table: `event_commits`
|
|
121
|
+
Stores VCS commits representing mutations on event logs.
|
|
122
|
+
* **`commitId`** (VARCHAR/TEXT, Primary Key): Unique commit ID.
|
|
123
|
+
* **`parentCommitId`** (VARCHAR/TEXT, Nullable): Parent commit ID.
|
|
124
|
+
* **`sessionId`** (VARCHAR/TEXT): Associated session.
|
|
125
|
+
* **`operation`** (VARCHAR/TEXT): Mutation type (`"add"`, `"update"`, `"remove"`, `"merge"`).
|
|
126
|
+
* **`mutations`** (JSON/TEXT): Array of mutations (detailing `event_id`, `before_data`, `data`, and `mutation_parent_ids`).
|
|
127
|
+
* **`mergeSourceCommitIds`** (JSON/TEXT, Nullable): Array of source merge parent IDs.
|
|
128
|
+
* **`mergeAcceptedIds`** (JSON/TEXT, Nullable): Array of accepted resolutions.
|
|
129
|
+
* **`mergeRejectedIds`** (JSON/TEXT, Nullable): Array of rejected resolutions.
|
|
130
|
+
* **`linearDepth`** (INTEGER): Depth.
|
|
131
|
+
* **`gcLock`** (BOOLEAN): Lock status.
|
|
132
|
+
* **`createdAt`** (TIMESTAMP/TEXT): Timestamp.
|
|
133
|
+
|
|
134
|
+
#### Table: `event_aliases`
|
|
135
|
+
* **`sessionId`** (VARCHAR/TEXT)
|
|
136
|
+
* **`alias`** (VARCHAR/TEXT)
|
|
137
|
+
* **`targetId`** (VARCHAR/TEXT)
|
|
138
|
+
* *Composite Primary Key*: `(sessionId, alias)`
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
# Validation Engine Examples
|
|
2
|
+
|
|
3
|
+
The `validation_engine` parameter in `tools.config.json` points to an external JSON Schema or Ajv configuration file to enforce advanced logic constraints on public filter inputs before they hit database compilation.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Example 1: Drug Dosage Threshold Restrictions (JSON Schema)
|
|
8
|
+
This validation file enforces that when querying pharmacy databases for controlled narcotics, the dosage or quantity filtered must not exceed strict medical limits.
|
|
9
|
+
|
|
10
|
+
### Configuration (`config/tools.config.json`)
|
|
11
|
+
```json
|
|
12
|
+
{
|
|
13
|
+
"tools": {
|
|
14
|
+
"dispense_medication": {
|
|
15
|
+
"schema": { "_type": "file", "path": "schemas/dispense.json" },
|
|
16
|
+
"validation_engine": { "_type": "file", "path": "validators/dispense_rules.json" },
|
|
17
|
+
"engine": { "_type": "adapter", "name": "postgres", "options": { "url": "env:DATABASE_URL" } }
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
### Validator File (`validators/dispense_rules.json`)
|
|
24
|
+
```json
|
|
25
|
+
{
|
|
26
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
27
|
+
"title": "Narcotics Dosage Guard",
|
|
28
|
+
"type": "array",
|
|
29
|
+
"items": {
|
|
30
|
+
"type": "object",
|
|
31
|
+
"properties": {
|
|
32
|
+
"property": { "type": "string" },
|
|
33
|
+
"operator": { "type": "string" },
|
|
34
|
+
"value": { "type": "any" }
|
|
35
|
+
},
|
|
36
|
+
"allOf": [
|
|
37
|
+
{
|
|
38
|
+
"if": {
|
|
39
|
+
"properties": {
|
|
40
|
+
"property": { "const": "qty" },
|
|
41
|
+
"operator": { "enum": ["eq", "gt", "geq"] }
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
"then": {
|
|
45
|
+
"properties": {
|
|
46
|
+
"value": {
|
|
47
|
+
"type": "number",
|
|
48
|
+
"maximum": 120,
|
|
49
|
+
"description": "Controlled narcotics dispense limit exceeded (Max: 120 units)"
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
]
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
## Example 2: Allowed Date Range Constraints (JSON Schema)
|
|
62
|
+
Enforces constraints ensuring start/end date ranges in transaction history queries are within a maximum period (e.g., no queries spanning more than 90 days).
|
|
63
|
+
|
|
64
|
+
### Validator File (`validators/date_range_rules.json`)
|
|
65
|
+
```json
|
|
66
|
+
{
|
|
67
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
68
|
+
"title": "90-Day Transaction Query Guard",
|
|
69
|
+
"type": "array",
|
|
70
|
+
"items": {
|
|
71
|
+
"type": "object",
|
|
72
|
+
"properties": {
|
|
73
|
+
"property": { "type": "string" },
|
|
74
|
+
"operator": { "type": "string" },
|
|
75
|
+
"value": { "type": "any" }
|
|
76
|
+
},
|
|
77
|
+
"allOf": [
|
|
78
|
+
{
|
|
79
|
+
"if": {
|
|
80
|
+
"properties": {
|
|
81
|
+
"property": { "const": "transaction_date" },
|
|
82
|
+
"operator": { "const": "between" }
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
"then": {
|
|
86
|
+
"properties": {
|
|
87
|
+
"value": {
|
|
88
|
+
"type": "array",
|
|
89
|
+
"minItems": 2,
|
|
90
|
+
"maxItems": 2,
|
|
91
|
+
"items": { "type": "string", "format": "date-time" }
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
]
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
## Example 3: External API Validator (Remote Schema URL)
|
|
104
|
+
If the validation schema is hosted remotely (e.g., an internal hospital compliance API), the system fetches it dynamically using a `remote_url` locator.
|
|
105
|
+
|
|
106
|
+
### Configuration (`config/tools.config.json`)
|
|
107
|
+
```json
|
|
108
|
+
{
|
|
109
|
+
"tools": {
|
|
110
|
+
"dispense_medication": {
|
|
111
|
+
"schema": { "_type": "file", "path": "schemas/dispense.json" },
|
|
112
|
+
"validation_engine": {
|
|
113
|
+
"_type": "remote_url",
|
|
114
|
+
"url": "https://compliance.hospital.org/api/v1/narcotics-schema",
|
|
115
|
+
"headers": { "Authorization": "Bearer env:API_TOKEN" }
|
|
116
|
+
},
|
|
117
|
+
"engine": { "_type": "adapter", "name": "postgres", "options": { "url": "env:DATABASE_URL" } }
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### Request & Response Payloads
|
|
124
|
+
When the filter is initialized or modified, the middleware sends a POST request to the `url` endpoint with the following payload structure:
|
|
125
|
+
|
|
126
|
+
#### POST Request Body
|
|
127
|
+
```json
|
|
128
|
+
{
|
|
129
|
+
"toolName": "dispense_medication",
|
|
130
|
+
"tableName": "prescriptions",
|
|
131
|
+
"rules": [
|
|
132
|
+
{
|
|
133
|
+
"property": "qty",
|
|
134
|
+
"operator": "gt",
|
|
135
|
+
"value": 150
|
|
136
|
+
}
|
|
137
|
+
]
|
|
138
|
+
}
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
#### Expected JSON Response (Success)
|
|
142
|
+
```json
|
|
143
|
+
{
|
|
144
|
+
"valid": true
|
|
145
|
+
}
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
#### Expected JSON Response (Validation Failure)
|
|
149
|
+
```json
|
|
150
|
+
{
|
|
151
|
+
"valid": false,
|
|
152
|
+
"errors": [
|
|
153
|
+
"Controlled narcotics dispense limit exceeded (Max: 120 units)"
|
|
154
|
+
]
|
|
155
|
+
}
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
---
|
|
159
|
+
|
|
160
|
+
## Example 4: Custom Validation Script Execution (Custom Adapter)
|
|
161
|
+
For complex validation logic (e.g., verifying a patient's active status via an external script), register a custom validation adapter factory that executes a script subprocess:
|
|
162
|
+
|
|
163
|
+
### Configuration (`config/tools.config.json`)
|
|
164
|
+
```json
|
|
165
|
+
{
|
|
166
|
+
"tools": {
|
|
167
|
+
"dispense_medication": {
|
|
168
|
+
"schema": { "_type": "file", "path": "schemas/dispense.json" },
|
|
169
|
+
"validation_engine": {
|
|
170
|
+
"_type": "adapter",
|
|
171
|
+
"name": "script_validator",
|
|
172
|
+
"options": {
|
|
173
|
+
"script_path": "scripts/validate_narcotic_limits.js",
|
|
174
|
+
"runtime_path": "node"
|
|
175
|
+
}
|
|
176
|
+
},
|
|
177
|
+
"engine": { "_type": "adapter", "name": "postgres", "options": { "url": "env:DATABASE_URL" } }
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
### Script Validator Factory Registration (`src/adapters/validators/script-validator.ts`)
|
|
184
|
+
|
|
185
|
+
> [!WARNING]
|
|
186
|
+
> Do **not** use `spawnSync` here. It blocks the entire Node.js event loop for the duration of the subprocess, freezing the MCP server for all concurrent requests. Use async `spawn` with a promise wrapper instead.
|
|
187
|
+
|
|
188
|
+
```typescript
|
|
189
|
+
import { registerAdapter } from "@stateful-mcp/loader";
|
|
190
|
+
import { spawn } from "child_process";
|
|
191
|
+
|
|
192
|
+
/** Wraps child_process.spawn as a non-blocking promise. */
|
|
193
|
+
function runScript(runtime: string, script: string, stdinPayload: string): Promise<string> {
|
|
194
|
+
return new Promise((resolve, reject) => {
|
|
195
|
+
const proc = spawn(runtime, [script], { stdio: ["pipe", "pipe", "pipe"] });
|
|
196
|
+
|
|
197
|
+
let stdout = "";
|
|
198
|
+
let stderr = "";
|
|
199
|
+
|
|
200
|
+
proc.stdout.on("data", (chunk) => { stdout += chunk; });
|
|
201
|
+
proc.stderr.on("data", (chunk) => { stderr += chunk; });
|
|
202
|
+
|
|
203
|
+
proc.on("close", (code) => {
|
|
204
|
+
if (code !== 0) reject(new Error(`Validator script exited ${code}: ${stderr.trim()}`));
|
|
205
|
+
else resolve(stdout);
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
proc.on("error", reject);
|
|
209
|
+
|
|
210
|
+
proc.stdin.write(stdinPayload);
|
|
211
|
+
proc.stdin.end();
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
registerAdapter("script_validator", {
|
|
216
|
+
create: async (options) => {
|
|
217
|
+
return {
|
|
218
|
+
validate: async (rules: any[]) => {
|
|
219
|
+
const script = String(options.script_path);
|
|
220
|
+
const runtime = String(options.runtime_path || "node");
|
|
221
|
+
|
|
222
|
+
// Pass rules as JSON via stdin — non-blocking, event loop stays free
|
|
223
|
+
const output = await runScript(runtime, script, JSON.stringify(rules));
|
|
224
|
+
return JSON.parse(output); // Expected: { valid: true } or { valid: false, errors: [...] }
|
|
225
|
+
}
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
});
|
|
229
|
+
```
|
|
230
|
+
|