Free

Lasciare che Claude renda il mio sito Rails AI-native

Sei mosse concrete per servire lettori umani e agenti AI dallo stesso sito, con checklist pronta da copiare


Negli ultimi quattro giorni io e Claude Code abbiamo trasformato smarts.md da sito di documentazione di smart contract pensato per esseri umani in un sito pensato per agenti AI.

Sembrano la stessa cosa. Non lo sono. Un sito per umani ottimizza leggibilità, ricerca e velocità di caricamento. Un sito per agenti AI deve rispondere a domande diverse:

  • I crawler AI riescono a raggiungerti, o il tuo robots.txt di default li chiude fuori?
  • Un AI può saltare il rendering dell'HTML e prendersi contenuto pulito direttamente?
  • Un AI può invocare i tuoi tool senza dover prima "leggere la documentazione"?
  • Ogni pagina dice all'AI esattamente come referenziarla e come interrogarla?

Ecco le sei cose che ho fatto costruire a Claude. Ognuna è utile da sola e puoi portarla nel tuo sito separatamente.

1. Accogliere esplicitamente i crawler AI in robots.txt

La maggior parte dei siti eredita un robots.txt da qualche vecchio template che implicitamente blocca qualunque User-Agent non riconosca. Ma dopo il 2025, molti crawler AI sono User-Agent nuovi — GPTBot, ClaudeBot, PerplexityBot, Applebot-Extended, Google-Extended, CCBot, meta-externalagent e altri. Se li vuoi dentro, devi autorizzarli esplicitamente.

Il robots.txt di smarts si apre con una nota di policy e poi elenca User-agent + Allow: / per 12 crawler che coprono OpenAI, Anthropic, Perplexity, Google Gemini, Apple, Common Crawl, Meta e Cohere. La posizione è netta: questo sito esiste per essere letto da AI.

2. llms.txt — un menù per gli LLM

llms.txt è una convenzione proposta da llmstxt.org: metti un Markdown sintetico nella radice del sito che dice all'LLM cos'è questo sito, come si usa, dove sono i punti di ingresso chiave. È l'elevator pitch del tuo sito.

