Questa è la chiamata di creazione che una connessione Claude riceve per le Notes di osFoundry, ottenuta con un tools/list non autenticato sull'endpoint MCP pubblico il 2026-11-09 e accorciata:
{"name": "create_note_tab",
"description": "Create a new Slides or Sheet tab in an approved Note. Use the version from list_note_tabs; returns the exact new tab ID for later edits. An optional outline fills a Slides tab. A repeated idempotency key returns the first tab.",
"inputSchema": {
"required": ["note_id", "expected_version", "idempotency_key", "tab_type"],
"properties": {
"tab_type": {"enum": ["slides", "sheet"]},
"label": {"type": "string"},
"slides": {"type": "array", "minItems": 1, "maxItems": 20,
"items": {"required": ["title"], "additionalProperties": false,
"properties": {"title": {}, "body": {}, "notes": {}}}}},
"additionalProperties": false},
"annotations": {"readOnlyHint": false, "destructiveHint": true},
"securitySchemes": [{"type": "oauth2", "scopes": ["osfoundry:write"]}]}
I suoi due vicini per questo lavoro:
{"name": "add_note_source",
"description": "Add an HTTP or HTTPS citation to the Sources tab of an approved Note. Uses the Note version and an idempotency key; returns the new source ID.",
"inputSchema": {"required": ["note_id", "expected_version", "idempotency_key", "title", "url"]}}
{"name": "list_note_tabs",
"description": "List every tab instance and its type. A Note may have multiple Slides or Sheet tabs; use the exact tab ID returned here."}
Tre frasi reggono il design: "every tab instance", "returns the exact new tab ID" e "a repeated idempotency key returns the first". Il resto del post ricostruisce gli strumenti in Python e misura cosa evita ciascuna di queste frasi. Il locking ottimistico sulla versione della Note è l'argomento di un altro post e qui compare solo dove interagisce con le altre due.
Cosa può mettere davvero Claude su una slide?
Meno dell'assistente integrato nell'app desktop. Dentro osFoundry, slide_create_deck_from_outline e slide_add_slide dell'assistente delle Notes (in apps/web/src/screens/notes/lib/note-surface-tools.ts) accettano layout, elenchi puntati, contenuti a due colonne, tabelle, immagini e uno di cinque temi. L'elemento di outline del connettore è title, body, notes, con additionalProperties: false. edit_note_tab offre slide_add per una slide e slides_add_outline per un array, entrambi con gli stessi tre campi.
code/note_store.py carica la risposta tools/list salvata e controlla i payload con un validatore minimo (required, enum, additionalProperties, minItems/maxItems). Otto payload che un modello potrebbe benissimo inviare:
edit_note_tab {"operation": "slide_add", "title": "Sources", "body": "3 links"} -> ok
edit_note_tab {"operation": "slide_add", "title": "Sources", "index": 0} -> unexpected index
edit_note_tab {"operation": "slides_add", "slides": [{"title": "a"}]} -> operation: not in enum
edit_note_tab {"operation": "slides_add_outline", "slides": [{... "bullets"}]} -> slides: [0] unexpected bullets
create_note_tab {"tab_type": "slides", "slides": "[21 items]"} -> slides: maxItems 20
create_note_tab {"tab_type": "markdown"} -> tab_type: not in enum
add_note_source {"url": "https://example.org/report"} -> missing title
list_note_attachments {"kind": "sources", "limit": 100} -> ok
L'ultimo passa questo validatore, ma non lo schema reale completo: limit ha maximum: 50, che la ricostruzione non controlla. slide_add non può scegliere una posizione, create_note_tab non può creare una scheda Markdown e un deck con più di 20 slide richiede più di una chiamata. Uno schema stretto è una scelta su chi dà lo stile al deck: il connettore scrive il contenuto, una persona nell'app desktop applica temi e layout.
Perché un ID di scheda esatto e non "la scheda slides"?
Perché l'app desktop stessa accetta ancora la scorciatoia, e la scorciatoia prende la prima corrispondenza. resolveTabInstanceId() in apps/web/src/screens/notes/types.ts:
if (cfg.some((t) => t.id === rawTabId)) return rawTabId
const byType = cfg.find((t) => t.type === rawTabId)
if (byType) return byType.id
Quel fallback tiene in vita vecchi link come …/slides. Per un agente significa che "modifica le slide" finisce nella scheda Slides che si trova per prima nella barra. Le nuove schede ricevono ID come slides-9-mh8z1k2a da nextTabId() in NotesContentPanel.tsx, quelle fisse sono markdown, files e sources, e una persona può aggiungere tutte le schede Slides che vuole, trascinarle in un nuovo ordine e rinominarle, anche con nomi duplicati (handleRename fa solo il trim).
La ricostruzione dà a una Note K schede Slides con etichette diverse. Un agente scriptato elenca le schede, sceglie un deck come obiettivo e tiene un riferimento a esso: la sua posizione nella barra, il suo tipo, la sua etichetta o il suo ID. Tra un turno e l'altro dell'agente, la persona fa da zero a due modifiche casuali (spostare una scheda 40%, rinominare una scheda Slides con una delle cinque etichette 35%, aggiungere una scheda Slides 25%). Poi l'agente rielenca le schede e risolve il suo riferimento. 10,000 prove per K, seed 7:
| K deck | riferimento | corretto | deck sbagliato | non trovato |
|---|---|---|---|---|
| 2 | posizione | 83.7% | 16.3% | 0.0% |
| 2 | tipo | 50.7% | 49.3% | 0.0% |
| 2 | etichetta | 85.6% | 2.1% | 12.3% |
| 2 | ID | 100% | 0 | 0 |
| 5 | posizione | 82.7% | 17.3% | 0.0% |
| 5 | tipo | 19.7% | 80.3% | 0.0% |
| 5 | etichetta | 92.1% | 3.0% | 5.0% |
| 5 | ID | 100% | 0 | 0 |
Il riferimento per tipo è giusto 1/K delle volte, come ci si aspetta. La posizione fallisce ogni volta che un trascinamento attraversa l'obiettivo, indipendentemente da K. L'etichetta è quella allettante, perché è ciò che la persona scrive nel prompt, ed è giusta la maggior parte delle volte. Il 2–3% in cui sbaglia sono i casi brutti: un altro deck ora porta quell'etichetta e l'agente ci scrive dentro senza alcun errore. "Non trovato" è il fallimento sicuro. Un ID viene assegnato una volta e mai riutilizzato, quindi sopravvive a ogni modifica che la persona può fare nella barra delle schede.
Il controllo della versione non intercetta già una scheda sbagliata?
No. Ognuna delle modifiche alla barra delle schede viste sopra incrementa la versione della Note, quindi una scrittura obsoleta viene respinta e l'agente rielenca. Il nuovo elenco gli dà una versione aggiornata, e poi risolve il suo vecchio riferimento sulla nuova lista. Nella ricostruzione, il 100% di queste scritture supera il controllo della versione, comprese tutte quelle dirette al deck sbagliato. La versione dimostra che l'agente ha visto la Note più recente. Non dice nulla sul fatto che ci abbia trovato la scheda giusta. Di questo si occupa l'ID della scheda.
Dove va il controllo di idempotenza, prima o dopo quello della versione?
Prima, altrimenti la promessa nella descrizione dello strumento salta. Si esegue create_note_tab 50 volte per run con il 30% delle risposte perse dopo il commit del server, che è come appare un timeout dell'host dal lato del client. Il client rimanda la stessa chiamata. Tre policy del client, due ordini del server, seed 1–3:
| il server controlla | policy della chiave del client | schede create (chiamate inviate) |
|---|---|---|
| chiave, poi versione | chiave nuova a ogni chiamata | 63 (76) / 75 (100) / 62 (74) |
| chiave, poi versione | stessa chiave al retry | 50 (67) / 50 (81) / 50 (62) |
| chiave, poi versione | chiave nuova dopo un conflitto | 50 (67) / 50 (81) / 50 (62) |
| versione, poi chiave | chiave nuova a ogni chiamata | 63 (76) / 75 (100) / 62 (74) |
| versione, poi chiave | stessa chiave al retry | 50 (79) / 50 (98) / 50 (72) |
| versione, poi chiave | chiave nuova dopo un conflitto | 72 (94) / 73 (96) / 61 (72) |
Il nuovo invio porta la versione di prima del commit perso. Un server che controlla prima la versione vede una versione obsoleta e restituisce un conflitto invece del primo risultato. Un client che reagisce ai conflitti nel modo sensato, rileggendo e trattando il tentativo successivo come una nuova intenzione con una nuova chiave, crea così un secondo deck. Perfino un client che tiene la sua chiave invia il 16–21% di chiamate in più. Con la chiave controllata per prima, il nuovo invio restituisce l'ID di scheda originale, qualunque cosa dica la versione. È questo che deve significare "a repeated idempotency key returns the first tab".
Quale chiave mettere su add_note_source?
La ricostruzione esegue 300 sessioni di ricerca. Ognuna ha tre turni, ogni turno cita 6 pagine su 8, e una pagina può tornare in una qualsiasi di sette grafie: con lo slash finale, con www., con ?utm_source=…, con un #fragment, con http:// o senza schema. Il 20% delle risposte si perde e viene rimandato con la stessa chiave.
| chiave | fonti salvate | duplicati | per sessione |
|---|---|---|---|
| UUID nuovo per citazione, riusato al reinvio | 5,400 | 3,038 | 10.13 |
| hash dell'URL così com'è scritto | 4,814 | 2,452 | 8.17 |
| hash dell'URL normalizzato e dell'ID della Note | 2,362 | 0 | 0.00 |
Una chiave nuova per citazione gestisce le risposte perse (nessuna voce di quella prima riga viene da un reinvio) e nient'altro. Una ricerca cita di nuovo la stessa pagina al turno successivo, e ogni nuovo turno è una nuova chiave. L'hash dell'URL così com'è scritto intercetta le ripetizioni esatte, ma non ?utm_source= né uno slash finale. La chiave che elimina i duplicati dà un nome all'intenzione, "questa pagina è una fonte di questa Note", facendo l'hash di note_id più un URL normalizzato (https, host in minuscolo senza www., senza fragment, senza utm_*, senza slash finale).
Il modulo Sources dell'app desktop fa una versione più leggera di questo. NotesSourcesTab.tsx normalizza il link digitato con new URL(...) e rifiuta quello già allegato ("This source is already attached"). Nella ricostruzione, confrontare gli href come fa un browser avrebbe segnalato 725 dei 3,038 duplicati della prima riga. L'add_note_source del connettore non dichiara alcun controllo sull'URL, quindi è nella chiave che l'agente lo esprime. Una chiave derivata dall'URL ha un effetto collaterale: aggiungere di nuovo la stessa pagina con una descrizione migliore restituisce la prima riga. Modificarla è compito della scheda Sources.
Una sola creazione con outline o una slide alla volta?
Costo di un deck da 12 slide, con ogni richiesta validata sullo schema reale:
| approccio | scritture | byte della richiesta | conflitti per deck |
|---|---|---|---|
create_note_tab con slides
|
1 | 1,673 | 0.070 |
creazione vuota + 12 × slide_add
|
13 | 5,237 | 0.817 |
La colonna dei conflitti è un modello, non una misura di osFoundry: 4 s tra la lettura dell'agente e la sua scrittura, una persona che salva da qualche parte nella Note in media una volta al minuto (esponenziale), 2,000 deck simulati. Ogni scrittura apre una nuova finestra in cui la versione a livello di Note può cambiare, quindi costruire slide per slide richiede circa dodici volte più riletture. Metti il deck nell'outline (fino a 20 slide) e usa slide_add per la slide che scopri solo dopo.
La paginazione è l'altro numero da conoscere. list_note_attachments limita limit a 50, quindi una Note con 120 fonti richiede 3 chiamate per essere elencata.
Come è stata eseguita la ricostruzione
Su questa macchina non gira nessun binario di Claude. code/run.py fa la parte dell'agente e chiama direttamente code/note_store.py. Lo store è in memoria dietro un unico lock, con un contatore come versione. Nomi degli strumenti, campi obbligatori, enum e limiti degli elementi vengono dallo schema reale in code/tools_list.raw, e gli ID delle schede seguono nextTabId(). La forma degli errori, l'ambito delle chiavi (qui per Note) e l'ordine di idempotenza sono scelte della ricostruzione, e il secondo ordine esiste solo per mostrare cosa va storto. Python 3.9.6 su Apple Silicon, tutti e cinque gli esperimenti in 1.0 s di tempo reale. L'output è in code/run.out.
Se lo colleghi tu
- Restituisci un ID per istanza in ogni chiamata di elenco e di creazione, e rendilo obbligatorio per gli strumenti di modifica. Non risolvere tipi o etichette nel percorso di scrittura, anche se la tua UI li accetta ancora negli URL.
- Controlla la idempotency key prima della versione. Un reinvio dopo una risposta persa porta sempre la versione vecchia.
- Spiega agli agenti nella descrizione dello strumento cosa deve identificare una chiave. Per le citazioni, "questo URL in questo documento", normalizzato, elimina le ripetizioni tra un turno e l'altro. Un UUID casuale copre solo i reinvii.
- Tieni piccola la forma della slide del connettore (title, body, notes) e lascia il design alla persona nell'editor. Uno schema piccolo vuol dire meno campi che un modello può sbagliare.
- Lascia che gli agenti inviino un outline intero in una sola chiamata e mettici un tetto (osFoundry usa 20). Meno scritture vuol dire meno finestre di conflitto su un documento condiviso.
- Restituisci tramite il canale dell'agente i metadati degli allegati, mai il contenuto o link firmati. Un link sopravvive alla sessione che lo ha ottenuto.
Cosa contiene la scheda Sources dal lato della persona è spiegato nella pagina della scheda Sources. Il protocollo sottostante è riassunto nella voce del glossario su MCP di osFoundry.
Top comments (0)