All work WEB PLATFORM / AGENT INFRASTRUCTURE in progress

29  /  WEB PLATFORM / AGENT INFRASTRUCTURE

Minto.Studio

Agents sent to evaluate an agency site can only scrape and guess, so my own studio's site answers over MCP.

April – July 2026Next.js 14 (App Router)TypeScriptModel Context ProtocolJSON-RPC 2.0OAuth 2.0 client credentialsRS256 JWT / JWKSWebMCP (navigator.modelContext)Resend

The challenge

Agency websites are written for human eyes. An agent sent to evaluate one scrapes marketing HTML and guesses. Minto is my own studio, so I built the site to demonstrate the argument rather than make it. Then an audit I ran across my own build came back with something worse than a styling note: I had computed the per-industry metrics on thirty pages from a position in an alphabetical list, and my contact form was dropping every enquiry into a console log while the interface promised a reply.

What I built

I built a Next.js App Router site with a second, machine-readable surface running alongside the pages. Page routes on an allowlist answer Accept: text/markdown with clean markdown instead of HTML, and /mcp is a real JSON-RPC 2.0 MCP endpoint exposing three tools and three resources over the service catalogue, gated by an OAuth 2.0 client-credentials server I wrote by hand on Node crypto. A .well-known suite carries the MCP server card, an RFC 9727 api-catalog linkset, an OpenAPI 3.1 description and four hosted agent skills with sha256 digests. In the July pass I acted on the audit: I deleted the index-derived metrics and replaced them with plain words behind an explicit disclaimer, and wired the contact form to Resend with graceful degradation when no key is present.

How it works

NO TOKEN REFUSED TOKEN EXCHANGE Agent calls /mcpan unauthenticated call 401, with directionsnames the auth server Token endpointhand-built on Node crypto RS256 JWTissuer, audience, expiry No key, no token503, never an unsigned one comes back authorised: three tools, three resources
The mechanism, drawn from the build.

What it looks like

Minto.Studio — screenshot
Running locallyWEB PLATFORM / AGENT INFRASTRUCTURE

The detail

01

Markdown on request

Middleware I wrote inspects the Accept header on an allowlist of page routes and rewrites matching requests to a markdown renderer, returning text/markdown with Vary: Accept and an x-markdown-tokens length header. The homepage answers in 573 words of structured markdown instead of 158 KB of HTML. I have not finished per-page negotiation: the rewrite loses its __pathname parameter at runtime, so every route currently returns the site-overview document, even though my renderer produces the correct page when called directly.

02

The MCP endpoint

On /mcp I implemented initialize, ping, tools/list, tools/call, resources/list and resources/read over JSON-RPC, with notification handling, an origin allowlist and CORS. Tools carry input schemas and return both text content and structuredContent. An unauthenticated call gets a 401 whose WWW-Authenticate header names the authorization server and the protected-resource metadata, so an agent can bootstrap itself from a single failed request.

03

OAuth by hand

No auth library. I wrote lib/oauth.ts to build and verify RS256 JWTs on Node crypto directly: base64url header and payload, RSA-SHA256 signing, a public JWK exported for /.well-known/jwks.json, issuer, audience and expiry all checked on the way back in, scopes intersected against the configured allowlist. With no signing key configured the token endpoint returns 503 with a stated reason rather than minting unsigned tokens, and the server card reports its own status as pending_credentials.

04

In-browser tools

A client component I wrote declares navigation tools on navigator.modelContext, so an agent already driving the browser can jump to a service or case study without guessing URLs. I generate the service and case-study enums from the same data module the pages render from, which means a route that does not exist cannot be offered as a tool argument. The industry enum is still a list I typed by hand and does not carry that guarantee.

05

Removing the numbers

The audit found that the per-industry metrics across thirty pages were literally `42 + offset`%, where offset was the industry's position in an alphabetical list. Rather than sourcing better numbers I deleted them; my builder now returns words such as "Less manual workload" behind an explicit illustrative-outcomes disclaimer. The fix is half done. Eight case-study figures I wrote by hand are still on the site, and they are the next thing to go.

The outcome

The site builds clean and runs locally: six service pages, thirty programmatic service×industry pages, eight case-study routes, and the full .well-known discovery suite. I checked it against a local production build: the MCP server card, the api-catalog linkset, the OpenAPI 3.1 description, the agent-skills index with its sha256 digests and the 401 challenge on /mcp all respond correctly. Markdown negotiation returns markdown, but not yet the right page's markdown. I have not deployed it. The minto.studio domain does not resolve and the repository carries no deployment configuration.