This site is an MCP server
Everything on this site — 5 tools over 84 blog posts, 8
case studies with real metrics, the topic clusters and the compiled knowledge base — is exposed
over the Model Context Protocol at https://www.dillonbrowne.com/api/mcp. Point an MCP client at it
and ask it about my work; the tool results come back from here.
It is the same tool registry the Copilot on this page uses, adapted to a second
transport rather than reimplemented. There is no SDK dependency and no Durable Object:
the protocol is a stateless JSON-RPC dispatcher, and after the
2026-07-28 revision that is genuinely all it needs to be.
Connect
Claude Desktop and Cursor both read a JSON config; Claude Code takes a one-liner.
{
"mcpServers": {
"dillonbrowne": {
"type": "http",
"url": "https://www.dillonbrowne.com/api/mcp"
}
}
} claude mcp add --transport http dillonbrowne https://www.dillonbrowne.com/api/mcp The request path
// no model inference here — your client brings the model
// no AI Search: every tool answers from data in the bundle
// stateless — no sessions, no SSE, nothing to resume
Try it without a client
This fires real JSON-RPC at /api/mcp from your browser
and shows both envelopes verbatim — the same bytes an MCP client would exchange.
Request
POST /api/mcp HTTP/1.1
Content-Type: application/json
Accept: application/json
MCP-Protocol-Version: 2026-07-28
Mcp-Method: tools/list
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/list",
"params": {
"_meta": {
"io.modelcontextprotocol/protocolVersion": "2026-07-28",
"io.modelcontextprotocol/clientInfo": {
"name": "dillonbrowne-playground",
"version": "1.0.0"
},
"io.modelcontextprotocol/clientCapabilities": {}
}
}
}Response
// press Send — this hits /api/mcp for realTools
Generated from the server's own registry, so this table cannot drift from what the endpoint serves.
| tool | arguments | what it returns |
|---|---|---|
| searchBlog | query | Keyword search across Dillon's blog posts on cloud, platform, Kubernetes, observability, security and AI infrastructure. Returns titles, descriptions, tags and canonical URLs — call getPost for the full text. |
| getExperience | query | Keyword search over Dillon's professional background — experience, skills, architecture notes and the site knowledge base. |
| getWork | slug? | Curated case studies with real metrics, architecture and tradeoffs. Omit slug for the index; pass a slug for the full entry. |
| getPost | slug | Fetch the full markdown of one blog post by slug. Use searchBlog first to find a slug. Returns the complete post body, not an excerpt. |
| listTopics | — | List the curated topic clusters the blog is organised into, with post counts and canonical URLs. A good first call to orient before searchBlog. |
Resources
- —
dillonbrowne://profile— identity and public career timeline, as JSON - —
dillonbrowne://knowledge-base— the compiled corpus the Copilot answers from - —
dillonbrowne://post/{slug}— full markdown of any post (all enumerated inresources/list) - —
dillonbrowne://work/{slug}— a case study with metrics, architecture and tradeoffs - — /llms.txt — an
https://resource, because a client can fetch it directly./llms-full.txtis deliberately not a resource: at ~1.2 MB it belongs behind a fetch, not in a tool result.
Protocol conformance
Revision 2026-07-28 removed the initialize handshake, protocol
sessions and the Mcp-Session-Id header, ping, and SSE
resumability; it added server/discover, which servers MUST implement but
clients MAY skip; required the
Mcp-Method / Mcp-Name request headers, and added
resultType to every result, and ttlMs / cacheScope to list and read results. Most shipping clients still speak the older era, so this server
answers both.
- — SSE. The transport allows answering a POST with one JSON object, and every tool here resolves in single-digit milliseconds against bundled data. Opening a stream to deliver one object would be decoration.
- — Sampling, roots, logging. Deprecated in
2026-07-28(SEP-2577) — still functional, but new implementations should not adopt them. Elicitation is not deprecated; it moved to the Multi Round-Trip Requests pattern, where the server returnsresultType: "input_required"instead of initiating a request. None of the four mean anything for a read-only corpus, which is why none is implemented here. - — Subscriptions. Content changes only on deploy and there is no notification
channel, so
listChangedis not advertised — claiming it would be a lie in the capabilities block.
There is no auth here — and that is the interesting part
This server is public, read-only, and serves data that is already on the page, so it
needs no authorization. A private MCP server — one reaching real systems — is a
different problem, and it is the one I actually work on: OAuth 2.1, a
/.well-known/oauth-protected-resource document, least-privilege scopes, short-lived
credentials, and an audit trail that can answer which client reached which data.
Here is the handshake a private server needs today. Client ID Metadata
Documents, not Dynamic Client Registration: 2026-07-28 deprecated DCR,
so a new build should not reach for it — the client publishes a document at a URL it controls,
and that URL is its client ID.
- 01 MCP client to Metadata document publish, once, at deploy time
served at https://app.example.com/oauth/client-metadata.json { "client_id": "https://app.example.com/oauth/client-metadata.json", "client_name": "Example MCP Client", "redirect_uris": ["http://127.0.0.1:3000/callback"], "token_endpoint_auth_method": "none" }There is no registration CALL. The URL the document is served from IS the client_id, and the two must match exactly — an authorization server is required to reject a document whose client_id disagrees with its own URL. The URL must be https and carry a path component.
- 02 User to MCP client connect me to this MCP server
TWO INDEPENDENT CHOICES, easy to conflate. How a client gets its identity (CIMD vs DCR vs pre-registration) is separate from which grant it then uses (authorization code vs client credentials) — swapping DCR for CIMD does not turn a machine-to-machine integration into a user-consent one. This diagram shows authorization code because a human is present to consent, which is the common MCP client case; the legacy diagram below happens to show client credentials. Either registration mechanism can front either grant.
- 03 MCP client to MCP server request without a token
The flow starts at the resource, not the authorization server — which is why the client needs no prior knowledge of either.
- 04 MCP server to MCP client 401 + WWW-Authenticate
HTTP/1.1 401 Unauthorized WWW-Authenticate: Bearer resource_metadata="https://mcp.example.com/.well-known/oauth-protected-resource", scope="tools.read"This is where the `resource` value used below comes from: the client fetches that Protected Resource Metadata document (RFC 9728, a MUST for MCP servers) to learn the server's canonical URI and which authorization server to talk to. The advertised `scope` is authoritative for this request — request that, not everything.
- 05 MCP client to Authorization server authorization request
GET /authorize? response_type=code &client_id=https://app.example.com/oauth/client-metadata.json &redirect_uri=http://127.0.0.1:3000/callback &code_challenge=…&code_challenge_method=S256 &scope=tools.read &resource=https%3A%2F%2Fmcp.example.com`scope` is the one the 401 already told you: the challenged scope is authoritative for this request, so ask for exactly it rather than the server's defaults — too little and the original call still fails, too much and least privilege was decorative. `resource` is the one people leave out entirely. MCP requires RFC 8707 resource indicators on BOTH this request and the token request, naming the canonical URI of the MCP server — that is what binds the resulting token to one audience, so a token minted for another server cannot be replayed against this one. Clients must send it whether or not the authorization server supports it. PKCE because this is a public client holding no secret; check the server advertises `client_id_metadata_document_supported: true` first, since CIMD cannot be adopted unilaterally.
- 06 Authorization server to Metadata document fetch the document it was pointed at
The authorization server sees a URL-formatted client_id and resolves it. This replaces the registration table: nothing was stored ahead of time, and nothing needs to be.
- 07 Metadata document to Authorization server client metadata
Cached per the document's HTTP cache headers. That TTL is now part of the security model — it bounds how long revoked or altered metadata is still honoured.
- 08 Authorization server to User consent, naming the client
The consent screen shows `client_name` from a document the client controls. Whoever controls the hostname controls how the client presents itself — the trade CIMD makes in exchange for needing no registration.
- 09 User to Authorization server approve
The authorization server validates the redirect_uri against the list in the fetched document before honouring it.
- 10 Authorization server to MCP client authorization code
302 → http://127.0.0.1:3000/callback?code=…&iss=https://auth.example.com2026-07-28 also asks authorization servers to send `iss` (RFC 9207), and requires clients to validate it against the recorded issuer before redeeming the code.
- 11 MCP client to Authorization server redeem the code
POST /token grant_type=authorization_code&code=…&code_verifier=… &redirect_uri=http://127.0.0.1:3000/callback &client_id=https://app.example.com/oauth/client-metadata.json &resource=https%3A%2F%2Fmcp.example.com`resource` again, identical to the authorization request — omitting it here is the subtler half of the same mistake, and costs the audience binding even though the first request looked correct. `redirect_uri` must be repeated because it was in the authorization request. No client secret: the PKCE verifier is what proves this is the same client that started the flow. A confidential client may instead authenticate with `private_key_jwt` via JWKS.
- 12 Authorization server to MCP client short-lived access token
The identity is portable: because the client_id is a self-hosted URL any authorization server can resolve, moving to a different authorization server needs no re-registration — where DCR credentials are bound to their issuer and must be re-registered.
- 13 MCP client to MCP server tools/call with bearer token
POST /mcp Authorization: Bearer … MCP-Protocol-Version: 2026-07-28 Mcp-Method: tools/call Mcp-Name: <tool> { "jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": { "name": "<tool>", "arguments": {} }, "_meta": { "io.modelcontextprotocol/protocolVersion": "2026-07-28", "io.modelcontextprotocol/clientCapabilities": {} } }The authorization header is the part this diagram is about, but the rest is not optional and is easy to drop when copying: 2026-07-28 is stateless, so EVERY request carries its own protocol version and client capabilities in `_meta`, alongside the `MCP-Protocol-Version` header and the `Mcp-Method` / `Mcp-Name` routing headers. A conforming server can reject the request on those before it ever evaluates the token.
- 14 MCP server to MCP client result, or 403 out of scope
The MCP server verifies the token and decides; the client does not assert its own authority. A refusal is a normal outcome — least privilege only means something if the denial path is exercised.
Below is RFC 7591 Dynamic Client Registration — the shape most deployed tooling still
speaks, and the one I wrote about in January. 2026-07-28 deprecated it;
it remains available for authorization servers that do not support CIMD yet.
The registration contrast is the useful part: DCR registers the client with the authorization server and leaves a row behind, while CIMD has the client publish who it is and lets the server come and look. Read only that difference into it. The second diagram also uses a client-credentials grant while the first uses authorization code, but that is an independent choice — how a client obtains its identity does not determine which grant it then uses, and swapping DCR for CIMD does not turn a machine-to-machine integration into a user-consent one.
- 01 Orchestrator to Authorization server register the client
POST /register { "software_id": "…", "software_version": "…" }Historically RFC 7591 Dynamic Client Registration. Note that MCP revision 2026-07-28 DEPRECATED DCR in favour of Client ID Metadata Documents — new builds should not reach for it, and this step is the one that changes first.
- 02 Authorization server to Orchestrator client credentials
201 Created { "client_id": "…", "client_secret": "…", "registration_access_token": "…" }Two DIFFERENT credentials, and conflating them is a common bug: `client_secret` authenticates the client at the TOKEN endpoint, while `registration_access_token` only manages this registration (RFC 7592) and can never be exchanged for an access token. A public client gets no secret at all — it uses the authorization-code flow with PKCE and does not authenticate itself. `private_key_jwt` is the other option for a CONFIDENTIAL client, replacing the shared secret with a signed assertion.
- 03 Orchestrator to Agent inject client_secret
The agent never mints its own identity — it receives one. That separation is what makes the audit trail meaningful later. The registration access token stays with the orchestrator; the agent has no reason to hold it.
- 04 Agent to Authorization server authenticate and exchange for an access token
POST /token Authorization: Basic base64(client_id:client_secret) grant_type=client_credentials&scope=tools.read &resource=https%3A%2F%2Fmcp.example.com`client_secret_basic` shown for brevity; `private_key_jwt` is the stronger option and avoids a shared secret entirely. Scopes are requested narrowly, per task — not "everything this agent might ever need".
- 05 Authorization server to Agent short-lived access token
Short TTL so a leaked token expires before it is useful, and rotation is routine rather than an incident.
- 06 Agent to MCP server tools/call with bearer token
Authorization: Bearer … { "method": "tools/call", "params": { "name": "…" } } - 07 MCP server to Authorization server verify token and scope
Introspection or local JWKS verification. The MCP server decides; the agent does not assert its own authority.
- 08 MCP server to Agent result, or 403 out of scope
A refusal is a normal outcome, not an error condition — least privilege only means something if the denial path is exercised.
- 09 MCP server to Orchestrator append to the audit trail
Which client_id reached which tool, under which scope, when. This is the question an auditor actually asks, and the reason the identity is per-client rather than shared.
Publicly correcting that January post is the reason the newer one exists: what changed in 2026-07-28, including how to migrate and which field to check on your authorization server first.
Before you connect
Do I need an API key or account?
No. The server is public, unauthenticated, and read-only — it serves data that is already on the site. Point an MCP client at the endpoint and start calling tools.
Does it run any AI or cost money to query?
No inference happens here. The tools answer from a bundled local index, so your client brings its own model and there is no usage-priced retrieval on this public route.
What can it actually do?
Search the blog, read a full post or a case study, list the topic clusters, and query the compiled knowledge base — the same tool registry the Copilot on this site uses, exposed over JSON-RPC 2.0.