ABS Data (observed)

io.github.SamJB123Published by GitHub account SamJB123v0.1.0Updated Oct 24, 2026

Australian Bureau of Statistics data: 1,227 tables, offering only options confirmed to serve data

VerifiedStreamable HTTPWeb executableDatabasesData & Analytics

Overview

AI-generated overview

Unofficial read-only connector to Australian Bureau of Statistics data: search 1,227 ABS tables, inspect their dimensions, and fetch live observations.

What you can do
  • Search ABS statistical tables by topic words, geography level or frequency, including matches on option labels inside dimensions.
  • Describe a table's dimensions in key order, coverage dates and observed options.
  • Find option codes by label text within one dimension, such as a suburb in a geography dimension.
  • Fetch observations for a selection of dimension codes or labels, with a row cap and most-recent-first ordering.
  • Run JavaScript in a sandbox over the whole catalogue, or over a client with the same four data verbs, for multi-series or multi-table questions.
When to use it
Useful when you need Australian official statistics — CPI, census, geography-level series — and want the assistant to find the right table, pull real values and explain caveats. Good for questions that mix several series or tables, which the sandboxed search and execute tools handle.
Requirements
A remote MCP endpoint added as a connector; no account, API key or local runtime. Network access to the service, which calls the ABS Data API live. Rate limits apply per client address, including a lower cap on data pulls and on sandboxed execute.
Before you install
Read-only and anonymous, but data pulls are live ABS calls and are rate limited, so heavy use returns errors. The sandboxed execute tool runs JavaScript, though with no network access. It is an independent, unofficial connector, not an ABS product; verify figures against ABS sources before relying on them.

Installation

In SourceWeft

  1. Open ABS Data (observed) in the dashboard and add it to a workspace.
  2. Enable the server for the chats that should use its tools.

Web executable via Streamable HTTP. Remote servers run from the web runtime once configured in a workspace.

Use with an agent

Paste this prompt into Claude Code, Codex, Cursor or another agent — or into SourceWeft chat. The agent reads this server's install guide, shows you its publisher, endpoint and sign-in, and adds it to your client once you agree. Keys never go into a command.

Read https://sourceweft.com/mcp/io.github.SamJB123%2Fabs-data/install.md and connect the MCP server it describes to my client. Before changing any config, show me its publisher, endpoint and sign-in method and wait for my OK. Never put an API key in a command or in a file I would commit; ask me where it should come from.

Read the install guide the agent follows

Other MCP clients

Add this to your client's mcpServers config.

{
  "mcpServers": {
    "abs-data-observed": {
      "type": "http",
      "url": "https://abs-data-front-door.aicolab.workers.dev/mcp"
    }
  }
}

README

ABS Data — Australian Bureau of Statistics data for your AI assistant

Ask the question you actually have. Add one web address to Claude, ChatGPT or Copilot, say what you care about, and your assistant finds the right ABS data, fetches it live, and explains it — with its caveats. Free, anonymous and read-only.

Start here: https://abs-data-front-door.aicolab.workers.dev — setup guides for each AI tool, things to ask, and a feedback form.

An independent, unofficial connector — not an ABS product, and not endorsed by the Australian Bureau of Statistics. Data: ABS, CC BY 4.0.

