socrata-mcp-server

v0.3.0 pre-1.0

Search and query government open-data portals (Socrata SODA API) via MCP. STDIO or Streamable HTTP.

socrata.caseyjhand.com/mcp
claude mcp add --transport http socrata-mcp-server https://socrata.caseyjhand.com/mcp
codex mcp add socrata-mcp-server --url https://socrata.caseyjhand.com/mcp
{
  "mcpServers": {
    "socrata-mcp-server": {
      "url": "https://socrata.caseyjhand.com/mcp"
    }
  }
}
gemini mcp add --transport http socrata-mcp-server https://socrata.caseyjhand.com/mcp
{
  "mcpServers": {
    "socrata-mcp-server": {
      "command": "bunx",
      "args": [
        "mcp-remote",
        "https://socrata.caseyjhand.com/mcp"
      ]
    }
  }
}
{
  "mcpServers": {
    "socrata-mcp-server": {
      "type": "http",
      "url": "https://socrata.caseyjhand.com/mcp"
    }
  }
}
curl -X POST https://socrata.caseyjhand.com/mcp \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-11-25","capabilities":{},"clientInfo":{"name":"curl","version":"1.0.0"}}}'

Tools

7

read 6

socrata_find_datasets

open-world

Search for datasets across all Socrata-powered government open-data portals, or scope to one portal with the domain parameter. Returns dataset IDs, names, domains, update timestamps, and column_names — the API field names SoQL takes, not display labels. Use socrata_get_dataset to fetch the typed column schema before writing queries — column_names carry no type information.

read
invocation
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "socrata_find_datasets",
    "arguments": {}
  }
}
schema
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "properties": {
    "query": {
      "description": "Full-text search across dataset names and descriptions. Omit to browse without filtering.",
      "type": "string"
    },
    "domain": {
      "description": "Scope search to a single portal by bare hostname (e.g. data.seattle.gov, data.cityofnewyork.us); URL forms like https://data.seattle.gov/ are accepted and reduced to the host. Omit to search all portals.",
      "type": "string"
    },
    "categories": {
      "description": "Filter by domain categories (e.g. [\"Public Safety\", \"Transportation\"]).",
      "type": "array",
      "items": {
        "type": "string"
      }
    },
    "tags": {
      "description": "Filter by tags (e.g. [\"covid19\", \"permits\"]).",
      "type": "array",
      "items": {
        "type": "string"
      }
    },
    "only": {
      "description": "Filter by asset type. Omit to include all types. Usually \"datasets\" is what you want.",
      "type": "string",
      "enum": [
        "datasets",
        "maps",
        "files",
        "calendars",
        "stories"
      ]
    },
    "order": {
      "description": "Sort order. Defaults to relevance. Use updated_at to surface recently-refreshed datasets.",
      "type": "string",
      "enum": [
        "relevance",
        "page_views_total",
        "created_at",
        "updated_at"
      ]
    },
    "limit": {
      "default": 10,
      "description": "Number of results to return (1–100). Default 10.",
      "type": "integer",
      "minimum": 1,
      "maximum": 100
    },
    "offset": {
      "default": 0,
      "description": "Pagination offset. Default 0.",
      "type": "integer",
      "minimum": 0,
      "maximum": 9007199254740991
    }
  },
  "required": [
    "limit",
    "offset"
  ],
  "additionalProperties": false
}
view source ↗

socrata_get_dataset

Fetch full metadata and column schema for a Socrata dataset by ID. Returns field names, data types, descriptions, row count, and licensing. Always call this before writing a socrata_query_dataset — the column types determine correct WHERE clause syntax: Number columns accept bare literals (year=2023) while Text columns require single-quoted strings (year='2023').

read
invocation
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "socrata_get_dataset",
    "arguments": {
      "dataset_id": "<dataset_id>"
    }
  }
}
schema
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "properties": {
    "domain": {
      "description": "Portal the dataset lives on, as a bare hostname (e.g. data.cityofnewyork.us); URL forms like https://data.cityofnewyork.us/ are accepted and reduced to the host. Pass the domain from the same socrata_find_datasets result as dataset_id. Defaults to SOCRATA_DEFAULT_DOMAIN or data.seattle.gov, which is wrong for another portal’s ID.",
      "type": "string"
    },
    "dataset_id": {
      "type": "string",
      "description": "Four-by-four dataset ID matching pattern like kzjm-xkqj. IDs are portal-scoped: take it from socrata_find_datasets together with that result’s domain."
    }
  },
  "required": [
    "dataset_id"
  ],
  "additionalProperties": false
}
view source ↗

socrata_query_dataset

