Vai al contenuto principale

MCP Server

L'API B1 offre un server MCP (Model Context Protocol) all'indirizzo /mcp. Qualsiasi client AI compatibile con MCP — Claude Code, Claude Desktop, l'OpenAI Agents SDK, Cursor, o uno realizzato da te — può connettersi e chiamare l'API REST sottostante per conto di un utente della chiesa autenticato. È un wrapper sottile e generico: tre strumenti generici espongono dinamicamente l'intera superficie dell'API invece di modellare a mano ogni endpoint, più uno strumento guida di dominio per il website builder.

Prima di iniziare

  • Una chiave API B1 (cak_…) con gli ambiti che il client dovrebbe avere
  • Un host API B1 raggiungibile — https://api.churchapps.org per le chiese ospitate, oppure il tuo deployment
  • Un client MCP. Vedi Claude e ChatGPT per la configurazione lato utente finale

Endpoint

POST /mcp
Content-Type: application/json
Accept: application/json, text/event-stream
Authorization: Bearer cak_<prefix>.<secret>
AspettoValore
Path/mcp (relativo all'host API)
Methodsolo POST — richiesta/risposta e streaming SSE avvengono entrambi sullo stesso endpoint
TransportMCP Streamable HTTP
Session modelStateless. Per ogni richiesta viene costruita una nuova istanza del server MCP — nessun id di sessione, nessuna ripresa
AuthToken bearer. Sia le chiavi API cak_… sia i JWT B1 funzionano; la risoluzione è la stessa di qualsiasi altro endpoint autenticato

Una richiesta la cui intestazione Authorization è mancante o non valida restituisce:

{ "error": "Unauthorized — MCP requires a valid bearer token (cak_* API key or JWT)." }

con HTTP 401.

Strumenti

Tre strumenti generici più una guida. Il modello usa list_endpoints per la scoperta, describe_endpoint per apprendere la forma di un payload, api_call per invocare effettivamente l'API, e describe_page_builder quando l'attività riguarda i contenuti del sito web.

list_endpoints

Restituisce l'inventario completo delle rotte REST registrate, filtrato per una sottostringa facoltativa e/o un verbo HTTP. Ogni voce include il nome del controller e gli ambiti di chiave API più probabilmente necessari.

Input:

CampoTipoDescrizione
filterstring (optional)Sottostringa case-insensitive confrontata con il percorso, ad es. "people"
methodenum (optional)GET / POST / PUT / DELETE / PATCH

Output: un documento JSON della forma

{
"total": 24,
"endpoints": [
{
"method": "GET",
"path": "/membership/people",
"controller": "PersonController.getAll",
"likelyScopes": ["people:read", "people:write"]
}
]
}

L'inventario viene costruito una sola volta all'avvio dell'API a partire dalla tabella delle rotte live — tutto ciò che puoi raggiungere con curl compare qui.

describe_endpoint

Restituisce un breve riepilogo più, dove disponibile, un corpo di richiesta e un esempio di risposta curati a mano per un endpoint.

Input:

CampoTipoDescrizione
methodstringVerbo HTTP
pathstringPercorso completo come restituito da list_endpoints

Output: per gli endpoint curati, un esempio con summary, requestBody e responseSample. Per gli endpoint non curati, un messaggio di fallback che istruisce il modello a chiamare prima GET per vedere la forma. Circa una dozzina di rotte ad alto traffico (persone, gruppi, donazioni, presenze, fondi) sono curate.

api_call

Invoca l'endpoint REST scelto, in-process, attraverso lo stesso stack di middleware Express di una normale richiesta HTTP — autenticazione, parsing del corpo, audit logging e scoping per chiesa si applicano tutti.

Input:

CampoTipoDescrizione
methodenumGET / POST / PUT / DELETE / PATCH
pathstringPercorso incluso qualsiasi prefisso di modulo, ad es. /membership/people
queryobject (optional)Oggetto piatto di parametri della query string
bodyany (optional)Corpo della richiesta JSON — tipicamente un array di oggetti modello per POST

Output:

{
"status": 200,
"truncated": false,
"body": [ /* the controller's JSON response */ ]
}

Il risultato dello strumento è contrassegnato come isError: true per qualsiasi risposta con stato ≥ 400.

describe_page_builder

L'unico strumento non generico: una guida statica e autonoma per la costruzione di pagine web attraverso gli endpoint /content/* — il modello dati Page → Section → Element, il flusso di creazione, ogni elementType con la sua forma di answersJSON, impostazioni a livello di sezione come i divisori a forma dividerTop/dividerBottom, e un esempio completo end-to-end. Non richiede input e rispecchia il catalogo degli elementi mantenuto nell'editor di B1Admin (vedi Architettura del Website Builder). Ci si aspetta che gli agenti lo chiamino una volta prima di creare o modificare i contenuti della pagina, e poi agiscano tramite api_call.

Modello di autenticazione

La richiesta MCP stessa passa attraverso CustomAuthProvider.getUser() — lo stesso percorso usato da ogni endpoint B1 autenticato. Un bearer cak_… si risolve in un Principal i cui permessi sono l'RBAC attuale della persona emittente, intersecato con gli ambiti concessi dalla chiave. Questa intersezione viene ricalcolata ad ogni richiesta, quindi:

  • Rimuovere un ambito da una chiave (eliminandola e ricreandola) taglia l'accesso alla chiamata successiva.
  • Rimuovere un permesso dalla persona sottostante in B1Admin taglia l'accesso alla chiamata successiva, anche se la chiave esiste ancora.

Per le invocazioni annidate di api_call, l'intestazione Authorization originale viene copiata sulla richiesta sintetica, così CustomAuthProvider viene eseguito di nuovo e l'intersezione degli ambiti viene riapplicata per ogni chiamata. Non c'è caching dei token.

Blocklist dei percorsi

Un piccolo insieme di rotte non è raggiungibile tramite api_call, nemmeno con una chiave valida:

PatternMotivo
/giving/donate/webhook/*Gli endpoint webhook dei provider si aspettano corpi grezzi, verificati tramite firma, da Stripe/PayPal — non da chiamanti generici
/membership/oauth/clients*La registrazione dei client OAuth è riservata all'operatore
/membership/people/apiEmailsProtetto dal jwtSecret dell'operatore, non dai permessi dell'utente
Qualsiasi rotta che si aspetta multipart/form-dataIl caricamento di file non è compatibile con JSON-RPC

Un percorso bloccato restituisce un risultato dello strumento isError: true con un messaggio descrittivo; la rotta sottostante non viene mai invocata.

Limite alla dimensione della risposta

Ogni corpo di risposta di api_call è limitato a 64 KB di output catturato. Se una query supera il limite, la risposta porta "truncated": true e ci si aspetta che il modello riprovi con parametri di query più ristretti. Questo evita che una singola risposta dello strumento saturi la finestra di contesto del client.

Limitazione della frequenza

Non c'è un limite di frequenza a livello applicativo su /mcp. Il throttling è demandato alla concorrenza di API Gateway / Lambda in produzione, e a qualunque cosa applichi il tuo reverse proxy nei deployment self-hosted.

Scoperta OAuth

Il server MCP non annuncia metadati OAuth 2.1 (/.well-known/oauth-authorization-server, registrazione dinamica dei client, flusso PKCE). I client che richiedono server MCP scopribili tramite OAuth — in particolare l'interfaccia "Add custom connector" di Claude.ai e la funzione "Connectors" di ChatGPT — non possono connettersi senza quella superficie.

I client che accettano un token bearer statico nella loro configurazione — Claude Code, Claude Desktop, OpenAI Agents SDK, Cursor, codice personalizzato — funzionano già oggi. L'OAuthController esistente emette già token tramite authorization-code + PKCE per app di terze parti; un livello di scoperta conforme alla specifica MCP costruito sopra di esso colmerebbe il divario.

Sviluppo locale

L'endpoint MCP si monta insieme a tutto il resto quando l'API viene eseguita localmente:

cd Api
npm run dev
# Server listening on http://localhost:8084

All'avvio, la riga di log 📡 MCP server ready at /mcp — N routes in inventory conferma che l'inventario è stato costruito.

Verificalo con l'MCP Inspector:

npx @modelcontextprotocol/inspector

Nell'interfaccia dell'Inspector, puntalo a http://localhost:8084/mcp e imposta l'intestazione Authorization su Bearer cak_<prefix>.<secret>. Chiama prima list_endpoints; dovresti vedere l'elenco completo delle rotte. Poi api_call({ method: "GET", path: "/membership/people" }) dovrebbe restituire le tue persone seed locali.

Struttura del codice

Il server MCP si trova in src/modules/mcp/ nel repository Api. File degni di nota:

FileScopo
McpController.ts@controller("/mcp"); collega StreamableHTTPServerTransport per ogni richiesta
McpServer.tsCostruisce un Server MCP, registra i quattro strumenti
RouteInventory.tsAttraversa i metadati di inversify-express-utils all'avvio per enumerare le rotte
internalDispatch.tsreq/res sintetici che rientrano nell'app Express in-process
tools/listEndpoints.ts, describeEndpoint.ts, apiCall.ts, describePageBuilder.ts
examples.tsEsempi curati di richiesta/risposta per gli endpoint ad alto traffico

Correlati