socrata-mcp-server-216ddf — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited socrata-mcp-server-216ddf (Plugin) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
<div align="center"> <h1>@cyanheads/socrata-mcp-server</h1> <p><b>Search and query government open-data portals (Socrata SODA API) via MCP. STDIO or Streamable HTTP.</b> <div>7 Tools • 2 Data Resources • 1 Prompt • 1 Skill</div> </p> </div>
<div align="center">
</div>
<div align="center">
Public Hosted Server: https://socrata.caseyjhand.com/mcp
</div>
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.
| 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-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.
socrata_list_portals <sub>tool</sub>0 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 <sub>tool</sub>domain, 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 <sub>tool</sub>row_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 <sub>tool</sub>search 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/havingnot_tabular identifies assets without queryable rows; search with only: "datasets". query_rejected identifies a gateway refusal of query clauses when a bare row request succeeds; simplify the query and use SoQL case(condition, value, true, fallback) for conditional aggregates.total_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 pagenot_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 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 <sub>tool</sub>canvas_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 <sub>tool</sub>canvas_id table staged by socrata_query_dataset; DDL, DML, file references, file-reading functions, and engine-setting functions (current_schema, current_setting) are rejected. Use direct table references instead of query_table; query() accepts only a single SQL string literal. Qualify a staged column named current_schema with its table (t.current_schema).number 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=none), canvas_not_found, table_not_found, sql_rejected (non-SELECT, system catalog access, or a denied function)CANVAS_PROVIDER_TYPE=none disables itsocrata_dataframe_drop <sub>tool</sub>SOCRATA_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=none), canvas_not_found (unknown, expired, or already dropped), table_not_found (carries the canvas's availableTables)socrata://datasets/{domain}/{datasetId} <sub>resource</sub>socrata_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 <sub>resource</sub>cursor 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 <sub>prompt</sub>topic 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, not_tabular, query_rejected, unknown_domain, invalid_domain, soql_error, rate_limited, canvas_id_required, canvas_not_found, table_not_found, sql_rejected, canvas_disabled) with actionable recovery textConnect 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.
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 | DataCanvas spillover for large result sets; none disables it. DuckDB ships with the server — no additional install required. | duckdb |
SOCRATA_DATAFRAME_DROP_ENABLED | Set to true to enable socrata_dataframe_drop. Off, the tool is omitted from the tool list. | 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.
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. # One-time build
bun run rebuild
# Run the built server
bun run start:stdio
# or
bun run start:http bun run devcheck # Lint, format, typecheck, security audit
bun run test # Vitest test suitedocker 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.
| 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. |
skills/ | Published guide for agents investigating civic data with the server. |
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 testApache-2.0 — see LICENSE for details.
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.