Execute a SoQL query against any dataset on any Socrata portal. Use the search parameter for quick full-text lookup, or combine select/where/group/having/order for full analytical control. Returns rows plus the assembled SoQL string so you can learn the pattern. Columns are referenced by API field name (field_name from socrata_get_dataset, e.g. cuisine_description), never the display label. All SODA 2.1 row values are strings even for numeric columns — check data_type from socrata_get_dataset to determine correct WHERE quoting: Number columns use bare literals (year=2023), Text columns use single-quoted strings (year='2023'). To enumerate distinct values, use select="col, count(*) as n" with group="col" and order="n DESC". When CANVAS_PROVIDER_TYPE=duckdb and rows fill limit, up to 50,000 matching rows spill to a DataCanvas table whatever the limit: list its columns with socrata_dataframe_describe, then run SQL with socrata_dataframe_query.

read
invocation
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "socrata_query_dataset",
    "arguments": {
      "dataset_id": "<dataset_id>"
    }
  }
}
schema
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "properties": {
    "domain": {
      "description": "Portal the dataset lives on, as a bare hostname (e.g. data.cityofnewyork.us); URL forms like https://data.cityofnewyork.us/ are accepted and reduced to the host. Pass the domain from the same socrata_find_datasets result as dataset_id. Defaults to SOCRATA_DEFAULT_DOMAIN or data.seattle.gov, which is wrong for another portal’s ID.",
      "type": "string"
    },
    "dataset_id": {
      "type": "string",
      "description": "Four-by-four dataset ID (e.g. kzjm-xkqj). IDs are portal-scoped: take it from socrata_find_datasets together with that result’s domain."
    },
    "search": {
      "description": "Full-text search across all text columns ($q). For field-specific filtering, use where instead.",
      "type": "string"
    },
    "select": {
      "description": "SoQL SELECT clause — API field names (field_name from socrata_get_dataset, not display labels), aliases, aggregates: \"state, sum(deaths) as total_deaths\". Omit for all columns.",
      "type": "string"
    },
    "where": {
      "description": "SoQL WHERE clause over API field names (field_name from socrata_get_dataset). Check column data_type there first — Number columns: year=2023, Text columns: year='2023'; an unquoted text value is read as a column name. Operators: =, !=, >, <, LIKE, IN(...), BETWEEN, IS NULL, starts_with(), contains(), AND, OR, NOT.",
      "type": "string"
    },
    "group": {
      "description": "SoQL GROUP BY clause over API field names (field_name from socrata_get_dataset). Requires an aggregate function in select.",
      "type": "string"
    },
    "having": {
      "description": "SoQL HAVING clause. Filters on aggregated results, e.g. count > 100.",
      "type": "string"
    },
    "order": {
      "description": "SoQL ORDER BY clause over API field names or select aliases, e.g. \"total_deaths DESC\" or \"date ASC\".",
      "type": "string"
    },
    "limit": {
      "default": 100,
      "description": "Max rows to return (1–5000). Default 100. Use with offset for pagination. When the canvas is enabled and the page fills limit, up to 50,000 matching rows are staged on it whatever the limit — pass a small limit (e.g. 10) to stage a large match without a large inline page.",
      "type": "integer",
      "minimum": 1,
      "maximum": 5000
    },
    "offset": {
      "default": 0,
      "description": "Row offset for pagination. Default 0.",
      "type": "integer",
      "minimum": 0,
      "maximum": 9007199254740991
    },
    "canvas_id": {
      "description": "Optional 10-char DataCanvas token from a prior socrata_query_dataset or socrata_dataframe_describe call. Omit on first call when CANVAS_PROVIDER_TYPE=duckdb to mint a fresh canvas. Large result sets spill here automatically.",
      "type": "string",
      "pattern": "^[A-Za-z0-9_-]{10}$"
    }
  },
  "required": [
    "dataset_id",
    "limit",
    "offset"
  ],
  "additionalProperties": false
}
view source ↗

socrata_list_portals

List known Socrata-powered government open-data portals with their domain, organization name, and approximate dataset count. The catalog is a curated list of 39 well-known portals; dataset counts are fetched from the Discovery API and cached for ~24 hours. Filtering is client-side substring match on the query parameter. Use this first when you do not know which portal to target, then pass the domain to socrata_find_datasets.

read
invocation
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "socrata_list_portals",
    "arguments": {}
  }
}
schema
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "properties": {
    "query": {
      "description": "Keyword to filter portal names or organization names (case-insensitive substring match). Omit to list all portals.",
      "type": "string"
    },
    "limit": {
      "default": 50,
      "description": "Max portals to return (1–200). Default 50.",
      "type": "integer",
      "minimum": 1,
      "maximum": 200
    },
    "offset": {
      "default": 0,
      "description": "Pagination offset. Default 0.",
      "type": "integer",
      "minimum": 0,
      "maximum": 9007199254740991
    }
  },
  "required": [
    "limit",
    "offset"
  ],
  "additionalProperties": false
}
view source ↗

