Skip to content

Connect an AI tool

Mostly Right runs a Model Context Protocol server at https://mostlyright.md/api/mcp. It speaks Streamable HTTP and negotiates protocol revisions 2025-11-25, 2025-06-18, 2025-03-26 and 2024-11-05. It is stateless: it issues no session id, so every request stands alone.

Searching the catalogue, reading schemas and pulling sample rows need no account. Signing in adds querying whole tables at 10,000 rows a page and downloading a full Parquet snapshot. A paid workspace adds building: write a recipe, run it, publish the table it produces.

Claude Code adds it from the shell.

Terminal window
claude mcp add --transport http mostlyright https://mostlyright.md/api/mcp

Claude Desktop takes it under Settings → Connectors → Add custom connector, pasting https://mostlyright.md/api/mcp.

Cursor reads ~/.cursor/mcp.json:

{
"mcpServers": {
"mostlyright": {
"url": "https://mostlyright.md/api/mcp"
}
}
}

VS Code reads .vscode/mcp.json:

{
"servers": {
"mostlyright": {
"type": "http",
"url": "https://mostlyright.md/api/mcp"
}
}
}

ChatGPT takes the same URL under Settings → Connectors → Add custom connector. Its connector contract requires two tools with exact names and shapes, and this server implements both. search takes a query and returns {results: [{id, title, url}]} with the dataset slug as the id. fetch takes that id and returns {id, title, text, url, metadata} with the dataset as Markdown in text. Both are anonymous. From the OpenAI Responses API, pass the server as a tool: {"type": "mcp", "server_label": "mostlyright", "server_url": "https://mostlyright.md/api/mcp"}.

Any other client handshakes by hand:

Terminal window
curl -sS https://mostlyright.md/api/mcp \
-H "content-type: application/json" \
-H "accept: application/json, text/event-stream" \
-H "mcp-protocol-version: 2025-06-18" \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"curl","version":"1"}}}'
Tool What it answers
search The ChatGPT connector search. A query in, up to 20 {id, title, url} results out.
fetch The ChatGPT connector fetch. One dataset by slug, as Markdown, with its metadata.
search_datasets Full-text search over the public catalogue, with cursor pagination.
get_dataset One dataset: summary, publisher, licence, tables, column schemas, canonical URL.
list_tables The tables and their ids. The cheap call.
get_table_schema Columns, types, published profiles, and the capabilities the publisher turned on.
sample_rows The publisher’s materialized preview, up to 100 real rows. A sample, not a query.
get_download_instructions The URL, method and header for the full Parquet snapshot.
get_access_instructions What needs an account, the key classes, and the numbered steps to get one.

A dataset://{slug} resource returns one dataset as Markdown, and a mostlyright://access resource explains access. The prompts are explore_dataset, find_dataset and build_dataset.

Tool What it does
query_table A bounded structured query: columns, filters, order, group_by, aggregates, up to 10,000 rows a page, with offset to the end of the table. Filters cover equality, ranges (between) and text (contains, starts_with, ends_with). No SQL.
catalog_search Searches a fixed snapshot of public data sources for feeds that might answer a question. It swept one provider, Data.gov, and only partially, so a hit is a lead and a miss proves nothing. Every ranked candidate comes back with its disposition, and the tool scores none of them. Each includes the catalogue’s own facts: bounding boxes or null, and a record URL only where the entry names one. About one in four do not, and the tool invents nothing. format is one lowercase token sent once, because the catalogue takes a single format filter per question and has no OR. An entry that does not declare the required format comes back refused with filters_match: false rather than held back. The tool checks question, limit and format before it calls, so it rejects a malformed argument itself. One that reached the catalogue another way can surface as its 422 rather than as CATALOG_SEARCH_INVALID.
list_connected_datasets The datasets this workspace has connected, which is what query_table can read.
connect_dataset Connects a public dataset to the workspace. Needs the Owner, Admin or Editor role and an OAuth connection.

OAuth is one of two routes, and it pastes no key anywhere. A client that speaks OAuth 2.1 finds the authorization server in the WWW-Authenticate header on the first refusal, registers itself, and opens a browser. You sign in there and pick one workspace. The client holds workspace-scoped credentials rather than an API key, and the server rechecks membership on every request. Settings → Access manages and revokes them.

A workspace key is the other route. Sign in, create an mr_use_… key under Settings → Access, and send it as Authorization: Bearer mr_use_…. The first query_table over a dataset connects it to the workspace. Scripted MCP clients with no browser take this route.

