abs-mcp 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- abs_mcp-0.1.0/.github/workflows/test.yml +47 -0
- abs_mcp-0.1.0/.gitignore +24 -0
- abs_mcp-0.1.0/CHANGELOG.md +12 -0
- abs_mcp-0.1.0/LICENSE +21 -0
- abs_mcp-0.1.0/PKG-INFO +188 -0
- abs_mcp-0.1.0/README.md +156 -0
- abs_mcp-0.1.0/examples/claude_desktop_config.json +8 -0
- abs_mcp-0.1.0/glama.json +4 -0
- abs_mcp-0.1.0/pyproject.toml +56 -0
- abs_mcp-0.1.0/src/abs_mcp/__init__.py +1 -0
- abs_mcp-0.1.0/src/abs_mcp/cache.py +87 -0
- abs_mcp-0.1.0/src/abs_mcp/catalog.py +129 -0
- abs_mcp-0.1.0/src/abs_mcp/client.py +109 -0
- abs_mcp-0.1.0/src/abs_mcp/curated.py +189 -0
- abs_mcp-0.1.0/src/abs_mcp/data/curated/ABS_ANNUAL_ERP_ASGS2021.yaml +49 -0
- abs_mcp-0.1.0/src/abs_mcp/data/curated/BA_GCCSA.yaml +71 -0
- abs_mcp-0.1.0/src/abs_mcp/data/curated/CPI.yaml +56 -0
- abs_mcp-0.1.0/src/abs_mcp/data/curated/JV.yaml +76 -0
- abs_mcp-0.1.0/src/abs_mcp/data/curated/LEND_HOUSING.yaml +72 -0
- abs_mcp-0.1.0/src/abs_mcp/data/curated/LF.yaml +54 -0
- abs_mcp-0.1.0/src/abs_mcp/data/curated/WPI.yaml +79 -0
- abs_mcp-0.1.0/src/abs_mcp/models.py +53 -0
- abs_mcp-0.1.0/src/abs_mcp/py.typed +0 -0
- abs_mcp-0.1.0/src/abs_mcp/server.py +237 -0
- abs_mcp-0.1.0/src/abs_mcp/shaping.py +252 -0
- abs_mcp-0.1.0/tests/__init__.py +0 -0
- abs_mcp-0.1.0/tests/conftest.py +16 -0
- abs_mcp-0.1.0/tests/fixtures/dataflows_min.xml +44927 -0
- abs_mcp-0.1.0/tests/fixtures/lf_dsd.xml +1731 -0
- abs_mcp-0.1.0/tests/fixtures/lf_one_obs.xml +1 -0
- abs_mcp-0.1.0/tests/test_cache.py +51 -0
- abs_mcp-0.1.0/tests/test_catalog.py +83 -0
- abs_mcp-0.1.0/tests/test_client.py +74 -0
- abs_mcp-0.1.0/tests/test_curated.py +123 -0
- abs_mcp-0.1.0/tests/test_integration.py +190 -0
- abs_mcp-0.1.0/tests/test_mcp_protocol.py +105 -0
- abs_mcp-0.1.0/tests/test_shaping.py +141 -0
- abs_mcp-0.1.0/uv.lock +1959 -0
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
name: tests
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
test:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
strategy:
|
|
13
|
+
fail-fast: false
|
|
14
|
+
matrix:
|
|
15
|
+
python-version: ["3.11", "3.12", "3.13"]
|
|
16
|
+
steps:
|
|
17
|
+
- uses: actions/checkout@v4
|
|
18
|
+
- name: Install uv
|
|
19
|
+
uses: astral-sh/setup-uv@v3
|
|
20
|
+
with:
|
|
21
|
+
enable-cache: true
|
|
22
|
+
- name: Set up Python ${{ matrix.python-version }}
|
|
23
|
+
run: uv python install ${{ matrix.python-version }}
|
|
24
|
+
- name: Sync dependencies
|
|
25
|
+
run: uv sync --extra dev
|
|
26
|
+
- name: Install package
|
|
27
|
+
run: uv pip install -e .
|
|
28
|
+
- name: Run unit tests
|
|
29
|
+
run: uv run pytest -q
|
|
30
|
+
|
|
31
|
+
build:
|
|
32
|
+
runs-on: ubuntu-latest
|
|
33
|
+
needs: test
|
|
34
|
+
steps:
|
|
35
|
+
- uses: actions/checkout@v4
|
|
36
|
+
- name: Install uv
|
|
37
|
+
uses: astral-sh/setup-uv@v3
|
|
38
|
+
- name: Build wheel + sdist
|
|
39
|
+
run: uv build
|
|
40
|
+
- name: Verify wheel installs cleanly
|
|
41
|
+
run: |
|
|
42
|
+
uv run --isolated --with ./dist/*.whl python -c \
|
|
43
|
+
"import abs_mcp.server as s; assert len(s.list_curated()) == 5; print('OK')"
|
|
44
|
+
- uses: actions/upload-artifact@v4
|
|
45
|
+
with:
|
|
46
|
+
name: dist
|
|
47
|
+
path: dist/
|
abs_mcp-0.1.0/.gitignore
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
__pycache__/
|
|
2
|
+
*.pyc
|
|
3
|
+
*.pyo
|
|
4
|
+
*.egg-info/
|
|
5
|
+
.eggs/
|
|
6
|
+
build/
|
|
7
|
+
dist/
|
|
8
|
+
.venv/
|
|
9
|
+
.pytest_cache/
|
|
10
|
+
.ruff_cache/
|
|
11
|
+
.mypy_cache/
|
|
12
|
+
.coverage
|
|
13
|
+
htmlcov/
|
|
14
|
+
*.swp
|
|
15
|
+
.DS_Store
|
|
16
|
+
.env
|
|
17
|
+
|
|
18
|
+
# Inspection-only DSDs (regenerate with: curl -H "Accept: application/vnd.sdmx.structure+xml;version=2.1" https://data.api.abs.gov.au/rest/datastructure/ABS/<ID>?references=all)
|
|
19
|
+
tests/fixtures/ABS_ANNUAL_ERP_ASGS2021_dsd.xml
|
|
20
|
+
tests/fixtures/BA_GCCSA_dsd.xml
|
|
21
|
+
tests/fixtures/CPI_dsd.xml
|
|
22
|
+
tests/fixtures/JV_dsd.xml
|
|
23
|
+
tests/fixtures/LEND_HOUSING_dsd.xml
|
|
24
|
+
tests/fixtures/WPI_dsd.xml
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.1.0 (2026-05-11)
|
|
4
|
+
|
|
5
|
+
Initial release.
|
|
6
|
+
|
|
7
|
+
- 5 MCP tools: `search_datasets`, `describe_dataset`, `get_data`, `latest`, `list_curated`
|
|
8
|
+
- Hand-curated plain-English mappings for 5 dataflows: `LF` (Labour Force), `CPI` (Consumer Price Index), `ABS_ANNUAL_ERP_ASGS2021` (Estimated Resident Population), `BA_GCCSA` (Building Approvals), `LEND_HOUSING` (Lending Indicators - Housing)
|
|
9
|
+
- Non-curated dataflows still queryable via raw SDMX dimension IDs and codes
|
|
10
|
+
- SQLite-backed cache with per-kind TTL: catalogue 24 h, codelists 7 d, data 1 h, latest 15 min
|
|
11
|
+
- Response shapes: `records` (default), `series`, `csv`
|
|
12
|
+
- 50 tests (35 unit + 7 live integration + 8 MCP-protocol)
|
abs_mcp-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Harry Vass
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
abs_mcp-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: abs-mcp
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: MCP server for the Australian Bureau of Statistics Data API. Hides SDMX behind plain-English tools, with curated mappings for Labour Force, CPI, ERP, Building Approvals, and Lending Indicators.
|
|
5
|
+
Project-URL: Homepage, https://github.com/Bigred97/abs-mcp
|
|
6
|
+
Project-URL: Issues, https://github.com/Bigred97/abs-mcp/issues
|
|
7
|
+
Author: Harry Vass
|
|
8
|
+
License: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Keywords: abs,australia,claude,mcp,sdmx,statistics
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
17
|
+
Classifier: Topic :: Scientific/Engineering :: Information Analysis
|
|
18
|
+
Requires-Python: >=3.11
|
|
19
|
+
Requires-Dist: aiosqlite>=0.20
|
|
20
|
+
Requires-Dist: fastmcp<4,>=2.0
|
|
21
|
+
Requires-Dist: httpx>=0.27
|
|
22
|
+
Requires-Dist: pandas<3,>=2.2
|
|
23
|
+
Requires-Dist: pydantic>=2.7
|
|
24
|
+
Requires-Dist: pyyaml>=6.0
|
|
25
|
+
Requires-Dist: rapidfuzz>=3.9
|
|
26
|
+
Requires-Dist: sdmx1>=2.20
|
|
27
|
+
Provides-Extra: dev
|
|
28
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
|
|
29
|
+
Requires-Dist: pytest>=8; extra == 'dev'
|
|
30
|
+
Requires-Dist: respx>=0.21; extra == 'dev'
|
|
31
|
+
Description-Content-Type: text/markdown
|
|
32
|
+
|
|
33
|
+
# abs-mcp
|
|
34
|
+
|
|
35
|
+
An MCP server that wraps the [Australian Bureau of Statistics Data API](https://data.api.abs.gov.au/) and hides SDMX behind plain-English tools. Ask Claude "What's the unemployment rate in NSW?" and get a real answer with a source link, instead of a wall of SDMX codes.
|
|
36
|
+
|
|
37
|
+
Five tools, hand-curated mappings for Labour Force, CPI, Estimated Resident Population, Building Approvals, and Lending Indicators.
|
|
38
|
+
|
|
39
|
+
## Install
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
# After publish:
|
|
43
|
+
uvx abs-mcp
|
|
44
|
+
|
|
45
|
+
# Local dev install:
|
|
46
|
+
uv pip install -e .
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### Claude Desktop
|
|
50
|
+
|
|
51
|
+
Add to `~/Library/Application Support/Claude/claude_desktop_config.json`:
|
|
52
|
+
|
|
53
|
+
```json
|
|
54
|
+
{
|
|
55
|
+
"mcpServers": {
|
|
56
|
+
"abs": {
|
|
57
|
+
"command": "uvx",
|
|
58
|
+
"args": ["abs-mcp"]
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
For a local checkout (before PyPI publish):
|
|
65
|
+
|
|
66
|
+
```json
|
|
67
|
+
{
|
|
68
|
+
"mcpServers": {
|
|
69
|
+
"abs": {
|
|
70
|
+
"command": "uv",
|
|
71
|
+
"args": ["run", "--directory", "/absolute/path/to/abs-mcp", "abs-mcp"]
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Restart Claude Desktop. The `abs` server appears in the tools panel with five tools.
|
|
78
|
+
|
|
79
|
+
### Cursor
|
|
80
|
+
|
|
81
|
+
Add to `~/.cursor/mcp.json` (or workspace `.cursor/mcp.json`):
|
|
82
|
+
|
|
83
|
+
```json
|
|
84
|
+
{
|
|
85
|
+
"mcpServers": {
|
|
86
|
+
"abs": {
|
|
87
|
+
"command": "uvx",
|
|
88
|
+
"args": ["abs-mcp"]
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## Tools
|
|
95
|
+
|
|
96
|
+
| Tool | What it does |
|
|
97
|
+
|---|---|
|
|
98
|
+
| `search_datasets(query, limit=10)` | Fuzzy-search ABS dataflow names. Returns the top matches. |
|
|
99
|
+
| `describe_dataset(dataset_id)` | Plain-English description of a dataflow's dimensions and values. |
|
|
100
|
+
| `get_data(dataset_id, filters, start_period, end_period, format)` | Query a dataflow with filters. Returns clean records (default), grouped series, or CSV. |
|
|
101
|
+
| `latest(dataset_id, filters)` | Just the most recent observation(s) — wraps `get_data` with `lastNObservations=1`. |
|
|
102
|
+
| `list_curated()` | The five dataflow IDs that have hand-curated plain-English support. |
|
|
103
|
+
|
|
104
|
+
## Curated dataflows
|
|
105
|
+
|
|
106
|
+
For these five, `filters` accepts plain-English values (e.g. `"region": "nsw"` instead of `"REGION": "1"`):
|
|
107
|
+
|
|
108
|
+
- **LF** — Labour Force, monthly: employment, unemployment, participation by state/sex
|
|
109
|
+
- **CPI** — Consumer Price Index, quarterly inflation by capital city and category
|
|
110
|
+
- **ABS_ANNUAL_ERP_ASGS2021** — Estimated Resident Population, annual by state and sub-state geography
|
|
111
|
+
- **BA_GCCSA** — Building Approvals, monthly by state/capital region and building type
|
|
112
|
+
- **LEND_HOUSING** — Lending Indicators, monthly housing finance commitments by purpose, lender, and state
|
|
113
|
+
|
|
114
|
+
Any other ABS dataflow still works — pass raw SDMX dimension IDs and codes.
|
|
115
|
+
|
|
116
|
+
## Worked examples
|
|
117
|
+
|
|
118
|
+
**"What's the current unemployment rate in NSW?"**
|
|
119
|
+
|
|
120
|
+
Claude calls:
|
|
121
|
+
```
|
|
122
|
+
latest(dataset_id="LF", filters={"region": "nsw", "measure": "unemployment_rate"})
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Returns:
|
|
126
|
+
```json
|
|
127
|
+
{
|
|
128
|
+
"dataset_id": "LF",
|
|
129
|
+
"dataset_name": "Labour Force",
|
|
130
|
+
"query": {"region": "nsw", "measure": "unemployment_rate"},
|
|
131
|
+
"period": {"start": "2026-03", "end": "2026-03"},
|
|
132
|
+
"records": [
|
|
133
|
+
{
|
|
134
|
+
"period": "2026-03",
|
|
135
|
+
"value": 4.2,
|
|
136
|
+
"dimensions": {"measure": "Unemployment rate", "region": "New South Wales", "sex": "Persons"}
|
|
137
|
+
}
|
|
138
|
+
],
|
|
139
|
+
"source": "Australian Bureau of Statistics",
|
|
140
|
+
"abs_url": "https://www.abs.gov.au/statistics/labour/employment-and-unemployment/labour-force-australia"
|
|
141
|
+
}
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
**"Show me NSW housing approvals over the last two years"**
|
|
145
|
+
|
|
146
|
+
```
|
|
147
|
+
get_data(dataset_id="BA_GCCSA", filters={"region": "nsw", "measure": "dwelling_units"}, start_period="2024")
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
**"Compare quarterly CPI in Sydney vs Melbourne"**
|
|
151
|
+
|
|
152
|
+
```
|
|
153
|
+
get_data(dataset_id="CPI", filters={"region": ["sydney", "melbourne"], "measure": "change_year"}, start_period="2023")
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
## Period formats
|
|
157
|
+
|
|
158
|
+
ABS uses different period formats per dataflow:
|
|
159
|
+
- Monthly (LF, BA_GCCSA, LEND_HOUSING): `"2026-03"`
|
|
160
|
+
- Quarterly (CPI): `"2024-Q1"`
|
|
161
|
+
- Annual (ABS_ANNUAL_ERP_ASGS2021): `"2024"`
|
|
162
|
+
|
|
163
|
+
Pass `start_period` / `end_period` in the matching format.
|
|
164
|
+
|
|
165
|
+
## Development
|
|
166
|
+
|
|
167
|
+
```bash
|
|
168
|
+
git clone https://github.com/Bigred97/abs-mcp.git
|
|
169
|
+
cd abs-mcp
|
|
170
|
+
uv sync --extra dev
|
|
171
|
+
uv pip install -e .
|
|
172
|
+
|
|
173
|
+
# Unit tests (no network)
|
|
174
|
+
uv run pytest
|
|
175
|
+
|
|
176
|
+
# Live integration tests (hits real ABS API)
|
|
177
|
+
uv run pytest -m live
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
The SQLite cache lives at `~/.abs-mcp/cache.db`. Catalogue refreshes every 24h, codelists every 7 days, data responses every hour, latest 15 minutes. Delete the file to force a refresh.
|
|
181
|
+
|
|
182
|
+
## How it differs from existing ABS MCP servers
|
|
183
|
+
|
|
184
|
+
The one existing community option (`seansoreilly/abs`) exposes a single `query_dataset` tool that passes raw SDMX through. This package offers semantic tools and curated mappings for the highest-value dataflows so an LLM can answer real questions without you needing to know what `M13.3.1599.20.1.M` means.
|
|
185
|
+
|
|
186
|
+
## License
|
|
187
|
+
|
|
188
|
+
MIT — Harry Vass, 2026.
|
abs_mcp-0.1.0/README.md
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
# abs-mcp
|
|
2
|
+
|
|
3
|
+
An MCP server that wraps the [Australian Bureau of Statistics Data API](https://data.api.abs.gov.au/) and hides SDMX behind plain-English tools. Ask Claude "What's the unemployment rate in NSW?" and get a real answer with a source link, instead of a wall of SDMX codes.
|
|
4
|
+
|
|
5
|
+
Five tools, hand-curated mappings for Labour Force, CPI, Estimated Resident Population, Building Approvals, and Lending Indicators.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
# After publish:
|
|
11
|
+
uvx abs-mcp
|
|
12
|
+
|
|
13
|
+
# Local dev install:
|
|
14
|
+
uv pip install -e .
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
### Claude Desktop
|
|
18
|
+
|
|
19
|
+
Add to `~/Library/Application Support/Claude/claude_desktop_config.json`:
|
|
20
|
+
|
|
21
|
+
```json
|
|
22
|
+
{
|
|
23
|
+
"mcpServers": {
|
|
24
|
+
"abs": {
|
|
25
|
+
"command": "uvx",
|
|
26
|
+
"args": ["abs-mcp"]
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
For a local checkout (before PyPI publish):
|
|
33
|
+
|
|
34
|
+
```json
|
|
35
|
+
{
|
|
36
|
+
"mcpServers": {
|
|
37
|
+
"abs": {
|
|
38
|
+
"command": "uv",
|
|
39
|
+
"args": ["run", "--directory", "/absolute/path/to/abs-mcp", "abs-mcp"]
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Restart Claude Desktop. The `abs` server appears in the tools panel with five tools.
|
|
46
|
+
|
|
47
|
+
### Cursor
|
|
48
|
+
|
|
49
|
+
Add to `~/.cursor/mcp.json` (or workspace `.cursor/mcp.json`):
|
|
50
|
+
|
|
51
|
+
```json
|
|
52
|
+
{
|
|
53
|
+
"mcpServers": {
|
|
54
|
+
"abs": {
|
|
55
|
+
"command": "uvx",
|
|
56
|
+
"args": ["abs-mcp"]
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Tools
|
|
63
|
+
|
|
64
|
+
| Tool | What it does |
|
|
65
|
+
|---|---|
|
|
66
|
+
| `search_datasets(query, limit=10)` | Fuzzy-search ABS dataflow names. Returns the top matches. |
|
|
67
|
+
| `describe_dataset(dataset_id)` | Plain-English description of a dataflow's dimensions and values. |
|
|
68
|
+
| `get_data(dataset_id, filters, start_period, end_period, format)` | Query a dataflow with filters. Returns clean records (default), grouped series, or CSV. |
|
|
69
|
+
| `latest(dataset_id, filters)` | Just the most recent observation(s) — wraps `get_data` with `lastNObservations=1`. |
|
|
70
|
+
| `list_curated()` | The five dataflow IDs that have hand-curated plain-English support. |
|
|
71
|
+
|
|
72
|
+
## Curated dataflows
|
|
73
|
+
|
|
74
|
+
For these five, `filters` accepts plain-English values (e.g. `"region": "nsw"` instead of `"REGION": "1"`):
|
|
75
|
+
|
|
76
|
+
- **LF** — Labour Force, monthly: employment, unemployment, participation by state/sex
|
|
77
|
+
- **CPI** — Consumer Price Index, quarterly inflation by capital city and category
|
|
78
|
+
- **ABS_ANNUAL_ERP_ASGS2021** — Estimated Resident Population, annual by state and sub-state geography
|
|
79
|
+
- **BA_GCCSA** — Building Approvals, monthly by state/capital region and building type
|
|
80
|
+
- **LEND_HOUSING** — Lending Indicators, monthly housing finance commitments by purpose, lender, and state
|
|
81
|
+
|
|
82
|
+
Any other ABS dataflow still works — pass raw SDMX dimension IDs and codes.
|
|
83
|
+
|
|
84
|
+
## Worked examples
|
|
85
|
+
|
|
86
|
+
**"What's the current unemployment rate in NSW?"**
|
|
87
|
+
|
|
88
|
+
Claude calls:
|
|
89
|
+
```
|
|
90
|
+
latest(dataset_id="LF", filters={"region": "nsw", "measure": "unemployment_rate"})
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Returns:
|
|
94
|
+
```json
|
|
95
|
+
{
|
|
96
|
+
"dataset_id": "LF",
|
|
97
|
+
"dataset_name": "Labour Force",
|
|
98
|
+
"query": {"region": "nsw", "measure": "unemployment_rate"},
|
|
99
|
+
"period": {"start": "2026-03", "end": "2026-03"},
|
|
100
|
+
"records": [
|
|
101
|
+
{
|
|
102
|
+
"period": "2026-03",
|
|
103
|
+
"value": 4.2,
|
|
104
|
+
"dimensions": {"measure": "Unemployment rate", "region": "New South Wales", "sex": "Persons"}
|
|
105
|
+
}
|
|
106
|
+
],
|
|
107
|
+
"source": "Australian Bureau of Statistics",
|
|
108
|
+
"abs_url": "https://www.abs.gov.au/statistics/labour/employment-and-unemployment/labour-force-australia"
|
|
109
|
+
}
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
**"Show me NSW housing approvals over the last two years"**
|
|
113
|
+
|
|
114
|
+
```
|
|
115
|
+
get_data(dataset_id="BA_GCCSA", filters={"region": "nsw", "measure": "dwelling_units"}, start_period="2024")
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
**"Compare quarterly CPI in Sydney vs Melbourne"**
|
|
119
|
+
|
|
120
|
+
```
|
|
121
|
+
get_data(dataset_id="CPI", filters={"region": ["sydney", "melbourne"], "measure": "change_year"}, start_period="2023")
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
## Period formats
|
|
125
|
+
|
|
126
|
+
ABS uses different period formats per dataflow:
|
|
127
|
+
- Monthly (LF, BA_GCCSA, LEND_HOUSING): `"2026-03"`
|
|
128
|
+
- Quarterly (CPI): `"2024-Q1"`
|
|
129
|
+
- Annual (ABS_ANNUAL_ERP_ASGS2021): `"2024"`
|
|
130
|
+
|
|
131
|
+
Pass `start_period` / `end_period` in the matching format.
|
|
132
|
+
|
|
133
|
+
## Development
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
git clone https://github.com/Bigred97/abs-mcp.git
|
|
137
|
+
cd abs-mcp
|
|
138
|
+
uv sync --extra dev
|
|
139
|
+
uv pip install -e .
|
|
140
|
+
|
|
141
|
+
# Unit tests (no network)
|
|
142
|
+
uv run pytest
|
|
143
|
+
|
|
144
|
+
# Live integration tests (hits real ABS API)
|
|
145
|
+
uv run pytest -m live
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
The SQLite cache lives at `~/.abs-mcp/cache.db`. Catalogue refreshes every 24h, codelists every 7 days, data responses every hour, latest 15 minutes. Delete the file to force a refresh.
|
|
149
|
+
|
|
150
|
+
## How it differs from existing ABS MCP servers
|
|
151
|
+
|
|
152
|
+
The one existing community option (`seansoreilly/abs`) exposes a single `query_dataset` tool that passes raw SDMX through. This package offers semantic tools and curated mappings for the highest-value dataflows so an LLM can answer real questions without you needing to know what `M13.3.1599.20.1.M` means.
|
|
153
|
+
|
|
154
|
+
## License
|
|
155
|
+
|
|
156
|
+
MIT — Harry Vass, 2026.
|
abs_mcp-0.1.0/glama.json
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "abs-mcp"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "MCP server for the Australian Bureau of Statistics Data API. Hides SDMX behind plain-English tools, with curated mappings for Labour Force, CPI, ERP, Building Approvals, and Lending Indicators."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.11"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
authors = [{ name = "Harry Vass" }]
|
|
13
|
+
keywords = ["mcp", "abs", "statistics", "australia", "sdmx", "claude"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Development Status :: 4 - Beta",
|
|
16
|
+
"Intended Audience :: Developers",
|
|
17
|
+
"License :: OSI Approved :: MIT License",
|
|
18
|
+
"Programming Language :: Python :: 3.11",
|
|
19
|
+
"Programming Language :: Python :: 3.12",
|
|
20
|
+
"Programming Language :: Python :: 3.13",
|
|
21
|
+
"Topic :: Scientific/Engineering :: Information Analysis",
|
|
22
|
+
]
|
|
23
|
+
dependencies = [
|
|
24
|
+
"fastmcp>=2.0,<4",
|
|
25
|
+
"httpx>=0.27",
|
|
26
|
+
"pydantic>=2.7",
|
|
27
|
+
"rapidfuzz>=3.9",
|
|
28
|
+
"pandas>=2.2,<3",
|
|
29
|
+
"sdmx1>=2.20",
|
|
30
|
+
"aiosqlite>=0.20",
|
|
31
|
+
"PyYAML>=6.0",
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
[project.optional-dependencies]
|
|
35
|
+
dev = [
|
|
36
|
+
"pytest>=8",
|
|
37
|
+
"pytest-asyncio>=0.23",
|
|
38
|
+
"respx>=0.21",
|
|
39
|
+
]
|
|
40
|
+
|
|
41
|
+
[project.scripts]
|
|
42
|
+
abs-mcp = "abs_mcp.server:main"
|
|
43
|
+
|
|
44
|
+
[project.urls]
|
|
45
|
+
Homepage = "https://github.com/Bigred97/abs-mcp"
|
|
46
|
+
Issues = "https://github.com/Bigred97/abs-mcp/issues"
|
|
47
|
+
|
|
48
|
+
[tool.hatch.build.targets.wheel]
|
|
49
|
+
packages = ["src/abs_mcp"]
|
|
50
|
+
|
|
51
|
+
[tool.pytest.ini_options]
|
|
52
|
+
asyncio_mode = "auto"
|
|
53
|
+
markers = ["live: hits the real ABS API"]
|
|
54
|
+
addopts = "-m 'not live'"
|
|
55
|
+
testpaths = ["tests"]
|
|
56
|
+
pythonpath = ["src"]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.1.0"
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
"""SQLite-backed HTTP cache with per-read TTL.
|
|
2
|
+
|
|
3
|
+
Single table; the same row can satisfy different TTL windows because TTL is
|
|
4
|
+
evaluated at read time. The `kind` column lets us run targeted invalidation
|
|
5
|
+
later without renaming.
|
|
6
|
+
"""
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import time
|
|
10
|
+
from datetime import timedelta
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import Literal
|
|
13
|
+
|
|
14
|
+
import aiosqlite
|
|
15
|
+
|
|
16
|
+
CacheKind = Literal["catalogue", "datastructure", "data", "latest"]
|
|
17
|
+
|
|
18
|
+
DEFAULT_DB_PATH = Path.home() / ".abs-mcp" / "cache.db"
|
|
19
|
+
|
|
20
|
+
TTL: dict[CacheKind, timedelta] = {
|
|
21
|
+
"catalogue": timedelta(hours=24),
|
|
22
|
+
"datastructure": timedelta(days=7),
|
|
23
|
+
"data": timedelta(hours=1),
|
|
24
|
+
"latest": timedelta(minutes=15),
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
_SCHEMA = """
|
|
28
|
+
CREATE TABLE IF NOT EXISTS http_cache (
|
|
29
|
+
cache_key TEXT PRIMARY KEY,
|
|
30
|
+
payload BLOB NOT NULL,
|
|
31
|
+
cached_at REAL NOT NULL,
|
|
32
|
+
kind TEXT NOT NULL
|
|
33
|
+
);
|
|
34
|
+
CREATE INDEX IF NOT EXISTS idx_kind_cached_at ON http_cache(kind, cached_at);
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class Cache:
|
|
39
|
+
def __init__(self, db_path: Path = DEFAULT_DB_PATH) -> None:
|
|
40
|
+
self.db_path = db_path
|
|
41
|
+
self.db_path.parent.mkdir(parents=True, exist_ok=True)
|
|
42
|
+
self._initialized = False
|
|
43
|
+
|
|
44
|
+
async def _ensure_init(self) -> None:
|
|
45
|
+
if self._initialized:
|
|
46
|
+
return
|
|
47
|
+
async with aiosqlite.connect(self.db_path) as conn:
|
|
48
|
+
await conn.execute("PRAGMA journal_mode=WAL")
|
|
49
|
+
await conn.executescript(_SCHEMA)
|
|
50
|
+
await conn.commit()
|
|
51
|
+
self._initialized = True
|
|
52
|
+
|
|
53
|
+
async def get(self, key: str, ttl: timedelta) -> bytes | None:
|
|
54
|
+
await self._ensure_init()
|
|
55
|
+
cutoff = time.time() - ttl.total_seconds()
|
|
56
|
+
async with aiosqlite.connect(self.db_path) as conn:
|
|
57
|
+
async with conn.execute(
|
|
58
|
+
"SELECT payload FROM http_cache WHERE cache_key = ? AND cached_at >= ?",
|
|
59
|
+
(key, cutoff),
|
|
60
|
+
) as cur:
|
|
61
|
+
row = await cur.fetchone()
|
|
62
|
+
return row[0] if row else None
|
|
63
|
+
|
|
64
|
+
async def set(self, key: str, value: bytes, kind: CacheKind) -> None:
|
|
65
|
+
await self._ensure_init()
|
|
66
|
+
async with aiosqlite.connect(self.db_path) as conn:
|
|
67
|
+
await conn.execute(
|
|
68
|
+
"""
|
|
69
|
+
INSERT INTO http_cache (cache_key, payload, cached_at, kind)
|
|
70
|
+
VALUES (?, ?, ?, ?)
|
|
71
|
+
ON CONFLICT(cache_key) DO UPDATE SET
|
|
72
|
+
payload = excluded.payload,
|
|
73
|
+
cached_at = excluded.cached_at,
|
|
74
|
+
kind = excluded.kind
|
|
75
|
+
""",
|
|
76
|
+
(key, value, time.time(), kind),
|
|
77
|
+
)
|
|
78
|
+
await conn.commit()
|
|
79
|
+
|
|
80
|
+
async def clear(self, kind: CacheKind | None = None) -> None:
|
|
81
|
+
await self._ensure_init()
|
|
82
|
+
async with aiosqlite.connect(self.db_path) as conn:
|
|
83
|
+
if kind:
|
|
84
|
+
await conn.execute("DELETE FROM http_cache WHERE kind = ?", (kind,))
|
|
85
|
+
else:
|
|
86
|
+
await conn.execute("DELETE FROM http_cache")
|
|
87
|
+
await conn.commit()
|