@sebamomann/plants-mcp 2.7.1 → 2.8.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/CHANGELOG.md +32 -0
- package/README.md +27 -5
- package/dist/index.js +7 -3
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,38 @@
|
|
|
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
|
+
## 2.8.1 — 2026-09-29
|
|
7
|
+
|
|
8
|
+
**Fertilizing schedule reads now match the app's calendar exactly.** Tool count unchanged (78
|
|
9
|
+
overall). A **patch** bump — nothing renamed or removed:
|
|
10
|
+
|
|
11
|
+
- `list_due_care`, `list_overdue_care`, and `get_care_calendar` gain `wateringAlignedOn` (a
|
|
12
|
+
`YYYY-MM-DD` day, `null` when absent) on every fertilizing entry: the day the app's calendar
|
|
13
|
+
water-align rule has attached that feeding to a watering, when it has. A feeding due before a
|
|
14
|
+
plant's next watering is grouped there unconditionally; one due shortly after may ride along with
|
|
15
|
+
it (within a third of the watering interval, capped at a quarter of the feeding's own cycle).
|
|
16
|
+
`get_care_calendar` now buckets a fertilizing entry on this effective day instead of its own, and
|
|
17
|
+
`list_due_care`/`list_overdue_care` judge their window and overdue membership by it too — a
|
|
18
|
+
feeding waiting on a future watering no longer shows as overdue even when its own due date has
|
|
19
|
+
passed. `nextDue` is unchanged: it still names the feeding's own due date.
|
|
20
|
+
- A plant with no watering schedule (reservoir/hydro) no longer gets a fertilizing entry from any
|
|
21
|
+
of the three tools, matching the calendar's reservoir view — its fertilizing status is still
|
|
22
|
+
readable via `get_plant`/`list_plants`, just not asked for here.
|
|
23
|
+
|
|
24
|
+
## 2.8.0 — 2026-09-27
|
|
25
|
+
|
|
26
|
+
**Is it still a cutting?** 78 tools registered overall (28 read, 32 write, 18 admin). A **minor**
|
|
27
|
+
bump — one new tool and one new enum value, nothing renamed or removed:
|
|
28
|
+
|
|
29
|
+
- `establish_propagation` — promote a `PROPAGATING` plant to `ESTABLISHED`, stamping
|
|
30
|
+
`establishedAt` to now. The same one-way transition the app's plant edit form applies manually,
|
|
31
|
+
and the action behind the new `propagationCheckDue` care recommendation.
|
|
32
|
+
- `list_care_recommendations`/`dismiss_care_recommendation` gain `propagationCheckDue`: a plant
|
|
33
|
+
left `PROPAGATING` long enough is now flagged, asking whether it should be promoted. Its
|
|
34
|
+
dismissal is deliberately temporary (it re-fires a few weeks later) rather than permanent, so a
|
|
35
|
+
stale `PROPAGATING` row can't silently skew the propagation success rate in `collection_stats`
|
|
36
|
+
forever.
|
|
37
|
+
|
|
6
38
|
## 2.7.1 — 2026-09-26
|
|
7
39
|
|
|
8
40
|
**A moved watering carries its feeding.** Tool count unchanged (77 overall). A **patch** 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
|
-
**
|
|
63
|
+
**78 tools** over your collection: 28 that read it, 32 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,
|
|
@@ -111,7 +111,7 @@ healthy server, not a hang — press Ctrl-C.
|
|
|
111
111
|
The scope lives on the API key, is enforced by the app, and cannot be widened from this side.
|
|
112
112
|
|
|
113
113
|
- **`read`** — on every key. Gates all 28 read tools.
|
|
114
|
-
- **`write`** — opt-in when you create the key. Gates the
|
|
114
|
+
- **`write`** — opt-in when you create the key. Gates the 32 write tools.
|
|
115
115
|
- **`admin`** — only offered when the key's creator is themselves an admin, and only while they
|
|
116
116
|
still are one (the app re-checks this on every admin-tool call, not just at key creation). Gates
|
|
117
117
|
the 18 admin catalog tools — 7 read-only, 11 that write — see **Admin tools** below. `admin` alone
|
|
@@ -201,12 +201,21 @@ overrides the Apr–Sep default), `locationId`.
|
|
|
201
201
|
| `get_care_calendar` | `GET /api/v1/care/calendar` | `days` (1–60, default 14) | Day-by-day projection plus an overdue group |
|
|
202
202
|
| `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 |
|
|
203
203
|
|
|
204
|
-
|
|
204
|
+
Four things to know when reading schedule results:
|
|
205
205
|
|
|
206
206
|
- **Days, not timestamps.** `nextDue` and `date` are local `YYYY-MM-DD` strings — a UTC timestamp
|
|
207
207
|
would render as the wrong day east of Greenwich.
|
|
208
208
|
- **`neverLogged` is its own bucket.** A plant with a cycle but no event yet has no anchor, so no
|
|
209
209
|
due date can be computed. Those are returned separately rather than reported as overdue.
|
|
210
|
+
- **Fertilizing follows the app's calendar, including where it's grouped.** A fertilizing entry
|
|
211
|
+
carries `wateringAlignedOn` (a `YYYY-MM-DD` day, `null` when absent) — set when the app's
|
|
212
|
+
water-align rule has attached that feeding to a watering: unconditionally when it's due before
|
|
213
|
+
the plant's next watering, or when it's due shortly after and close enough to ride along.
|
|
214
|
+
`get_care_calendar` buckets the entry there instead of under its own date, and
|
|
215
|
+
`list_due_care`/`list_overdue_care` judge their window/overdue membership by the same day.
|
|
216
|
+
`nextDue` keeps its own meaning throughout — the feeding's own due date. A plant with no watering
|
|
217
|
+
schedule (reservoir/hydro) never gets a fertilizing entry from these tools at all, matching the
|
|
218
|
+
calendar's own reservoir view.
|
|
210
219
|
- **Recommendations return i18n message keys plus values**, not rendered prose. Dismissed ones are
|
|
211
220
|
excluded.
|
|
212
221
|
|
|
@@ -438,6 +447,7 @@ don't exist yet, `propagate_plant` creates them *and* sets the link in one call.
|
|
|
438
447
|
| `unmerge_plant` | `POST /api/v1/plants/:id/unmerge` | `plantId` **(required)** |
|
|
439
448
|
| `set_plant_status` | `PATCH /api/v1/plants/:id/status` | `plantId`, `status` (`LIVING`\|`DEAD`\|`LOST`) **(both required)**, plus `deadDeclaredAt`/`deathCause` (only apply when `status` is `DEAD`) |
|
|
440
449
|
| `set_plant_reference` | `PATCH /api/v1/plants/:id/reference` | `plantId`, `reference` (boolean) **(both required)** |
|
|
450
|
+
| `establish_propagation` | `PATCH /api/v1/plants/:id/establish` | `plantId` **(required)** |
|
|
441
451
|
|
|
442
452
|
`create_plant`'s `plantTypeId` is now **optional**: pass an existing global type id (from
|
|
443
453
|
`list_plant_types`/`search_plant_types`) when the species is known, or omit it (or pass `null`) to
|
|
@@ -474,6 +484,11 @@ Marking a plant as a reference also removes it from any vacation plan, since a r
|
|
|
474
484
|
never cared for. `create_plant`'s `reference: true` covers making a *new* reference plant; this is
|
|
475
485
|
for converting one already in the collection.
|
|
476
486
|
|
|
487
|
+
`establish_propagation` promotes a `PROPAGATING` plant to `ESTABLISHED` and stamps `establishedAt`
|
|
488
|
+
to now — the same one-way transition the app's plant edit form applies manually, and the action
|
|
489
|
+
behind the `propagationCheckDue` recommendation ("is it still a cutting?", see **Recommendations**
|
|
490
|
+
below) on the Improvements page. It answers HTTP 400 if the plant isn't currently `PROPAGATING`.
|
|
491
|
+
|
|
477
492
|
### Health entries
|
|
478
493
|
|
|
479
494
|
| Tool | Endpoint | Arguments |
|
|
@@ -497,8 +512,15 @@ Pass all three **exactly as returned by `list_care_recommendations`**. The `fing
|
|
|
497
512
|
that specific occurrence, so dismissing one never suppresses a later, different recurrence.
|
|
498
513
|
|
|
499
514
|
Dismissible `type` values: `wateringOftenLate`, `fertilizingOftenLate`, `noFertilizerThisSeason`,
|
|
500
|
-
`recentlyRepottedAvoidFertilizer`, `noRecentPhoto`. `wateringCycleMismatch` is
|
|
501
|
-
it is a configuration contradiction, fixed by editing the plant rather than
|
|
515
|
+
`recentlyRepottedAvoidFertilizer`, `noRecentPhoto`, `propagationCheckDue`. `wateringCycleMismatch` is
|
|
516
|
+
**not** dismissible — it is a configuration contradiction, fixed by editing the plant rather than
|
|
517
|
+
hidden.
|
|
518
|
+
|
|
519
|
+
`propagationCheckDue` ("is it still a cutting?") fires for a plant that has been `PROPAGATING` for a
|
|
520
|
+
while and asks whether it has rooted. Dismissing it only hides it for a few weeks, not permanently —
|
|
521
|
+
it re-fires periodically rather than letting a stale `PROPAGATING` row silently skew the propagation
|
|
522
|
+
success rate in `collection_stats`. `establish_propagation` (see **Plant lifecycle** above) is how an
|
|
523
|
+
assistant acts on it — promoting the plant is the actual fix, not just dismissing the prompt.
|
|
502
524
|
|
|
503
525
|
### Wishlist
|
|
504
526
|
|
package/dist/index.js
CHANGED
|
@@ -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?'.", {
|
|
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.", {
|
|
88
88
|
...careArgs,
|
|
89
89
|
windowDays: z
|
|
90
90
|
.number()
|
|
@@ -98,8 +98,8 @@ server.tool("list_due_care", "What needs water or fertilizer now: overdue work p
|
|
|
98
98
|
.optional()
|
|
99
99
|
.describe("Include already-overdue work (default true)."),
|
|
100
100
|
}, async (args) => apiGet("/api/v1/care/due", { ...args, includeOverdue: args.includeOverdue?.toString() }));
|
|
101
|
-
server.tool("list_overdue_care", "Only work whose due date has already passed, most overdue first, with how many days each is late. Use for 'what have I fallen behind on?'.", careArgs, async (args) => apiGet("/api/v1/care/overdue", args));
|
|
102
|
-
server.tool("get_care_calendar", "Day-by-day care schedule over a date range, plus an overdue group. Each plant appears on its next due day only — this projects the current schedule rather than simulating future waterings.", {
|
|
101
|
+
server.tool("list_overdue_care", "Only work whose due date has already passed, most overdue first, with how many days each is late. Use for 'what have I fallen behind on?'. A feeding waiting on a future watering (`wateringAlignedOn` set — see `get_care_calendar`) is not listed here even when its own `nextDue` has passed: it's due on the watering's day, not neglected.", careArgs, async (args) => apiGet("/api/v1/care/overdue", args));
|
|
102
|
+
server.tool("get_care_calendar", "Day-by-day care schedule over a date range, plus an overdue group. Each plant appears on its next due day only — this projects the current schedule rather than simulating future waterings. Fertilizing follows the app calendar's water-align rule: a feeding due before a plant's next watering is bucketed there unconditionally, and one due shortly after may ride along with it (within a third of the watering interval, capped at a quarter of the feeding's own cycle) — either way the fertilizing entry's `wateringAlignedOn` (YYYY-MM-DD) names that day and the bucket it's placed in follows it, while `nextDue` keeps the feeding's own, unchanged due date. A plant with no watering schedule (reservoir/hydro) never gets a fertilizing entry here at all — its fertilizing is entirely the reservoir view's job.", {
|
|
103
103
|
...careArgs,
|
|
104
104
|
days: z.number().int().min(1).max(60).optional().describe("Number of days to project (default 14, max 60)."),
|
|
105
105
|
}, async (args) => apiGet("/api/v1/care/calendar", args));
|
|
@@ -238,6 +238,7 @@ server.tool("dismiss_care_recommendation", "WRITE: hide one care recommendation,
|
|
|
238
238
|
"noFertilizerThisSeason",
|
|
239
239
|
"recentlyRepottedAvoidFertilizer",
|
|
240
240
|
"noRecentPhoto",
|
|
241
|
+
"propagationCheckDue",
|
|
241
242
|
])
|
|
242
243
|
.describe("Recommendation type, from list_care_recommendations."),
|
|
243
244
|
fingerprint: z.string().min(1).describe("Occurrence fingerprint, from list_care_recommendations."),
|
|
@@ -289,6 +290,9 @@ server.tool("set_plant_reference", "WRITE: flip a plant between LIVING (the call
|
|
|
289
290
|
plantId: z.number().int().positive().describe("Plant id."),
|
|
290
291
|
reference: z.boolean().describe("true: mark as a reference plant (must be LIVING now). false: turn a reference plant back into the caller's own (must be EXTERNAL now)."),
|
|
291
292
|
}, async ({ plantId, reference }) => apiSend("PATCH", `/api/v1/plants/${plantId}/reference`, { reference }));
|
|
293
|
+
server.tool("establish_propagation", "WRITE: promote a plant from PROPAGATING to ESTABLISHED, stamping establishedAt to now. This is the same one-way transition the app's plant edit form applies manually, and the action behind the 'is it still a cutting?' recommendation (propagationCheckDue, from list_care_recommendations) on the Improvements page. Fails with a 400 if the plant is not currently PROPAGATING. Requires a write-scoped API key.", {
|
|
294
|
+
plantId: z.number().int().positive().describe("The PROPAGATING plant to promote."),
|
|
295
|
+
}, async ({ plantId }) => apiSend("PATCH", `/api/v1/plants/${plantId}/establish`));
|
|
292
296
|
// --- Wishlist ----------------------------------------------------------
|
|
293
297
|
/**
|
|
294
298
|
* The caller's personal wishlist (R2-15) — plants they want but don't own
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sebamomann/plants-mcp",
|
|
3
|
-
"version": "2.
|
|
4
|
-
"description": "MCP server for the Sprig plant app:
|
|
3
|
+
"version": "2.8.1",
|
|
4
|
+
"description": "MCP server for the Sprig plant app: 78 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, 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>",
|