KONDWIT MCP
KONDWIT is regulatory intelligence for U.S. pipeline safety. This MCP server lets your AI assistant — Claude, ChatGPT, Cursor, VS Code, Goose, or Microsoft Copilot — ground its answers in the live federal corpus: 49 CFR Parts 190–199, PHMSA interpretations, enforcement actions, court holdings, and Federal Register amendments — with version-aware citations. Same workflows that power the KONDWIT canvas, through one OAuth-protected URL.
Server URL
https://mcp.kondwit.comQuickstart
- Sign up at kondwit.com (existing customers — no extra signup needed).
- Open your MCP host's connector / config and paste the server URL.
- Approve the OAuth consent on
mcp.kondwit.comwhen prompted.
Try these prompts
Once connected, paste any of these into your assistant to see KONDWIT ground its answer in the live corpus with version-aware citations:
1. Retrieve + ground a section
Using KONDWIT, show me 49 CFR 192.619 and explain in plain English what it requires for a pipeline’s maximum allowable operating pressure. Cite the paragraphs.2. Find related provisions
Using KONDWIT, what other pipeline-safety regulations cross-reference or relate to 49 CFR 192.619? Show how they connect.3. Track recent amendments
Using KONDWIT, what are the most recent Federal Register amendments to 49 CFR Part 192, and what changed in each?4. Search + synthesize across sources
Using KONDWIT, find PHMSA interpretations and enforcement actions about pipeline corrosion control and summarize the key requirements an operator must meet.5. Assemble an audit-prep packet
Using KONDWIT, build an audit-prep packet for 49 CFR 192.619: the requirement, the related interpretations and court holdings, and what a PHMSA auditor expects to see.Per-host setup
Each host has a different way to add an MCP server. Pick yours below; the snippets are also available with one-click copy on the in-product /settings/integrations page after you sign in.
Claude Desktop
{
"mcpServers": {
"kondwit": { "type": "http", "url": "https://mcp.kondwit.com" }
}
}claude.ai
https://mcp.kondwit.comChatGPT
https://mcp.kondwit.comCursor
{
"mcpServers": {
"kondwit": { "url": "https://mcp.kondwit.com" }
}
}VS Code
"mcp": {
"servers": {
"kondwit": { "url": "https://mcp.kondwit.com" }
}
}Goose
goose configure # Add remote extension (HTTP), URL=https://mcp.kondwit.comMicrosoft Copilot Studio
Server URL: https://mcp.kondwit.com
Authentication: OAuth 2.0 with Dynamic discovery
Workaround: append &resource=https%3A%2F%2Fmcp.kondwit.com to the authorization URLOAuth scopes
| Scope | Grants |
|---|---|
mcp:tools | Run Kondwit retrieval + workflow tools on your behalf. |
mcp:resources | Read Kondwit canvas resources (TOC, applicability matrix, references, memo preview). |
Revoke any connection at any time from /settings/integrations.
Tool reference
The list below is auto-generated from the live tool registry on every CI build. Tool inputs are JSON-Schema; expect them to evolve as Kondwit's regulatory model evolves.
{/* AUTO-GENERATED — do not edit by hand. */}
{/* Regenerate via: pnpm --filter api exec ts-node -r tsconfig-paths/register --transpile-only scripts/144-generate-mcp-docs.ts */}
_48 tools registered._
### `canvas_add_subquestion`
Add a subquestion node to a canvas session research tree. Returns the new node id so subsequent retrieval calls can target it as parentNodeId. Always creates a new node unconditionally (deterministic). Args accept aliases: sessionId (or canvasSessionId); text (or question). You may also pass the canonical `canvas: { sessionId, subquestionNodeId }` object (the same shape the reads accept) instead of a top-level sessionId.
Input schema:
```json
{
"type": "object",
"properties": {
"sessionId": {
"type": "string",
"minLength": 1,
"description": "Canvas session id"
},
"canvasSessionId": {
"type": "string",
"minLength": 1,
"description": "Alias for sessionId."
},
"canvas": {
"type": "object",
"properties": {
"sessionId": {
"type": "string",
"minLength": 1,
"description": "Canvas session id"
},
"canvasSessionId": {
"type": "string",
"minLength": 1,
"description": "Alias for sessionId."
},
"subquestionNodeId": {
"type": "string",
"minLength": 1,
"description": "Subquestion node id"
},
"parentNodeId": {
"type": "string",
"minLength": 1,
"description": "Alias for subquestionNodeId."
},
"subquestionId": {
"type": "string",
"minLength": 1,
"description": "Alias for subquestionNodeId."
}
},
"additionalProperties": false,
"description": "OBJECT { sessionId (or canvasSessionId), subquestionNodeId (or parentNodeId/subquestionId) } — the SAME canonical canvas binding shape every canvas read accepts; its members alias the top-level session/subquestion args. A bare string is also accepted as the sessionId."
},
"parentId": {
"type": "string",
"description": "Parent node id (defaults to the session root research_question node)"
},
"text": {
"type": "string",
"minLength": 1,
"description": "The subquestion body"
},
"question": {
"type": "string",
"minLength": 1,
"description": "Alias for text."
},
"sortOrder": {
"type": "integer",
"minimum": 0
},
"mode": {
"type": "string",
"enum": [
"always_new"
],
"default": "always_new",
"description": "'always_new' (default) creates a new subquestion node unconditionally."
}
},
"additionalProperties": false
}
```
### `canvas_classify`
Upgrade a candidate evidence node's qualification under a given subquestion to include/maybe/exclude with a required justification. The required `nodeId` is an evidence node id from the "📌 Canvas evidence created" list that a retrieval tool (find_citing_documents / search_corpus) appends to its TEXT output when called with the `canvas` parameter — use one of those node IDs verbatim. Do NOT pass the citing document's own id (e.g. a smart_interpretation cuid); that is not a canvas node id and will be rejected. Same evidence can be include for one subquestion and exclude for another (N×M qualifications). BATCH: pass `items` (an array of {nodeId, subquestionNodeId, classification, justification}) to assess a whole set in ONE call — this is how you close out check_source_coverage's "assess these" block. Args accept aliases: nodeId (or evidenceNodeId); subquestionNodeId (or subquestionId); justification (or rationale).
Input schema:
```json
{
"type": "object",
"properties": {
"nodeId": {
"type": "string",
"minLength": 1,
"description": "The evidence node being classified"
},
"evidenceNodeId": {
"type": "string",
"minLength": 1,
"description": "Alias for nodeId."
},
"subquestionNodeId": {
"type": "string",
"minLength": 1,
"description": "The subquestion context the classification applies under"
},
"subquestionId": {
"type": "string",
"minLength": 1,
"description": "Alias for subquestionNodeId."
},
"classification": {
"type": "string",
"enum": [
"include",
"maybe",
"exclude"
],
"description": "'include' | 'maybe' | 'exclude' — candidates stay candidate by not calling this tool"
},
"justification": {
"type": "string",
"minLength": 1,
"description": "The LLM's reasoning. Required — an 'exclude' without rationale is unauditable."
},
"rationale": {
"type": "string",
"minLength": 1,
"description": "Alias for justification."
},
"items": {
"type": "array",
"items": {
"type": "object",
"properties": {
"nodeId": {
"type": "string",
"minLength": 1,
"description": "The evidence node being classified"
},
"evidenceNodeId": {
"type": "string",
"minLength": 1,
"description": "Alias for nodeId."
},
"subquestionNodeId": {
"type": "string",
"minLength": 1,
"description": "The subquestion context the classification applies under"
},
"subquestionId": {
"type": "string",
"minLength": 1,
"description": "Alias for subquestionNodeId."
},
"classification": {
"type": "string",
"enum": [
"include",
"maybe",
"exclude"
],
"description": "'include' | 'maybe' | 'exclude'"
},
"justification": {
"type": "string",
"minLength": 1,
"description": "The LLM's reasoning. Required."
},
"rationale": {
"type": "string",
"minLength": 1,
"description": "Alias for justification."
}
},
"required": [
"classification"
],
"additionalProperties": false
},
"minItems": 1,
"description": "Batch: an array of {nodeId, subquestionNodeId, classification, justification} — assess every source from check_source_coverage's \"assess these\" block in one call."
}
},
"additionalProperties": false
}
```
### `canvas_set_synthesis`
Write (or overwrite) a synthesis paragraph. Per-subquestion (pass subquestionNodeId): the 'what this subquestion adds up to' summary the LLM authors from its include-tagged evidence — one per subquestion. SESSION-LEVEL (omit subquestionNodeId, or pass target='session'): the overall bottom-line for the whole canvas — recall renders it as the lead. Pass sessionId for the session-level case, or it resolves your most-recent active session. Args accept aliases: text (or synthesis); subquestionNodeId (or subquestionId); sessionId (or canvasSessionId). You may also pass the canonical `canvas: { sessionId, subquestionNodeId }` object (the same shape the reads accept).
Input schema:
```json
{
"type": "object",
"properties": {
"subquestionNodeId": {
"type": "string",
"minLength": 1,
"description": "The subquestion node this synthesis lives under. Omit for a session-level synthesis."
},
"subquestionId": {
"type": "string",
"minLength": 1,
"description": "Alias for subquestionNodeId."
},
"sessionId": {
"type": "string",
"minLength": 1,
"description": "Target session for a session-level synthesis (when no subquestion is given)."
},
"canvasSessionId": {
"type": "string",
"minLength": 1,
"description": "Alias for sessionId."
},
"target": {
"type": "string",
"enum": [
"session",
"subquestion"
],
"description": "'session' writes the overall bottom-line; defaults to per-subquestion."
},
"text": {
"type": "string",
"minLength": 1,
"description": "The synthesis paragraph (markdown allowed)"
},
"synthesis": {
"type": "string",
"minLength": 1,
"description": "Alias for text."
},
"canvas": {
"type": "object",
"properties": {
"sessionId": {
"type": "string",
"minLength": 1,
"description": "Canvas session id"
},
"canvasSessionId": {
"type": "string",
"minLength": 1,
"description": "Alias for sessionId."
},
"subquestionNodeId": {
"type": "string",
"minLength": 1,
"description": "Subquestion node id"
},
"parentNodeId": {
"type": "string",
"minLength": 1,
"description": "Alias for subquestionNodeId."
},
"subquestionId": {
"type": "string",
"minLength": 1,
"description": "Alias for subquestionNodeId."
}
},
"additionalProperties": false,
"description": "OBJECT { sessionId (or canvasSessionId), subquestionNodeId (or parentNodeId/subquestionId) } — the SAME canonical canvas binding shape every canvas read accepts; its members alias the top-level session/subquestion args. A bare string is also accepted as the sessionId."
}
},
"additionalProperties": false
}
```
### `canvas_start_session`
Start (or attach to) a structured-research canvas session and return its sessionId. Call this FIRST before canvas_add_subquestion / canvas_classify / canvas_set_synthesis — those tools require a sessionId they do not create. IDEMPOTENT on the key (caller + anchorSectionId + titleHint): if you pass an anchorSectionId, this RETURNS an existing live session anchored to that section with the same title (when titleHint is given) instead of minting a duplicate — so calling it twice for the same anchor yields the same session and research does not fragment. Without an anchorSectionId there is no stable key, so it auto-attaches to your most recent live session or creates a new one. Pass `subquestions: string[]` to create the initial objectives in this SAME call — their node ids come back so you can attach evidence per-objective without separate canvas_add_subquestion calls. Pass `researchQuestion` for the overall question. Trigger: when a question warrants a structured decomposition (multi-part research, an audit-prep workflow, a memo you intend to build up across several turns) rather than answering inline. Returns: { canvasSessionId, wasCreated, wasReused, wasAutoAttached } — wasCreated=true means a fresh session; wasReused=true means an existing session for the same anchor key was returned; wasAutoAttached=true means a recent session was reused.
Input schema:
```json
{
"type": "object",
"properties": {
"titleHint": {
"type": "string",
"description": "Optional title for a newly created session (e.g., the research question)."
},
"researchQuestion": {
"type": "string",
"description": "The research question for this session — becomes the session's first objective so back-filled / captured evidence reads under it, not a generic bucket."
},
"anchorSectionId": {
"type": "string",
"description": "Optional CFR section id to anchor a newly created session to."
},
"canvasSessionId": {
"type": "string",
"description": "Optional existing session id to reuse (verified for ownership; falls through to auto-attach/create when absent or not owned)."
},
"sessionId": {
"type": "string",
"description": "Alias for canvasSessionId."
},
"subquestions": {
"type": "array",
"items": {
"type": "string",
"minLength": 1
},
"description": "Optional initial objectives — each becomes a subquestion node and its id is returned, folding in the canvas_add_subquestion calls. Attach evidence per-objective in one go."
}
},
"additionalProperties": false
}
```
### `check_source_coverage`
CHECK what your research MISSED — the completeness lint to run before a regulated deliverable. OPENS with the OBLIGATION LATTICE: the anchor section decomposed into its atomic enumerated obligations (one row per (a), (b)(1)…(b)(12), (c)(1)…, each deadline/carve-out + binding cross-references), each with a per-obligation compliance meter. An obligation is COVERED when a captured BINDING/JUDICIAL source anchors to it — a real READ credits it, no classify ritual required (or it is explicitly scoped out); FINALIZED is the higher-confidence sub-state where you also classified that evidence. A BARE obligation — an enforceable requirement with no binding anchor — is the leading gap, RANKED BY FILING RISK (per-paragraph enforcement-citation density), not head-count. Captured + session-read evidence auto-credits to every obligation its paragraph anchor satisfies (no false "0 evidence" rows after a real pull). BELOW the lattice, a SECONDARY source-family lens diffs the corpus neighborhood (court holdings, FR amendments, special permits, PHMSA interpretations, cross-references that EXIST on §X) against what the session actually PULLED, opening with the ranked source GAPS — absent JUDICIAL holdings and unread controlling AMENDMENTS first. This is the orthogonal thing you cannot check yourself: your own omissions. BELOW the gap it shows what you HAVE gathered — the demoted, collapsed annotated bibliography (grouped by objective; per source the authority tier, the classification already recorded, the anchored excerpt, and a bounded supporting dossier). The authority tier reflects each source's OWN corpus label (a BINDING interpretation is shown BINDING, never re-stamped). scope='all' (default) keeps the screened-out sources in the catalog; 'included' drops them. Omit canvasSessionId to check your most recently active session. Captured sources carry a DETERMINISTIC provisional verdict at capture; pass `finalize:true` to accept them as final. Override only the 🤔 maybe rows with `canvas_classify` if you disagree. READ-ONLY: a deterministic set-difference + a bounded corpus gather — it never scores, synthesizes, advances a workflow, or files (those stay in Kondwit).
Input schema:
```json
{
"type": "object",
"properties": {
"canvasSessionId": {
"type": "string",
"description": "Canvas session to compile. Omit to compile your most recently active session."
},
"sessionId": {
"type": "string",
"description": "Alias for canvasSessionId."
},
"citation": {
"type": "string",
"description": "Optional CFR section to key the coverage check to (e.g. \"§192.465\"). Omit to use the dominant / most-recently-worked anchor from your session."
},
"sectionNumber": {
"type": "string",
"description": "Alias for citation."
},
"scope": {
"type": "string",
"enum": [
"included",
"all"
],
"description": "'all' (default) renders the full PRISMA-style auditable trail — the screened-out sources with their exclusion rationale included. 'included' drops the screened-out items and renders only the kept sources."
},
"verbosity": {
"type": "string",
"enum": [
"compact",
"full"
],
"description": "Response size. 'full' (default on Claude) renders the full obligation lattice + cross-references; 'compact' renders the ranked bare-obligation gap + a deep-link to the full coverage view, sized for a ~2,700-token client ceiling. Omit to resolve per client."
},
"maxSources": {
"type": "integer",
"description": "Total source budget across the whole bibliography (default 20, hard cap 40).",
"minimum": 1
},
"assessments": {
"type": "array",
"items": {
"type": "object",
"properties": {
"nodeId": {
"type": "string",
"minLength": 1,
"description": "The evidence node being assessed"
},
"evidenceNodeId": {
"type": "string",
"minLength": 1,
"description": "Alias for nodeId."
},
"subquestionNodeId": {
"type": "string",
"minLength": 1,
"description": "The subquestion the assessment applies under (from the assess-these block)."
},
"subquestionId": {
"type": "string",
"minLength": 1,
"description": "Alias for subquestionNodeId."
},
"classification": {
"type": "string",
"enum": [
"include",
"maybe",
"exclude"
],
"description": "'include' | 'maybe' | 'exclude'"
},
"justification": {
"type": "string",
"minLength": 1,
"description": "The client LLM's one-line reasoning."
},
"rationale": {
"type": "string",
"minLength": 1,
"description": "Alias for justification."
}
},
"required": [
"classification"
],
"additionalProperties": false
},
"minItems": 1,
"description": "Inline assessments — {nodeId, subquestionNodeId, classification, justification} per source from the \"assess these\" block. Written before compiling, so one call returns the assessed deliverable."
},
"finalize": {
"type": "boolean",
"description": "Accept the deterministic auto/provisional verdicts as final (no assess-these nag). The terminal call — captured sources are auto-assessed at capture, so this finishes the deliverable in one shot. Default false (provisionals render flagged + overridable)."
},
"dismissGaps": {
"type": "array",
"items": {
"type": "object",
"properties": {
"dimension": {
"type": "string",
"description": "A coverage family to dispose: court_holdings | amendments | interpretations | cross_refs | special_permits."
},
"sourceId": {
"type": "string",
"description": "A specific source id to dispose (instead of a whole dimension)."
},
"disposition": {
"type": "string",
"enum": [
"out_of_scope",
"reviewed_sufficient"
],
"description": "'out_of_scope' | 'reviewed_sufficient' — required unless clear:true."
},
"note": {
"type": "string",
"description": "Optional one-line reason recorded with the disposition."
},
"clear": {
"type": "boolean",
"description": "Undo: remove this gap disposition so the gap returns to the open list."
}
},
"additionalProperties": false
},
"minItems": 1,
"description": "Dispose coverage gaps so they stop reappearing — [{dimension|sourceId, disposition, note?, clear?}]. A dismissed gap stays dropped across runs; pass clear:true to undo."
}
},
"additionalProperties": false
}
```
### `compare_neighborhoods`
Given two CFR section citations, return both section texts plus a deterministic diff of their 1-hop regulatory neighborhoods — which provisions are shared, which are unique to the first, and which are unique to the second. Use this instead of fetching two sections and comparing them yourself: it runs the same fixed pipeline KONDWIT uses internally, so the comparison evidence is repeatable across calls. Prefer this for "how do the regulatory contexts of §A and §B differ" questions; use the atomic compare_provisions instead only when you want a paragraph-by-paragraph text diff of the two sections themselves (this bundle diffs their neighborhoods, not their paragraph bodies). The result carries a `familiesPresent`/`familiesEmpty` summary so you can see which edge families (interpretations, enforcement, amendments, cross-refs, structural) the two neighborhoods actually covered.
Input schema:
```json
{
"type": "object",
"properties": {
"citationA": {
"type": "string",
"minLength": 1,
"description": "First CFR citation to compare, e.g. \"§192.605\"."
},
"citationB": {
"type": "string",
"minLength": 1,
"description": "Second CFR citation to compare, e.g. \"§195.402\"."
}
},
"required": [
"citationA",
"citationB"
],
"additionalProperties": false
}
```
### `compare_provisions`
Side-by-side structured diff of two CFR sections at the paragraph-body level. Returns paragraph-aligned rows with status (identical / different / only_in_a / only_in_b). Use when comparing analogous provisions (e.g., 192 vs 195) or assessing how an amendment changed wording. This diffs the two sections’ own text; for a deterministic diff of their 1-hop regulatory neighborhoods (which related provisions are shared vs unique to each), use the bundled compare_neighborhoods tool instead. Results carry an authority tier; only BINDING (49 CFR) and JUDICIAL sources state law — POLICY and lower is guidance, not legally binding.
Input schema:
```json
{
"type": "object",
"properties": {
"citationA": {
"type": "string",
"description": "First citation, any common form (e.g. \"192.605\"). Canonical pair member."
},
"citationB": {
"type": "string",
"description": "Second citation, any common form (e.g. \"195.452\"). Canonical pair member."
},
"left_citation": {
"type": "string",
"description": "Alias for citationA."
},
"right_citation": {
"type": "string",
"description": "Alias for citationB."
},
"citations": {
"type": "array",
"items": {
"type": "string"
},
"minItems": 2,
"description": "Two-element array form: [first, second]. Arrays with more than two elements use the first two and ignore the rest."
},
"level": {
"type": "string",
"enum": [
"headings",
"paragraphs",
"full"
],
"default": "paragraphs",
"description": "Diff granularity"
},
"diffMode": {
"type": "string",
"enum": [
"identity",
"words"
],
"default": "identity",
"description": "Spec 158 F09.2 — when 'words', `different` rows include a word-level diff array. Default 'identity' for backwards compat."
}
},
"additionalProperties": false
}
```
### `compare_state_to_federal`
The headline state cross-reference tool — BIDIRECTIONAL. Given a federal § (federalSection), returns which states restate / add to / are stricter than / narrow it, grouped by relation. Given a state delta Section id (stateSectionId), returns the federal §§ that section maps to + the two-axis freshness badge. Both directions read F06's StateFederalCrossRef via the shared read service. COVERAGE: state→federal cross-references are not carried yet, so this tool returns 0 states for every federal section today. The payload says so via `coverage` - an empty result never means "no state modifies this section". Read-only; no inference.
Input schema:
```json
{
"type": "object",
"properties": {
"federalSection": {
"type": "string",
"minLength": 1,
"description": "A federal CFR section number (e.g. \"192.605\") — returns the states that restate / add to / are stricter than / narrow it. Provide THIS or stateSectionId."
},
"stateSectionId": {
"type": "string",
"minLength": 1,
"description": "A state delta Section id — returns the federal §§ that state section maps to, with the two-axis freshness badge. Provide THIS or federalSection."
}
},
"additionalProperties": false
}
```
### `compare_versions`
Return the section-history view for a CFR section: current version + the version covering a given date + the chain of versions between, with plain-text content snippets and a generated textual diff (ADDED/REMOVED fragments) between the earliest-in-range and current versions. Use INSTEAD of describe_section when the question is specifically "what changed about §X recently" — describe_section returns only an amendment count + FR document refs, not the version content itself. Results carry an authority tier; only BINDING (49 CFR) and JUDICIAL sources state law — POLICY and lower is guidance, not legally binding.
Input schema:
```json
{
"type": "object",
"properties": {
"citation": {
"type": "string",
"minLength": 1,
"description": "CFR section citation (e.g., \"§192.605\")."
},
"since_date": {
"type": "string",
"description": "ISO date string. Diff is computed between the current version and the earliest version effective on/after this date. Defaults to 2 years before today."
}
},
"required": [
"citation"
],
"additionalProperties": false
}
```
### `data_freshness`
Reports the ingestion freshness of KONDWIT data sources: when last checked, when last imported, when last detected changes, status (GREEN/YELLOW/RED/UNKNOWN), and the schedule. Use to answer "is your enforcement data current?" type questions with grounded numbers. Defaults to the FEDERAL sources; pass `scope:"all"` for every source including all 52 state jurisdictions, or `sources:["key",...]` for specific keys.
Input schema:
```json
{
"type": "object",
"properties": {
"sources": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter to specific source keys (e.g. [\"phmsa-enforcement-case-details\", \"federal-register\", \"phmsa-interpretations\"]). Keys are exact-match — an unknown key is reported back with did-you-mean suggestions, never silently dropped. Omit for all sources."
},
"scope": {
"type": "string",
"enum": [
"federal",
"all"
],
"description": "Which slice of the source registry to report. \"federal\" (the default when `sources` is omitted) excludes the per-state `state-*` sources; \"all\" reports every source including all 52 jurisdictions. Ignored when `sources` is given — explicit keys always win."
},
"stale_threshold_days": {
"type": "number",
"description": "Spec 158 F09.5 — when set, each source gets a `stale: boolean` field comparing now() to lastImportedAt. Summary gains `staleCount`. Default unset (no staleness derivation).",
"minimum": 0
}
},
"additionalProperties": false
}
```
### `describe_graph`
Returns the KONDWIT Neo4j knowledge-graph schema. Default returns the static curated catalog (10 node labels, 14 edge types, 12 families, 6 reasoning patterns). Pass live:true to introspect Neo4j directly — useful when you suspect schema drift; cached 5 min to amortize the round-trip. Call this before find_related_provisions when you need to ground your edge_types choice.
Input schema:
```json
{
"type": "object",
"properties": {
"live": {
"type": "boolean",
"default": false,
"description": "Spec 158 F09.6 — when true, query Neo4j for the actual current schema (node labels + relationship types). Cached in-memory for 5 min to amortize the round-trip. Static curated schema returned when false (default)."
}
},
"additionalProperties": false
}
```
### `describe_section`
COMPOUND briefing tool. One call returns the full picture for a CFR section: text (snippet by default) + IAQ compliance criteria + enforcement profile + amendment count + recent amendments + citing-doc counts + related sections + recent smart interpretations + pre-rendered Markdown. Use INSTEAD of chaining get_section + find_amendments + find_citing_documents + find_related_provisions + find_documents — describe_section batches all the queries in parallel and returns a coherent one-page briefing. This is the MULTI-FACET BRIEFING lane (reach for it when you need 3+ facets of one section at once). For the 1-hop graph-neighborhood edge set as a single deterministic evidence bundle, use section_neighborhood instead. Single-facet carve-out: if you need only ONE facet, call the atomic tool (get_section for text, find_amendments for history, find_citing_documents for cross-references, find_related_provisions for graph neighbors). IMPORTANT: Amendments derived from PROPOSED rules are excluded by default: an NPRM states what the agency proposed to change, not what changed. Pass include_proposed=true (or read `proposedExcludedCount`) to see them. Both amendmentCount and the recent-amendments list here are filtered that way. A recent-amendment row with provisionScoped=true took effect for OTHER sections of the CFR but NOT for the one you asked about - PHMSA delayed, stayed, or withdrew it for this provision alone, and effectQualifier says which. Its effectStatus still describes the DOCUMENT (which really is in effect elsewhere), so never report the whole rule as withdrawn and never present that row as the current text of this section. Results carry an authority tier; only BINDING (49 CFR) and JUDICIAL sources state law — POLICY and lower is guidance, not legally binding. Interpretations extracted from PROPOSED rules (NPRMs) are served at the GUIDANCE tier, never BINDING: an NPRM states what the agency proposed to require, not what it requires. Each such row carries `proposalDerived: true` and `ruleStatus: "PROPOSED"` — never cite one as a legal requirement.
Input schema:
```json
{
"type": "object",
"properties": {
"citation": {
"type": "string",
"minLength": 1,
"description": "CFR citation in any common form."
},
"body": {
"type": "string",
"enum": [
"full",
"snippet",
"none"
],
"default": "snippet",
"description": "Section body rendering. Default \"snippet\" (800 chars) keeps the briefing token-light; \"full\" returns the whole section text."
},
"verbosity": {
"type": "string",
"enum": [
"compact",
"full"
]
},
"canvas": {
"type": "object",
"properties": {
"sessionId": {
"type": "string",
"minLength": 1,
"description": "Canvas session id"
},
"canvasSessionId": {
"type": "string",
"minLength": 1,
"description": "Alias for sessionId."
},
"subquestionNodeId": {
"type": "string",
"minLength": 1,
"description": "Subquestion node id under which to attach candidate evidence (a.k.a. parentNodeId)"
},
"parentNodeId": {
"type": "string",
"minLength": 1,
"description": "Alias for subquestionNodeId."
},
"subquestionId": {
"type": "string",
"minLength": 1,
"description": "Alias for subquestionNodeId."
}
},
"additionalProperties": false,
"description": "OBJECT { sessionId (or canvasSessionId), subquestionNodeId (or parentNodeId/subquestionId) } — a bare string is accepted as the sessionId. OPTIONAL — attach this read's citation-bearing hits to a canvas research session as candidate evidence. subquestionNodeId is a subquestion node id (from canvas_add_subquestion / canvas_start_session); evidence binds to the NAMED subquestion — never the session root; omit it and the response lists the subquestion node ids to pick from. The response returns the created evidence node ids so you can canvas_classify them."
}
},
"required": [
"citation"
],
"additionalProperties": false
}
```
### `expand_ia_question`
Expand a PHMSA IA (Integrity Assessment) question code into its FULL requirement text. Trigger: you have an IA code from get_section/describe_section (e.g. "CR.CRMRR.PRESSLIMITS.O") and need the complete, untruncated question + the CFR sections it maps to. Returns: full questionText, classification (level/topic/area/type), linked sections, and compliance-exemplar structure guidance when one exists. When the same code spans datasets, resolves the canonical variant and lists the others. Results carry an authority tier; only BINDING (49 CFR) and JUDICIAL sources state law — POLICY and lower is guidance, not legally binding.
Input schema:
```json
{
"type": "object",
"properties": {
"question_id": {
"type": "string",
"minLength": 1,
"description": "The IA question code (e.g., \"CR.CRMRR.PRESSLIMITS.O\") exactly as it appears in get_section / describe_section output."
},
"dataset_slug": {
"type": "string",
"description": "Optional dataset disambiguator. Omit to resolve the canonical (most recently imported) variant when the same code exists across datasets."
}
},
"required": [
"question_id"
],
"additionalProperties": false
}
```
### `expand_paragraph`
NARROW / single-purpose — the paragraph chain of authority for ONE deep paragraph citation, nothing else. Return the chain of paragraph context for a paragraph-specific CFR citation: section heading + section intro + each enclosing paragraph header down to the target paragraph. Use INSTEAD of get_section when you need to cite §192.605(b)(4) — gives you the (b) framing and the section-level "Each operator shall..." statement that establish authority, without dragging in the unrelated (c)(d)(e) paragraphs. Returns a render-ready Markdown chain. Fails with invalid_input if the citation has no paragraph path. Results carry an authority tier; only BINDING (49 CFR) and JUDICIAL sources state law — POLICY and lower is guidance, not legally binding.
Input schema:
```json
{
"type": "object",
"properties": {
"citation": {
"type": "string",
"minLength": 1,
"description": "Paragraph-specific CFR citation (e.g., \"§192.605(b)(4)\" or \"49 CFR 195.452(c)(2)(ii)(B)\"). MUST include a paragraph path — for whole-section retrieval use get_section instead."
},
"format": {
"type": "string",
"enum": [
"plain",
"html"
],
"default": "plain",
"description": "Spec 158 F11.8 — output text format. Default \"plain\" returns the rendered Markdown chain. \"html\" preserves typographic markup (italics, definitions) from the underlying section HTML, sanitized to a small allowlist."
}
},
"required": [
"citation"
],
"additionalProperties": false
}
```
### `finalize_memo`
FINALIZE — the terminus that LOCKS your deliverable. YOU (the client LLM) write the memo narrative; Kondwit GROUNDS it, FREEZES it, VERSIONS it, and ATTESTS it. The server runs NO inference — it never writes prose. Pass your prose as `narrative` and the server runs the deterministic GROUND-TRUTH GATE over it (neutralizing any case caption / docket / named incident with no trace in the corpus your canvas actually pulled — defeating the fabricated-citation class), then freezes it as the memo body. OMIT `narrative` and it freezes the deterministic SKELETON instead (citation-locked bibliography + obligation lattice + your recorded synthesis) — grounded by construction. EITHER path assembles the deterministic citation-locked Appendix A from the FULL finalized evidence set and carries a COVERAGE-ATTESTATION page: per-authority-tier read counts, the obligation-lattice attestation (N obligations: X covered / Y scoped-out / Z bare), a provenance hash (which folds in your narrative, so changed prose mints a new version), and a two-appendix ledger. Emits a FROZEN, immutable, VERSIONED artifact — re-finalizing identical state returns the same version (idempotent); after a material change it mints a new version with a "since v(N-1)" DELTA (sources added, new authority tiers, coverage %, obligations newly finalized) and flags which citations drifted. NEVER FAILS SILENTLY. PREVIEW: pass `preview:true` to RENDER the full deliverable (gated narrative or skeleton, Appendix A, attestation, the would-be "since vN" delta) WITHOUT minting a version or writing an artifact — use it to inspect the memo, or to schema-probe, before the deliberate freeze. Trigger: you have gathered + classified evidence into a canvas and want to LOCK the memo as a durable, diff-able, attested filing record (auto-resolves your active session if none is passed). Returns: the frozen memo + attestation, its version id + provenance/content hash, the since-last delta, and the version history.
Input schema:
```json
{
"type": "object",
"properties": {
"canvasSessionId": {
"type": "string",
"description": "Canvas session id (auto-attach/create when absent)"
},
"sessionId": {
"type": "string",
"description": "Alias for canvasSessionId."
},
"canvasId": {
"type": "string",
"description": "Alias for canvasSessionId."
},
"narrative": {
"type": "string",
"description": "YOUR memo prose (the client LLM writes it). Supplied → it is ground-gated then frozen as the memo body. The server never generates prose."
},
"memo": {
"type": "string",
"description": "Alias for narrative."
},
"memoMarkdown": {
"type": "string",
"description": "Alias for narrative."
},
"skeleton": {
"type": "boolean",
"description": "Explicitly freeze the deterministic no-narrative SKELETON (citation-locked bibliography + obligation lattice + your recorded synthesis; grounded by construction). Without a narrative, a preview, or skeleton:true, an empty finalize freezes NOTHING and returns the schema hint."
},
"preview": {
"type": "boolean",
"description": "DRY-RUN: render the full deliverable without minting a version or writing an artifact. A schema-probe or \"show me first\" should pass preview:true; omit it to deliberately FREEZE."
}
},
"additionalProperties": false
}
```
### `find_amendments`
Returns Federal Register amendments touching a CFR section, newest-first. Joins FrAmendment.section + parent FrDocument; falls back to FrDocumentSectionLink for coarse linkage. since_date filters by FR publicationDate. Use to answer "Has §X been amended in 2025?" type questions. Pass jurisdiction=<two-letter state code> to return that STATE's register amendments (Texas Register, Pennsylvania Bulletin, …) for the section instead of federal; omit for federal (the default). This returns amendment history only; if you also need text, IAQs, enforcement, and cross-references for the section, call describe_section instead. IMPORTANT: Amendments derived from PROPOSED rules are excluded by default: an NPRM states what the agency proposed to change, not what changed. Pass include_proposed=true (or read `proposedExcludedCount`) to see them. The default result set therefore answers "what changed", and proposedExcludedCount says how many proposals were held back. IMPORTANT: every row carries effectStatus. Read effectiveDateOperative — NOT effectiveDate — when answering what the section says today: a WITHDRAWN or SUPERSEDED rule has effectiveDateOperative=null and never took effect, and effectQualifier explains why. An EFFECT_PENDING row is a direct final rule whose date is conditional until PHMSA confirms it; show the date WITH its qualifier. Withdrawn rows are deliberately RETAINED here as history — lifecycleRole="ACTOR" marks a withdrawal notice, which un-amended the section and must never be reported as amending it. A row with provisionScoped=true took effect for OTHER sections of the CFR but NOT for the one you asked about - PHMSA delayed, stayed, or withdrew it for this provision alone. Its effectStatus still describes the DOCUMENT (which really is in effect elsewhere), so trust effectiveDateOperative=null and effectQualifier over effectStatus for this section, and never report the whole rule as withdrawn. Results carry an authority tier; only BINDING (49 CFR) and JUDICIAL sources state law — POLICY and lower is guidance, not legally binding.
Input schema:
```json
{
"type": "object",
"properties": {
"citation": {
"type": "string",
"minLength": 1,
"description": "CFR citation in any common form (e.g., \"§192.605\", \"49 CFR 195.452\")."
},
"since_date": {
"type": "string",
"pattern": "^\\d{4}-\\d{2}-\\d{2}(T.*)?$",
"description": "ISO date (YYYY-MM-DD) — only amendments with FR publicationDate on/after this date."
},
"jurisdiction": {
"type": "string",
"pattern": "^[A-Za-z]{2}$",
"description": "Two-letter state code (e.g. \"TX\", \"PA\") to return that STATE's register amendments for the section instead of federal. Omit for federal (49 CFR) — the default, byte-identical to prior behavior."
},
"include_proposed": {
"type": "boolean",
"default": false,
"description": "Include amendment rows derived from PROPOSED rules (NPRMs/ANPRMs). Default false: an NPRM states what the agency proposed to change, not what changed. The default answer is what the regulation actually says."
},
"limit": {
"type": "integer",
"maximum": 100,
"default": 25,
"minimum": 1
},
"canvas": {
"type": "object",
"properties": {
"sessionId": {
"type": "string",
"minLength": 1,
"description": "Canvas session id"
},
"canvasSessionId": {
"type": "string",
"minLength": 1,
"description": "Alias for sessionId."
},
"subquestionNodeId": {
"type": "string",
"minLength": 1,
"description": "Subquestion node id under which to attach candidate evidence (a.k.a. parentNodeId)"
},
"parentNodeId": {
"type": "string",
"minLength": 1,
"description": "Alias for subquestionNodeId."
},
"subquestionId": {
"type": "string",
"minLength": 1,
"description": "Alias for subquestionNodeId."
}
},
"additionalProperties": false,
"description": "OBJECT { sessionId (or canvasSessionId), subquestionNodeId (or parentNodeId/subquestionId) } — a bare string is accepted as the sessionId. OPTIONAL — attach this read's citation-bearing hits to a canvas research session as candidate evidence. subquestionNodeId is a subquestion node id (from canvas_add_subquestion / canvas_start_session); evidence binds to the NAMED subquestion — never the session root; omit it and the response lists the subquestion node ids to pick from. The response returns the created evidence node ids so you can canvas_classify them."
}
},
"required": [
"citation"
],
"additionalProperties": false
}
```
### `find_citing_documents`
Inverse of resolve_citation — given a section (by Section.id OR a citation string like "§192.605"), return every corpus document that cites it. Unioned across the 8 *SectionLink tables + SmartInterpretation primaryProvisionId. Use to answer "what interpretations cite §X?" / "what references this section?". This returns citing documents only; if you also need the section text, IAQs, enforcement, and amendments in one shot, call describe_section instead. Results carry an authority tier; only BINDING (49 CFR) and JUDICIAL sources state law — POLICY and lower is guidance, not legally binding. Interpretations extracted from PROPOSED rules (NPRMs) are served at the GUIDANCE tier, never BINDING: an NPRM states what the agency proposed to require, not what it requires. Each such row carries `proposalDerived: true` and `ruleStatus: "PROPOSED"` — never cite one as a legal requirement.
Input schema:
```json
{
"type": "object",
"properties": {
"section_id": {
"type": "string",
"minLength": 1,
"description": "Section.id (from resolve_citation or any retrieval tool). Provide this OR `citation`."
},
"citation": {
"type": "string",
"minLength": 1,
"description": "CFR citation in any common form (e.g., \"§192.605\", \"49 CFR 195.452\"). Resolved to a Section.id internally. Provide this OR `section_id`."
},
"types": {
"type": "array",
"items": {
"type": "string",
"enum": [
"interpretation",
"notice",
"enforcement_case",
"guidance",
"special_permit",
"policy_document",
"faq",
"smart_interpretation"
]
},
"description": "Filter to specific source types. Omit to include all."
},
"limit": {
"type": "integer",
"maximum": 200,
"default": 50,
"minimum": 1
},
"canvas": {
"type": "object",
"properties": {
"sessionId": {
"type": "string",
"minLength": 1,
"description": "Canvas session id"
},
"canvasSessionId": {
"type": "string",
"minLength": 1,
"description": "Alias for sessionId."
},
"subquestionNodeId": {
"type": "string",
"minLength": 1,
"description": "Subquestion node id under which to attach candidate evidence (a.k.a. parentNodeId)"
},
"parentNodeId": {
"type": "string",
"minLength": 1,
"description": "Alias for subquestionNodeId."
},
"subquestionId": {
"type": "string",
"minLength": 1,
"description": "Alias for subquestionNodeId."
}
},
"additionalProperties": false,
"description": "OBJECT { sessionId (or canvasSessionId), subquestionNodeId (or parentNodeId/subquestionId) } — a bare string is accepted as the sessionId. OPTIONAL — attach this read's citation-bearing hits to a canvas research session as candidate evidence. subquestionNodeId is a subquestion node id (from canvas_add_subquestion / canvas_start_session); evidence binds to the NAMED subquestion — never the session root; omit it and the response lists the subquestion node ids to pick from. The response returns the created evidence node ids so you can canvas_classify them."
}
},
"additionalProperties": false
}
```
### `find_court_holdings`
Find JUDICIAL-tier court holdings from court_holdings (spec 137). Filter by citation (joins via section.id), judgment_type (UPHOLDS / VACATES / REMANDS / etc.), holding_category, or court_level. Direct surface — previously only reachable via smart_interpretation search with authorityTier:JUDICIAL. Trigger: the user asks how a court has ruled on / interpreted a section, or wants judicial precedent for a provision. Returns: court holdings with judgment type, holding category, court level, and the precedent each cites. Results carry an authority tier; only BINDING (49 CFR) and JUDICIAL sources state law — POLICY and lower is guidance, not legally binding.
Input schema:
```json
{
"type": "object",
"properties": {
"citation": {
"type": "string",
"description": "CFR citation to filter holdings by (e.g., \"§192.605\"). Joins via CourtHolding.section."
},
"judgment_type": {
"type": "string",
"enum": [
"AFFIRMED",
"VACATED",
"VACATED_AND_REMANDED",
"REVERSED",
"REMANDED",
"UPHELD_ENFORCEMENT",
"RULED_FOR_PLAINTIFF",
"RULED_FOR_DEFENDANT",
"DISMISSED_FOR_LACK_OF_STANDING",
"REJECTED_AGENCY_INTERPRETATION",
"NARROWED",
"OTHER"
],
"description": "Disposition type filter. One of: AFFIRMED, VACATED, VACATED_AND_REMANDED, REVERSED, REMANDED, UPHELD_ENFORCEMENT, RULED_FOR_PLAINTIFF, RULED_FOR_DEFENDANT, DISMISSED_FOR_LACK_OF_STANDING, REJECTED_AGENCY_INTERPRETATION, NARROWED, OTHER."
},
"holding_category": {
"type": "string",
"enum": [
"PREEMPTION",
"STANDARD_OF_CARE",
"ENFORCEMENT_SCOPE",
"PROCEDURAL",
"CONSTITUTIONAL",
"PROVISION_INTERPRETATION",
"AGENCY_AUTHORITY",
"FACT_FINDING",
"OTHER"
],
"description": "Holding category filter. One of: PREEMPTION, STANDARD_OF_CARE, ENFORCEMENT_SCOPE, PROCEDURAL, CONSTITUTIONAL, PROVISION_INTERPRETATION, AGENCY_AUTHORITY, FACT_FINDING, OTHER."
},
"court_level": {
"type": "string",
"enum": [
"CIRCUIT",
"DISTRICT"
],
"description": "Court level filter. One of: CIRCUIT, DISTRICT."
},
"limit": {
"type": "integer",
"maximum": 100,
"default": 20,
"minimum": 1
},
"canvas": {
"type": "object",
"properties": {
"sessionId": {
"type": "string",
"minLength": 1,
"description": "Canvas session id"
},
"canvasSessionId": {
"type": "string",
"minLength": 1,
"description": "Alias for sessionId."
},
"subquestionNodeId": {
"type": "string",
"minLength": 1,
"description": "Subquestion node id under which to attach candidate evidence (a.k.a. parentNodeId)"
},
"parentNodeId": {
"type": "string",
"minLength": 1,
"description": "Alias for subquestionNodeId."
},
"subquestionId": {
"type": "string",
"minLength": 1,
"description": "Alias for subquestionNodeId."
}
},
"additionalProperties": false,
"description": "OBJECT { sessionId (or canvasSessionId), subquestionNodeId (or parentNodeId/subquestionId) } — a bare string is accepted as the sessionId. OPTIONAL — attach this read's citation-bearing hits to a canvas research session as candidate evidence. subquestionNodeId is a subquestion node id (from canvas_add_subquestion / canvas_start_session); evidence binds to the NAMED subquestion — never the session root; omit it and the response lists the subquestion node ids to pick from. The response returns the created evidence node ids so you can canvas_classify them."
}
},
"additionalProperties": false
}
```
### `find_documents`
Filter-based document discovery without a query string — useful when you know typed attributes (authority tier, specificity, source type, CFR section, status, region, etc.) but no keywords. Supports all 4 types: interpretation / notice / enforcement_case / smart_interpretation. Each type accepts its own filter set; pass the discriminator + at least one filter. For a specific section interpretations, list_section_interpretations is the canonical entry (this filter-getter is for cross-type attribute discovery, not a section SI list). Results carry an authority tier; only BINDING (49 CFR) and JUDICIAL sources state law — POLICY and lower is guidance, not legally binding. Interpretations extracted from PROPOSED rules (NPRMs) are served at the GUIDANCE tier, never BINDING: an NPRM states what the agency proposed to require, not what it requires. Each such row carries `proposalDerived: true` and `ruleStatus: "PROPOSED"` — never cite one as a legal requirement.
Input schema:
```json
{
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": [
"interpretation",
"notice",
"enforcement_case",
"smart_interpretation",
"special_permit",
"guidance",
"faq",
"policy_document"
]
},
"authorityTier": {
"type": "string",
"enum": [
"STATUTORY",
"BINDING_PROVISION",
"AUTHORITATIVE_AGENCY",
"PROCEDURAL",
"JUDICIAL",
"OVERSIGHT",
"GUIDANCE",
"INDUSTRY"
]
},
"specificity": {
"type": "string",
"enum": [
"GENERAL",
"PARAGRAPH_SPECIFIC",
"OPERATOR_SPECIFIC",
"INCIDENT_SPECIFIC",
"AGENCY_SPECIFIC"
]
},
"sourceType": {
"type": "string"
},
"primaryProvision": {
"type": "string",
"description": "CFR section number filter (e.g., \"192.605\") — smart_interpretation only"
},
"status": {
"type": "string",
"description": "Interpretation status filter (e.g., \"issued\")"
},
"docType": {
"type": "string",
"description": "Notice docType filter (e.g., \"advisory bulletin\", \"notice of proposed rulemaking\")"
},
"partNumbers": {
"type": "array",
"items": {
"type": "string"
},
"description": "CFR part numbers to filter (e.g., [\"192\", \"195\"]) — applies to all types via cfrReferences"
},
"caseStatus": {
"type": "string",
"description": "Enforcement case status (e.g., \"OPEN\", \"CLOSED\")"
},
"region": {
"type": "string",
"description": "PHMSA region filter — enforcement_case only"
},
"operatorName": {
"type": "string",
"description": "Operator name contains (case-insensitive) — enforcement_case + special_permit"
},
"permitStatus": {
"type": "string",
"description": "Special permit status (e.g., \"Issued\", \"Pending\", \"Expired\") — special_permit only"
},
"system": {
"type": "string",
"description": "Special permit pipeline system filter — special_permit only"
},
"sectionCode": {
"type": "string",
"description": "Guidance/FAQ section code filter (e.g., \"192.605\") — guidance + faq"
},
"category": {
"type": "string",
"description": "Policy document category (e.g., \"POLICY_MEMO\") — policy_document only"
},
"isActive": {
"type": "boolean",
"description": "Restrict to active policy documents — policy_document only"
},
"limit": {
"type": "integer",
"maximum": 200,
"default": 50,
"minimum": 1
},
"lens": {
"type": "string",
"enum": [
"profile",
"off"
],
"default": "profile",
"description": "Spec 228 F06 — role-profile relevance lens. 'profile' (default) stably reorders results by the authed user's Kondwit role profile (focus-area relevance); the result SET is unchanged — items are only reordered, with non-focus items marked `outOfFocus: true`. 'off' disables the lens for this call (stateless override). Callers without a role profile get identical results either way. Not a filter — a `type`-relevant filter is still required."
},
"canvas": {
"type": "object",
"properties": {
"sessionId": {
"type": "string",
"minLength": 1,
"description": "Canvas session id"
},
"canvasSessionId": {
"type": "string",
"minLength": 1,
"description": "Alias for sessionId."
},
"subquestionNodeId": {
"type": "string",
"minLength": 1,
"description": "Subquestion node id under which to attach candidate evidence (a.k.a. parentNodeId)"
},
"parentNodeId": {
"type": "string",
"minLength": 1,
"description": "Alias for subquestionNodeId."
},
"subquestionId": {
"type": "string",
"minLength": 1,
"description": "Alias for subquestionNodeId."
}
},
"additionalProperties": false,
"description": "OBJECT { sessionId (or canvasSessionId), subquestionNodeId (or parentNodeId/subquestionId) } — a bare string is accepted as the sessionId. OPTIONAL — attach this read's citation-bearing hits to a canvas research session as candidate evidence. subquestionNodeId is a subquestion node id (from canvas_add_subquestion / canvas_start_session); evidence binds to the NAMED subquestion — never the session root; omit it and the response lists the subquestion node ids to pick from. The response returns the created evidence node ids so you can canvas_classify them."
}
},
"required": [
"type"
],
"additionalProperties": false,
"description": "Filter-based discovery — pick a type and supply at least one type-relevant filter."
}
```
### `find_enforcement_exemplars`
Find FAILURE EXEMPLARS for one or more CFR sections: penalized probable violations from PHMSA enforcement cases (allegations where a penalty was actually resolved), each with the factual basis — what the operator did or failed to do. Use this to answer "what does a violation of §X look like in practice?" or to ground audit-risk and compliance-gap reasoning in real adjudicated outcomes. Filter by `operatorName` or `allegationCategory`. Set sort:"recency" (default "penalty") to get the most recently opened cases first, and includeUnresolved:true to include OPEN cases / recent NOPVs that have no resolved penalty yet — use both together to answer "what are the most recent NOPVs for §X?". NOTE: results are HISTORICAL enforcement evidence — they show what PHMSA has penalized or alleged, NOT what the law requires; never cite them as a binding requirement, and treat OPEN allegations as unproven. The returned allegationIds are CORPUS enforcement-precedent ids (PHMSA CaseAllegation rows), NOT research_allegation inputs — to research your OWN audit case's allegations, list them with list_audit_allegations. Results carry an authority tier; only BINDING (49 CFR) and JUDICIAL sources state law — POLICY and lower is guidance, not legally binding.
Input schema:
```json
{
"type": "object",
"properties": {
"citations": {
"type": "array",
"items": {
"type": "string",
"minLength": 1
},
"minItems": 1,
"description": "One or more CFR section citations to find penalized probable violations for (e.g., [\"§192.605\", \"49 CFR 195.452\"]). Each is parsed via the canonical citation parser."
},
"operatorName": {
"type": "string",
"minLength": 1,
"description": "Optional: restrict to an operator whose name contains this string (case-insensitive)."
},
"allegationCategory": {
"type": "string",
"enum": [
"PROCEDURE",
"RECORD",
"OPERATIONAL",
"UNCLASSIFIED"
],
"description": "Optional facet: PROCEDURE (written-procedure adequacy), RECORD (recordkeeping), OPERATIONAL (field operations), or UNCLASSIFIED."
},
"sort": {
"type": "string",
"enum": [
"penalty",
"recency"
],
"default": "penalty",
"description": "Ordering: 'penalty' (default — highest resolved penalty first; the original behavior) or 'recency' (most recently OPENED enforcement case first, by the case `openedDate`). Use 'recency' to answer \"what are the most recent NOPVs for §X?\"."
},
"includeUnresolved": {
"type": "boolean",
"default": false,
"description": "When true, include allegations with no resolved penalty (e.g. OPEN cases / recent NOPVs not yet adjudicated), not just penalized ones. Default false (penalized only). NOTE: setting this changes what `total` counts — it then counts ALL matching allegations, not just penalized ones. Pair with sort:'recency' to see the newest enforcement activity for a section. These remain HISTORICAL evidence, never binding — an OPEN allegation is an unproven assertion, not an adjudicated outcome."
},
"limit": {
"type": "integer",
"minimum": 1,
"maximum": 50,
"default": 20,
"description": "Max exemplars to return (default 20, max 50). Ordered by `sort` — penalty (highest first, default) or recency (most recently opened case first)."
},
"canvas": {
"type": "object",
"properties": {
"sessionId": {
"type": "string",
"minLength": 1,
"description": "Canvas session id"
},
"canvasSessionId": {
"type": "string",
"minLength": 1,
"description": "Alias for sessionId."
},
"subquestionNodeId": {
"type": "string",
"minLength": 1,
"description": "Subquestion node id under which to attach candidate evidence (a.k.a. parentNodeId)"
},
"parentNodeId": {
"type": "string",
"minLength": 1,
"description": "Alias for subquestionNodeId."
},
"subquestionId": {
"type": "string",
"minLength": 1,
"description": "Alias for subquestionNodeId."
}
},
"additionalProperties": false,
"description": "OBJECT { sessionId (or canvasSessionId), subquestionNodeId (or parentNodeId/subquestionId) } — a bare string is accepted as the sessionId. OPTIONAL — attach this read's citation-bearing hits to a canvas research session as candidate evidence. subquestionNodeId is a subquestion node id (from canvas_add_subquestion / canvas_start_session); evidence binds to the NAMED subquestion — never the session root; omit it and the response lists the subquestion node ids to pick from. The response returns the created evidence node ids so you can canvas_classify them."
}
},
"required": [
"citations"
],
"additionalProperties": false,
"description": "Find penalized probable violations (enforcement FAILURE EXEMPLARS) for one or more CFR sections — concrete, adjudicated evidence of what violating the requirement looks like, with the factual basis."
}
```
### `find_oversight_recommendations`
Find oversight recommendations referencing CFR sections. Unifies GAO + DOT OIG audit recommendations (OversightRecommendation) and NTSB safety recommendations (NtsbSafetyRecommendation) into one normalized result. Filter by citation, agency (GAO / DOT_OIG / NTSB), status, or since_date. Surfaces congressional/auditor pressure on a provision. Results carry an authority tier; only BINDING (49 CFR) and JUDICIAL sources state law — POLICY and lower is guidance, not legally binding.
Input schema:
```json
{
"type": "object",
"properties": {
"citation": {
"type": "string",
"description": "CFR citation to filter recommendations by (e.g., \"§192.605\"). Joins via section linkage tables."
},
"agency": {
"type": "string",
"enum": [
"GAO",
"DOT_OIG",
"NTSB"
],
"description": "Filter to a specific agency. Omit for all three."
},
"status": {
"type": "string",
"description": "Status filter (e.g., \"open\", \"closed\", \"implemented\"). Case-insensitive."
},
"since_date": {
"type": "string",
"pattern": "^\\d{4}-\\d{2}-\\d{2}(T.*)?$",
"description": "ISO date — only recommendations issued on/after this date."
},
"limit": {
"type": "integer",
"maximum": 100,
"default": 25,
"minimum": 1
},
"canvas": {
"type": "object",
"properties": {
"sessionId": {
"type": "string",
"minLength": 1,
"description": "Canvas session id"
},
"canvasSessionId": {
"type": "string",
"minLength": 1,
"description": "Alias for sessionId."
},
"subquestionNodeId": {
"type": "string",
"minLength": 1,
"description": "Subquestion node id under which to attach candidate evidence (a.k.a. parentNodeId)"
},
"parentNodeId": {
"type": "string",
"minLength": 1,
"description": "Alias for subquestionNodeId."
},
"subquestionId": {
"type": "string",
"minLength": 1,
"description": "Alias for subquestionNodeId."
}
},
"additionalProperties": false,
"description": "OBJECT { sessionId (or canvasSessionId), subquestionNodeId (or parentNodeId/subquestionId) } — a bare string is accepted as the sessionId. OPTIONAL — attach this read's citation-bearing hits to a canvas research session as candidate evidence. subquestionNodeId is a subquestion node id (from canvas_add_subquestion / canvas_start_session); evidence binds to the NAMED subquestion — never the session root; omit it and the response lists the subquestion node ids to pick from. The response returns the created evidence node ids so you can canvas_classify them."
}
},
"additionalProperties": false
}
```
### `find_related_provisions`
NARROW / atomic graph walk — use only for a single edge family, an SI anchor, or a multi-hop walk; for a section whole 1-hop neighborhood as one repeatable bundle use section_neighborhood, for a multi-facet section briefing use describe_section. Unified graph walk from a Section or SmartInterpretation. `edge_types` filters across both Section edge families (cites/interprets/amends/enforces/permits/structural/curation/notifies/subsidiary_of/recommends/oversight) and SI edge types (TARGETS/RESTATES/CONTRADICTS/REINFORCES/MIRRORS/CITED_FROM/AFFECTS/CLARIFIES_BINDING). Returns related nodes with human-readable labels (§192.605, not cuids) and a `groupedByFamily` summary for fast pivot. Replaces 5 prior tools (findRelatedSections, findImplicitRelationships, graph_traverse, cross_doc_links, find_related_smart_interpretations). Use this for a single edge family, an SI anchor, or a multi-hop walk; if you want a section’s full 1-hop neighborhood as one deterministic, repeatable evidence set, prefer the bundled section_neighborhood tool instead. For a Section anchor you may pass a citation string instead of anchorType+anchorId (resolved internally). This returns graph neighbors only; if you also need the section text, IAQs, enforcement, and amendments in one shot, call describe_section instead. Results carry an authority tier; only BINDING (49 CFR) and JUDICIAL sources state law — POLICY and lower is guidance, not legally binding.
Input schema:
```json
{
"type": "object",
"properties": {
"anchorType": {
"type": "string",
"enum": [
"section",
"smart_interpretation"
],
"description": "Whether the anchor is a CFR Section or a SmartInterpretation. Determines which graph subgraph is walked. Required with anchorId; implied \"section\" when `citation` is used instead."
},
"anchorId": {
"type": "string",
"minLength": 1,
"description": "Stable id of the anchor (Section.id or SmartInterpretation.id). Provide anchorType+anchorId OR `citation`."
},
"citation": {
"type": "string",
"minLength": 1,
"description": "CFR citation in any common form (e.g., \"§192.605\"). Resolved to a Section anchor internally. Provide this OR anchorType+anchorId."
},
"edgeTypes": {
"type": "array",
"items": {
"type": "string",
"enum": [
"cites",
"interprets",
"amends",
"enforces",
"permits",
"structural",
"curation",
"notifies",
"subsidiary_of",
"recommends",
"oversight",
"TARGETS",
"RESTATES",
"CONTRADICTS",
"REINFORCES",
"MIRRORS",
"CITED_FROM",
"AFFECTS",
"CLARIFIES_BINDING"
]
},
"description": "Filter by edge types. Section anchors accept cites / interprets / amends / enforces / permits / structural / curation / notifies / subsidiary_of / recommends / oversight. SI anchors accept TARGETS / RESTATES / CONTRADICTS / REINFORCES / MIRRORS / CITED_FROM / AFFECTS / CLARIFIES_BINDING. Omit to include all."
},
"hops": {
"type": "integer",
"minimum": 1,
"maximum": 3,
"description": "Max traversal depth for Section anchors (default 1, max 3). Ignored for SI anchors."
},
"limit": {
"type": "integer",
"maximum": 200,
"description": "Result cap (default 50, max 200)",
"minimum": 1
},
"canvas": {
"type": "object",
"properties": {
"sessionId": {
"type": "string",
"minLength": 1,
"description": "Canvas session id"
},
"canvasSessionId": {
"type": "string",
"minLength": 1,
"description": "Alias for sessionId."
},
"subquestionNodeId": {
"type": "string",
"minLength": 1,
"description": "Subquestion node id under which to attach candidate evidence (a.k.a. parentNodeId)"
},
"parentNodeId": {
"type": "string",
"minLength": 1,
"description": "Alias for subquestionNodeId."
},
"subquestionId": {
"type": "string",
"minLength": 1,
"description": "Alias for subquestionNodeId."
}
},
"additionalProperties": false,
"description": "OBJECT { sessionId (or canvasSessionId), subquestionNodeId (or parentNodeId/subquestionId) } — a bare string is accepted as the sessionId. OPTIONAL — attach this read's citation-bearing hits to a canvas research session as candidate evidence. subquestionNodeId is a subquestion node id (from canvas_add_subquestion / canvas_start_session); evidence binds to the NAMED subquestion — never the session root; omit it and the response lists the subquestion node ids to pick from. The response returns the created evidence node ids so you can canvas_classify them."
}
},
"additionalProperties": false
}
```
### `find_state_delta`
The state-specific regulatory sections a jurisdiction carries — what it adds or modifies over the incorporated federal baseline (states adopt 49 CFR by reference and author their own §§). Returns the native-structure TOC (document unit → section tree). Use compare_state_to_federal on a returned stateSectionId to see the federal anchor + relation. COVERAGE: state-authored section text is served ONLY for a jurisdiction whose loaded text has been verified to be that state’s own pipeline-safety regulation - call list_jurisdictions for the live per-state `textStatus`/`sectionCount` and the corpus-wide `textCoverage`. An empty result is one of THREE things and the payload’s `coverage` block says which, so read it rather than inferring from the empty list: (a) not-yet-carried; (b) `textStatus: "unverified"` - source text IS held and is being withheld pending verification, with `withheldSectionCount` saying how much; or (c) a jurisdiction that adopts no part of 49 CFR (AK, HI today), where there is genuinely no state overlay to carry. None of the three ever means "this state authors no rules". Read-only; no inference.
Input schema:
```json
{
"type": "object",
"properties": {
"stateCode": {
"type": "string",
"pattern": "^[A-Za-z]{2}$",
"description": "Two-letter USPS state code (e.g. \"TX\", \"PA\", \"CA\", \"DC\", \"PR\")."
}
},
"required": [
"stateCode"
],
"additionalProperties": false
}
```
### `find_state_enforcement`
CONSOLIDATED state enforcement for a jurisdiction — reg-text orders (F08) AND damage-prevention penalty actions (F09) in one tool. Optionally narrow to one stream with category ("enforcement" | "damage-prevention"). Returns each action with its docket/case id, title, issued date, penalty, and respondent. COVERAGE: state enforcement actions are not carried for any jurisdiction yet, so this tool returns 0 everywhere today. The payload says so via `coverage` - do not report the zero as "this state has brought no actions". Federal PHMSA enforcement is a different corpus and IS carried. Read-only; no inference.
Input schema:
```json
{
"type": "object",
"properties": {
"stateCode": {
"type": "string",
"pattern": "^[A-Za-z]{2}$",
"description": "Two-letter USPS state code (e.g. \"TX\", \"PA\", \"CA\", \"DC\", \"PR\")."
},
"category": {
"type": "string",
"enum": [
"enforcement",
"damage-prevention"
],
"description": "Optional stream filter: \"enforcement\" (reg-text orders, F08) or \"damage-prevention\" (one-call/dig-in penalty actions, F09). Omit for both."
},
"limit": {
"type": "integer",
"maximum": 200,
"description": "Max rows to return (default 50, cap 200).",
"minimum": 1
}
},
"required": [
"stateCode"
],
"additionalProperties": false
}
```
### `find_state_waivers`
State-issued waivers (the state analog of a federal special-permit condition) for a jurisdiction, optionally narrowed to those touching one federal § (federalSection). Returns each condition with its title, text, category, cited regulations, and the state waiver docket reference. COVERAGE: state waivers are not carried for any jurisdiction yet, so this tool returns 0 everywhere today. The payload says so via `coverage` - do not report the zero as "this state has issued no waivers". Read-only; no inference.
Input schema:
```json
{
"type": "object",
"properties": {
"stateCode": {
"type": "string",
"pattern": "^[A-Za-z]{2}$",
"description": "Two-letter USPS state code (e.g. \"TX\", \"PA\", \"CA\", \"DC\", \"PR\")."
},
"federalSection": {
"type": "string",
"minLength": 1,
"description": "Optional federal § (e.g. \"192.605\") — narrow to waivers touching that section."
},
"limit": {
"type": "integer",
"maximum": 200,
"description": "Max rows to return (default 50, cap 200).",
"minimum": 1
}
},
"required": [
"stateCode"
],
"additionalProperties": false
}
```
### `get_document`
Retrieve any single corpus document by stable id and type. Polymorphic getter — covers interpretation letters, FR notices, enforcement cases, and smart interpretations. Replaces four separate narrow getters; pick the type from the discriminator and pass the id. For a section interpretations, start with list_section_interpretations; use this only to fetch one document (incl. a smart interpretation) by a known id. Results carry an authority tier; only BINDING (49 CFR) and JUDICIAL sources state law — POLICY and lower is guidance, not legally binding. Interpretations extracted from PROPOSED rules (NPRMs) are served at the GUIDANCE tier, never BINDING: an NPRM states what the agency proposed to require, not what it requires. Each such row carries `proposalDerived: true` and `ruleStatus: "PROPOSED"` — never cite one as a legal requirement.
Input schema:
```json
{
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": [
"interpretation",
"notice",
"enforcement_case",
"smart_interpretation",
"special_permit",
"guidance",
"faq",
"policy_document",
"fr_document",
"regulation"
],
"description": "Document type: interpretation (PHMSA interpretation letter), notice (FR notice), enforcement_case, smart_interpretation (derived SI), special_permit, guidance, faq, policy_document, fr_document, or regulation (a 49 CFR section)."
},
"id": {
"type": "string",
"minLength": 1,
"description": "Stable id of the document (cuid). You may also pass the human handle a discovery tool surfaces: for `interpretation` the PHMSA id (e.g. \"PI-20-0001\"); for `regulation` a citation (\"§192.605\"); for `enforcement_case` the CPF number (e.g. \"52023046NOPV\" or the dashed \"5-2023-046\"); for `fr_document` / `notice` the Federal Register document number (e.g. \"2025-12117\")."
},
"body": {
"type": "string",
"enum": [
"full",
"snippet",
"none"
],
"default": "full",
"description": "Body rendering: full text (default), short snippet, or metadata-only."
},
"canvas": {
"type": "object",
"properties": {
"sessionId": {
"type": "string",
"minLength": 1,
"description": "Canvas session id"
},
"canvasSessionId": {
"type": "string",
"minLength": 1,
"description": "Alias for sessionId."
},
"subquestionNodeId": {
"type": "string",
"minLength": 1,
"description": "Subquestion node id under which to attach candidate evidence (a.k.a. parentNodeId)"
},
"parentNodeId": {
"type": "string",
"minLength": 1,
"description": "Alias for subquestionNodeId."
},
"subquestionId": {
"type": "string",
"minLength": 1,
"description": "Alias for subquestionNodeId."
}
},
"additionalProperties": false,
"description": "OBJECT { sessionId (or canvasSessionId), subquestionNodeId (or parentNodeId/subquestionId) } — a bare string is accepted as the sessionId. OPTIONAL — attach this read's citation-bearing hits to a canvas research session as candidate evidence. subquestionNodeId is a subquestion node id (from canvas_add_subquestion / canvas_start_session); evidence binds to the NAMED subquestion — never the session root; omit it and the response lists the subquestion node ids to pick from. The response returns the created evidence node ids so you can canvas_classify them."
}
},
"required": [
"type",
"id"
],
"additionalProperties": false
}
```
### `get_glossary_term`
Look up a regulatory or industry term in the KONDWIT glossary. Returns the definition plus the CFR section that authoritatively defines the term (when applicable). fuzzy=true (default) falls back to case-insensitive prefix when no exact match.
Input schema:
```json
{
"type": "object",
"properties": {
"term": {
"type": "string",
"minLength": 1,
"description": "Term to look up (exact or close match)"
},
"fuzzy": {
"type": "boolean",
"default": true,
"description": "Allow case-insensitive prefix fallback when no exact match"
},
"context_section": {
"type": "string",
"description": "Optional CFR citation (e.g. \"192.619\" or \"§195.418\") giving the reading context. When provided, the most specific scope-applicable definition is returned and definitions scoped to an unrelated section are excluded."
},
"context_part": {
"type": "string",
"description": "Optional CFR part (e.g. \"192\") giving the reading context when no specific section is known. Used as a coarser fallback than context_section."
}
},
"required": [
"term"
],
"additionalProperties": false
}
```
### `get_operator`
Get a pipeline operator profile by DOT operator id or business name. Returns operator metadata (id, businessName, status, system flags), enforcement history, special permits, pipeline incidents, tenant-scoped procedures (when applicable), and the PHMSA OPID parent-child hierarchy from SafetyProgramRelationship (which orgs this op files under, which orgs file under this op). Name matching is fuzzy (starts-with > word-boundary > substring); pass id for exact match. Trigger: the user names a pipeline operator (by company name or DOT/OPID number) and asks about its record, permits, incidents, or corporate hierarchy. Returns: operator profile + enforcement history + special permits + incidents + parent/child OPID hierarchy.
Input schema:
```json
{
"type": "object",
"properties": {
"name": {
"type": "string",
"minLength": 2,
"description": "Operator business name (≥2 chars, fuzzy contains)."
},
"id": {
"type": "string",
"description": "Exact DOT operator id (e.g., \"12345\")."
}
},
"additionalProperties": false
}
```
### `get_section`
NARROW / single-facet fallback — returns just ONE section text, nothing else; for a multi-facet section briefing use describe_section, for the 1-hop graph neighborhood use section_neighborhood. Retrieve a CFR section by citation. Resolves any citation form via the canonical parser, looks up the section directly (no search detour), and returns full text plus paragraph-aware URL. Use body:"snippet" for a 800-char excerpt when token budget is tight, body:"none" for metadata only. Use paragraph_only:true when the citation includes a paragraph path (e.g., §192.605(b)(4)) to get JUST that paragraph instead of the whole section — saves substantial tokens on deep citations. This returns text only; if you also need IAQs, enforcement, amendments, and cross-references, call describe_section instead. IMPORTANT: the amendmentCount returned here is filtered. Amendments derived from PROPOSED rules are excluded by default: an NPRM states what the agency proposed to change, not what changed. Pass include_proposed=true (or read `proposedExcludedCount`) to see them. Results carry an authority tier; only BINDING (49 CFR) and JUDICIAL sources state law — POLICY and lower is guidance, not legally binding.
Input schema:
```json
{
"type": "object",
"properties": {
"citation": {
"type": "string",
"minLength": 1,
"description": "CFR citation in any common form (e.g., \"§192.605\", \"49 CFR 195.452(c)(2)\", \"Part 192.605\", or an appendix like \"Appendix D to Part 192\"). The parser handles all five canonical forms, appendices, plus the most common malformed variants."
},
"body": {
"type": "string",
"enum": [
"full",
"snippet",
"none"
],
"default": "full",
"description": "Body rendering: full text (default), short snippet, or metadata-only."
},
"paragraph_only": {
"type": "boolean",
"default": false,
"description": "When true AND the citation includes a paragraph path (e.g., \"§192.605(b)(4)\"), return only the cited paragraph text rather than the whole section. Falls back to full section text if the paragraph cannot be isolated. Saves tokens significantly on deep citations."
},
"format": {
"type": "string",
"enum": [
"plain",
"html"
],
"default": "plain",
"description": "Spec 158 F11.8 — output format. 'plain' (default) returns textPlain; 'html' returns sanitized textHtml suitable for Tiptap embedding (paragraph anchors + structural tags preserved; scripts/styles/event handlers stripped). Falls back to plain when the section's HTML column is empty."
},
"canvas": {
"type": "object",
"properties": {
"sessionId": {
"type": "string",
"minLength": 1,
"description": "Canvas session id"
},
"canvasSessionId": {
"type": "string",
"minLength": 1,
"description": "Alias for sessionId."
},
"subquestionNodeId": {
"type": "string",
"minLength": 1,
"description": "Subquestion node id under which to attach candidate evidence (a.k.a. parentNodeId)"
},
"parentNodeId": {
"type": "string",
"minLength": 1,
"description": "Alias for subquestionNodeId."
},
"subquestionId": {
"type": "string",
"minLength": 1,
"description": "Alias for subquestionNodeId."
}
},
"additionalProperties": false,
"description": "OBJECT { sessionId (or canvasSessionId), subquestionNodeId (or parentNodeId/subquestionId) } — a bare string is accepted as the sessionId. OPTIONAL — attach this read's citation-bearing hits to a canvas research session as candidate evidence. subquestionNodeId is a subquestion node id (from canvas_add_subquestion / canvas_start_session); evidence binds to the NAMED subquestion — never the session root; omit it and the response lists the subquestion node ids to pick from. The response returns the created evidence node ids so you can canvas_classify them."
}
},
"required": [
"citation"
],
"additionalProperties": false
}
```
### `get_section_history`
Returns the SectionVersion timeline for a CFR section, newest-first. Each version shows the effective window and the FR amendment that drove the change (when matchable on effectiveDate). Useful for "when was §X last revised?" and "what is the regulatory history of §Y?" questions. IMPORTANT: Amendments derived from PROPOSED rules are excluded by default: an NPRM states what the agency proposed to change, not what changed. Pass include_proposed=true (or read `proposedExcludedCount`) to see them. A version synthesised here therefore reflects only rulemakings that were adopted. The `versions` timeline contains only rulemakings that actually took effect. Rulemakings that touched the section without changing it — withdrawn, superseded, or still conditional — are returned separately in `lifecycleEvents`, each with the reason and the verbatim sentence proving it. A non-empty `lifecycleEvents` is often the most interesting fact about a section: it means PHMSA tried to change it and the change did not stand. Never report a `lifecycleEvents` entry as a revision. A `lifecycleEvents` entry with `provisionScoped: true` means the rulemaking DID take effect for other sections and not for this one - PHMSA delayed, stayed, or withdrew it for this provision alone. Never generalise such an entry to the rest of the rule, and never say the rule was withdrawn. An empty `versions` alongside a `provisionScoped` entry means no Federal Register amendment in the corpus has changed this section: its text is the older text, and that is the correct answer to "what does it say today". Results carry an authority tier; only BINDING (49 CFR) and JUDICIAL sources state law — POLICY and lower is guidance, not legally binding.
Input schema:
```json
{
"type": "object",
"properties": {
"citation": {
"type": "string",
"minLength": 1,
"description": "CFR citation in any common form (e.g., \"§192.605\")."
},
"canvas": {
"type": "object",
"properties": {
"sessionId": {
"type": "string",
"minLength": 1,
"description": "Canvas session id"
},
"canvasSessionId": {
"type": "string",
"minLength": 1,
"description": "Alias for sessionId."
},
"subquestionNodeId": {
"type": "string",
"minLength": 1,
"description": "Subquestion node id under which to attach candidate evidence (a.k.a. parentNodeId)"
},
"parentNodeId": {
"type": "string",
"minLength": 1,
"description": "Alias for subquestionNodeId."
},
"subquestionId": {
"type": "string",
"minLength": 1,
"description": "Alias for subquestionNodeId."
}
},
"additionalProperties": false,
"description": "OBJECT { sessionId (or canvasSessionId), subquestionNodeId (or parentNodeId/subquestionId) } — a bare string is accepted as the sessionId. OPTIONAL — attach this read's citation-bearing hits to a canvas research session as candidate evidence. subquestionNodeId is a subquestion node id (from canvas_add_subquestion / canvas_start_session); evidence binds to the NAMED subquestion — never the session root; omit it and the response lists the subquestion node ids to pick from. The response returns the created evidence node ids so you can canvas_classify them."
}
},
"required": [
"citation"
],
"additionalProperties": false
}
```
### `get_sections`
NARROW / batch-text fallback — full text of MANY sections at once, text only; for one section full context use describe_section (facets) or section_neighborhood (graph edges) on each instead. Batch-retrieve multiple CFR sections in ONE call. Pass `citations` (a list — e.g. ["§192.605", "§195.452", "Appendix D to Part 192"]) or `range` (a span string — e.g. "§§192.605-192.625"). Resolves every anchor through the canonical parser (single, range, list, and appendix forms all work), looks them all up at once, and returns one row per anchor with `exists` flagged so you can see which inputs landed. Use this instead of calling get_section N times. body:"snippet" (default) keeps the batch budget tight; capped at 25 sections. Results carry an authority tier; only BINDING (49 CFR) and JUDICIAL sources state law — POLICY and lower is guidance, not legally binding.
Input schema:
```json
{
"type": "object",
"properties": {
"citations": {
"type": "array",
"items": {
"type": "string",
"minLength": 1
},
"description": "A list of CFR citations to fetch in one call (e.g., [\"§192.605\", \"49 CFR 195.452\", \"Appendix D to Part 192\"]). Each entry is parsed independently via the canonical parser, so any common citation form — including appendices — works. Use this OR `range`, not both."
},
"range": {
"type": "string",
"minLength": 1,
"description": "A contiguous same-part section range as a single string (e.g., \"§§192.605-192.625\" or \"192.605 to 192.625\"). Enumerates every section number in the span. Use this OR `citations`, not both."
},
"body": {
"type": "string",
"enum": [
"full",
"snippet",
"none"
],
"default": "snippet",
"description": "Body rendering per section: full text, a short snippet (default — keeps the batch token budget tight), or metadata only."
},
"format": {
"type": "string",
"enum": [
"plain",
"html"
],
"default": "plain",
"description": "Output format per section. 'plain' (default) returns textPlain; 'html' returns sanitized textHtml — use this with body:'full' to retrieve intact tables (rows survive). Falls back to plain when a section has no HTML column."
},
"canvas": {
"type": "object",
"properties": {
"sessionId": {
"type": "string",
"minLength": 1,
"description": "Canvas session id"
},
"canvasSessionId": {
"type": "string",
"minLength": 1,
"description": "Alias for sessionId."
},
"subquestionNodeId": {
"type": "string",
"minLength": 1,
"description": "Subquestion node id under which to attach candidate evidence (a.k.a. parentNodeId)"
},
"parentNodeId": {
"type": "string",
"minLength": 1,
"description": "Alias for subquestionNodeId."
},
"subquestionId": {
"type": "string",
"minLength": 1,
"description": "Alias for subquestionNodeId."
}
},
"additionalProperties": false,
"description": "OBJECT { sessionId (or canvasSessionId), subquestionNodeId (or parentNodeId/subquestionId) } — a bare string is accepted as the sessionId. OPTIONAL — attach this read's citation-bearing hits to a canvas research session as candidate evidence. subquestionNodeId is a subquestion node id (from canvas_add_subquestion / canvas_start_session); evidence binds to the NAMED subquestion — never the session root; omit it and the response lists the subquestion node ids to pick from. The response returns the created evidence node ids so you can canvas_classify them."
}
},
"additionalProperties": false,
"description": "Fetch multiple CFR sections in one call. Provide `citations` (a list) or `range` (a span string). Appendices (e.g., \"Appendix D to Part 192\") resolve like normal sections."
}
```
### `get_smart_interpretation`
Fetch a single SmartInterpretation by id with full verbatim text + provenance + targeting. To DISCOVER which SIs touch a section, start with list_section_interpretations (the canonical section-SI entry); this tool fetches ONE SI by id once you have it. describe_section returns SI snippets; use this to pull the complete text when you need to quote or cite. Returns interpretationType (one of 26 speech-acts), authorityTier, publisher metadata, and any operator/incident targeting. authorityTier precedence (highest→lowest): STATUTORY > BINDING > JUDICIAL > POLICY > GUIDANCE > OVERSIGHT > HISTORICAL. Only STATUTORY, BINDING, and JUDICIAL state law; POLICY and below are guidance and must NOT be presented as a legal requirement. See the kondwit://taxonomy/smart-interpretation resource for every speech-act + tier definition and the tools-core tier cross-map. Fails with not_found if the id does not exist. Results carry an authority tier; only BINDING (49 CFR) and JUDICIAL sources state law — POLICY and lower is guidance, not legally binding. Interpretations extracted from PROPOSED rules (NPRMs) are served at the GUIDANCE tier, never BINDING: an NPRM states what the agency proposed to require, not what it requires. Each such row carries `proposalDerived: true` and `ruleStatus: "PROPOSED"` — never cite one as a legal requirement.
Input schema:
```json
{
"type": "object",
"properties": {
"id": {
"type": "string",
"minLength": 1,
"description": "SmartInterpretation.id (e.g., from describe_section.smartInterpretations[].id)."
},
"canvas": {
"type": "object",
"properties": {
"sessionId": {
"type": "string",
"minLength": 1,
"description": "Canvas session id"
},
"canvasSessionId": {
"type": "string",
"minLength": 1,
"description": "Alias for sessionId."
},
"subquestionNodeId": {
"type": "string",
"minLength": 1,
"description": "Subquestion node id under which to attach candidate evidence (a.k.a. parentNodeId)"
},
"parentNodeId": {
"type": "string",
"minLength": 1,
"description": "Alias for subquestionNodeId."
},
"subquestionId": {
"type": "string",
"minLength": 1,
"description": "Alias for subquestionNodeId."
}
},
"additionalProperties": false,
"description": "OBJECT { sessionId (or canvasSessionId), subquestionNodeId (or parentNodeId/subquestionId) } — a bare string is accepted as the sessionId. OPTIONAL — attach this read's citation-bearing hits to a canvas research session as candidate evidence. subquestionNodeId is a subquestion node id (from canvas_add_subquestion / canvas_start_session); evidence binds to the NAMED subquestion — never the session root; omit it and the response lists the subquestion node ids to pick from. The response returns the created evidence node ids so you can canvas_classify them."
}
},
"required": [
"id"
],
"additionalProperties": false
}
```
### `get_state_adoption`
A state's adoption posture, verified against the primary source: which federal parts it adopts (and which its clause explicitly does NOT adopt), under which incorporation mode (static-ibr / rolling-ibr / rewrite / mixed / none), the edition pin date, and the adopting citation clause where one is recorded (null for a jurisdiction that adopts nothing, or whose citation is not yet recorded). The adoption posture is the core of the index and is carried for all 52. Read-only; no inference.
Input schema:
```json
{
"type": "object",
"properties": {
"stateCode": {
"type": "string",
"pattern": "^[A-Za-z]{2}$",
"description": "Two-letter USPS state code (e.g. \"TX\", \"PA\", \"CA\", \"DC\", \"PR\")."
}
},
"required": [
"stateCode"
],
"additionalProperties": false
}
```
### `get_state_agency`
Who regulates pipeline safety in a state — one row per role (gas / hazliq / UNGS / damage-prevention / rulemaker), each with its name, live link, and link status. Carried and primary-source verified for all 52 jurisdictions; this and get_state_adoption are what the state index IS. Read-only; no inference.
Input schema:
```json
{
"type": "object",
"properties": {
"stateCode": {
"type": "string",
"pattern": "^[A-Za-z]{2}$",
"description": "Two-letter USPS state code (e.g. \"TX\", \"PA\", \"CA\", \"DC\", \"PR\")."
}
},
"required": [
"stateCode"
],
"additionalProperties": false
}
```
### `list_audit_allegations`
List YOUR audit case's allegation items (AuditInstrumentAllegation rows the operator authored on a received PHMSA instrument), so you can pick an allegationId for research_allegation. Returns id + item number + cited regulation + resolved §section + disposition + parent instrument + audit-session title. These are YOUR audit-workflow allegations, NOT corpus enforcement precedent — for penalized/historical enforcement examples use find_enforcement_exemplars (those ids are NOT research_allegation inputs). Read-only; no inference.
Input schema:
```json
{
"type": "object",
"properties": {
"auditSessionId": {
"type": "string",
"minLength": 1,
"description": "Narrow to one audit session."
},
"instrumentId": {
"type": "string",
"minLength": 1,
"description": "Narrow to one received instrument."
},
"limit": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"description": "Max rows (default 50, cap 100)."
}
},
"additionalProperties": false
}
```
### `list_canvas_sessions`
List your canvas research sessions so you can discover and RESUME work started in an earlier conversation. Includes both ACTIVE and COMPLETED sessions (only ARCHIVED sessions are hidden) — a completed thread is still fully resumable. Returns each session as { id, title, anchorSectionId, status, completedAt, updatedAt, nodeCount }, most-recently-active first; `status` is "ACTIVE" or "COMPLETED". Pass a returned id as canvasSessionId to canvas_start_session / canvas_add_subquestion / finalize_memo to continue that thread instead of starting cold. Trigger: the user references prior research ("pick up where we left off", "the §192.5 memo from before") or you want to avoid duplicating an existing session.
Input schema:
```json
{
"type": "object",
"properties": {
"limit": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"description": "Max sessions to return (default 20, most-recently-active first)."
}
},
"additionalProperties": false
}
```
### `list_jurisdictions`
The 52 state pipeline-safety jurisdictions with their adoption posture, certification/applicability profile (gas / hazliq / UNGS / LNG), incorporation mode + edition pin, per-state text-loaded status, and the two-axis freshness badge. The entry point — pick a stateCode here, then drill in with get_state_adoption / get_state_agency / find_state_delta / compare_state_to_federal. KONDWIT carries a primary-source-verified adoption and regulator index for all 52 pipeline-safety jurisdictions: which agency regulates gas, hazardous liquid, UNGS and LNG in each, the §60105/60106 certification profile, the damage-prevention regime, and the exact adopting citation - which federal parts the state adopts, under which incorporation mode, pinned to which edition date. The index is not a state regulatory-text corpus: state-authored section text is served only for a jurisdiction whose loaded text has been verified to be that state’s own pipeline-safety regulation. A jurisdiction holding text that has not been verified reports `textStatus: "unverified"`, serves no sections, and is counted in `textCoverage.withheldStateCodes` rather than in `withText`. `list_jurisdictions` reports all of this live - read it rather than assuming text exists. Read-only; no inference.
Input schema:
```json
{
"type": "object",
"properties": {},
"additionalProperties": false
}
```
### `list_search_pins`
List a canvas session's research-search pins, optionally filtered by status (PROPOSED / ACCEPTED / DISMISSED / ALL), with per-status counts — the read side of the triage workspace. Each pin includes a resultsCacheJson summary (result count + top items + source narrowing) of the rows the proposal pre-executed, so a proposed pin's hits can be read WITHOUT a separate search call.
Input schema:
```json
{
"type": "object",
"properties": {
"sessionId": {
"type": "string",
"minLength": 1,
"description": "Canvas session id to list pins for"
},
"status": {
"type": "string",
"enum": [
"PROPOSED",
"ACCEPTED",
"DISMISSED",
"ALL"
],
"default": "ALL",
"description": "Filter by pin status, or 'ALL' (default) for every status with per-status counts"
}
},
"required": [
"sessionId"
],
"additionalProperties": false
}
```
### `list_section_interpretations`
CANONICAL START for a section Smart Interpretations — begin here when the question is "what interpretations touch this section". Enumerate the Smart Interpretations at a section (optionally scoped to one paragraph anchor), paginated. The agent equivalent of clicking the reader's rolled-up interpretation-density pill: where get_smart_interpretation fetches one SI by id and describe_section returns a top-N sample, this returns the FULL paginated list at a (section, paragraph) with each row's source end (type + verbatim snippet) and target end (§section + paragraph + verbatim snippet). Pass paragraph_anchor in paren form ("(c)(4)(iii)") to scope to one paragraph; omit it for the whole-section interpretations. Narrow a dense anchor with the facet filters interpretation_type (relationship verb / speech-act, e.g. "CLARIFIES"), authority_tier (e.g. "BINDING", "JUDICIAL"), and source_type (e.g. "court_holding") — they AND together (e.g. the binding court holdings at this section). For the enforcement source, allegation_status narrows the case_allegation rows by outcome bucket (upheld / active / other), and each allegation row carries its raw allegationStatus token. By DEFAULT the list HIDES the high-volume special-permit-condition + non-compliance-notification rows that otherwise bury the genuine PHMSA interpretation letters and inflate the count, so `total` is one consistent noise-free denominator and `letterTotal` is the genuine-letter count; pass include_permit_conditions:true to see those families. Page through dense anchors (some sections carry thousands). Then call get_smart_interpretation with a row id for the complete verbatim text. Read-only; no inference. Results carry an authority tier; only BINDING (49 CFR) and JUDICIAL sources state law — POLICY and lower is guidance, not legally binding. Interpretations extracted from PROPOSED rules (NPRMs) are served at the GUIDANCE tier, never BINDING: an NPRM states what the agency proposed to require, not what it requires. Each such row carries `proposalDerived: true` and `ruleStatus: "PROPOSED"` — never cite one as a legal requirement.
Input schema:
```json
{
"type": "object",
"properties": {
"section_id": {
"type": "string",
"minLength": 1,
"description": "Section.id (from resolve_citation or any retrieval tool). Provide this OR `citation`."
},
"citation": {
"type": "string",
"minLength": 1,
"description": "CFR citation in any common form (e.g., \"§192.605\", \"49 CFR 195.452\"). Resolved to a Section.id internally. Provide this OR `section_id`."
},
"paragraph_anchor": {
"type": "string",
"description": "Corpus paren-form paragraph key (\"(c)(4)(iii)\") to scope the list to one paragraph. OMIT (or pass empty) to list the whole-section interpretations. Match the form the margin pill groups on — the list total equals that anchor's pill count."
},
"interpretation_type": {
"type": "string",
"description": "Facet: narrow to one relationship verb / speech-act (e.g. \"CLARIFIES\", \"ENFORCES_AGAINST\", \"RESTATES\"). The reader's DEFAULT triage facet (\"this source says THIS\"). Combine with the other facets (AND)."
},
"authority_tier": {
"type": "string",
"description": "Facet: narrow to one authority tier (e.g. \"BINDING\", \"JUDICIAL\", \"GUIDANCE\", \"POLICY\"). Combine with the other facets (AND) — e.g. the binding court holdings here."
},
"source_type": {
"type": "string",
"description": "Facet: narrow to one source corpus kind (e.g. \"interpretation\", \"court_holding\", \"enforcement_case\", \"fr_document\"). The common discovery-tool / get_document type names are accepted and mapped to the SI source vocabulary (e.g. \"interpretation\" → interpretation_response, \"enforcement_case\" → case_allegation). Combine with the other facets (AND)."
},
"allegation_status": {
"type": "string",
"enum": [
"upheld",
"active",
"other"
],
"description": "Facet (enforcement only): narrow the case_allegation interpretations by outcome bucket — \"upheld\" (the defensible / penalty-upheld allegations), \"active\" (open/proposed), or \"other\" (withdrawn/reduced). Use with source_type \"enforcement_case\". Each allegation row returns its raw allegationStatus token (UPHELD/PROPOSED/REDUCED/WITHDRAWN)."
},
"include_permit_conditions": {
"type": "boolean",
"default": false,
"description": "Include the special-permit-condition + non-compliance-notification rows that are HIDDEN by default (they otherwise bury the genuine PHMSA interpretation letters and inflate the count). Default false — the list leads with real interpretation letters/authorities."
},
"page": {
"type": "integer",
"default": 1,
"description": "1-based page number. The densest anchor (§192.605 ≈ 5,105 SIs) MUST paginate.",
"minimum": 1
},
"limit": {
"type": "integer",
"maximum": 50,
"default": 25,
"description": "Page size, max 50.",
"minimum": 1
},
"canvas": {
"type": "object",
"properties": {
"sessionId": {
"type": "string",
"minLength": 1,
"description": "Canvas session id"
},
"canvasSessionId": {
"type": "string",
"minLength": 1,
"description": "Alias for sessionId."
},
"subquestionNodeId": {
"type": "string",
"minLength": 1,
"description": "Subquestion node id under which to attach candidate evidence (a.k.a. parentNodeId)"
},
"parentNodeId": {
"type": "string",
"minLength": 1,
"description": "Alias for subquestionNodeId."
},
"subquestionId": {
"type": "string",
"minLength": 1,
"description": "Alias for subquestionNodeId."
}
},
"additionalProperties": false,
"description": "OBJECT { sessionId (or canvasSessionId), subquestionNodeId (or parentNodeId/subquestionId) } — a bare string is accepted as the sessionId. OPTIONAL — attach this read's citation-bearing hits to a canvas research session as candidate evidence. subquestionNodeId is a subquestion node id (from canvas_add_subquestion / canvas_start_session); evidence binds to the NAMED subquestion — never the session root; omit it and the response lists the subquestion node ids to pick from. The response returns the created evidence node ids so you can canvas_classify them."
}
},
"additionalProperties": false
}
```
### `propose_search_pin`
Propose a research-search pin on a canvas session (status PROPOSED, proposedByAi=true) — the agent-side equivalent of the in-reader Guide proposing a search to triage. The proposal is rejected with a typed error when the session is already at the hard PROPOSED cap (triage existing proposals first), mirroring the /read Search-Triage governance. Inference (deciding what to propose) is client-side; this tool only persists the proposal.
Input schema:
```json
{
"type": "object",
"properties": {
"sessionId": {
"type": "string",
"minLength": 1,
"description": "Canvas session id the pin lands on"
},
"keyword": {
"type": "string",
"minLength": 1,
"description": "The search keyword / query the pin captures"
},
"aiRationale": {
"type": "string",
"description": "Why the agent is proposing this search (surfaced in the triage UI)"
},
"searchType": {
"type": "string",
"description": "Search flavor — 'keyword' (FTS) | 'dsl' (boolean DSL — AND/OR/NOT, field:value) | 'ai' (semantic) | 'similar_to'. Omit to auto-stamp: a boolean-DSL keyword → 'dsl' (routes through the compiled-tsquery path), a plain phrase → 'keyword'."
},
"subQuestionId": {
"type": "string",
"description": "Optional subquestion node id this proposed search belongs under"
},
"color": {
"type": "string",
"description": "Optional chip color for the pin"
}
},
"required": [
"sessionId",
"keyword"
],
"additionalProperties": false
}
```
### `recall_canvas`
RECALL the accumulated research context of a canvas session back INTO your context: the research question, each subquestion, the evidence gathered under it (with source provenance + the classification it received), the per-subquestion synthesis, and the session memo. The canvas_* tools only WRITE; this is the read-back that makes the canvas a working memory you can offload to instead of re-fetching. Trigger: resuming a multi-turn research/audit-prep arc, before drafting a memo, or any time you need to re-ground in what you already gathered. Omit canvasSessionId to recall your most recently active session. READ-ONLY: reads back what was stored — it never advances a workflow, classifies, pins, or files (those stay in Kondwit).
Input schema:
```json
{
"type": "object",
"properties": {
"canvasSessionId": {
"type": "string",
"description": "Canvas session to recall. Omit to recall your most recently active session (from list_canvas_sessions / canvas_start_session)."
},
"sessionId": {
"type": "string",
"description": "Alias for canvasSessionId."
}
},
"additionalProperties": false
}
```
### `refine_search_pin`
Refine an existing research-search pin's CRITERIA (keyword, searchScope, sourceTypeFilters, tierFilters, dateFilter) — the agent-side equivalent of the in-app Edit-criteria affordance. Updates the SAME pin in place (never creates a second pin) and does NOT change its status: refining a PROPOSED pin leaves it PROPOSED, refining an ACCEPTED pin leaves it ACCEPTED — status transitions stay in triage_search_pin. Send only the fields that change; the pin color and AI-proposal provenance are preserved. Deciding HOW to refine is client-side; this tool only persists the new criteria.
Input schema:
```json
{
"type": "object",
"properties": {
"sessionId": {
"type": "string",
"minLength": 1,
"description": "Canvas session id the pin lives on"
},
"pinId": {
"type": "string",
"minLength": 1,
"description": "The pin whose criteria are being refined"
},
"keyword": {
"type": "string",
"minLength": 1,
"description": "New search keyword / query for the pin"
},
"searchScope": {
"type": "string",
"enum": [
"CFR_ONLY",
"ALL_DOCS"
],
"description": "Search scope — 'CFR_ONLY' (this part) | 'ALL_DOCS' (cross-source)"
},
"sourceTypeFilters": {
"type": "array",
"items": {
"type": "string"
},
"description": "Source-type filter ids (ALL_DOCS), e.g. regulation / interpretation / notice"
},
"tierFilters": {
"type": "array",
"items": {
"type": "string"
},
"description": "Authority-tier filter ids (ALL_DOCS), e.g. BINDING / QUASI_BINDING"
},
"dateFilter": {
"type": "string",
"enum": [
"all",
"1y",
"5y",
"10y"
],
"description": "Date window — 'all' | '1y' | '5y' | '10y'"
}
},
"required": [
"sessionId",
"pinId"
],
"additionalProperties": false
}
```
### `research_allegation`
Build the enforcement research lens for a single audit-instrument allegation: project the allegation as a subquestion node under the audit session's canvas, with two register-tagged evidence bundles beneath it. The REQUIREMENTS bundle is DETERMINISTIC (the rule-cited section + paragraph requirement text resolved from the allegation) — auditable, never a corpus aggregate. The RELATED ACTIONS bundle is INTERPRETIVE (similar enforcement actions from the corpus) — precedent / intelligence, NEVER a requirement. Keep the two registers separate when you synthesize. This tool ONLY builds the structured tree and returns the created node ids + register tags; it adds no server-side inference. Synthesize a position yourself (or via canvas_set_synthesis) from the returned nodes. Idempotent: re-running reuses existing nodes (wasCreated=false). These are YOUR audit case's allegations, NOT corpus enforcement precedent (that is find_enforcement_exemplars). Get a valid allegationId from list_audit_allegations.
Input schema:
```json
{
"type": "object",
"properties": {
"allegationId": {
"type": "string",
"minLength": 1,
"description": "The AuditInstrumentAllegation to research. The lens builds (or reuses, idempotently) the allegation-as-subquestion node under the audit session's canvas, with two evidence bundles hung beneath it. — obtain it from list_audit_allegations; it is NOT a corpus CaseAllegation id from find_enforcement_exemplars."
},
"includeRequirements": {
"type": "boolean",
"description": "Include the DETERMINISTIC requirements bundle — the cited section + paragraph requirement text resolved from the allegation. Default true. Requirements are rule-cited and auditable; they are NEVER corpus aggregates."
},
"includeRelatedActions": {
"type": "boolean",
"description": "Include the INTERPRETIVE related-actions bundle — similar enforcement actions from the ingested corpus (precedent). Default true. Related actions are intelligence, NEVER a requirement; do not blur them into the requirements register when synthesizing."
}
},
"required": [
"allegationId"
],
"additionalProperties": false
}
```
### `resolve_citation`
Parse any CFR citation form and confirm the section exists in the regulatory corpus. Returns canonical anchor (part, section, paragraph_path) plus a stable section_id usable by get_section or find_related_provisions.
Input schema:
```json
{
"type": "object",
"properties": {
"citation": {
"type": "string",
"minLength": 1,
"description": "CFR citation in any common form: \"§192.605\", \"49 CFR 195.452(c)(2)\", \"Part 192.605\", \"192.605\", \"§ 192.605\", or an appendix like \"Appendix D to Part 192\"."
}
},
"required": [
"citation"
],
"additionalProperties": false
}
```
### `search_corpus`
Unified corpus search across CFR sections, interpretations, notices, special permits, guidance, FAQs, enforcement actions, and policy documents. mode="lexical" for exact-token match (Postgres FTS), "semantic" for paraphrase via pgvector, "hybrid" for both ranked together (default). Replaces searchDatabase, substring_search, and semantic_search. Returns up to `limit` hits with stable ids consumable by get_document. jurisdiction scopes the CFR-section lane: "federal" (default), "all" (federal + every state), or a two-letter state code. IMPORTANT: Enforcement records that carry metadata only and no case text are NOT returned: an empty result row is worse than an absent one. They remain retrievable by CPF number. The missing text itself is tracked as kondwit/KONDWIT#945. This tool reports no suppressed count; call the Kondwit enforcement search for one. Results carry an authority tier; only BINDING (49 CFR) and JUDICIAL sources state law — POLICY and lower is guidance, not legally binding. Interpretations extracted from PROPOSED rules (NPRMs) are served at the GUIDANCE tier, never BINDING: an NPRM states what the agency proposed to require, not what it requires. Each such row carries `proposalDerived: true` and `ruleStatus: "PROPOSED"` — never cite one as a legal requirement.
Input schema:
```json
{
"type": "object",
"properties": {
"query": {
"type": "string",
"minLength": 1,
"description": "Short keyword or phrase to search (2–5 key terms work best)."
},
"mode": {
"type": "string",
"enum": [
"lexical",
"semantic",
"hybrid"
],
"default": "hybrid",
"description": "Search mode: lexical (Postgres FTS, fast, exact matches), semantic (pgvector cosine, paraphrase-aware), hybrid (both, ranked)."
},
"sources": {
"type": "array",
"items": {
"type": "string",
"enum": [
"regulation",
"interpretation",
"notice",
"special_permit",
"guidance",
"faq",
"enforcement_action",
"policy_document",
"smart_interpretation",
"court_holdings",
"court_holding"
]
},
"description": "Filter to specific corpus source types. Omit for all sources."
},
"parts": {
"type": "array",
"items": {
"type": "string"
},
"description": "CFR part numbers to scope the result set to (e.g., [\"192\"] for gas, [\"195\"] for hazardous liquid). Applies across regulation, Smart Interpretation, interpretation, enforcement, notice, special-permit, guidance and FAQ hits — scopes out the mirror-part 192↔195 material on every one. (policy_document is NOT part-scoped — it has no reliable CFR-part anchor — so those hits come back unfiltered and are named in `scopeNote`.) Omit to search all parts."
},
"limit": {
"type": "integer",
"maximum": 100,
"default": 15,
"description": "Max results (default 15, max 100).",
"minimum": 1
},
"highlight": {
"type": "boolean",
"default": false,
"description": "Spec 158 F09.3 — when true, wrap query-token matches in each snippet with <mark>...</mark> tags. HTML entities are escaped first so corpus content can't smuggle markup. Default false for backwards compat."
},
"min_similarity": {
"type": "number",
"minimum": 0,
"maximum": 1,
"description": "Spec 158 F11.7 — minimum cosine similarity (0-1) for semantic mode. Default 0.3 (drops obvious noise). Set 0 to opt out. Lexical mode ignores this — FTS pre-filters by tsquery match."
},
"lens": {
"type": "string",
"enum": [
"profile",
"off"
],
"default": "profile",
"description": "Spec 228 F06 — role-profile relevance lens. 'profile' (default) stably reorders results by the authed user's Kondwit role profile (focus-area relevance); the result SET is unchanged — items are only reordered, with non-focus items marked `outOfFocus: true`. 'off' disables the lens for this call (stateless override). Callers without a role profile get identical results either way."
},
"jurisdiction": {
"type": "string",
"description": "Jurisdiction scope for the CFR-section (regulation) lane: \"federal\" (the DEFAULT — federal 49 CFR only, byte-identical to prior behavior when omitted), \"all\" (federal + every state), or a two-letter state code (e.g. \"TX\") to scope sections to one state. Only the regulation lane is jurisdiction-scoped — the other corpora (interpretations, notices, permits, …) have no state analog."
},
"canvas": {
"type": "object",
"properties": {
"sessionId": {
"type": "string",
"minLength": 1,
"description": "Canvas session id"
},
"canvasSessionId": {
"type": "string",
"minLength": 1,
"description": "Alias for sessionId."
},
"subquestionNodeId": {
"type": "string",
"minLength": 1,
"description": "Subquestion node id under which to attach candidate evidence (a.k.a. parentNodeId)"
},
"parentNodeId": {
"type": "string",
"minLength": 1,
"description": "Alias for subquestionNodeId."
},
"subquestionId": {
"type": "string",
"minLength": 1,
"description": "Alias for subquestionNodeId."
}
},
"additionalProperties": false,
"description": "OBJECT { sessionId (or canvasSessionId), subquestionNodeId (or parentNodeId/subquestionId) } — a bare string is accepted as the sessionId. OPTIONAL — attach this read's citation-bearing hits to a canvas research session as candidate evidence. subquestionNodeId is a subquestion node id (from canvas_add_subquestion / canvas_start_session); evidence binds to the NAMED subquestion — never the session root; omit it and the response lists the subquestion node ids to pick from. The response returns the created evidence node ids so you can canvas_classify them."
}
},
"required": [
"query"
],
"additionalProperties": false
}
```
### `section_neighborhood`
Given one CFR section citation, return that section's text plus its 1-hop regulatory neighborhood — interpretations of it, enforcement against it, amendments, cross-references, and structural parent/children — as a single deterministic evidence set. Use this instead of chaining get_section + find_related_provisions yourself: it runs the same fixed pipeline KONDWIT uses internally, so the evidence is repeatable across calls. This is the GRAPH-NEIGHBORHOOD lane: use it when you need the 1-hop edge set (which provisions connect to this section) as one repeatable evidence bundle. For a multi-facet compound briefing of a single section instead (IAQ compliance criteria + enforcement profile + amendment history + citing-doc counts), use describe_section; drop to the atomic find_related_provisions only for a single edge family or a multi-hop walk this bundle does not run. The result carries a `familiesPresent`/`familiesEmpty` summary (in the rendered text and the structured payload) so you can see which edge families were checked-and-present vs checked-and-empty — never assume an absent family means the bundle dropped it.
Input schema:
```json
{
"type": "object",
"properties": {
"citation": {
"type": "string",
"minLength": 1,
"description": "A CFR citation, e.g. \"§192.605\", \"192.605\", or \"49 CFR 195.452(e)\"."
}
},
"required": [
"citation"
],
"additionalProperties": false
}
```
### `triage_search_pin`
Triage a research-search pin: accept (status ACCEPTED — keep) or dismiss (status DISMISSED — reject, with an optional reason) — the agent-side equivalent of the /read Include / Exclude triage controls. A dismiss emits the same graph-feedback event the UI does, so the intelligence layer learns from rejected proposals.
Input schema:
```json
{
"type": "object",
"properties": {
"sessionId": {
"type": "string",
"minLength": 1,
"description": "Canvas session id the pin lives on"
},
"pinId": {
"type": "string",
"minLength": 1,
"description": "The pin being triaged"
},
"decision": {
"type": "string",
"enum": [
"accept",
"dismiss"
],
"description": "'accept' → ACCEPTED (keep) | 'dismiss' → DISMISSED (reject)"
},
"dismissReason": {
"type": "string",
"enum": [
"not_relevant",
"duplicate",
"wrong_scope",
"wrong_artifact"
],
"description": "Optional reason slug — only used when decision=dismiss"
}
},
"required": [
"sessionId",
"pinId",
"decision"
],
"additionalProperties": false
}
```
### `verify_claim`
Verify a specific statement against the regulatory corpus. Returns candidate evidence (passages that may support or contradict the claim) with quoted excerpts and stable ids. The LLM applies the final supports/contradicts judgment; this tool surfaces grounded material.
Input schema:
```json
{
"type": "object",
"properties": {
"claim": {
"type": "string",
"minLength": 8,
"description": "The specific statement to verify (≥8 chars). Example: \"Operators must inspect cathodic protection annually.\""
},
"sources": {
"type": "array",
"items": {
"type": "string",
"enum": [
"regulation",
"interpretation",
"notice",
"enforcement_action",
"policy_document",
"guidance"
]
},
"description": "Restrict evidence to these source types"
},
"parts": {
"type": "array",
"items": {
"type": "string"
},
"description": "CFR part numbers to scope candidate evidence to (e.g., [\"192\"] for gas, [\"195\"] for hazardous liquid). Applies across regulation, Smart Interpretation, interpretation, enforcement, notice and guidance evidence — a gas claim stops surfacing mirror-part 192↔195 liquid material. (policy_document evidence is NOT part-scoped — no reliable CFR-part anchor — and is named in `scopeNote` when it appears.) Omit for all parts."
},
"limit": {
"type": "integer",
"maximum": 20,
"default": 8,
"minimum": 1
},
"classify_stance": {
"anyOf": [
{
"type": "boolean"
},
{
"type": "string",
"const": "heuristic"
}
],
"default": "heuristic",
"description": "Stance pre-label. 'heuristic' (default): a fast, deterministic verb/negation hint per evidence item — NON-AUTHORITATIVE, not entailment. false: raw candidates, no labels. This tool surfaces grounded evidence; YOU (the client LLM) apply the supports/contradicts judgment against each quoted excerpt."
}
},
"required": [
"claim"
],
"additionalProperties": false
}
```
## Prompts
_4 prompts registered._
### `cite_and_explain`
**Cite & explain a CFR section**
Look up a specific 49 CFR section and explain what it requires, grounded verbatim in the regulatory text. Use for "what does §X say / require" questions.
Arguments:
- `citation` _(required)_ — The CFR section to look up, e.g. "§192.605" or "49 CFR 195.452(e)".
- `question` — Optional specific question about the section.
### `compare_provisions`
**Compare two CFR provisions**
Compare two CFR sections: both texts plus a deterministic diff of their regulatory neighborhoods (what they share, what is unique to each). Use for "compare / contrast §A against §B" questions.
Arguments:
- `citationA` _(required)_ — First CFR section, e.g. "§192.605".
- `citationB` _(required)_ — Second CFR section, e.g. "§195.402".
- `question` — Optional comparison focus, e.g. "which has stricter recordkeeping".
### `multi_hop_research`
**Cross-source research sweep**
Research a topic across regulation, interpretations, enforcement, and oversight sources. Use for open questions that need evidence pulled from multiple source types rather than a single section lookup.
Arguments:
- `topic` _(required)_ — The research question or topic, e.g. "operator qualification for welding tasks".
### `single_hop_reference`
**Section + its related provisions**
Pull a section's text plus its 1-hop regulatory neighborhood (interpretations, enforcement, amendments, cross-references) in one deterministic call. Use when a question centers on one section but needs the surrounding context to answer.
Arguments:
- `citation` _(required)_ — The anchor CFR section, e.g. "§192.605".
- `question` — Optional specific question to answer from the neighborhood.
Troubleshooting
- 401 invalid_token — your access token expired or the connection was revoked. Re-run the OAuth flow.
- 429 / too many requests — you've hit the rate-limit cap. Wait a minute and try again.
- 403 / no Kondwit account — the OAuth identity isn't bound to a Kondwit user. Sign up at kondwit.com.
No connector? Ask by email
You do not need an account, a client, or an OAuth flow to get an answer out of KONDWIT. Email a regulation question to guide@kondwit.com and the KONDWIT Guide replies with a cited answer grounded in the same corpus these tools read. New senders get 10 instant replies; after that, answers arrive with the next daily brief.
Support
Setup problems, access questions, and anything else: kondwit.com/support or support@kondwit.com. Source freshness for every corpus these tools read is published at /data-status.
Reviewer notes
For connector-directory reviewers: a demo account is available on request (no MFA, no signup wall) so you can run the prompts above end-to-end. Email mcp-review@kondwit.com and we will provision credentials and a short walkthrough.
- Privacy Policy — every data category we collect, process, and return, including MCP tool request/response logging.
- Terms of Service