Front door — setup guides for Claude, ChatGPT, Copilot and developer tools; feedback formhttps://abs-data-front-door.aicolab.workers.dev
Add to claude.ai / any MCP client as a connectorhttps://abs-data-front-door.aicolab.workers.dev/mcp (registry name io.github.SamJB123/abs-data)
MCP (2026-07-28 stateless; 2025 revisions served too) — 4 fixed verbs, Code Mode search/execute, feedback_form/submit_feedback, MCP Apps views (chart, feedback form), resources, prompts, completionhttps://abs-data-front-door.aicolab.workers.dev/mcp
Feedback from the web form (the same operation as submit_feedback)POST https://abs-data-front-door.aicolab.workers.dev/api/feedback
RPC door (Cap'n Web; what @abs/sdk speaks — HTTP batch or WebSocket)https://abs-data-front-door.aicolab.workers.dev/rpc
HTTP APIhttps://abs-data-front-door.aicolab.workers.dev/api/tables
Scalar referencehttps://abs-data-front-door.aicolab.workers.dev/api/docs
OpenAPIhttps://abs-data-front-door.aicolab.workers.dev/api/openapi.json
The menu as one documenthttps://abs-data-front-door.aicolab.workers.dev/api/catalogue.json
Live browsable cataloguehttps://abs-data-front-door.aicolab.workers.dev/catalogue
Static snapshot (claude.ai artifact)https://claude.ai/code/artifact/d0635dee-5cce-40dd-bfa2-7ab7dd03e5bd

How it was built

Before offering anything, this project measured what the ABS Data API actually serves — every dataflow, by retrieval — so the connector only ever offers tables and options that have data, and a selection that doesn't exist comes back with the ones that do.

[The ABS Data API cartography: unmasking the 4.21% reality — a comic-style map of the findings and the front door]

  • 621,536,714 series exist across 1,227 dataflows. ABS's metadata implies 14,761,557,818 — a density of 4.21%, overstated 23.8×.
  • The per-dimension metadata is exact: every advertised option exists, and nothing real is unadvertised. The entire overstatement is combinatorial.
  • 81.0% of series sit in flows whose sparsity is principled and verified in the keys — almost entirely hierarchical geography encoded as separate dimensions (STATE is a function of REGION in 664 flows). The remaining 19% is not reducible to a single-dimension dependency.
  • detail=serieskeysonly — the endpoint that should answer "what exists" — is broken (HTTP 200, malformed JSON). Eight of fifteen documented behaviours do not conform. See reports/.

DESIGN.md is the full record: verified API findings, every decision and why, and the corrections made along the way.

Layout

packages/schema      Drizzle schema (declared vs observed, never joined), shared CSV/period parsingpackages/crawler     structural crawl, observed probe, conformance suite, sizingpackages/catalogue   delta report, sparsity analysis, contract generator, OpenAPI emitter, artifactpackages/contract    the corrected contract: stable Zod verbs + generated tables/options (per crawl)packages/worker      the front door: MCP door (SDK v2) + HTTP door (oRPC + Scalar) + RPC door (Cap'n Web) on one Worker;                     src/ops/ holds new operations in @aicolab/app-kit's shape (DESIGN.md decision 34)packages/site        the pages (landing, /admin) and the MCP App views — Solid 2 + @aicolab/ui-solid, built by Vitepackages/ui-solid    git submodule: the AI CoLab component system (public); packages/solid is its Solid helperspackages/sdk         @abs/sdk — typed TypeScript client over the RPC door (connect(), invalidSelectionFromError())tooling/tsconfig     copy of the portal's shared tsconfigs, for the submodules (deleted at the move to app-kit)reports/             delta.md/json, sparsity.md/json, openapi.json, abs-report-draft.mddata/                local SQLite catalogue (~400GB), raw archive, D1 import SQL — not committed

Running it

sh
git submodule update --init           # packages/ui-solid and packages/solidpnpm installpnpm db:migrate                       # local SQLite at data/abs-catalogue.sqlite
pnpm crawl:structural                 # 7 bulk requests, ~1 minutepnpm crawl:probe -- '--key-values=*'  # every dataflow by retrieval; ~24h, 20GB heap for the giantspnpm report:delta && pnpm report:sparsitypnpm generate:contract                # -> packages/contract/src/generated, data/d1/*.sqlpnpm --filter @abs/catalogue emit:openapi
cd packages/workernpx wrangler d1 execute abs-catalogue --local --file=../../data/d1/00-schema.sql   # then each data filenpx wrangler d1 execute abs-catalogue --local --file=../schema/migrations/0003_public_micromacro.sql  # feedback tableprintf 'FEEDBACK_ADMIN_TOKEN=local-dev-token\nFEEDBACK_SALT=local-dev-salt\n' > .dev.vars  # gitignoredpnpm dev                                                                           # builds packages/site, then wrangler devABS_MCP_URL=http://localhost:8787/mcp FEEDBACK_ADMIN_TOKEN=local-dev-token npx tsx test/protocol.ts  # ~80 checks, all doorspnpm --filter @abs/sdk example                                                     # the SDK against productionABS_URL=http://localhost:8787 npx tsx test/limits.ts                               # rate limits (spends a minute's budget)
# The in-chat views in a dev-only MCP Apps host, against the local Worker:pnpm --filter @abs/site dev            # then http://localhost:5180/harness.html

Deploying (pnpm run deploy in packages/worker builds the site first — not pnpm deploy, which is pnpm's own command) needs, once: the feedback migration on the remote D1 (wrangler d1 execute abs-catalogue --remote --file=../schema/migrations/0003_public_micromacro.sql) and two secrets (wrangler secret put FEEDBACK_ADMIN_TOKEN, wrangler secret put FEEDBACK_SALT).

Rate limits, per client address: 600 requests/min overall; 120/min for data pulls (each is a live ABS call); 20/min for Code Mode execute; 5/min for feedback (and 20 per day, in D1). Over the limit, HTTP doors answer 429 with Retry-After; an MCP tool call gets a tool error.

Using the SDK from TypeScript:

ts
import { connect, invalidSelectionFromError } from "@abs/sdk";
const abs = connect(); // https://abs-data-front-door.aicolab.workers.dev/rpcconst [hits, cpi] = await Promise.all([          // one HTTP batch  abs.searchTables({ query: "cpi rent", limit: 3 }),  abs.describeTable("CPI"),]);try {  const data = await abs.getData({ table: "CPI", select: { REGION: "Sydney", INDEX: "10001" }, lastN: 4 });} catch (err) {  const invalid = invalidSelectionFromError(err); // typed: reason, dimension, validOptions from observation}

The probe is resumable: rerunning skips flows already recorded as complete. For the largest census flows run with NODE_OPTIONS=--max-old-space-size=20480.

Gotcha: wrangler keys the local D1 state by database_id. Changing the id in wrangler.toml (e.g. after d1 create) gives wrangler dev a fresh, empty local database — re-run the local import.

Principles

  • Observed-only. Declared metadata steers the crawler and feeds the defect report; it never enters what a caller sees. Availability is empirical: observed options from the catalogue, exact combination checks live against ABS's availableconstraint (which agrees with the probe on every dimension of every flow).
  • One contract, derived doors. Every crawl regenerates one contract; the MCP verbs, the HTTP routes, the OpenAPI document and the docs all read or render it. Nothing hardcodes the surface, so nothing drifts.
  • Only calls that return data. A selection is verified before it is forwarded; an invalid one comes back with the observed valid options — as an MRTR input_required on MCP, a typed 422 on HTTP.

Source: README.md at commit f6ce923

Tools

6

search_tables

Search ABS tables

Find ABS statistical tables (dataflows) by topic words, geography level or frequency. Matches table names, topics and dimension names, and also option labels inside dimensions — a search for 'rent' finds CPI through its INDEX option 'Rents' and reports the match in matchedOptions. Census tables published at several geography levels are collapsed to one result with familyGeographies listing the others (use the geography filter to pick one). Every result is confirmed to serve data — nothing here comes from documentation alone. Start here, then describe_table.

Parameterslimitqueryfrequencygeography

describe_table

Describe a table

A table's dimensions in key order, its coverage dates, and its observed options. Dimensions with up to 64 options list them inline with labels; larger ones (geography, occupations) say how many and are searchable with search_options.

Parameterstablerequired

search_options

Search a dimension's options

Find option codes by label text within one dimension of one table — e.g. a suburb name in a geography dimension. Returns codes to use in get_data's select.

Parameterslimitqueryrequiredtablerequireddimensionrequired

get_data

Get data

Fetch observations. `select` maps dimension ids to option codes or labels (several allowed); omitted dimensions match everything. The selection is verified live against ABS before fetching, so a call that succeeds always returns real data. Returns up to maxRows observations (default 500, most recent 12 periods per series unless a period range is given), a summary, and the URL for the complete pull. If an option or combination does not exist you are asked to choose from the valid ones.

ParameterslastNtablerequiredfirstNselectmaxRowsendPeriodstartPeriod

execute

Fetch and analyse data by writing code

Run JavaScript in an isolated sandbox whose only capability is `abs`, a client with the same four verbs as this server (searchTables, describeTable, searchOptions, getData) and the same guarantees: every selection is verified against ABS before fetching. Use it for multi-series or multi-table analysis — fetch several series, compute growth rates, rank capitals, join tables — and `return` only the computed result, so large payloads never reach the conversation. No network beyond `abs`. Budgets: 10s CPU, 50 calls, 25s wall clock, 200KB result. Your code runs inside an async function; use `await`; console.log is captured. // The `abs` object available in execute(): interface Abs { searchTables(input: { query?: string; geography?: string; frequency?: "A"|"S"|"Q"|"M"|"W"|"D"; limit?: number }): Promise<{ results: TableSummary[]; total: number }>; describeTable(table: string): Promise<{ table: TableSummary; dimensions: { id: string; position: number; optionCount: number; options?: { code: string; label: string|null }[] }[]; keyFormat: string }>; searchOptions(input: { table: string; dimension: string; query: string; limit?: number }): Promise<{ options: { code: string; label: string|null; parent?: string|null }[]; total: number }>; getData(input: { table: string; select?: Record<string, string|string[]>; startPeriod?: string; endPeriod?: string; lastN?: number; firstN?: number; maxRows?: number }): Promise<{ key: string; rows: { series: string; period: string; value: number|null; unit?: string|null }[]; rowsReturned: number; truncated: boolean; seriesMatched: number; fullDataUrl: string }>; } interface TableSummary { id: string; name: string|null; seriesCount: number; frequencies: string[]; coverage: { from: string|null; to: string|null }; dimensions: string[]; family: string|null; geography: string|null; matchedOptions?: { dimension: string; code: string; label: string|null }[] } // getData throws an Error whose message is JSON: { reason, dimensi

Parameterscoderequired

Version history

1
  1. v0.1.0LatestSep 16, 2026

Score

90% of the applicable checks met

How complete this listing is, computed from the registry entry, the endpoint and public sources. Not a rating of the server and not a statement that it is safe or working today: reachability is a dated check, and no installs or reviews go into the score.

  • Reached in the last 30 daysRequired25 pts
  • Auth declaredRequired10 pts
  • Tools listedRequired15 pts
  • DescriptionRequired15 pts
  • Logo10 pts
  • Documentation15 pts
  • License10 pts

    Declare a license in the repository or the registry entry.