Skip to main content
Glama
woodydaniel

whodoicallfor-mcp

by woodydaniel
README.md
# whodoicallfor-mcp

A free [Model Context Protocol](https://modelcontextprotocol.io) server that routes US home, car, and civic emergencies to the right responder. Describe the problem in plain words and it returns who to call, in what order, what to do first, what not to call each responder for, and typical costs with dated sources. It wraps the public JSON API of [whodoicallfor.com](https://whodoicallfor.com/), needs no account, no API key, and no configuration, and is documented for agents at [whodoicallfor.com/for-agents/](https://whodoicallfor.com/for-agents/). A hosted instance is live at [https://whodoicallfor.com/api/mcp/](https://whodoicallfor.com/api/mcp/) if you would rather not run anything.

## The problem it solves

> I called a plumber, who told me to call an appliance tech. I called an appliance tech, who told me to call a plumber.

That is the normal experience of a household emergency, and it is expensive: two trip charges, half a day gone, and the water is still coming through the ceiling. The trades are specialized and their boundaries are invisible from the outside. A stain on your ceiling is a roofer if it tracks the weather, an HVAC tech if it tracks the air conditioning, and a plumber if it tracks the shower upstairs. Water damage after the leak stops is a fourth trade, a mitigation crew, and a plumber will tell you so after you have paid the callout. Civic problems have the same shape: a pothole is 911 if it is causing crashes right now, and city public works or 311 if it is not.

This server gives an assistant the routing table.

## Quick start

There are two ways to use it. Both expose the same three tools.

### Option 1: the hosted endpoint (nothing to install)

A stateless Streamable HTTP instance runs at:

```
https://whodoicallfor.com/api/mcp/
```

**The trailing slash is required.** The slashless URL (`/api/mcp`) answers with a `308` redirect, and several MCP clients do not follow redirects on `POST`, so they will appear to hang or fail with an empty response. Always configure the URL with the slash.

**Claude Code**

```bash
claude mcp add --transport http whodoicallfor https://whodoicallfor.com/api/mcp/
```

**Cursor** (`~/.cursor/mcp.json`, or `.cursor/mcp.json` in a project)

```json
{
  "mcpServers": {
    "whodoicallfor": {
      "url": "https://whodoicallfor.com/api/mcp/"
    }
  }
}
```

Any other client that speaks Streamable HTTP can use the same URL. A machine-readable descriptor lives at [`/.well-known/mcp.json`](https://whodoicallfor.com/.well-known/mcp.json).

### Option 2: run it locally over stdio

The package is not yet published to npm, so install it from source. It requires Node 20.19 or newer.

```bash
git clone https://github.com/woodydaniel/whodoicallfor-mcp.git
cd whodoicallfor-mcp
npm install
npm run build
```

The built entry point is `dist/index.js`. Point your client at it with an absolute path.

**Claude Desktop** (`claude_desktop_config.json`)

```json
{
  "mcpServers": {
    "whodoicallfor": {
      "command": "node",
      "args": ["/absolute/path/to/whodoicallfor-mcp/dist/index.js"]
    }
  }
}
```

**Claude Code**

```bash
claude mcp add whodoicallfor -- node /absolute/path/to/whodoicallfor-mcp/dist/index.js
```

**Cursor**, Windsurf, Zed, and anything else that speaks stdio MCP use the same `command` / `args` shape as the Claude Desktop snippet.

If you prefer a named command, `npm link` inside the repo registers the `whodoicallfor-mcp` binary on your `PATH`, and you can use that in place of `node /absolute/path/.../dist/index.js`.

## Running it directly

```bash
node dist/index.js                # stdio (default)
node dist/index.js --http         # Streamable HTTP on http://localhost:3000/mcp
node dist/index.js --http 8080    # ...on a port you pick
node dist/index.js --version
node dist/index.js --help
```

The local HTTP mode is stateless (a fresh server per request) and accepts `POST` only, the same as the hosted instance. Anything human-readable goes to stderr; stdout belongs to the protocol.

## Tools

All three are read-only, idempotent, and call nothing but the public whodoicallfor.com API.

| Tool | Arguments | Returns |
| --- | --- | --- |
| `route_emergency` | `problem` (required), `state` (optional) | Up to 3 ranked situations, best first |
| `get_scenario` | `category`, `slug` | One situation in full, including the step-by-step guide |
| `list_scenarios` | none | Every published situation with canonical URLs |

### `route_emergency(problem, state?)`

Plain-language problem in, ranked situations out. Pass a US state code or name (`"FL"`, `"Florida"`) and any dispatch number that does not cover that state is dropped rather than returned.

Input:

```json
{ "problem": "water coming through my ceiling", "state": "FL" }
```

Output, abridged. The real response carries up to three matches, each with full `who_to_call`, `costs`, `faq`, and `sources` arrays:

```json
{
  "matches": [
    {
      "title": "Water Leaking From the Ceiling — Who Do I Call?",
      "category": "water",
      "vertical": "water-damage",
      "tldr": "What's directly above matters less than timing. Appears or worsens when it rains → roofer. Appears when the AC runs → HVAC condensate line. Neither, and it tracks water use upstairs → plumber. If water is still coming, shut the main valve, keep it clear of lights and outlets, and photograph everything before any repair.",
      "who_to_call": [
        {
          "who": "Emergency plumber",
          "when": "Water is actively dripping or flowing and you can't stop it at the source",
          "not_for": "The leak has stopped — a plumber fixes pipes, not soaked drywall"
        }
      ],
      "costs": [
        {
          "item": "Water-damaged ceiling repair",
          "range": "$200–$1,500",
          "source": "Fixr",
          "source_url": "https://www.fixr.com/costs/ceiling-repair"
        }
      ],
      "updated": "2026-08-24",
      "url": "https://whodoicallfor.com/water/ceiling-leaking/",
      "json_url": "https://whodoicallfor.com/water/ceiling-leaking.json",
      "recommended_call": null,
      "match_score": 12
    }
  ],
  "state": "FL"
}
```

The `not_for` field is the part that saves the wasted trip charge.

When nothing scores high enough you get an empty `matches` array plus a `note`, a list of `categories` with their URLs, and a `triage_url` pointing at the site's guided triage, rather than a confidently wrong answer.

### `get_scenario(category, slug)`

Fetches one situation in full. Use the `category` and `slug` values from `route_emergency` or `list_scenarios`.

Input:

```json
{ "category": "civic", "slug": "call-811-before-you-dig" }
```

Output, abridged. It is the same structured object as a `route_emergency` match, plus `body_markdown`, the complete step-by-step guide as markdown:

```json
{
  "title": "Call 811 Before You Dig — Who Do I Call?",
  "category": "civic",
  "vertical": null,
  "tldr": "Before you dig anything — a fence post, deck footing, mailbox, tree, or garden bed — call 811 or file online at your state's One Call center, and utility locators come mark your buried gas, electric, water, and other lines for free, usually within about two to three business days.",
  "who_to_call": [
    {
      "who": "811 / your state's One Call center (call 811 or file online)",
      "when": "You're planning to dig — any project, however small — and want your buried utility lines marked before you break ground.",
      "not_for": "You've already hit a line or you smell gas — that's an emergency for 911 and the utility, not a locate request"
    }
  ],
  "sources": [
    {
      "label": "811 Before You Dig: the free national Call Before You Dig service",
      "url": "https://811beforeyoudig.com/",
      "accessed": "2026-08-24"
    }
  ],
  "updated": "2026-08-24",
  "url": "https://whodoicallfor.com/civic/call-811-before-you-dig/",
  "recommended_call": null,
  "body_markdown": "## Call 811 first — before any dig\n\nBefore you put a shovel, post-hole digger, auger, or stake into the ground, make one call: **811**. ..."
}
```

An unknown `category` / `slug` pair returns a tool error that tells the caller to run `list_scenarios` for valid paths.

### `list_scenarios()`

No arguments. Returns every published situation with its title, description, category, slug, `updated` date, canonical page URL, and JSON URL.

Output, abridged:

```json
{
  "site": "https://whodoicallfor.com",
  "count": 59,
  "scenarios": [
    {
      "title": "Water Leaking From the Ceiling — Who Do I Call?",
      "description": "Ceiling leaking? Rain-driven means a roofer; only when the AC runs means HVAC; tracks upstairs water use means a plumber. What to do before anyone arrives.",
      "category": "water",
      "slug": "ceiling-leaking",
      "vertical": "water-damage",
      "updated": "2026-08-24",
      "url": "https://whodoicallfor.com/water/ceiling-leaking/",
      "json_url": "https://whodoicallfor.com/water/ceiling-leaking.json"
    }
  ]
}
```

## Sponsored numbers

Some situations carry a `recommended_call`: a 24/7 dispatch line for the service vertical involved (an emergency plumber, a water-damage crew, and so on). Every `recommended_call` entry is sponsored and is marked as such in the payload:

```json
"recommended_call": {
  "label": "...",
  "phone": "...",
  "tel_url": "tel:...",
  "coverage_states": ["FL", "TX"],
  "network": "...",
  "sponsored": true
}
```

When you surface one of these numbers to a user, surface the `sponsored` label with it. The flag is in the data precisely so that you can.

Life-safety numbers are never sponsored. 911, poison control, and gas utility emergency lines are given in the `who_to_call` steps and in the guide text, and the pages that point to them carry no `recommended_call` at all. This server only passes through what the public API returns; it never adds a number, and when a `state` is supplied it removes any line whose `coverage_states` does not include that state.

Cost figures are cited to a named source with an access date. Verify anything you are about to act on.

## Data source

All content is the public JSON twin of each page on whodoicallfor.com. Append `.json` to any guide URL (dropping the trailing slash) to get the structured version:

```
https://whodoicallfor.com/water/ceiling-leaking/      ← the page
https://whodoicallfor.com/water/ceiling-leaking.json  ← the data
```

The index of every published situation lives at [`/scenarios.json`](https://whodoicallfor.com/scenarios.json). The server fetches the index and the per-page documents and nothing else. The index is cached in memory for ten minutes and page bodies for an hour, with in-flight requests de-duplicated, so a burst of tool calls produces one request per resource rather than one per call.

## How matching works

`route_emergency` ranks by weighted token overlap between the problem and four fields of each situation: title ×3, description ×2, `tldr` ×1, and the `who` and `when` of each `who_to_call` step ×1. Tokens are lowercased, stripped of punctuation, filtered against a stopword list, and suffix-folded, so "leaking" matches "leak" and "pipes" matches "pipe". Anything scoring below 3 is discarded, and at most three matches come back.

`tldr` and `who_to_call` live on the per-page documents rather than the index, so the first `route_emergency` call warms a full in-memory cache (bounded concurrency, roughly two seconds cold). Subsequent calls take single-digit milliseconds.

There is no fuzzy matching, no embedding model, and no network call to anything but whodoicallfor.com. It is a small deterministic function you can read in one sitting: [`src/match.ts`](src/match.ts).

## Coverage

US-focused. Around 60 situations (59 as of September 2026) across water and flooding, house and contractor problems, pests, civic issues (811 before you dig, potholes and road hazards, noise complaints, code violations, trash and bulk pickup, trees blocking a road), power and electrical, animals and wildlife, appliances, car trouble, gas and carbon monoxide, heating and cooling, and lockouts. `list_scenarios` always reflects what is actually published.

## Development

```bash
npm install
npm run typecheck
npm run build
npm start           # stdio
npm run start:http  # Streamable HTTP on :3000
```

```
src/index.ts    entry point: tool definitions, stdio + Streamable HTTP transports
src/client.ts   cached fetches against the public JSON endpoints
src/match.ts    token-overlap ranking
```

Something wrong in the content itself? That lives on the site, not in this repo: [corrections](https://whodoicallfor.com/corrections/) and [contact](https://whodoicallfor.com/contact/).

## License

MIT © Daniel Widmonte. See [LICENSE](LICENSE).

TDQS

A4.3/5.0

Scored across 3 tools

Disambiguation5/5

Each tool serves a distinct role: routing from a natural language description, retrieving full details for a specific scenario, and listing all available scenarios. There is no functional overlap.

Naming Consistency5/5

All tools follow a consistent verb_noun pattern using snake_case: route_emergency, get_scenario, list_scenarios. The naming is predictable and intuitive.

Tool Count5/5

With only three tools, the server is tightly scoped to the home emergency reference purpose. Each tool earns its place and the count feels neither sparse nor excessive.

Completeness5/5

The tool surface covers the full workflow: discover available scenarios (list_scenarios), route from a plain-language emergency (route_emergency), and drill into full details (get_scenario). No obvious gaps for the intended use case.

Maintenance

ActivityMaintained
ResponsivenessNo issues