kaax-mcp 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,305 @@
1
+ /**
2
+ * Static documentation resources exposed to the agent.
3
+ *
4
+ * MCP resources are read-only blobs the agent can fetch by URI. We keep them
5
+ * embedded in the binary instead of fetching the live web pages so the MCP
6
+ * server stays usable offline and snappy. The URIs map 1:1 to the references
7
+ * that the tools above emit (kaax://docs/quickstart, kaax://docs/tags, …) —
8
+ * if an agent says "see kaax://docs/X" the agent can then call
9
+ * `resources/read` on that URI to actually consume the doc.
10
+ */
11
+ export function buildResources() {
12
+ return [
13
+ {
14
+ resource: {
15
+ uri: "kaax://docs/quickstart",
16
+ name: "Kaax — Quick start (30 min)",
17
+ description: "End-to-end walkthrough: account → field → model → first analysis. Use when the user is new.",
18
+ mimeType: "text/markdown",
19
+ },
20
+ text: `# Kaax — Quick start
21
+
22
+ You can take an account from zero to a working analysis in about 30 minutes. The
23
+ critical-path is short:
24
+
25
+ 1. **Activate your account** — verify your email, complete the profile, and
26
+ confirm your subscription tier on \`/dashboard/payment\`. Replanting,
27
+ counting and path analyses are all included in the Pro tier.
28
+ 2. **Upload a field** — go to \`/dashboard/manage\` → *Tus Campos* → *Nuevo
29
+ campo*. Drop a KML with the parcel boundary (or convert from shapefile via
30
+ \`kaax_convert_shapefile_to_kml\`). Name the field after the parcel; you can
31
+ tag it later.
32
+ 3. **Upload imagery** — drone orthomosaic or satellite tile. The dashboard
33
+ accepts a single GeoTIFF or a folder of tiles. Multipart upload is on by
34
+ default for files > 100 MB.
35
+ 4. **(For counting/replanting) Upload a model** — \`/dashboard/models\` →
36
+ *Subir modelo*. Without a detection model these two analysis types cannot
37
+ run; the path analysis works out of the box.
38
+ 5. **Configure** — pick the analysis type (replanting / counting / path), set
39
+ the advanced parameters (call \`kaax_suggest_configuration\` for a sensible
40
+ default) and run.
41
+ 6. **Inspect the report** — the analysis surfaces survival/depopulation rates,
42
+ total counts, total area in ha, and per-zone breakdowns.
43
+
44
+ Useful follow-ups:
45
+ - \`kaax_check_setup_status\` — what's still missing on the account.
46
+ - \`kaax_suggest_tags_for_field\` — organise across season / region / crop.
47
+ - \`kaax_explain_advanced_config\` — what every parameter does.
48
+ `,
49
+ },
50
+ {
51
+ resource: {
52
+ uri: "kaax://docs/tags",
53
+ name: "Kaax — Tagging best practices",
54
+ description: "How to organise fields and analyses with tags.",
55
+ mimeType: "text/markdown",
56
+ },
57
+ text: `# Tagging in Kaax
58
+
59
+ Tags are the only structured way to slice across fields and analyses. Use them
60
+ as a lightweight CRM: a single field can carry several tags from different
61
+ axes, and the dashboard lets you filter analyses by any combination.
62
+
63
+ ## Recommended taxonomy (4 axes)
64
+
65
+ | Axis | Example tag | Why |
66
+ | ------- | ----------------- | ---------------------------- |
67
+ | Season | \`zafra-2024\` | Compare year-over-year |
68
+ | Region | \`region-escuintla\` | Cluster by agronomy & climate |
69
+ | Crop | \`cultivo-cana\` | Filter analyses by species |
70
+ | Status | \`estado-activo\` | Hide archived parcels |
71
+
72
+ You can layer in extras — \`riego-secano\`, \`semilla-cp741714\`,
73
+ \`zonificacion-este\` — but resist the urge to invent a tag per field; use the
74
+ parcel name field for that.
75
+
76
+ ## Normalisation rules
77
+
78
+ The server normalises every tag on the way in:
79
+
80
+ - lowercase
81
+ - accents stripped (NFD + diacritic removal)
82
+ - non-alphanumeric runs collapsed to \`-\`
83
+ - leading/trailing \`-\` trimmed
84
+ - 50-character hard cap
85
+ - 200-tag soft cap per user (forces curation)
86
+
87
+ Sending the same tag twice is **idempotent**, so it's safe for an agent to
88
+ retry.
89
+
90
+ ## Useful tools
91
+
92
+ - \`kaax_list_tags\` — current taxonomy with usage counts.
93
+ - \`kaax_suggest_tags_for_field\` — heuristic tags from a field name.
94
+ - \`kaax_attach_field_tags\` — persist tags to a field.
95
+ `,
96
+ },
97
+ {
98
+ resource: {
99
+ uri: "kaax://docs/configurations",
100
+ name: "Kaax — Advanced configuration",
101
+ description: "Parameter glossary and tuning recipes.",
102
+ mimeType: "text/markdown",
103
+ },
104
+ text: `# Advanced configuration
105
+
106
+ Every analysis has a JSON configuration. The defaults are reasonable, but
107
+ tuning is what separates a good report from a great one. Below is the cheat
108
+ sheet — call \`kaax_explain_advanced_config\` for the per-parameter detail.
109
+
110
+ ## Counting
111
+
112
+ \`\`\`json
113
+ {
114
+ "blockSize": 2048,
115
+ "accuracy": 0.25,
116
+ "postProcessing": { "refinement": true, "radiusFactor": 0.8, "iou": 0.2 },
117
+ "sizeClassification": { "smallMax": 0.05, "mediumMax": 0.15, "largeMax": 0.3 }
118
+ }
119
+ \`\`\`
120
+
121
+ If you see too many duplicates in dense areas, lower \`iou\` to 0.15. If you
122
+ see misses on overlapping plants, raise \`radiusFactor\` to 1.0.
123
+
124
+ ## Replanting
125
+
126
+ \`\`\`json
127
+ {
128
+ "blockSize": 2048,
129
+ "accuracy": 0.25,
130
+ "lineThickness": 1.5,
131
+ "seedsPerMeter": 3.75,
132
+ "seedsPerBox": 30,
133
+ "postProcessing": { "refinement": true, "iou": 0.2 }
134
+ }
135
+ \`\`\`
136
+
137
+ \`seedsPerMeter\` and \`seedsPerBox\` are agronomy-driven — set them from the
138
+ crop's planting plan, not from the model.
139
+
140
+ ## Path
141
+
142
+ \`\`\`json
143
+ { "blockSize": 2048, "accuracy": 0.3, "overlap": 0.1 }
144
+ \`\`\`
145
+
146
+ For very narrow paths, raise \`overlap\` to 0.2 to reduce seam artefacts.
147
+
148
+ ## Sourcing defaults
149
+
150
+ - \`kaax_suggest_configuration\` — the curated preset above.
151
+ - \`kaax_peer_config_suggestions\` — anonymised crowd average for the same
152
+ analysis type. Use it as a sanity check, not a replacement.
153
+ `,
154
+ },
155
+ {
156
+ resource: {
157
+ uri: "kaax://docs/shapefile-import",
158
+ name: "Kaax — Importing shapefiles",
159
+ description: "Convert .shp → .kml for upload. No GDAL or ogr2ogr needed.",
160
+ mimeType: "text/markdown",
161
+ },
162
+ text: `# Importing shapefiles into Kaax
163
+
164
+ Kaax accepts KML for parcel boundaries. If you only have ESRI shapefiles you
165
+ can convert them in one tool call:
166
+
167
+ \`\`\`
168
+ kaax_convert_shapefile_to_kml({
169
+ shpPath: "C:/finca/parcela_a.shp",
170
+ outputPath: "C:/finca/parcela_a.kml" // optional
171
+ })
172
+ \`\`\`
173
+
174
+ What the tool does:
175
+
176
+ 1. Opens the \`.shp\` and the sibling \`.dbf\` (must be in the same folder).
177
+ 2. Re-emits every geometry as KML 2.2 — Polygon, MultiPolygon, Point,
178
+ MultiPoint, LineString, MultiLineString. \`Z\` coordinates are dropped to
179
+ 2D (Kaax does not consume elevation).
180
+ 3. Preserves DBF attributes as \`<ExtendedData><Data name="…">\` pairs so
181
+ you don't lose plot ids / variety names.
182
+ 4. Writes the file next to the .shp by default.
183
+
184
+ What it doesn't do:
185
+
186
+ - Reproject. Your .shp **must already be in WGS84 (EPSG:4326)**. If it isn't,
187
+ reproject first with QGIS or \`ogr2ogr -t_srs EPSG:4326\`.
188
+ - Merge multiple .shp files. Run one tool call per shapefile.
189
+
190
+ After conversion, upload the .kml at \`/dashboard/manage → Tus Campos →
191
+ Nuevo campo\`.
192
+ `,
193
+ },
194
+ {
195
+ resource: {
196
+ uri: "kaax://docs/models",
197
+ name: "Kaax — Detection models",
198
+ description: "Why counting & replanting need a model, and how to upload one.",
199
+ mimeType: "text/markdown",
200
+ },
201
+ text: `# Detection models
202
+
203
+ Counting and replanting analyses depend on a detection model trained on your
204
+ crop. Kaax does **not** ship a one-size-fits-all model — accuracy on
205
+ sugarcane, pineapple, banana and oil palm is too dependent on resolution,
206
+ flight height and growth stage to use a single weights file.
207
+
208
+ ## Uploading
209
+
210
+ - Page: \`/dashboard/models\`
211
+ - Format: PyTorch \`.pt\` checkpoint
212
+ - Naming: include crop + version (e.g. \`cana-v3.pt\`) — the agent uses the
213
+ name to match models against analyses.
214
+
215
+ ## When you don't have a model
216
+
217
+ - **Path analysis** doesn't need one — point the user there first.
218
+ - **Pilot programmes** — the Kaax team can train one for you. Send a sample
219
+ orthomosaic + ground truth annotations to support.
220
+
221
+ ## After upload
222
+
223
+ Run \`kaax_list_models\` to confirm the model is visible to the API. If it
224
+ shows up there, it'll show up in the analysis configuration drop-down too.
225
+ `,
226
+ },
227
+ {
228
+ resource: {
229
+ uri: "kaax://docs/density-stats",
230
+ name: "Kaax — Density and rate aggregates",
231
+ description: "How density / survival / depopulation metrics are computed.",
232
+ mimeType: "text/markdown",
233
+ },
234
+ text: `# Density and rate metrics
235
+
236
+ Every analysis returns three families of metrics. The MCP server aggregates
237
+ them across fields when you call \`kaax_get_density_stats\`.
238
+
239
+ ## Core counts
240
+
241
+ - \`totalAreaHa\` — analysed area in hectares.
242
+ - \`totalCounts\` — number of detected plants.
243
+ - \`averageTotalCounts\` — counts per analysis tile.
244
+
245
+ \`detectionsPerHa = totalCounts / totalAreaHa\` is the canonical density.
246
+
247
+ ## Rates (0–1)
248
+
249
+ - \`survivalRate\` — fraction of expected plants that are alive.
250
+ - \`depopulationRate\` — fraction of expected positions that are empty.
251
+ - \`replantingRate\` — fraction of expected positions that need re-seeding.
252
+ - \`pathRate\` — fraction of the parcel covered by path-like geometry.
253
+
254
+ When you ask "which fields are underperforming", filter analyses where
255
+ \`survivalRate < 0.85\` or \`depopulationRate > 0.15\`. Use
256
+ \`kaax_find_similar_fields\` to find historic analyses with comparable
257
+ profiles.
258
+ `,
259
+ },
260
+ {
261
+ resource: {
262
+ uri: "kaax://docs/security",
263
+ name: "Kaax — MCP security model",
264
+ description: "How the MCP server protects the user's API key and respects privacy.",
265
+ mimeType: "text/markdown",
266
+ },
267
+ text: `# Security model
268
+
269
+ The MCP server is a local process. Every guarantee below holds **on the
270
+ user's machine** — there is no Kaax-hosted intermediary.
271
+
272
+ ## API key
273
+
274
+ - Read once from the \`KAAX_API_KEY\` environment variable.
275
+ - Never logged, never echoed back to the agent.
276
+ - Sent only to the configured \`KAAX_BASE_URL\` (defaults to
277
+ https://www.kaax-agritech.com). Override only if you have a private
278
+ deployment.
279
+ - The server uses HTTPS and a 30 s timeout per request.
280
+
281
+ ## Privacy
282
+
283
+ - Every Kaax API call is scoped to the api-key owner. The server cannot
284
+ return another user's fields, models or analyses.
285
+ - \`kaax_peer_config_suggestions\` is the **only** tool that touches other
286
+ users' data, and it does so through the \`/api/v2/peer-config-suggestions\`
287
+ endpoint, which:
288
+ - excludes the caller from the aggregate,
289
+ - requires a minimum number of distinct peers,
290
+ - returns only numeric averages rounded to natural quanta — no names, no
291
+ geometries, no user identifiers.
292
+ - Local conversions (\`kaax_convert_shapefile_to_kml\`) never leave the
293
+ user's machine; they are pure file I/O.
294
+
295
+ ## What the agent sees
296
+
297
+ The agent only sees the tool outputs above — never the raw API key, never
298
+ HTTP details, never the user's session cookies. If you need to debug HTTP
299
+ errors, set \`DEBUG=kaax-mcp:*\` to print to stderr; stderr is not piped to
300
+ the LLM.
301
+ `,
302
+ },
303
+ ];
304
+ }
305
+ //# sourceMappingURL=resources.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resources.js","sourceRoot":"","sources":["../src/resources.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AASH,MAAM,UAAU,cAAc;IAC5B,OAAO;QACL;YACE,QAAQ,EAAE;gBACR,GAAG,EAAE,wBAAwB;gBAC7B,IAAI,EAAE,6BAA6B;gBACnC,WAAW,EACT,6FAA6F;gBAC/F,QAAQ,EAAE,eAAe;aAC1B;YACD,IAAI,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA4BX;SACI;QAED;YACE,QAAQ,EAAE;gBACR,GAAG,EAAE,kBAAkB;gBACvB,IAAI,EAAE,+BAA+B;gBACrC,WAAW,EAAE,gDAAgD;gBAC7D,QAAQ,EAAE,eAAe;aAC1B;YACD,IAAI,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAsCX;SACI;QAED;YACE,QAAQ,EAAE;gBACR,GAAG,EAAE,4BAA4B;gBACjC,IAAI,EAAE,+BAA+B;gBACrC,WAAW,EAAE,wCAAwC;gBACrD,QAAQ,EAAE,eAAe;aAC1B;YACD,IAAI,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAiDX;SACI;QAED;YACE,QAAQ,EAAE;gBACR,GAAG,EAAE,8BAA8B;gBACnC,IAAI,EAAE,6BAA6B;gBACnC,WAAW,EACT,4DAA4D;gBAC9D,QAAQ,EAAE,eAAe;aAC1B;YACD,IAAI,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA8BX;SACI;QAED;YACE,QAAQ,EAAE;gBACR,GAAG,EAAE,oBAAoB;gBACzB,IAAI,EAAE,yBAAyB;gBAC/B,WAAW,EACT,gEAAgE;gBAClE,QAAQ,EAAE,eAAe;aAC1B;YACD,IAAI,EAAE;;;;;;;;;;;;;;;;;;;;;;;;CAwBX;SACI;QAED;YACE,QAAQ,EAAE;gBACR,GAAG,EAAE,2BAA2B;gBAChC,IAAI,EAAE,oCAAoC;gBAC1C,WAAW,EAAE,6DAA6D;gBAC1E,QAAQ,EAAE,eAAe;aAC1B;YACD,IAAI,EAAE;;;;;;;;;;;;;;;;;;;;;;;;CAwBX;SACI;QAED;YACE,QAAQ,EAAE;gBACR,GAAG,EAAE,sBAAsB;gBAC3B,IAAI,EAAE,2BAA2B;gBACjC,WAAW,EACT,sEAAsE;gBACxE,QAAQ,EAAE,eAAe;aAC1B;YACD,IAAI,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAkCX;SACI;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Lightweight spatial / similarity helpers — no external geospatial deps.
3
+ *
4
+ * Two flavours of "similar field" used by the MCP tools:
5
+ *
6
+ * 1. Metric similarity — cosine distance over the analysis numeric vector
7
+ * (survival, depopulation, density, etc.). Works even when the API
8
+ * does not return geometry, which is the common case today.
9
+ *
10
+ * 2. Geographic similarity — Haversine distance between centroids parsed
11
+ * from the KML string in the analysis record (when present).
12
+ */
13
+ import type { AnalysisRecord, Coord } from "./types.js";
14
+ /**
15
+ * Great-circle distance between two coordinates in kilometres.
16
+ * Returns `Infinity` if either point is missing.
17
+ */
18
+ export declare function haversineKm(a: Coord | undefined, b: Coord | undefined): number;
19
+ /**
20
+ * Parses a KML `<coordinates>` block (loose match) and returns the centroid
21
+ * of the *first* polygon found, in lon/lat decimal degrees.
22
+ * Returns `undefined` if no coordinates are found.
23
+ */
24
+ export declare function centroidFromKml(kml: string | undefined): Coord | undefined;
25
+ /**
26
+ * Builds a numeric feature vector from an analysis record.
27
+ * Missing values become 0. The order is stable so cosine works.
28
+ */
29
+ export declare function vectorize(rec: AnalysisRecord): number[];
30
+ /** Cosine similarity between two equal-length vectors. */
31
+ export declare function cosine(a: number[], b: number[]): number;
32
+ /**
33
+ * Given a reference analysis, find the most similar records from a pool.
34
+ * Hybrid score: 0.7 · cosine metric similarity + 0.3 · normalised proximity
35
+ * (closer = higher) when geometry is available, else metric only.
36
+ */
37
+ export declare function findSimilar(ref: AnalysisRecord, pool: AnalysisRecord[], topK?: number): {
38
+ record: AnalysisRecord;
39
+ score: number;
40
+ distanceKm?: number;
41
+ }[];
42
+ /** Aggregates density stats across a pool of analyses. */
43
+ export declare function aggregateDensity(pool: AnalysisRecord[]): {
44
+ count: number;
45
+ totalAreaHa: number;
46
+ totalDetections: number;
47
+ avgDetectionsPerHa: number | null;
48
+ avgSurvivalRate: number | null;
49
+ avgDepopulationRate: number | null;
50
+ avgReplantingRate: number | null;
51
+ };
@@ -0,0 +1,152 @@
1
+ /**
2
+ * Lightweight spatial / similarity helpers — no external geospatial deps.
3
+ *
4
+ * Two flavours of "similar field" used by the MCP tools:
5
+ *
6
+ * 1. Metric similarity — cosine distance over the analysis numeric vector
7
+ * (survival, depopulation, density, etc.). Works even when the API
8
+ * does not return geometry, which is the common case today.
9
+ *
10
+ * 2. Geographic similarity — Haversine distance between centroids parsed
11
+ * from the KML string in the analysis record (when present).
12
+ */
13
+ /** Earth radius in km. */
14
+ const EARTH_R = 6371.0088;
15
+ /** Convert degrees to radians. */
16
+ function toRad(deg) {
17
+ return (deg * Math.PI) / 180;
18
+ }
19
+ /**
20
+ * Great-circle distance between two coordinates in kilometres.
21
+ * Returns `Infinity` if either point is missing.
22
+ */
23
+ export function haversineKm(a, b) {
24
+ if (!a || !b)
25
+ return Infinity;
26
+ const dLat = toRad(b.lat - a.lat);
27
+ const dLon = toRad(b.lon - a.lon);
28
+ const lat1 = toRad(a.lat);
29
+ const lat2 = toRad(b.lat);
30
+ const h = Math.sin(dLat / 2) ** 2 + Math.cos(lat1) * Math.cos(lat2) * Math.sin(dLon / 2) ** 2;
31
+ return 2 * EARTH_R * Math.asin(Math.min(1, Math.sqrt(h)));
32
+ }
33
+ /**
34
+ * Parses a KML `<coordinates>` block (loose match) and returns the centroid
35
+ * of the *first* polygon found, in lon/lat decimal degrees.
36
+ * Returns `undefined` if no coordinates are found.
37
+ */
38
+ export function centroidFromKml(kml) {
39
+ if (!kml)
40
+ return undefined;
41
+ const m = /<coordinates>([\s\S]+?)<\/coordinates>/.exec(kml);
42
+ if (!m)
43
+ return undefined;
44
+ const raw = m[1].trim();
45
+ // KML coordinates are whitespace-separated triplets: lon,lat[,alt]
46
+ const points = raw
47
+ .split(/\s+/)
48
+ .map((triplet) => {
49
+ const parts = triplet.split(",").map(Number);
50
+ if (parts.length < 2 || parts.some((n) => !Number.isFinite(n)))
51
+ return null;
52
+ return { lon: parts[0], lat: parts[1] };
53
+ })
54
+ .filter((p) => p !== null);
55
+ if (points.length === 0)
56
+ return undefined;
57
+ const sum = points.reduce((acc, p) => ({ lon: acc.lon + p.lon, lat: acc.lat + p.lat }), { lon: 0, lat: 0 });
58
+ return { lon: sum.lon / points.length, lat: sum.lat / points.length };
59
+ }
60
+ /**
61
+ * Builds a numeric feature vector from an analysis record.
62
+ * Missing values become 0. The order is stable so cosine works.
63
+ */
64
+ export function vectorize(rec) {
65
+ const a = rec.analysis ?? {};
66
+ return [
67
+ a.totalAreaHa ?? 0,
68
+ a.totalCounts ?? 0,
69
+ a.survivalRate ?? 0,
70
+ a.depopulationRate ?? 0,
71
+ a.replantingRate ?? 0,
72
+ a.pathRate ?? 0,
73
+ a.totalPathAreaHa ?? 0,
74
+ a.averageTotalCounts ?? 0,
75
+ ];
76
+ }
77
+ /** Cosine similarity between two equal-length vectors. */
78
+ export function cosine(a, b) {
79
+ if (a.length !== b.length)
80
+ return 0;
81
+ let dot = 0;
82
+ let na = 0;
83
+ let nb = 0;
84
+ for (let i = 0; i < a.length; i++) {
85
+ dot += a[i] * b[i];
86
+ na += a[i] * a[i];
87
+ nb += b[i] * b[i];
88
+ }
89
+ if (na === 0 || nb === 0)
90
+ return 0;
91
+ return dot / (Math.sqrt(na) * Math.sqrt(nb));
92
+ }
93
+ /**
94
+ * Given a reference analysis, find the most similar records from a pool.
95
+ * Hybrid score: 0.7 · cosine metric similarity + 0.3 · normalised proximity
96
+ * (closer = higher) when geometry is available, else metric only.
97
+ */
98
+ export function findSimilar(ref, pool, topK = 5) {
99
+ const refVec = vectorize(ref);
100
+ const refCenter = centroidFromKml(ref.kml);
101
+ const scored = pool
102
+ .filter((r) => r._id !== ref._id)
103
+ .map((r) => {
104
+ const sim = cosine(refVec, vectorize(r));
105
+ const center = centroidFromKml(r.kml);
106
+ let geoScore = 0;
107
+ let distanceKm;
108
+ if (refCenter && center) {
109
+ distanceKm = haversineKm(refCenter, center);
110
+ // Decay over 50 km — beyond that, geographic relatedness is weak.
111
+ geoScore = Math.max(0, 1 - distanceKm / 50);
112
+ }
113
+ const score = refCenter ? sim * 0.7 + geoScore * 0.3 : sim;
114
+ return { record: r, score, distanceKm };
115
+ })
116
+ .sort((a, b) => b.score - a.score);
117
+ return scored.slice(0, topK);
118
+ }
119
+ /** Aggregates density stats across a pool of analyses. */
120
+ export function aggregateDensity(pool) {
121
+ if (pool.length === 0) {
122
+ return {
123
+ count: 0,
124
+ totalAreaHa: 0,
125
+ totalDetections: 0,
126
+ avgDetectionsPerHa: null,
127
+ avgSurvivalRate: null,
128
+ avgDepopulationRate: null,
129
+ avgReplantingRate: null,
130
+ };
131
+ }
132
+ const totalAreaHa = pool.reduce((s, r) => s + (r.analysis?.totalAreaHa ?? 0), 0);
133
+ const totalDetections = pool.reduce((s, r) => s + (r.analysis?.totalCounts ?? 0), 0);
134
+ const mean = (key) => {
135
+ const values = pool
136
+ .map((r) => r.analysis?.[key])
137
+ .filter((v) => typeof v === "number" && Number.isFinite(v));
138
+ if (values.length === 0)
139
+ return null;
140
+ return values.reduce((a, b) => a + b, 0) / values.length;
141
+ };
142
+ return {
143
+ count: pool.length,
144
+ totalAreaHa,
145
+ totalDetections,
146
+ avgDetectionsPerHa: totalAreaHa > 0 ? totalDetections / totalAreaHa : null,
147
+ avgSurvivalRate: mean("survivalRate"),
148
+ avgDepopulationRate: mean("depopulationRate"),
149
+ avgReplantingRate: mean("replantingRate"),
150
+ };
151
+ }
152
+ //# sourceMappingURL=spatial.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spatial.js","sourceRoot":"","sources":["../src/spatial.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Tool registry — every callable function exposed to the LLM agent.
3
+ *
4
+ * Each entry pairs a JSON-Schema input definition (what the agent must send)
5
+ * with an async handler that returns text-form results suitable for the
6
+ * agent to reason over. Outputs are kept compact and **explicit about next
7
+ * steps** so the agent never has to guess what to do next.
8
+ */
9
+ import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
10
+ import type { KaaxClient } from "./client.js";
11
+ export interface ToolDef {
12
+ name: string;
13
+ description: string;
14
+ inputSchema: object;
15
+ handler: (args: unknown, ctx: {
16
+ client: KaaxClient;
17
+ }) => Promise<CallToolResult>;
18
+ }
19
+ export declare function buildTools(): ToolDef[];