---
name: gribstream-mysql
description: Query GribStream weather forecasts through its read-only MySQL-compatible connection. Use when an agent must discover datasets or weather parameter columns, translate exact selectors when needed, choose between timeseries and runs, write or validate supported weather SQL, reason about time zones and forecast semantics, consume bounded results, or recover from clear SQL errors without inventing unsupported syntax.
---

# GribStream MySQL

Use the GribStream MySQL connection as a self-describing weather-data tool. It provides a focused, read-only SQL dialect rather than a general relational database.

## Connection contract

- Host: `mysql.gribstream.com`
- Port: `3307`
- Username: accepted for client compatibility; `gribstream` is conventional
- Password: the user's GribStream API token
- TLS: required; verify both the certificate chain and `mysql.gribstream.com`
- Access: read only
- Dataset: select a dataset schema such as `gfs`, or qualify weather tables

Never reveal, log, query, or place the API token in generated SQL. Use the connection already provided by the host application.

## Choose the virtual table

| Need | Table | Time predicate |
|---|---|---|
| Best eligible run for every valid time | `<dataset>.timeseries` | Bound `forecasted_time` |
| Forecasts from specific model initializations | `<dataset>.runs` | Bound `forecasted_at` and `lead_time` |

`forecasted_at` is model initialization time. `forecasted_time` is the valid time being predicted. On `timeseries`, `forecasted_at <= timestamp` is an optional historical run cutoff and is the only supported operator for that purpose.

## Required workflow

1. Read the running adapter identity, dialect, and examples when the SQL syntax is unfamiliar:

   ```sql
   SELECT adapter_version, build_revision, sql_dialect_version, release_stage
   FROM gribstream.adapter_info;

   SELECT topic, supported_syntax, example, notes
   FROM gribstream.sql_dialect
   ORDER BY topic;

   SELECT name, description, `sql`
   FROM gribstream.query_examples
   ORDER BY name;
   ```

   Standard MySQL introspection also works. Use `SHOW TABLES FROM gribstream`, `SHOW FULL COLUMNS FROM <dataset>.timeseries`, or `DESCRIBE <dataset>.timeseries` when the client discovers schemas that way.

   The public beta compatibility contract is versioned by `sql_dialect_version`. Do not assume that an undocumented MySQL feature is available merely because the connection uses the MySQL protocol.

2. Discover a dataset. Do not infer availability from model knowledge alone:

   ```sql
   SELECT code, full_name, provider, archive_start, archive_window,
          rolling_window, min_lead_time, max_lead_time,
          is_ensemble, member_count, members, parameter_count
   FROM gribstream.datasets
   WHERE full_name LIKE '%forecast%'
   LIMIT 20;
   ```

   Before a large historical query, compare the requested period with `archive_start` and `archive_window`; a rolling window can move forward. `catalog_updated_at` shows when the catalog metadata was refreshed.

3. Search parameter groups in one exact dataset:

   ```sql
   SELECT short_name, full_name, units, has_code_table, variation_count
   FROM gribstream.parameters
   WHERE dataset = 'gfs'
     AND (short_name = 'TMP' OR full_name LIKE '%temperature%')
   LIMIT 20;
   ```

4. Discover the dataset's weather columns. Copy identifiers; never derive or guess them:

   ```sql
   SHOW FULL COLUMNS FROM gfs.timeseries;

   SELECT column_name, full_name, units, name, level, info,
          selector_json, gs_value_sql
   FROM gribstream.selector_columns
   WHERE dataset = 'gfs'
     AND column_name = 'tmp_2_m_above_ground';
   ```

   `SHOW FULL COLUMNS` gives concise labels, units, and exact `GS_VALUE` mappings. `gribstream.selector_columns` gives the original JSON selector and full catalog mapping. Long or colliding generated identifiers may contain a deterministic suffix, so copy `column_name` exactly.

5. Build the smallest query that answers the request. Narrow time, locations, lead times, members, and selected weather columns before applying weather-value filters. For an interactive response, use a conservative `LIMIT` unless the user explicitly needs a large export.

6. Execute directly when the query is routine, bounded, and matches the user's intent.

7. Use `EXPLAIN` only as an advanced diagnostic when a query needs validation or troubleshooting. It does not retrieve weather rows or consume query quota.

For a comparable signal across datasets, inspect `gribstream.shared_parameters` for the concept, units, and supported datasets. Then discover the exact selector tuple for each dataset. Do not assume that different datasets use the same selector strings or invent a conversion.

## Build weather SQL

Select result columns explicitly. Common columns are `dataset`, `forecasted_at`, `forecasted_time`, `lat`, `lon`, `name`, `member`, `index_updated_at`, `lead_time`, and `grid_step`, plus the dataset-specific weather columns returned by `SHOW FULL COLUMNS`.

