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.
package/README.md ADDED
@@ -0,0 +1,274 @@
1
+ # Kaax MCP server
2
+
3
+ Connect any [MCP](https://modelcontextprotocol.io)-aware agent — Claude
4
+ Desktop, Claude Code, Cursor, Windsurf, Zed — to your Kaax account so it can
5
+ query your analyses, suggest tag taxonomies, recommend advanced
6
+ configurations, find similar fields across your history, aggregate density
7
+ statistics, and convert shapefiles to KML — all locally, with your own API
8
+ key.
9
+
10
+ ## What you get
11
+
12
+ **Full onboarding from inside the agent** — no Kaax account required to start:
13
+
14
+ ```
15
+ kaax_start_onboarding → browser opens at /sign-up?onboardToken=…
16
+ user fills the form (password stays in the browser)
17
+ page binds the new account to the token
18
+
19
+ kaax_complete_onboarding(token) → polls server, returns apiKey ONCE
20
+ late-binds it on the in-process client
21
+
22
+ kaax_request_download → server-side HubSpot form submission
23
+ kaax_check_download_status → poll for admin approval
24
+ kaax_download_kaax → stream the binary to disk
25
+ ```
26
+
27
+ **21 tools**, split by tier:
28
+
29
+ - **Free** (no Pro subscription needed): onboarding, license check, billing
30
+ page opener, download flow, shapefile conversion, tag/config suggestions,
31
+ parameter docs.
32
+ - **Pro** (subscription required): everything that reads or writes data on
33
+ your Kaax account (analyses, fields, models, tags, density stats, peer
34
+ configs, similarity search). Free users get a localized "necesitás Pro"
35
+ message + the upgrade URL instead of an error.
36
+
37
+ The agent should **call `kaax_check_license` first** if it doesn't already
38
+ know the user's plan — every Pro tool tagged its description with `[Pro]`
39
+ so the agent can read intent from the schema.
40
+
41
+ The full list:
42
+
43
+ | Tool | Tier | What it does |
44
+ | --------------------------------- | ----- | -------------------------------------------------- |
45
+ | `kaax_start_onboarding` | Free | Open browser at signup with a single-use 15-min token |
46
+ | `kaax_complete_onboarding` | Free | Poll for binding; returns the apiKey once on success |
47
+ | `kaax_check_license` | Free | Show plan, capabilities, upgrade URL |
48
+ | `kaax_open_billing` | Free | Open the plans page in the user's browser |
49
+ | `kaax_request_download` | Free | Submit the HubSpot form for desktop apps |
50
+ | `kaax_check_download_status` | Free | Poll for approval; returns binary URLs on success |
51
+ | `kaax_download_kaax` | Free | Stream a binary to disk (no buffering) |
52
+ | `kaax_suggest_tags_for_field` | Free | Heuristic tag set for a new parcel |
53
+ | `kaax_suggest_configuration` | Free | Curated configuration preset per analysis type |
54
+ | `kaax_explain_advanced_config` | Free | Plain-language docs per parameter |
55
+ | `kaax_convert_shapefile_to_kml` | Free | Local .shp → .kml conversion (no GDAL needed) |
56
+ | `kaax_check_setup_status` | Pro | Snapshot of fields, models, configs, analyses, tags |
57
+ | `kaax_list_analyses` | Pro | Paginated, filterable list of analyses (v0) |
58
+ | `kaax_get_analysis` | Pro | Full detail for a single analysis |
59
+ | `kaax_list_fields` | Pro | Fields — name, tags, geometry flag (v2) |
60
+ | `kaax_list_models` | Pro | Detection models you've uploaded (v2) |
61
+ | `kaax_list_tags` | Pro | Tags in use with frequencies |
62
+ | `kaax_attach_field_tags` | Pro | Persist tags to a field (v2) |
63
+ | `kaax_peer_config_suggestions` | Pro | Anonymised, privacy-preserving crowd average (v2) |
64
+ | `kaax_find_similar_fields` | Pro | Cosine + geo similarity over your history |
65
+ | `kaax_get_density_stats` | Pro | Total area, detections/ha, mean rates |
66
+
67
+ **7 docs** the agent can `resources/read`:
68
+
69
+ - `kaax://docs/quickstart` — 30-minute zero-to-first-analysis walkthrough
70
+ - `kaax://docs/tags` — 4-axis taxonomy & normalisation rules
71
+ - `kaax://docs/configurations` — parameter cheat sheet
72
+ - `kaax://docs/shapefile-import` — .shp → .kml constraints
73
+ - `kaax://docs/models` — when and how to upload detection models
74
+ - `kaax://docs/density-stats` — what every metric means
75
+ - `kaax://docs/security` — what the MCP server does (and doesn't) send
76
+
77
+ **5 guided prompts** to seed common conversations:
78
+
79
+ - `kaax_onboard_from_zero` — full signup → activate → download flow (no account needed)
80
+ - `kaax_quickstart` — orient an existing user
81
+ - `kaax_analyze_field` — queue an analysis end-to-end
82
+ - `kaax_organize_data` — audit and clean up tags
83
+ - `kaax_import_shapefile` — convert + upload .shp parcels
84
+
85
+ ## Install
86
+
87
+ ```bash
88
+ git clone https://github.com/Ahau-x/Kaax-LandingPage.git
89
+ cd Kaax-LandingPage/mcp
90
+ npm install
91
+ npm run build
92
+ ```
93
+
94
+ The build emits a runnable `dist/index.js` you can invoke directly via
95
+ `node dist/index.js` or — once published — `npx -y @kaax/mcp-server`.
96
+
97
+ ## Configure
98
+
99
+ **Two paths:**
100
+
101
+ 1. **You already have a Kaax account** — get an API key at
102
+ <https://www.kaax-agritech.com/dashboard/api-manage> (Pro plan or higher)
103
+ and set it as `KAAX_API_KEY` in your MCP client config.
104
+ 2. **You're starting from zero** — just install the MCP without an
105
+ `KAAX_API_KEY` env var. Tell the agent "onboard me" and it will run
106
+ `kaax_start_onboarding`, which opens your browser at the signup form.
107
+ Once you submit, `kaax_complete_onboarding` retrieves the apiKey and
108
+ binds it to the session. Save the key from the tool output so you don't
109
+ have to onboard again next time.
110
+
111
+ ### Claude Desktop
112
+
113
+ Edit `~/Library/Application Support/Claude/claude_desktop_config.json` on
114
+ macOS or `%APPDATA%\Claude\claude_desktop_config.json` on Windows:
115
+
116
+ ```json
117
+ {
118
+ "mcpServers": {
119
+ "kaax": {
120
+ "command": "node",
121
+ "args": ["C:/path/to/Kaax-LandingPage/mcp/dist/index.js"],
122
+ "env": {
123
+ "KAAX_API_KEY": "your_api_key_here"
124
+ }
125
+ }
126
+ }
127
+ }
128
+ ```
129
+
130
+ Restart Claude Desktop. The Kaax tools appear in the tool picker.
131
+
132
+ ### Claude Code
133
+
134
+ Add to `.mcp.json` at the root of any project (or `~/.claude/mcp.json` for
135
+ all projects):
136
+
137
+ ```json
138
+ {
139
+ "mcpServers": {
140
+ "kaax": {
141
+ "command": "node",
142
+ "args": ["/absolute/path/to/Kaax-LandingPage/mcp/dist/index.js"],
143
+ "env": { "KAAX_API_KEY": "your_api_key_here" }
144
+ }
145
+ }
146
+ }
147
+ ```
148
+
149
+ ### Cursor
150
+
151
+ `Settings → MCP → Add new MCP server`. Use the same `command` / `args` /
152
+ `env` shape as above.
153
+
154
+ ### Windsurf
155
+
156
+ `Settings → Cascade → MCP Servers → Add new`. Same payload.
157
+
158
+ ### Zed
159
+
160
+ Add to `~/.config/zed/settings.json`:
161
+
162
+ ```json
163
+ {
164
+ "context_servers": {
165
+ "kaax": {
166
+ "command": { "path": "node", "args": ["…/mcp/dist/index.js"] },
167
+ "env": { "KAAX_API_KEY": "your_api_key_here" }
168
+ }
169
+ }
170
+ }
171
+ ```
172
+
173
+ ## Free vs Pro tier
174
+
175
+ The MCP works without a paid subscription — onboarding, downloading the
176
+ desktop apps, converting shapefiles and getting tag/config suggestions all
177
+ run on the Free tier. Tools that read or write account data (analyses,
178
+ fields, models, etc.) are tagged `[Pro]` in their descriptions; calling
179
+ one from a Free account returns a localized "necesitás Pro" message with
180
+ the upgrade URL instead of failing with a raw 403.
181
+
182
+ The agent should run `kaax_check_license` first when the user wants to do
183
+ anything beyond onboarding — that's a single cheap call that tells it
184
+ exactly what's available. Free-tier limits (5 fields, 1 local model,
185
+ 3 custom configs, no API access) are enforced server-side; the MCP just
186
+ gives clearer messages than the raw 403.
187
+
188
+ ## Language (KAAX_LOCALE)
189
+
190
+ User-facing strings default to **Spanish** because that's Kaax's primary
191
+ market. Set `KAAX_LOCALE=en` in your MCP client env to switch to English:
192
+
193
+ ```json
194
+ "env": {
195
+ "KAAX_API_KEY": "…",
196
+ "KAAX_LOCALE": "en"
197
+ }
198
+ ```
199
+
200
+ The agent (Claude) translates the rest fluently on its own, so even
201
+ without setting `KAAX_LOCALE` you can chat in either language and the
202
+ user-facing answers will be in your language. The env var only affects
203
+ the small set of stub messages that the LLM tends to copy verbatim (URLs,
204
+ error codes, "this requires Pro").
205
+
206
+ ## Override the base URL (private deployments only)
207
+
208
+ ```json
209
+ "env": {
210
+ "KAAX_API_KEY": "…",
211
+ "KAAX_BASE_URL": "https://kaax.acme-internal.com"
212
+ }
213
+ ```
214
+
215
+ ## Security
216
+
217
+ Read [`kaax://docs/security`](src/resources.ts) from inside the MCP itself,
218
+ or the short version below:
219
+
220
+ - API key read from `KAAX_API_KEY` once, never logged, sent only over HTTPS.
221
+ - Every API call is scoped to the api-key owner — the server cannot fetch
222
+ other users' data.
223
+ - `kaax_peer_config_suggestions` is the only tool that touches anonymised
224
+ aggregate data; the server endpoint enforces a minimum sample size, excludes
225
+ the caller, and rounds numeric values into natural quanta so a single
226
+ contributor cannot be re-identified.
227
+ - All write endpoints (`kaax_attach_field_tags`, anything creating data) are
228
+ rate-limited server-side (30/min/key) and audit-logged.
229
+ - Shapefile conversion is local file I/O — your .shp never leaves your
230
+ machine.
231
+
232
+ ## Develop
233
+
234
+ ```bash
235
+ npm run dev # tsx watch on src/index.ts
236
+ npm run build # tsc → dist/
237
+ ```
238
+
239
+ Type-check only: `npx tsc --noEmit`.
240
+
241
+ ## Architecture
242
+
243
+ ```
244
+ mcp/src/
245
+ ├── index.ts stdio bootstrap — wires tools, resources, prompts
246
+ ├── client.ts HTTP wrapper + standalone onboarding helpers
247
+ ├── tools.ts 19 callable tools
248
+ ├── resources.ts 7 markdown docs served as MCP resources
249
+ ├── prompts.ts 5 guided conversation starters
250
+ ├── spatial.ts Haversine, KML centroid, cosine, density aggregation
251
+ ├── convert.ts shapefile → KML in pure JS (no GDAL)
252
+ ├── platform.ts openBrowser + streaming file download (no extra deps)
253
+ └── types.ts shared types for v0, v2 and landing shapes
254
+ ```
255
+
256
+ ## Performance & resource use
257
+
258
+ The MCP is engineered to stay light:
259
+
260
+ - **Stateless tools** — no in-process caches that could grow unbounded.
261
+ - **HTTP only** — the MCP never opens a Mongo / Redis / S3 client. All DB
262
+ access stays on the Kaax server, which already pools connections at
263
+ `maxPoolSize: 5` per instance with the shared `dbConnect()` cache.
264
+ - **Bounded polling** — `kaax_complete_onboarding` and
265
+ `kaax_check_download_status` use exponential back-off with a hard wall
266
+ (15 min for signup, configurable for downloads). They never spin.
267
+ - **Streamed downloads** — `kaax_download_kaax` pipes the response body
268
+ directly to disk; binaries never live in memory.
269
+ - **Detached child processes** — `openBrowser` spawns + `unref`s the
270
+ browser so the MCP exits cleanly even if you leave the browser open.
271
+
272
+ ## License
273
+
274
+ MIT — see [LICENSE](../LICENSE).
@@ -0,0 +1,118 @@
1
+ /**
2
+ * Thin HTTP client wrapping the public Kaax v0 API.
3
+ *
4
+ * The MCP server runs locally on the user's machine and talks to Kaax over
5
+ * HTTPS using the API key that lives at `/dashboard/api-manage`. We use
6
+ * `undici.fetch` for the small footprint and Node 18+ compatibility.
7
+ */
8
+ import type { AccountOverview, AnalysesFilters, AnalysesPage, AnalysisType, DownloadStatusResponse, IssueOnboardingTokenResponse, OnboardingStatus, RequestDownloadResponse, V2FieldsPage, V2ModelsList, V2PeerSuggestion, V2SetupStatus, V2TagsList } from "./types.js";
9
+ /**
10
+ * POST /api/landing/onboarding-token — issue a 15-minute single-use token.
11
+ *
12
+ * The MCP server uses this at the very start of the browser-handoff signup
13
+ * flow. Returns the opaque token + a hint URL the MCP can `openBrowser` on.
14
+ */
15
+ export declare function issueOnboardingToken(baseUrl?: string, source?: "mcp" | "landing-cli" | "other"): Promise<IssueOnboardingTokenResponse>;
16
+ /**
17
+ * GET /api/landing/onboarding-status?token=… — single poll of the binding
18
+ * state. Callers (e.g. `pollOnboardingUntilBound`) loop on this with their
19
+ * own bounded back-off.
20
+ */
21
+ export declare function fetchOnboardingStatus(token: string, baseUrl?: string): Promise<OnboardingStatus>;
22
+ /**
23
+ * Bounded polling — increasing back-off, hard ceiling on total elapsed time.
24
+ * Returns the first non-`pending` response. Designed so a stuck MCP can
25
+ * never hammer the server: cap of ~120 polls over 15 minutes.
26
+ */
27
+ export declare function pollOnboardingUntilBound(token: string, opts?: {
28
+ baseUrl?: string;
29
+ maxWaitMs?: number;
30
+ initialDelayMs?: number;
31
+ maxDelayMs?: number;
32
+ onTick?: (state: OnboardingStatus, attempt: number) => void;
33
+ }): Promise<OnboardingStatus>;
34
+ export declare class KaaxAPIError extends Error {
35
+ status?: number | undefined;
36
+ body?: string | undefined;
37
+ constructor(message: string, status?: number | undefined, body?: string | undefined);
38
+ }
39
+ export interface KaaxClientOptions {
40
+ /** Optional — if absent, only the onboarding helpers work. */
41
+ apiKey?: string;
42
+ baseUrl?: string;
43
+ /** Timeout in milliseconds. Defaults to 30s. */
44
+ timeoutMs?: number;
45
+ }
46
+ export declare class KaaxClient {
47
+ /** May be undefined while the user has not finished onboarding yet. */
48
+ private apiKey;
49
+ readonly baseUrl: string;
50
+ private readonly timeoutMs;
51
+ constructor(opts?: KaaxClientOptions);
52
+ /** True iff the client has an apiKey and can hit authenticated endpoints. */
53
+ hasApiKey(): boolean;
54
+ /**
55
+ * Late-bind an apiKey acquired via the onboarding flow. The new key is
56
+ * used for every subsequent request without restarting the MCP.
57
+ */
58
+ setApiKey(apiKey: string): void;
59
+ private requireKey;
60
+ /**
61
+ * `POST /api/v0/analyses` — the single public endpoint.
62
+ * Filters go in the JSON body; pagination as query params.
63
+ */
64
+ listAnalyses(filters?: AnalysesFilters): Promise<AnalysesPage>;
65
+ /**
66
+ * Pulls *every* analysis page (up to a safety cap) and concatenates them.
67
+ * Useful for aggregates / heuristics — never on hot UI paths.
68
+ */
69
+ listAllAnalyses(filters?: Omit<AnalysesFilters, "page" | "limit">, opts?: {
70
+ maxPages?: number;
71
+ pageSize?: number;
72
+ }): Promise<AnalysesPage>;
73
+ /** GET /api/v2/setup-status — snapshot of fields, models, configs, tags. */
74
+ setupStatus(): Promise<V2SetupStatus | null>;
75
+ /** GET /api/v2/fields — paginated, filterable list of the user's fields. */
76
+ listFields(params?: {
77
+ tag?: string;
78
+ type?: AnalysisType;
79
+ q?: string;
80
+ page?: number;
81
+ limit?: number;
82
+ }): Promise<V2FieldsPage | null>;
83
+ /** GET /api/v2/models — every detection model owned by the caller. */
84
+ listModels(): Promise<V2ModelsList | null>;
85
+ /** GET /api/v2/tags — tag dictionary with usage counts. */
86
+ listTagsV2(): Promise<V2TagsList | null>;
87
+ /** POST /api/v2/tags — append a tag to the user dictionary. */
88
+ createTag(tag: string): Promise<{
89
+ tag: string;
90
+ created: boolean;
91
+ } | null>;
92
+ /** POST /api/v2/fields/:id/tags — attach tags to a field. */
93
+ attachFieldTags(fieldId: string, tags: string[]): Promise<{
94
+ fieldId: string;
95
+ tags: string[];
96
+ } | null>;
97
+ /** GET /api/v2/peer-config-suggestions?type=… — anonymised peer averages. */
98
+ peerSuggestions(type: AnalysisType): Promise<V2PeerSuggestion | null>;
99
+ /** POST /api/landing/request-download — submit the HubSpot form server-side. */
100
+ requestDownload(body: {
101
+ useCase?: string;
102
+ company?: string;
103
+ locale?: "en" | "es" | "zh";
104
+ }): Promise<RequestDownloadResponse>;
105
+ /** GET /api/landing/download-status — returns status + binary URLs on approval. */
106
+ downloadStatus(): Promise<DownloadStatusResponse>;
107
+ private overviewCache;
108
+ private static readonly OVERVIEW_TTL_MS;
109
+ /** GET /api/landing/account-overview — plan + limits + capabilities. */
110
+ accountOverview(force?: boolean): Promise<AccountOverview>;
111
+ /** Invalidate the overview cache — call after a successful upgrade. */
112
+ invalidateOverview(): void;
113
+ /** Helper — GET that returns `null` on 404 / network errors. */
114
+ private tryGet;
115
+ /** Helper — POST that returns `null` on 404. */
116
+ private tryPost;
117
+ private request;
118
+ }