DrupalCamp Asheville 2026

DKAN+AI: From Catalog to Conversation


AI agents on DKAN, connected with MCP (Model Context Protocol).

Dan Goodwin · Senior Engineer, Drupal + React · CivicActions

Modules: dkan_ai_query  ·  dkan_mcp_server

Who I am

Dan Goodwin


Senior Engineer, Drupal + React

Apex, NC

  • Drupal since D6; now focused on federal open data
  • I write custom DKAN AI modules and the React frontends that consume them
  • Exploring how AI can lower the barrier between open data and the people who need it

Off the clock: guitar, Aikido.

Dan Goodwin
The promise

Open data is a simple promise


Governments and institutions publish what they know — and let anyone use it.

  • In the U.S., it's often the law. Federal agencies have been required to publish data open by default since a 2013 executive order, codified permanently by the OPEN Government Data Act in 2019.
data.gov city & county portals health agencies transit & environment

The infrastructure got good — fast APIs, reliable catalogs, (sometimes) careful metadata.

That's not the problem anymore.

The promise · what good looks like

The bar the community set: FAIR


Findable

Rich metadata in searchable catalogs; stable identifiers.

Accessible

Retrievable over standard, open protocols — plain HTTP APIs.

Interoperable

Shared formats and vocabularies — CSV, JSON, DCAT.

Reusable

Open licenses, provenance, documentation — data dictionaries.

Written for machines as much as people — which is about to matter.

FAIR gets data to the doorstep. It still doesn't answer a question.

The gap

The answer is in here somewhere


Somewhere in a 12-million-row CSV is the number a reporter, a researcher, or a neighbor came for.

Getting there takes real work:

  • knowing which question to ask
  • understanding what the numbers mean
  • making sense of the answer
Illustration of an endless grid of spreadsheet data extending toward the horizon, with a single cell glowing orange among millions of muted ones.

That gap is where most people stop.

The gap, precisely

"Published" ≠ "answered"


Two plateaus of published data separated by a dark chasm, connected by a single glowing amber arc. A dataset is published A person got their answer

Everything after this slide is about closing the gap.

DKAN in four minutes

What DKAN is


  • The Drupal distribution for open-data catalogs, in active use since 2013. Inspired by CKAN (another open-data catalog platform), built on Drupal.
  • Maintained by CivicActions. Many public agencies run their data portals on it.
  • DKAN 4 shipped on Drupal.org in March 2026 — composer require drupal/dkan, like any module.
Why you should care even if you'll never run DKAN: it's a clean, real-world example of structured public data with an API — exactly the shape AI agents are good at.
×
DKAN in four minutes

Two halves: Metastore & Datastore


Metastore

The records describing the data. CRUD over metadata (DCAT, the standard open-data metadata format): titles, descriptions, distributions, schemas.

→ tells you what exists.

Datastore

The rows themselves, made queryable via the DatastoreQuery endpoint.

→ lets you compute on it.

The AI tools discover through the metastore and answer through the datastore.

DKAN in four minutes

Anatomy of a dataset page


  • Everything up top is the metastore: title, topics, description, publisher, distributions
  • The table is the datastore: 2,969 rows — sortable, searchable, queryable
  • "Also available via the API" — everything a human sees here, a tool can fetch
DKAN dataset page for Florida Bike Lanes: publisher card, topic tags, description, a CSV distribution link, and an interactive 2,969-row data table with sortable, searchable columns.
DKAN in four minutes

The DatastoreQuery language — not raw SQL


{
  "conditions": [
    { "property": "region", "value": "Southeast", "operator": "=" }
  ],
  "properties": [
    "park_name",
    { "expression": { "operator": "sum",
        "operands": ["sightings"] }, "alias": "total" }
  ],
  "groupings": [{ "property": "park_name" }],
  "sorts": [{ "property": "total", "order": "desc" }]
}

A bounded query language: filters, sorts, pagination, and aggregates (sum/count/avg/min/max + groupings).

Deliberately bounded: no window functions, subqueries, self-joins, CTEs. Remember this — the bound becomes a safety feature later.
Why AI + open data

The three jobs an end-user actually does


1

Discover the schema

"What's even in here?"

2

Form the query

