socrata-mcp-server
An MCP server that allows agents to search, discover, and query government open-data portals powered by the Socrata SODA API. It provides tools to list portals, find datasets, fetch column schemas, and execute SoQL queries or SQL over DataCanvas tables.
Endpoint and access
No sign-in to connect and list tools. Individual tools may still need a vendor account, credentials, or permissions; check the documented prerequisites.
https://socrata.caseyjhand.com/mcpWhat its tools do
6 tools, grouped by the hints the server advertises. Hints are self-declared, not verified behavior.
Checks and signals
Each line is separate evidence, not a trust score.
- Passed Endpoint answered the live check ·
- Availability 1 of 1 checks passed in 30 days
What you can do with it
- Search for datasets across Socrata-powered government portals
- Fetch metadata and typed column schema for a specific dataset
- Execute SoQL queries to retrieve and filter dataset rows
- Run SQL queries against DataCanvas tables populated by the server
Source and maintenance
- Maintenance checked
Protocol and capabilities
- Server
- socrata-mcp-server 0.6.1
- MCP protocol negotiated in check
- 2025-11-25
- Tools
- 6 advertised
- Resources
- 3 advertised
- Resource templates
- 2 advertised
- Prompts
- 1 advertised
{
"tools": {
"listChanged": true
},
"logging": {},
"prompts": {
"listChanged": true
},
"resources": {
"subscribe": true,
"listChanged": true
},
"extensions": {
"io.modelcontextprotocol/skills": {
"directoryRead": true
}
}
}Server-provided instructions
Untrusted server output shown for inspection. It is not Koskamo guidance and is excluded from knowledge search.
Tools
6 advertised
socrata_find_datasets — Find Socrata Datasets
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.
{
"type": "object",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"only": {
"enum": [
"datasets",
"maps",
"files",
"calendars",
"stories"
],
"type": "string",
"description": "Filter by asset type. Omit to include all types. Usually \"datasets\" is what you want."
},
"tags": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by the portal's own tags (e.g. [\"covid19\", \"permits\"]). Requires domain: each portal assigns its own tags. Multiple values are ORed: a dataset carrying any listed tag matches. Across portals, search with query instead."
},
"limit": {
"type": "integer",
"default": 10,
"maximum": 10000,
"minimum": 1,
"description": "Number of results to return (1–10,000). One call returns at most 100 results: a larger limit is served as 100, with a notice naming the offset that continues. Default 10."
},
"order": {
"enum": [
"relevance",
"page_views_total",
"created_at",
"updated_at"
],
"type": "string",
"description": "Sort order. Defaults to relevance. Use updated_at to surface recently-refreshed datasets."
},
"query": {
"type": "string",
"description": "Full-text search across dataset names and descriptions. Omit to browse without filtering."
},
"domain": {
"type": "string",
"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. Required with categories or tags."
},
"offset": {
"type": "integer",
"default": 0,
"maximum": 9007199254740991,
"minimum": 0,
"description": "Pagination offset. Default 0. Must be below 10,000: the Discovery catalog serves only the first 10,000 matches of a search."
},
"categories": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by the portal's own categories (e.g. [\"Public Safety\", \"Transportation\"]). Requires domain: each portal assigns its own categories. Multiple values are ORed: a dataset in any listed category matches. Across portals, search with query instead."
}
},
"additionalProperties": false
}{
"type": "object",
"anyOf": [
{
"not": {
"required": [
"error"
]
},
"required": [
"results",
"totalCount"
]
},
{
"required": [
"error"
]
}
],
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"error": {
"type": "object",
"required": [
"code",
"message"
],
"properties": {
"code": {
"type": "integer",
"maximum": 9007199254740991,
"minimum": -9007199254740991,
"description": "JSON-RPC error code for this failure."
},
"data": {
"type": "object",
"properties": {
"reason": {
"type": "string",
"examples": [
"domain_required",
"result_window_exceeded",
"rate_limited",
"unknown_domain",
"invalid_domain",
"retry_deadline_exceeded"
],
"description": "Machine-readable failure mode. Declared by this tool: `domain_required`: categories or tags was sent without domain: both match one portal's own vocabulary, and without domain the catalog matches them against a Socrata-wide taxonomy that portal results do not report. `result_window_exceeded`: offset is 10,000 or more: the Discovery catalog serves only the first 10,000 matches of a search. `rate_limited`: Discovery API returned 429. `unknown_domain`: The Discovery catalog does not index the domain (\"Domain not found\"). `invalid_domain`: The domain is not a hostname, even after dropping a URL scheme, path, or query. `retry_deadline_exceeded`: The Discovery API did not answer within the 50-second time limit the call shares across its requests, retries included. Other values are possible when a failure originates below the handler."
},
"recovery": {
"type": "object",
"required": [
"hint"
],
"properties": {
"hint": {
"type": "string"
}
},
"description": "Actionable next step for the caller.",
"additionalProperties": {}
},
"retryable": {
"type": "boolean",
"description": "Whether retrying may succeed."
}
},
"additionalProperties": {}
},
"message": {
"type": "string",
"description": "Human-readable description of what went wrong."
}
},
"description": "Present when the call failed. Absent on success.",
"additionalProperties": {}
},
"notice": {
"type": "string",
"description": "Guidance for this page. When limit was served below the requested value (the 100-result per-call cap or the 10,000-match window), names the served limit and where paging continues; when results are empty, echoes the filters and suggests how to broaden, or, when offset passed the last match, names the offset to page below. Absent otherwise."
},
"results": {
"type": "array",
"items": {
"type": "object",
"required": [
"dataset_id",
"domain",
"name",
"tags",
"column_names"
],
"properties": {
"name": {
"type": "string",
"description": "Dataset display name."
},
"tags": {
"type": "array",
"items": {
"type": "string"
},
"description": "Associated tags."
},
"domain": {
"type": "string",
"description": "Portal domain hosting this dataset (e.g. data.seattle.gov)."
},
"license": {
"type": "string",
"description": "Dataset license when available."
},
"category": {
"type": "string",
"description": "Domain category when available."
},
"asset_type": {
"type": "string",
"description": "Catalog asset type, such as dataset, filter (a filtered view), map, chart, calendar, visualization, measure, story, href (an external link), federated_href, file. Only dataset and filter answer socrata_query_dataset with rows. Absent when the catalog lists none."
},
"dataset_id": {
"type": "string",
"description": "Four-by-four dataset ID (e.g. kzjm-xkqj). Pass to socrata_get_dataset or socrata_query_dataset."
},
"view_count": {
"type": "number",
"description": "Total page views when available."
},
"description": {
"type": "string",
"description": "Dataset description when available."
},
"column_names": {
"type": "array",
"items": {
"type": "string"
},
"description": "API field names — the identifiers SoQL takes in select/where/group/order (e.g. cuisine_description), not display labels. Computed-region system columns are dropped; empty when the catalog lists no field names. No type info — call socrata_get_dataset for the typed schema."
},
"data_updated_at": {
"type": "string",
"description": "ISO 8601 timestamp of last data update when available."
},
"parent_dataset_ids": {
"type": "array",
"items": {
"type": "string"
},
"description": "IDs, on the same domain, of the assets this one is built from (Discovery parent_fxf): the table a filter, map, chart, or calendar reads, or every asset a story embeds, charts and maps included. Not every parent is public. Absent when the catalog lists none."
}
},
"description": "A single matching dataset.",
"additionalProperties": false
},
"description": "Matching datasets. Empty when no results."
},
"totalCount": {
"type": "number",
"description": "Total matches before pagination. 0 when empty. Discovery counts no further than 10,000: when totalCountCapped is true, 10,000 is a lower bound."
},
"effectiveQuery": {
"type": "string",
"description": "Search query applied, for reference."
},
"totalCountCapped": {
"type": "boolean",
"description": "True when totalCount reached Discovery's 10,000-match count ceiling, so more datasets may match than totalCount says. Absent otherwise."
}
},
"additionalProperties": false
}{
"readOnlyHint": true,
"openWorldHint": true,
"idempotentHint": true
}socrata_get_dataset — Get Dataset Schema
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').
{
"type": "object",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"required": [
"dataset_id"
],
"properties": {
"domain": {
"type": "string",
"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."
},
"dataset_id": {
"type": "string",
"pattern": "^[A-Za-z0-9]{4}-[A-Za-z0-9]{4}$",
"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."
}
},
"additionalProperties": false
}{
"type": "object",
"anyOf": [
{
"not": {
"required": [
"error"
]
},
"required": [
"dataset_id",
"domain",
"name",
"tags",
"columns"
]
},
{
"required": [
"error"
]
}
],
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"name": {
"type": "string",
"description": "Dataset display name."
},
"tags": {
"type": "array",
"items": {
"type": "string"
},
"description": "Associated tags."
},
"error": {
"type": "object",
"required": [
"code",
"message"
],
"properties": {
"code": {
"type": "integer",
"maximum": 9007199254740991,
"minimum": -9007199254740991,
"description": "JSON-RPC error code for this failure."
},
"data": {
"type": "object",
"properties": {
"reason": {
"type": "string",
"examples": [
"not_found",
"unknown_domain",
"invalid_domain",
"rate_limited",
"retry_deadline_exceeded"
],
"description": "Machine-readable failure mode. Declared by this tool: `not_found`: Valid ID format but the dataset does not exist on the domain queried — including a gateway HTTP 403 for an ID the portal does not serve. `unknown_domain`: The domain does not serve the Socrata API to this server: its hostname does not resolve (DNS ENOTFOUND), its API answered HTTP 404 without a Socrata error body, it redirected the request to another host that did not answer with Socrata data, or a gateway refused a dataset the Discovery catalog lists there. `invalid_domain`: The domain is not a hostname, even after dropping a URL scheme, path, or query. `rate_limited`: SODA endpoint returned 429. `retry_deadline_exceeded`: The portal did not answer within the 50-second time limit the call shares across its requests, retries included. Other values are possible when a failure originates below the handler."
},
"recovery": {
"type": "object",
"required": [
"hint"
],
"properties": {
"hint": {
"type": "string"
}
},
"description": "Actionable next step for the caller.",
"additionalProperties": {}
},
"retryable": {
"type": "boolean",
"description": "Whether retrying may succeed."
}
},
"additionalProperties": {}
},
"message": {
"type": "string",
"description": "Human-readable description of what went wrong."
}
},
"description": "Present when the call failed. Absent on success.",
"additionalProperties": {}
},
"domain": {
"type": "string",
"description": "Portal domain hosting this dataset."
},
"columns": {
"type": "array",
"items": {
"type": "object",
"required": [
"field_name",
"data_type"
],
"properties": {
"data_type": {
"type": "string",
"description": "Socrata data type (e.g. Number, Text, Calendar date). Determines WHERE clause quoting: Number → bare literal, Text → single-quoted string."
},
"field_name": {
"type": "string",
"description": "Column field name as used in SoQL queries."
},
"description": {
"type": "string",
"description": "Column description when available."
},
"non_null_count": {
"type": "number",
"description": "Non-null row count for this column when available."
}
},
"description": "A single column in the dataset schema.",
"additionalProperties": false
},
"description": "Column schema. Computed region columns (:@computed_region_*) are excluded to reduce noise."
},
"license": {
"type": "string",
"description": "License name when available."
},
"category": {
"type": "string",
"description": "Domain category when available."
},
"row_count": {
"type": "number",
"description": "Approximate row count when available. See row_count_source for provenance."
},
"asset_type": {
"type": "string",
"description": "Catalog asset type, such as dataset, filter (a filtered view), map, chart, calendar, visualization, measure, story, href (an external link), federated_href, file. Only dataset and filter answer socrata_query_dataset with rows. Absent when the catalog lists none."
},
"dataset_id": {
"type": "string",
"description": "Four-by-four dataset ID."
},
"description": {
"type": "string",
"description": "Dataset description when available."
},
"data_updated_at": {
"type": "string",
"description": "ISO 8601 timestamp of last data update when available."
},
"row_count_source": {
"enum": [
"top_level_cached_contents",
"column_cached_contents"
],
"type": "string",
"description": "How row_count was obtained: 'top_level_cached_contents' — reported directly by the portal's views metadata; 'column_cached_contents' — derived as the maximum per-column cached count when the top-level value is absent. Absent when row_count is absent."
}
},
"additionalProperties": false
}{
"readOnlyHint": true,
"openWorldHint": true,
"idempotentHint": true
}socrata_query_dataset — 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 the server has a canvas and more rows match past a full page, 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. Pass stage false to return the page alone, without staging.
{
"type": "object",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"required": [
"dataset_id"
],
"properties": {
"group": {
"type": "string",
"description": "SoQL GROUP BY clause over API field names (field_name from socrata_get_dataset). Requires an aggregate function in select."
},
"limit": {
"type": "integer",
"default": 100,
"maximum": 50000,
"minimum": 1,
"description": "Max rows to return (1–50,000). One call returns at most 5,000 rows: a larger limit is served as 5,000, with a notice naming the offset that continues when more rows match. Default 100. Use with offset for pagination. When the server has a canvas and more rows match past a full page, 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, or stage false to return the page without staging."
},
"order": {
"type": "string",
"description": "SoQL ORDER BY clause over API field names or select aliases, e.g. \"total_deaths DESC\" or \"date ASC\"."
},
"stage": {
"type": "boolean",
"default": true,
"description": "Stage the matching rows on a canvas when more rows match past this page and the server has a canvas. Default true. Pass false to return the page alone, skipping the staging request; canvas_id and table_name then go unused."
},
"where": {
"type": "string",
"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."
},
"domain": {
"type": "string",
"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."
},
"having": {
"type": "string",
"description": "SoQL HAVING clause. Filters on aggregated results, e.g. count > 100."
},
"offset": {
"type": "integer",
"default": 0,
"maximum": 9007199254740991,
"minimum": 0,
"description": "Row offset for pagination. Default 0."
},
"search": {
"type": "string",
"description": "Full-text search across all text columns ($q). For field-specific filtering, use where instead."
},
"select": {
"type": "string",
"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."
},
"canvas_id": {
"type": "string",
"pattern": "^[A-Za-z0-9_-]{10}$",
"description": "Optional 10-char DataCanvas token from a prior socrata_query_dataset or socrata_dataframe_describe call. Omit on first call to mint a fresh canvas. Large result sets spill here automatically unless stage is false."
},
"dataset_id": {
"type": "string",
"pattern": "^[A-Za-z0-9]{4}-[A-Za-z0-9]{4}$",
"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."
},
"table_name": {
"type": "string",
"pattern": "^[A-Za-z_][A-Za-z0-9_]{0,62}$",
"description": "Name for the canvas table a spill stages: letters, digits, and underscores, starting with a letter or underscore, 63 characters at most; lowercased, and not a SQL keyword such as select or order. Defaults to a name derived from dataset_id (ijzp_q8t2_rows). A spill under a name the canvas already holds replaces that table, so give each extract its own name, with the same canvas_id, to keep several on one canvas."
}
},
"additionalProperties": false
}{
"type": "object",
"anyOf": [
{
"not": {
"required": [
"error"
]
},
"required": [
"rows",
"row_count",
"assembled_query",
"domain",
"dataset_id"
]
},
{
"required": [
"error"
]
}
],
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"cap": {
"type": "number",
"description": "The row limit that was applied when capped."
},
"rows": {
"type": "array",
"items": {
"type": "object",
"propertyNames": {
"type": "string"
},
"additionalProperties": {}
},
"description": "Result rows. Scalar values are strings (SODA 2.1); geo/location columns return nested objects. Use column schema from socrata_get_dataset for type context."
},
"error": {
"type": "object",
"required": [
"code",
"message"
],
"properties": {
"code": {
"type": "integer",
"maximum": 9007199254740991,
"minimum": -9007199254740991,
"description": "JSON-RPC error code for this failure."
},
"data": {
"type": "object",
"properties": {
"reason": {
"type": "string",
"examples": [
"query_rejected",
"not_tabular",
"not_found",
"unknown_domain",
"invalid_domain",
"soql_error",
"rate_limited",
"retry_deadline_exceeded"
],
"description": "Machine-readable failure mode. Declared by this tool: `query_rejected`: The gateway refused query clauses but a bare row request succeeded. `not_tabular`: The asset has no queryable rows: SODA refused row access to a non-tabular asset (HTTP 403), or answered with no fields, as maps, charts, calendars, and visualizations do. data.asset_type and data.parent_dataset_ids then name the asset type and the tables it is built on, when the catalog lists them. `not_found`: The dataset does not exist on the domain queried — including a gateway HTTP 403 for an ID the portal does not serve. `unknown_domain`: The domain does not serve the Socrata API to this server: its hostname does not resolve (DNS ENOTFOUND), its API answered HTTP 404 without a Socrata error body, it redirected the request to another host that did not answer with Socrata data, or a gateway refused a dataset the Discovery catalog lists there. `invalid_domain`: The domain is not a hostname, even after dropping a URL scheme, path, or query. `soql_error`: SoQL syntax error, unknown column, or literal/column type mismatch. data.socrataCode carries the upstream code and data.column the offending token when upstream names one. `rate_limited`: SODA endpoint returned 429. `retry_deadline_exceeded`: The portal did not answer the query within the 50-second time limit the call shares across its requests, retries included. Other values are possible when a failure originates below the handler."
},
"recovery": {
"type": "object",
"required": [
"hint"
],
"properties": {
"hint": {
"type": "string"
}
},
"description": "Actionable next step for the caller.",
"additionalProperties": {}
},
"retryable": {
"type": "boolean",
"description": "Whether retrying may succeed."
}
},
"additionalProperties": {}
},
"message": {
"type": "string",
"description": "Human-readable description of what went wrong."
}
},
"description": "Present when the call failed. Absent on success.",
"additionalProperties": {}
},
"shown": {
"type": "number",
"description": "Rows returned in this response when capped."
},
"domain": {
"type": "string",
"description": "Portal hostname queried, normalized from the domain input."
},
"notice": {
"type": "string",
"description": "Guidance when the query returned zero rows (review the SoQL or broaden the filter; past offset 0, whether the offset passed the end of the match), or when more rows match past this page: how to page, naming the requested and served limit when a limit above the 5,000-row per-call cap was served as 5,000; the staged table to query when the result spilled, and the earlier table of that name it replaced; or why nothing was staged, naming a canvas_id or table_name that stage false left unused. Absent otherwise."
},
"canvas_id": {
"type": "string",
"description": "DataCanvas token when results spilled. Pass to socrata_dataframe_query to run SQL over the staged rows in table_name — a bounded copy of the matching set (up to 50,000 rows, reported in canvas_row_count), not the full set when total_count exceeds that cap. Page with offset to reach rows beyond it."
},
"row_count": {
"type": "number",
"description": "Rows returned in this response."
},
"truncated": {
"type": "boolean",
"description": "True when more rows match past this page: total_count exceeds offset + row_count, or rows filled the limit and no count was made. Spills to canvas when enabled and stage is not false; table_name names the staged table."
},
"dataset_id": {
"type": "string",
"description": "Dataset ID queried."
},
"table_name": {
"type": "string",
"description": "Canvas table holding the staged rows; present when canvas_id is. Use it as the FROM target in socrata_dataframe_query SQL; list its columns with socrata_dataframe_describe."
},
"total_count": {
"type": "number",
"description": "Rows the query matches, in the same unit as rows: groups for a grouped query, distinct values for a distinct select. Counted when rows fill limit or a page past offset 0 comes back empty; present when the match holds more rows than this response. Absent otherwise, and when the count failed or ran past its 15-second bound."
},
"assembled_query": {
"type": "string",
"description": "SoQL clauses assembled for this request — useful for learning the syntax."
},
"canvas_row_count": {
"type": "number",
"description": "Rows staged onto the DataCanvas — a bounded copy of the matching result set (capped at 50,000). Fewer than total_count when the match exceeds the cap. Present only when canvas_id is."
}
},
"additionalProperties": false
}{
"readOnlyHint": true,
"openWorldHint": true,
"idempotentHint": true
}socrata_list_portals — List Socrata 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.
{
"type": "object",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"limit": {
"type": "integer",
"default": 50,
"maximum": 200,
"minimum": 1,
"description": "Max portals to return (1–200). Default 50."
},
"query": {
"type": "string",
"description": "Keyword to filter portal names or organization names (case-insensitive substring match). Omit to list all portals."
},
"offset": {
"type": "integer",
"default": 0,
"maximum": 9007199254740991,
"minimum": 0,
"description": "Pagination offset. Default 0."
}
},
"additionalProperties": false
}{
"type": "object",
"anyOf": [
{
"not": {
"required": [
"error"
]
},
"required": [
"portals",
"totalCount"
]
},
{
"required": [
"error"
]
}
],
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"error": {
"type": "object",
"required": [
"code",
"message"
],
"properties": {
"code": {
"type": "integer",
"maximum": 9007199254740991,
"minimum": -9007199254740991,
"description": "JSON-RPC error code for this failure."
},
"data": {
"type": "object",
"properties": {
"reason": {
"type": "string",
"description": "Machine-readable failure mode."
},
"recovery": {
"type": "object",
"required": [
"hint"
],
"properties": {
"hint": {
"type": "string"
}
},
"description": "Actionable next step for the caller.",
"additionalProperties": {}
},
"retryable": {
"type": "boolean",
"description": "Whether retrying may succeed."
}
},
"additionalProperties": {}
},
"message": {
"type": "string",
"description": "Human-readable description of what went wrong."
}
},
"description": "Present when the call failed. Absent on success.",
"additionalProperties": {}
},
"notice": {
"type": "string",
"description": "Recovery hint for an empty page: no portals matched the filter, or offset passed the last portal (names the offset to page below). Absent on non-empty pages."
},
"portals": {
"type": "array",
"items": {
"type": "object",
"required": [
"domain",
"dataset_count"
],
"properties": {
"domain": {
"type": "string",
"description": "Portal domain (e.g. data.seattle.gov). Pass to socrata_find_datasets."
},
"organization": {
"type": "string",
"description": "Organization name when available (e.g. City of Seattle)."
},
"dataset_count": {
"type": [
"number",
"null"
],
"description": "Approximate count of dataset-type assets on this portal from the Discovery API catalog (point-in-time, refreshed ~daily). 0 means the portal exposes no dataset assets to the catalog; null means the live count is temporarily unavailable."
}
},
"description": "A single Socrata portal.",
"additionalProperties": false
},
"description": "Matching portals. Empty when no results."
},
"totalCount": {
"type": "number",
"description": "Total portals before pagination. 0 when empty."
}
},
"additionalProperties": false
}{
"readOnlyHint": true,
"openWorldHint": true,
"idempotentHint": true
}socrata_dataframe_describe — Describe DataCanvas Tables
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. Meaningful when the server has a DataCanvas. Use after socrata_query_dataset spills a large result set to canvas.
{
"type": "object",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"canvas_id": {
"type": "string",
"pattern": "^[A-Za-z0-9_-]{10}$",
"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."
}
},
"additionalProperties": false
}{
"type": "object",
"anyOf": [
{
"not": {
"required": [
"error"
]
},
"required": [
"tables"
]
},
{
"required": [
"error"
]
}
],
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"error": {
"type": "object",
"required": [
"code",
"message"
],
"properties": {
"code": {
"type": "integer",
"maximum": 9007199254740991,
"minimum": -9007199254740991,
"description": "JSON-RPC error code for this failure."
},
"data": {
"type": "object",
"properties": {
"reason": {
"type": "string",
"examples": [
"canvas_id_required",
"canvas_not_found"
],
"description": "Machine-readable failure mode. Declared by this tool: `canvas_id_required`: Canvas is enabled but canvas_id was omitted. `canvas_not_found`: Provided canvas_id does not match any registered canvas. Other values are possible when a failure originates below the handler."
},
"recovery": {
"type": "object",
"required": [
"hint"
],
"properties": {
"hint": {
"type": "string"
}
},
"description": "Actionable next step for the caller.",
"additionalProperties": {}
},
"retryable": {
"type": "boolean",
"description": "Whether retrying may succeed."
}
},
"additionalProperties": {}
},
"message": {
"type": "string",
"description": "Human-readable description of what went wrong."
}
},
"description": "Present when the call failed. Absent on success.",
"additionalProperties": {}
},
"notice": {
"type": "string",
"description": "Status message when canvas is not enabled or no tables are registered. Absent when tables are present."
},
"tables": {
"type": "array",
"items": {
"type": "object",
"required": [
"table_id",
"row_count",
"columns"
],
"properties": {
"columns": {
"type": "array",
"items": {
"type": "object",
"required": [
"name",
"type"
],
"properties": {
"name": {
"type": "string",
"description": "Column name."
},
"type": {
"type": "string",
"description": "DuckDB column type (e.g. VARCHAR, DOUBLE, BOOLEAN, JSON). SODA number columns are DOUBLE; text and timestamp columns are VARCHAR."
}
},
"description": "Column name and DuckDB type.",
"additionalProperties": false
},
"description": "Column names and DuckDB types. SODA number columns are staged as DOUBLE, so numeric comparisons (year > 2020) need no cast; compare timestamps with CAST(col AS TIMESTAMP)."
},
"table_id": {
"type": "string",
"description": "Table name registered on the canvas."
},
"row_count": {
"type": "number",
"description": "Number of rows in this table."
}
},
"description": "A registered DataCanvas table.",
"additionalProperties": false
},
"description": "Tables available for SQL queries. Empty when none registered."
},
"canvas_id": {
"type": "string",
"description": "Canvas ID resolved, when canvas is enabled."
}
},
"additionalProperties": false
}{
"readOnlyHint": true,
"openWorldHint": false,
"idempotentHint": true
}socrata_dataframe_query — Query DataCanvas Table
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). Works when the server has a DataCanvas. Use socrata_dataframe_describe to see registered tables and their schemas.
{
"type": "object",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"required": [
"canvas_id",
"sql"
],
"properties": {
"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": {
"type": "integer",
"default": 1000,
"maximum": 50000,
"minimum": 1,
"description": "Max rows to return (1–50,000). One call returns at most 10,000 rows: a larger limit is served as 10,000, with a notice naming how to page through the rest when more rows match. Default 1000."
},
"canvas_id": {
"type": "string",
"pattern": "^[A-Za-z0-9_-]{10}$",
"description": "Canvas ID returned from socrata_query_dataset or socrata_dataframe_describe."
}
},
"additionalProperties": false
}{
"type": "object",
"anyOf": [
{
"not": {
"required": [
"error"
]
},
"required": [
"rows",
"row_count",
"sql",
"canvas_id"
]
},
{
"required": [
"error"
]
}
],
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"cap": {
"type": "number",
"description": "The row limit that was applied when capped."
},
"sql": {
"type": "string",
"description": "SQL that was executed."
},
"rows": {
"type": "array",
"items": {
"type": "object",
"propertyNames": {
"type": "string"
},
"additionalProperties": {}
},
"description": "Query result rows. DuckDB may return native JS types (number, boolean, null) for numeric/boolean columns."
},
"error": {
"type": "object",
"required": [
"code",
"message"
],
"properties": {
"code": {
"type": "integer",
"maximum": 9007199254740991,
"minimum": -9007199254740991,
"description": "JSON-RPC error code for this failure."
},
"data": {
"type": "object",
"properties": {
"reason": {
"type": "string",
"examples": [
"canvas_disabled",
"canvas_not_found",
"table_not_found",
"sql_rejected"
],
"description": "Machine-readable failure mode. Declared by this tool: `canvas_disabled`: CANVAS_PROVIDER_TYPE is not set to duckdb — DataCanvas is unavailable. `canvas_not_found`: canvas_id does not match any registered canvas. `table_not_found`: The SQL referenced a canvas table that does not exist — expired, dropped, or a mistyped name. `sql_rejected`: SQL was not a SELECT statement, referenced a system catalog, or contained disallowed functions. Other values are possible when a failure originates below the handler."
},
"recovery": {
"type": "object",
"required": [
"hint"
],
"properties": {
"hint": {
"type": "string"
}
},
"description": "Actionable next step for the caller.",
"additionalProperties": {}
},
"retryable": {
"type": "boolean",
"description": "Whether retrying may succeed."
}
},
"additionalProperties": {}
},
"message": {
"type": "string",
"description": "Human-readable description of what went wrong."
}
},
"description": "Present when the call failed. Absent on success.",
"additionalProperties": {}
},
"shown": {
"type": "number",
"description": "Rows returned in this response when capped."
},
"notice": {
"type": "string",
"description": "Guidance for this page: how to adjust the SQL when it returned zero rows, or how to reach the rest when rows were capped, naming the requested and served limit when a limit above the 10,000-row per-call cap was served as 10,000. Absent otherwise."
},
"canvas_id": {
"type": "string",
"description": "Canvas ID queried."
},
"row_count": {
"type": "number",
"description": "Number of rows returned."
},
"truncated": {
"type": "boolean",
"description": "True when results were capped at the limit — more rows match the query."
}
},
"additionalProperties": false
}{
"readOnlyHint": true,
"openWorldHint": false,
"idempotentHint": true
}Prompts
1 advertised
explore_open_data
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.
- topic
- Civic data topic or question to investigate (e.g. "traffic collisions in 2023", "food inspection failures", "311 service requests by neighborhood"). (required)
- 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.
Resources
3 advertised
socrata-portals — Socrata Portal Catalog
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.
socrata://portalsSeattle 911 Incidents (example) — Socrata Dataset Metadata
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).
socrata://datasets/data.seattle.gov/kzjm-xkqjsocrata-guide
Investigate civic data with socrata_* tools, check dataset coverage, and choose upstream SoQL or staged DuckDB analysis. Use for government open-data questions involving absence claims, trends, conditional aggregates, or comparisons across datasets.
skill://socrata-guide/SKILL.mdsocrata-dataset — Socrata Dataset Metadata
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).
socrata://datasets/{domain}/{datasetId}io.modelcontextprotocol/skills
skill://{+path}90-day uptime
A lightweight connection check about every six hours, sampled on hosts with many listings. It shows the server answered, not that its tools work.
- 2026-11-07: 1 of 1 checks answered
30-day availability
Aggregated from read-only remote checks; this is not a tool-execution or security guarantee.
- Successful checks
- 1 of 1 (100%)
- Average latency
- 689 ms
- 95th percentile latency
- 689 ms
Maintenance history
Public check and source-refresh events. Editorial and ownership records remain private.
- · probe · passed · 689 ms · metadata changed · 6 tool(s), 3 resource(s), 2 resource template(s), 1 prompt(s)
Connect from your client
Each snippet registers the same endpoint; pick the client you use.
Claude Code
claude mcp add --transport http socrata-mcp-server-caseyjhand https://socrata.caseyjhand.com/mcpCodex
codex mcp add socrata-mcp-server-caseyjhand --url https://socrata.caseyjhand.com/mcpCursor
{
"mcpServers": {
"socrata-mcp-server-caseyjhand": {
"url": "https://socrata.caseyjhand.com/mcp"
}
}
}Cline
{
"mcpServers": {
"socrata-mcp-server-caseyjhand": {
"type": "streamableHttp",
"url": "https://socrata.caseyjhand.com/mcp",
"disabled": false
}
}
}VS Code
{
"servers": {
"socrata-mcp-server-caseyjhand": {
"type": "http",
"url": "https://socrata.caseyjhand.com/mcp"
}
}
}Gemini CLI
gemini mcp add --transport http socrata-mcp-server-caseyjhand https://socrata.caseyjhand.com/mcpClaude Desktop
https://socrata.caseyjhand.com/mcpWindsurf
{
"mcpServers": {
"socrata-mcp-server-caseyjhand": {
"serverUrl": "https://socrata.caseyjhand.com/mcp"
}
}
}Let your agent find its tools
Find servers like socrata-mcp-server from your agent. Add Koskamo once to search the index for endpoints, access requirements, and client setup. Public discovery needs no key.