Every legal, policy, and AI governance team I have talked to in the last six months is trying to keep up with the same thing — fifty states writing AI legislation in parallel, at different speeds, in different vocabularies, with nobody centralizing the picture. The federal answer is moving slowly. The state answer is moving fast. And the people who actually need to keep track of it have been stitching the picture together by hand, one statute search at a time, across fifty separate websites.
That is the problem a Naftiko Capability is built for. Not “write us a chatbot.” Not “stand up an MCP server.” The actual job is: take a real, governed upstream API, scope it cleanly to the unit of work that matters, and expose the result simultaneously as an MCP tool for agents and a REST endpoint for humans — from the same spec, with the same auth, the same rate-limit posture, the same observability. Today I want to walk through exactly that, end to end, using a fresh capability I wired up: manage-openlaw.
The upstream — OpenLaws
OpenLaws publishes a clean, bearer-authenticated legal data API at https://api.openlaws.us. The endpoint I care about for this is GET /api/v1/jurisdictions/{jurisdiction_key}/laws/search, which does keyword search across statutes, rules, regulations, and constitutions for a single U.S. jurisdiction. jurisdiction_key is the 2-letter postal code — CA, TX, NY, AL, all the way through WY. Search supports or, and, and phrase query types. There is a with_federal flag if you want to pull in federal sources alongside the state. The response is an array of Division objects — the OpenLaws name for a hierarchical document inside a body of law.
That is the wire-level surface. It is exactly the kind of surface that, before capabilities, every team would have wrapped fifty times in fifty slightly different scripts. Below, it is wrapped exactly once.
The capability — manage-openlaw
The shape of the YAML is straightforward and intentionally repetitive. One consumes block per state. Fifty of them. Each one names a different jurisdiction in the path, but otherwise looks identical — same base URI, same bearer token, same query pre-loaded to artificial intelligence, same type=phrase so the search lands on real AI legislation rather than every law that mentions either word in passing.
Here is the Alabama block, lightly trimmed:
naftiko: "1.0.0-alpha2"
info:
title: Manage OpenLaw
description: >
Searches OpenLaws (https://api.openlaws.us) for artificial intelligence
legislation across all 50 U.S. state jurisdictions. One consume namespace
per state, plus a 50-state aggregator. Exposed as both MCP and REST.
capability:
consumes:
- namespace: openlaws-al
type: http
baseUri: "https://api.openlaws.us"
description: "OpenLaws keyword search scoped to Alabama (AL)."
authentication:
type: bearer
token: "{{OPENLAWS_TOKEN}}"
resources:
- name: laws-search
path: "/api/v1/jurisdictions/AL/laws/search"
operations:
- name: search-ai-legislation
method: GET
description: "Keyword search for AI-related laws in Alabama (AL)."
inputParameters:
- name: query
in: query
value: "artificial intelligence"
- name: type
in: query
value: "phrase"
- name: with_federal
in: query
required: false
- name: limit
in: query
required: false
- name: page
in: query
required: false
# 49 more state namespaces follow (openlaws-ak through openlaws-wy)
If you have read any of the other capabilities I have written about this year, that shape will be familiar. The interesting part is what happens above and below those fifty blocks.
The aggregator — fifty calls, one tool
A capability is not just a passthrough. The whole point of putting a consumes block in the middle is that the exposes block on top can do something that the upstream cannot. In this case, the upstream gives you one state at a time. The capability gives you all fifty at once — as a single MCP tool the agent can call, and as a single REST endpoint a human can hit.
exposes:
- type: mcp
address: "0.0.0.0"
port: 3055
namespace: manage-openlaw
description: >
OpenLaws AI-legislation tracker — search statutes, rules, regulations,
and constitutions across all 50 U.S. states for artificial intelligence
legislation. One tool per state plus a 50-state aggregator.
tools:
- name: search-ai-legislation-all-states
description: "Fan-out: run AI-legislation search across all 50 states and return one merged result set keyed by jurisdiction."
hints:
readOnly: true
destructive: false
idempotent: true
aggregates:
- openlaws-al.search-ai-legislation
- openlaws-ak.search-ai-legislation
- openlaws-az.search-ai-legislation
# 47 more aggregate refs follow
- openlaws-wy.search-ai-legislation
# 50 per-state MCP tools also exposed (search-ai-legislation-al through search-ai-legislation-wy)
- type: rest
address: "0.0.0.0"
port: 8155
namespace: manage-openlaw-rest
resources:
- name: ai-legislation-all-states
path: "/openlaw/ai-legislation"
operations:
- name: search-ai-legislation-all-states
method: GET
aggregates:
- openlaws-al.search-ai-legislation
# 49 more aggregate refs follow
# 50 per-state REST routes also exposed at /openlaw/ai-legislation/<state>
The MCP tool search-ai-legislation-all-states is the headline. An agent calls it once. The engine fans out fifty bearer-authenticated GET requests in parallel against OpenLaws. The results come back keyed by jurisdiction. Claude or GPT can now answer “what new AI legislation is happening across the country right now?” with one tool call, against a real, citable, jurisdictionally-scoped legal data source — not a vibe from training data, not a stale blog post, not a scraped table.
Why fifty consumes, not one parameterized one
Someone will reasonably ask why I did not write one consumes namespace with {{jurisdiction_key}} as a path variable and pass the state in as an argument. That works too. It is a valid alpha2 shape. I chose the fifty-namespace form deliberately, and the reason is governance.
Every state is its own legal jurisdiction. The auth boundary, the rate limit, the retry policy, the cost-attribution tag, the agent-safety label, even the data-residency posture if you are pulling case law for a regulated client — every one of those policies is per jurisdiction. Modeling each state as its own consume namespace means each one is independently observable, independently rate-limitable, independently governable. When the legal team comes back next quarter and says “we need to throttle California separately because we are paying per-call there,” the answer is a one-line change inside the openlaws-ca block, not a recompile of the whole adapter. Bounded contexts at the jurisdiction level. The cost is a longer YAML. The win is fifty separate policy surfaces.
This is the third parent — Domain-Driven Design — doing real work. Fifty states is fifty bounded contexts. The YAML reflects that. The engine respects it. The governance surface inherits it.
One spec, two protocols
The other thing worth pointing at is the dual exposure. The same fifty consumes blocks feed two exposes blocks at once — MCP on port 3055 for Claude, GPT, Cursor, and any other agent runtime that speaks the protocol, and REST on port 8155 for humans, dashboards, CI jobs, journalists, or that one curl one-liner that ends up in every legal-tech newsletter for a week.
Both surfaces are generated from the same spec. The MCP tool descriptions and the REST OpenAPI documentation stay in sync because they are the same artifact. Auth posture, retry policy, observability, agent-safety hints — all uniform. If I update the query from artificial intelligence to a richer string like "artificial intelligence" OR "machine learning" OR "automated decision-making", both the MCP and the REST surface pick it up the next time the capability is deployed. There is no separate “MCP version” and “API version” to keep in sync.
This is the part of the capability story that I keep coming back to in customer conversations. You are not writing one thing for the agent team and a different thing for the platform team. You are writing one capability, and you get both audiences for free.
What this actually unlocks
The reason I picked AI legislation as the example is that the use case is not theoretical — it is the work I am hearing about every week.
→ For a general counsel or compliance lead. “Show me everything that has moved on AI legislation in California, Texas, New York, and Illinois this quarter, with citations I can paste into a memo.” One MCP call inside Claude. Or one curl against /openlaw/ai-legislation from a weekly Slack digest.
→ For an AI policy team. “Build me a fifty-state heatmap of how many AI statutes each jurisdiction has on the books, refreshed weekly, alerting me when a new one lands.” Same capability. The aggregates block already returns a per-jurisdiction result set — wrap it in a small worker that writes to a dashboard, done.
→ For a vendor selling into regulated industries. “When a customer asks how our AI feature aligns with state law in their jurisdiction, I want my SE to be able to ground that answer in real statute text, not a sales deck.” Drop the MCP tool into the SE’s agent. The grounding is real, the citations are real, and the answer is jurisdiction-scoped automatically.
→ For a journalist or researcher. “Pull every mention of automated decision across the fifty states this month, dedupe by statute path, and rank by jurisdiction activity.” Same capability. The fifty-namespace shape means the per-state result is already structured — the consumer does not have to disambiguate “is this California or Colorado?” — the namespace tells you.
The capability itself is read-only, idempotent, and bearer-authenticated. Agent-safety hints are tagged correctly. There is nothing destructive on the surface. It is a clean, governable, fifty-state legal data feed that any agent runtime or any HTTP client can call.
Where the capability lives
The full YAML — all fifty consumes blocks, both exposes blocks, every per-state tool and route — is committed today. It is live in the Naftiko capability repository and mirrored in the api-evangelist all/naftiko index where I publish capability references for the broader community to fork and adapt. If you want to wire it into your own Naftiko deployment, the steps are:
- Pull the spec.
- Provide an
OPENLAWS_TOKEN(request access at openlaws.us/api). - Start the engine. The MCP server lights up on port 3055. The REST API lights up on port 8155.
- Point Claude, GPT, Cursor, or any other MCP client at the MCP endpoint. Point your dashboard, CI, or curl loop at the REST endpoint.
That is the whole story. One YAML. Fifty jurisdictions. Two protocols. One AI-legislation tracker your agents can talk to and your legal team can read.
The thing I want to leave on the record is the pattern, not just the artifact. Anything that needs to be fanned out across fifty (or five, or five hundred) similar upstream surfaces is a capability-shaped problem. Per-tenant adapters. Per-region cloud accounts. Per-country tax APIs. Per-state legal data. The work to wrap one of them is the same as the work to wrap all of them — and you only do it once.
The engine and the fleet are on GitHub at naftiko/framework and naftiko/fleet. The capability index is at naftiko.io. The OpenLaws upstream is at openlaws.us.