`lead_time` is returned as hours. `grid_step` is the requested latitude/longitude spacing in degrees and is `NULL` for enumerated points.

`index_updated_at` is optional freshness metadata: the latest source-data update associated with the row. It is not model initialization time; use `forecasted_at` for that.

Treat catalog units as authoritative. A weather parameter column or `GS_VALUE` returns the exact selector's native units; an alias does not convert them. Inspect `has_code_table` before interpreting a coded field as a continuous measurement. Numeric weather values and calculations are MySQL `DOUBLE`; missing numeric or timestamp values are SQL `NULL`, tested with `IS NULL` or `IS NOT NULL`, never `= NULL`.

Prefer a discovered weather column:

```sql
tmp_2_m_above_ground AS temp_k
```

Use `GS_VALUE(name, level[, info]) AS alias` as the exact-selector fallback when translating an existing HTTP API selector, parameterizing the selector in a prepared statement, or using a weather-column identifier that is inconvenient. Preserve case and include the third `info` argument when it is non-empty.

Aliases defined earlier in `SELECT` behave as identifiers in calculations:

```sql
SELECT forecasted_time,
       tmp_2_m_above_ground AS temp_k,
       temp_k - 273.15 AS temp_c
FROM gfs.timeseries
WHERE forecasted_time BETWEEN NOW() AND NOW() + INTERVAL 6 HOUR
  AND lat = 40.758
  AND lon = -73.985
  AND temp_c BETWEEN 0 AND 40
ORDER BY forecasted_time
LIMIT 100;
```

Supported calculations include numeric and boolean literals, parentheses, unary `+`, `-`, and `NOT`, arithmetic, comparisons, and boolean `AND`/`OR`. Familiar numeric functions include `ABS`, `CEIL`/`CEILING`, `FLOOR`, `ROUND`, `SQRT`, `POW`/`POWER`, `MOD`, and `TRUNCATE`. Coordinate names support `LOWER`/`LCASE`, `UPPER`/`UCASE`, `TRIM`, and Unicode-aware `CHAR_LENGTH`; do not substitute MySQL's byte-counting `LENGTH`. Additional GribStream expression functions use `func.Name(...)`, for example `func.Hypot(u, v)`. Consult `https://gribstream.com/expressions` for registered functions; do not invent others.

Join time, location, lead-time, member, and run-cutoff conditions with `AND`. Use `OR` only within weather-value conditions.

## Time rules

- Sessions and returned source timestamps are UTC.
- Timestamp literals accept a date, MySQL datetime, ISO `T` datetime, or RFC 3339 with an offset.
- A timestamp without an offset is UTC except as the input wall time to `CONVERT_TZ`.
- `NOW()`, `CURRENT_TIMESTAMP`, and `UTC_TIMESTAMP()` are evaluated once per statement.
- Fixed intervals from microseconds through weeks are supported. Do not use calendar month or year intervals.
- Use paired `>=` and `<` bounds for half-open ranges.
- Prefer half-open bounds for adjoining windows so their shared boundary is not returned twice.
- Exact non-contiguous times may use `forecasted_time IN (...)` on `timeseries` or `forecasted_at IN (...)` on `runs`.

For a local civil-day query, convert both wall-time boundaries to UTC:

```sql
WHERE forecasted_time >=
        CONVERT_TZ('2026-10-25 00:00:00', 'Europe/Paris', 'UTC')
  AND forecasted_time <
        CONVERT_TZ('2026-10-26 00:00:00', 'Europe/Paris', 'UTC')
```

Named IANA zones account for daylight-saving transitions. Ambiguous or nonexistent local input times fail. Result-side `CONVERT_TZ(forecasted_time, 'UTC', zone)` is presentation-only; its alias cannot be referenced in `WHERE`. Keep the UTC source column when a repeated local hour matters.

## Locations, grids, and ensembles

- One point: `lat = 40.758 AND lon = -73.985`
- One named point: `(lat, lon, name) = (40.758, -73.985, 'Times Square')`
- Several points: `(lat, lon) IN ((40.758, -73.985), (...))`
- Grid: bounded `lat BETWEEN ...`, `lon BETWEEN ...`, and `grid_step = ...`
- Lead time: `lead_time = '24h'` or a bounded range
- Ensemble: discover the dataset's `members` array, then use `member = 0` or `member IN (...)` only for ensemble datasets

Lead-time values use quoted Go durations such as `'90m'`, `'24h'`, or `'168h'`. Equality, `BETWEEN`, paired comparisons, and one-sided minimum or maximum comparisons are supported.

