seattle-open-data
A read-only MCP server for Seattle Open Data (Socrata SODA2) that lets an agent find, understand, and query City of Seattle datasets.
Search datasets (
seattle_data_search_datasets): find datasets by topic words, category, or most-viewed; returns candidates with match evidence, queryability status, paging, and up to 3 external (non-queryable) matches.Describe a dataset (
seattle_data_describe_dataset): get columns, live row count, actual time coverage, place fields for "in " questions (with reliability), geo columns for proximity queries, column stats, and sample rows.Query a dataset (
seattle_data_query_dataset): run bounded reads and aggregates using SoQL clauses (select, where, group_by, having, order_by, near, search_text) or raw SoQL; cursor-paged up to 10,000 rows, with query/download URLs for full extracts.Read reference material (
seattle_data_get_reference): capability summary, SoQL help, curated guides for flagship datasets, and the full error catalog.Works over stdio with Claude Desktop, Claude Code, Codex, or any MCP client, with optional
SOCRATA_APP_TOKENsupport.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@seattle-open-datahow many 311 requests were reported in Ballard last month?"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
seattle-open-data
An agent-friendly MCP server for Seattle Open Data (Socrata SODA2).
Status: alpha (0.2.0). All four tools work against the live portal, with curated guides for seven flagship datasets. See evals/results for the agent evaluation.
Tools
Tool | Purpose |
| Find datasets by topic |
| Columns, row count, time coverage, place fields, geo columns |
| Bounded reads and aggregates (SoQL clauses or raw SoQL) |
| Capability summary, SoQL help, guides, error catalog |
Related MCP server: data-seattle
Install
The server runs over stdio. The uvx commands below need uv.
Claude Desktop: download seattle-open-data-<version>.mcpb from the latest release and open it with Claude Desktop.
Claude Code:
claude mcp add seattle-open-data -- uvx seattle-open-dataCodex:
codex mcp add seattle-open-data -- uvx seattle-open-dataAny other MCP client: run uvx seattle-open-data as a stdio server:
{
"mcpServers": {
"seattle-open-data": { "command": "uvx", "args": ["seattle-open-data"] }
}
}Optional app token: set SOCRATA_APP_TOKEN to use a Socrata application token, for example claude mcp add seattle-open-data -e SOCRATA_APP_TOKEN=... -- uvx seattle-open-data. The bundle asks for it in Claude Desktop's settings. A rejected token is reported as app_token_rejected, never silently dropped.
Develop
From a checkout:
uv sync
uv run seattle-open-data # run the server over stdio
uv run pytest
uv run ruff check && uv run ruff format --check && uv run ty check
UPDATE_SNAPSHOTS=1 uv run pytest tests/test_contract.py # after an intended tool-surface change
uv run pytest --run-integration -m integration # live tests against data.seattle.gov
uv run python evals/run.py [--host claude|codex] <out_dir> [task_id ...]
uv run python evals/report.py <out_dir> --answersTo build the MCPB bundle locally (needs the mcpb CLI): make mcpb.
Release
Tag vX.Y.Z after bumping the version: the release workflow builds and smoke-tests the wheel, sdist, and .mcpb, publishes to PyPI after maintainer approval, and attaches all three to a GitHub Release. See docs/releasing.md for the steps, the TestPyPI rehearsal, and one-time setup.
License
MIT
Available Tools
4 toolsseattle_data_describe_datasetSeattle Data Describe DatasetARead-only
Decide whether and how to query a dataset: columns, live row count, data_coverage
(actual time range), place fields for "in " questions (with reliability),
and geo columns for near.
Call before querying a dataset whose columns you do not know.
For area questions, use detail="full".
Filter a place field on its value, never its label; an area may span several values.
Example: {"dataset_id": "kzjm-xkqj", "detail": "full"}.
Typically 1-3 s; fails with query_timeout after 30 s.
| Name | Required | Description | Default |
|---|---|---|---|
| detail | No | summary (default) or full: adds place-field values and column stats. | |
| dataset_id | Yes | Socrata dataset id, e.g. kzjm-xkqj (from seattle_data_search_datasets). | |
| sample_rows | No | Example rows to include. Default 0. | |
| column_query | No | Only columns whose name or description contains this; omit for all. |
Output Schema
| Name | Required | Description |
|---|---|---|
| title | Yes | |
| source | Yes | |
| columns | Yes | |
| guide_uri | No | |
| row_count | Yes | |
| dataset_id | Yes | |
| geo_columns | Yes | |
| sample_rows | No | |
| geo_unusable | No | |
| place_fields | Yes | |
| queryability | Yes | |
| data_coverage | Yes | |
| time_semantics | Yes | |
| example_queries | No | |
| stats_available | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already cover the safety profile (readOnlyHint, openWorldHint), so the description earns credit for adding latency and failure behavior: 'Typically 1-3 s; fails with query_timeout after 30 s.' It also flags a non-obvious data caveat (place values vs labels, areas spanning multiple values), which is genuinely beyond structured fields.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loads the payoff, then usage rules, then an example and a performance note. Dense but every line carries decision-relevant information; the enumerated return list is a touch verbose but justified for a metadata tool.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
An output schema exists, so return formatting need not be explained. The description supplies everything an agent needs to decide whether to call it, choose detail, and interpret place fields, matching the tool's complexity.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so the baseline is 3, but the description adds real decision semantics: when to set detail="full" (area questions) and how to use place fields (filter on value, not label). It also supplies a concrete example call.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb (decide whether/how to query) and enumerates exactly what the tool returns: columns, live row count, data_coverage, place fields with reliability, and geo columns. The mention of querying clearly separates it from seattle_data_search_datasets and seattle_data_get_reference.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Gives a clear trigger ('Call before querying a dataset whose columns you do not know') and conditional guidance ('For area questions, use detail="full"'), plus a field-filtering rule. It stops short of explicitly naming seattle_data_query_dataset as the next step or stating when this call can be skipped.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
seattle_data_get_referenceSeattle Data Get ReferenceARead-only
Read the server's reference: capability summary, SoQL help, dataset guides, and the full error catalog. Call with no arguments for the index.
Example: {"topic": "soql", "item_id": "dates"}.
| Name | Required | Description | Default |
|---|---|---|---|
| topic | No | Default index. | |
| item_id | No | An item from the topic's entries; omit for the topic index. |
Output Schema
| Name | Required | Description |
|---|---|---|
| uri | Yes | |
| body | No | |
| title | Yes | |
| topic | Yes | |
| source | Yes | |
| entries | No | |
| summary | Yes | |
| version | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint=true and openWorldHint=false, so the safety profile is covered. The description adds useful behavioral context beyond that: the no-argument default returns the index, and the reference is organized into named sections that can be drilled into by topic/item_id.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Two tight sentences plus a single example line; the content list is front-loaded and every clause carries information (what is retrievable, default behavior, worked example). No filler.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With an output schema present and zero required parameters, the description does not need to explain return shapes. It covers content scope, default behavior, and parameterization adequately; only an explicit pointer away from the dataset siblings would make it fully complete.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100% and the enum values are self-explanatory, so the baseline is 3. The description adds an illustrative example ({"topic": "soql", "item_id": "dates"}) that demonstrates the two-level topic→item lookup semantics, which is a genuine increment over the schema.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb ('Read') and a well-scoped resource ('the server's reference'), then enumerates exactly what the reference contains: capability summary, SoQL help, dataset guides, and error catalog. This clearly separates it from the dataset-oriented siblings (search/describe/query), which operate on data rather than documentation.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
'Call with no arguments for the index' plus a concrete call example tells the agent how to enter the reference at two levels (index vs. specific item). It gives clear context for use but never explicitly contrasts itself with alternatives such as seattle_data_describe_dataset, so it stops short of full when/when-not guidance.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
seattle_data_query_datasetSeattle Data Query DatasetARead-only
Run a bounded read or aggregate on one dataset; paging stops at 10,000 rows.
For counts and breakdowns, use count() with group_by, not row paging. With cursor, send only dataset_id, cursor, and optionally page_size or detail. For all rows, give the user download_url (detail="full") instead of paging. Example: {"dataset_id": "kzjm-xkqj", "select": ["type", "count() AS n"], "group_by": ["type"], "order_by": ["n DESC"]}. Typically 1-5 s; fails with query_timeout after 30 s.
| Name | Required | Description | Default |
|---|---|---|---|
| mode | No | clauses (default): built from the clause arguments, paged by cursor. soql: run `soql` verbatim, one page. | |
| near | No | Rows within radius_m meters of a point. | |
| soql | No | Full SoQL statement; set mode='soql' and no clause arguments. Not paged: use LIMIT <= page_size and advance OFFSET. | |
| where | No | SoQL predicate, e.g. "datetime >= '2026-09-01T00:00:00'". Never append Z or call now(); both fail. Omit for all rows. | |
| cursor | No | next_cursor from the previous page; omit for page one. | |
| detail | No | summary (default) or full: adds query_url and download_url (CSV of all matching rows). | |
| having | No | Predicate on aggregated rows, e.g. "n > 10"; omit for none. | |
| select | No | Columns or expressions, e.g. ["type", "count(*) AS n"]. Omit for all data columns. | |
| group_by | No | e.g. ["type"]; omit for no grouping. | |
| order_by | No | e.g. ["n DESC"]; omit for arbitrary order. DESC lists nulls first: for most recent, add `col IS NOT NULL` to where. | |
| page_size | No | Most rows to return (default 100; wide rows may return fewer). Omit on a continuation to keep the first page's size. | |
| dataset_id | Yes | Socrata dataset id, e.g. kzjm-xkqj (from seattle_data_search_datasets). | |
| search_text | No | Full-text search across text columns. |
Output Schema
| Name | Required | Description |
|---|---|---|
| rows | Yes | |
| source | Yes | |
| columns | Yes | |
| has_more | Yes | |
| query_url | No | |
| truncated | Yes | |
| dataset_id | Yes | |
| consistency | Yes | |
| next_cursor | No | |
| download_url | No | |
| returned_rows | Yes | |
| effective_query | No | |
| truncation_hint | No | |
| coverage_warnings | No | |
| query_url_omitted | No | |
| truncation_reason | No | |
| download_url_omitted | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations only cover read-only and open-world status; the description adds the operational traits that matter: a 10,000-row paging ceiling, typical latency of 1-5 s, and a hard failure mode ('query_timeout after 30 s'). The cursor continuation constraint is a behavioral rule not derivable from the schema alone.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loads the core operation and row cap, then layers paging rules, an alternative path, a compact JSON example, and timing. Every sentence carries actionable information with no filler.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a 13-parameter query tool with an output schema, the description covers the things the schema and annotations cannot: aggregation-vs-paging guidance, continuation semantics, row limits, and timeout behavior. Nothing an agent needs before invoking it is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so the baseline is 3; the description still adds value by constraining continuation payloads (only dataset_id, cursor, and optionally page_size or detail) and by pairing count(*) with group_by in the worked example.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ('run a bounded read or aggregate on one dataset') plus an explicit scope limit ('paging stops at 10,000 rows'). This cleanly separates it from the sibling search/describe/reference tools, which don't execute queries against a dataset.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Gives explicit routing rules: use count(*) with group_by for counts and breakdowns rather than paging, and hand the user download_url with detail='full' instead of paging for all rows. It also states what to send on a continuation call with cursor, which is exactly the when/when-not guidance an agent needs.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
seattle_data_search_datasetsSeattle Data Search DatasetsARead-only
Find Seattle datasets by topic. Call first when you lack a dataset id.
Results are candidates: match_evidence lists the query words each one's metadata lacks. Only queryability "supported" can be described or queried; a tabular search also returns up to 3 external_matches, which cannot. Example: {"query": "fire 911"}. Typically 1-3 s.
| Name | Required | Description | Default |
|---|---|---|---|
| query | No | Words to match; omit to list the most-viewed. | |
| cursor | No | next_cursor from the previous page; omit for page one. | |
| detail | No | summary (default) or full: adds descriptions. | |
| category | No | Exact category name, e.g. Public Safety; omit for all. | |
| page_size | No | Default 10. | |
| asset_scope | No | tabular (default): queryable datasets and filter views. all: adds federated ArcGIS and external links, which cannot be queried. |
Output Schema
| Name | Required | Description |
|---|---|---|
| source | Yes | |
| datasets | Yes | |
| has_more | Yes | |
| suggestion | No | |
| next_cursor | No | |
| match_evidence | No | |
| external_matches | No | |
| external_search_status | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Adds substantial context beyond readOnlyHint/openWorldHint: match_evidence semantics, the fact that only 1-3 external_matches come back for tabular searches and are unusable, candidate-vs-confirmed result status, and a latency expectation (1-3 s). This is the kind of information an agent cannot get from annotations.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loads purpose and the when-to-use rule, then layers result semantics, an example, and timing in short clauses. Dense but every sentence carries information; the match_evidence/queryability sentences are slightly heavy for a first read.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
An output schema exists and the description still supplies the key result semantics (candidates, match_evidence, external_matches), plus when to call, an example invocation, and expected latency. An agent has everything needed to call it correctly and interpret the payload.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so the baseline is 3, and the description earns a bump by showing query usage ({"query": "fire 911"}) and clarifying the behavioral consequence of asset_scope=tabular (external_matches returned but not queryable). It does not cover cursor/page_size, which the schema already documents.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
Specific verb+resource ("Find Seattle datasets by topic") with an explicit role in the workflow: "Call first when you lack a dataset id." That sentence alone separates it from seattle_data_describe_dataset and seattle_data_query_dataset, which both require an id.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Gives a clear entry condition (no dataset id yet) and a downstream constraint (only queryability "supported" can be described or queried; external_matches cannot). It does not name the sibling tools it hands off to, so the routing is implied rather than explicit.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
1 tool update
v0.2.0- Changed
seattle_data_search_datasets4 fields changed- added
Output schema / properties / external_matchesAdded value: +{ + "items": { + "additionalProperties": false, + "properties": { + "asset_type": { + "type": "string" + }, + "category": { + "type": "string" + }, + "dataset_id": { + "type": "string" + }, + "derived_from": { + "type": "string" + }, + "description": { + "type": "string" + }, + "external_url": { + "type": "string" + }, + "guide_uri": { + "type": "string" + }, + "portal_url": { + "type": "string" + }, + "queryability": { + "enum": [ + "supported", + "external", + "unsupported", + "unknown" + ], + "type": "string" + }, + "title": { + "type": "string" + }, + "updated_at": { + "type": "string" + } + }, + "required": [ + "dataset_id", + "title", + "asset_type", + "queryability", + "portal_url" + ], + "type": "object" + }, + "type": "array" +} - added
Output schema / properties / external_search_statusAdded value: +{ + "enum": [ + "completed", + "unavailable" + ], + "type": "string" +} - added
Output schema / properties / match_evidenceAdded value: +{ + "items": { + "additionalProperties": false, + "description": "Which query words a result's catalog metadata visibly contains; never a relevance verdict.", + "properties": { + "dataset_id": { + "type": "string" + }, + "matched_fields": { + "items": { + "enum": [ + "title", + "tags", + "columns", + "description" + ], + "type": "string" + }, + "type": "array" + }, + "matched_terms": { + "items": { + "type": "string" + }, + "type": "array" + }, + "matched_via": { + "type": "string" + }, + "unmatched_terms": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "dataset_id", + "matched_terms", + "unmatched_terms", + "matched_fields" + ], + "type": "object" + }, + "type": "array" +} - added
Output schema / properties / suggestionAdded value: +{ + "additionalProperties": false, + "properties": { + "alternative": { + "type": "string" + }, + "arguments": { + "additionalProperties": true, + "type": "object" + }, + "next_step": { + "type": "string" + }, + "tool": { + "type": "string" + } + }, + "required": [ + "next_step", + "tool", + "arguments" + ], + "type": "object" +}
4 tool updates
v0.1.0- First observed
seattle_data_describe_dataset - First observed
seattle_data_get_reference - First observed
seattle_data_query_dataset - First observed
seattle_data_search_datasets
TDQS
Scored across 4 tools
Each tool has a clearly distinct purpose: search for datasets, describe a specific dataset's metadata, access server-wide reference documentation, and query a dataset. The descriptions clearly delineate when to use each, with no overlapping functionality.
All tools follow the consistent pattern seattle_data_<verb>_<noun> (search_datasets, describe_dataset, get_reference, query_dataset). The verbs are distinct but appropriate, and the naming is predictable and readable.
Four tools are well-scoped for a read-only open data query service, covering discovery, metadata inspection, reference, and query execution without redundancy. Each tool earns its place.
The toolset covers the core workflow: search datasets, describe schema, query data, and access reference help. A minor gap is the lack of a tool to list all datasets without a topic query, but this is a minor limitation in an otherwise complete surface for the domain.
Maintenance
Related MCP Connectors
DataSeattle MCP — Seattle open data (data.seattle.gov, Socrata SODA API).
Search and query government open-data portals (Socrata SODA API).
Provides access to Civic Plus - See Click Fix, allowing you to interact with your data via an LLM.…
Query and explore Nova Scotia open datasets via the Socrata SODA API.
Related MCP Servers
- AlicenseNot gradedqualityBmaintenanceEnables AI agents to query and retrieve San Francisco open data from data.sfgov.org via the Socrata SODA API.38 npmMIT
- AlicenseNot gradedqualityBmaintenanceEnables querying Seattle open data from data.seattle.gov via the Socrata SODA API.9 npmMIT
- AlicenseNot gradedqualityBmaintenanceProvides access to Cincinnati open data via the Socrata SODA API, allowing users to query datasets using natural language or direct tool calls.693 npmMIT
- AlicenseNot gradedqualityBmaintenanceEnables searching and querying Orlando Open Data datasets via Socrata SoQL, providing access to metadata and data rows through natural language or direct tool calls.659 npmMIT