michigan-law-mcp
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@michigan-law-mcpWhat is the penalty for retail fraud under MCL 750.356?"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
michigan-law-mcp
An MCP server that gives Claude and other agents access to Michigan statutes and rules from a local SQLite store refreshed by a daily job.
MIT-licensed (see LICENSE). This repository is the code only: no copy of the Michigan Compiled
Laws is included or distributed. If you're standing up your own instance, read NOTICE and
docs/lsb-reply-2026-09-21.md first -- they cover what the Michigan Legislature has said about
automated access, rate limits, and reuse, as of 2026-09-21. That understanding was reached for
this project specifically; running your own instance means keeping to it yourself (a real
contact address in your crawler's user agent, the requested rate limit, the standing NOTICE) and
confirming nothing has changed since. None of this is legal advice.
Status: two independent parsers agree on real law; XML is primary, HTML is the cross-check. The server, schema,
full-text search, version history, change tracking, cross-reference graph, polite crawler and ingest pipeline are
tested (139 tests). Two parsers now read the same chapters -- the Legislature's whole-chapter HTML
(Home/RenderDoc?objectName=mcl-chapNN, chapterdoc.py) and its own machine-readable XML
(documents/mcl/Chapter N.xml, confirmed as the canonical source in docs/lsb-reply-2026-09-21.md; mclxml.py) --
and Chapter 37 (17 acts and E.R.O.s, 142 sections) parses identically from both, field for field (text, catchlines,
history, compiler's notes, division structure, the repealed range), with zero warnings either way. Chapter 2 exercises
the XML source's edge cases: a .new variant section (a law passed but not yet in force, represented as its own
statute entry alongside the real acts), a table embedded inside a paragraph, and two old repealed entries whose
SectRef names an extra citation beyond their own MCLNumber (kept as a range stub, not dropped). Three more real
chapters (760, 333, 750, 43 MB combined) now parse with zero warnings and zero integrity problems too, confirmed with
a full end-to-end ingest (3,777 documents, 22 chapters, one run); see "Real chapters parsed so far" below for what
each one exercised, including Chapter 760.xml's MultiChapter case (one file legitimately spanning 20 chapters).
MclWebSource and MclXmlWebSource fetch chapters through the polite client (the Legislature has permitted automated
access, see below); MclXmlWebSource discovers chapters from the directory listing (manifest.py). mlaw ingest --source mcl-files|mcl-xml-files loads chapter files saved by hand. Fixture text is prefixed [FIXTURE] and is not
real law.
Quick start (fixtures)
python -m venv .venv && . .venv/bin/activate
pip install -e ".[dev]"
export MLAW_DB=data/mlaw.sqlite3
mlaw ingest --source fixture --path fixtures/day1 # day 1
mlaw ingest --source fixture --path fixtures/day2 # day 2: one modified, one added, one removed
mlaw status
pytest
mlaw serve # stdio, for Claude DesktopReal chapter files (no network): save a chapter's HTML from its Download page (the HTML icon) into a folder and run
mlaw ingest --source mcl-files --path ~/Desktop/MCL/chapters # every *.html holding a chapter is loaded
mlaw status # shows the site's "Complete Through PA n of year" stampOr the Legislature's own XML (save Chapter N.xml files, e.g. via the site's "Download Chapter" link or the
directory listing):
mlaw ingest --source mcl-xml-files --path ~/Desktop/MCL # every Chapter *.xml is loadedA run over some chapters only touches those chapters: sections missing from a loaded chapter are marked removed,
everything else in the database is left alone. Live crawl (permitted by the Legislature; set a real
contact in the user agent and MLAW_ROBOTS_ERROR_OK=1, since robots.txt returns an error): MLAW_ROBOTS_ERROR_OK=1 MLAW_USER_AGENT="north-coast-legal-mlaw/0.1 (mike@thenorthcoastlegal.com)" mlaw ingest --source mcl-xml-web (all chapters, discovered from the directory listing, 2 s between requests; --source mcl-web is the HTML equivalent) or --path "37 38" for specific chapters.
Claude Desktop (stdio) config:
{ "mcpServers": { "michigan-law": {
"command": "/path/to/.venv/bin/mlaw", "args": ["serve"],
"env": { "MLAW_DB": "/path/to/data/mlaw.sqlite3" } } } }Remote (team) mode: mlaw serve --transport http --host 127.0.0.1 --port 8000 behind TLS
(deploy/Caddyfile.example), with MLAW_BEARER_TOKEN set. Endpoint /mcp, health check /healthz.
The bearer token is a stopgap; check which auth schemes Claude's custom-connector flow accepts for
remote servers before rollout. Updates: deploy/mlaw-feed-refresh.{service,timer} polls the real
MCLupdate.xml feed hourly and re-fetches live, via MclXmlWebSource, only the chapters it flags;
deploy/mlaw-ingest.{service,timer} runs underneath it as a nightly full-crawl safety net (unrestricted
mcl-xml-web, 04:30 America/Detroit). See "Feed-driven refresh" below.
Related MCP server: michigan-code
What the source site looks like (from saved pages and the full updates feed)
Hierarchy: chapter (or chapter group, e.g.
mcl-chapters-760-777) -> act or E.R.O. (mcl-Act-175-of-1927) -> division (mcl-175-1927-II, description carries its citation range "(762.1...762.16)") -> section (Home/GetObject?objectName=mcl-750-83). Initiated laws also exist.Every page shows a currency banner, "Michigan Compiled Laws Complete Through PA 91 of 2026". That is the site's own freshness stamp;
mclsite.parse_currencyreads it.Every level has a "Download ..." link to
Home/Document?objectName=.... For a section that page offers an HTML rendering (Home/RenderDoc?objectName=...) and a static PDF at a predictable path (documents/mcl/pdf/MCL-750-145P.pdf, upper-case). The chapter-level download page (Home/Document?objectName=mcl-chap37) offers the same pair for a whole chapter (Home/RenderDoc?objectName=mcl-chap37,documents/mcl/pdf/MCL-CHAP37.pdf) and has Previous/Next Document links, so the chapters can be walked without an index. If the chapter-level HTML holds every section with usable structure, the full MCL is a few hundred requests instead of tens of thousands. The RenderDoc HTML for a chapter has not been seen yet; the statute-level download page is still unseen too.Daily Bill Status Report (
Home/DailyBillStatus?dateFrom=…&dateTo=…): bills grouped by stage (Introduced, Passed by Chamber, Enrolled, Adopted; others may exist) with one-line descriptions such as "Amends sec. 9 of 1972 PA 348 (MCL 554.609)".billstatus.pyparses it into rows with the statutes each bill touches. This is pending legislation, not law; the plan is a clearly-labelledpending_legislationtool, never mixed into text.Section pages: catchline heading (
750.145p Caregiver, ...), body paragraphs beginning "Sec. 145p.", then labeled notes ("History:", "Compiler's Notes:"). A repealed section keeps its status in the heading ("Repealed. 2010, Act 96, Imd. Eff. June 22, 2010.") and may have no body. Division pages list their sections with catchlines. Statutes cite each other as "being section 400.11b of the Michigan Compiled Laws", which the cross-reference extractor understands.The footer links the three RSS feeds (bills, meetings, laws) and an Acceptable Use Policy at
/Home/AcceptableUse. Its four guidelines: respect copyright/licence in the data; do not use the Services in a way that "precludes or significantly hinders use by others"; do not violate laws; respect others' privacy. It does not mention automated access either way, so we crawl slowly, identify ourselves, and ask (seedocs/lsb-inquiry.md).Variant nodes. The Legislature confirmed that
.amended,.addedand.newsections display changes made by laws that have passed but are not yet in effect (for example Section 2.13.new, added by PA 7 of 2026, takes effect 91 days after adjournment).mcl-333-16335-amended(saved page) is the same citation with a red notice, "THIS AMENDED SECTION IS EFFECTIVE JANUARY 22, 2028": future text next to the ordinary node that holds today's text. The bracket form (mcl-333-5474c[1]) was not addressed in the reply and stays unconfirmed. Note that.newsections have no ordinary node yet, soget_documentmust not say "not found" for them (to do). We store variants as separate rows, never in the search index, andget_documentreturns them underother_formswith the effective date and a caution. Whether chapter-level RenderDoc files include variants is not yet known (Chapter 37 has none).Tables sit inline inside a section (
<table><tr><td>between paragraphs) and the next subsection can be a loose text node after</table>. An earlier version of the parser silently dropped that subsection; the section parser now visits every node and compares its output with all the text in the section, and a chapter whose sections fail that check aborts the run instead of storing truncated law.Chapter files also contain range lines for wholly repealed acts ("37.1-37.9 Repealed. 1976, Act 453, ..."); these are stored as ranges so
get_document("MCL 37.5")can explain instead of saying "not found".Notes after the History line ("Compiler's Notes", "Admin Rule", "Constitutionality") are kept verbatim; the Constitutionality note (e.g. a provision held unconstitutional) is exactly what a lawyer needs to see.
Act and section histories use a compact grammar ("Am. 1980, Act 506, Imd. Eff. Jan. 22, 1981") that
history.pyparses, including effective dates.
Update feed
mlaw feed --file saved.xml [--record] (or --url ...) parses the "MCL updates" RSS feed and can log its
events to feed_events. Findings from the full 2026-09-02 build (2,410 items, saved as a regression sample):
all one re-load of the Public Health Code (chapter 333): 2,209 section events, 137 heading nodes, 29 act-level
deletions, 27 E.R.O. deletions, plus initiated laws and 26 variant nodes. Items carry only a catchline (no text,
no effective date) and share one pubDate per build, so it is a batch id, not a change date. Volume is not a
measure of legal change. The feed tells the daily job which sections and acts to re-fetch; it never edits stored
text itself. Unknown title shapes are kept as unrecognized. Per the Legislature: items stay in the feed for one day
(a missed day is a gap, so keep a periodic full refresh), "deleted" means only "removed from the website" (not repealed),
and a modified document currently shows up as deleted then added instead of "updated" (they have logged a fix).
The XML directory (primary source)
https://legislature.mi.gov/documents/mcl/ is an IIS directory listing of one Chapter N.xml per chapter (241 files,
480 MB in total on 2026-09-21; largest Chapter 324 at 38 MB, Chapter 333 at 24 MB), plus pdf/, xml/ (2005) and
archive/ (monthly snapshots, one directory per year 2013-2026). manifest.py parses the listing: one request tells
the daily job which chapters exist and which changed (size or time differs from the last parsed copy), and vanished
files are reported, never turned into repeals. There is no Chapter 764.xml to Chapter 777.xml; confirmed against
a fetched copy that Chapter 760.xml (11.7 MB) holds them, via its own <MultiChapter>true</MultiChapter> flag --
one file declared "760" actually spans 760, 761, 762, 763, 764, 765, 766, 767, 767A, 768, 769, 770, 771, 771A, 772,
773, 774, 775, 776, and 777 (672 sections). Listing times have no time zone. Caution: file dates are per chapter and
can be old (the saved Chapter 37.xml is dated 2026-02-06 while the site says it is complete through PA 91 of 2026)
-- the files carry no currency stamp of their own, unlike the HTML rendering's "Complete Through PA ## of ####"
banner, so MclXmlFilesSource/MclXmlWebSource always report currency=None. Rely on the directory listing's own
modified time, or an occasional cross-check against the HTML rendering, for freshness.
mclxml.py parses MCLChapterInfo -> MCLStatuteInfo (one per act/E.R.O./resolution, and, for a variant section, a
whole extra statute entry standing in for the amending act) -> MCLSectionInfo / MCLDivisionInfo, and the embedded
pseudo-XML inside BodyText (Section-Number, Paragraph/Paragraph-Number/P, and an occasional literal <table>
either inside a <P> or, seen only in Chapter 333.xml, as a direct child of <Paragraph> with no <P> at all).
Verified byte-for-byte against the HTML parser on Chapter 37 (see Status above); Chapter 2 and the fully-repealed
Chapter 131 exercise variants, tables, and the SectRef-names-an-extra-citation edge case; Chapter 760 exercises
MultiChapter (one file, many chapters -- each section's own citation decides its stored chapter, not the file's
declared one); Chapter 333 exercises .amended/.added/[1] variants, the direct-<Paragraph> table shape, and
1,883 editor's notes that are bare text with no embedded markup at all. Structured History/HistoryInfo/Legislation
data exists in the XML (effective dates, PA numbers, session type) but is not yet consumed -- current text comes
first, per plan; that data is there for the historical/as_of backfill later.
Real chapters parsed so far
Chapter | Size | What it exercised |
37 (Civil Rights) | 1.2 MB | Baseline cross-check against the HTML parser: byte-for-byte match, 17 statutes, 142 sections. |
2 | small |
|
131 (Municipal Finance Act, fully repealed) | small | A chapter with no current sections at all -- just a repealed range. |
760 (Code of Criminal Procedure) | 11.7 MB |
|
333 (Public Health Code) | 24 MB |
|
750 (Michigan Penal Code) | 8.7 MB | A large, structurally ordinary chapter (896 sections) -- clean confirmation before it's used for cross-reference validation. |
robots.txt and permission
The Legislature replied on 2026-09-21 (docs/lsb-reply-2026-09-21.md): automated access is permitted, requests should
be rate-limited to at most 1 per second (may change), storing and serving a copy is fine with no wording requirements, and
they do not believe they serve a robots.txt (the 502 seen by hand is a gateway error). The crawler still fails closed by
default; set MLAW_ROBOTS_ERROR_OK=1 to proceed when robots.txt errors. A robots.txt that does load is still honored.
Requests are spaced 2 s apart (MLAW_MIN_INTERVAL, never below 2). Keep the reply on file; counsel should confirm the wording
if the tool is offered beyond your own office.
Confirmed live, 2026-09-21 (with curl, independent of this project's own code): robots.txt doesn't
actually return that 502 (or anything else) to an automated request -- it just hangs, 0 bytes, indefinitely.
So PoliteClient gives that one probe its own short robots_timeout (15s default, separate from the main
request timeout) rather than waiting out the full timeout to learn the same thing every single run. Both
deploy/*.service units set MLAW_ROBOTS_ERROR_OK=1; if you're running mlaw by hand, you need it too.
TLS
A cloud fetcher, wget on a Mac, and this session's own WebFetch tool have all independently rejected
www.legislature.mi.gov's certificate (issuer DigiCert G2 TLS RSA SHA256 2020 CA1: "unable to locally verify
the issuer"), which suggests the server does not send its intermediate certificate. The crawler never
disables verification. Set MLAW_CA_BUNDLE (certifi bundle plus the intermediate) or MLAW_USE_SYSTEM_TRUST=1.
Confirm the cause with:
openssl s_client -connect www.legislature.mi.gov:443 -showcerts </dev/null | grep -E '^ *(s|i):'
(one certificate listed means the chain is incomplete).
Confirmed live, 2026-09-21: MLAW_USE_SYSTEM_TRUST=1 fixes this for a real Python httpx client, not
just non-Python fetchers. truststore is now a base dependency (pyproject.toml), not an optional extra to
remember.
Confirmed live, 2026-09-22, on Ubuntu (NUC deployment): MLAW_USE_SYSTEM_TRUST=1 does NOT fix this on
Linux, even though it fixed it on macOS. truststore delegates to the OS's own certificate verification --
on macOS that's Apple's Security framework, which opportunistically fetches a missing intermediate via the
certificate's AIA (Authority Information Access) URL and silently completes the chain. Linux's OpenSSL
(what truststore sits on on Linux) does not do this AIA fetching, so the exact same incomplete-chain
problem that macOS papers over is fully exposed there: SSL: CERTIFICATE_VERIFY_FAILED: unable to get local issuer certificate. On Linux, use MLAW_CA_BUNDLE instead (it's checked first regardless of
MLAW_USE_SYSTEM_TRUST) -- build it once from the missing intermediate's own AIA URL:
# 1. Find the AIA "CA Issuers" URL for the cert the site actually presents:
openssl s_client -connect www.legislature.mi.gov:443 -showcerts </dev/null 2>/dev/null \
| openssl x509 -noout -text | grep -A1 "CA Issuers"
# 2026-09-22: http://cacerts.digicert.com/DigiCertGlobalG2TLSRSASHA2562020CA1-1.crt (DER-encoded)
# 2. Download it and convert to PEM:
curl -s http://cacerts.digicert.com/DigiCertGlobalG2TLSRSASHA2562020CA1-1.crt -o /tmp/intermediate.der
openssl x509 -inform DER -in /tmp/intermediate.der -outform PEM -out /tmp/intermediate.pem
# 3. Append it to certifi's bundle to make a combined trust bundle:
CERTIFI_BUNDLE=$(/opt/michigan-law-mcp/.venv/bin/python3 -c "import certifi; print(certifi.where())")
sudo mkdir -p /etc/mlaw
sudo bash -c "cat '$CERTIFI_BUNDLE' /tmp/intermediate.pem > /etc/mlaw/ca-bundle.pem"
# 4. Point the crawler at it:
export MLAW_CA_BUNDLE=/etc/mlaw/ca-bundle.pemDigiCert's intermediate doesn't rotate often, but if the server's issuer ever changes, redo step 1 to find
the new AIA URL. Both deploy/*.service units set MLAW_CA_BUNDLE=/etc/mlaw/ca-bundle.pem for this reason
-- a Linux deploy must build that file once (steps above) before the first run; MLAW_USE_SYSTEM_TRUST=1 is
left in place too as a harmless no-op fallback (macOS deployments still get real benefit from it).
Tools
Tool | Purpose |
| Full text by citation ( |
| Full-text (stemmed, phrases, AND/OR/NOT) with source / chapter filters |
| Sections in a chapter, citation order, paged |
| What a provision cites, and what cites it |
| Added / modified / removed since a date |
| Public Act metadata and affected citations |
| Freshness per source |
How it stays trustworthy
Every response carries provenance (source URL, last confirmed, first observed) and an unofficial-copy notice. Confirm the notice wording against the source site's own disclaimer.
Versions are kept whenever text changes.
as_ofanswers "what had we observed by this date", not "what was legally effective"; history begins at the first ingest.A complete crawl that sees under 90% of previously known sections aborts and changes nothing, so a half-loaded site cannot look like a mass repeal. The daily job ingests into a copy of the database and swaps it in atomically; failures are recorded and shown by
data_status.The crawler honors robots.txt (and fails closed if it cannot read it), identifies itself, rate-limits, and uses conditional requests. Set
MLAW_USER_AGENTto include a real contact address.
To do, in order
Done, and confirmed live. Feed-driven refresh has a live path:
refresh.refresh_from_web()andmlaw feed --url ... --refresh-webpoll the realMCLupdate.xmlURL and fetch flagged chapters live viaMclXmlWebSource, exactly mirroring the offline--refresh-frompath through the same atomic copy-and-swap. Cadence decided and deployed: feed poll hourly, full crawl nightly (deploy/mlaw-feed-refresh.timer,deploy/mlaw-ingest.timer). Proven with mocked HTTP (tests/test_refresh.py) and, 2026-09-21, against the real live site end to end (real feed parsed and resolved correctly, a real chapter fetched and ingested live) -- see "Live-wiring verification" below for what was confirmed and the two real bugs (plus one deploy-config gap) that verification caught and fixed. Read the equivalents for courts.michigan.gov.Done. Cross-reference validation against 6 real chapters (750, 760-777, 333, 37, 2, 131 -- 3,994 documents, 2,383 MCL xrefs, 1,162 Public Act xrefs). Found and fixed two bugs in
citations.py: a citation-list scan would sweep up an unrelated decimal figure (e.g. "MCL 257.625, 0.02 grams...") as a bogus "MCL 0.02" citation, since chapter "0" was never rejected; and Public Act citations written as "Act No. 267 of the Public Acts of 1976" or the bare "Act 368 of 1978" (the common form in real body text, alongside "1978 PA 368") weren't recognized at all. Both fixed with real-corpus regression tests. Zero false negatives found for MCL/MCR/MRE literal citations. Known, accepted limitation (not fixed, too rare/ambiguous to be worth it): a "being section 17766a of the Michigan Compiled Laws" clause with no chapter-number prefix (1 occurrence in the corpus). Follow-up done: theranges-table gap this validation surfaced (item 3 below) is now closed too.Done.
ranges-table coverage gap closed. The compilers sometimes declare a span of sections repealed/expired in running prose rather than the act/chapter-level stub line_stub_rangesalready caught from a LongTitle ("37.1-37.9 Repealed. 1976, Act 453..."): a whole section can BE a narrative repeal notice for a different span (e.g. MCL 333.13607: "Sections 13601 to 13606 ..., being sections 333.13601 to 333.13606 of the Michigan Compiled Laws, are repealed effective December 31, 1993"), or a Compiler's Note on some other, often unrelated, live section can say "Former MCL 750.85, which pertained to ..., was repealed by Act 266 of 1974" or "Former MCL 333.20701-333.20773 Expired...". Newcitations.extract_narrative_ranges()recognizes both shapes (39 real hits across the 6-chapter corpus: 2 in-body, 37 Compiler's Notes -- 35 single citations, 2 ranges) and bothchapterdoc.pyandmclxml.pynow call it per section, feeding the samerangestable the act-level stubs already do.service.get_cross_references()now also checksrangesfor an unresolved MCL reference and, when found, attaches aknown_repealed_rangefield instead of a bare"in_database": false-- so an agent asking why a citation didn't resolve gets "the compilers themselves say this was repealed" rather than an unexplained miss. The two confirmed typos in the Legislature's own XML (a citation to a chapter/section number that never existed) still correctly show noknown_repealed_rangeand remain genuinely unresolved, as they should -- this doesn't paper over real errors, only recognizes real, compiler-stated repeals.Deeper structural verification of the
.amended/.added/[1]variants confirmed in Chapter 333.xml, the way.newwas verified against Chapter 2's state duck example.Constitution, then court rules and Rules of Evidence (PDF), then Public Acts.
Historical/
as_ofbackfill from the monthly archives (2013-2026) and the structuredHistory/HistoryInfodata already sitting in the chapter XML, once current-text ingest has run cleanly for a while.Hosting, auth, and rollout to the team.
Feed-driven refresh
MCLupdate.xml (the Legislature's own feed, rebuilt every 5 minutes per its channel description) reports
database events, not law changes -- see feed.py's module docstring. feed.resolve_refresh_chapters()
turns a batch of new events into the actual MCL chapters that need re-fetching: a section-level event names
its own chapter directly; an act-level or heading event is resolved by looking up which chapter(s) that
act's documents (or, for a fully repealed/expired act, its ranges stub -- both are checked, a real gap
the first version had until it was tested against real data, see Status) currently occupy. Anything that
resolves to nothing is reported as unmapped_acts rather than guessed at. refresh.py ties this to an
actual re-ingest: MclXmlFilesSource gained a chapters filter so a refresh can point at one directory
holding every chapter ever saved and only read the ones flagged (a MultiChapter file like Chapter 760.xml
is pulled in whole if any one of its chapters is wanted, since there's no cheaper fetch unit). From the CLI,
mlaw feed --file/--url ... --refresh-from DIR parses the feed, records new events, resolves the plan, and
-- if it names any chapters -- re-ingests just those, through the same atomic copy-the-live-DB-then-swap-it-in
mlaw ingest uses. Proven against the real 2,410-item feed sample and the real Chapter 333.xml: resolves to
exactly ["333"] with zero unmapped acts, and a repeat poll of the same build is a true no-op.
Live wiring (2026-09-21): the same shape now exists against the live site instead of a saved directory --
refresh.refresh_from_web() and mlaw feed --url ... --refresh-web build (or reuse, when --url was also
used to fetch the feed itself) one PoliteClient and hand it to MclXmlWebSource with exactly the chapters
the plan named. MclXmlWebSource's own former known limitation (a MultiChapter chapter requested by an
explicit chapter list, e.g. the feed flagging "767A") is fixed too: _KNOWN_MULTI_CHAPTER_FILES resolves the
request to the real file (Chapter 760.xml) and declares the file's whole real coverage up front, so this no
longer fails even when the feed happens to flag a chapter living inside that file. Covered by mocked-HTTP
tests (tests/test_mclxml.py, tests/test_refresh.py); see "Live-wiring verification" just below for what
running this against the real site still needs.
Live-wiring verification
Confirmed live end to end, 2026-09-21, by Mike running it for real (from a session with no real internet access of its own, everything up to this point had only been proven with mocked HTTP or a browser -- see below for what that gap looked like and how it closed):
MLAW_USE_SYSTEM_TRUST=1(thetruststorepackage, now a base dependency) does fix legislature.mi.gov's incomplete certificate chain for a real Pythonhttpxclient, not justcurl/wget.mlaw feed --url https://legislature.mi.gov/documents/publications/RssFeeds/MCLupdate.xml --recordparses the real live feed correctly and resolves it to the right chapter (refresh_chapters: ["333"]), matching the saved 2026-09-02 sample -- the feed hadn't actually rebuilt with new content since then, which is itself a useful data point (it rebuilds on real database changes, not on a fixed clock, despite what its own channel description implies).mlaw ingest --source mcl-xml-web --path "333"fetched the real, liveChapter 333.xml(~24 MB) and ingested it cleanly: 2,209 new documents, matching the live feed's own count for that chapter exactly.mlaw feed --url ... --refresh-webcorrectly reused one client for the feed fetch and the (in this case skipped, since no new events) chapter refresh, and correctly reported "nothing to refresh" rather than re-fetching a build already recorded -- the offline idempotency guarantee holds against the live feed too.
Two real bugs turned up along the way, both fixed:
robots.txtdoesn't error, it hangs -- confirmed independently withcurl(0 bytes, no response, for as long as you'll wait), not just this client.PoliteClientnow gives that one probe its own short timeout (robots_timeout, 15s default) instead of burning the full request timeout finding this out on every run against a new host -- see "robots.txt and permission" below.mlaw feed --url ...silently ignoredMLAW_ROBOTS_ERROR_OK. The CLI built its ownPoliteClientfor fetching the feed URL, separately from the one a source builds, and only the latter ever read that env var -- so setting it had no effect on the feed fetch itself, which then failed closed exactly as if it had never been passed. Fixed (_client_from_env()incli.py);mlaw feed --url ..., with or without--refresh-web, now honors it correctly.
A third issue was caught by inspection once the first one made it obvious to look: neither
deploy/mlaw-ingest.service nor deploy/mlaw-feed-refresh.service set MLAW_ROBOTS_ERROR_OK or
MLAW_USE_SYSTEM_TRUST at all, and neither set a TimeoutStartSec -- so, unfixed, both scheduled jobs would
have failed closed on robots.txt in production, and even once that was fixed, systemd's 90-second default
would have killed the nightly full crawl (8+ minutes minimum) and, before the 15s robots_timeout fix, the
hourly job too. Both unit files now set both env vars and a generous TimeoutStartSec.
If you haven't yet: systemctl daemon-reload and re-enable both timers to pick up these unit file changes
before relying on the schedule.
Available Tools
7 toolsdata_statusA
Report, per source, how many provisions are stored, when the last successful update and last full crawl ran, and the source's own currency stamp (e.g. "Complete Through PA 91 of 2026"). Call this when the user asks whether the data is current.
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
No annotations are supplied, so the description carries the full burden. It usefully discloses the shape of what comes back (counts per source, last update/crawl timestamps, currency stamp), which implies a read-only operation, but it never explicitly states read-only behavior, permission requirements, or whether the numbers can be stale/incomplete. Adequate but leaves behavioral gaps.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Two sentences, with the reporting content front-loaded and the usage trigger in the second. Every clause carries information; it is slightly dense but not padded.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
An output schema exists, so return values need not be explained, yet the description still summarizes them at a useful level. Combined with the explicit usage trigger, an agent has enough to decide and invoke correctly; only explicit read-only/auth framing is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The tool takes zero parameters, so there is nothing for the description to disambiguate; baseline 4 applies. The description correctly adds no spurious parameter guidance.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb ('Report, per source') and enumerates exactly what is reported: provision counts, last successful update, last full crawl, and the source's currency stamp. It is clearly distinguishable from lookup/search/get_document siblings by being a status/metrics read rather than content retrieval, though it does not name any sibling explicitly.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Gives an explicit trigger: 'Call this when the user asks whether the data is current.' That routes the agent well. It stops short of stating when NOT to use it or naming a concrete alternative among siblings, so it is clear context without exclusions.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_chapter_outlineA
List the sections in a chapter (e.g. chapter "750" for the Michigan Penal Code) in citation order, with catchlines. Paged with limit/offset. The Michigan Constitution is filed as chapter "1" but is NOT an MCL chapter -- pass source="const" to list it (chapter "1" with the default source="mcl" returns nothing, since there is no ordinary MCL chapter 1).
| Name | Required | Description | Default |
|---|---|---|---|
| limit | No | ||
| offset | No | ||
| source | No | mcl | |
| chapter | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations the description carries the full burden, and it does disclose ordering (citation order), pagination (limit/offset), and a non-obvious trap (empty result for chapter 1 under the default source). It omits error behavior for a nonexistent chapter and any auth/permission notes.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loaded with the core action, followed by the source caveat. Every sentence adds information; the parenthetical example and the chapter-1 warning earn their space. Slightly dense but no waste.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
An output schema exists, so return values need not be described. The description covers the confusing parts (source selection, empty-result trap, paging) adequately for this read tool, though it leaves the exact meaning of limit/offset to the schema.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 0% for 4 params, so the description must compensate. It explains source semantics (mcl vs const) and the chapter format well, but only says 'paged with limit/offset' without clarifying paging behavior, bounds, or defaults beyond what the schema itself already declares.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ('List the sections in a chapter... in citation order, with catchlines') and gives a concrete example (chapter "750" for the Michigan Penal Code). It does not name or contrast any sibling tool, but an agent can still tell what this returns.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Gives a real usage rule: pass source="const" for the Michigan Constitution and warns that the default source="mcl" returns nothing for chapter "1". No guidance on when to prefer a sibling (e.g. get_document or search) instead, so it isn't a full 5.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_cross_referencesB
Follow citations. direction="cites": provisions this one refers to. direction="cited_by": provisions whose text refers to this one. Extracted automatically from explicit citations; not exhaustive.
| Name | Required | Description | Default |
|---|---|---|---|
| limit | No | ||
| citation | Yes | ||
| direction | No | cites |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
No annotations are provided, so the description carries the full burden. It usefully discloses provenance and a quality caveat ('Extracted automatically from explicit citations; not exhaustive'), which tells the agent results may be incomplete. However it omits pagination/limit behavior, performance characteristics, and the fact that the traversal is one-directional per call beyond the direction enum.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Four short clauses, front-loaded with the core action and then the direction semantics. Slightly telegraphic but no wasted sentences; only the trailing provenance note is truly optional.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
An output schema exists so return values need not be explained, and the read-only nature is self-evident. The main gap is the required 'citation' parameter, whose expected format is undocumented in both the schema and the description for a 3-parameter tool.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 0%, so the description must compensate. It does add real meaning for the direction enum (defining 'cites' vs 'cited_by'), which the schema lists only as bare values, but it says nothing about the required 'citation' string format or the 'limit' parameter's role.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description opens with a specific verb+resource ('Follow citations') and immediately enumerates the two traversal directions, so the agent understands this is a citation-graph walker rather than a text search. It does not explicitly name or distinguish itself from any sibling (search, get_document), but the resource is unambiguous.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Usage is implied by explaining which direction yields which set of provisions, which is enough to pick the right direction value, but there is no statement of when to prefer this tool over search or lookup_public_act, nor any prerequisites or exclusions.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_documentA
Get the full text of one Michigan legal provision by citation.
citation: "MCL 750.83", "MCR 6.110", "MRE 404", or "Const 1963, art 1, § 17". Subdivisions like (1)(a) are ignored; the whole section is returned. as_of: optional YYYY-MM-DD. Returns the version this server had observed by that date. This is observation history, not a legal effective date, and only goes back to this server's first ingest. Returns text, catchline, history note, compiler's notes (including Admin Rule and Constitutionality notes when the source has them), status, and provenance (source URL, last confirmed, and the source's own "complete through PA n of year" stamp). If the source lists another form of the same citation (for example an amended version that takes effect on a future date) it appears under other_forms with its effective date; that text is NOT the law currently in force unless its notice says so. A citation that falls inside a repealed range gets an explanatory error naming the range.
| Name | Required | Description | Default |
|---|---|---|---|
| as_of | No | ||
| citation | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
No annotations are provided, so the description carries the full burden of behavioral disclosure, and it does so richly: what is returned (text, catchline, history note, compiler's notes, status, provenance), the meaning of as_of (observation history, not legal effective date, bounded by first ingest), how other_forms work (not the law in force unless its notice says so), and the repealed-range error behavior. This goes well beyond what a mutation/read annotation would say.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Dense but organized, with each paragraph covering a distinct concern (citation format, as_of semantics, return shape, other_forms caveat, error case). The as_of and other_forms passages are lengthy but earn their place given the ambiguity they resolve; slightly overlong only in the compiler's-notes enumeration.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given an output schema exists, the description need not explain return values, yet it usefully names the fields and clarifies the tricky as_of and other_forms semantics that the output schema alone would not disambiguate. For a 2-param, read-only lookup tool with no annotations, it is complete enough to call correctly.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 0%, so the description must compensate, and it does: it defines citation format with examples, states that subdivisions are ignored and the whole section is returned, and specifies as_of as optional YYYY-MM-DD with its exact semantics. This is meaning far beyond the bare schema.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb+resource ('Get the full text of one Michigan legal provision by citation') with a scope qualifier ('one... full text'). The citation-format examples (MCL 750.83, MCR 6.110, MRE 404, Const 1963...) and the sibling tool names (lookup_public_act, search) make it distinguishable without opening the schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Clear implied usage: retrieve the full text of a specific provision by citation. It explains subdivision handling ('Subdivisions like (1)(a) are ignored') and notes that repealed-range citations produce an explanatory error, giving the agent realistic input expectations. It does not explicitly route the agent to siblings like search or get_cross_references, so no full 5.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
list_changesA
List provisions added, modified, removed or restored since a date (YYYY-MM-DD), newest first, as detected by this server's ingest runs.
| Name | Required | Description | Default |
|---|---|---|---|
| limit | No | ||
| since | Yes | ||
| source | No | ||
| change_type | No |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
No annotations are provided, so the description carries the full burden. It usefully discloses ordering (newest first), the change taxonomy, and that results come from this server's ingest runs, but says nothing about limit/pagination behavior, result caps, or whether it is a pure read.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
A single dense sentence with zero filler; the date format, change taxonomy, and ordering are all front-loaded before the caveat about ingest-run detection.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
An output schema exists, so return-value detail is unnecessary, and the description covers filtering semantics and ordering well. The remaining gap is the unexplained limit/source parameters for a tool with 0% schema description coverage.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 0%, so the description must compensate, and it partly does: it gives the YYYY-MM-DD format for 'since' and enumerates the change types matching the change_type enum. It leaves 'limit' and 'source' entirely unexplained, so the coverage gap is only partially filled.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb (List) and resource (provisions added/modified/removed/restored), plus ordering and detection source. It's clear what the tool returns, but it never contrasts itself with siblings like search or data_status, which an agent would need to disambiguate a change-feed from a general query.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Usage is only implied through the 'since a date' framing; there is no explicit when-to-use or when-not-to-use guidance, nor any mention of search/data_status as alternatives. An agent can infer this is a what-changed-since query, but nothing confirms the boundary.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
lookup_public_actA
Look up a Public Act (e.g. year 1931, number 328): title, effective date, and the citations it affects, when known.
| Name | Required | Description | Default |
|---|---|---|---|
| year | Yes | ||
| number | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the full behavioral burden. It does add genuinely useful disclosure: the return contents and the 'when known' hedge, which warns that effective date / citations may be absent. It says nothing about permissions, determinism, or whether a missing act yields an error vs. an empty result, so meaningful gaps remain.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
A single sentence, front-loaded with the verb and resource, with the example folded in parenthetically and no wasted words. Every clause earns its place by either naming the resource or describing the payload.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a simple two-param lookup with an output schema (so return shape needn't be restated) this is nearly complete: purpose, inputs, and expected data gaps are all covered. The missing pieces are valid-parameter ranges and a routing rule to the 'search' sibling, which would make it fully self-sufficient.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 0%, so the description must carry the load. The example 'year 1931, number 328' usefully clarifies that both parameters are integers identifying a public act and its year. It does not define valid ranges, accepted formats, or edge cases for either parameter, so it only partially compensates for the coverage gap.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb ('Look up') and a specific resource ('a Public Act'), and even enumerates what the caller gets back (title, effective date, citations). It is clearly distinguishable from generic siblings like 'search' and 'get_document' by its year+number identification model. It stops short of a 5 only because it never explicitly contrasts itself with those siblings.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The inline example ('year 1931, number 328') implies the usage pattern: call this when you already know a public act's year and number. However, there is no explicit when-to-use, no when-not-to-use, and no pointer to the 'search' sibling for the case where only a topical query is available. Usage is inferable but not stated.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
searchA
Full-text search across stored provisions (stemmed keywords; "quoted phrases"; uppercase AND / OR / NOT). Returns citations, catchlines and snippets; call get_document for full text. Optionally restrict by source or MCL chapter number (e.g. "750").
| Name | Required | Description | Default |
|---|---|---|---|
| limit | No | ||
| query | Yes | ||
| source | No | ||
| chapter | No | ||
| include_repealed | No |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the full burden and does well: it discloses the query grammar (stemming, quoted phrases, uppercase boolean operators), the shape of returned data, and the narrowing options. It does not address permissions, result caps, or pagination, which are minor gaps for a read-only search tool.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Three dense clauses with zero filler: what it searches, how results are returned and where to go for full text, and how to narrow. Everything is front-loaded and each sentence earns its place.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
An output schema exists, so return values need not be explained, yet the description helpfully summarizes them anyway. It covers query construction and primary filters but omits the semantics of the limit and include_repealed parameters, leaving a small hole for an otherwise complete definition.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 0% across five parameters, so the description must compensate. It explains query syntax, source restriction, and gives a concrete chapter example ('750'), but the limit and include_repealed parameters are never mentioned, leaving two of five parameters semantically undocumented.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ('Full-text search across stored provisions') and describes exactly what result type it produces (citations, catchlines, snippets). It also explicitly distinguishes itself from the sibling get_document by noting that full text requires that tool instead.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Clearly signals the context: use this for searching, call get_document for full text, and it explains how to narrow results via source or MCL chapter. It lacks explicit when-not guidance (e.g., when another sibling like get_cross_references is more appropriate), so it falls just short of the top band.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
7 tool updates
v0.1.0- First observed
data_status - First observed
get_chapter_outline - First observed
get_cross_references - First observed
get_document - First observed
list_changes - First observed
lookup_public_act - First observed
search
TDQS
Scored across 7 tools
Each tool targets a clearly distinct operation: public act metadata, provision full text by citation, full-text search, chapter listing, citation graph traversal, change tracking, and data health. There is no overlap between lookup_public_act and get_document, and search is clearly scoped to stored provisions. An agent can reliably select the right tool for each task.
Most names follow a predictable verb_noun pattern (lookup_public_act, get_document, get_chapter_outline, get_cross_references, list_changes). The bare verb 'search' and the noun phrase 'data_status' are minor deviations but remain readable and unambiguous in context.
Seven tools are well-scoped for a legal reference server: retrieval, search, navigation, cross-referencing, change tracking, and status reporting. Each tool earns its place with no redundant capabilities. The count sits comfortably in the typical 3-15 range.
The surface covers core read-only legal research workflows: lookup by citation, full-text retrieval, search, chapter outlines, citation following, change history, and data currency. Minor gaps exist—no direct way to list public acts by year or fetch a public act's full text—but agents can work around these using search and citation references.
Maintenance
Related MCP Connectors
Search US and Indian statutes, regulations, constitutions, and court rules.
Resolve, search and verify legal citations against the official sources, with provenance.
Judged, citation-checked policy corpus over MCP. Keyless public reads; API key for AI tools.
Search U.S. case law, fetch opinions, and ask matter-aware legal questions over your documents.
Related MCP Servers
- AlicenseAqualityBmaintenanceServes 484 Claude Code skills and 17 global rules as MCP tools, enabling skill and rule retrieval, keyword search, and project-level rule discovery for any Claude client.115 npm2MIT
- AlicenseNot gradedqualityBmaintenanceEnables AI agents to retrieve Michigan Compiled Laws statutes by citation from the official state source without requiring an API key.7 npmMIT
- AlicenseNot gradedqualityBmaintenanceEnables Claude to query a locally imported collection of PDF, TXT, and MD files via SQLite FTS5 full-text search (including Chinese), and to browse notebooks, source files, and individual passages. Every search result carries traceable citations such as filename, page number, line number, or timestamp.MIT
- AlicenseNot gradedqualityBmaintenanceEnables AI agents to pull the full text of any state statute by citation, and to search official section catchlines by topic or keyword to find matching citations. Keyless, and handles citation suffixes such as "12.1-16-01" or "47-16-13.1".385 npmMIT