## DuckDB clients

DuckDB's MySQL extension can attach the connection as a read-only remote database. It reads credentials from `MYSQL_HOST`, `MYSQL_TCP_PORT`, `MYSQL_USER`, `MYSQL_PWD`, and `MYSQL_DATABASE`, so do not place the token in generated SQL. Attach with `ssl_mode=verify_identity`, then query three-part names such as `gribstream_db.gfs.timeseries`.

Normal DuckDB queries against an attached weather table currently support one point or a regular grid defined by latitude/longitude bounds and `grid_step`. They do not preserve GribStream's non-contiguous `(lat, lon) IN (...)` shape. To enumerate several separate points in one request, use the extension's `mysql_query` pass-through with a supported literal GribStream SQL statement:

```sql
SELECT *
FROM mysql_query('gribstream_db', $sql$
  SELECT forecasted_time, name, lat, lon,
         tmp_2_m_above_ground AS temp_k
  FROM gfs.timeseries
  WHERE forecasted_time BETWEEN NOW() AND NOW() + INTERVAL 6 HOUR
    AND (lat, lon, name) IN (
          (40.758, -73.985, 'Times Square'),
          (29.7604, -95.3698, 'Houston')
        )
  LIMIT 100
$sql$);
```

## Efficient queries, ordering, and result size

Quota is based on weather data read to evaluate a query, not the number of rows returned. A condition on a weather value or calculated alias can remove result rows only after those values have been read. Do not expect a highly selective weather-value filter to make a broad query inexpensive.

Reduce quota usage by choosing `timeseries` unless run history is required, shortening the time range or using exact times, selecting only needed weather columns, preferring exact points or an appropriate grid step, bounding lead time, and selecting only needed ensemble members.

Weather queries may use `SELECT DISTINCT ... LIMIT n` for bounded deduplication. `DISTINCT` applies to the complete selected row, cannot be combined with `ORDER BY`, and does not reduce the weather data read to evaluate the query.

Treat `ORDER BY` mainly as an interactive-session and data-exploration feature. For a bounded query, a time-first order can usually return rows incrementally: use `forecasted_time` first for `timeseries` or `forecasted_at` first for `runs`. Up to eight selected columns or aliases and either direction are supported; secondary keys break ties after the leading time key.

Other ordering shapes may severely reduce throughput. A weather-value-first global ranking requires `LIMIT`, scans the complete selection, and returns rows only after the scan. A selective weather-value filter does not make a broad ordered query inexpensive.

For backfills and other high-volume data pulls, omit `ORDER BY`. If order matters, sort the data after receiving it.

`LIMIT` controls returned rows, not the amount of weather data read. Use it to keep interactive output small, never as the only quota control.

A generic MySQL MCP may buffer the complete result into one tool response. Keep agent-facing queries small and use a streaming MySQL client for large exports.

## Debug, cancel, and recover

Do not add `EXPLAIN` to routine queries. To validate or troubleshoot a query without retrieving weather rows:

```sql
EXPLAIN
SELECT forecasted_time, lat, lon,
       tmp_2_m_above_ground AS temp_k
FROM gfs.timeseries
WHERE forecasted_time BETWEEN '2026-07-13T00:00:00Z'
                          AND '2026-07-13T12:00:00Z'
  AND (lat, lon) = (40.758, -73.985)
LIMIT 100;
```

Disconnecting the client cancels its active query. From a second connection authenticated with the same token, `KILL QUERY <connection_id>` cancels the active statement and leaves the target session reusable.

A connection has one active statement at a time. Consume or close the result before reusing it; use a pool only when concurrent queries are actually needed.

Interpret common errors:

- `1064`: fix SQL syntax or validation error.
- `1235`: remove the specifically named unsupported SQL construct.
- `1045`: authentication failed; obtain a valid token without exposing it.
- `1317`: query was cancelled.
- `1226`: respect the wait time in the rate-limit message; do not immediately retry in a loop.

If a query is rejected as unsafe to order, remove `ORDER BY` and sort downstream, reduce the selection, or use a bounded top-N only when ranking is the actual goal.

## Deliberate boundaries

Do not generate writes, DDL, transactions, locks, joins, subqueries, aggregation, grouping, weather `SELECT *`, non-zero weather offsets, order expressions, ordinal ordering, `DISTINCT` without `LIMIT`, `DISTINCT` with `ORDER BY`, or unbounded global sorts. Metadata tables support a broader but bounded discovery subset.

When unsure whether syntax is supported, query `gribstream.sql_dialect` first. Use `EXPLAIN` to validate a supported weather statement when needed; do not probe by issuing a large weather query.