"How do I ask for it?"

3

Pick the right visualization

"What does this look like?"

These are exactly the three things that stop people.

Why AI + open data

…map directly onto AI strengths


The user's job What AI is good at
Discover the schema reading & structuring unfamiliar fields
Form the query translating intent into a formal query language
Pick the right visualization matching data shape to a chart

The pairing isn't a gimmick — AI models are strong at exactly the interpretive layer people get stuck on. The deterministic catalog underneath keeps them honest.

How we'll close it

Two ways to close the gap


A · End-user UX

A chat box on the site.

dkan_ai_query

for the visitor

B · Agent access

Expose the whole site to an AI agent.

dkan_mcp_server

for the developer / operator

Same goal, two audiences — though A quietly depends on B: dkan_ai_query builds on a submodule that ships inside dkan_mcp_server.

How we'll close it · the Drupal AI Initiative

Drupal has names for these two ways


Inside AI

People use AI inside Drupal — assistants, in-product workflows, page building.

dkan_ai_query is Inside AI for open data.

Outside AI

Agents use Drupal from outside — connect, inspect, change, verify.

dkan_mcp_server is Outside AI for open data.

The bridge between them: dkan_query_tools — one protocol-neutral service layer that both workstreams consume.

Drupal AI Initiative, June 2026: one roadmap, two workstreams.

Demo 1 · dkan_ai_query

Meet the chat widget


"Ask in plain English — which parks had the most bear sightings? — and get an answer and a table, grounded in real queries. Ask for a chart and you'll get one, inline."

  • A Drupal block; vanilla-JS library (no framework)
  • Sanitized markdown · interactive tables + CSV · inline Vega-Lite charts
  • A status pill shows the work ("Calling query_datastore…"), then the answer lands
Chat widget answering 'Which parks had the most bear sightings in 2024?' with a ranked table led by Great Smoky Mountains National Park at 3,066 sightings, plus sample-row and datastore-query result panels.
● Live demo

Bear sightings, asked in English


Dataset: Bear Sightings in U.S. National Parks (2019–2024) · 96 rows

  • Which parks had the most bear sightings in 2024?
  • Show me the trend of bear sightings in Great Smoky Mountains
  • Which region has the most sightings? …and the fewest?
  • What share of sightings comes from each species? Show it as a pie chart
  • What's the average weight of the bears?
Demo 1 · under the hood

What just happened


question ai_agents loop discovery tools query_datastore answer + artifacts
  • A Drupal AI Agent (ai_agents, max 10 loops) with ~16 function-call tools.
  • Mandated workflow: find resource → get schema (exact, case-sensitive columns) → sample / distinct values → query.
  • It never guesses column names, and never does math in its head — every number routes through a tool.
Demo 1 · under the hood

The charts are model-authored


The AI writes the full chart definition via a create_chart tool. The tool only returns a placeholder — the real chart is captured on the server and drawn with a charting library (Vega-Lite).

Intent Mark
comparison bar
trend line
distribution histogram
proportion arc
correlation point

Chart-type selection is the model's job — "matching data shape to a chart," the AI-strengths mapping in practice.

Demo 2 · dkan_mcp_server

From a chat box to an agent


Now hand the whole site to an agent.

  • MCP — Model Context Protocol. A standard way for an AI client (Claude Code) to discover and call tools.
  • Anthropic's open standard (Nov 2024); now under the Linux Foundation's Agentic AI Foundation.
  • Tens of thousands of public MCP servers. This module makes DKAN one of them.
● Live demo

An agent explores the site like a new hire


Claude Code, connected over stdio (its terminal, not the network) · drush dkan-mcp-server:serve

  • List the DKAN tools you have access to. As we go, tell me which ones you use.
  • What's on this site?
  • What do we know about bears?
  • Break sightings down by bear weight
  • How did this data get onto the site?
Demo 2 · what it exposes

38 tools, in groups


38

25 read 13 write

One #[Tool] attribute plugin each. Tools are thin — they forward to plain Drupal services.

Group #
metastore (catalog) 9
datastore (query) 8
harvest 7
write 10
status / search / resource 4

3 of the "harvest" group's tools are also writes (register/run/deregister) — that's where the 13 comes from.

