Let an AI agent read your site
Every Web Buddy site speaks the Model Context Protocol. Point Claude — or any MCP client — at one URL, give it a site API key, and it can answer questions about that site's domains, traffic and team. It cannot change anything.
What it is
MCP is the protocol AI assistants use to call tools. This server exposes the same read-only REST API documented on the REST API page, as 4 tools an agent can call directly — so instead of copying a chart into a chat, you can ask “which pages did best last week?” and have the assistant fetch the answer itself.
Every tool here is backed by an endpoint on that page, and calling one runs exactly the same code path as the equivalent curl, including the same authentication and the same rate limit. There is nothing an agent can do through MCP that your API key cannot already do over HTTP.
The whole v1 API is read-only — it publishes no endpoint that creates, changes or deletes anything. So there is no MCP tool that can, either. An agent connected here can look at your site; it cannot connect a domain, invite a member, toggle an add-on or touch billing.
Connecting a client
Create a key on your site's APItab in the console (the same keys the REST API uses), then add this server to your client's configuration:
{
"mcpServers": {
"webbuddy-console": {
"url": "https://webbuddy.radioworkflow.com/api/mcp",
"headers": {
"Authorization": "Bearer wb_live_your_key_here"
}
}
}
}With the Claude Code CLI, that is one command:
claude mcp add --transport http webbuddy-console https://webbuddy.radioworkflow.com/api/mcp \ --header "Authorization: Bearer wb_live_your_key_here"
Replace wb_live_your_key_here with your real key. Treat that configuration file the way you would treat any file with a password in it.
The auth model
- One key, one site. A key resolves to exactly one Web Buddy site, and no tool takes a site argument — there is no way to ask this server about a different site, whatever an agent is told to try.
- The key is the only credential. No cookies, no session, no OAuth. Every request carries the bearer token and is authenticated on its own; nothing is remembered between them.
- The key's scopes decide which tools exist.
tools/listis filtered to what the connecting key can actually call, so an agent is never shown a capability that would answerinsufficient_scope. Tick a scope less when you mint the key and the matching tool disappears from the connection; scopes cannot be added to a key afterwards, so widening one means a new key. What each scope unlocks. - Revoking is immediate. Revoke the key in the console and the next tool call fails. There is no session to expire separately.
- A key for a suspended site stops working. Authentication checks the site is still active, so a paused site's key answers
unauthorizedrather than serving stale data.
The tools
Generated from the server's own catalogue. This is every tool the server can publish; the tools/list a given connection sees is this table filtered by the scopes on the key it connected with, so a key that carries two of the three scopes sees fewer rows than this.
| Tool | Calls | Needs scope | Hints |
|---|---|---|---|
get-site The key's own site: identity, serving URLs, and its custom domains with live HTTPS status. | GET /api/v1/site | site:read | read-onlyidempotent |
get-analytics-daily Page-view counts per day over a window, oldest first and zero-filled. | GET /api/v1/analytics/daily | analytics:read | read-onlyidempotent |
get-analytics-top-paths The most-viewed paths over a window, highest first. | GET /api/v1/analytics/top-paths | analytics:read | read-onlyidempotent |
get-members The site's team members and their roles. | GET /api/v1/members | members:read | read-onlyidempotent |
Those badges are MCP annotations — advisory labels a server sends so a client can decide what to show a person before it calls something. The protocol says a client should treat annotations from any server as unverified claims, and it is right to. They are true here because the API behind them is read-only, not because the label enforces anything. The only real boundary is your API key.
What a tool call returns
Every result carries the endpoint's JSON twice: once as a text block, byte-for-byte what the HTTP endpoint returns, and once as structuredContent matching the tool's published outputSchema. Two of the endpoints answer with a top-level array; MCP requires structured content to be an object, so those are published under a named key ( days, paths, members ) while the text block keeps the bare array.
The published output schemas are deliberately loose in two ways, both so that an ordinary successful call can never be rejected by a client that validates: no field is marked required, and documented value sets (a certificate state, a role, a template) are published as plain strings with the known values in the description. New values in those sets ship without a new API version, so a schema that froze them would break on the first site that had one.
Limits
- 120 requests per minute, per key — the same bucket the REST API uses. A tool call spends two of them: one for the MCP request, one for the API call it makes on your behalf.
- 256 KB request bodies. Far more than any legitimate call needs; the largest tool here takes two integers.
- One message per request. JSON-RPC batches are refused — MCP removed batching from the protocol, and a batch here would multiply every other limit by the number of messages in it.
POSTonly. There is no SSE stream and no session id: the server is stateless, and every request is authenticated on its own. AGETto the endpoint answers 405.
What it cannot reach, and why
A tool exists here only when a documented REST endpoint backs it. That rule costs a few things, and it is worth knowing which:
Add-ons and plugins
The console knows which add-ons a site has enabled (`site_plugins`), but `/api/v1` does not publish them, so there is no endpoint for a tool to call. Reading them straight out of the database from here would create an MCP-only capability with no REST equivalent — and one that sits next to a column holding the site's own BYOK Anthropic key, which is exactly the neighbourhood not to improvise in.
Changing anything
`/api/v1` is read-only. There is no endpoint that connects a domain, invites a member, toggles an add-on or touches billing, so there is nothing for a write tool to call. Every tool here is annotated `readOnlyHint`, and that annotation is true because of the API's shape rather than because of a promise this layer makes.
Anything about another site
Not a gap — the guarantee. A key resolves to exactly one site and the v1 URLs carry no site selector, so there is no argument to pass and no tool to add. An agent driving this server can only ever see the site whose key it was given.
Errors
Failures come back in two different shapes, and the difference is deliberate. A problem with your request — an unknown method, an unknown tool name, a revoked key — is a JSON-RPC error, addressed to the client. A problem with a call — arguments that do not validate, or an API error — comes back as a tool result with isError: true, so the model can read what went wrong and fix its own call rather than the failure disappearing into a transport layer.
Tool errors carry the API's own error code and remediation under a _meta key. The codes are the same ones the REST API uses:
unauthorized(HTTP 401, do not retry) — No usable API key on the request. The `Authorization` header was missing or malformed, the token did not match any key, the key has been revoked, or the key's site is no longer active.insufficient_scope(HTTP 403, do not retry) — The API key is valid, but does not carry the scope this endpoint requires. Scopes are chosen when a key is created and cannot be changed afterwards.rate_limited(HTTP 429, retryable) — The key exceeded 120 requests in the current 60-second window. The same code also answers a caller that has presented too many tokens which do not resolve — that brake is counted per network address rather than per key, because a request nobody can attribute to a key has no key bucket to charge, and looking a bad token up still costs a database call.internal_error(HTTP 500, retryable) — The request failed inside the server. The cause is never disclosed to the caller — the wrapper swallows the exception so a stack trace cannot reach an API consumer.
Calling it without a client
It is plain JSON-RPC 2.0 over HTTP POST, so anything that can make a request can drive it:
curl 'https://webbuddy.radioworkflow.com/api/mcp' \
-H "Authorization: Bearer wb_live_your_key_here" \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'Server webbuddy-console 1.0.0, speaking MCP revision 2025-06-18. The methods it implements are initialize, ping, tools/list and tools/call — there are no resources, prompts or sampling.