Torna alla documentazione dell'API

Il server MCP di GroceryAI

Collega un agente AI all'ambiente di prova del tuo negozio: l'indirizzo, la chiave di prova, i comandi per Claude Code, Claude Desktop e Cursor.

Questa pagina in Markdown

Un server Model Context Protocol remoto con cui un agente AI (Claude Code, Claude Desktop o claude.ai, Cursor, o qualunque client MCP) legge l'ambiente di prova del tuo negozio: quanti prodotti hai, i prodotti, lo stato del catalogo, gli ordini di prova da prendere in carico, i punti vendita. Non scrive mai. I numeri vengono dallo stesso codice che serve l'API REST (GET /v1/...): l'agente e il tuo codice vedono sempre la stessa cosa.

Versione inglese: mcp.en.md.

L'indirizzo

https://oxjqgjbiltoviwldailg.supabase.co/functions/v1/core-mcp

  • Trasporto: Streamable HTTP, una POST per ogni richiesta JSON-RPC, risposte JSON. Nessuna sessione, nessuno stream SSE (GET e DELETE rispondono 405).
  • Versioni del protocollo: 2026-07-28 (la revisione corrente: senza stato, server/discover, le intestazioni MCP-Protocol-Version, Mcp-Method e Mcp-Name) e le precedenti 2025-11-25, 2025-06-18, 2025-03-26 (con la stretta di mano initialize). Il client sceglie da solo; i client di oggi parlano per lo piu' 2025-11-25.
  • I metadati della risorsa protetta (RFC 9728), senza chiave: GET https://oxjqgjbiltoviwldailg.supabase.co/functions/v1/core-mcp/.well-known/oauth-protected-resource. Una richiesta senza chiave riceve 401 con WWW-Authenticate: Bearer resource_metadata="…", scope="catalog:read orders:read".

La chiave

Si usa una chiave segreta di prova del negozio, gai_test_…, mandata come Authorization: Bearer gai_test_… in ogni richiesta. Si crea nel portale, in Sviluppatori, con gli ambiti degli strumenti che servono:

ambitostrumenti
catalog:readcount_products, list_products, get_catalog_status, list_stores
orders:readlist_orders
  • Le chiavi live (gai_live_…) si rifiutano (CHIAVE_LIVE_NON_AMMESSA): questo server serve solo l'ambiente di prova. Per i dati veri si usa l'API REST dal proprio codice.
  • Le chiavi pubblicabili (gai_pk_…) si rifiutano (CHIAVE_PUBBLICABILE_NON_AMMESSA): non hanno ambiti.
  • Uno strumento di cui la chiave non ha l'ambito risponde 403 con WWW-Authenticate: Bearer error="insufficient_scope", scope="…" (AMBITO_MANCANTE). Gli ambiti non si sottintendono: catalog:write non contiene catalog:read.
  • La chiave sta in una variabile d'ambiente (qui GROCERYAI_TEST_KEY), mai in un file che si committa. Quando non serve piu' la si revoca dal portale.

L'accesso con OAuth non c'e' ancora: la chiave e' un gettone che hai gia', come la chiave «agente» di Stripe. Per questo i metadati non elencano authorization_servers: un client che non trova la chiave non avvia un flusso OAuth, riporta il 401.

Aggiungerlo al tuo agente

Claude Code (un comando; la shell mette nella configurazione la chiave presa dal tuo ambiente):

claude mcp add --transport http groceryai https://oxjqgjbiltoviwldailg.supabase.co/functions/v1/core-mcp \
  --header "Authorization: Bearer $GROCERYAI_TEST_KEY"

