Day-in-the-Country-LLC/garden-mcp-server
If you are the rightful owner of garden-mcp-server and would like to certify it and/or have it hosted online, please leave a comment on the right or send an email to dayong@mcphub.com.
MCP server with tools for gardening questions and planning.
garden-mcp-server
MCP server providing tools for gardening questions and planning.
Current toolset (fastMCP):
- Weather Forecast: Get daily forecasts for a location (Open‑Meteo).
- Weather Archive: Historical daily data via Open‑Meteo ERA5 archive.
- Meteostat History: Station observations (daily) with common fields (tavg/tmin/tmax/prcp).
- Identify Plant: Identify plants from an image using Plant.id (top candidates with details).
- Enrich Plants: Fetch extra info for candidate scientific names (iNaturalist + Wikipedia).
- Fetch URL Text: Fetch a web page and extract readable text (for client‑side RAG).
- Garden Search: Search the web for gardening topics (DuckDuckGo).
- Bed Layout Planner: Calculate plant counts and simple grid layouts.
Quick start (using uv)
- Requirements: Python 3.11+ and
uvinstalled (macOS:brew install uv). - Create a virtual environment and install project (with dev tools):
uv venv --python 3.11uv pip install -e .[dev]
- Run the MCP server over stdio:
uv run garden-mcp-server
Testing and linting (with uv)
- Run tests:
uv run pytest - Lint:
uv run ruff check .
Configuration
- Weather forecast uses Open‑Meteo’s free APIs (no key required).
- Weather archive uses Open‑Meteo ERA5 archive (no key required).
- Meteostat tools use the free Meteostat client (no key required).
- Garden search uses DuckDuckGo via
duckduckgo-search.
Integration
This server uses the fastMCP framework and speaks MCP over stdio. Point your MCP-capable client (e.g., an IDE or agent runtime) to the garden-mcp-server executable (or invoke with uv run garden-mcp-server). See the client’s documentation for configuring MCP servers.
Authentication and secrets
- Do not commit API keys or secrets to this repository. It is public by design.
- All tools that require authentication must read credentials from environment variables. Suggested naming:
GARDEN_<SERVICE>_API_KEY(e.g.,GARDEN_WEATHER_API_KEY). - The server automatically loads a local
.envfile at startup for convenience..envis already in.gitignore. - Example usage:
- One‑off:
GARDEN_WEATHER_API_KEY=sk_live_... uv run garden-mcp-server - With .env:
- Create
.envin the project root (not committed):GARDEN_WEATHER_API_KEY=sk_live_...
- Run:
uv run garden-mcp-server
- Create
- One‑off:
- Our default weather tool uses Open‑Meteo (no key). If you switch to a key‑based provider, follow the env‑var pattern above.
- Plant identification uses Plant.id and requires an API key. BYOT/K policy applies:
- Public: callers pass
plant_id_api_keyper request. - Private/internal: set
GARDEN_PLANT_ID_API_KEYin env; authenticated callers with scopeGARDEN_INTERNAL_SCOPE(defaultinternal) may omitplant_id_api_key. - Do not log or store user‑supplied keys.
- Public: callers pass
Client examples
-
Python (stdio via uv):
pip install fastmcp- Example:
-
import asyncio from fastmcp.client import Client from fastmcp.client.transports import UvStdioTransport async def main(): # Launch the server in a subprocess via uv run transport = UvStdioTransport( command="garden-mcp-server", project_directory=".", # repo root with pyproject.toml ) client = Client(transport) async with client: # List available tools tools = await client.list_tools() print([t.name for t in tools]) # Call forecast tool res = await client.call_tool( "tool_get_weather_forecast", {"location": "Portland, OR", "days": 3}, ) print(res) # Call Open-Meteo archive tool res = await client.call_tool( "tool_open_meteo_archive", { "location": "Portland, OR", "start_date": "2024-01-01", "end_date": "2024-01-07", "daily_vars": [ "temperature_2m_max", "temperature_2m_min", "precipitation_sum", ], }, ) print(res) # Call Meteostat history tool res = await client.call_tool( "tool_meteostat_history", { "location": "Portland, OR", "start_date": "2024-01-01", "end_date": "2024-01-07", "fields": ["tavg", "tmin", "tmax", "prcp"], }, ) print(res) # Identify a plant by image URL (BYOK or env-based) res = await client.call_tool( "tool_identify_plant", { "image_url": "https://example.com/photo.jpg", "top_n": 3, # Public deployment (BYOK): pass your Plant.id key here # "plant_id_api_key": "pk_live_...", }, ) print(res) # Enrich the first two scientific names via iNaturalist/Wikipedia names = [c["scientific_name"] for c in res["candidates"][:2]] enriched = await client.call_tool( "tool_enrich_plants", {"scientific_names": names, "max_results": 2}, ) print(enriched) # Fetch page text for RAG (LLM summarization stays client-side) page = await client.call_tool( "tool_fetch_url_text", {"url": "https://example.com/garden-guide", "max_chars": 12000}, ) print(page["title"], len(page["text"])) asyncio.run(main())
-
-
Error handling and validation:
- Tools validate ISO date formats (YYYY-MM-DD) and enforce
start_date <= end_date. - Open‑Meteo archive checks variable names for simple identifier format.
- Meteostat history validates requested fields against known columns (e.g.,
tavg,tmin,tmax,prcp,snow,wdir,wspd,wpgt,pres,tsun).
- Tools validate ISO date formats (YYYY-MM-DD) and enforce
Cloud Run deployment
- Build container locally:
docker build -t gcr.io/PROJECT_ID/garden-mcp-server:latest .docker push gcr.io/PROJECT_ID/garden-mcp-server:latest
- Or using gcloud (recommended):
gcloud builds submit --tag gcr.io/PROJECT_ID/garden-mcp-server:latest
- Deploy to Cloud Run:
gcloud run deploy garden-mcp-server --image gcr.io/PROJECT_ID/garden-mcp-server:latest --region REGION --platform managed --allow-unauthenticated- Set env vars as needed:
FASTMCP_HOST=0.0.0.0(default)GARDEN_MCP_HTTP_TRANSPORT=streamable-httporsse(default: streamable-http)- Optional paths:
FASTMCP_STREAMABLE_HTTP_PATH=/mcp,FASTMCP_SSE_PATH=/sse,FASTMCP_MESSAGE_PATH=/messages/
- Connecting from a client:
- Streamable HTTP:
Client("https://SERVICE.run.app/mcp") - SSE:
Client("https://SERVICE.run.app/sse") - If you enable auth (JWT or OAuth), include appropriate
authin the client (e.g.,Client(url, auth="<bearer-token>")).
- Streamable HTTP:
Mobile uploads (GCS pre‑signed URLs)
- The HTTP server exposes a helper route to upload images from mobile apps without exposing credentials:
POST /upload-urlwith JSON:{ "file_name": "uploads/uuid.jpg", "content_type": "image/jpeg" }- Response: signed
upload_url(PUT) and a signedget_url(GET) valid for a short time. - Requires environment:
GARDEN_GCS_BUCKETand Cloud Run service account withstorage.objects.createpermission.
- Flow:
- Mobile requests
/upload-urlto obtain URLs. - Mobile
PUTthe image bytes toupload_urlwith headerContent-Typeset. - Client calls
tool_identify_plantwithimage_url=get_url(temporary access). - Optionally, delete or rotate objects later per your retention policy.
- Mobile requests
Upload URL policy (use your own storage by default)
- Control access to
/upload-urlviaGARDEN_UPLOAD_URL_POLICY:disabled: Do not serve upload URLs (public BYOS only).internal(default): Only allow callers with theGARDEN_INTERNAL_SCOPE(defaultinternal).public: Allow anyone (not recommended for shared buckets).
- BYOS (Bring Your Own Storage): Clients can skip
/upload-urlentirely by:- Sending
image_base64(small/medium images), or - Passing an
image_urlthat points to their own signed URL in their storage (GCS/S3/etc.). - This ensures third parties never use your bucket.
- Sending
Recommended auth (JWT) for Cloud Run
- Symmetric JWT (HS256) with a shared secret (simple and secure if managed via Secret Manager):
- Create a secret:
echo -n 'a-strong-shared-secret' | gcloud secrets create garden-jwt-secret --data-file=- - Deploy with auth enabled:
gcloud run deploy garden-mcp-server \ --image gcr.io/PROJECT_ID/garden-mcp-server:latest \ --region REGION --platform managed --allow-unauthenticated \ --set-env-vars=FASTMCP_HOST=0.0.0.0,GARDEN_MCP_HTTP_TRANSPORT=streamable-http,FASTMCP_SERVER_AUTH=JWT,FASTMCP_SERVER_AUTH_JWT_ALGORITHM=HS256 \ --update-secrets=FASTMCP_SERVER_AUTH_JWT_PUBLIC_KEY=garden-jwt-secret:latest
- Client usage: pass a Bearer token signed with HS256 using the shared secret.
- Create a secret:
Defaults and endpoints
- Default transport for Cloud Run is
streamable-http(robust, works well with Cloud Run request model). Usesseif your client explicitly requires SSE and your service concurrency/timeouts are tuned for long-lived connections. - Health endpoints are provided when running in HTTP mode:
GET /healthz→ JSON{"status":"ok"}GET /readyz→ok
Public MCP + BYOK (Bring Your Own Key)
- To keep the MCP public while ensuring others pay for their own Plant.id usage:
- Public instance: do NOT set
GARDEN_PLANT_ID_API_KEYso callers must passplant_id_api_keyper request (BYOK). - Private/internal instance: set
GARDEN_PLANT_ID_API_KEYfor your own app. - Single instance with policy: set
GARDEN_PLANT_ID_API_KEYand require an auth scope (defaultinternal) to use it. Others must passplant_id_api_key.- Configure the required scope via
GARDEN_INTERNAL_SCOPE(default:internal).
- Configure the required scope via
- Public instance: do NOT set
- Applies to all paid provider tools in this repo. When users pass a key (e.g.,
plant_id_api_key), the server uses it only for the upstream API call and never logs/stores it.