@cyanheads/socrata-mcp-server
Supports Cloudflare KV, R2, and D1 as pluggable storage backends for persisting server state, caching portal data, or storing session information.
Provides an embedded analytical SQL engine (DuckDB) for querying large result sets that spill over from Socrata SODA queries, enabling complex aggregation and filtering with full SQL support.
Supports Supabase as a pluggable storage backend for persisting server state, caching portal data, or storing session information.
This server has a remotely hosted endpoint. Open its connector listing for connection details, authentication requirements, and setup instructions:
Public Hosted Server: https://socrata.caseyjhand.com/mcp
Overview
Query city, county, state, and federal open-data portals built on Socrata. Discover portals and datasets, inspect column schemas, and run SoQL queries, or SQL over large result sets.
Tools
Tool | Description |
| List known Socrata-powered government open-data portals with domain, organization name, and approximate dataset count |
| Search for datasets across all Socrata portals or scope to one portal via the Discovery API |
| Fetch full metadata and typed column schema for a dataset by ID — required before writing SoQL queries |
| Execute a SoQL query against any dataset: search, select, where, group, having, order, with DataCanvas spillover |
| List registered tables in a DataCanvas session — schema, row count, column names |
| Run SELECT-only SQL against DataCanvas tables populated by |
| Drop a DataCanvas canvas, or one table on it — opt-in via |
Resources
Resource | Description |
| Fetch full metadata and column schema for a dataset by stable URI — same payload as |
| 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.
Prompts
Prompt | Description |
| Structured six-step civic data investigation workflow: find portal → discover datasets → inspect schema → query → aggregate → synthesize |
Agent skill
socrata-guide covers dataset coverage checks, SoQL and DuckDB query syntax, and comparisons across staged tables. It ships with plugin installs, npm, Docker, and the MCPB bundle, and is available over MCP as skill://socrata-guide/SKILL.md.
Related MCP server: socrata-mcp
Capability reference
socrata_list_portals tool
Curated catalog of 39 well-known city, county, state, and federal portals — every member verified live in the Discovery catalog
Per-portal dataset counts fetched live from the Discovery API, cached ~24 hours (
0means the portal exposes no dataset assets to the catalog;nullmeans the count is temporarily unavailable)Client-side substring filtering on domain or organization name; pagination up to 200 per page with offset
Returns domain (pass to
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)Never fails on an upstream error — a count that cannot be fetched, or that the call's 50-second deadline cuts off, keeps its last known value, or comes back
nullwhen it has none, and the listing still returnsPast-the-end notice: an empty page at
offset > 0names the offset to page below
socrata_find_datasets tool
Full-text query across dataset names/descriptions; scope with
domain, filter bycategories/tags(several values of one filter are ORed), restrictonlyto an asset type (datasets, maps, files, calendars, stories)Sort by relevance, page views, created date, or updated date;
limitup to 10,000 withSOCRATA_CLAMP_LIMITSon, served 100 per call with a notice naming the offset that continues (the schema maximum is 100 with it off)Discovery serves only the first 10,000 matches of a search:
offsetmust stay below 10,000, andtotalCountstops at 10,000 withtotalCountCapped: truemarking that figure a lower boundReturns dataset IDs, names, domains, tags, update timestamps, and
column_names— the API field names SoQL takes (cuisine_description, not the display labelCUISINE DESCRIPTION), computed-region columns dropped — callsocrata_get_datasetfor typed schema before writing queriesRecovery hints on empty results — echoes applied filters and suggests how to broaden; a page past the last match says so and names the offset to page below. The notice also covers a
limitserved below the one requesteddomaintakes 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 domainTyped errors:
result_window_exceededwhenoffsetis 10,000 or more,rate_limited(retryable) when the Discovery API returns 429,retry_deadline_exceeded(retryable) when the call passes its 50-second deadline,unknown_domainwhen the Discovery catalog does not index the domain,invalid_domainwhen the domain is not a hostname
socrata_get_dataset tool
Returns field names, Socrata data types, descriptions, row count (with
row_count_sourceprovenance), and licensing when availableColumn
data_typedetermines WHERE clause syntax:Number→ bare literals (year=2023),Text→ single-quoted strings (year='2023')Excludes computed region columns (
:@computed_region_*) to reduce noise; includes per-column non-null counts when availableDataset IDs are portal-scoped — pass the
domainfrom the samesocrata_find_datasetsresult; URL-form domains are reduced to the hostTyped errors:
invalid_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 upstreamRetry-After),retry_deadline_exceeded(retryable; the call passed its 50-second deadline)Always call before writing a
socrata_query_datasetWHERE clause
socrata_query_dataset tool
searchfor quick full-text lookup ($q), or combineselect/where/group/having/orderfor full analytical control — clauses reference columns by API field name (field_namefromsocrata_get_dataset), never the display label; operators=,!=,>,<,LIKE,IN(...),BETWEEN,IS NULL,starts_with(),contains(),AND,OR,NOTAggregation via
count(*),sum(),avg(),min(),max()withgroup/havingnot_tabularidentifies assets without queryable rows; search withonly: "datasets".query_rejectedidentifies a gateway refusal of query clauses when a bare row request succeeds; simplify the query and use SoQLcase(condition, value, true, fallback)for conditional aggregates.limitup to 50,000 withSOCRATA_CLAMP_LIMITSon, but one call returns at most 5,000 rows: a largerlimitis served as 5,000, with a notice naming the offset that continues when more rows match (off, the schema maximum is 5,000). Page withoffsettotal_countcounts the query as written — groups for a grouped query, distinct values for a distinct select — through a bounded SoQL$querypipe recount. It is present when the match holds more rows than the response, and absent when the response holds the whole match or when the recount failed or ran past 15 seconds.truncatedis set only whentotal_countexceedsoffset + row_count, or when the page fillslimitand no count existsAn empty page at
offset > 0gets a past-the-end notice naming the match size, or a may-be-past-the-end notice when the count failedassembled_queryechoes the SoQL string for learning the syntax; all SODA 2.1 row values are strings except geo/location columns, which return nested objectsWhen the server has a canvas and more rows match past the page, up to 50,000 matching rows spill to a DataCanvas table whatever the
limit(canvas_id,table_name,canvas_row_count) — list its columns withsocrata_dataframe_describe, then run SQL withsocrata_dataframe_query. A smalllimit(e.g. 10) stages a large match without a large inline page. Staging is one SODA request; if it fails or runs out of time the call still returns its inline rows, with nocanvas_idand a notice saying nothing was stagedtable_namenames the staged table (default derived fromdataset_id, e.g.ijzp_q8t2_rows). Spilling under a name the canvas already holds replaces that table, and the notice reports the replacement; pass a distincttable_namewith the samecanvas_idto keep several filtered extracts of one dataset side by side. When a named call stages nothing because the whole match fits the page, or the server has no canvas, the notice says so; alimitbelow the page's row count stages a match that fitsEvery request of a call shares one 50-second deadline, retries and backoffs included
Typed errors:
not_tabular,query_rejected,invalid_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 upstreamsocrataCodeand, when upstream names it, the offendingcolumn; 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 upstreamRetry-After),retry_deadline_exceeded(retryable; the call passed its 50-second deadline)
socrata_dataframe_describe tool
Requires
canvas_idfrom a priorsocrata_query_datasetspill — canvases cannot be enumerated, so omitting it fails withcanvas_id_requiredrather than listing tablesShows table name, row count, and DuckDB column types for each registered table (SODA
number→DOUBLE)Typed errors:
canvas_id_required,canvas_not_found(expired or unknown token — re-runsocrata_query_datasetto stage a fresh canvas)
socrata_dataframe_query tool
SELECT-only SQL against a
canvas_idtable staged bysocrata_query_dataset; DDL, DML, file references, file-reading functions, and engine-setting functions (current_schema,current_setting) are rejected. Use direct table references instead ofquery_table;query()accepts only a single SQL string literal. Qualify a staged column namedcurrent_schemawith its table (t.current_schema).Spilled columns are typed from the SODA response headers:
numbercolumns (aggregate aliases included) areDOUBLE, so numeric comparisons work without a cast (year > 2020,amount < 500); text and timestamp columns stayVARCHAR— compare times withCAST(date AS TIMESTAMP)limitup to 50,000 withSOCRATA_CLAMP_LIMITSon (default 1000), but one call returns at most 10,000 rows: a largerlimitis served as 10,000. When more rows match, the notice gives the paging recipe —ORDER BYa unique column withLIMIT/OFFSETin the SQL. With the switch off, the schema maximum is 10,000content[]shows the**SQL:**echo as a code span, or as a fencedsqlblock when the SQL spans lines;structuredContentcarries it unchangedTyped errors:
canvas_disabled(CANVAS_PROVIDER_TYPE=none),canvas_not_found,table_not_found,sql_rejected(non-SELECT, system catalog access, or a denied function)Works out of the box — DuckDB ships as a regular dependency and is enabled by default;
CANVAS_PROVIDER_TYPE=nonedisables it
socrata_dataframe_drop tool
Opt-in: omitted from the tool list until
SOCRATA_DATAFRAME_DROP_ENABLED=true; also needsCANVAS_PROVIDER_TYPE=duckdbWithout
table_name, drops the whole canvas — itscanvas_idstops resolving forsocrata_dataframe_describeandsocrata_dataframe_query. Withtable_name, drops that one table and leaves the canvas and its other tables in placeReports the dropped tables with their row counts and the tables still on the canvas
Deletes only the staged copy —
socrata_query_datasetcan stage the data againTyped errors:
canvas_disabled(CANVAS_PROVIDER_TYPE=none),canvas_not_found(unknown, expired, or already dropped),table_not_found(carries the canvas'savailableTables)
socrata://datasets/{domain}/{datasetId} resource
Returns the same payload as
socrata_get_dataset— field names, data types, descriptions, row count, licensingdomainanddatasetIdcome fromsocrata_find_datasets;datasetIdmust match the four-by-four pattern (e.g.kzjm-xkqj)Fails validation on a malformed ID, and not-found when the dataset doesn't exist on the domain (naming the portal that holds the ID when the Discovery catalog knows it)
socrata://portals resource
Curated catalog of 39 known Socrata portals, cursor-paginated (
cursorparam, default 50 per page, capped at 200)Returns domain, organization name, and approximate dataset count (
0= no dataset assets,null= temporarily unavailable; a count the 50-second call deadline cuts off keeps its last known value, or isnullwhen it has none), cached ~24 hoursPass
domaintosocrata_find_datasetsto scope a search to one portal
explore_open_data prompt
Arguments:
topicrequired;portalandgeographyoptional to skip discovery or scope WHERE clausesReturns one user message walking a six-step workflow: find portal → discover datasets → inspect schema → query → aggregate → synthesize
Features
Built 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:
Full Socrata SODA 2.1 API integration — SoQL query builder with select, where, group, having, order, search, limit, offset
Discovery API for cross-portal dataset search and per-portal dataset counts (curated 39-portal catalog, counts cached ~24h)
App token support (
SOCRATA_APP_TOKEN) for higher per-IP rate limitsConfigurable default portal domain via
SOCRATA_DEFAULT_DOMAINDataCanvas spillover (DuckDB, bundled) — large query results register as SQL tables for analytical queries
Agent-friendly output:
Assembled SoQL string echoed in every
socrata_query_datasetresponse so agents can learn and refine syntaxRecovery hints on empty results — echoes applied filters with specific suggestions for broadening
Truncation disclosure —
truncated/shown/capfields when more rows match past the page, with guidance to page or raise the limit, naming the staged table and both dataframe tools when the result spilledTyped error reasons across every tool (
invalid_id,not_found,not_tabular,query_rejected,unknown_domain,invalid_domain,soql_error,rate_limited,retry_deadline_exceeded,result_window_exceeded,canvas_id_required,canvas_not_found,table_not_found,sql_rejected,canvas_disabled) with actionable recovery text
Getting started
Connect to the hosted instance, no install:
claude mcp add --transport http socrata-mcp-server https://socrata.caseyjhand.com/mcpOr run it locally:
claude mcp add socrata-mcp-server -- npx -y @cyanheads/socrata-mcp-serverFor other clients, add this to the MCP config file:
{
"mcpServers": {
"socrata-mcp-server": {
"command": "npx",
"args": ["-y", "@cyanheads/socrata-mcp-server"]
}
}
}An optional SOCRATA_APP_TOKEN from a Socrata portal raises rate limits. For Docker or a source build, see Running the server.
Configuration
All configuration is validated at startup via Zod schemas in src/config/server-config.ts. Key environment variables:
Variable | Description | Default |
| Socrata app token (X-App-Token header). Without a token, requests share a throttled pool per source IP. | — |
| Default portal domain when |
|
| Transport: |
|
| Port for HTTP server. |
|
| Session handling: |
|
| Auth mode: |
|
| Log level (RFC 5424): |
|
| Serve a |
|
| DataCanvas spillover for large result sets; |
|
| Set to |
|
| Directory for log files (Node.js only). |
|
| Storage backend: |
|
| Enable OpenTelemetry instrumentation. |
|
| Log each failed tool call's arguments and result (key-name redaction only). |
|
See .env.example for the full list of optional overrides.
Running the server
Requires Bun v1.4.0+ or Node.js v24+.
git clone https://github.com/cyanheads/socrata-mcp-server.git
cd socrata-mcp-server
bun install
cp .env.example .env
# Set SOCRATA_APP_TOKEN in .env if you have one.Local development
Build and run:
# One-time build bun run rebuild # Run the built server bun run start:stdio # or bun run start:httpRun checks and tests:
bun run devcheck # Lint, format, typecheck, security audit bun run test # Vitest test suite
Docker
docker build -t socrata-mcp-server .
docker run --rm -e MCP_TRANSPORT_TYPE=http -p 3010:3010 socrata-mcp-serverThe 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.
Project structure
Directory | Purpose |
|
|
| Server-specific environment variable parsing and validation with Zod. |
| Tool definitions ( |
| Resource definitions ( |
| Prompt definitions ( |
| Socrata service layer — SODA 2.1 API client, Discovery API, query builder, type normalization. |
| Published guide for agents investigating civic data with the server. |
| Unit and integration tests mirroring |
Development guide
See CLAUDE.md for development guidelines and architectural rules. The short version:
Handlers throw, framework catches — no
try/catchin tool logicUse
ctx.logfor request-scoped logging,ctx.statefor tenant-scoped storageCall
socrata_get_datasetbefore writing WHERE clauses —field_nameis what SoQL references and columndata_typedetermines quotingWrap external API calls: validate raw → normalize to domain type → return output schema; never fabricate missing fields
Contributing
Issues are welcome. Run checks and tests before submitting:
bun run devcheck
bun run testLicense
Apache-2.0 — see LICENSE for details.
This server cannot be deployed
Maintenance
Related MCP Connectors
Data.gov MCP — wraps Data.gov CKAN API (catalog.data.gov/api/3)
DataSeattle MCP — Seattle open data (data.seattle.gov, Socrata SODA API).
Search and preview datasets publicly. Account-required full data via MCP.
Query U.S. Census Bureau data, variables, and geography via MCP.
Related MCP Servers
- FlicenseAqualityCmaintenanceMCP server for querying Huwise/Opendatasoft data portals. Enables dataset search, metadata retrieval, record filtering with ODSQL, and data export.53-
- AlicenseAqualityDmaintenanceAn MCP server that gives LLM agents typed, cached access to civic open-data portals via Socrata (SODA 2.1 + Discovery API), enabling search, query, profiling, sampling, and CSV export of datasets.6MIT
- AlicenseNot gradedqualityDmaintenanceEnables to discover, inspect, query, and profile live NYC Open Data through a CLI and MCP server, providing a developer-first interface to Socrata APIs.17 npmMIT
- AlicenseNot gradedqualityBmaintenanceEnables searching datasets and fetching records from Opendatasoft public data portals through Pipeworx MCP gateway.356 npmMIT