socrata_dataframe_describe

List registered tables in a DataCanvas session — schema, row count, and column names. Shows what datasets are available for SQL queries via socrata_dataframe_query. Only meaningful when CANVAS_PROVIDER_TYPE=duckdb is set. Use after socrata_query_dataset spills a large result set to canvas.

read
invocation
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "socrata_dataframe_describe",
    "arguments": {}
  }
}
schema
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "properties": {
    "canvas_id": {
      "description": "Canvas ID returned by socrata_query_dataset when a large result spills to canvas. Required in practice when canvas is enabled — canvases cannot be enumerated, so omitting it fails with canvas_id_required instead of listing tables.",
      "type": "string",
      "pattern": "^[A-Za-z0-9_-]{10}$"
    }
  },
  "additionalProperties": false
}
view source ↗

socrata_dataframe_query

Run SELECT-only SQL against a DataCanvas table populated by socrata_query_dataset. Columns SODA types as number (including aggregate aliases like count(*) as n) are staged as 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). Only works when CANVAS_PROVIDER_TYPE=duckdb is set. Use socrata_dataframe_describe to see registered tables and their schemas.

read
invocation
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "socrata_dataframe_query",
    "arguments": {
      "canvas_id": "<canvas_id>",
      "sql": "<sql>"
    }
  }
}
schema
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "properties": {
    "canvas_id": {
      "type": "string",
      "pattern": "^[A-Za-z0-9_-]{10}$",
      "description": "Canvas ID returned from socrata_query_dataset or socrata_dataframe_describe."
    },
    "sql": {
      "type": "string",
      "description": "SELECT-only SQL to run against registered canvas tables. DDL, DML, and file-reading functions are rejected. Use table names from socrata_dataframe_describe."
    },
    "limit": {
      "default": 1000,
      "description": "Max rows to return (1–10000). Default 1000.",
      "type": "integer",
      "minimum": 1,
      "maximum": 10000
    }
  },
  "required": [
    "canvas_id",
    "sql",
    "limit"
  ],
  "additionalProperties": false
}
view source ↗

disabled 1

socrata_dataframe_drop

Permanently remove staged DataCanvas data — the whole canvas, or one table on it when table_name is given. Dropping the canvas invalidates its canvas_id: socrata_dataframe_describe and socrata_dataframe_query calls with it then fail as canvas_not_found. Dropping one table leaves the canvas and its other tables in place. Only the server's staged copy is deleted; the portal data is untouched, and socrata_query_dataset can stage it again. Call socrata_dataframe_describe first to see what a canvas holds. Only works when CANVAS_PROVIDER_TYPE=duckdb is set.

disabledwould be destructive

Disabled. Dropping DataCanvas data is turned off in this deployment.

SOCRATA_DATAFRAME_DROP_ENABLED=true
schema
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "properties": {
    "canvas_id": {
      "type": "string",
      "pattern": "^[A-Za-z0-9_-]{10}$",
      "description": "Canvas ID returned by socrata_query_dataset when a large result spilled to canvas."
    },
    "table_name": {
      "description": "Exact table name from socrata_dataframe_describe to drop only that table. Omit to drop the whole canvas.",
      "type": "string",
      "minLength": 1
    }
  },
  "required": [
    "canvas_id"
  ],
  "additionalProperties": false
}
view source ↗

Resources

2

Fetch full metadata and column schema for a Socrata dataset addressable by stable URI. Same payload as socrata_get_dataset. Name, description, and column descriptions are upstream-provided portal metadata, not server-authored text. URI format: socrata://datasets/{domain}/{datasetId} (e.g. socrata://datasets/data.seattle.gov/kzjm-xkqj).

uri socrata://datasets/{domain}/{datasetId} mime application/json

List of known Socrata-powered government open-data portals with organization name and approximate dataset count. The catalog is a curated list of 39 well-known portals; dataset counts are fetched from the Discovery API and cached for ~24 hours (null when temporarily unavailable). Paginated — default 50 per page. Use the domain values with socrata_find_datasets to search a specific portal.

uri socrata://portals mime application/json

Prompts

1

Structured workflow for investigating a civic data question. Guides: discover relevant datasets on the right portal, inspect schemas, query for baseline data, group/aggregate for trends, and synthesize findings with data-freshness caveats.

  • topicrequired — Civic data topic or question to investigate (e.g. "traffic collisions in 2023", "food inspection failures", "311 service requests by neighborhood").
  • portal — Target portal domain if known (e.g. data.seattle.gov). Omit to use socrata_list_portals to find the right portal first.
  • geography — Geographic scope to focus on (e.g. "Seattle", "King County", "New York City"). Used to scope WHERE clauses.