@sebamomann/plants-mcp 2.13.0 → 3.0.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/CHANGELOG.md CHANGED
@@ -3,6 +3,70 @@
3
3
  Notable changes to `@sebamomann/plants-mcp`. Versioning is semver against the **tool surface** —
4
4
  see the table in `AGENTS.md` for what counts as major, minor, and patch.
5
5
 
6
+ ## 3.0.0 — 2026-10-04
7
+
8
+ **`noFertilizerThisSeason` is renamed `fertilizingNotStarted`.** Tool count unchanged (81). A **major**
9
+ bump — an accepted argument value was removed:
10
+
11
+ - `list_care_recommendations` no longer returns `noFertilizerThisSeason` ("no feeding since the
12
+ season started", which fired on day one of every season). The new type `fertilizingNotStarted`
13
+ fires only for a plant with a fertilizer and an active fertilizing cycle that has **never** been
14
+ fertilized, once a full cycle has passed since feeding could have started (acquisition, the end of
15
+ a repotting rest, or the end of a winter pause). Cuttings are excluded.
16
+ - `dismiss_care_recommendation`'s `type` enum: `noFertilizerThisSeason` → `fertilizingNotStarted`.
17
+ Old dismissals of the removed type no longer apply to anything.
18
+ - Additive: `wateringCycleMismatch.values` gains `season` (`"summer"`/`"winter"`), the column the
19
+ suggestion is expressed in. The mismatch is now judged against the cycle in effect when each
20
+ interval started (season, gradual season ramp, controlled climate), so a season change alone no
21
+ longer makes a plant look off-cycle.
22
+ - `list_notifications`: a `CHANGELOG_PUBLISHED` row may now carry `messageKey:
23
+ "changelogPublishedSummary"` with `values: { summary }` (that day's summary) instead of
24
+ `changelogPublished` with `{ count }`.
25
+
26
+ ## 2.16.0 — 2026-10-04
27
+
28
+ **Autumn "Prüfen" checks: `list_due_care` gains `checks`, new `dismiss_transition_check`.** Tool count
29
+ 81 (was 80). A **minor** bump — one new tool and one additive response field:
30
+
31
+ - `list_due_care` returns an additive `checks` array — `{ plantId, name, lastWateredAt, checkSince,
32
+ regularDueAt }` — for plants whose watering interval grew with the autumn season change while the
33
+ plant may dry out at its summer pace. A question, not a task: not in `tasks`/`counts`. Existing
34
+ fields are unchanged.
35
+ - New write tool `dismiss_transition_check` (`plantId`): answers a check with "noch feucht", hiding
36
+ it until its regular due date without moving that date. The other answer ("dry") is
37
+ `record_watering`.
38
+
39
+ ## 2.15.0 — 2026-10-04
40
+
41
+ **`update_plant_care` gains `keepWinterCare`; `get_plant` reports the effective schedule.** Tool count
42
+ unchanged (80). A **minor** bump — one new optional argument and new response fields:
43
+
44
+ - `update_plant_care` accepts `keepWinterCare: boolean`. A plant in a controlled-climate location
45
+ follows its summer watering and fertilizing cycle all year; `keepWinterCare: true` brings the
46
+ winter cycle back for both. The stored winter values are never cleared by it.
47
+ - `get_plant` returns `keepWinterCare`, the location's `controlledClimate` (inside `location`) and a
48
+ new top-level `followsSummerYearRound` boolean.
49
+ - **Behaviour change in `get_plant`:** its six schedule fields now show what scheduling actually uses.
50
+ For a plant that follows its summer cycle all year, the winter fields
51
+ (`wateringFrequencyWinterDays`, `wateringFrequencyWinter`, `fertilizingCycleWinterWeeks`) are the
52
+ *effective* (summer) values, not the stored winter ones. Nothing changes for plants outside a
53
+ controlled-climate location.
54
+ - `update_plant_care`'s result includes `keepWinterCare`.
55
+
56
+ ## 2.14.0 — 2026-10-04
57
+
58
+ **New tool: `update_location`; `create_location` gains `controlledClimate`.** Tool count 79 → 80
59
+ (28 read, 34 write, 18 admin). A **minor** bump — one new write tool and one new optional argument:
60
+
61
+ - `PATCH /api/v1/locations/:id` takes any of `name`, `parentId` (`null` = top level) and
62
+ `controlledClimate`; only the fields present change. Shares its core with the location detail
63
+ page's save action, so cycle checks on `parentId` behave identically.
64
+ - `controlledClimate` marks a place with constant light and warmth (e.g. a plant cabinet): plants
65
+ placed directly there follow their summer watering and fertilizing cycle all year, unless the
66
+ plant opts back into winter care. `create_location` accepts it too.
67
+ - `list_locations` now returns `controlledClimate` per location (a new response field — no
68
+ change needed by callers).
69
+
6
70
  ## 2.13.0 — 2026-09-30
7
71
 
8
72
  **New tool: `merge_soils`.** Tool count 78 → 79 (28 read, 33 write, 18 admin). A **minor** bump —
package/README.md CHANGED
@@ -60,7 +60,7 @@ needs a running Sprig instance and an API key from it. Start at the
60
60
 
61
61
  ## What an assistant can do
62
62
 
63
- **79 tools** over your collection: 28 that read it, 33 that write to it. **18 admin tools** are also
63
+ **81 tools** over your collection: 28 that read it, 35 that write to it. **18 admin tools** are also
64
64
  available — but only to a key created by an admin — for finding and understanding duplicates in the
65
65
  shared global plant type catalog, restructuring its genus/species/cultivar tree, reading, directly
66
66
  editing, merging, and reviewing proposals and duplicate candidates in that catalog, and (7 read-only,
@@ -112,7 +112,7 @@ healthy server, not a hang — press Ctrl-C.
112
112
  The scope lives on the API key, is enforced by the app, and cannot be widened from this side.
113
113
 
114
114
  - **`read`** — on every key. Gates all 28 read tools.
115
- - **`write`** — opt-in when you create the key. Gates the 33 write tools.
115
+ - **`write`** — opt-in when you create the key. Gates the 35 write tools.
116
116
  - **`admin`** — only offered when the key's creator is themselves an admin, and only while they
117
117
  still are one (the app re-checks this on every admin-tool call, not just at key creation). Gates
118
118
  the 18 admin catalog tools — 7 read-only, 11 that write — see **Admin tools** below. `admin` alone
@@ -174,7 +174,10 @@ facts (light, water, feeding need, toxicity, climate, growth, substrate/propagat
174
174
  region), keyed by the same field names `get_plant_type` uses — and its six schedule fields
175
175
  (`wateringFrequencySummerDays`/`WinterDays`, `wateringFrequencySummer`/`Winter`,
176
176
  `fertilizingCycleSummerWeeks`/`WinterWeeks`) as `{ value, source: "plant"|"type"|"none", typeValue }`
177
- instead of a raw value — see **Breaking changes in 2.0.0** below.
177
+ instead of a raw value — see **Breaking changes in 2.0.0** below. They show what scheduling actually
178
+ uses: for a plant in a controlled-climate location without `keepWinterCare`, the winter fields equal the
179
+ summer values, and `followsSummerYearRound` is `true` (also returned: `keepWinterCare` and
180
+ `location.controlledClimate`).
178
181
 
179
182
  ### Per-plant history
180
183
 
@@ -200,10 +203,10 @@ overrides the Apr–Sep default), `locationId`.
200
203
 
201
204
  | Tool | Endpoint | Extra arguments | Answers |
202
205
  |---|---|---|---|
203
- | `list_due_care` | `GET /api/v1/care/due` | `windowDays` (0–60, default 0 = that day only), `includeOverdue` (default true) | "What should I do today?" |
206
+ | `list_due_care` | `GET /api/v1/care/due` | `windowDays` (0–60, default 0 = that day only), `includeOverdue` (default true) | "What should I do today?" Also carries the additive `checks` array (autumn "Prüfen" questions — see `dismiss_transition_check`). |
204
207
  | `list_overdue_care` | `GET /api/v1/care/overdue` | — | "What have I fallen behind on?" — most overdue first, with days late |
205
208
  | `get_care_calendar` | `GET /api/v1/care/calendar` | `days` (1–60, default 14) | Day-by-day projection plus an overdue group |
206
- | `list_care_recommendations` | `GET /api/v1/care/recommendations` | none | Detected problems: cycle mismatch, chronic lateness, missed seasonal fertilizing, fertilizer pause after repotting, repotting overdue, stale photos |
209
+ | `list_care_recommendations` | `GET /api/v1/care/recommendations` | none | Detected problems: cycle mismatch, chronic lateness, fertilizing never started, fertilizer pause after repotting, repotting overdue, stale photos |
207
210
 
208
211
  Five things to know when reading schedule results:
209
212
 
@@ -362,7 +365,7 @@ tools** → **Catalogs** below) lets a write-scoped key add it.
362
365
 
363
366
  ## Write tools
364
367
 
365
- **All 31 ordinary write tools require a `write`-scoped key**; a read-only key gets HTTP 403. Every
368
+ **All 32 ordinary write tools require a `write`-scoped key**; a read-only key gets HTTP 403. Every
366
369
  description starts with `WRITE:` so a model cannot mistake one for a read.
367
370
 
368
371
  ### Care logging
@@ -419,7 +422,7 @@ it is — that backlog is not the watering's doing.
419
422
 
420
423
  | Tool | Endpoint | Arguments |
421
424
  |---|---|---|
422
- | `update_plant_care` | `PATCH /api/v1/plants/:id/care` | `plantId` **(required)**, plus any of `wateringMode` (`scheduled`\|`reservoir`\|`hydro`), `fertilizingMode` (`scheduled`\|`with_watering`), `wateringFrequencySummer`/`wateringFrequencyWinter` (labels), `wateringFrequencySummerDays`/`wateringFrequencyWinterDays` (1–365), `wateringNotes`, `fertilizingCycleSummerWeeks`/`fertilizingCycleWinterWeeks` (0–52), `fertilizerPercent` (0–1000), `fertilizingNotes` |
425
+ | `update_plant_care` | `PATCH /api/v1/plants/:id/care` | `plantId` **(required)**, plus any of `wateringMode` (`scheduled`\|`reservoir`\|`hydro`), `fertilizingMode` (`scheduled`\|`with_watering`), `wateringFrequencySummer`/`wateringFrequencyWinter` (labels), `wateringFrequencySummerDays`/`wateringFrequencyWinterDays` (1–365), `wateringNotes`, `fertilizingCycleSummerWeeks`/`fertilizingCycleWinterWeeks` (0–52), `fertilizerPercent` (0–1000), `fertilizingNotes`, `keepWinterCare` (boolean — winter cycle despite a controlled-climate location) |
423
426
  | `update_plant` | `PATCH /api/v1/plants/:id` | `plantId` **(required)**, plus any of `plantTypeId`, `nickname`, `locationId`, `soilId`, `fertilizerId`, `quantity` (0–9999), `notes`, `tags` (replaces the full list), `sitterInstructions`, `parentPlantId` |
424
427
 
425
428
  Both take a `plantId` and edit **only the fields you pass** — an omitted field keeps its current
@@ -529,11 +532,20 @@ The two edit tools take an **`entryId`, not a `plantId`** — get it from `list_
529
532
  | Tool | Endpoint | Arguments |
530
533
  |---|---|---|
531
534
  | `dismiss_care_recommendation` | `POST /api/v1/care/recommendations/dismiss` | `plantId`, `type`, `fingerprint` — **all required** |
535
+ | `dismiss_transition_check` | `POST /api/v1/care/transition-checks/dismiss` | `plantId` **(required)** — a plant listed in `checks` of `list_due_care` |
536
+
537
+ `dismiss_transition_check` answers an autumn "Prüfen" check with "noch feucht": as the season changes
538
+ to winter the watering interval grows, so a plant that dries out at its summer pace would go
539
+ unreminded for longer. `list_due_care` lists such plants in `checks`
540
+ (`{ plantId, name, lastWateredAt, checkSince, regularDueAt }`) from the day the summer-pace watering
541
+ would have been due until the regular due date. The dismissal hides the check until then and never
542
+ moves the due date; a new watering reopens the question. The other answer, "dry", is a plain
543
+ `record_watering`.
532
544
 
533
545
  Pass all three **exactly as returned by `list_care_recommendations`**. The `fingerprint` identifies
534
546
  that specific occurrence, so dismissing one never suppresses a later, different recurrence.
535
547
 
536
- Dismissible `type` values: `wateringOftenLate`, `fertilizingOftenLate`, `noFertilizerThisSeason`,
548
+ Dismissible `type` values: `wateringOftenLate`, `fertilizingOftenLate`, `fertilizingNotStarted`,
537
549
  `recentlyRepottedAvoidFertilizer`, `noRecentPhoto`, `propagationCheckDue`, `possibleDecline`.
538
550
  `wateringCycleMismatch` is **not** dismissible — it is a configuration contradiction, fixed by
539
551
  editing the plant rather than hidden.
@@ -593,7 +605,8 @@ mirror (opening the page already marks everything read).
593
605
 
594
606
  | Tool | Endpoint | Arguments |
595
607
  |---|---|---|
596
- | `create_location` | `POST /api/v1/locations` | `name` **(required)**, `parentId` (an owned location, to nest under) |
608
+ | `create_location` | `POST /api/v1/locations` | `name` **(required)**, `parentId` (an owned location, to nest under), `controlledClimate` (plants there follow their summer watering/fertilizing cycle year-round unless they opt back into winter care) |
609
+ | `update_location` | `PATCH /api/v1/locations/:id` | `locationId` **(required)**, `name`, `parentId` (`null` = top level), `controlledClimate` — only the fields passed change |
597
610
  | `create_soil` | `POST /api/v1/soils` | `name` **(required)**, `notes`, `items` (`{ componentName, parts }[]`, each component upserted by name) |
598
611
  | `merge_soils` | `POST /api/v1/soils/merge` | `sourceId` **(required)**, `targetId` **(required)** |
599
612
  | `create_fertilizer` | `POST /api/v1/fertilizers` | `name` **(required)**, `npk`, `baseDosePerLiterMl` (0–100) |
package/dist/index.js CHANGED
@@ -53,7 +53,7 @@ server.tool("list_plants", "List the user's plants, with optional filters. Retur
53
53
  offset: z.number().int().min(0).optional().describe("Result offset (pagination)."),
54
54
  sort: z.enum(["displayName", "createdAt", "updatedAt", "acquiredAt"]).optional().describe("Sort key. 'displayName' sorts by the plant's resolved name (nickname, or its type's name); the rest sort newest first."),
55
55
  }, async (args) => apiGet("/api/v1/plants", args));
56
- server.tool("get_plant", "Get one plant with full detail: identity (type, nickname, display name, botanical name, unidentified state — see 'type'), typeFacts (the type's effective light/water/feeding need, toxicity, climate, growth, substrate/propagation, family/native region — using the catalog field keys from get_plant_type), catalogs (location, soil, fertilizer), care config, and recent event summaries. The six schedule fields (wateringFrequencySummerDays/WinterDays, wateringFrequencySummer/Winter, fertilizingCycleSummerWeeks/WinterWeeks) are each { value, source: 'plant'|'type'|'none', typeValue } — value is what scheduling/display use, source says where it came from, typeValue is what the type would say even when the plant overrides it.", { id: z.number().int().describe("Plant id.") }, async ({ id }) => apiGet(`/api/v1/plants/${id}`));
56
+ server.tool("get_plant", "Get one plant with full detail: identity (type, nickname, display name, botanical name, unidentified state — see 'type'), typeFacts (the type's effective light/water/feeding need, toxicity, climate, growth, substrate/propagation, family/native region — using the catalog field keys from get_plant_type), catalogs (location, soil, fertilizer), care config, and recent event summaries. The six schedule fields (wateringFrequencySummerDays/WinterDays, wateringFrequencySummer/Winter, fertilizingCycleSummerWeeks/WinterWeeks) are each { value, source: 'plant'|'type'|'none', typeValue } — value is what scheduling/display use, source says where it came from, typeValue is what the type would say even when the plant overrides it. For a plant in a controlled-climate location (location.controlledClimate) that has not set keepWinterCare, followsSummerYearRound is true and the three winter schedule fields show the SUMMER values — the plant follows its summer cycle all year (the raw stored winter values are kept and return when keepWinterCare is set or the plant moves elsewhere).", { id: z.number().int().describe("Plant id.") }, async ({ id }) => apiGet(`/api/v1/plants/${id}`));
57
57
  // --- Events connected to a plant -------------------------------------------
58
58
  const eventArgs = {
59
59
  id: z.number().int().describe("Plant id."),
@@ -84,7 +84,7 @@ const careArgs = {
84
84
  .describe("Override the season (Apr–Sep is summer). Defaults to the season of `date`."),
85
85
  locationId: z.number().int().optional().describe("Only consider plants in this location."),
86
86
  };
87
- server.tool("list_due_care", "What needs water or fertilizer now: overdue work plus anything due on the given day. Use `windowDays` to look ahead. This is the right tool for 'what should I do today?'. A fertilizing entry carries `wateringAlignedOn` (YYYY-MM-DD, null when absent) when the app's calendar has grouped that feeding with a watering; `windowDays`/`includeOverdue` are judged by that day, not the feeding's own `nextDue` — see `get_care_calendar`'s description for the full rule.", {
87
+ server.tool("list_due_care", "What needs water or fertilizer now: overdue work plus anything due on the given day. Use `windowDays` to look ahead. This is the right tool for 'what should I do today?'. A fertilizing entry carries `wateringAlignedOn` (YYYY-MM-DD, null when absent) when the app's calendar has grouped that feeding with a watering; `windowDays`/`includeOverdue` are judged by that day, not the feeding's own `nextDue` — see `get_care_calendar`'s description for the full rule. The additive `checks` array lists autumn 'Prüfen' questions — { plantId, name, lastWateredAt, checkSince, regularDueAt } — for plants that dry out at their summer pace while the season change has stretched their interval: the summer-pace date (`checkSince`) has passed but the regular due date (`regularDueAt`) has not. A check is a question ('is the soil dry?'), not a task — it is not in `tasks`/`counts`. Answer 'dry' with `record_watering`, 'still moist' with `dismiss_transition_check`.", {
88
88
  ...careArgs,
89
89
  windowDays: z
90
90
  .number()
@@ -188,7 +188,7 @@ server.tool("snooze_care", "WRITE: postpone a plant's next watering or fertilizi
188
188
  originalDueAt: z.string().describe("The due date being pushed out, e.g. from list_due_care's nextDue for this plant/careType."),
189
189
  note: z.string().max(160).optional().describe("Free-text reason, e.g. 'away for the week'."),
190
190
  }, async (args) => apiSend("POST", "/api/v1/care/snooze", { ...args }));
191
- server.tool("update_plant_care", "WRITE: edit a plant's care schedule — watering mode, days/labels, fertilizing mode, cycles and percent, and the watering/fertilizing notes. Omitted fields keep their current value. null on wateringFrequencySummerDays/WinterDays, wateringFrequencySummer/Winter, or fertilizingCycleSummerWeeks/WinterWeeks means 'follow the type' — the plant's effective schedule then falls back to its type's suggested value (see get_plant's typeFacts / the schedule fields' typeValue) instead of having no schedule at all; a value equal to the type's suggested value is stored the same way (it follows the type). null on wateringNotes/fertilizingNotes just clears the note. Watering fields are ignored while the plant's watering isn't on a schedule (reservoir/hydro), and the fertilizing-cycle fields are ignored while it fertilizes with every watering — there's no cycle to configure in either case. If wateringMode/fertilizingMode is part of this same call, that check runs against the NEW mode, not the plant's current one — e.g. switching to 'reservoir' while also passing a watering cycle ignores that cycle. sunRequirement/waterRequirement are no longer settable here — light and water need are type-only facts now (get_plant's typeFacts, or get_plant_type). Use this (not update_plant) to fix a 'wateringCycleMismatch' recommendation where the mode itself is wrong. Requires a write-scoped API key.", {
191
+ server.tool("update_plant_care", "WRITE: edit a plant's care schedule — watering mode, days/labels, fertilizing mode, cycles and percent, and the watering/fertilizing notes. Omitted fields keep their current value. null on wateringFrequencySummerDays/WinterDays, wateringFrequencySummer/Winter, or fertilizingCycleSummerWeeks/WinterWeeks means 'follow the type' — the plant's effective schedule then falls back to its type's suggested value (see get_plant's typeFacts / the schedule fields' typeValue) instead of having no schedule at all; a value equal to the type's suggested value is stored the same way (it follows the type). null on wateringNotes/fertilizingNotes just clears the note. Watering fields are ignored while the plant's watering isn't on a schedule (reservoir/hydro), and the fertilizing-cycle fields are ignored while it fertilizes with every watering — there's no cycle to configure in either case. If wateringMode/fertilizingMode is part of this same call, that check runs against the NEW mode, not the plant's current one — e.g. switching to 'reservoir' while also passing a watering cycle ignores that cycle. sunRequirement/waterRequirement are no longer settable here — light and water need are type-only facts now (get_plant's typeFacts, or get_plant_type). Use this (not update_plant) to fix a 'wateringCycleMismatch' recommendation where the mode itself is wrong. keepWinterCare brings the winter cycle back for a plant in a controlled-climate location (which otherwise follows its summer cycle all year, see get_plant's followsSummerYearRound) — it governs both watering and fertilizing and has no effect elsewhere; the stored winter values are never cleared by it. Requires a write-scoped API key.", {
192
192
  plantId: z.number().int().positive().describe("Plant id."),
193
193
  wateringMode: z.enum(["scheduled", "reservoir", "hydro"]).optional().describe("How this plant is watered: on a schedule, from a reservoir, or in water/hydro culture."),
194
194
  fertilizingMode: z.enum(["scheduled", "with_watering"]).optional().describe("How this plant is fertilized: on its own cycle, or automatically with every watering."),
@@ -201,6 +201,7 @@ server.tool("update_plant_care", "WRITE: edit a plant's care schedule — wateri
201
201
  fertilizingCycleWinterWeeks: z.number().int().min(0).max(52).nullable().optional().describe("Winter fertilizing interval in weeks. null follows the type's suggested weeks. Ignored while fertilizing is 'with every watering'."),
202
202
  fertilizerPercent: z.number().int().min(0).max(1000).optional().describe("Strength as a percentage of the base dose."),
203
203
  fertilizingNotes: z.string().max(500).nullable().optional().describe("Free-text fertilizing notes."),
204
+ keepWinterCare: z.boolean().optional().describe("true: use the winter cycle despite a controlled-climate location; false: follow the summer cycle all year there. Only matters while the plant stands in a location with controlledClimate."),
204
205
  }, async ({ plantId, ...body }) => apiSend("PATCH", `/api/v1/plants/${plantId}/care`, body));
205
206
  server.tool("update_plant", "WRITE: edit a plant's type, nickname, location, soil, fertilizer, quantity, notes, tags, sitter instructions, or parent plant. Omitted fields keep their current value; null unassigns a catalog entry, clears notes/instructions, empties the tag list, or detaches the plant from its parent — and for plantTypeId, makes the plant 'unidentified'. nickname is required whenever the plant's state after this edit has no type (whether because plantTypeId is set to null here, or was already null and nickname isn't being set to something non-empty) — see plantIdentity's validatePlantNickname. Requires a write-scoped API key.", {
206
207
  plantId: z.number().int().positive().describe("Plant id."),
@@ -239,7 +240,7 @@ server.tool("dismiss_care_recommendation", "WRITE: hide one care recommendation,
239
240
  .enum([
240
241
  "wateringOftenLate",
241
242
  "fertilizingOftenLate",
242
- "noFertilizerThisSeason",
243
+ "fertilizingNotStarted",
243
244
  "recentlyRepottedAvoidFertilizer",
244
245
  "noRecentPhoto",
245
246
  "propagationCheckDue",
@@ -248,6 +249,9 @@ server.tool("dismiss_care_recommendation", "WRITE: hide one care recommendation,
248
249
  .describe("Recommendation type, from list_care_recommendations."),
249
250
  fingerprint: z.string().min(1).describe("Occurrence fingerprint, from list_care_recommendations."),
250
251
  }, async (args) => apiSend("POST", "/api/v1/care/recommendations/dismiss", { ...args }));
252
+ server.tool("dismiss_transition_check", "WRITE: answer an autumn 'Prüfen' check with 'noch feucht' (still moist) — hides that plant's check until its regular due date, without moving the due date. Pass the plantId of an entry in `checks` of list_due_care. The answer is tied to the plant's last watering, so a new watering reopens the question next autumn/interval. For the other answer — the soil is dry — just call `record_watering`: a normal watering makes the check disappear. Requires a write-scoped API key.", {
253
+ plantId: z.number().int().positive().describe("Plant id, from `checks` of list_due_care."),
254
+ }, async (args) => apiSend("POST", "/api/v1/care/transition-checks/dismiss", { ...args }));
251
255
  // --- Deleting events ---------------------------------------------------------
252
256
  /**
253
257
  * The only destructive tools in this server. Everything else — plants,
@@ -388,7 +392,14 @@ server.tool("create_plant_type", "WRITE: add a species/cultivar the shared globa
388
392
  server.tool("create_location", "WRITE: create a location — the same 'create inline' picker on the plant form. Never conflicts with an existing owned catalog entry: a duplicate name is rejected by the underlying unique constraint, surfaced as an error. Requires a write-scoped API key.", {
389
393
  name: z.string().min(1).max(100).describe("Location name."),
390
394
  parentId: z.number().int().positive().optional().describe("An owned location id to nest this one under (from list_locations). Omit for a top-level location."),
391
- }, async ({ name, parentId }) => apiSend("POST", "/api/v1/locations", { name, parentId }));
395
+ controlledClimate: z.boolean().optional().describe("True for a place with constant light and warmth all year (e.g. a plant cabinet with a grow light): plants placed directly here follow their summer watering and fertilizing cycle year-round, unless the plant opts back into winter care. Omit for false."),
396
+ }, async ({ name, parentId, controlledClimate }) => apiSend("POST", "/api/v1/locations", { name, parentId, controlledClimate }));
397
+ server.tool("update_location", "WRITE: rename, move or change the climate setting of an owned location. Only the fields you pass change. Requires a write-scoped API key.", {
398
+ locationId: z.number().int().positive().describe("The location id (from list_locations)."),
399
+ name: z.string().min(1).max(100).optional().describe("New name."),
400
+ parentId: z.number().int().positive().nullable().optional().describe("An owned location id to nest this one under; null moves it to the top level. Omit to keep the current parent. Cycles are rejected."),
401
+ controlledClimate: z.boolean().optional().describe("True: plants placed directly in this location (not its sublocations) follow their summer watering and fertilizing cycle year-round, unless the plant opts back into winter care. False: normal seasonal cycle again. Omit to keep the current setting."),
402
+ }, async ({ locationId, name, parentId, controlledClimate }) => apiSend("PATCH", `/api/v1/locations/${locationId}`, { name, parentId, controlledClimate }));
392
403
  server.tool("list_soils", "List the user's soils.", async () => apiGet("/api/v1/soils"));
393
404
  server.tool("create_soil", "WRITE: create a soil mix, optionally with components (name + parts) — mirrors the app's soil mix editor. Each component is matched or created by name under the caller's account. Requires a write-scoped API key.", {
394
405
  name: z.string().min(1).max(100).describe("Soil name."),
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@sebamomann/plants-mcp",
3
- "version": "2.13.0",
4
- "description": "MCP server for the Sprig plant app: 79 tools to read a plant collection, browse the shared global plant type catalog, log care (watering, fertilizing, repotting, refills, hydro events), edit a plant's identity/care/lifecycle status, add, propagate or merge plants, snooze a due date, check for nudges worth mentioning, manage a wishlist and notifications, watch a plant type for changes, check vacation status, read past problem diagnoses, create a location/soil/fertilizer/pot, merge a duplicate soil into another, and contribute a new species to the catalog. Read-only by default; writes need a write-scoped API key. Two tools can delete a watering/fertilization event; nothing else deletes collection history. 18 more tools read, edit, merge and review the shared global plant type catalog and the instance's operational status (queues, catalog health, activity, growth, community, trade and content aggregates) for an admin-scoped key.",
3
+ "version": "3.0.0",
4
+ "description": "MCP server for the Sprig plant app: 81 tools to read a plant collection, browse the shared global plant type catalog, log care (watering, fertilizing, repotting, refills, hydro events), edit a plant's identity/care/lifecycle status, add, propagate or merge plants, snooze a due date, answer an autumn watering check, check for nudges worth mentioning, manage a wishlist and notifications, watch a plant type for changes, check vacation status, read past problem diagnoses, create or update a location (incl. its controlled-climate setting), create a soil/fertilizer/pot, merge a duplicate soil into another, and contribute a new species to the catalog. Read-only by default; writes need a write-scoped API key. Two tools can delete a watering/fertilization event; nothing else deletes collection history. 18 more tools read, edit, merge and review the shared global plant type catalog and the instance's operational status (queues, catalog health, activity, growth, community, trade and content aggregates) for an admin-scoped key.",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
7
  "author": "sebamomann <github@sebamomann.de>",