Skip to main content
Glama
cambridge-open-data-ma-mcp

cambridge-open-data-ma-mcp

by ali5ter

cambridge-open-data-ma-ai

An MCP server, a Skill to enable an AI agent or agentic harness to discover, fetch, cross-reference, and infer insights from the Cambridge MA Open Data Portal; a Socrata portal with 280+ public datasets (budget, public safety, permitting, GIS/parcels, public health, schools, and more).

This enables natural-language questions about Cambridge civic data and responses built from real cross-dataset analysis.

What do you get?

  • An MCP server — cambridge_data.mcp (console script cambridge-open-data-ma-mcp), the agent-facing interface: eight tools and four resources an AI agent uses to discover, fetch, cross-reference, and query the Portal.

  • A CLI — cambridge-data, the human-facing interface over the same shared library (src/cambridge_data/): the eight tools as subcommands, so the agent path and the shell path are one call set and the two never drift (one source of truth).

  • A Skill — .claude/skills/cambridge-open-data/SKILL.md, the cognitive interface: the discover → fetch → cross-reference → infer playbook plus the gotchas that keep the agent's answers trustworthy.

  • Documentation — docs/ explaining how to install, configure, and use the Skill and the MCP server as complementary tools in your AI interaction with the Cambridge Open Data Portal.

Related MCP server: CKAN MCP Server

Eight tools, four resources

The server and the CLI expose the same eight tools — refresh_catalog, search_datasets, describe_dataset, fetch_dataset, field_coverage, coverage_audit, list_cached_datasets, and query — plus four opencode-native resources — cambridge://catalog, cambridge://datasets/{id}, cambridge://reports, and cambridge://reports/{slug}. The CLI subcommands are refresh, search, describe, fetch, coverage, audit, cached, query, catalog, and reports/report. Argument shapes, returns, and the CLI mirror are in docs/reference.md.

Install

uv is the recommended runner. From a clone of this repo:

uv sync                                                   # install dependencies
mkdir -p ~/.local/bin && ln -sf "$PWD/scripts/cambridge-data" ~/.local/bin/cambridge-data
cambridge-data refresh        # pull the 280+ dataset index (seconds)
uv run scripts/mcp_smoke.py   # end-to-end check
NOTE

Theln -sf line puts the CLI on your PATH. Add ~/.local/bin to your PATH if it isn't already, and cambridge-data runs from any directory — no uv run, no repo-relative CWD.

The smoke test drives the live server and, with a populated cache, returns a report of tests. To do this, three datasets need to be retrieved from the Portal, to create a catalog the smoke tests can run against:

cambridge-data fetch 9qm7-wbdc    # building permits — the coverage audit
cambridge-data fetch ixg8-tyau    # salaries — the cross-dataset SQL
cambridge-data fetch 5bn4-5wey    # operating budget — the wrong-name error

Registering the MCP server

These are the commands to register the MCP server on the system where you run your AI agentic harness:

Harness

Register

opencode (TUI / Desktop / Web)

opencode mcp add cambridge-open-data-ma-ai -- uv run cambridge-open-data-ma-mcp — writes opencode.json

Claude Code

claude mcp add cambridge-open-data-ma-ai -- uv run cambridge-open-data-ma-mcp

Cursor

.cursor/mcp.json: { "mcpServers": { "cambridge-open-data-ma-ai": { "command": "uv", "args": ["run", "cambridge-open-data-ma-mcp"] } } }

Cline / Roo (VS Code)

add a stdio MCP server in settings: command uv, args run cambridge-open-data-ma-mcp

The same stdio server registers with Codex, Gemini CLI, and Antigravity by a single command each; HTTP-only clients (OpenWebUI, ChatGPT, Claude.ai) need a bridge because the server is stdio-only. The registration commands give every harness's exact form, including the bridge.

The shareable registration is opencode.json at the repo root — that is OpenCode's own config, so copy it in and the key cambridge-open-data-ma-ai is enough. The table above shows each other harness's equivalent form; the server is a stdio FastMCP server, so the launch line is the same for all of them and only the config shape differs.

Installing the Skill