Demo 2 · permissions in action

Same server, two connections


Read-only (anonymous)

tools/list25 tools

the 13 writes are invisible

Write role

tools/listall 38

and delete_dataset works

Any MCP-aware client works out of the box — the gate, not the client, decides what's visible.

Architecture · for the developers

Where the AI actually lives


dkan_ai_query

The AI model lives here — via the Drupal AI module + AI Agents.

This module is an agent.

dkan_mcp_server

No AI model at all. A pure tool server.

This module serves an agent.

Keys live in the Drupal key module, never in code.

Architecture · for the developers

Built on the Drupal AI ecosystem


dkan_ai_query

Built on ai + ai_agents — the AI Initiative's foundation modules.

Works with any provider module — swap by settings, not code:

Anthropic OpenAI local — LM Studio / Ollama

dkan_mcp_server

Built on contrib mcp_server + the official MCP PHP SDK.

Protocol, transports, JSON-RPC — all upstream. This module adds only the DKAN tools and the access gate.

The DKAN modules are a domain layer on community foundations — no bespoke LLM plumbing, no bespoke protocol code.

Architecture · for the developers

Build once, expose twice


Chat widget

Inside AI — the agent loop runs in Drupal (ai_agents).

MCP client

Outside AI — the loop runs in the client; the server only serves tools.

↘    ↙

dkan_query_tools · plain Drupal services DKAN metastore / datastore

Each surface is a ~10-line adapter over the same service methods — a future protocol is an adapter, not a rewrite.

The permission gate from the demo sits below both transports — stdio and HTTP behave identically.

Architecture · for the developers

Keeping token usage reasonable


  • Row caps everywhere: ≤500 datastore, ≤100 metastore, ≤50 search
  • Truncated descriptions (200 chars); heavy spatial fields dropped
  • The expensive catalog read is permanently cached
Errors come back structured.

An unknown_column error carries available_columns — the agent fixes itself in one turn instead of round-tripping. Cheaper and faster.

The whole design assumes tokens cost money.

The hard parts · being honest

Cost & latency


3–30s per turn

  • Each turn holds a server process the whole time it's thinking — no background job, no cancel button yet
The tradeoff:

More concurrent conversations needs more server capacity. Cheap models for the small jobs, and a mock mode for demos, keep costs down.

The hard parts · earning trust

Grounding, refusals, sanity flags


  • Every number traces to a tool result — not model memory
  • Refusals and errors are typed — enums and sanity flags (zero_rows, row_cap_hit), so they're scorable
  • Deterministic validators where prompts fail — a server-side check fixed what three prompt rewrites couldn't

This is how you make an answer defensible to a reporter or researcher.

40% → 100%

eval-harness pass rate across five phases. Query-limit false confidence: 30% → 0%.

The hard parts · proving it

How we know it works


edit the prompt → v14 run the eval — 30-case golden set pass? ship & sync
  • The system prompt is a versioned file (v13 today) — not a config blob tweaked in prod
  • 30 cases run against the live agent: expected answers, expected refusals, forbidden patterns
  • Every stored answer records its prompt version + tool calls — regressions are traceable after the fact
CI for the AI layer. Same discipline as code: version it, test it, then ship it.
The hard parts · a write-enabled MCP server

Who can write, and what's switched on


Who — permissions

Edit, delete, drop, harvest — each its own Drupal permission. stdio defaults to anonymous = read-only; OAuth scopes enforce the same over HTTP.

What's on — tool groups

Toggle whole subsystems. A disabled group is hidden and rejected. Destructive tools (delete, drop, full replace) are flagged honestly.

Honest caveat: --user= stdio elevation is "same trust level as drush uli" — never expose beyond a trusted shell.
Close

Open data is in its own transition


Like the developer learning to work with AI agents, open data is mid-transition too.

The question isn't whether these tools replace the platforms we've built.

It's whether we can help the data we've already published finally reach the people it's for.

Close

Three things to take home


  1. The gap is interpretation, not infrastructure.
  2. The catalog keeps the AI honest; the AI makes the catalog reachable.
  3. Determinism where it counts, AI where it helps.
Thank you

Questions?


Demo site is live — throw "can it do X?" questions at it.