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/browser.md
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# Stateful Middleware: Client-Side Browser Storage
|
|
2
|
+
|
|
3
|
+
This guide explains how to run the stateful middleware (Filters, Objects, Forms) directly in web browsers (e.g., in React, Vue, Svelte, or vanilla SPAs) utilizing native browser storage APIs.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 1. Premade Storage Drivers
|
|
8
|
+
|
|
9
|
+
The package provides two pre-configured storage drivers optimized for browser environments:
|
|
10
|
+
|
|
11
|
+
### LocalStorage (`localStorage`)
|
|
12
|
+
* **Best for**: Simple state preservation, small questionnaires, or persistent user preferences that don't change rapidly.
|
|
13
|
+
* **Classes**:
|
|
14
|
+
* `LocalStorageSessionStore` (implements `SessionFilterStore`, `SessionObjectStore`, `SessionFormStore`)
|
|
15
|
+
* `LocalStoragePersistentStore` (implements `PersistentFilterStore`, `PersistentObjectStore`, `PersistentFormStore`)
|
|
16
|
+
|
|
17
|
+
### IndexedDB (`indexedDB`)
|
|
18
|
+
* **Best for**: Highly active workloads, large state objects, event mutation logging, and transactional safety. Runs asynchronously on a separate thread.
|
|
19
|
+
* **Classes**:
|
|
20
|
+
* `IndexedDbSessionStore` (implements `SessionFilterStore`, `SessionObjectStore`, `SessionFormStore`)
|
|
21
|
+
* `IndexedDbPersistentStore` (implements `PersistentFilterStore`, `PersistentObjectStore`, `PersistentFormStore`)
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## 2. Programmatic Integration Example
|
|
26
|
+
|
|
27
|
+
To run a form engine or filter manager inside a browser app, import the stores and the browser adapters directly:
|
|
28
|
+
|
|
29
|
+
```typescript
|
|
30
|
+
import { IndexedDbSessionStore, IndexedDbPersistentStore } from "stateful-mcp/adapters/browser-repo";
|
|
31
|
+
import { FormStore } from "stateful-mcp/middleware/form";
|
|
32
|
+
import type { FormSchema } from "stateful-mcp/config/types";
|
|
33
|
+
|
|
34
|
+
// 1. Initialize IndexedDB database stores
|
|
35
|
+
const dbName = "user_questionnaires";
|
|
36
|
+
const sessionStore = new IndexedDbSessionStore(dbName);
|
|
37
|
+
const persistentStore = new IndexedDbPersistentStore(dbName);
|
|
38
|
+
|
|
39
|
+
// 2. Define your schema
|
|
40
|
+
const intakeSchema: FormSchema = {
|
|
41
|
+
form_id: "medical_intake",
|
|
42
|
+
start_question: "q_name",
|
|
43
|
+
questions: {
|
|
44
|
+
q_name: {
|
|
45
|
+
text: "Please enter your name",
|
|
46
|
+
answer_type: "free_text",
|
|
47
|
+
required: true
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const schemas = new Map([["medical_intake", intakeSchema]]);
|
|
53
|
+
|
|
54
|
+
// 3. Create the FormStore engine
|
|
55
|
+
const formStore = new FormStore(sessionStore, persistentStore, schemas);
|
|
56
|
+
|
|
57
|
+
// 4. Initialize and use the store programmatically in your component
|
|
58
|
+
const sessionId = "current-user-session";
|
|
59
|
+
const formId = await formStore.init("medical_intake", sessionId, "current_intake");
|
|
60
|
+
|
|
61
|
+
// Answer a question
|
|
62
|
+
const result = await formStore.answer(formId, "q_name", "Alice Cooper", sessionId);
|
|
63
|
+
console.log("Form Status:", result.complete ? "Completed!" : "More questions remaining.");
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## 3. Writing Custom Browser Storage Adapters
|
|
69
|
+
|
|
70
|
+
If you want to use custom storage engines such as **SQLite WASM**, **RxDB**, or a cloud-synchronized database (like **PouchDB/CouchDB**), you can write a class implementing the repository interfaces exported by `stateful-mcp/adapters/storage/interfaces`:
|
|
71
|
+
|
|
72
|
+
```typescript
|
|
73
|
+
import { SessionFormStore } from "stateful-mcp/adapters/storage/interfaces";
|
|
74
|
+
import type { FormState } from "stateful-mcp/middleware/form/types";
|
|
75
|
+
|
|
76
|
+
export class MySqliteWasmFormStore implements SessionFormStore {
|
|
77
|
+
async get(sessionId: string, id: string): Promise<FormState | null> {
|
|
78
|
+
// Execute SQL WASM: SELECT state FROM form_sessions WHERE session_id = ? AND id = ?
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async create(sessionId: string, state: FormState, alias?: string): Promise<string> {
|
|
82
|
+
// Insert state record and return generated ID
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async set(sessionId: string, id: string, state: FormState): Promise<void> {
|
|
86
|
+
// Update state record
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
async delete(sessionId: string, id: string): Promise<void> {
|
|
90
|
+
// Delete state record
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// ... implement setAlias, getAlias, and listing methods
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
Because the middleware uses Dependency Inversion, any custom class satisfying the TypeScript contract is fully compatible.
|
package/docs/config.md
ADDED
|
@@ -0,0 +1,498 @@
|
|
|
1
|
+
# Configuration Specification
|
|
2
|
+
|
|
3
|
+
This document details how to configure the stateful MCP middleware suite. The configuration can be defined in a single file (`filter.config.json`) or split into three files under `config/`: `tools.config.json` (tool schemas and engines — a developer concern), `storage.config.json` (storage backends — an operations concern), and `about.config.json` (documentation/example resources — a separate concern that changes at a different cadence from both).
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 1. Resource Locator (`ResourceLocator`)
|
|
8
|
+
|
|
9
|
+
Every state, schema, or configuration file reference in the middleware is specified using a `ResourceLocator` object.
|
|
10
|
+
|
|
11
|
+
### Fields
|
|
12
|
+
* **`_type`** (string, required): The locator type. Must be `"adapter"`, `"file"`, or `"remote_url"`.
|
|
13
|
+
* **`name`** (string): The registration name of the storage adapter (required when `_type === "adapter"`). Examples: `"memory"`, `"sqlite"`, `"postgres"`.
|
|
14
|
+
* **`options`** (object): Adapter-specific option parameters (e.g. database URLs).
|
|
15
|
+
* **`path`** (string): Absolute or relative filesystem path to a file (required when `_type === "file"`).
|
|
16
|
+
* **`url`** (string): Remote HTTP URL (required when `_type === "remote_url"`). Supports `{userId}` substitutions.
|
|
17
|
+
* **`ttl_ms`** (number): Time-to-live caching interval in milliseconds.
|
|
18
|
+
* **`headers`** (object): HTTP headers to supply during fetch operations (when `_type === "remote_url"`).
|
|
19
|
+
|
|
20
|
+
#### Examples
|
|
21
|
+
```json
|
|
22
|
+
{
|
|
23
|
+
"_type": "file",
|
|
24
|
+
"path": "config/about/middleware.md"
|
|
25
|
+
}
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
```json
|
|
29
|
+
{
|
|
30
|
+
"_type": "remote_url",
|
|
31
|
+
"url": "https://api.firm.internal/ontology/concepts",
|
|
32
|
+
"ttl_ms": 3600000,
|
|
33
|
+
"headers": {
|
|
34
|
+
"Authorization": "Bearer token123"
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## 2. Storage & Session Configuration (`storage.config.json`)
|
|
42
|
+
|
|
43
|
+
Controls backends for VCS DAG chains, dictionaries, and compression thresholds.
|
|
44
|
+
|
|
45
|
+
### Parameters
|
|
46
|
+
* **`version`** (integer, required): Must be `1`.
|
|
47
|
+
* **`filter_session_state`**: Session storage for filters (must resolve to a `SessionFilterStore` adapter).
|
|
48
|
+
* **`filter_persistent_state`**: Persistent storage for whitelisted filters. Contains `{ global: ResourceLocator, user: ResourceLocator }`.
|
|
49
|
+
* **`object_session_state`**: Session storage for objects.
|
|
50
|
+
* **`object_persistent_state`**: Persistent storage for objects. Contains `{ global: ResourceLocator, user: ResourceLocator }`.
|
|
51
|
+
* **`dictionary_state`**: Resource locator to pre-populate concept definitions.
|
|
52
|
+
* **`dictionary_resolver`**: Resource locator to load the concept resolver engine.
|
|
53
|
+
* **`auto_compression`**:
|
|
54
|
+
- `filter_chain_threshold` (number, default: 20): Maximum linear version depth of a filter chain before suffix squashing is triggered.
|
|
55
|
+
- `object_chain_threshold` (number, default: 15): Maximum linear version depth of an object change chain before suffix squashing is triggered.
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
## 3. About & Examples Configuration (`about.config.json`)
|
|
60
|
+
|
|
61
|
+
Optional documentation/example resources for the `*_about` / `*_examples` developer-assistance tools. It is kept in its own file because it changes at a different cadence than storage backends (ops concern) and tool schemas (dev concern). When the file is absent, each service falls back to its built-in default doc path (e.g. `config/about/filter.md`, `config/examples/filter.md`).
|
|
62
|
+
|
|
63
|
+
### Parameters
|
|
64
|
+
* **`about_and_examples`**: Mapping of documentation resources (each an array of `ResourceLocator`s).
|
|
65
|
+
- `middleware_about`, `filter_about`, `filter_examples`, `object_about`, `object_examples`, `dictionary_about`, `dictionary_examples`, `event_about`, `event_examples`.
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## 3. Tool & Schema Configuration (`tools.config.json`)
|
|
70
|
+
|
|
71
|
+
Registers the tools the LLM can access and how they validate and compile.
|
|
72
|
+
|
|
73
|
+
### Parameters
|
|
74
|
+
* **`tools`** (object): Key-value pair of tool registrations.
|
|
75
|
+
- **`schema`** (`ResourceLocator`): Path to JSON Schema defining table columns, operations, and constraints.
|
|
76
|
+
- **`engine`** (`ResourceLocator` | object): The query/execution engine. Can be a single `ResourceLocator` or a key-value mapping table-names to resource engines.
|
|
77
|
+
* **Built-in Query Engines**:
|
|
78
|
+
* `"memory-engine"`: In-memory arrays.
|
|
79
|
+
* `"sqlite"` / `"postgres"`: Relational databases.
|
|
80
|
+
* `"dataframe"`: Runs DuckDB SQL queries natively over CSV, Parquet, or JSONL files.
|
|
81
|
+
* **Options**:
|
|
82
|
+
* `source_file` (string, required): Path to CSV, Parquet, JSON, or JSONL.
|
|
83
|
+
* `dataframe_name` (string, optional): View name registered in DuckDB (defaults to `"df"`).
|
|
84
|
+
* **Custom External Adapters (`@` alias)**:
|
|
85
|
+
Use `name: "@<path>"` to dynamically load an adapter module from any location. The path after `@` is resolved using these rules (in priority order):
|
|
86
|
+
|
|
87
|
+
| Path form | Interpretation |
|
|
88
|
+
|---|---|
|
|
89
|
+
| `@adapters/my-engine` | Workspace-relative (default) |
|
|
90
|
+
| `@/opt/hospital/engine` | Absolute path |
|
|
91
|
+
| `@~/projects/adapters/engine` | Home-directory relative |
|
|
92
|
+
|
|
93
|
+
```json
|
|
94
|
+
{ "_type": "adapter", "name": "@adapters/my-engine" }
|
|
95
|
+
{ "_type": "adapter", "name": "@/opt/hospital/engine" }
|
|
96
|
+
{ "_type": "adapter", "name": "@~/projects/adapters/engine" }
|
|
97
|
+
```
|
|
98
|
+
The module must call `registerAdapter(name, factory)` as a side-effect at the top level.
|
|
99
|
+
Since `tsconfig.json` defines `@stateful-mcp/*` path aliases, the module itself also avoids relative imports:
|
|
100
|
+
```typescript
|
|
101
|
+
// <workspaceRoot>/adapters/my-custom-engine.ts
|
|
102
|
+
import { registerAdapter } from "@stateful-mcp/loader";
|
|
103
|
+
import type { AdapterFactory } from "@stateful-mcp/types";
|
|
104
|
+
|
|
105
|
+
registerAdapter("@adapters/my-custom-engine", {
|
|
106
|
+
create: async (options) => { /* your implementation */ }
|
|
107
|
+
});
|
|
108
|
+
```
|
|
109
|
+
- **`validation_engine`** (`ResourceLocator`, optional): Custom verification validator. A reserved slot to point to an external script or schema (e.g., custom AJV options or validation rules) to enforce advanced business constraints on filter inputs. See [validation_examples.md](file:///home/denny/lu/filter/docs/validation_examples.md) for concrete examples.
|
|
110
|
+
- **`inspect`**: Configuration options for runtime inspections.
|
|
111
|
+
- `expose_compiled` (boolean): Exposes fully compiled SQL statements.
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
## 4. Object Schemas and Limits
|
|
116
|
+
|
|
117
|
+
* **`object_schemas`** (object): Mapping of object schema names to their schema resource definitions.
|
|
118
|
+
* **`object_schema_limits`**:
|
|
119
|
+
- `max_fields_per_def` (number, default: 7): Warning threshold for maximum keys in a single flat object schema (encourages nesting).
|
|
120
|
+
- `max_ref_depth` (number, default: 5): Maximum recursion depth allowed during lazy reference link parsing.
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
## 5. Pagination Limits (`pagination_limits`)
|
|
125
|
+
|
|
126
|
+
An optional block that controls how many items are returned per page across paginated surfaces. Each field has a built-in default and a hard ceiling that cannot be exceeded regardless of what the caller requests.
|
|
127
|
+
|
|
128
|
+
```json
|
|
129
|
+
{
|
|
130
|
+
"pagination_limits": {
|
|
131
|
+
"log_page_size": 20,
|
|
132
|
+
"examples_page_size": 5,
|
|
133
|
+
"merge_conflicts_page_size": 50
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
| Field | Default | Hard Ceiling | Surface |
|
|
139
|
+
|---|---|---|---|
|
|
140
|
+
| `log_page_size` | `20` | `200` | `log_open` / `log_next` entries per page |
|
|
141
|
+
| `examples_page_size` | `5` | `50` | `*_examples` tools examples per page |
|
|
142
|
+
| `merge_conflicts_page_size` | `50` | `500` | `event_merge_inspect` conflicts per page |
|
|
143
|
+
|
|
144
|
+
All fields must be **integers ≥ 1** if provided. The validator rejects values of `0` or below.
|
|
145
|
+
|
|
146
|
+
### Merge conflict pagination
|
|
147
|
+
|
|
148
|
+
When an `event_merge` call produces conflicts, it returns only summary counts — no inline array. Use `event_merge_inspect` to iterate through conflicts in pages:
|
|
149
|
+
|
|
150
|
+
```
|
|
151
|
+
event_merge → { status: "conflict", merge_session_id, total_conflicts, pending_count }
|
|
152
|
+
event_merge_inspect → { conflicts: [...], has_more, next_offset, pending_count, resolved_count }
|
|
153
|
+
event_merge_resolve → { merge_session_id: "merge_next" } (one conflict resolved, new session ID)
|
|
154
|
+
event_merge_inspect (new session, offset: N) → next page
|
|
155
|
+
... repeat until pending_count == 0 ...
|
|
156
|
+
event_merge_commit → { commit_id }
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
`event_merge_inspect` response shape:
|
|
160
|
+
|
|
161
|
+
```json
|
|
162
|
+
{
|
|
163
|
+
"merge_session_id": "merge_abc123",
|
|
164
|
+
"conflicts": [ /* page of MergeConflict objects */ ],
|
|
165
|
+
"total_conflicts": 50,
|
|
166
|
+
"pending_count": 48,
|
|
167
|
+
"resolved_count": 2,
|
|
168
|
+
"has_more": true,
|
|
169
|
+
"next_offset": 5
|
|
170
|
+
}
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
---
|
|
174
|
+
|
|
175
|
+
## 6. Environment Files & Substitutions (`env_sources` / `env:VAR_NAME`)
|
|
176
|
+
|
|
177
|
+
To load environment variables dynamically from filesystem files, define `env_sources` at the root of your configuration:
|
|
178
|
+
|
|
179
|
+
```json
|
|
180
|
+
{
|
|
181
|
+
"env_sources": [
|
|
182
|
+
{ "_type": "file", "path": ".env", "optional": true }
|
|
183
|
+
]
|
|
184
|
+
}
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
### String Substitution
|
|
188
|
+
Any JSON configuration value prefixed with `env:` is substituted dynamically at load time with the corresponding environment variable value:
|
|
189
|
+
|
|
190
|
+
```json
|
|
191
|
+
{
|
|
192
|
+
"postgres_connection": "env:DATABASE_URL"
|
|
193
|
+
}
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
---
|
|
197
|
+
|
|
198
|
+
## 6. Pinned Constants (`constants`)
|
|
199
|
+
|
|
200
|
+
You can define static or dynamic query values that are injected into every query:
|
|
201
|
+
|
|
202
|
+
```json
|
|
203
|
+
{
|
|
204
|
+
"constants": {
|
|
205
|
+
"global": { "_type": "file", "path": "config/global_constants.json" },
|
|
206
|
+
"user": { "_type": "remote_url", "url": "https://api.firm.internal/users/{userId}/constants" }
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
```
|
|
210
|
+
* **User Constant Interpolation**: The `{userId}` placeholder in the URL is dynamically substituted per-request based on the authenticated user ID executing the query.
|
|
211
|
+
|
|
212
|
+
---
|
|
213
|
+
|
|
214
|
+
## 7. Pipeline Translation (`translation`)
|
|
215
|
+
|
|
216
|
+
In `tools.config.json`, you can define an optional `translation` engine to preprocess queries or map dialect variables before query compilation:
|
|
217
|
+
|
|
218
|
+
```json
|
|
219
|
+
{
|
|
220
|
+
"tools": {
|
|
221
|
+
"search_orders": {
|
|
222
|
+
"schema": { "_type": "file", "path": "schemas/orders.json" },
|
|
223
|
+
"translation": { "_type": "file", "path": "translations/postgres_map.json" },
|
|
224
|
+
"engine": { "_type": "adapter", "name": "postgres" }
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
---
|
|
231
|
+
|
|
232
|
+
## 8. System Environment Variables
|
|
233
|
+
|
|
234
|
+
The following system-wide environment variables can be set to configure the servers at startup:
|
|
235
|
+
|
|
236
|
+
* **`SERVICE_TYPE`** (string): Determines which service to run when using the monolith router index (`filter`, `object`, `dictionary`, `event`, or `log`).
|
|
237
|
+
* **`LOG_SERVICE_SECRET`** (string, optional): A 32-byte cryptographic secret used as the HMAC key to sign stateless log page tokens. If not set, a random secret is generated on startup.
|
|
238
|
+
* **`WORKSPACE_ID`** (string, optional): The fallback workspace identifier for the Dictionary Service when no workspace is supplied per-request. Defaults to `"global"`.
|
|
239
|
+
|
|
240
|
+
---
|
|
241
|
+
|
|
242
|
+
## 9. Worked Example: Pharmacy / Retail Setup
|
|
243
|
+
|
|
244
|
+
Here is a complete, copy-pasteable configuration for a pharmacy or retail store utilizing SQLite session state, Postgres persistent databases, and standard tools/schemas.
|
|
245
|
+
|
|
246
|
+
### 9.1 `storage.config.json`
|
|
247
|
+
```json
|
|
248
|
+
{
|
|
249
|
+
"version": 1,
|
|
250
|
+
"filter_session_state": {
|
|
251
|
+
"_type": "adapter",
|
|
252
|
+
"name": "sqlite",
|
|
253
|
+
"options": { "url": "sqlite://data/filter_sessions.db" }
|
|
254
|
+
},
|
|
255
|
+
"filter_persistent_state": {
|
|
256
|
+
"global": {
|
|
257
|
+
"_type": "adapter",
|
|
258
|
+
"name": "postgres",
|
|
259
|
+
"options": { "url": "env:DATABASE_URL" }
|
|
260
|
+
},
|
|
261
|
+
"user": {
|
|
262
|
+
"_type": "adapter",
|
|
263
|
+
"name": "postgres",
|
|
264
|
+
"options": { "url": "env:DATABASE_URL" }
|
|
265
|
+
}
|
|
266
|
+
},
|
|
267
|
+
"object_session_state": {
|
|
268
|
+
"_type": "adapter",
|
|
269
|
+
"name": "sqlite",
|
|
270
|
+
"options": { "url": "sqlite://data/object_sessions.db" }
|
|
271
|
+
},
|
|
272
|
+
"object_persistent_state": {
|
|
273
|
+
"global": {
|
|
274
|
+
"_type": "adapter",
|
|
275
|
+
"name": "postgres",
|
|
276
|
+
"options": { "url": "env:DATABASE_URL" }
|
|
277
|
+
},
|
|
278
|
+
"user": {
|
|
279
|
+
"_type": "adapter",
|
|
280
|
+
"name": "postgres",
|
|
281
|
+
"options": { "url": "env:DATABASE_URL" }
|
|
282
|
+
}
|
|
283
|
+
},
|
|
284
|
+
"dictionary_state": {
|
|
285
|
+
"_type": "file",
|
|
286
|
+
"path": "config/pharmacy_dictionary.json"
|
|
287
|
+
},
|
|
288
|
+
"dictionary_resolver": {
|
|
289
|
+
"_type": "adapter",
|
|
290
|
+
"name": "memory"
|
|
291
|
+
},
|
|
292
|
+
"auto_compression": {
|
|
293
|
+
"filter_chain_threshold": 10,
|
|
294
|
+
"object_chain_threshold": 10
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
### 9.2 `tools.config.json`
|
|
300
|
+
```json
|
|
301
|
+
{
|
|
302
|
+
"tools": {
|
|
303
|
+
"dispense_medication": {
|
|
304
|
+
"schema": {
|
|
305
|
+
"_type": "file",
|
|
306
|
+
"path": "schemas/dispense.json"
|
|
307
|
+
},
|
|
308
|
+
"translation": {
|
|
309
|
+
"_type": "file",
|
|
310
|
+
"path": "translations/dispense_postgres.json"
|
|
311
|
+
},
|
|
312
|
+
"engine": {
|
|
313
|
+
"prescriptions": {
|
|
314
|
+
"_type": "adapter",
|
|
315
|
+
"name": "postgres",
|
|
316
|
+
"options": { "url": "env:DATABASE_URL" }
|
|
317
|
+
}
|
|
318
|
+
},
|
|
319
|
+
"inspect": {
|
|
320
|
+
"expose_compiled": true
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
},
|
|
324
|
+
"object_schemas": {
|
|
325
|
+
"prescription_order": {
|
|
326
|
+
"_type": "file",
|
|
327
|
+
"path": "schemas/prescription_order.json"
|
|
328
|
+
}
|
|
329
|
+
},
|
|
330
|
+
"object_schema_limits": {
|
|
331
|
+
"max_fields_per_def": 12,
|
|
332
|
+
"max_ref_depth": 3
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
### 9.3 Content of Referenced Paths
|
|
338
|
+
|
|
339
|
+
Below are the contents of the files referenced in the configurations above:
|
|
340
|
+
|
|
341
|
+
#### `schemas/dispense.json` (Table Schema)
|
|
342
|
+
Defines target table columns, data types, and supported comparison operators.
|
|
343
|
+
```json
|
|
344
|
+
{
|
|
345
|
+
"table_schemas": {
|
|
346
|
+
"prescriptions": {
|
|
347
|
+
"filterable_properties": ["medication_name", "qty", "dispense_status", "physician_id"],
|
|
348
|
+
"operators": ["eq", "neq", "lt", "leq", "gt", "geq", "like"],
|
|
349
|
+
"result_shape": "json_array",
|
|
350
|
+
"max_results": 100
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
#### `translations/dispense_postgres.json` (Translation Mapping)
|
|
357
|
+
Translates the LLM's public field name references to PostgreSQL database representations.
|
|
358
|
+
```json
|
|
359
|
+
{
|
|
360
|
+
"properties": {
|
|
361
|
+
"medication_name": {
|
|
362
|
+
"internal": "rx_name"
|
|
363
|
+
},
|
|
364
|
+
"qty": {
|
|
365
|
+
"internal": "quantity",
|
|
366
|
+
"allowed_operators": ["eq", "lt", "gt"]
|
|
367
|
+
},
|
|
368
|
+
"dispense_status": {
|
|
369
|
+
"internal": "status"
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
#### `schemas/prescription_order.json` (Object Validation Schema)
|
|
376
|
+
Strict validation schema verifying properties and value constraints for a prescription form.
|
|
377
|
+
```json
|
|
378
|
+
{
|
|
379
|
+
"type": "object",
|
|
380
|
+
"required": ["patient_id", "medication", "quantity"],
|
|
381
|
+
"properties": {
|
|
382
|
+
"patient_id": { "type": "string" },
|
|
383
|
+
"medication": { "type": "string" },
|
|
384
|
+
"quantity": {
|
|
385
|
+
"type": "integer",
|
|
386
|
+
"minimum": 1
|
|
387
|
+
},
|
|
388
|
+
"refills": {
|
|
389
|
+
"type": "integer",
|
|
390
|
+
"default": 0
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
#### `config/pharmacy_dictionary.json` (Dictionary Ontology)
|
|
397
|
+
Ontology coordinates translating colloquial abbreviations to standardized concept identifiers.
|
|
398
|
+
```json
|
|
399
|
+
{
|
|
400
|
+
"namespaces": [
|
|
401
|
+
{ "id": "CLINICAL", "name": "Standard Medical Registry", "mutable": false }
|
|
402
|
+
],
|
|
403
|
+
"concepts": [
|
|
404
|
+
{
|
|
405
|
+
"id": "CLINICAL::AMOXICILLIN",
|
|
406
|
+
"namespace": "CLINICAL",
|
|
407
|
+
"coordinate": "CLINICAL::AMOXICILLIN",
|
|
408
|
+
"description": "Amoxicillin antibiotic prescription"
|
|
409
|
+
}
|
|
410
|
+
],
|
|
411
|
+
"expressions": [
|
|
412
|
+
{
|
|
413
|
+
"id": "expr_amox",
|
|
414
|
+
"conceptId": "CLINICAL::AMOXICILLIN",
|
|
415
|
+
"term": "amox shorthand",
|
|
416
|
+
"regexPattern": "^amox(y)?$",
|
|
417
|
+
"isCaseInsensitive": true,
|
|
418
|
+
"targetAssignment": "clinical",
|
|
419
|
+
"priorityWeight": 10,
|
|
420
|
+
"active": true
|
|
421
|
+
}
|
|
422
|
+
]
|
|
423
|
+
}
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
---
|
|
427
|
+
|
|
428
|
+
## 4. State References & Integrity Validation (`x-mcp-ref`)
|
|
429
|
+
|
|
430
|
+
To establish dynamic referential integrity across the stateful services, properties in JSON schemas (used by Object schemas, Event payload schemas, or Form question schemas) can be configured with the custom attribute `"x-mcp-ref"`.
|
|
431
|
+
|
|
432
|
+
### Allowed Values
|
|
433
|
+
* `"filter"`: Asserts that the field value points to a valid, active filter state ID or alias in the current session.
|
|
434
|
+
* `"object"`: Asserts that the field value points to a valid, active object state ID or alias in the current session.
|
|
435
|
+
* `"form"`: Asserts that the field value points to a valid, active form state ID or alias in the current session.
|
|
436
|
+
|
|
437
|
+
### Schema Example
|
|
438
|
+
(Note: Property/field names are completely arbitrary; validation is driven solely by the `"x-mcp-ref"` keyword.)
|
|
439
|
+
```json
|
|
440
|
+
{
|
|
441
|
+
"type": "object",
|
|
442
|
+
"required": ["report_title", "cohort_query", "patient_record"],
|
|
443
|
+
"properties": {
|
|
444
|
+
"report_title": { "type": "string" },
|
|
445
|
+
"cohort_query": {
|
|
446
|
+
"type": "string",
|
|
447
|
+
"x-mcp-ref": "filter"
|
|
448
|
+
},
|
|
449
|
+
"patient_record": {
|
|
450
|
+
"type": "string",
|
|
451
|
+
"x-mcp-ref": "object"
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
```
|
|
456
|
+
|
|
457
|
+
### Runtime Validation Behavior
|
|
458
|
+
During schema verification (e.g. `object_validate`, `form_answer`, or `event_log`), the validation runner:
|
|
459
|
+
1. Parses the schema to locate any `"x-mcp-ref"` directives.
|
|
460
|
+
2. Queries the corresponding session store (resolving aliases) to check if the state ID exists.
|
|
461
|
+
3. Throws a descriptive validation error if the referenced entity is missing in the current session.
|
|
462
|
+
|
|
463
|
+
---
|
|
464
|
+
|
|
465
|
+
## 5. Programmatic Integration in External Tools
|
|
466
|
+
|
|
467
|
+
External tools running in the same codebase environment or sharing the database/file persistence can bypass the MCP protocol entirely and interact with the stores programmatically.
|
|
468
|
+
|
|
469
|
+
### Middleware Exports
|
|
470
|
+
The package exports entrypoints for all middleware stores:
|
|
471
|
+
* `stateful-mcp/middleware/filter` $\rightarrow$ `FilterStore`
|
|
472
|
+
* `stateful-mcp/middleware/object` $\rightarrow$ `ObjectStore`
|
|
473
|
+
* `stateful-mcp/middleware/form` $\rightarrow$ `FormStore`
|
|
474
|
+
|
|
475
|
+
### Code Example
|
|
476
|
+
```typescript
|
|
477
|
+
import { FilterStore } from "stateful-mcp/middleware/filter";
|
|
478
|
+
import { MemorySessionFilterStore, MemoryPersistentFilterStore } from "stateful-mcp";
|
|
479
|
+
|
|
480
|
+
// 1. Initialize store programmatically
|
|
481
|
+
const filterStore = new FilterStore(
|
|
482
|
+
new MemorySessionFilterStore(),
|
|
483
|
+
new MemoryPersistentFilterStore(),
|
|
484
|
+
new Map(), // toolSchemas
|
|
485
|
+
new Map(), // pinnedSchemas
|
|
486
|
+
20 // auto-compression threshold
|
|
487
|
+
);
|
|
488
|
+
|
|
489
|
+
// 2. Fetch active rules by ID or alias
|
|
490
|
+
const filterNode = await filterStore.getFilter("my_active_alias", "session_id");
|
|
491
|
+
if (filterNode) {
|
|
492
|
+
console.log("Active rules:", filterNode.rules);
|
|
493
|
+
}
|
|
494
|
+
```
|
|
495
|
+
|
|
496
|
+
|
|
497
|
+
|
|
498
|
+
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# Dictionary Service Reference Guide
|
|
2
|
+
|
|
3
|
+
The Dictionary Service is an ontology normalization engine. It translates colloquial user terminology, shorthand, and abbreviations into standardized database coordinate keys before they enter queries or forms.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 1. Coordinates and Resolution
|
|
8
|
+
|
|
9
|
+
Concepts inside the dictionary are addressed using coordinate identifiers:
|
|
10
|
+
* Format: `[NAMESPACE]::[CONCEPT_CODE]` (e.g. `CLINICAL::MYOCARDIAL_INFARCTION`).
|
|
11
|
+
* Calling `dictionary_resolve(term)` processes matches against defined concepts and expression aliases, returning the formal coordinate identifier.
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## 2. Alias Synonyms & Score Boosting
|
|
16
|
+
|
|
17
|
+
* **Regular Expression Synonyms**: Dictionary expressions can match terms using exact strings or custom regex patterns.
|
|
18
|
+
* **Engine**: Evaluated using the standard **JavaScript (Node.js) `RegExp` engine**.
|
|
19
|
+
* **Syntax Rules**:
|
|
20
|
+
* **Do NOT wrap** patterns in forward slashes `/.../`.
|
|
21
|
+
* **Do NOT append** flags (like `/i` or `/g`) inside the pattern string.
|
|
22
|
+
* Use the dedicated `isCaseInsensitive` boolean property to toggle case sensitivity (adds the `i` flag under the hood).
|
|
23
|
+
* Supports standard PCRE features: word boundaries `\b`, anchors `^`/`$`, character classes `[a-z]`, and capture groups.
|
|
24
|
+
* **Example**: E.g. raw pattern `^amox(y)?$` combined with `isCaseInsensitive: true`.
|
|
25
|
+
* **Score Boosting**: When multiple expressions match a search query, the service calculates weights:
|
|
26
|
+
* Positive feedback (e.g. selection) increases the expression's weight score.
|
|
27
|
+
* Loser expressions in the same candidate match set decay over time.
|
|
28
|
+
* The resolver prioritizes candidates with higher weight scores.
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## 3. Scope Hierarchy
|
|
33
|
+
|
|
34
|
+
Ontology resolution supports multiple administrative scopes:
|
|
35
|
+
1. **User Scope**: User-specific shorthand (e.g. customized abbreviations). Takes highest precedence.
|
|
36
|
+
2. **Workspace Scope**: Shorthand shared across a team workspace. Shadows global definitions.
|
|
37
|
+
3. **Global Scope**: Default, organization-wide medical/business vocabularies.
|
package/docs/event.md
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# Event Service Reference Guide
|
|
2
|
+
|
|
3
|
+
The Event Service provides git-like version control over append-only arrays of structured records (such as clinical flowsheet logs or point-of-sale transaction logs).
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 1. Commit DAG Structures
|
|
8
|
+
|
|
9
|
+
Instead of a flat array, the log is represented as a directed acyclic graph (DAG) of commits:
|
|
10
|
+
* **`event_init`**: Registers a schema and starts an initial log commit.
|
|
11
|
+
* **`event_append`**: Adds a new typed record to the end of the projected array.
|
|
12
|
+
* **`event_patch`**: Updates a specific record key-value structure using a sparse delta. It records `before_data` and tracks `mutation_parent_ids` indicating the last version where that record was mutated.
|
|
13
|
+
* **`event_delete`**: Marks a record as deleted in the projected output.
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## 2. N-Way Merging & Conflict Detection
|
|
18
|
+
|
|
19
|
+
Multiple parallel streams can be merged into a target commit in a single operation:
|
|
20
|
+
`event_merge(session_id, source_ids_or_aliases, target_id)`
|
|
21
|
+
|
|
22
|
+
### LCA Resolution
|
|
23
|
+
The merge engine finds the Lowest Common Ancestor (LCA) of all merging commits to establish the baseline state.
|
|
24
|
+
|
|
25
|
+
### Conflict Rules
|
|
26
|
+
A conflict is detected if the **same record ID** was updated or deleted by **more than one** of the merging branches since their LCA.
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## 3. Stateful Merge Sessions
|
|
31
|
+
|
|
32
|
+
If conflicts are detected, the merge enters a stateful session:
|
|
33
|
+
1. **`event_merge_inspect`**: Returns the list of conflicting values on each branch since LCA.
|
|
34
|
+
2. **`event_merge_resolve`**: Resolves a specific conflict. Strategies include `accept_source`, `accept_target`, or a custom merged `patch`. Resolving a conflict spawns a resolution checkpoint.
|
|
35
|
+
3. **`event_merge_commit`**: Writes the final merge commit once all conflicts have been resolved.
|