MCP Server (Connect AI to WALDO)
The WALDO MCP server lets you point Claude Desktop, Claude.ai, Cursor, ChatGPT, or any other MCP-aware AI directly at your WALDO analyses. Once connected, you can ask the AI things like "summarize my Cuyahoga County analysis," "what's the location quotient for healthcare in Travis County?", or even "run an analysis on Hamilton County, Ohio for Q2 2026" — and the AI does it, reading and writing live data through tools rather than guessing.
TL;DR for CRE professionals: MCP (Model Context Protocol) is a standard way for AI assistants to talk to outside services. Set it up once with your WALDO API key. From then on, the AI can pull your analyses into a chat, summarize them, compare them, and trigger new ones — all without leaving the conversation.
What you get from this
- Ask the AI about your data instead of digging through dashboards. "Which of my completed analyses has the highest LQ in healthcare?" — the AI calls the tools, finds the answer.
- Use the AI you already pay for. No new vendor, no new login. If you have Claude Desktop, claude.ai, Cursor, or ChatGPT (with the right plan), you can connect WALDO.
- Trigger analyses from a chat. "Run an analysis on Madison County, Alabama for Q1 2026" → AI calls , returns the analysis ID, polls for completion, summarizes the result when it's done.text
run_analysis - Compose with everything else the AI knows. Have the AI pull your latest WALDO numbers AND draft a client email referencing them, in one chat.
- No code required. This is a config-file-edit + restart operation. About 5 minutes per AI client.
Real-world workflows
| Ask the AI… | What it does |
|---|---|
| "What's the population growth and supply gap on my latest Travis County analysis?" | Calls text text text |
| "Run an analysis on Mecklenburg County, multifamily." | Calls text text text |
| "Compare the comparative advantage of Saint Louis vs. Kansas City using 3-digit shift-share for 2019 Q4 → 2025 Q3." | Calls text text text text |
| "Compare the supply/demand gap between my Cuyahoga and Hamilton county analyses." | Pulls both via text |
| "Find all my analyses where the investment recommendation is BUY or STRONG_BUY." | Calls text text |
| "Draft a one-page client email summarizing my Travis County analysis." | Pulls the data via MCP tools, formats it as an email. |
What you'll need
- A WALDO API key with the scope. Generate one in Settings → API Keys. Pick the scopes you want — typicallytext
mcp:invoke+textread:analyses, plustextmcp:invokeif you want the AI to be able to trigger new analyses (not just read existing ones).textwrite:analyses - An MCP-aware AI client. As of April 2026:
- Claude Desktop (Anthropic) — supports MCP natively. ✓
- claude.ai (web) — supports MCP via Connectors (web app sidebar). ✓
- Cursor (code editor) — supports MCP natively. ✓
- ChatGPT (OpenAI) — supports MCP via Custom Connectors on ChatGPT Pro / Team / Enterprise plans. ✓
- Other MCP clients (Continue.dev, Goose, custom) — anything speaking the MCP spec works.
- Five minutes to edit one config or click through one settings UI per client you want to connect.
Step-by-step: Claude Desktop
This is the most common setup. Claude Desktop reads MCP servers from a config file you edit by hand.
-
Get your WALDO API key (Settings → API Keys → New key, scopes
+textread:analyses+ optionallytextmcp:invoke). Copy the fulltextwrite:analysesvalue — you'll only see it once.textwaldo_live_... -
Open the Claude Desktop config:
- Click the Claude Desktop menu → Settings…
- Click Developer in the left sidebar
- Click Edit Config. This opens in your default text editor.text
claude_desktop_config.json
-
Paste this block into the file (if
already exists, just add thetextmcpServersentry inside it):text"waldo"json{ "mcpServers": { "waldo": { "command": "npx", "args": [ "-y", "mcp-remote", "https://www.waldocre.io/api/mcp", "--header", "Authorization:${AUTH_HEADER}" ], "env": { "AUTH_HEADER": "Bearer waldo_live_PASTE_YOUR_KEY_HERE" } } } }Claude Desktop launches
, an npm bridge that forwards stdio JSON-RPC to WALDO's streamable-HTTP endpoint and injects thetextmcp-remoteheader. Claude Desktop has no native config field for HTTP headers, so this bridge is the standard pattern for any remote MCP server with bearer auth.textAuthorizationWhy the odd
syntax (no space,textAuthorization:${AUTH_HEADER}baked into the env value)? Claude Desktop's env-var substitution does not interpolatetextBearerplaceholders that sit mid-string after a space (text${...}arrives as the literal string). The workaround is to put the entire header value — including thetextBearer ${KEY}prefix — into one env var, and pass the argument as a single colon-joined token.textBearer -
Replace
with the real API key value you copied in step 1. Keep the literaltextwaldo_live_PASTE_YOUR_KEY_HEREprefix in front of it.textBearer -
Save the file.
-
Quit Claude Desktop completely and reopen it. (On macOS: ⌘Q, then relaunch from Applications.) MCP servers only load on startup. The first launch takes 10–20 seconds while npx downloads
.textmcp-remote -
Verify it worked. Open Settings → Developer → "Local MCP servers".
should show a green/connected status (not "Failed"). In a new chat, look for a 🔧 wrench icon or "Search and tools" menu. You should see WALDO tools listed:textwaldo,textsearch_markets,textlist_analyses, etc. If they're not there, see Troubleshooting below.textget_analysis
Try it:
"Use the WALDO tools to list my analyses."
The AI will call
list_analysesStep-by-step: claude.ai (web)
Anthropic's web app also supports MCP via the Connectors UI:
- Generate a WALDO API key as above.
- Sign in to claude.ai.
- In the conversation sidebar, click the + icon or the integrations menu (location varies as Anthropic ships UI updates — look for "Connectors" or "Integrations").
- Click Add custom connector (or similar).
- Fill in:
- Name: WALDO
- URL: text
https://www.waldocre.io/api/mcp - Authentication: Bearer token
- Token: your keytext
waldo_live_...
- Save. The WALDO tools should appear in the chat tool picker for new conversations.
Step-by-step: Cursor
Cursor is the AI code editor by Anysphere; same MCP support as Claude Desktop.
-
Generate a WALDO API key.
-
In Cursor, open Settings → Cursor Settings → MCP (or Features → MCP).
-
Click Add new MCP server.
-
Paste:
json{ "name": "waldo", "url": "https://www.waldocre.io/api/mcp", "headers": { "Authorization": "Bearer waldo_live_PASTE_YOUR_KEY_HERE" } } -
Replace the placeholder with your real key.
-
Save. Restart Cursor. The WALDO tools appear in the chat tool picker.
Step-by-step: ChatGPT (Pro / Team / Enterprise)
OpenAI added MCP support via Custom Connectors in 2025. Available on paid plans only.
- Generate a WALDO API key.
- Sign in to chatgpt.com.
- Click your profile → Settings → Connectors (also called Custom Connectors depending on plan).
- Click Add connector or + New custom connector.
- Fill in:
- Name: WALDO
- MCP Server URL: text
https://www.waldocre.io/api/mcp - Authentication: Bearer token / Custom header
- Header: (paste your key)text
Authorization: Bearer waldo_live_...
- Save. Enable the connector for the conversations you want to use it in.
ChatGPT's exact UI moves around — if "Connectors" isn't where you expect, search OpenAI's help center for "MCP" or "custom connector."
Troubleshooting
"WALDO tools don't appear in the menu after restart."
- Confirm you fully quit and reopened the app (not just closed the window).
- Open the config file again and validate it as JSON (paste into jsonlint.com if unsure — a missing comma or bracket breaks all MCP servers, not just WALDO).
- Check the Claude Desktop logs: macOS , Windowstext
~/Library/Logs/Claude/mcp-server-waldo.log. A 401 means the key is wrong; a 403 means the key lackstext%APPDATA%\Claude\logs\mcp-server-waldo.logscope.textmcp:invoke
"Server disconnected" / "Failed" status in Settings → Developer.
- Tail . If you seetext
mcp-server-waldo.log, your config is using an outdated bridge package. Switch to thetextnpm error 404 Not Found ... @modelcontextprotocol/server-fetchblock above.textmcp-remote - If you see arriving as a literal string in the request, the env var didn't interpolate — make sure the argument istext
Authorization: Bearer ${...}(no space after the colon) and the env value carries the fulltextAuthorization:${AUTH_HEADER}string.textBearer waldo_live_... - First launch takes 10–20 seconds while npx fetches . Give it a moment before declaring it broken.text
mcp-remote
"Tools appear but every call fails with 401."
- The API key is wrong, revoked, or has trailing whitespace. Open Settings → API Keys, confirm the key is Active, regenerate if unsure.
"Tools appear but every call fails with 403."
- The key doesn't have the scope. Revoke it and create a new one withtext
mcp:invoke+textread:analyses(andtextmcp:invokeif you wanttextwrite:analyses).textrun_analysis - OR you're on a Basic-tier WALDO plan. MCP requires Professional or Enterprise.
"run_analysis
- It runs asynchronously; the 6-phase chain takes 30 seconds to a few minutes depending on the market. The AI should poll (ortext
get_analysis) untiltextGET /v1/analyses/{id}/statusbefore callingtextstatus === 'completed'or asking for the recommendation. Tell the AI explicitly: "Wait for the analysis to complete, then summarize it."textget_supply - Note: ,text
get_shift_share, andtextget_economic_basedon't require completion — they can be called the momenttextget_market_metricsreturns thetextrun_analysis. Onlytextanalysis_idand the final recommendation are phase-dependent.textget_supply
"I'm getting rate-limited."
- The MCP endpoint is on the same per-user sliding-window rate limiter as the REST API (default 100 requests / 60 seconds). If the AI is hammering tools in a loop, slow it down with a clearer prompt.
Tier requirements + cost
MCP is included on Professional and Enterprise plans. Each MCP tool call bills 5 cost units against your key's monthly quota. Running an analysis via
run_analysisFor your integrator (or AI agent)
Everything below is the technical reference. Paste a link to this page into Claude / Cursor / ChatGPT — the AI has all the information it needs to wire up additional MCP integrations.
Endpoint + auth
textPOST https://www.waldocre.io/api/mcp Authorization: Bearer waldo_live_<key> Content-Type: application/json Accept: application/json, text/event-stream
Stateless streamable-HTTP transport (
@modelcontextprotocol/sdksessionIdGenerator: undefinedwaldo_live_...withApiAuthTool responses are serialized through the same lib functions as the REST routes. A WALDO resource fetched via MCP is byte-equal to the JSON returned by the corresponding
/api/v1/*Tools
| Tool | Scope required | Cost units | Phase-dependent? | Purpose |
|---|---|---|---|---|
text | none (any active key) | 5 | no | Look up county/MSA text text text text text text text text |
text | text | 5 | no | List the caller's analyses |
text | text | 5 | no | Summary (status, phase, market) — call this to poll completion |
text | text | 5 | reads phase 2 | LQs, multipliers, top sectors — returns text |
text | text | 5 | reads phase 3 | Demographics / housing / economics — returns text |
text | text | 5 | reads phase 5 | Pipeline + supply/demand gap — returns text |
text | text | 5 | no — computes fresh | CCIM CI 102 decomposition for any base/end period. Computes from BLS QCEW on every call; only needs the analysis's text text text text |
text | text | 5 | n/a (creates) | Start a new analysis. Required: text text text text text text text text text text |
run_analysisget_shift_share3"3"Recommended workflow
text1. search_markets({ q }) resolve human name → market_code 2. list_analyses({ status: "completed" }) check whether one already exists for this market/period 3. run_analysis({ market_code, market_type, market_name }) only if step 2 didn't find one 4a. get_shift_share({ analysis_id, ... }) independent of phase progress — 4a. get_economic_base({ analysis_id }) call any of these immediately 4a. get_market_metrics({ analysis_id }) after run_analysis returns 4b. get_analysis({ analysis_id }) poll until status === "completed" BEFORE calling get_supply 5. get_supply({ analysis_id }) only after status === "completed"
The same shape applies whether you're building a script, a webhook consumer, or feeding tools to an LLM. The most common mistake is skipping step 2 and creating a duplicate analysis when a finished one already exists.
Direct curl test
Smoke-test without an MCP client:
bash# List available tools curl -X POST "https://www.waldocre.io/api/mcp" \ -H "Authorization: Bearer $WALDO_KEY" \ -H "Content-Type: application/json" \ -H "Accept: application/json, text/event-stream" \ -d '{ "jsonrpc": "2.0", "id": 1, "method": "tools/list" }' # Call a tool — search_markets curl -X POST "https://www.waldocre.io/api/mcp" \ -H "Authorization: Bearer $WALDO_KEY" \ -H "Content-Type: application/json" \ -H "Accept: application/json, text/event-stream" \ -d '{ "jsonrpc": "2.0", "id": 2, "method": "tools/call", "params": { "name": "search_markets", "arguments": { "q": "Lafayette", "type": "county", "state": "LA" } } }' # Call a tool — run_analysis (year/quarter optional; defaults to latest QCEW) curl -X POST "https://www.waldocre.io/api/mcp" \ -H "Authorization: Bearer $WALDO_KEY" \ -H "Content-Type: application/json" \ -H "Accept: application/json, text/event-stream" \ -d '{ "jsonrpc": "2.0", "id": 3, "method": "tools/call", "params": { "name": "run_analysis", "arguments": { "market_code": "C41180", "market_type": "msa", "market_name": "St. Louis, MO-IL" } } }' # → { "analysis_id": "...", "status": "draft", "status_url": "/v1/analyses/.../status" } # Call a tool — get_shift_share (period args accept "3" or 3; coerced) curl -X POST "https://www.waldocre.io/api/mcp" \ -H "Authorization: Bearer $WALDO_KEY" \ -H "Content-Type: application/json" \ -H "Accept: application/json, text/event-stream" \ -d '{ "jsonrpc": "2.0", "id": 4, "method": "tools/call", "params": { "name": "get_shift_share", "arguments": { "analysis_id": "PASTE_FROM_RUN_ANALYSIS_RESPONSE", "base_year": 2019, "base_quarter": 4, "end_year": 2025, "end_quarter": 3, "naics_level": 3 } } }'
Rate limiting + audit
- Per-user sliding window via Upstash Redis (default 100 req / 60 sec, tier-overridable).
- Each MCP HTTP request writes to (route=text
api_audit_log, cost_units=5, latency_ms, status). Usetext/api/mcpfor support correlation.textX-Request-ID - Tier gating in — Basic-tier keys 403 with the same problem-details body the REST API uses.text
authenticateMCPRequest
What's NOT in MCP yet
- Streaming responses — the SDK supports SSE; we currently return JSON-only for simpler curl debugging.
- Server-pushed notifications — clients poll for completion status when they need a phase-dependent result (text
get_analysis, recommendation).textget_supply,textget_shift_share,textget_economic_basedon't need polling. A futuretextget_market_metricschannel mirroring webhooks for the duration of the connection is on the roadmap.textnotifications/... - Binary export resource — isn't a great fit for MCP text/JSON results. Usetext
exportfrom REST or the Send-to-webhook/email/CRM tile on the Phase 6 dashboard instead.textGET /v1/analyses/{id}/export?format=pdf|excel|pptx
Error format
JSON-RPC 2.0 error responses with WALDO-specific codes:
json{ "jsonrpc": "2.0", "id": 2, "error": { "code": -32602, "message": "Invalid params: market_code is required", "data": { "request_id": "..." } } }
Source of truth
Tool implementations live in
lib/mcp/server.ts/api/v1/*buildWaldoMCP(ctx)