Use the MCP server from an agent
Outcome. An MCP client launches mailwoman-mcp as a subprocess and can call its tools, and you know
which of them work with no data on disk and which need a gazetteergazetteerA geographical index that maps place names and postcodes to real-world coordinates. Mailwoman uses a custom-built Who's On First (WOF) SQLite database as its gazetteer — the 'atlas' half of the grammar/atlas architecture..
@mailwoman/mcp is not a service. It speaks JSON-RPC over stdin and stdout, so the client starts it,
talks to it, and stops it. There is no port to open and nothing to deploy.
Prerequisites
- Node.js ≥ 24.18.0 and an MCP client. Claude Code is the one configured below; the wiring is the same for any client that launches a stdio server.
- A candidate gazetteergazetteerA geographical index that maps place names and postcodes to real-world coordinates. Mailwoman uses a custom-built Who's On First (WOF) SQLite database as its gazetteer — the 'atlas' half of the grammar/atlas architecture. under the data root you configure, if you want the four modelneural classifierThe machine learning model at the core of Mailwoman's parser — a transformer encoder (~30M parameters) trained from scratch to do BIO token classification over addresses. It learns the 'grammar' of address formats; the gazetteer supplies the 'atlas.'-backed tools. Your first ten minutes pulls one.
@mailwoman/mcp8.7.0 or later. Earlier releases did not declare the weightsparameterA single learned number inside a model — one weight or bias. Mailwoman's encoder has roughly 30 million of them; training is the search for good values. package, so a standalone install could not load the modelneural classifierThe machine learning model at the core of Mailwoman's parser — a transformer encoder (~30M parameters) trained from scratch to do BIO token classification over addresses. It learns the 'grammar' of address formats; the gazetteer supplies the 'atlas.'.
1. Install it
npm install -g @mailwoman/mcp
The package brings its own model weightsmodel weightsThe learned parameters of the neural classifier, shipped as ONNX files in the @mailwoman/neural-weights-* packages. Weights are locale-specific bundles that include the model, tokenizer, and a model-card.json metadata file. (39.4 MB) and the resolverresolverThe component that converts parsed address components (locality, region, postcode) into coordinates by looking them up in the gazetteer. The resolver ranks candidates by name match, population, and proximity, and returns the best-matching place with its centroid or polygon.. Nothing else is needed to start it.
2. Wire it into a client
Claude Code reads .mcp.json in the project root:
{
"mcpServers": {
"mailwoman": {
"command": "mailwoman-mcp",
"env": {
"MAILWOMAN_DATA_ROOT": "/path/to/mailwoman-data"
}
}
}
}
One variable is enough. The server finds the gazetteergazetteerA geographical index that maps place names and postcodes to real-world coordinates. Mailwoman uses a custom-built Who's On First (WOF) SQLite database as its gazetteer — the 'atlas' half of the grammar/atlas architecture. at <data root>/wof/candidate.db, the path
mailwoman data pull candidate writes to, so pointing at the data root wires up all four modelneural classifierThe machine learning model at the core of Mailwoman's parser — a transformer encoder (~30M parameters) trained from scratch to do BIO token classification over addresses. It learns the 'grammar' of address formats; the gazetteer supplies the 'atlas.'-backed
tools. Add "MAILWOMAN_CANDIDATE_DB" beside it only for a gazetteergazetteerA geographical index that maps place names and postcodes to real-world coordinates. Mailwoman uses a custom-built Who's On First (WOF) SQLite database as its gazetteer — the 'atlas' half of the grammar/atlas architecture. that lives outside that root.
This is a change. Through 8.6.0 this server read the variable and nothing else, as most entry points
did, so a config with only the data root in it left the modelneural classifierThe machine learning model at the core of Mailwoman's parser — a transformer encoder (~30M parameters) trained from scratch to do BIO token classification over addresses. It learns the 'grammar' of address formats; the gazetteer supplies the 'atlas.'-backed tools without a resolverresolverThe component that converts parsed address components (locality, region, postcode) into coordinates by looking them up in the gazetteer. The resolver ranks candidates by name match, population, and proximity, and returns the best-matching place with its centroid or polygon.. Only the
Nominatim and Photon drop-ins reached the convention path, and they did it by hand. 8.7.0 moved that
fallback into the shared resolverresolverThe component that converts parsed address components (locality, region, postcode) into coordinates by looking them up in the gazetteer. The resolver ranks candidates by name match, population, and proximity, and returns the best-matching place with its centroid or polygon., so the MCP server inherited it along with everything else.
Add "args": ["--poi-db", "/path/to/poi.db"] if you have the POIpoint of interest (POI). A named place that is not strictly an address — landmark, transit stop, venue, amenity, or franchise. Mailwoman tags these as venue and resolves them through the gazetteer. layerlayerOne transformer block — attention plus a feed-forward network, with normalization and residual connections — applied to every position. Stacking layers lets the model build up richer representations; Mailwoman's encoder has 6.. Without it, mailwoman_poi_search
still extracts the query's intent — the subject and the spatial anchor — and returns that rather than
executing a lookup.
3. Confirm the round trip
You do not need an agent to test this. The transport is newline-delimited JSON-RPC, so 40 lines of Node is a complete client:
import { spawn } from "node:child_process"
const child = spawn(process.argv[2], process.argv.slice(3), { stdio: ["pipe", "pipe", "inherit"] })
const pending = new Map()
let buffer = ""
let nextID = 0
child.stdout.on("data", (chunk) => {
buffer += chunk
for (let i; (i = buffer.indexOf("\n")) >= 0; buffer = buffer.slice(i + 1)) {
const message = JSON.parse(buffer.slice(0, i))
pending.get(message.id)?.(message.result)
pending.delete(message.id)
}
})
const send = (method, params) =>
new Promise((resolve) => {
const id = ++nextID
pending.set(id, resolve)
child.stdin.write(JSON.stringify({ jsonrpc: "2.0", id, method, params }) + "\n")
})
await send("initialize", {
protocolVersion: "2024-11-05",
capabilities: {},
clientInfo: { name: "probe", version: "0" },
})
child.stdin.write(JSON.stringify({ jsonrpc: "2.0", method: "notifications/initialized" }) + "\n")
const { tools } = await send("tools/list", {})
console.log(tools.map((t) => t.name).join("\n"))
const call = await send("tools/call", {
name: "mailwoman_geocode",
arguments: { text: "1600 Pennsylvania Ave NW, Washington, DC 20500" },
})
console.log(call.isError ? `ERROR: ${call.content[0].text}` : call.content[0].text)
child.kill()
MAILWOMAN_DATA_ROOT=/path/to/mailwoman-data node probe.mjs mailwoman-mcp
mailwoman_parse
mailwoman_geocode
mailwoman_poi_search
mailwoman_overpass_export
mailwoman_layer_manifest
mailwoman_bdc_filing_landscape
mailwoman_plausibility_check
mailwoman_filer_lookup
mailwoman_filer_family
[resolver] candidate-table backend (demo-parity, population-first): /path/to/mailwoman-data/wof/candidate.db
{
"input": "1600 Pennsylvania Ave NW, Washington, DC 20500",
"lat": 38.904831,
"lon": -77.016216,
"resolution_tier": "admin",
"uncertainty_m": null,
"locality": "Washington",
"region": "DC",
"postcode": "20500",
"house_number": "1600",
"street": "Pennsylvania Ave NW",
"venue": null,
"dependent_locality": null,
"countryCode": "US",
"hierarchy": [
{
"tag": "locality",
"value": "Washington",
"name": "Washington",
"lat": 38.904831,
"lon": -77.016216,
"placeID": "wof:85931779"
},
{
"tag": "region",
"value": "DC",
"name": "District of Columbia",
"lat": 38.904772,
"lon": -77.016289,
"placeID": "wof:85688741"
}
],
"candidates": [
{
"name": "Washington",
"tag": "locality",
"lat": 38.904831,
"lon": -77.016216,
"countryCode": "US",
"placeID": "wof:85931779"
}
]
}
That transcript is from a standalone npm install @mailwoman/mcp in an empty directory, not from this
repository, so it is what a stranger gets. The [resolver] line arrives on stderr — the client's log,
not the protocol stream. The tier is admin because that data root holds only the candidate gazetteergazetteerA geographical index that maps place names and postcodes to real-world coordinates. Mailwoman uses a custom-built Who's On First (WOF) SQLite database as its gazetteer — the 'atlas' half of the grammar/atlas architecture.;
with per-state shardsshardA partial output file of the corpus build, written in Parquet format. The training pipeline streams shards row by row. mounted the same call reaches address_point.
4. Know which tools need what
Nine tools, and they fall into three groups by what has to be on disk.
| Tool | Needs the modelneural classifierThe machine learning model at the core of Mailwoman's parser — a transformer encoder (~30M parameters) trained from scratch to do BIO token classification over addresses. It learns the 'grammar' of address formats; the gazetteer supplies the 'atlas.' and a gazetteergazetteerA geographical index that maps place names and postcodes to real-world coordinates. Mailwoman uses a custom-built Who's On First (WOF) SQLite database as its gazetteer — the 'atlas' half of the grammar/atlas architecture. |
|---|---|
mailwoman_parse | Yes |
mailwoman_geocode | Yes |
mailwoman_poi_search | Yes |
mailwoman_overpass_export | Yes |
mailwoman_layer_manifest | No — reads a layerlayerOne transformer block — attention plus a feed-forward network, with normalization and residual connections — applied to every position. Stacking layers lets the model build up richer representations; Mailwoman's encoder has 6. database you name |
mailwoman_bdc_filing_landscape | No — reads a bdc.db you name |
mailwoman_filer_lookup | No — reads a filer.db you name |
mailwoman_filer_family | No — reads a filer.db you name |
mailwoman_plausibility_check | Only when it geocodes |
mailwoman_overpass_export is in the first group even though it never executes a query. It renders a
POIpoint of interest (POI). A named place that is not strictly an address — landmark, transit stop, venue, amenity, or franchise. Mailwoman tags these as venue and resolves them through the gazetteer. query as OverpassQL, and finding the subject and the anchor to render means parsingaddress parsingThe process of decomposing a free-text postal address string into structured components — house number, street name, locality, region, postcode, and country — so a geocoder can resolve them to coordinates. the input first.
The modelneural classifierThe machine learning model at the core of Mailwoman's parser — a transformer encoder (~30M parameters) trained from scratch to do BIO token classification over addresses. It learns the 'grammar' of address formats; the gazetteer supplies the 'atlas.' and the gazetteergazetteerA geographical index that maps place names and postcodes to real-world coordinates. Mailwoman uses a custom-built Who's On First (WOF) SQLite database as its gazetteer — the 'atlas' half of the grammar/atlas architecture. load on the first call that needs them, not at startup. So a client with no data still connects and lists all nine tools; the failure arrives inside the first call:
✗ no gazetteer data found — the endpoint needs a resolver database to answer queries.
Fastest path (worldwide resolution, population-first ranking, ~1.65 GB):
mailwoman data pull candidate
The file lands at /tmp/mw-bare-1/wof/candidate.db and is auto-detected there — just re-run.
Or point at your own:
--candidate-db <path> / $MAILWOMAN_CANDIDATE_DB (candidate gazetteer)
$MAILWOMAN_WOF_DB / <data-root>/wof/*.db (admin WOF distribution)
Docs: https://mailwoman.sister.software/docs/developers/how-to/use-the-mcp-server
Needs it: mailwoman_parse, mailwoman_geocode, mailwoman_poi_search, mailwoman_overpass_export
Works without it: mailwoman_layer_manifest, mailwoman_bdc_filing_landscape, mailwoman_filer_lookup, mailwoman_filer_family
It arrives as a tool error with isError: true, not as a crash, so the agent reads it and can act on it.
That last pair of lines is there because an agent in the middle of a task needs to know what it can still
do, not only what broke.
Verify
Call one tool that needs no data at all. Every server answers this one:
node probe.mjs mailwoman-mcp
If tools/list returns the nine names, the client is wired correctly, whatever the state of your data
root.
Limits
- stdio only. There is no HTTP or SSE transport. A client that cannot launch a subprocess cannot use this server.
- The
en-USweightsparameterA single learned number inside a model — one weight or bias. Mailwoman's encoder has roughly 30 million of them; training is the search for good values. are the only ones loaded. The localelocaleThe combination of language and country an address comes from. en-US and fr-FR are the locales Mailwoman ships weights for. is fixed in the server; a French address parsesaddress parsingThe process of decomposing a free-text postal address string into structured components — house number, street name, locality, region, postcode, and country — so a geocoder can resolve them to coordinates. with theen-USmodelneural classifierThe machine learning model at the core of Mailwoman's parser — a transformer encoder (~30M parameters) trained from scratch to do BIO token classification over addresses. It learns the 'grammar' of address formats; the gazetteer supplies the 'atlas.' rather than thefr-FRoverlay. - One process, one geocode at a time. InferenceinferenceRunning the trained model on new input to get predictions, as opposed to training, which produces the model. In Mailwoman that means a small transformer encoder reads an address string and classifies every token — house number, street, locality, region, postcode, and the rest. A Who's On First gazetteer can feed soft location hints into the pass, but the model makes the final call on every label. Where a generative model writes text token by token, Mailwoman's output is a retrieval-augmented token classification: one label per input piece. blocks the JavaScript threadthreadA parallel workstream within a release. Threads compose; they are not sequential milestones like phases., so a client issuing parallel tool calls serializes behind the first one.
- The POI, BDC and filer tools need layerlayerOne transformer block — attention plus a feed-forward network, with normalization and residual connections — applied to every position. Stacking layers lets the model build up richer representations; Mailwoman's encoder has 6. databases that are not published.
mailwoman data pull poiprovides the POIpoint of interest (POI). A named place that is not strictly an address — landmark, transit stop, venue, amenity, or franchise. Mailwoman tags these as venue and resolves them through the gazetteer. layerlayerOne transformer block — attention plus a feed-forward network, with normalization and residual connections — applied to every position. Stacking layers lets the model build up richer representations; Mailwoman's encoder has 6.;bdc.dbandfiler.dbare built locally. A missing file becomes a friendly error naming the layerlayerOne transformer block — attention plus a feed-forward network, with normalization and residual connections — applied to every position. Stacking layers lets the model build up richer representations; Mailwoman's encoder has 6., never a raw SQLite message.
Related
- Install the Claude Code skill — the other half of the agent story: what an agent should know about using Mailwoman at all.
- Run the API server — the HTTP surface, for clients that want a port.
- Keep your data current — pulling the gazetteergazetteerA geographical index that maps place names and postcodes to real-world coordinates. Mailwoman uses a custom-built Who's On First (WOF) SQLite database as its gazetteer — the 'atlas' half of the grammar/atlas architecture. the four modelneural classifierThe machine learning model at the core of Mailwoman's parser — a transformer encoder (~30M parameters) trained from scratch to do BIO token classification over addresses. It learns the 'grammar' of address formats; the gazetteer supplies the 'atlas.'-backed tools need.