Per condividerlo con un progetto senza scrivere la chiave da nessuna parte, in .mcp.json alla radice del progetto (Claude Code espande ${GROCERYAI_TEST_KEY} dall'ambiente di chi apre il progetto):

{
  "mcpServers": {
    "groceryai": {
      "type": "http",
      "url": "https://oxjqgjbiltoviwldailg.supabase.co/functions/v1/core-mcp",
      "headers": { "Authorization": "Bearer ${GROCERYAI_TEST_KEY}" }
    }
  }
}

Claude Desktop e claude.ai: Customize > Connectors > Add > Add custom connector; l'indirizzo qui sopra; Authentication No sign in; in Request headers si aggiunge Authorization con valore Bearer gai_test_…. Il connettore lo chiama il cloud di Anthropic, quindi l'indirizzo deve essere raggiungibile da internet (lo e').

Cursor: in ~/.cursor/mcp.json (tutti i progetti) o .cursor/mcp.json (un progetto):

{
  "mcpServers": {
    "groceryai": {
      "url": "https://oxjqgjbiltoviwldailg.supabase.co/functions/v1/core-mcp",
      "headers": { "Authorization": "Bearer ${env:GROCERYAI_TEST_KEY}" }
    }
  }
}

Poi si chiede: «quanti prodotti ho?». L'agente chiama count_products.

Gli strumenti

Tutti di sola lettura (readOnlyHint: true), in ordine alfabetico; ognuno esegue un GET dell'API REST con la stessa chiave, lo stesso ambito e lo stesso ambiente.

strumentoche cosa rendeargomentiequivalente REST
count_products{environment, products, available}nessunoGET /v1/catalog/freshness (products, available); uguale a sfogliare GET /v1/products fino in fondo
get_catalog_status{freshness, feed}: ultimo aggiornamento, eta', cadenza dichiarata, stale, il feed programmato e il suo ultimo gironessunoGET /v1/catalog/freshness + GET /v1/catalog/feed
list_orders{orders}, dal piu' vecchiostatus (new di serie, acked), limit (1-200, di serie 50)GET /v1/orders
list_products{products, next_cursor}, in ordine di skusku, gtin, limit (1-200, di serie 50), cursor, store_codeGET /v1/products
list_stores{stores}nessunoGET /v1/stores

Ogni risultato porta i dati due volte: in structuredContent e come testo JSON in content. Un argomento sbagliato (nome sconosciuto, fuori limite, fuori elenco) o un errore della rotta (per esempio PUNTO_VENDITA_SCONOSCIUTO per uno store_code che non c'e') torna come risultato dello strumento con isError: true e il messaggio, cosi' l'agente si corregge da solo; l'errore della rotta e' la stessa busta dell'API REST (error.code, error.doc_url, request_id).

I titoli e le descrizioni dei prodotti sono dati del negozio: un agente li tratta come dati, non come istruzioni.

I limiti

  • 120 richieste al minuto per chiave (il limite della chiave, lo stesso contatore dell'API REST). Ogni POST conta una volta, anche initialize, tools/list e server/discover; uno strumento che legge due rotte conta comunque una volta. Oltre il limite: 429 con Retry-After.
  • Corpo fino a 1 MiB; una richiesta JSON-RPC per POST (niente lotti).
  • Solo l'ambiente di prova, solo letture. Gli strumenti che scrivono e l'accesso OAuth 2.1 arriveranno in una versione successiva.
  • Le richieste con l'intestazione Origin si rifiutano (403) se l'origine non e' ammessa esplicitamente: il server e' per gli agenti, non per le pagine web.
  • Ogni richiesta autenticata finisce nel registro d'audit dell'API del tuo account (route mcp:tools/call:<strumento>, esito, durata), come le chiamate REST.

Gli errori

Gli errori JSON-RPC portano error.data.code (stabile, gli stessi codici dell'API REST dove esistono) e, dove il codice ha una pagina, error.data.doc_url.

HTTPerror.codeerror.data.codequando
401-32001CHIAVE_ASSENTE, CHIAVE_MALFORMATA, CHIAVE_SCONOSCIUTA, CHIAVE_REVOCATA, CHIAVE_SCADUTA, CHIAVE_INCOERENTEla chiave
401-32001CHIAVE_LIVE_NON_AMMESSA, CHIAVE_PUBBLICABILE_NON_AMMESSAuna chiave che questo server non serve
403-32003AMBITO_MANCANTE (required_scopes), TENANT_SOSPESOl'ambito, l'account
403-32005ORIGINE_NON_AMMESSAun'Origin non ammessa
429-32004LIMITE_RICHIESTE (retry_after)il limite di richieste
400-32020MCP-Protocol-Version, Mcp-Method o Mcp-Name non corrispondono al corpo (2026-07-28)
400-32022versione del protocollo non servita; error.data.supported elenca quelle servite
404-32601metodo non servito (2026-07-28)

Esempi provati

Tutti e due gli esempi li esegue contro il server tests/mcp/esempi.mjs (su un negozio sintetico di 437 prodotti).

Con curl, una sola richiesta senza stato, nella revisione corrente:

curl -sS "$GROCERYAI_MCP_URL" \
  -H "Authorization: Bearer $GROCERYAI_TEST_KEY" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -H "MCP-Protocol-Version: 2026-07-28" \
  -H "Mcp-Method: tools/call" \
  -H "Mcp-Name: count_products" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"count_products","arguments":{},"_meta":{"io.modelcontextprotocol/protocolVersion":"2026-07-28"}}}'

La risposta: {"jsonrpc":"2.0","id":1,"result":{"resultType":"complete","content":[{"type":"text","text":"…"}],"structuredContent":{"environment":"test","products":437,"available":291},"isError":false,"_meta":{…}}}.

Con l'SDK TypeScript ufficiale (npm install @modelcontextprotocol/sdk):

import { Client } from '@modelcontextprotocol/sdk/client/index.js';
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';

const client = new Client({ name: 'my-integration', version: '1.0.0' });
await client.connect(new StreamableHTTPClientTransport(new URL(process.env.GROCERYAI_MCP_URL), {
  requestInit: { headers: { Authorization: `Bearer ${process.env.GROCERYAI_TEST_KEY}` } },
}));
const result = await client.callTool({ name: 'count_products', arguments: {} });
console.log(JSON.stringify(result.structuredContent));
await client.close();

Sul negozio sintetico stampa {"environment":"test","products":437,"available":291}; sul tuo, i tuoi numeri.

Torna alla documentazione dell'API