You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
docs+ci: dplyr/dbplyr + ibis examples on the source.coop mirror (#300)
* docs: dplyr/dbplyr + ibis examples reading the source.coop mirror
Add a dataframe-idiom access route for R (dplyr/dbplyr) and Python (ibis)
that points a local DuckDB straight at the public source.coop mirror
(AWS us-west-2, anonymous) — no MCP server needed to read the data. Verbs
push down into DuckDB; only collect()/execute() pulls rows.
- programmatic-access.md: new "R — dplyr/dbplyr" and "Python — ibis"
sections; document the public-<name> -> cboettig/<name> path mapping.
- examples/query_dbplyr.R, examples/query_ibis.py: self-checking scripts.
- Fix mcptools links (tidyverse -> posit-dev) and clarify the client is
stdio-only (posit-dev/mcptools#88 deferred direct HTTP transport).
* ci: standalone workflow running the dbplyr + ibis examples
Self-checking example scripts against the public source.coop mirror (no
secrets). Path-filtered on PRs plus a weekly cron to catch the mirror
dataset/schema rolling forward. Separate from the pytest workflow since R
isn't part of the codebase.
Copy file name to clipboardExpand all lines: docs/guide/programmatic-access.md
+94-2Lines changed: 94 additions & 2 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -48,7 +48,7 @@ asyncio.run(main())
48
48
49
49
### R
50
50
51
-
There is no R MCP client library yet. The server runs in stateless mode, so you can hit the JSON-RPC endpoint directly with `httr2`. Responses arrive as server-sent events (SSE).
51
+
No R MCP client speaks HTTP directly ([`mcptools`](https://posit-dev.github.io/mcptools/) is stdio-only — see the [ellmer + mcptools](#r-ellmer-mcptools) section below). The server runs in stateless mode, so you can hit the JSON-RPC endpoint directly with `httr2`. Responses arrive as server-sent events (SSE).
52
52
53
53
```r
54
54
library(httr2)
@@ -104,6 +104,98 @@ for (block in resp$result$content) {
104
104
}
105
105
```
106
106
107
+
### R — dplyr / dbplyr
108
+
109
+
If you'd rather write queries in `dplyr` than SQL, you don't need the MCP server at all: point a **local** DuckDB at the public [source.coop](https://source.coop) mirror of the same data and let `dbplyr` compile your `dplyr` verbs to DuckDB SQL. The mirror lives on AWS `us-west-2` (anonymous reads, reader doesn't pay egress), so it works from anywhere. Use the STAC catalog to *discover* paths and columns; read the Parquet directly here.
110
+
111
+
```r
112
+
library(DBI)
113
+
library(duckdb)
114
+
library(dplyr)
115
+
library(dbplyr)
116
+
117
+
con<- dbConnect(duckdb::duckdb())
118
+
dbExecute(con, "INSTALL httpfs; LOAD httpfs;")
119
+
120
+
# Anonymous reads from the source.coop mirror (AWS us-west-2). The bucket name
121
+
# has dots, so URL_STYLE 'path' is required for the TLS certificate to match.
122
+
dbExecute(con, "
123
+
CREATE SECRET source_coop (
124
+
TYPE S3, KEY_ID '', SECRET '',
125
+
ENDPOINT 's3.us-west-2.amazonaws.com', REGION 'us-west-2',
126
+
URL_STYLE 'path', USE_SSL 'true',
127
+
SCOPE 's3://us-west-2.opendata.source.coop'
128
+
)")
129
+
130
+
# Register the Parquet dataset as a view, then treat it as a dplyr table.
dbExecute(con, sprintf("CREATE VIEW countries AS SELECT * FROM read_parquet('%s')", path))
133
+
134
+
countries<- tbl(con, "countries")
135
+
136
+
q<-countries|>
137
+
filter(subtype=="country", is_land) |>
138
+
select(country, name_en, subtype) |>
139
+
arrange(name_en) |>
140
+
head(10)
141
+
142
+
q|> show_query() # inspect the DuckDB SQL dbplyr generated
143
+
q|> collect() # pull the result into a tibble
144
+
145
+
dbDisconnect(con, shutdown=TRUE)
146
+
```
147
+
148
+
`dbplyr` pushes `filter`/`select`/`arrange`/`summarise`/joins down into DuckDB, so column and row pruning happen in the engine — only the final `collect()` pulls data into R.
149
+
150
+
::: tip
151
+
The STAC catalog publishes NRP paths (`s3://public-<name>/…`); the source.coop mirror maps them to `s3://us-west-2.opendata.source.coop/cboettig/<name>/…`. Discover paths and columns via `get_stac_details` (see the [`httr2` example above](#r)) or the [web catalog](https://beta.source.coop), then translate the prefix. The usual DuckDB read rule still applies: always `read_parquet('s3://…')`, never a bare table name.
152
+
:::
153
+
154
+
### Python — ibis
155
+
156
+
The Python parallel to `dbplyr`: [ibis](https://ibis-project.org) drives the same local DuckDB against the same source.coop mirror, and its deferred expressions compile to DuckDB SQL — nothing runs until `.execute()`.
157
+
158
+
```bash
159
+
pip install 'ibis-framework[duckdb]'
160
+
```
161
+
162
+
```python
163
+
import ibis
164
+
165
+
con = ibis.duckdb.connect()
166
+
con.raw_sql("INSTALL httpfs; LOAD httpfs;")
167
+
168
+
# Anonymous reads from the source.coop mirror (AWS us-west-2). The bucket name
169
+
# has dots, so URL_STYLE 'path' is required for the TLS certificate to match.
170
+
con.raw_sql("""
171
+
CREATE SECRET source_coop (
172
+
TYPE S3, KEY_ID '', SECRET '',
173
+
ENDPOINT 's3.us-west-2.amazonaws.com', REGION 'us-west-2',
print(ibis.to_sql(expr)) # inspect the DuckDB SQL ibis generated
194
+
print(expr.execute()) # run it, returns a pandas DataFrame
195
+
```
196
+
197
+
Like `dbplyr`, ibis pushes `filter`/`select`/`order_by`/aggregations/joins down into DuckDB, so only the final `.execute()` pulls rows into Python. The same path mapping and `read_parquet` rules from the R tip above apply.
198
+
107
199
## LLM tool use
108
200
109
201
Let the model discover datasets, write SQL, and interpret results autonomously. The MCP tools (`browse_stac_catalog`, `get_stac_details`, `query`) are registered as callable tools so the model decides when and how to use them.
@@ -151,7 +243,7 @@ asyncio.run(main())
151
243
152
244
### R — ellmer + mcptools
153
245
154
-
[`mcptools`](https://github.com/tidyverse/mcptools) is an MCP client for R that plugs MCP tools into `ellmer` chats. It speaks stdio, so we bridge to the remote HTTP server with [`mcp-remote`](https://www.npmjs.com/package/mcp-remote) (requires Node.js on PATH).
246
+
[`mcptools`](https://posit-dev.github.io/mcptools/) is an MCP client for R that plugs MCP tools into `ellmer` chats. Its client (`mcp_tools()`) speaks **stdio only** — direct HTTP-transport support was proposed in [posit-dev/mcptools#88](https://github.com/posit-dev/mcptools/issues/88) but deliberately deferred (the maintainers still [recommend `mcp-remote`](https://posit-dev.github.io/mcptools/reference/client.html#connecting-to-remote-http-servers)). So we bridge to the remote HTTP server with [`mcp-remote`](https://www.npmjs.com/package/mcp-remote) (requires Node.js on PATH).
0 commit comments