Il llms.txt di smarts fa circa 60 righe:

  • Posizionamento in una frase (Live docs for every verified smart contract)
  • Come usarlo da un agente AI (endpoint MCP, comando di setup in una riga)
  • Pattern URL (/{chain}/{address} vs slug corto)
  • Tool MCP con descrizioni in una frase
  • Lista curata di contratti (per dare all'LLM "risposte di default")
  • Scope e limiti (quali chain non sono supportate, cache policy)

L'intuizione: gli LLM pagano in token per leggere la tua documentazione, quindi non fanno crawl dell'intero sito. Consegna loro un indice distillato e il tasso di successo schizza.

3. .well-known/mcp.json — scommettere in anticipo

Il protocollo MCP non ha ancora una convenzione di discovery standardizzata tipo /.well-known/openid-configuration. Oggi nessun client pubblico fa crawl di un percorso well-known fisso.

Ma pubblicare il manifest costa 30 righe:

def well_known_mcp
  response.set_header("Cache-Control", "public, max-age=3600")
  response.set_header("Access-Control-Allow-Origin", "*")
  render json: {
    name: "smarts",
    version: "0.1.0",
    description: "...",
    protocol_version: "2024-11-05",
    transports: [{ type: "sse", endpoint: "https://smarts.md/mcp/sse" }],
    capabilities: { tools: true, resources: false, prompts: false },
    tools: MCP_TOOLS.map { |t| { name: t[:name], description: t[:blurb] } }
  }
end

Nel momento in cui emerge una convenzione di discovery, non cambio una riga. E intanto è auto-documentazione per gli sviluppatori — chiunque curioso del mio MCP fa semplicemente curl smarts.md/.well-known/mcp.json.

Il trucco: l'array tools è derivato dalla costante MCP_TOOLS, single source of truth. Un test strutturale esistente forza ogni app/tools/*_tool.rb a comparire in quella costante, quindi il manifest eredita gratis la garanzia di "nessun tool nuovo dimenticato".

4. Una variante .md: distillazione WebFetch-friendly per ogni pagina

La mia preferita. Il tool WebFetch di Claude Code deve ripulire ed estrarre contenuto da HTML arbitrario — costoso in token e inaffidabile. E se gli servi direttamente .md?

Il respond_to di Rails lo supporta nativamente:

# config/routes.rb
get ":slug(.:format)", to: "contracts#show",
    constraints: { slug: ContractSlugs::ROUTE_PATTERN, format: /html|md/ }

https://smarts.md/usdc-eth restituisce HTML; https://smarts.md/usdc-eth.md restituisce 40 righe di Markdown — indirizzo, chain, classificazione, endpoint MCP, come interrogare via agente AI, link sorgente.

# USD Coin on Ethereum

- **Address:** `0xa0b8...`
- **Chain:** Ethereum
- **Classification:** ERC-20 Token

## Query via AI agent

- **MCP endpoint:** `https://smarts.md/mcp/sse`
- **Reference:** `usdc-eth`
- **Sample prompt:** "Tell me the current state of usdc-eth"

Un fetch, tutto il contenuto strutturato che un agente AI vuole, nessun parser DOM. Claude ha spedito questa parte in fretta — un controller, due template .md.erb, (.:format) nelle routes, fatto.

5. Una card MCP su ogni pagina: fai puntare agli utenti la loro AI qui

Scoperta in direzione opposta: una parte dei miei visitatori usa già Claude Code / Cursor / Windsurf. Arrivano su una pagina contratto e la mossa successiva è di solito "faccio dare un'occhiata alla mia AI".

Quindi ogni pagina contratto renderizza una card con:

  • Reference: lo slug curato (es. usdc-eth) oppure chain/address, con pulsante Copia
  • Sample prompt: «Tell me the current state of usdc-eth», con pulsante Copia
  • Non hai ancora un'AI collegata?: puntatore a mcp.smarts.md (setup in una riga)

Un click di attrito tra "sto guardando questo contratto" e "sto chiedendo alla mia AI". La card usa una card DaisyUI e un controller Stimulus copy generico — 50 righe di erb. Un test di regressione blocca gli attributi data-copy-text-value affinché un refactor futuro non incolli silenziosamente la stringa sbagliata negli appunti degli utenti.

6. SEO classico, ma schema.org fatto come si deve

OpenGraph, Twitter Card, JSON-LD — lo strato tradizionale di ricerca e social card. Vale la pena, perché anche i crawler AI leggono qui.

La scelta interessante è come JSON-LD debba descrivere uno smart contract. Claude ha optato per un WebPage che incapsula un SoftwareApplication:

{
  "@type": "WebPage",
  "about": {
    "@type": "SoftwareApplication",
    "name": "USD Coin",
    "applicationCategory": "SmartContract",
    "operatingSystem": "Ethereum",
    "identifier": "0xa0b8..."
  }
}

operatingSystem prende il nome della chain, identifier l'indirizzo, additionalType la classificazione (es. «Uniswap V3 Pool»). schema.org non ha un tipo dedicato SmartContract, ma SoftwareApplication con questi campi è sufficiente per far capire a un LLM "questo è un contratto su Ethereum con un indirizzo preciso".

L'immagine OG è un summary_large_image 1200×630; Twitter Card, breadcrumb, softwareVersion e license completano il quadro.

Quanto lavoro è tutto questo

I sei punti sopra sono usciti da sette PR in quattro giorni:

PR File Dimensione
feat/ai-crawler-discovery 2 ~126 loc
feat/well-known-mcp-manifest 4 ~129 loc
feat/markdown-contract-pages 6 ~298 loc
feat/contract-mcp-card 3 ~126 loc
feat/seo-meta-tags 8 ~292 loc
feat/seo-enrichments 8 ~189 loc
feat/og-card

Ognuno è un PR indipendente con test e commit message propri. Questa cadenza di taglio fine è la cosa più preziosa nel lavorare con Claude — ogni PR è abbastanza piccolo da recensirne il diff in un'occhiata, ma impilati insieme il sito è cresciuto in una superficie AI completa.

Una checklist da copiare

Se hai un sito di contenuti e vuoi renderlo AI-friendly, procedi in quest'ordine:

  1. Autorizza esplicitamente i principali crawler AI in robots.txt
  2. Scrivi un /llms.txt (un menù distillato)
  3. Aggiungi una variante .md alle tue pagine di contenuto principali
  4. Se hai un server MCP, pubblica /.well-known/mcp.json
  5. Metti una card "chiedi alla tua AI di questa pagina" sulle pagine di contenuto
  6. Riempi JSON-LD / OpenGraph con i tipi schema.org che combaciano con il contenuto

I primi cinque non te li dice la maggior parte dei SEO engineer — non sono nel playbook classico. Ma con quattro giorni di dati dalla mia parte, la curva del traffico AI e quella del traffico umano sono due serie completamente indipendenti. Bisogna servire entrambe.