API and MCP

Connecting the MCP server

Add https://nhmohio.com/api/mcp to Claude Code, Claude Desktop or any HTTP MCP client with an nhm_ key, confirm what the key can reach, and use the prompts.

Updated · 3 min read

The MCP server hands an assistant your whole account: every domain, every stored result, and, with the write scope, the ability to add a domain by answering questions, start scans, spend credits, and update saved records. It speaks Model Context Protocol over Streamable HTTP with no OAuth flow and no event stream to open.

Plan: Pro (read) and Enterprise (read and write). Free and Starter get 401.

1. Create a key

Dashboard, Settings, API Keys. Pick the scopes the key should reach (domains, scans, seo, accessibility, vulnscan, compliance, status). On Enterprise tick write if the assistant should be able to act; leave it off for a key that only reports. Copy the key when it is shown; it is shown once. Give it an expiry if you have a date in mind.

2. Add the server

Claude Code:

claude mcp add --transport http domainguard https://nhmohio.com/api/mcp \
  --header "Authorization: Bearer nhm_your_key_here"

Claude Desktop, or any client that takes a JSON config:

{
  "mcpServers": {
    "domainguard": {
      "type": "http",
      "url": "https://nhmohio.com/api/mcp",
      "headers": { "Authorization": "Bearer nhm_your_key_here" }
    }
  }
}

Any MCP client that supports a remote server over HTTP with a custom header works the same way.

3. Confirm it works

curl -s https://nhmohio.com/api/mcp \
  -H "Authorization: Bearer nhm_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'

The list that comes back is already filtered to the key's scopes and the account's plan. If a tool you expected is missing, the key is missing a scope: mint a new one rather than rewording the prompt.

4. Ask it something

  • "Which of my domains have SSL certificates expiring in the next 30 days?"
  • "Why might email from my domain be going to spam?"
  • "Read the SEO results for every domain and give me one ranked fix list."
  • "Where do we drop out of the map pack for 'emergency plumber', and why?"
  • "Compare this month against last month and tell me what actually moved."

Prompts and resources the server carries

Prompt What it does
onboard_domain The add-domain interview, start to finish
set_up_local_seo Profile, phrases, quote, first measurement
review_domain Read everything, verify it, report mistakes, deliver fixes
portfolio_triage One ranked list across every domain, spending nothing
fix_email_deliverability SPF, DKIM, DMARC and the report stream, in order

In Claude Code they appear as /mcp__domainguard__onboard_domain and so on. The server also publishes a machine-readable operating guide at https://nhmohio.com/mcp/claude-guide.md, which tells a connected assistant to prefer stored results over new scans, to quote before spending, and to verify findings before presenting them as fact.

Rules the server enforces

  • Scopes decide the tool list. Tools outside a key's scopes are never offered and cannot be called.
  • Read-only means read-only. Without write, an assistant cannot start live lookups or scans, spend credits, or change configuration. Live-scan tools are write-gated even when they do not modify the target, because they spend allowances and contact upstream providers.
  • The plan is re-read per request. An account that moves from Enterprise to Pro loses write on the next call.
  • Ownership is inherited. Every tool goes through the same registered routes the dashboard uses, with the same tenant checks.
  • Tool lists are private. tools/list results are marked private and scope-dependent; a shared proxy must never serve one key's list to another.

Errors

You see Meaning
401 No key, or a plan below Pro
isError: "missing the … scope" Mint a key with that scope
isError: "read-only" The tool changes state and the key has no write scope
429 The key's hourly limit, or a per-plan manual-scan limit (Free allows one manual SEO scan a day)
409 idempotency_mismatch That request_key was used for a different request; generate a fresh one

Still stuck?

Ask the people who run the scanner.

Send the domain and what you expected to see. We look at the same scan you are looking at and write back with what it means and what to change.