MCP Tools Reference
Site Health's MCP server exposes 9 read-only tools at:
POST https://sitehealth.octagramlabs.com/api/mcp
Transport: Streamable HTTP (stateless). Protocol: JSON-RPC 2.0. Auth: Authorization: Bearer <wsh_… | at_…>.
Every tool queries the authenticated user's own scan data — no AI inference happens server-side. Your Claude subscription does the reasoning; Site Health is just a structured data source.
Protocol methods
| Method | Purpose |
|---|---|
initialize | Client handshake. Server returns protocol version and capabilities. |
notifications/initialized | Client ack (no response). |
tools/list | Enumerate available tools with JSON Schema input definitions. |
tools/call | Invoke a tool by name with arguments. |
ping | Health check. |
initialize
{
"jsonrpc": "2.0",
"id": 1,
"method": "initialize",
"params": {
"protocolVersion": "2025-03-26",
"capabilities": {},
"clientInfo": { "name": "claude-desktop", "version": "0.8.1" }
}
}
Response:
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"protocolVersion": "2025-03-26",
"capabilities": { "tools": {} },
"serverInfo": { "name": "site-health-mcp", "version": "0.1.0", "title": "Site Health" }
}
}
Manual test with curl
curl -X POST https://sitehealth.octagramlabs.com/api/mcp \
-H "Authorization: Bearer wsh_…" \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
Expected status codes: 200 OK (success), 202 Accepted (notification batch), 401 Unauthorized (bad/missing token), 400 (parse error).
Tool 1 — list_sites
List every Webflow site connected to the user's account, plus latest health score.
Input schema: none.
Example call
{
"jsonrpc": "2.0",
"id": 2,
"method": "tools/call",
"params": { "name": "list_sites", "arguments": {} }
}
Example response
{
"jsonrpc": "2.0",
"id": 2,
"result": {
"content": [{
"type": "text",
"text": "{\"sites\":[{\"id\":\"11111111-…\",\"name\":\"Acme Inc\",\"domain\":\"acme.com\",\"healthScore\":87,\"lastPublished\":\"2026-04-17T10:00:00Z\",\"founding\":false}],\"count\":1}"
}]
}
}
Tool 2 — get_latest_scan
Get the most recent completed scan for a site, including overall Lighthouse scores and budget thresholds.
Input schema:
| Field | Type | Required | Description |
|---|---|---|---|
siteId | string (uuid) | yes | Site ID from list_sites |
Example call
{
"jsonrpc": "2.0",
"id": 3,
"method": "tools/call",
"params": {
"name": "get_latest_scan",
"arguments": { "siteId": "11111111-2222-3333-4444-555555555555" }
}
}
Example result payload
{
"scanId": "aaaa-bbbb-…",
"siteName": "Acme Inc",
"domain": "acme.com",
"completedAt": "2026-04-17T10:05:00Z",
"pagesTotal": 12,
"strategy": "both",
"scores": { "health": 87, "performance": 82, "seo": 95, "accessibility": 91, "bestPractices": 100 },
"budget": { "performance": 80, "lcp": 2500, "cls": 0.1 }
}
Tool 3 — get_page_details
Full Lighthouse data for one page. If pagePath is omitted, returns the worst-performing page for that strategy.
Input schema:
| Field | Type | Required | Description |
|---|---|---|---|
scanId | string (uuid) | yes | Scan ID |
pagePath | string | no | E.g. /pricing. Omit for worst-performing page. |
strategy | "mobile" | "desktop" | no | Default mobile |
Example call
{
"jsonrpc": "2.0",
"id": 4,
"method": "tools/call",
"params": {
"name": "get_page_details",
"arguments": { "scanId": "aaaa-…", "pagePath": "/pricing", "strategy": "mobile" }
}
}
Result includes: scores, coreWebVitals (lcp/cls/inp/fcp/ttfb/tbt/speedIndex/tti), crux (or null), opportunities, thirdPartyScripts, accessibilityIssues, bestPracticesIssues, seoIssues, lcpElement, clsElements, diagnostics, stackPacks.
Tool 4 — get_lcp_elements
Return the specific DOM element causing LCP and the elements causing CLS shifts on every page in a scan.
Input schema:
| Field | Type | Required | Description |
|---|---|---|---|
scanId | string (uuid) | yes | — |
strategy | "mobile" | "desktop" | no | Default mobile |
Example response
{
"pages": [
{
"pagePath": "/",
"lcpMs": 3200,
"clsScore": 0.14,
"lcpElement": { "selector": "img.hero", "snippet": "<img class=\"hero\" src=\"…\">" },
"clsElements": [{ "selector": ".promo-banner", "clsContribution": 0.08 }]
}
]
}
Tool 5 — get_third_party_scripts
Aggregate third-party scripts across every page of the latest scan, sorted by impact (blocking time × pages affected).
Input schema:
| Field | Type | Required | Description |
|---|---|---|---|
siteId | string (uuid) | yes | — |
Example response
{
"scripts": [
{ "domain": "www.googletagmanager.com", "totalTransferBytes": 450000, "avgBlockingMs": 220, "pagesAffected": 12, "impactScore": 2640 },
{ "domain": "connect.facebook.net", "totalTransferBytes": 180000, "avgBlockingMs": 95, "pagesAffected": 12, "impactScore": 1140 }
],
"pagesAnalyzed": 12
}
Tool 6 — get_accessibility_issues
Aggregated accessibility audits across the latest scan, grouped by audit ID and classified by severity.
Input schema:
| Field | Type | Required | Description |
|---|---|---|---|
siteId | string (uuid) | yes | — |
severity | "critical" | "serious" | "moderate" | "minor" | "all" | no | Default all |
Severity is derived from the Lighthouse audit score: 0 → critical, <0.5 → serious, <0.75 → moderate, else minor.
Example response
{
"issues": [
{
"id": "color-contrast",
"title": "Background and foreground colors do not have a sufficient contrast ratio.",
"description": "Low-contrast text is difficult or impossible for many users to read.",
"severity": "critical",
"pagesAffected": 8,
"affectedPaths": ["/", "/pricing", "/blog", "…"]
}
],
"count": 14,
"pagesAnalyzed": 12,
"avgAccessibilityScore": 88
}
Tool 7 — get_regression_report
Compare the latest scan against the previous completed scan. Detects score drops >5 points and Core Web Vitals threshold crossings.
Input schema:
| Field | Type | Required |
|---|---|---|
siteId | string (uuid) | yes |
Example response
{
"latestScanId": "aaaa-…",
"latestScanCompletedAt": "2026-04-17T10:05:00Z",
"previousScanId": "zzzz-…",
"hasRegression": true,
"regressionCount": 2,
"regressions": [
{ "metric": "performance", "from": 88, "to": 74, "delta": -14, "pagePath": "/" },
{ "metric": "lcp", "from": 2300, "to": 3400, "delta": 1100, "pagePath": "/blog" }
]
}
Tool 8 — get_trend
Historical time-series of average scores across the last N completed scans.
Input schema:
| Field | Type | Required | Description |
|---|---|---|---|
siteId | string (uuid) | yes | — |
limit | integer (1–50) | no | Default 20 |
Example response
{
"scans": [
{ "scanId": "…", "completedAt": "2026-04-01T09:00Z", "performance": 82, "seo": 95, "accessibility": 91, "bestPractices": 100, "pagesTotal": 12 },
{ "scanId": "…", "completedAt": "2026-04-08T09:00Z", "performance": 85, "seo": 95, "accessibility": 91, "bestPractices": 100, "pagesTotal": 12 }
],
"count": 2
}
Oldest first, so a line chart reads left-to-right.
Tool 9 — generate_client_report
Create a publicly shareable report URL for a scan (no login required for the recipient). Good for agencies sending results to end-clients.
Input schema:
| Field | Type | Required | Description |
|---|---|---|---|
scanId | string (uuid) | yes | — |
title | string (≤200) | no | Defaults to "{siteName} Performance Report" |
expiresInDays | integer (0–365) | no | Default 30. 0 = no expiry. |
Example response
{
"url": "https://sitehealth.octagramlabs.com/report/Zk3p2xQw8R…",
"token": "Zk3p2xQw8R…",
"expiresInDays": 30
}
Error envelope
All tool errors are returned inside a successful JSON-RPC response with isError: true:
{
"jsonrpc": "2.0",
"id": 4,
"result": {
"content": [{ "type": "text", "text": "Site not found or not owned by you" }],
"isError": true
}
}
Protocol-level failures (bad token, unknown method, invalid params) use the JSON-RPC error field:
{
"jsonrpc": "2.0",
"id": 4,
"error": { "code": -32601, "message": "Unknown method: tools/cool" }
}
When wiring this up in Claude Desktop, the Authorization: Bearer wsh_… header goes into mcpServers.<name>.env or the headers config depending on your transport. Consult the MCP integration guide for the full config snippet.