---
title: "Guida per agenti: Espresso Code: Italian Dev Insights"
lang: "it"
translations:
  en: "https://blog.sf-paris.dev/agents.md"
---

# Guida per agenti: Espresso Code: Italian Dev Insights

Come leggere Espresso Code: Italian Dev Insights (https://blog.sf-paris.dev/) da un programma e, con un account, preparare bozze. Il blog è bilingue (lingue: en, it); ogni articolo può avere la traduzione nell'altra lingua. Questa guida non contiene credenziali: l'accesso in scrittura lo dà il titolare del blog.

## Leggere senza account

- **Markdown di ogni articolo**: l'indirizzo dell'articolo con `.md` al posto della barra finale. Esempio: https://blog.sf-paris.dev/en/temu-shein-3-euro-duty-2-euro-fee.md (pagina HTML: https://blog.sf-paris.dev/en/temu-shein-3-euro-duty-2-euro-fee/). Le pagine statiche funzionano allo stesso modo; la home di una lingua con pagina statica è `/index.md` o `/it/index.md`.
  - Stesso contenuto anche con `?format=markdown` sull'indirizzo HTML, o con l'header `Accept: text/markdown` (le risposte HTML dichiarano `Vary: Accept`).
  - Risposta `text/markdown; charset=utf-8` con `Link: <pagina HTML>; rel="canonical"`, `X-Robots-Tag: noindex` (il documento canonico è l'HTML), `ETag` e `Last-Modified` per le richieste condizionali (304).
  - In testa un front matter YAML: `title`, `url`, `lang`, `translations` (lingua: URL), `author`, `published`, `updated`, `categories`, `tags`, `summary`, `reading_time_minutes`, `word_count`. Poi il titolo e il testo: titoli, liste, codice con il linguaggio, tabelle GFM, immagini con testo alternativo, FAQ come `<details><summary>`.
  - Bozze, contenuti protetti da password e indirizzi inesistenti rispondono 404.
- **Elenco completo**: https://blog.sf-paris.dev/llms-full.txt (articoli pubblicati per lingua: titolo, URL `.md`, data, riassunto; mai bozze né contenuti noindex). Indice breve: https://blog.sf-paris.dev/llms.txt.
- **Feed RSS per lingua**:
  - `en`: https://blog.sf-paris.dev/en/feed/
  - `it`: https://blog.sf-paris.dev/feed/
- **Sitemap**: https://blog.sf-paris.dev/sitemap.xml
- **REST API di WordPress** (sola lettura pubblica): https://blog.sf-paris.dev/wp-json/wp/v2/posts; la lingua si sceglie con `?lang=it` o `?lang=en`. Ogni articolo indica il suo JSON in `<link rel="alternate" type="application/json">`.
- Ogni pagina di articolo ha anche `<link rel="alternate" type="text/markdown">` nell'head.

## Abilities (con account)

Le operazioni sono abilities dell'Abilities API di WordPress, categoria `espresso`. Tutte richiedono un utente WordPress autenticato; ognuna controlla le capability dell'utente.

| Ability | Metodo REST | Permesso | Cosa fa |
| --- | --- | --- | --- |
| `espresso/list-posts` | GET | `read` (+ `edit_posts` for unpublished) | List posts or pages with filters (lang, status, category, search, paging). |
| `espresso/get-post` | GET | `read_post` | One post by id or URL: metadata, Markdown (or block) content, SEO fields, translations. |
| `espresso/seo-audit` | GET | `edit_posts` (published) / `edit_post` | Read-only SEO/GEO checks with score and concrete fixes. |
| `espresso/create-draft` | POST | `edit_posts` | Create a draft (block markup or Markdown); never publishes; idempotency key. |
| `espresso/update-post` | POST | `edit_post` (+ `publish_posts` beyond draft/pending) | Update fields of a post you can edit; revision first; `dry_run` returns the diff. |
| `espresso/link-translation` | POST | `edit_post` on both posts | Link two posts as Polylang translations (guarded, idempotent). |

- Elenco e schemi JSON: `GET https://blog.sf-paris.dev/wp-json/wp-abilities/v1/abilities?category=espresso` (ogni ability ha `input_schema` e `output_schema`).
- Esecuzione: `GET https://blog.sf-paris.dev/wp-json/wp-abilities/v1/abilities/espresso/list-posts/run?input[lang]=it&input[per_page]=5` per quelle in sola lettura; `POST https://blog.sf-paris.dev/wp-json/wp-abilities/v1/abilities/espresso/create-draft/run` con corpo JSON `{"input": {"title": "…", "content": "## Titolo\n\nTesto in Markdown…", "lang": "it", "idempotency_key": "pipeline-2026-10-31-01"}}` per le altre.
- I campi SEO (titolo, descrizione, parola chiave) si leggono con `espresso/get-post` e si scrivono con l'ability di AIOSEO `aioseo-posts/seo-data-update`.
- Regole: gli agenti creano **bozze**; pubblica una persona. `espresso/update-post` salva una revisione prima di scrivere, cambia lo stato solo bozza ⇄ in revisione senza `publish_posts`, non cancella mai; con `"dry_run": true` restituisce le differenze senza salvare (né creare tag) e con `expected_modified_gmt` non sovrascrive modifiche altrui (409). Le traduzioni: bozza nell'altra lingua, poi `espresso/link-translation`.
- Errori: `WP_Error` con codice (`espresso_not_found` 404, `espresso_forbidden` 403, `espresso_conflict` 409, `espresso_invalid` 400, `ability_invalid_input` 400 per l'input fuori schema) e messaggio.

## MCP

Quando sul sito è attivo il plugin ufficiale MCP Adapter, le abilities marcate `meta.mcp.public` (sola lettura e `espresso/create-draft`) sono raggiungibili dal server MCP predefinito: https://blog.sf-paris.dev/wp-json/mcp/mcp-adapter-default-server (Streamable HTTP; strumenti `mcp-adapter-discover-abilities`, `mcp-adapter-get-ability-info`, `mcp-adapter-execute-ability`). Le altre restano solo via REST. Se l'indirizzo risponde 404 l'adapter non è ancora installato.

## Statistiche

Quando la misura è attiva (le pagine caricano `misura.js` del plugin), il blog conta le visite con Umami self-hosted, dal suo dominio (`/_s/script.js`, `/_s/api/send`): niente cookie, nessuna terza parte, IP accorciato prima di Umami, che non lo salva. Non si misurano gli utenti collegati (anche gli agenti con un account), le anteprime, i feed, la REST e le risposte per gli agenti (`.md`, `?format=markdown`, `Accept: text/markdown`, llms-full.txt, questa guida); né chi ha chiesto di non essere misurato (`umami.disabled` in localStorage) o manda Global Privacy Control o Do Not Track.

- **Viste**: URL senza query né `#`; il tag di Umami è il formato per gli articoli (`ristretto`, `espresso`, `lungo`, `doppio`, `moka`, dai minuti di lettura) e il tipo per le altre pagine (`home`, `archivio`, `pagina`; `articolo` per un articolo protetto da password). Ricerca e 404 non sono viste ma eventi; nessun URL porta la query, il `#` o il termine cercato (`/search/<termine>/` diventa `/search/`).
- **Eventi**: `letto` (fine del testo vista e almeno 15 s di pagina visibile, una volta, non sugli articoli protetti; con `secondi`), `commento`, `copia-codice` (`linguaggio`), `copia-link`, `link-esterno` (`host`), `lingua` (`da`, `verso`), `ricerca` (`lingua`, `risultati`, `termine` solo se breve e senza l'aria di un dato personale, altrimenti `escluso`), `404` (`lingua`, `path`).
- **Proprietà della pagina** (con `letto` e `commento`): `tipo` (`articolo`, `home`, `archivio`, `pagina`, `ricerca`, `404`), `lingua`, e per gli articoli `formato`, `minuti`, `categoria`, `mesi` (dalla pubblicazione).
- **Leggere i dati**: dall'MCP di Umami, `https://stats.sf-paris.dev/api/mcp`, con una chiave API data dal titolare; risponde solo dalla rete autorizzata. Su WordPress non c'è un'ability per le statistiche.

## Autenticazione

- **Password per applicazioni** di WordPress, solo via HTTPS: header `Authorization: Basic base64(utente:password-applicazione)`. Una password per agente o servizio, con un nome che dica chi la usa; si revoca dal profilo utente.
- **Utente dedicato con ruolo minimo**: per leggere e preparare bozze basta un ruolo con `read` ed `edit_posts` (Collaboratore o un ruolo su misura). Mai un amministratore: la password per applicazioni eredita tutte le capability dell'utente.
- **Two-Factor**: per gli utenti con la verifica in due passaggi il plugin Two-Factor blocca il login via API con la password normale; le password per applicazioni restano valide (dalla versione 0.17). Il comportamento si cambia solo con il filtro `two_factor_user_api_login_enable`.
- I token dell'API editoriale interna del blog sono un'altra cosa e qui non valgono.

English version: https://blog.sf-paris.dev/agents.md