The Skill is the operational playbook for the discover → fetch → cross-reference → infer workflow. It ships in two forms:

  • Canonical — .claude/skills/cambridge-open-data/SKILL.md, the Agent Skills form: a folder + SKILL.md with name/description frontmatter, read natively by OpenCode, Claude Code, Antigravity, and Gemini CLI. Drop the folder into the project or ~/.claude/skills/ (OpenCode and Claude Code also read it in-repo).

  • Harness-agnostic mirror — skills/cambridge-open-data/SKILL.md, the same body with the frontmatter and self-audit step removed, ready to paste into a harness that ingests a rules or agent file (Cursor, Cline / Roo, Codex, and the like).

Each harness's own delivery is tabulated in the Installing the Skill section of install.md.

Worked use-cases (the docs/ tutorials)

  1. Budget — FY2027 salary vs. operating budget, cross-referenced by department (ixg8-tyau ⋈ 5bn4-5wey).

  2. Parking tickets — the recent-window trap: the portal cannot answer "tickets paid vs. issued, revenue lost" for the full history.

  3. Privately owned public spaces — 28 spaces, 13 governing special permits, found by spatial discovery across the Open Space and PUD zoning layers.

  4. Property profile, 80 Thorndike St — address + the building-permit era gap (current datasets start 2018-07).

  5. First Street Garage / East End House — three distinct legal instruments; a headline finding that a childcare commitment was never fulfilled (existence is not delivery).

  6. Drive the Skill in opencode — a harness how-to: the Skill running inside opencode against the live MCP server, end to end.

Items 1–5 are grounded analyses of real portal data; a few of them trace the author's own research, so read the findings as that research and verify before relying on them. Item 6 shows how to drive the whole workflow from a popular harness rather than by hand. See docs/tutorials for the full walkthroughs and the gotchas that make the answers trustworthy.

Configuration

Secrets and per-machine state live in .env — copy .env.template and fill in. The four CAM_OPEN_DATA_* credentials are optional; two storage overrides (CAM_OPEN_DATA_CACHE_DIR, CAM_OPEN_DATA_REPORTS_DIR) default to <repo>/data and <repo>/docs/reports. The opencode key and the cambridge:// resource namespace are not environment variables — they are a client-side label, not a secret.

Updating the docs

The website at https://ali5ter.github.io/cambridge-open-data-ma-ai/ is built from docs/ using VitePress; every push to main rebuilds and redeploys it automatically.

To change the site, edit the Markdown in docs/ and push — or click the "Edit this page" link in a page's footer to open its source file on GitHub. The link is a small footer element (a pencil icon and text), present on every documentation page (Guide, Tutorials, Reference); VitePress omits the doc footer on the homepage (layout: home) and the 404 page, so it is absent there. It is a public hyperlink — every visitor sees it — and GitHub then gates the action: a contributor with write access commits directly, everyone else is routed to "Fork and propose changes." The root README.md is the GitHub project page, not part of the site build, so the two surfaces are edited independently.

License

MIT — see LICENSE.

Available Tools

8 tools
coverage_auditC

Automated field-population audit: scan every field of a cached dataset and return the least-populated first -- the automated version of 'check field population before trusting a total'. This is how you discover, up front, that a 'complete-looking' column is really a recent window. Use it instead of hand-checking fields one at a time.

Inputs: dataset_id (required), top, threshold. Returns: object

describe_datasetA

Full schema and coverage profile for one dataset. Returns the machine field names (query with these, not the display names), their types, display names, tags, update cadence, and -- when the dataset is cached -- the row count and the dataset's own earliest/latest date (the era-gap signal: a zero result for an old date may mean the dataset predates it, not that nothing happened). Set probe=true to also get coverage_flags: fields that are only partly populated -- the recent-window traps to check before trusting any total or rate.

Inputs: dataset_id (required), probe. Returns: object

fetch_datasetA

Pull every row of a dataset via the SODA API and cache it as Parquet (data/raw/.parquet), so it can be cross-referenced in SQL. Supports a server-side $where filter and $select projection. Re-running overwrites the cache; check describe_dataset's cached/fetched_at first to avoid re-pulling time-sensitive data unnecessarily. Large datasets (millions of rows) take time; prefer a $where filter when a subset suffices.

Inputs: dataset_id (required), where, select. Returns: object

field_coverageA

