indonesia-civic-stack
by suryast
README.md
# 🇮🇩 indonesia-civic-stack
<!-- mcp-name: io.github.suryast/indonesia-civic-stack -->
[](https://pypi.org/project/indonesia-civic-stack/)
[](https://registry.modelcontextprotocol.io/servers/io.github.suryast/indonesia-civic-stack)
[](https://github.com/suryast/indonesia-civic-stack/actions)
[](https://pypi.org/project/indonesia-civic-stack/)
[](LICENSE)
Python SDK, MCP tools, and REST wrappers for Indonesian public-sector data.
Release **1.2.1** includes **14 scraper modules**, **45 unified MCP tools**, and
**11 REST module routers**. These counts describe code, not live availability.
Indonesian public-data portals expose different APIs, HTML structures, browser
requirements, and access restrictions. This project provides a shared response
schema and reusable clients rather than requiring every civic-tech app to build
its own integration. Portal changes can still break individual operations;
verify the response and its provenance before drawing conclusions.
## Module inventory and maturity
Each row has a scraper, normalizer, and module test file. “Tests present” means
mocked/replayed tests exist, not that they establish current portal liveness.
The MCP column counts decorators in the **unified** `civic_stack/server.py`.
| Module | Source configured in code | Data / method | MCP tools | REST | Tests present |
|---|---|---|---:|:---:|:---:|
| [bpom](civic_stack/bpom/) | cekbpom.pom.go.id | Product registration; HTTP HTML | 3 | Yes | Yes |
| [bpjph](civic_stack/bpjph/) | cmsbl.halal.go.id | Halal certificates; HTTP REST | 4 | Yes | Yes |
| [ahu](civic_stack/ahu/) | ahu.go.id | Company registry; browser helper | 4 | Yes | Yes |
| [ojk](civic_stack/ojk/) | www.ojk.go.id / sikapiuangmu.ojk.go.id | Institutions and alerts; HTTP + optional browser list | 4 | Yes | Yes |
| [oss_nib](civic_stack/oss_nib/) | oss.go.id | NIB identity; Playwright | 3 | Yes | Yes |
| [lpse](civic_stack/lpse/) | spse.inaproc.id and configured portals | Vendors/tenders; multi-portal HTTP | 4 | Yes | Yes |
| [kpu](civic_stack/kpu/) | infopemilu.kpu.go.id / sirekap-obj-data.kpu.go.id | Candidates and 2024 elections; HTTP JSON | 4 | Yes | Yes |
| [lhkpn](civic_stack/lhkpn/) | elhkpn.kpk.go.id | Wealth declarations; Playwright search, HTTP PDFs | 3 | Yes | Yes |
| [bps](civic_stack/bps/) | webapi.bps.go.id | Statistics; HTTP API, requires `BPS_API_KEY` | 3 | Yes | Yes |
| [bmkg](civic_stack/bmkg/) | data.bmkg.go.id | Earthquakes/weather; HTTP JSON/XML | 4 | Yes | Yes |
| [simbg](civic_stack/simbg/) | simbg.pu.go.id and pilot portals | Permits; multi-portal HTTP | 3 | Yes | Yes |
| [jdih](civic_stack/jdih/) | peraturan.go.id | Regulations; Playwright recent-list filtering | 2 | No | Yes |
| [ksei](civic_stack/ksei/) | web.ksei.co.id | Securities and statistics links; HTTP HTML | 2 | No | Yes |
| [djpb](civic_stack/djpb/) | data-apbn.kemenkeu.go.id | Budget themes; HTTP JSON | 2 | No | Yes |
JDIH, KSEI, and DJPB are available through the SDK and unified MCP server, but
have no individual MCP server or REST router. JDIH search checks up to 50 recent
listing links; KSEI securities fetch/search only inspect page 1. Neither is a
complete registry search. LHKPN's browser search implementation does not prove
that reCAPTCHA or the upstream portal works in your deployment.
**Current portal liveness is unverified here.** Historical March/April 2026
observations in [endpoint status](references/endpoint-status.md) and
[geo-restrictions](references/geo-restrictions.md) are troubleshooting context,
not present-day health evidence. An HTTP 200 homepage, local `/health`, or passing
mock/VCR test is not a successful live registry lookup.
## Install
```bash
pip install indonesia-civic-stack # Core HTTP SDK
pip install "indonesia-civic-stack[mcp]" # + unified MCP server
pip install "indonesia-civic-stack[api]" # + FastAPI and uvicorn
pip install "indonesia-civic-stack[browser]" # + Playwright and Camoufox
pip install "indonesia-civic-stack[all]" # mcp + api + browser
```
Browser-backed operations also need binaries:
```bash
playwright install chromium
python -m camoufox fetch # AHU Camoufox path
```
AHU, OSS NIB, JDIH, LHKPN search, and OJK's full-list helper use browser paths.
BPJPH's current scraper uses HTTP REST. `all` does not download browser binaries
or include LHKPN's optional PDF packages. Install `pdfplumber` directly for PDF
text extraction; the optional scanned-document fallback needs `anthropic` and
`ANTHROPIC_API_KEY`, sends the document to an external API, and may incur cost.
There are no `pdf` or `playwright` extras. SOCKS HTTP transport additionally
requires `pip install "httpx[socks]"`.
## Python SDK
```python
import asyncio
from civic_stack.bpom.scraper import search
async def main():
try:
async with asyncio.timeout(120):
records = await search("paracetamol")
except Exception as exc:
print("Could not verify:", type(exc).__name__)
return
if not records:
print("No records returned; acquisition may be unverified")
for record in records:
print(record.status, record.found, record.source_url, record.fetched_at)
if record.status == "ERROR":
print("Could not verify:", record.result)
elif record.found:
print(record.result) # Check identity, period, and domain-specific status
else:
print("No match reported within the query scope; not proof of absence")
asyncio.run(main())
```
Every scraper has `fetch()` and `search()`, with module-specific signatures.
Specialized helpers include BMKG's `get_latest_earthquake`, KPU's
`get_election_results`, OJK's `check_waspada`, KSEI's `get_statistics_links`,
and DJPB's `get_budget_summary`. See [usage and limitations](references/usage.md)
for actual return shapes, scope limits, and unified-tool wrapper caveats.
## Response interpretation
Core lookups return `CivicStackResponse` or lists of envelopes:
```json
{
"result": {"product_name": "Example", "registration_status": "ACTIVE"},
"found": true,
"status": "ACTIVE",
"confidence": 1.0,
"source_url": "https://cekbpom.pom.go.id/example",
"fetched_at": "2026-03-14T06:30:00Z",
"last_updated": null,
"module": "bpom",
"raw": null
}
```
This is illustrative, not a live response. Status values are `ACTIVE`, `EXPIRED`,
`SUSPENDED`, `REVOKED`, `NOT_FOUND`, and `ERROR`. Error details are under
`result.detail`, not a top-level `detail`. Some helpers return raw lists/dicts,
PDF extraction data, or URLs instead of envelopes; status shortcut MCP tools
omit source and timestamp. Do not assume every tool has the full schema.
Check `status`, `found`, `source_url`, and `fetched_at` before reading `result`.
Retrieval time is not publication time; verify the source's reporting period.
**`ERROR` is not `NOT_FOUND`.** Current paths in several modules also collapse
network/browser failures into `NOT_FOUND` or `[]`. Consequently, an empty or
not-found result alone cannot prove acquisition succeeded or that the record
does not exist. BPS `fetch()` can raise for a missing key; other callers can
also encounter unexpected exceptions. Catch exceptions at the caller boundary.
A found registration is not a general safety/legal guarantee. An alert-list
match is a warning, not an endorsement. Partial multi-portal results and scraper
confidence values must not be presented as exhaustive or authoritative checks.
## Unified MCP server
```bash
civic-stack-mcp # stdio, 45 tools
python -m civic_stack.server # packaged equivalent
civic-stack mcp # CLI stdio entry point
python -m civic_stack.server --transport http --host 127.0.0.1 --port 8000
# Individual module server (where present):
python -m civic_stack.bpom.server
```
For local clients, configure `civic-stack-mcp` as the command. The repository's
[.mcp.json](.mcp.json) uses `.venv/bin/python -m civic_stack.server`; create that
virtual environment and install `[mcp]` before discovery. Discovery does not
install dependencies or ensure portals respond. There is no shared hosted server.
### Unified MCP tool inventory
| Module | Registered tool names |
|---|---|
| bpom | `check_bpom`, `search_bpom`, `get_bpom_status` |
| bpjph | `check_halal_cert`, `lookup_halal_by_product`, `get_halal_status`, `cross_reference_halal_bpom` |
| ahu | `lookup_company_ahu`, `get_company_directors`, `verify_company_status`, `search_companies_ahu` |
| ojk | `check_ojk_license`, `search_ojk_institutions`, `get_ojk_status`, `check_ojk_waspada` |
| oss_nib | `lookup_nib`, `verify_nib`, `search_oss_businesses` |
| lpse | `lookup_vendor_lpse`, `search_lpse_vendors`, `search_lpse_tenders`, `get_lpse_portals` |
| kpu | `get_candidate`, `search_kpu_candidates`, `get_election_results_kpu`, `get_campaign_finance_kpu` |
| lhkpn | `get_lhkpn`, `search_lhkpn`, `get_lhkpn_pdf` |
| bps | `search_bps_datasets`, `get_bps_indicator`, `list_bps_regions` |
| bmkg | `get_bmkg_alerts`, `get_weather_forecast`, `get_earthquake_history`, `get_latest_earthquake` |
| simbg | `lookup_building_permit`, `search_permits_by_area`, `list_simbg_portals` |
| jdih | `search_jdih`, `get_jdih` |
| ksei | `search_ksei`, `get_ksei` |
| djpb | `search_djpb`, `get_djpb` |
These are 45 registered names, not 45 independently validated operations. Some
wrappers call generic scraper functions rather than the specialized helper
suggested by the tool name; read [wrapper caveats](references/usage.md) before
using election-results, campaign-finance, tender, alert-list, or portal-list tools.
Verify registration offline, without calling tools:
```bash
python -c 'import asyncio; from civic_stack.server import mcp; print(len(asyncio.run(mcp.list_tools())))'
```
## REST API
```bash
civic-stack api --host 127.0.0.1 --port 8000
uvicorn civic_stack.app:app --host 127.0.0.1 --port 8000
uvicorn civic_stack.bpom.app:app --host 127.0.0.1 --port 8001
```
The unified app mounts 11 routers. JDIH, KSEI, and DJPB have no REST routes.
Open `/docs` or `/openapi.json` for the actual route signatures. Examples:
```text
GET /bpom/search?q=paracetamol
GET /bpjph/search?q=kopi
GET /ahu/search?q=Contoh
GET /ojk/check/Bank%20BCA
GET /kpu/search?q=Contoh
GET /lhkpn/search?q=Contoh
GET /bps/search?q=inflasi
GET /bmkg/forecast/Jakarta
GET /bmkg/earthquake/latest
GET /simbg/search?q=Jakarta
```
`/health` only describes the local app; it does not probe upstream portals.
## Deployment and security
`civic_client()` reads `PROXY_URL`, with environment-based `PROXY_MODE=connect`
or `rewrite` selection. A `*.workers.dev` URL auto-selects rewriting. Browser
proxy handling is separate: a Worker rewrite URL is not a standard Chromium
proxy, AHU and OSS-NIB fall back to `PROXY_URL` only in CONNECT mode, JDIH uses
its explicit proxy argument, and LHKPN search does not forward its `proxy_url`
argument to its browser. Do not assume setting an environment variable proxies
every operation. See [usage](references/usage.md).
Whoever runs a proxy controls the portal responses the server receives. The
REST routers and the standalone AHU/OSS-NIB MCP servers therefore accept a
caller-supplied `proxy_url` only when it exactly matches `PROXY_URL` or an entry
in `CIVIC_ALLOWED_PROXIES` (comma-separated full proxy URLs); anything else is
rejected (HTTP 400 or an `ERROR` envelope). Configure proxies server-side.
Access can vary by deployment, endpoint, and time. Historical regional tests do
not establish a current geo-block or WAF cause. Stop after bounded attempts;
do not bypass authenticated tiers or repeatedly retry access controls.
**The current unified REST app does not implement API-key authentication or
per-client rate-limit middleware.** Setting `CIVIC_API_KEY` or `CIVIC_RATE_LIMIT`
alone does not enable either. `CIVIC_ALLOWED_PROXIES` only gates caller-supplied
proxies; it is not an SSRF allowlist for SDK calls. Deploy behind your own
authentication, quotas, and network controls. Bind loopback
for local use. Module acquisition rate limiters are not public-API abuse controls.
## Agent Skills installation
[SKILL.md](SKILL.md) is the portable skill entry point. Its metadata version is
`"1.2.1"`; it uses trigger phrases in `description`, not unsupported frontmatter
keys. Installation must retain the **complete repository**, especially
`references/usage.md` and the other bundled references, package code, manifest,
and licenses. The installed directory must be named `indonesia-civic-stack`.
For a host that supports a user-selected skills directory:
```bash
# Set this to your host's configured directory; destination must not already exist.
git clone https://github.com/suryast/indonesia-civic-stack.git "$SKILLS_DIR/indonesia-civic-stack"
cd "$SKILLS_DIR/indonesia-civic-stack"
pip install -e ".[mcp]" # if running the bundled MCP code
```
Do not install only `SKILL.md`, and do not rename the package to a release
worktree's suffix. Host discovery and Python installation are separate steps.
For version-pinned installs, choose the reviewed release revision explicitly;
cloning the default branch is not a version pin.
## Development and offline verification
```bash
git clone https://github.com/suryast/indonesia-civic-stack.git
cd indonesia-civic-stack
python -m venv .venv && source .venv/bin/activate
pip install -e ".[all,dev]"
pytest --collect-only -q # derive current collected tests; no fixed advertised total
pytest -v # mocked/VCR replay; no live portal calls intended
pytest tests/jdih/ tests/ksei/ tests/djpb/ -v
ruff check civic_stack/ tests/ scripts/ examples/ app.py server.py
ruff format --check civic_stack/ tests/ scripts/ examples/ app.py server.py
mypy civic_stack/
```
All 14 modules have test files, including JDIH, KSEI, and DJPB. A passing offline
suite validates fixtures/mocks, not upstream operation. Do not record live
cassettes in CI; review the actual test isolation and recording settings first.
Read [AGENTS.md](AGENTS.md), [CLAUDE.md](CLAUDE.md), and
[CONTRIBUTING.md](CONTRIBUTING.md) before development. The contribution guide
states the desired full module contract; the implemented surfaces above take
precedence over older blanket inventory statements in other documents.
## References and related projects
- [Usage and implementation limits](references/usage.md)
- [Historical endpoint observations](references/endpoint-status.md)
- [Historical regional access observations](references/geo-restrictions.md)
- [Portal patterns](references/portal-patterns.md)
- [Proxy operations](references/vps-hardening.md)
- [Example prompts](PROMPTS.md)
- [indonesia-civic-signal-monitor](https://github.com/suryast/indonesia-civic-signal-monitor)
- [indonesia-gov-apis](https://github.com/suryast/indonesia-gov-apis)
## License
The root skill/documentation license is MIT; see [LICENSE](LICENSE).
Module licensing varies; retain module license files and consult
[LICENSES.md](LICENSES.md) (its older `modules/` labels refer to the current
`civic_stack/` package directories).
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessUnresponsive