
Search and query government open-data portals (Socrata SODA API).
Search and query government open-data portals (Socrata SODA API) via MCP. STDIO or Streamable HTTP.
Government open-data portals — searched and queried via the Socrata SODA 2.1 API and Discovery API. Discover portals and datasets, inspect typed column schemas, and run SoQL queries or DuckDB-powered SQL over large result sets, from any MCP client. Runs as a stdio process, a local Streamable HTTP server, or the public hosted endpoint above.
| Tool | Description |
|---|---|
socrata_list_portals | List known Socrata-powered government open-data portals with domain, organization name, and approximate dataset count |
socrata_find_datasets | Search for datasets across all Socrata portals or scope to one portal via the Discovery API |
socrata_get_dataset | Fetch full metadata and typed column schema for a dataset by ID — required before writing SoQL queries |
socrata_query_dataset | Execute a SoQL query against any dataset: search, select, where, group, having, order, with DataCanvas spillover |
socrata_dataframe_describe | List registered tables in a DataCanvas session — schema, row count, column names |
socrata_dataframe_query | Run SELECT-only SQL against DataCanvas tables populated by socrata_query_dataset |
socrata_dataframe_drop | Drop a DataCanvas canvas, or one table on it — opt-in via SOCRATA_DATAFRAME_DROP_ENABLED=true |
| Resource | Description |
|---|---|
socrata://datasets/{domain}/{datasetId} | Fetch full metadata and column schema for a dataset by stable URI — same payload as socrata_get_dataset |
socrata://portals | Paginated list of known Socrata portals with organization name and approximate dataset count |
All resource data is also reachable via tools. Use the corresponding tool for agent workflows — resources are for clients that support URI-addressable data.
| Prompt | Description |
|---|---|
explore_open_data | Structured six-step civic data investigation workflow: find portal → discover datasets → inspect schema → query → aggregate → synthesize |
socrata_list_portals tool0 means the portal exposes no dataset assets to the catalog; null means the count is temporarily unavailable)socrata_find_datasets), organization name, and approximate dataset count; the count includes datasets a portal federates from another Socrata tenant (Austin, Illinois, Mesa, and San Francisco publish through a data hub; Seattle catalogs under a sibling tenant)null and the listing still returnssocrata_find_datasets tooldomain, filter by categories/tags, restrict only to an asset type (datasets, maps, files, calendars, stories)column_names — the API field names SoQL takes (cuisine_description, not the display label CUISINE DESCRIPTION), computed-region columns dropped — call socrata_get_dataset for typed schema before writing queriesdomain takes a bare hostname; URL forms (https://data.cdc.gov/) are reduced to the host. A scoped search covers every dataset the portal publishes, including ones federated from another Socrata tenant, reported under the portal's own domainrate_limited (retryable) when the Discovery API returns 429, unknown_domain when the Discovery catalog does not index the domain, invalid_domain when the domain is not a hostnamesocrata_get_dataset toolrow_count_source provenance), and licensing when availabledata_type determines WHERE clause syntax: Number → bare literals (year=2023), Text → single-quoted strings (year='2023'):@computed_region_*) to reduce noise; includes per-column non-null counts when availabledomain from the same socrata_find_datasets result; URL-form domains are reduced to the hostinvalid_id (malformed four-by-four ID), not_found (no such dataset on the domain queried — the message names the ID and domain, and the recovery names the portal that holds the ID when the Discovery catalog knows it), unknown_domain (the domain is not serving the Socrata API — it does not resolve, is not a Socrata portal, or redirects elsewhere; fails on the first attempt), invalid_domain (not a hostname), rate_limited (retryable; honors the upstream Retry-After)socrata_query_dataset WHERE clausesocrata_query_dataset toolsearch for quick full-text lookup ($q), or combine select/where/group/having/order for full analytical control — clauses reference columns by API field name (field_name from socrata_get_dataset), never the display label; operators =, !=, >, <, LIKE, IN(...), BETWEEN, IS NULL, starts_with(), contains(), AND, OR, NOTcount(*), sum(), avg(), min(), max() with group/havingtotal_count returned when a plain row query is truncated (absent for grouped/aggregate queries)assembled_query echoes the SoQL string for learning the syntax; all SODA 2.1 row values are strings except geo/location columns, which return nested objectsCANVAS_PROVIDER_TYPE=duckdb and the page fills limit, up to 50,000 matching rows spill to a DataCanvas table whatever the limit (canvas_id, table_name, canvas_row_count) — list its columns with socrata_dataframe_describe, then run SQL with socrata_dataframe_query. A small limit (e.g. 10) stages a large match without a large inline pageinvalid_id, not_found (names the ID, the domain queried, and the portal holding the ID when known), unknown_domain, invalid_domain, soql_error (bad SoQL, unknown column, or type mismatch — carries the upstream socrataCode and, when upstream names it, the offending column; the recovery hint matches the code: API field names for a parse error, both fixes for an unknown identifier, the quoting rule for a type mismatch), rate_limited (retryable; honors the upstream Retry-After)socrata_dataframe_describe toolcanvas_id from a prior socrata_query_dataset spill — canvases cannot be enumerated, so omitting it fails with canvas_id_required rather than listing tablesnumber → DOUBLE)CANVAS_PROVIDER_TYPE=duckdb is setcanvas_id_required, canvas_not_found (expired or unknown token — re-run socrata_query_dataset to stage a fresh canvas)socrata_dataframe_query toolcanvas_id table staged by socrata_query_dataset; DDL, DML, and file-reading functions (read_csv, read_parquet) are rejectednumber columns (aggregate aliases included) are DOUBLE, so numeric comparisons work without a cast (year > 2020, amount < 500); text and timestamp columns stay VARCHAR — compare times with CAST(date AS TIMESTAMP)canvas_disabled (CANVAS_PROVIDER_TYPE not set), canvas_not_found, table_not_found, sql_rejected (non-SELECT, system catalog access, or a denied function)CANVAS_PROVIDER_TYPE=duckdb is set — DuckDB ships as a regular dependencysocrata_dataframe_drop toolSOCRATA_DATAFRAME_DROP_ENABLED=true; also needs CANVAS_PROVIDER_TYPE=duckdbtable_name, drops the whole canvas — its canvas_id stops resolving for socrata_dataframe_describe and socrata_dataframe_query. With table_name, drops that one table and leaves the canvas and its other tables in placesocrata_query_dataset can stage the data againcanvas_disabled (CANVAS_PROVIDER_TYPE not set), canvas_not_found (unknown, expired, or already dropped), table_not_found (carries the canvas's availableTables)socrata://datasets/{domain}/{datasetId} resourcesocrata_get_dataset — field names, data types, descriptions, row count, licensingdomain and datasetId come from socrata_find_datasets; datasetId must match the four-by-four pattern (e.g. kzjm-xkqj)socrata://portals resourcecursor param, default 50 per page, capped at 200)0 = no dataset assets, null = temporarily unavailable), cached ~24 hoursdomain to socrata_find_datasets to scope a search to one portalexplore_open_data prompttopic required; portal and geography optional to skip discovery or scope WHERE clausesBuilt on @cyanheads/mcp-ts-core: stdio and Streamable HTTP transports, pluggable auth (none / jwt / oauth), swappable storage (in-memory, filesystem, Supabase, Cloudflare KV/R2/D1), structured logging with optional OpenTelemetry tracing.
Socrata-specific:
SOCRATA_APP_TOKEN) for higher per-IP rate limitsSOCRATA_DEFAULT_DOMAINAgent-friendly output:
socrata_query_dataset response so agents can learn and refine syntaxtruncated/shown/cap fields when rows fill the limit, with guidance to page or raise the limit, naming the staged table and both dataframe tools when the result spilledinvalid_id, not_found, unknown_domain, invalid_domain, soql_error, rate_limited, canvas_id_required, canvas_not_found, table_not_found, sql_rejected, canvas_disabled) with actionable recovery textAdd the following to your MCP client configuration file.
A public instance is available at https://socrata.caseyjhand.com/mcp — no installation required. Point any MCP client at it via Streamable HTTP:
{
"mcpServers": {
"socrata-mcp-server": {
"type": "streamable-http",
"url": "https://socrata.caseyjhand.com/mcp"
}
}
}
{
"mcpServers": {
"socrata-mcp-server": {
"type": "stdio",
"command": "bunx",
"args": ["@cyanheads/socrata-mcp-server@latest"],
"env": {
"MCP_TRANSPORT_TYPE": "stdio",
"MCP_LOG_LEVEL": "info"
}
}
}
}
Or with npx (no Bun required):
{
"mcpServers": {
"socrata-mcp-server": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@cyanheads/socrata-mcp-server@latest"],
"env": {
"MCP_TRANSPORT_TYPE": "stdio",
"MCP_LOG_LEVEL": "info"
}
}
}
}
Or with Docker:
{
"mcpServers": {
"socrata-mcp-server": {
"type": "stdio",
"command": "docker",
"args": [
"run", "-i", "--rm",
"-e", "MCP_TRANSPORT_TYPE=stdio",
"ghcr.io/cyanheads/socrata-mcp-server:latest"
]
}
}
}
For Streamable HTTP, set the transport and start the server:
MCP_TRANSPORT_TYPE=http MCP_HTTP_PORT=3010 bun run start:http
# Server listens at http://localhost:3010/mcp
git clone https://github.com/cyanheads/socrata-mcp-server.git
cd socrata-mcp-server
bun install
cp .env.example .env
# edit .env and set SOCRATA_APP_TOKEN if you have one
All configuration is validated at startup via Zod schemas in src/config/server-config.ts. Key environment variables:
| Variable | Description | Default |
|---|---|---|
SOCRATA_APP_TOKEN | Socrata app token (X-App-Token header). Without a token, requests share a throttled pool per source IP. | — |
SOCRATA_DEFAULT_DOMAIN | Default portal domain when domain is omitted from tool calls. | data.seattle.gov |
MCP_TRANSPORT_TYPE | Transport: stdio or http. | stdio |
MCP_HTTP_PORT | Port for HTTP server. | 3010 |
MCP_SESSION_MODE | Session handling: stateful, stateless, or auto (schema default auto resolves to stateful). This server sets it explicitly to stateless. | stateless |
MCP_AUTH_MODE | Auth mode: none, jwt, or oauth. | none |
MCP_LOG_LEVEL | Log level (RFC 5424): debug, info, notice, warning, error. | info |
CANVAS_PROVIDER_TYPE | Set to duckdb to enable DataCanvas spillover for large result sets. DuckDB ships with the server — no additional install required. | — |
SOCRATA_DATAFRAME_DROP_ENABLED | Set to true to enable socrata_dataframe_drop. Off, the tool is listed as disabled. | false |
LOGS_DIR | Directory for log files (Node.js only). | <project-root>/logs |
STORAGE_PROVIDER_TYPE | Storage backend: in-memory, filesystem, supabase, cloudflare-kv/r2/d1. | in-memory |
OTEL_ENABLED | Enable OpenTelemetry instrumentation. | false |
LOG_TOOL_FAILURE_PAYLOADS | Log each failed tool call's arguments and result (key-name redaction only). | false |
See .env.example for the full list of optional overrides.
Build and run:
# One-time build
bun run rebuild
# Run the built server
bun run start:stdio
# or
bun run start:http
Run checks and tests:
bun run devcheck # Lint, format, typecheck, security audit
bun run test # Vitest test suite
docker build -t socrata-mcp-server .
docker run --rm -e MCP_TRANSPORT_TYPE=http -p 3010:3010 socrata-mcp-server
The Dockerfile defaults to HTTP transport, stateless session mode, and logs to /var/log/socrata-mcp-server. OpenTelemetry peer dependencies are installed by default — build with --build-arg OTEL_ENABLED=false to omit them.
| Directory | Purpose |
|---|---|
src/index.ts | createApp() entry point — registers tools, resources, prompts, and inits the Socrata service. |
src/config | Server-specific environment variable parsing and validation with Zod. |
src/mcp-server/tools | Tool definitions (*.tool.ts). Seven tools covering portal listing, dataset search, schema fetch, SoQL query, and DataCanvas SQL and cleanup. |
src/mcp-server/resources | Resource definitions (*.resource.ts). Dataset metadata and portal catalog resources. |
src/mcp-server/prompts | Prompt definitions (*.prompt.ts). Civic data investigation workflow prompt. |
src/services/socrata | Socrata service layer — SODA 2.1 API client, Discovery API, query builder, type normalization. |
tests/ | Unit and integration tests mirroring src/. |
See CLAUDE.md for development guidelines and architectural rules. The short version:
try/catch in tool logicctx.log for request-scoped logging, ctx.state for tenant-scoped storagesocrata_get_dataset before writing WHERE clauses — field_name is what SoQL references and column data_type determines quotingIssues are welcome. Run checks and tests before submitting:
bun run devcheck
bun run test
Apache-2.0 — see LICENSE for details.