Probe how ONE field is actually populated across a cached dataset -- the defence against the recent-window trap. Reports the populated fraction and, for populated rows, the date range they span, and states a warning when the pattern looks like a recent operational window rather than the full history (e.g. a 'balance' field only filled for the last year). Always run this before summing or rate-computing over a field. The field must be a machine name from columns_field_name; a display name raises an actionable error listing the real fields.

Inputs: field (required), dataset_id (required), date_field. Returns: object

list_cached_datasetsA

List the dataset ids whose rows are cached locally in data/raw/. Only cached datasets can be queried in SQL or audited for coverage; use fetch_dataset to pull one that is not yet cached.

Inputs: no named parameters. Returns: object

queryA

Run a DuckDB SELECT over the cached datasets, exposed as one SQL view per dataset named ds_<id_with_underscores> (e.g. ixg8-tyau -> ds_ixg8_tyau). This is the cross-referencing step: join/aggregate across ds_* views. Every Socrata column is a string -- use TRY_CAST(col AS DOUBLE) for numeric work, and expect join keys (e.g. department names) to need normalization before they line up. NOTE: runs arbitrary SQL over local Parquet only; a trusted single-user tool, not sandboxed. Prefer describe_dataset + coverage_audit first so you know the fields exist and are fully populated.

Inputs: sql (required), limit. Returns: object

refresh_catalogA

Refresh the local catalog cache from the live Socrata Discovery API. Pulls the full dataset listing (~280 datasets: id, name, description, fields, tags, update cadence) into data/catalog.parquet. Run first if search_datasets reports a missing cache, or when the portal may have new datasets. Needs no credentials; the app token in .env only lifts the anonymous rate limit. Returns the count of datasets cached.

Inputs: no named parameters. Returns: object

search_datasetsA

Search the cached dataset catalog by keyword. Matches name, description, category, and tags. Returns each result's id, name, category, link, and -- crucially -- columns_field_name, the machine field names a query actually accepts (NOT columns_name, the human display names; querying with a display name silently returns zero rows). Use this to find a dataset, then describe_dataset to get its full schema before querying.

Inputs: keyword (required), limit. Returns: object

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

Loading tool schema changelog…

    TDQS

    A3.8/5.0

    Scored across 8 tools

    Disambiguation4/5

    Most tools have clearly distinct pipeline roles: refresh_catalog (metadata cache), fetch_dataset (row cache), search/describe (metadata lookup), query (SQL). The only real overlap is field_coverage vs coverage_audit (one field vs all fields), but the descriptions explicitly frame the audit as the batch alternative to the manual check.

    Naming Consistency4/5

    Six of eight names follow a clear verb_noun pattern (refresh_catalog, search_datasets, describe_dataset, fetch_dataset, list_cached_datasets), plus the short query. field_coverage and coverage_audit break the pattern as noun phrases, a minor but noticeable deviation.

    Tool Count5/5

    Eight tools is well-scoped for a dataset-portal pipeline: discovery, schema, fetch, cache listing, coverage checks, and SQL querying, with no redundant entries. Each tool earns its place in a coherent workflow.

    Completeness4/5

    The surface covers the full lifecycle from catalog refresh through schema inspection, fetching, coverage auditing, and cross-dataset SQL. Minor gaps exist: no way to delete/evict cached data, no export or pagination of query results, and no catalog filtering by category or update date beyond keyword search.

    Maintenance

    ActivityMaintained
    ResponsivenessNo recent community issues

    Related MCP Connectors

    Related MCP Servers

    • A
      license
      A
      quality
      D
      maintenance
      Enables AI assistants to search, explore, and query San Francisco's open data portal through a standardized interface for public datasets. It supports SQL-like querying via the Socrata platform and includes features like fuzzy column matching and schema caching.
      4
      MIT
    • A
      license
      A
      quality
      A
      maintenance
      Enables AI assistants to search, explore, and query any CKAN open data portal through natural language, making public datasets accessible without requiring knowledge of the portal's API.
      20
      2,654 npm
      60
      MIT
    • A
      license
      Not graded
      quality
      B
      maintenance
      Enables querying City of Boston open data through the CKAN API, providing access to datasets from data.boston.gov via natural language.
      116 npm
      MIT
    • A
      license
      Not graded
      quality
      B
      maintenance
      Enables querying and exploring the City of Worcester's open data portal (ArcGIS Hub) through natural language, including dataset search, type filtering, schema inspection, spatial point-in-polygon queries, and geocoding.
      MIT