What is MCP?
The Model Context Protocol is Anthropic’s
open spec for letting LLMs call external tools over JSON-RPC. garden.gg
hosts a Streamable HTTP MCP server at https://garden.gg/mcp so any
MCP-aware client can connect to your account and act on your data.
What you can ask Claude to do:
- “What’s due in my garden today?”
- “List my plots.”
- “What plants are in my main bed?”
- “I just watered Plot A — log it.”
- “Did I harvest anything this week?”
- “What’s the weather like?”
The server is OAuth-only — no API keys to paste anywhere. You click Authorize once per client; everything else is invisible.
Connecting Claude.ai
In Claude.ai:
- Open Settings → Connectors.
- Click Add custom connector.
- Server URL:
https://garden.gg/mcp. - Save. Claude.ai will redirect you to
garden.gg/authorize— sign in if prompted, review what the connector wants access to, click Authorize. - You’ll land back in Claude.ai with the connection live. Try asking “what plots do I have on garden.gg?”
Connecting Claude Desktop
Claude Desktop reads claude_desktop_config.json. Add an entry like:
{
"mcpServers": {
"garden-gg": {
"url": "https://garden.gg/mcp"
}
}
}
Restart Claude Desktop. The first time it tries to call a tool, it’ll open the OAuth flow in your browser. Same drill: sign in, review, Authorize.
Connecting Cursor / Continue / other clients
Any MCP client that supports HTTP transport with OAuth will work. Point
it at https://garden.gg/mcp and let the OAuth discovery do the rest —
the server publishes RFC 9728 + RFC 8414 metadata at
/.well-known/oauth-protected-resource and
/.well-known/oauth-authorization-server.
Tools available
| Tool | Type | Args | Description |
|---|---|---|---|
list_plots | read | — | All your plots with summary metadata |
get_plot | read | plot_id | Drill down into one plot — schedules, last_watered/fertilized, plant count, hero image |
list_plants_in_plot | read | plot_id | What’s growing in a specific plot |
list_recent_events | read | optional plot_id, event_type, since | Read your activity log. since accepts RFC 3339 (2026-04-29T00:00:00Z) or relative (7d, 24h) |
list_harvests | read | optional plot_id, since, until | Harvest history with plot + plant names joined and weight_grams surfaced |
list_seed_inventory | read | optional search, supplier | Up to 100 of your seed packets |
list_seedlings | read | optional status | Seedlings pipeline (active/germinated/hardening/transplanted) |
get_sensor_readings | read | plot_id | Latest reading + 24h min/max per sensor kind for a plot. Empty sensors: [] if no IoT kit attached |
list_tasks_today | read | — | Watering, fertilizing, harvest readiness due today |
list_tasks_this_week | read | — | Same, 7-day window |
get_weather | read | — | Current conditions + 3-day forecast for your zone |
search_plant_varieties | read | query | Search your variety library |
log_event | write | event_type, target id | Log a care or observation event. See log_event verbs below |
create_seed_inventory_items | write | items (1-50) | Create seed packets in bulk |
update_seed_inventory_items | write | items with id (1-50) | Update owned seed packets in bulk |
delete_seed_inventory_items | write | ids (1-50), confirm: true | Delete owned seed packets |
create_seedling | write | one of seed_inventory_id/plant_id + fields | Start a seedling (decrements inventory if from a packet) |
update_seedling | write | seedling_id + fields | Update an owned seedling |
delete_seedling | write | seedling_id, confirm: true | Delete an owned seedling |
create_plot | write | plot fields | Create a new plot |
update_plot | write | plot_id + fields | Update an owned plot |
delete_plot | write | plot_id, confirm: true, confirm_plot_name | Delete an owned plot (name match required) |
add_plant_to_plot | write | plot_id, plant_id + fields | Place a plant in an owned plot |
update_plot_plant | write | plot_plant_id + fields | Update a plot_plant |
remove_plant_from_plot | write | plot_plant_id, confirm: true | Remove a plot_plant |
create_plant_variety | write | variety fields | Add a variety to your library |
update_plant_variety | write | variety_id + fields | Update an owned variety |
delete_plant_variety | write | variety_id, confirm: true | Delete an unused owned variety |
delete_event | write | event_id, confirm: true | Delete a logged event; recomputes last_watered_on, harvest totals |
Write tools — destructive confirmation
Every delete_* and remove_* tool requires confirm: true. delete_plot
also requires confirm_plot_name to exactly match the plot’s current
name — anti-typo guard so a fat-fingered plot_id doesn’t silently nuke
the wrong garden.
log_event verbs
log_event is one tool but supports eight verbs, each with its own
required-arg shape:
| event_type | Targets | Required | Optional |
|---|---|---|---|
water | a plot | plot_id | notes |
fertilize | a plot | plot_id | notes |
mulch | a plot | plot_id | notes |
harvest | a plot_plant | plot_plant_id | weight_grams, notes |
transplant | a plot_plant | plot_plant_id | destination_plot_id (must be your plot), notes |
prune | a plot_plant | plot_plant_id | notes |
pest_observation | a plot OR plot_plant | one of plot_id/plot_plant_id plus description | severity (low/medium/high), notes |
problem | a plot OR plot_plant | one of plot_id/plot_plant_id plus description | severity, notes |
Result shape
Every list tool returns a stable envelope:
{
"items": [...], // up to the tool's documented cap (50 or 100)
"count": 23, // == items.length
"has_more": false // true means narrow your filters and call again
}
Single-entity tools (get_plot, get_sensor_readings, get_weather)
return the entity directly without a wrapper.
If has_more: true, the tool found more matching rows than fit in its
cap. The right move is a tighter filter — pass a narrower since
window, or scope to one plot_id. We do not paginate with cursors;
real-world MCP clients drop them too often, and LLMs naturally reason
in filtered queries anyway.
When a tool’s arguments are malformed (bad UUID, missing required
field, unknown enum value), the response has isError: true and the
admin observability log records error_code: "invalid_args". When a
target id doesn’t exist or isn’t yours, the response is not_found
with the same shape — there’s no way to enumerate other users’ ids by
poking the tool.
Daily limits
MCP calls count against a per-day quota that varies by plan:
| Plan | Daily MCP calls |
|---|---|
| Sprout (free) | 50 |
| Bloom | 500 |
| Harvest | 5000 |
Quotas reset at UTC midnight. If you hit the limit, the tool returns an error message Claude can recover from — your account isn’t suspended. You can see your current usage on the Settings page in the Connected Applications section.
Privacy
The MCP server logs only which tool was called and whether it succeeded — never the arguments or result payloads. We do not retain your search queries, plot names, or any free-text content you ask Claude to send through.
Specifically, the mcp_usage_log table holds (user_id, oauth_client_id, tool_name, success, latency_ms, error_code, created_at) and nothing else.
Managing connected clients
You can see and revoke every authorized client from Settings → Connected Applications. Revoke is immediate — the next request from that client returns 401 within one cycle.
If a client’s tokens are ever leaked, revoking from the Settings page invalidates every access token AND every refresh token for that client in one shot. The user re-authorizes from the consent screen on next use.
How the OAuth flow works (technical)
For developers building their own MCP clients:
- Discovery: GET
https://garden.gg/.well-known/oauth-protected-resourceto learn the auth-server URL. - Dynamic Client Registration (RFC 7591): POST to
/oauth/registerwithclient_name+redirect_uris. You get aclient_id(noclient_secret— public clients only). - Authorization (RFC 6749 with PKCE-S256): redirect the user to
/oauth/authorizewith the standard params. PKCE is required —plainis banned.redirect_urimust exact-match what you registered. - Token exchange: POST to
/oauth/tokenwith the code + verifier. You get an access token (1h TTL) and a refresh token (90d TTL). - Use the access token:
Authorization: Bearer …onPOST /mcp. - Refresh: POST to
/oauth/tokenwith the refresh. The old refresh is single-use and rotates on every call. Replaying a used refresh invalidates the entire chain — both the legitimate client and the attacker get logged out, the user re-authorizes.
Tokens are opaque (gg_at_…, gg_rt_…) and stored hashed at rest.
Plaintext is never logged.
Troubleshooting
“Authorization failed” on the consent screen. The pending request expired (10-minute TTL) or the redirect_uri doesn’t match what was registered. Restart the flow from your MCP client.
Tool calls return 401. Your access token expired (1h TTL) or you revoked the client from Settings. Refresh tokens are good for 90 days; your client should rotate them automatically.
“Daily limit reached”. Wait until UTC midnight or upgrade your plan. Admins can grant per-user overrides for support tickets.
Built something interesting with the garden.gg MCP server? We’d love to hear about it: [email protected].