An mr_use_ key only reads: query_table over any public dataset and over the workspace’s own tables, plus list_connected_datasets. It never needs connect_dataset, because its first query over a public dataset connects it. An OAuth connection carries only the scopes the person approved. datasets:read reads datasets the workspace has already used. datasets:use lets it use new public datasets, on first query or through connect_dataset. datasets:build also reads the workspace’s own tables.

An mr_live_… key is a different credential. It authenticates the Hosted Table API for tables your own workspace publishes, and the MCP server says so rather than refusing silently.

A paid workspace builds datasets through the same server, at $49 a month per workspace. It needs three things: an OAuth connection that approved the datasets:build scope, the Owner, Admin or Editor role in that workspace, and an active subscription. An mr_use_ key cannot build. Every build tool refuses it by name and points at the OAuth route. A workspace with no subscription gets subscription_required and the billing URL.

Tools What they do
create_dataset, update_dataset, list_my_datasets, get_my_dataset The dataset page: create one, rename it, write its description, read its tables and their state.
register_recipe Send one recipe document. Returns with recipe_id, recipe_digest, dataset_id, table_id and source_ids.
start_run, confirm_run, cancel_run Start a run in sample, full, refresh or backfill mode with row and byte ceilings, settle a run held for a confirmation, or stop one. A backfill covers one exact window and rejects without a window of {start, end}.
get_run, run_events, list_runs Read a run’s record, page its events until it is terminal, and find runs you did not start.
query_run One bounded read-only SQL statement over a finished run’s own table, where the relation is named run_table.
run_artifacts, get_artifact_download What the run wrote, and a signed URL for one artifact. Bytes never stream through MCP.
write_note One cell of the decision record, on the dataset or on the run.
promote_table, get_table Publish a table, and read what one is serving.
list_source_credentials The names of the secrets the workspace holds. Never a value.

The loop runs in this order:

  1. Read the build guide and the recipe reference.
  2. Propose sources.
  3. Write a recipe with a description on every column and units on every numeric column.
  4. register_recipe.
  5. start_run in full mode within the user’s authorization.
  6. run_events until the run is terminal.
  7. query_run to check the rows.
  8. write_note for each decision.
  9. Repair the recipe and register it again only when failure evidence identifies a recipe defect.

Full builds use progressive acquisition automatically. A long build exposes an inspection checkpoint after about five minutes and continues in the same run without a second approval or acquisition. Any required spend confirmation happens before acquisition. Use sample only for a deliberately bounded experiment; a row ceiling does not limit acquisition time. The first successful run makes the table live automatically; promote_table records an authorized refresh cadence.

A person pastes credentials, never an agent. A source that needs an API key takes its value from Settings → Secrets in the dashboard. The Source credentials panel on the dataset’s page does the same. Either one stores the value under a name, and the recipe names that name. No secret passes through MCP or through a chat message.

Four resources carry the rest:

  • mostlyright://build-guide walks the whole loop with the tool names, and also ships as an agent skill at /.well-known/agent-skills/mostlyright-build/SKILL.md.
  • mostlyright://recipe-reference lists every member of the recipe document, every bound, every refusal.
  • mostlyright://recipe-connections covers using a saved workspace connection instead of a named secret.
  • mostlyright://recipes/examples holds six recipe documents that ran, each readable at mostlyright://recipes/examples/{name}.

The build_dataset prompt walks the same loop for a question you give it.

Every refusal names what to do next. An anonymous caller reaching query_table gets a 401. Its body carries the signup URL, the header format and the numbered steps, the same content get_access_instructions returns. A client that speaks OAuth turns that 401 into a sign-in prompt. One that does not shows the instructions to the model.

Bytes never stream through MCP. get_download_instructions returns the one request that fetches a complete Parquet snapshot, and you make that request yourself. Applications, scripts and notebooks use the Public Dataset API directly.

  • /.well-known/oauth-protected-resource/api/mcp carries the ratified protected-resource metadata.
  • /.well-known/mcp.json is a server card for registries and directory crawlers, listing the anonymous, authenticated and build tools.
  • /.well-known/agent-skills/index.json lists the agent skills this deployment publishes.
  • /llms.txt and /llms-full.txt carry the catalogue as plain text.
  • /datasets/{slug}/llms.txt carries one dataset as Markdown.