@chaprola/mcp-server 1.2.0 → 1.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/dist/index.js +37 -0
- package/package.json +2 -2
- package/references/cookbook.md +212 -7
- package/references/gotchas.md +27 -0
package/dist/index.js
CHANGED
|
@@ -665,6 +665,43 @@ server.tool("chaprola_schedule_delete", "Delete a scheduled job by name", {
|
|
|
665
665
|
const res = await authedFetch("/schedule/delete", { name });
|
|
666
666
|
return textResult(res);
|
|
667
667
|
}));
|
|
668
|
+
// --- Record CRUD ---
|
|
669
|
+
server.tool("chaprola_insert_record", "Insert a new record into a data file's merge file (.MRG). The record appears at the end of the file until consolidation.", {
|
|
670
|
+
project: z.string().describe("Project name"),
|
|
671
|
+
file: z.string().describe("Data file name (without extension)"),
|
|
672
|
+
record: z.record(z.string()).describe("Field name → value pairs. Unspecified fields default to blanks."),
|
|
673
|
+
}, async ({ project, file, record }) => withBaaCheck(async () => {
|
|
674
|
+
const { username } = getCredentials();
|
|
675
|
+
const res = await authedFetch("/insert-record", { userid: username, project, file, record });
|
|
676
|
+
return textResult(res);
|
|
677
|
+
}));
|
|
678
|
+
server.tool("chaprola_update_record", "Update fields in a single record matched by a where clause. If no sort-key changes, updates in place; otherwise marks old record ignored and appends to merge file.", {
|
|
679
|
+
project: z.string().describe("Project name"),
|
|
680
|
+
file: z.string().describe("Data file name (without extension)"),
|
|
681
|
+
where: z.record(z.string()).describe("Field name → value pairs to identify exactly one record"),
|
|
682
|
+
set: z.record(z.string()).describe("Field name → new value pairs to update"),
|
|
683
|
+
}, async ({ project, file, where: whereClause, set }) => withBaaCheck(async () => {
|
|
684
|
+
const { username } = getCredentials();
|
|
685
|
+
const res = await authedFetch("/update-record", { userid: username, project, file, where: whereClause, set });
|
|
686
|
+
return textResult(res);
|
|
687
|
+
}));
|
|
688
|
+
server.tool("chaprola_delete_record", "Delete a single record matched by a where clause. Marks the record as ignored (.IGN). Physically removed on consolidation.", {
|
|
689
|
+
project: z.string().describe("Project name"),
|
|
690
|
+
file: z.string().describe("Data file name (without extension)"),
|
|
691
|
+
where: z.record(z.string()).describe("Field name → value pairs to identify exactly one record"),
|
|
692
|
+
}, async ({ project, file, where: whereClause }) => withBaaCheck(async () => {
|
|
693
|
+
const { username } = getCredentials();
|
|
694
|
+
const res = await authedFetch("/delete-record", { userid: username, project, file, where: whereClause });
|
|
695
|
+
return textResult(res);
|
|
696
|
+
}));
|
|
697
|
+
server.tool("chaprola_consolidate", "Merge a .MRG file into its parent .DA, producing a clean sorted data file. Deletes .MRG and .IGN after success. Aborts if .MRG was modified during the operation.", {
|
|
698
|
+
project: z.string().describe("Project name"),
|
|
699
|
+
file: z.string().describe("Data file name (without extension)"),
|
|
700
|
+
}, async ({ project, file }) => withBaaCheck(async () => {
|
|
701
|
+
const { username } = getCredentials();
|
|
702
|
+
const res = await authedFetch("/consolidate", { userid: username, project, file });
|
|
703
|
+
return textResult(res);
|
|
704
|
+
}));
|
|
668
705
|
// --- Start server ---
|
|
669
706
|
async function main() {
|
|
670
707
|
const transport = new StdioServerTransport();
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@chaprola/mcp-server",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "MCP server for Chaprola — agent-first data platform. Gives AI agents
|
|
3
|
+
"version": "1.3.1",
|
|
4
|
+
"description": "MCP server for Chaprola — agent-first data platform. Gives AI agents 46 tools for structured data storage, record CRUD, querying, schema inspection, web search, URL fetching, scheduled jobs, and execution via plain HTTP.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"bin": {
|
package/references/cookbook.md
CHANGED
|
@@ -136,17 +136,222 @@ Supports: CSV, TSV, JSON, NDJSON, Parquet (zstd/snappy/lz4), Excel (.xlsx/.xls).
|
|
|
136
136
|
AI instructions are optional — omit to import all columns as-is.
|
|
137
137
|
Lambda: 10 GB /tmp, 900s timeout, 500 MB download limit.
|
|
138
138
|
|
|
139
|
-
## HULDRA Optimization
|
|
139
|
+
## HULDRA Optimization — Nonlinear Parameter Fitting
|
|
140
140
|
|
|
141
|
-
|
|
142
|
-
R21–R40 = objectives (your program computes, HULDRA reads after)
|
|
143
|
-
R41–R50 = scratch space
|
|
141
|
+
HULDRA finds the best parameter values for a mathematical model by minimizing the difference between model predictions and observed data. You propose a model, HULDRA finds the coefficients.
|
|
144
142
|
|
|
143
|
+
### How It Works
|
|
144
|
+
|
|
145
|
+
1. You write a VALUE program (normal Chaprola) that reads data, computes predictions using R-variable parameters, and stores the error in an objective R-variable
|
|
146
|
+
2. HULDRA repeatedly runs your program with different parameter values, using gradient descent to minimize the objective
|
|
147
|
+
3. When the objective stops improving, HULDRA returns the optimal parameters
|
|
148
|
+
|
|
149
|
+
### R-Variable Interface
|
|
150
|
+
|
|
151
|
+
| Range | Purpose | Who sets it |
|
|
152
|
+
|-------|---------|-------------|
|
|
153
|
+
| R1–R20 | **Elements** (parameters to optimize) | HULDRA sets these before each VM run |
|
|
154
|
+
| R21–R40 | **Objectives** (error metrics) | Your program computes and stores these |
|
|
155
|
+
| R41–R50 | **Scratch space** | Your program uses these for temp variables |
|
|
156
|
+
|
|
157
|
+
### Complete Example: Fit a Linear Model
|
|
158
|
+
|
|
159
|
+
**Goal:** Find `salary = a × years_exp + b` that best fits employee data.
|
|
160
|
+
|
|
161
|
+
**Step 1: Import data**
|
|
162
|
+
```bash
|
|
163
|
+
POST /import {
|
|
164
|
+
userid, project: "fit", name: "EMP",
|
|
165
|
+
data: [
|
|
166
|
+
{"years_exp": 2, "salary": 55000},
|
|
167
|
+
{"years_exp": 5, "salary": 72000},
|
|
168
|
+
{"years_exp": 8, "salary": 88000},
|
|
169
|
+
{"years_exp": 12, "salary": 105000},
|
|
170
|
+
{"years_exp": 15, "salary": 118000}
|
|
171
|
+
]
|
|
172
|
+
}
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
**Step 2: Write and compile the VALUE program**
|
|
176
|
+
```chaprola
|
|
177
|
+
// VALUE program: salary = R1 * years_exp + R2
|
|
178
|
+
// R1 = slope (per-year raise), R2 = base salary
|
|
179
|
+
// R21 = sum of squared residuals (SSR)
|
|
180
|
+
|
|
181
|
+
DEFINE VARIABLE REC R41
|
|
182
|
+
DEFINE VARIABLE YRS R42
|
|
183
|
+
DEFINE VARIABLE SAL R43
|
|
184
|
+
DEFINE VARIABLE PRED R44
|
|
185
|
+
DEFINE VARIABLE RESID R45
|
|
186
|
+
DEFINE VARIABLE SSR R46
|
|
187
|
+
|
|
188
|
+
LET SSR = 0
|
|
189
|
+
LET REC = 1
|
|
190
|
+
100 SEEK REC
|
|
191
|
+
IF EOF GOTO 200
|
|
192
|
+
GET YRS FROM P.years_exp
|
|
193
|
+
GET SAL FROM P.salary
|
|
194
|
+
LET PRED = R1 * YRS
|
|
195
|
+
LET PRED = PRED + R2
|
|
196
|
+
LET RESID = PRED - SAL
|
|
197
|
+
LET RESID = RESID * RESID
|
|
198
|
+
LET SSR = SSR + RESID
|
|
199
|
+
LET REC = REC + 1
|
|
200
|
+
GOTO 100
|
|
201
|
+
200 LET R21 = SSR
|
|
202
|
+
END
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
Compile with: `primary_format: "EMP"`
|
|
206
|
+
|
|
207
|
+
**Step 3: Run HULDRA**
|
|
145
208
|
```bash
|
|
146
209
|
POST /optimize {
|
|
147
|
-
userid, project
|
|
148
|
-
|
|
149
|
-
|
|
210
|
+
userid, project: "fit",
|
|
211
|
+
program: "SALFIT",
|
|
212
|
+
primary_file: "EMP",
|
|
213
|
+
elements: [
|
|
214
|
+
{index: 1, label: "per_year_raise", start: 5000, min: 0, max: 20000, delta: 10},
|
|
215
|
+
{index: 2, label: "base_salary", start: 40000, min: 0, max: 100000, delta: 100}
|
|
216
|
+
],
|
|
217
|
+
objectives: [
|
|
218
|
+
{index: 1, label: "SSR", goal: 0.0, weight: 1.0}
|
|
219
|
+
],
|
|
150
220
|
max_iterations: 100
|
|
151
221
|
}
|
|
152
222
|
```
|
|
223
|
+
|
|
224
|
+
**Response:**
|
|
225
|
+
```json
|
|
226
|
+
{
|
|
227
|
+
"status": "converged",
|
|
228
|
+
"iterations": 12,
|
|
229
|
+
"elements": [
|
|
230
|
+
{"index": 1, "label": "per_year_raise", "value": 4876.5},
|
|
231
|
+
{"index": 2, "label": "base_salary", "value": 46230.1}
|
|
232
|
+
],
|
|
233
|
+
"objectives": [
|
|
234
|
+
{"index": 1, "label": "SSR", "value": 2841050.3, "goal": 0.0}
|
|
235
|
+
],
|
|
236
|
+
"elapsed_seconds": 0.02
|
|
237
|
+
}
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
**Result:** `salary = $4,877/year × experience + $46,230 base`
|
|
241
|
+
|
|
242
|
+
### Element Parameters Explained
|
|
243
|
+
|
|
244
|
+
| Field | Description | Guidance |
|
|
245
|
+
|-------|-------------|----------|
|
|
246
|
+
| `index` | Maps to R-variable (1 → R1, 2 → R2, ...) | Max 20 elements |
|
|
247
|
+
| `label` | Human-readable name | Returned in results |
|
|
248
|
+
| `start` | Initial guess | Closer to true value = faster convergence |
|
|
249
|
+
| `min`, `max` | Bounds | HULDRA clamps parameters to this range |
|
|
250
|
+
| `delta` | Step size for gradient computation | ~0.1% of expected value range. Too large = inaccurate gradients. Too small = numerical noise |
|
|
251
|
+
|
|
252
|
+
### Choosing Delta Values
|
|
253
|
+
|
|
254
|
+
Delta controls how HULDRA estimates gradients (via central differences). Rules of thumb:
|
|
255
|
+
- **Dollar amounts** (fares, salaries): `delta: 0.01` to `1.0`
|
|
256
|
+
- **Rates/percentages** (per-mile, per-minute): `delta: 0.001` to `0.01`
|
|
257
|
+
- **Counts/integers**: `delta: 0.1` to `1.0`
|
|
258
|
+
- **Time values** (hours, peaks): `delta: 0.05` to `0.5`
|
|
259
|
+
|
|
260
|
+
If optimization doesn't converge, try making delta smaller.
|
|
261
|
+
|
|
262
|
+
### Performance & Limits
|
|
263
|
+
|
|
264
|
+
HULDRA runs your VALUE program **1 + 2 × N_elements** times per iteration (once for evaluation, twice per element for gradient). With `max_iterations: 100`:
|
|
265
|
+
|
|
266
|
+
| Elements | VM runs/iteration | At 100 iterations |
|
|
267
|
+
|----------|-------------------|-------------------|
|
|
268
|
+
| 2 | 5 | 500 |
|
|
269
|
+
| 3 | 7 | 700 |
|
|
270
|
+
| 5 | 11 | 1,100 |
|
|
271
|
+
| 10 | 21 | 2,100 |
|
|
272
|
+
|
|
273
|
+
**Lambda timeout is 900 seconds.** If each VM run takes 0.01s (100 records), you're fine. If each run takes 1s (100K records), 3 elements × 100 iterations = 700s — cutting it close.
|
|
274
|
+
|
|
275
|
+
**Strategy for large datasets:** Sample first. Query 200–500 representative records into a smaller dataset, optimize against that. The coefficients transfer to the full dataset.
|
|
276
|
+
|
|
277
|
+
```bash
|
|
278
|
+
# Sample 500 records from a large dataset
|
|
279
|
+
POST /query {userid, project, file: "BIGDATA", limit: 500, offset: 100000}
|
|
280
|
+
# Import the sample
|
|
281
|
+
POST /import {userid, project, name: "SAMPLE", data: [...results...]}
|
|
282
|
+
# Optimize against the sample
|
|
283
|
+
POST /optimize {... primary_file: "SAMPLE" ...}
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
### Async Optimization
|
|
287
|
+
|
|
288
|
+
For optimizations that might exceed 30 seconds (API Gateway timeout), use async mode:
|
|
289
|
+
|
|
290
|
+
```bash
|
|
291
|
+
POST /optimize {
|
|
292
|
+
... async_exec: true ...
|
|
293
|
+
}
|
|
294
|
+
# Response: {status: "running", job_id: "20260325_..."}
|
|
295
|
+
|
|
296
|
+
POST /optimize/status {userid, project, job_id: "20260325_..."}
|
|
297
|
+
# Response: {status: "converged", elements: [...], ...}
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
### Multi-Objective Optimization
|
|
301
|
+
|
|
302
|
+
HULDRA can minimize multiple objectives simultaneously with different weights:
|
|
303
|
+
|
|
304
|
+
```bash
|
|
305
|
+
objectives: [
|
|
306
|
+
{index: 1, label: "price_error", goal: 0.0, weight: 1.0},
|
|
307
|
+
{index: 2, label: "volume_error", goal: 0.0, weight: 10.0}
|
|
308
|
+
]
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
Higher weight = more important. HULDRA minimizes `Q = sum(weight × (value - goal)²)`.
|
|
312
|
+
|
|
313
|
+
### Interpreting Results
|
|
314
|
+
|
|
315
|
+
- **`status: "converged"`** — Optimal parameters found. The objective stopped improving.
|
|
316
|
+
- **`status: "timeout"`** — Hit 900s wall clock. Results are the best found so far — often still useful.
|
|
317
|
+
- **`total_objective`** — The raw Q value. Compare across runs, not in absolute terms. Lower = better fit.
|
|
318
|
+
- **`SSR` (objective value)** — Sum of squared residuals. Divide by record count for mean squared error. Take the square root for RMSE in the same units as your data.
|
|
319
|
+
- **`dq_dx` on elements** — Gradient. Values near zero mean the parameter is well-optimized. Large values may indicate the bounds are too tight.
|
|
320
|
+
|
|
321
|
+
### Nonlinear Models
|
|
322
|
+
|
|
323
|
+
HULDRA handles any model you can express in Chaprola, not just linear. Use EXP, LOG, SQRT, POW for curves:
|
|
324
|
+
|
|
325
|
+
```chaprola
|
|
326
|
+
// Exponential decay: value = R1 * exp(-R2 * time) + R3
|
|
327
|
+
DEFINE VARIABLE T R41
|
|
328
|
+
DEFINE VARIABLE OBS R42
|
|
329
|
+
DEFINE VARIABLE PRED R43
|
|
330
|
+
DEFINE VARIABLE ARG R44
|
|
331
|
+
DEFINE VARIABLE SSR R45
|
|
332
|
+
|
|
333
|
+
GET T FROM P.time
|
|
334
|
+
GET OBS FROM P.observed
|
|
335
|
+
LET ARG = R2 * T
|
|
336
|
+
LET ARG = ARG * -1
|
|
337
|
+
LET PRED = EXP ARG
|
|
338
|
+
LET PRED = PRED * R1
|
|
339
|
+
LET PRED = PRED + R3
|
|
340
|
+
LET R46 = PRED - OBS // residual
|
|
341
|
+
LET R46 = R46 * R46
|
|
342
|
+
LET SSR = SSR + R46
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
This fits exponential decay, growth curves, dose-response functions, depreciation models — any formula where you need to find the best-fitting coefficients.
|
|
346
|
+
|
|
347
|
+
### Agent Workflow Summary
|
|
348
|
+
|
|
349
|
+
1. **Inspect** — Call `/format` to see what fields exist
|
|
350
|
+
2. **Sample** — Use `/query` with `limit` to get a manageable subset (200–500 records)
|
|
351
|
+
3. **Import sample** — `/import` the subset as a new small dataset
|
|
352
|
+
4. **Hypothesize** — Propose a model relating the fields
|
|
353
|
+
5. **Write VALUE program** — Loop through records, compute predicted vs actual, accumulate SSR in R21
|
|
354
|
+
6. **Compile** — `/compile` with `primary_format` pointing to the sample
|
|
355
|
+
7. **Optimize** — `/optimize` with elements, objectives, and the sample as primary_file
|
|
356
|
+
8. **Interpret** — Read the converged element values — those are your model coefficients
|
|
357
|
+
9. **Iterate** — If SSR is high, try a different model (add terms, try nonlinear)
|
package/references/gotchas.md
CHANGED
|
@@ -72,6 +72,33 @@ Only one secondary file can be open. CLOSE before opening another. Save any need
|
|
|
72
72
|
### CLOSE flushes writes
|
|
73
73
|
Always CLOSE before END if you wrote to the secondary file. Unflushed writes are lost.
|
|
74
74
|
|
|
75
|
+
## HULDRA Optimization
|
|
76
|
+
|
|
77
|
+
### Use R41–R50 for scratch variables, not R1–R20
|
|
78
|
+
R1–R20 are reserved for HULDRA elements. R21–R40 are reserved for objectives. Your VALUE program's DEFINE VARIABLE declarations must use R41–R50 only.
|
|
79
|
+
```chaprola
|
|
80
|
+
// WRONG: DEFINE VARIABLE counter R1 (HULDRA will overwrite this)
|
|
81
|
+
// RIGHT: DEFINE VARIABLE counter R41
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### Sample large datasets before optimizing
|
|
85
|
+
HULDRA runs your program `1 + 2 × N_elements` times per iteration. With 3 elements and 100 iterations, that's 700 VM runs. If each run processes 1M records (7+ seconds), total time = 5,000+ seconds — well beyond the 900-second Lambda timeout. Query 200–500 records into a sample dataset and optimize against that.
|
|
86
|
+
|
|
87
|
+
### Delta too large = bad convergence
|
|
88
|
+
If HULDRA doesn't converge or oscillates, reduce `delta`. Start with ~0.1% of the expected parameter range. For dollar amounts, try `delta: 0.01`. For rates, try `delta: 0.001`.
|
|
89
|
+
|
|
90
|
+
### Always initialize SSR to zero
|
|
91
|
+
Your VALUE program accumulates squared residuals across all records. If you forget `LET SSR = 0` before the loop, SSR carries garbage from a previous HULDRA iteration (R-variables persist between runs within an optimization).
|
|
92
|
+
|
|
93
|
+
### Filter bad data in the VALUE program
|
|
94
|
+
Negative fares, zero distances, and other anomalies will corrupt your fit. Add guards:
|
|
95
|
+
```chaprola
|
|
96
|
+
GET FARE FROM P.fare
|
|
97
|
+
IF FARE LE 0 GOTO 300 // skip bad records
|
|
98
|
+
// ... compute residual ...
|
|
99
|
+
300 LET REC = REC + 1
|
|
100
|
+
```
|
|
101
|
+
|
|
75
102
|
## Email
|
|
76
103
|
|
|
77
104
|
### Content moderation on outbound
|