Connect and discover
This page covers one integration pattern: the agent explores a canon
itself. It connects to an MCP server (directly or through a typed
client), searches in its own words, picks one question, learns which
inputs that question needs, and asks it. If your application instead
exposes its own domain tools and the model never sees law_* calls,
or prints a canon into code, or runs a pinned service, the choice is
made on Choose an integration pattern;
this page does not repeat that comparison.
MCP is not the only network interface: law serve is a long-running
HTTP service over one pinned world with an audit journal and /v1/...
routes (/v1/ask, /v1/world, plus health and metrics), run as a
private deployment; see Private HTTP service with law
serve. The pattern on this page uses
MCP.
Connect
Section titled “Connect”Public server and slices
Section titled “Public server and slices”Each MCP server has its own name, corpus slice and tool set, and every answer names its jurisdiction.
claude mcp add --transport http law-dsl https://mcp.arxo.io/mcpclaude mcp add --transport http law-dsl-islam https://mcp.arxo.io/mcp/islamThe root server carries the whole corpus with Kazakhstan as its main jurisdiction, under the law of the Republic of Kazakhstan with its official calendar. The Islam slice sees Islamic doctrine (fiqh, faraid, the Quran), which is not the law of a state and has no calendar. Never present a doctrinal or scientific slice as state law; the answers say what they are, and the agent repeats that.
Route check, dated: every path below was probed with tools/list
on 2026-10-03 (probe command in the Validation record).
| Route | Status | Meaning |
|---|---|---|
https://mcp.arxo.io/mcp | 200 | use this server |
https://mcp.arxo.io/mcp/islam | 200 | use this server for the Islam slice |
https://mcp.arxo.io/mcp/kz | 410 Gone | dead address; see the migration note |
Treat every slice path as a claim to re-check with tools/list, not
as a constant.
Migration note (diagnostics only). The old Kazakhstan slice address
https://mcp.arxo.io/mcp/kzanswers410 Gone: that slice no longer exists as a separate server. If a stored configuration still points at it, move it to the root route; the root server already carries Kazakhstan as its main jurisdiction. Never put the dead address in a fresh example.
Local server and typed client
Section titled “Local server and typed client”The repository carries an MCP configuration that starts the same server over stdio with the Kazakhstan profile selected. The network variant serves HTTP on loopback by default; binding off loopback requires an explicit authentication decision. As published in the MCP guide; not re-run for this page.
From TypeScript, @arxo/law-client offers a method per tool over
Streamable HTTP, a generic call(name, args), and registry access.
It is transport and shape validation only: no legal semantics, no
engine. Its listTools() asks the live server, and the server’s list,
not the client’s built-in table, is the truth about what is available.
The tool list is the authority
Section titled “The tool list is the authority”Call tools/list before planning anything. The set differs per
server and slice: slices do not carry the author workbench or
adversarial analytics, and the server instructions name what is
missing so an agent does not plan those calls. This section keeps no
tool list and no tool count, because both go stale and the server’s
answer does not. A tool-by-tool reading lives in the MCP tool
reference.
Two related lists deserve the same treatment:
- Prompts. Servers may offer prompts (task routes, review checklists). List them; a prompt that calls tools your profile denies is not offered. Do not reconstruct it by hand.
- Answer capabilities. The answer-capabilities matrix describes one thing only: which answer preparations a server supports. It is not a catalogue of the platform. Read it when you prepare or publish an answer (Prepare and publish an answer), not when you connect.
The discovery loop
Section titled “The discovery loop”Five steps: search in your own words, compare the candidates, read the winner’s signature, ask the rules for its inputs, pick the query kind. A wrong pick gives a correct answer to the wrong question.
The through-example comes from the Labour Code of the Republic of
Kazakhstan (kz-labour-code): a worker with one child under eighteen
months is given a twenty-minute feeding break. Is that shorter than
the minimum?
1. Start from the candidate list
Section titled “1. Start from the candidate list”Status: ran via MCP law_search against the repo server (jurisdiction
Republic of Kazakhstan as reported by the server; no profile name is
exposed).
// law_search request{ "query": "перерыв для кормления ребёнка короче минимума", "limit": 6 }The captured answer listed six candidates. The agent’s job is to compare, not to grab the top hit:
| # | Candidate | Kind | Score | Agent’s reading |
|---|---|---|---|---|
| 1 | feeding_break_too_short(employee, employer) | predicate | 0.4027 | Matches the story: a break shorter than the minimum |
| 2 | meal_break_too_short(employee, employer) | predicate | 0.2475 | A different break: meal rest, not child feeding |
| 3 | TK_ART82, article 82 point 3 | source fragment | 0.2108 | The pinned source text behind candidate 1 |
| 4 | TK_ART81, article 81 point 1 | source fragment | 0.1883 | The text behind the meal-break question |
| 5 | TK_ART82, article 82 point 4 | source fragment | 0.1816 | Nearby text about joining breaks; not the question |
| 6 | child_feeding_break_minutes(employee, employer, minutes) | predicate | 0.1801 | Looks like an input fact, not the verdict |
Similarity is not applicability: the score measures text overlap, not
whether the norm governs this case. The list is discovery only and
establishes no fact and no norm; only an ask can. Each candidate also carries
a next field, a ready template of the follow-up call; use it instead
of assembling arguments from memory.
2. Compare before you commit
Section titled “2. Compare before you commit”Candidate 1 wins and candidate 2 is the trap: both are “break too short” questions in the same act, and their labels differ by one concept. The agent reads both signatures and source fragments and records why the loser lost: the meal-break predicate is about daily rest-and-meal breaks, the story is about feeding a child.
Candidate 6 is a different case. Its three-place signature (employee, employer, minutes) marks it as data the case supplies; the two-place candidate 1 is the verdict the rules derive. Asking candidate 6 would only echo the input, so the agent asks candidate 1 and feeds candidate 6 in as a fact. Collect facts and evidence develops this input-versus-verdict split.
When two candidates survive comparison, the honest move is to ask the user a clarifying question, or to run both asks and report both. Never silently merge two questions into one.
3. Read the signature exactly
Section titled “3. Read the signature exactly”The agent reads the winner’s full name, argument order, argument types and namespace from the package catalog.
Status: ran via MCP law_packages (package kz-labour-code, Labour
Code of the Republic of Kazakhstan), same session as the search above.
The catalog confirms three-place and two-place signatures with typed
parameters, for example feeding_break_too_short(employee: Employee, employer: Employer) and child_feeding_break_minutes(employee: Employee, employer: Employer, minutes: Integer).
Short-name matching is not enough. On the local engine, a structural query for producers of the bare name
feeding_break_too_short returned No matches. Rows: 0, while the
same query with the namespaced name
urn:kz:corpus:clir:labour-code#feeding_break_too_short returned both
strict rules (commands under How to verify). Keep the full namespace
on every name you store; two acts can define similar names, and the
namespace is what keeps them apart.
4. Ask the rules which inputs the question needs
Section titled “4. Ask the rules which inputs the question needs”Do not guess facts, and do not rely only on the model reading rule
text. law_rules has two modes:
- Readable mode (
predicateset): the rules that derive the predicate, verbalized, with their premises and source articles. For a large act,packagealone returns a paged table of contents (limit,offset, and anextcall for the remainder). - Machine contract mode (
predicateset,contract: true): an input contract computed over the same import and dependency world thatlaw_askwould use. No case is evaluated; the server states that the result describes the program, not a legal answer.
Status: labeled pseudocode. The request shape and field names below are read from the server’s tool schema and contract builder; this call was NOT RUN for this page. Reproduce by sending it to the root server and comparing the field names.
// law_rules request, machine contract mode{ "package": "kz-labour-code", "predicate": "feeding_break_too_short", "contract": true, "inputLimit": 50 }The answer’s inputContract lists what the agent must collect:
| Field | What it tells the agent |
|---|---|
inputs | The leaf declarations the question depends on: canonical id, owning package, typed parameters (enum variants listed), argsTemplate, and inputKind: case (the case supplies it), table (a table input), or unresolved |
complete, gaps | Whether the projection is complete; each gap names a node the projection cannot follow, with a reason |
derivedPredicates, directRules | What the rules derive on the way, and the rules that produce the goal directly |
world | The packages and their semantic hashes the contract was computed over |
inputTotal, truncated, next | Paging: next is a ready law_rules call with the following inputOffset |
The top-level next is a law_ask template for the goal. Two rules
follow. If complete is false, the input list is not exhaustive:
say so rather than presenting it as everything the norm needs. And
contract requires predicate; a package-only contract call is
refused.
5. Pick the query kind
Section titled “5. Pick the query kind”The same predicate can be asked in several query kinds; the authority
for the set is the kind enum in the server’s law_ask schema, which
on the check date held six values. The agent picks the kind that
matches what the user actually needs:
| Query kind | What it returns | When the agent uses it |
|---|---|---|
truth | Whether the statement is established on the facts | The user wants a yes / not-established finding |
collect | The values a relation takes on the facts | The user wants a number, a date, or a list |
why_not | A per-premise report on a goal | The user asks “why didn’t this hold?” |
positions | Normative positions arising from the facts, each with its modality (duty, prohibition, power, liberty, immunity …) and status | The user asks “who must, may, or may not do what?” |
deadline | A computed time limit | The user asks “by when?” |
calc | An arithmetic expression over values and normative constants from the canon | The user wants a computed amount or comparison |
Status for truth: ran via the public MCP endpoint (root route); the
full call is below. Status for collect: ran via MCP law_ask with
kind collect, asking child_feeding_break_minutes with the minutes
argument left as the sample variable ?minutes and one case fact (25
minutes) supplied; the captured answer returned the single value 25,
grounded on that fact.
Status for why_not: observed as the per-premise report embedded in
the captured truth answers (satisfied / not-satisfied / undefined
premises); a standalone call was NOT RUN (reproduce: the minutes-only
truth call with kind why_not). Status for positions, deadline
and calc: NOT RUN; the through-example needed none. A deadline ask
names its deadline policy explicitly: without one, a norm with a term
in days answers MISSING_POLICY, and the server does not assume a
policy.
Two cautions on reading the result. An empty positions list does not by itself establish a cause; do not explain it as “missing facts” without the diagnostics. And an empty collection, an unknown value, a zero and a proven absence are four different outcomes; only the question’s own contract says which one an empty result means. Never translate “no rows” into “no obligation”. Every status and the next step it allows are in the single table on Read answers.
The ask
Section titled “The ask”The whole loop ends in one call. Field names below are the contract; values illustrate one run (check date in the Validation record). The date of law is named by the human, never defaulted to today; see Pin context, editions and time.
curl -s https://mcp.arxo.io/mcp \ -H 'content-type: application/json' \ -H 'accept: application/json, text/event-stream' \ -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{ "name":"law_ask", "arguments":{ "kind":"truth", "package":"kz-labour-code", "predicate":"feeding_break_too_short", "args":["Айгуль","Работодатель"], "facts":[ {"predicate":"child_feeding_break_minutes","args":["Айгуль","Работодатель",20]}, {"predicate":"children_under_eighteen_months","args":["Айгуль",1]} ], "legalTime":"2026-09-01"}}}'Status: ran via the public MCP endpoint (root route) on 2026-10-03.
The reply carries a human line plus the same answer as a document:
truthStatus TRUE_ONLY, the rule for one child (“not less than
thirty minutes”, Article 82), the Labour Code as provenance, and a
resultHash. The MCP guide walks the same call in
full.
Empty search results
Section titled “Empty search results”Sometimes the search returns nothing useful. The session’s garbage query is instructive.
Status: ran via MCP law_search with a nonsense query string. The
captured answer returned a single low-score candidate (score 0.158)
from an unrelated act about construction in seismic zones.
Three lessons for the agent:
- A near-empty result says nothing about whether a norm exists in the system. The catalog may hold the norm under words the query did not use; the search tool itself states that an empty result does not mean the norm is absent.
- A lone low-score candidate is usually noise. The agent must not inflate it into a “closest match” answer.
- The recovery is to rephrase, not to conclude. Try the act’s own
vocabulary (found via the package catalog), narrow the search with
packageorkinds, split the story into smaller concepts, or ask the user for the document or article they have in mind.
If rephrasing still yields nothing, the agent reports “no suitable question found in the executable model” and names the queries it tried. That report is itself useful output. Keep it distinct from two stronger findings: a matching question is absent from the checked package version; or a discovered question has no executable coverage in the current model. Those findings require inspection of the package or the question’s coverage, not an empty search response. Never conclude from a search alone that the norm was never formalized, and never conclude that the law is silent: “the model does not contain this norm” and “the law says nothing” are different answers.
Ambiguous terms and homonyms
Section titled “Ambiguous terms and homonyms”Legal language reuses words: “break” in the Labour Code means at least meal rest and child feeding, and “minimum” appears across dozens of thresholds. Compare full labels rather than single words, open the source fragments (article 82 on nursing breaks, article 81 on meal rest), check which facts the rules read (child counts and minutes versus shift schedules), and keep namespaces. When the story genuinely fits two questions, run both and label both. A candidate list is a starting point for checks, never a completed check.
Missing capabilities
Section titled “Missing capabilities”Every absence has its own shape, and the agent must not collapse them:
| Absence | Looks like | Agent behavior |
|---|---|---|
Tool not in tools/list | the name is simply not there | do not call it; do not emulate it; say which server would have it, if one is known |
| Slice without a contour | direct call refuses with a reason | relay the refusal; reroute to a server that offers it, or stop |
| Unknown predicate | refusal naming available addresses | search in your own words; never guess a name (Empty search results) |
| Unknown profile path | transport error, not an answer | check the URL; a transport failure is never an answer about the model |
The failure case to memorize: an agent plans a workbench call on a slice, gets a refusal, and “helpfully” performs the step itself in prose. The refusal named a boundary; crossing it in prose is exactly what the agent contract forbids.
How to verify
Section titled “How to verify”- Re-run the
curlabove; expectTRUE_ONLYwith the Article 82 rule and aresultHash(values may move with the corpus; the field names are the stable part). - Call
tools/liston each server you use; diff the sets and confirm your agent plans only listed tools. - Repeat the
law_search,law_packagesandcollectcalls and compare with the six candidates, the two signatures and the value 25. - Send the
law_rulescontract request above and compare the inputsinputContractnames, and itscompleteflag, with the facts thelaw_askcall supplies. - Reproduce the namespace lesson on the local engine:
./law version./law query producers \ --param predicate='urn:kz:corpus:clir:labour-code#feeding_break_too_short' \ --param pkg=kz.corpus.labour_code --format human./law query producers \ --param predicate='feeding_break_too_short' \ --param pkg=kz.corpus.labour_code --format humanStatus: ran locally (the three commands above). The namespaced query
returned the two strict producers; the short-name query returned
No matches. Rows: 0.
Validation record
Section titled “Validation record”Show commands, versions and results
| Check | Status | Date |
|---|---|---|
Route probe: /mcp 200, /mcp/islam 200, /mcp/kz 410 Gone | ran (curl, below) | 2026-10-03 |
Feeding-break truth ask; law_ask kind enum (six values) from tools/list | ran via the public MCP endpoint (root route) | 2026-10-03 |
law_search, law_packages, collect; law query producers | ran via MCP (repo server); ran locally | earlier session |
law_rules contract mode | schema and contract builder read; call NOT RUN | 2026-10-04 |
Standalone why_not, positions, deadline, calc; local-versus-public byte comparison | NOT RUN (no local server started; reproduce from the checkout’s MCP configuration) | — |
The 2026-10-03 checks ran at commit
2b894bea80bae78d081202f93f0911615eeeec04 (uncommitted changes in the
tree not covered). Route probe, once per route:
curl -s -o /dev/null -w '%{http_code}\n' https://mcp.arxo.io/mcp \ -H 'content-type: application/json' \ -H 'accept: application/json, text/event-stream' \ -d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}'Previous: The agent contract Next: Pin context, editions and time Related: Choose an integration pattern · Read answers
Documentation for Arxo. Writings — blog.arxo.io.
Anonymous visit counts on stats.arxo.io, no cookies.