Connettore MCP¶
Il connettore MCP (Model Context Protocol) collega GesTask a Claude: claude.ai, l'app mobile, Claude Desktop e Claude Code. Claude riceve un tool per ogni operazione dell'API HTTP e agisce a nome dell'utente che ha dato il consenso, con i suoi permessi.
Principio: l'MCP è l'API. Un tool non contiene logica propria: esegue l'action v1 corrispondente, quindi permessi, validazione, notifiche, webhook e registro operazioni sono gli stessi delle chiamate HTTP.
Pezzi¶
| Cosa | Dove |
|---|---|
Server MCP (POST /mcp) |
api/controllers/McpController.php |
| Catalogo dei tool, generato | api/mcp/tools.php |
| Prompt, scritti a mano | api/mcp/prompts.php |
| Approvazioni delle operazioni pesanti | common/models/McpApproval.php, frontend/controllers/McpApprovalController.php |
| Client ID Metadata Documents | common/services/OAuthClientMetadata.php |
| Plugin per Claude Code | claude-plugin/gestask/, marketplace in .claude-plugin/marketplace.json |
| Generatore del catalogo | api/components/McpCatalog.php, comando php yii mcp/tools |
| Endpoint OAuth pubblici | api/controllers/OauthController.php |
| Pagina di consenso | frontend/controllers/OauthController.php, viste in frontend/views/oauth/ |
| Server di autorizzazione | common/services/OAuthServer.php, modelli OAuthClient e OAuthToken |
| App collegate e revoca | Profilo › App collegate (ProfileController::actionRevokeApp) |
Due connettori¶
| Indirizzo | Nome | Tool | Per chi |
|---|---|---|---|
POST /mcp |
GesTask | il lavoro di tutti i giorni (147 tool), più prompt e risorse | ogni utente |
POST /mcp/admin |
GesTask amministrazione | i soli strumenti del pannello (31 tool, tag Amministrazione) |
chi ha admin, superAdmin o admin.panel; gli altri ricevono 403 |
Sono lo stesso McpController: actionAdmin() verifica l'accesso al pannello,
accende $admin e prosegue come actionIndex(). Ognuno elenca ed esegue solo i
propri tool, quindi un amministratore che usa Claude per le sue task non si
porta dietro gli strumenti del pannello. Il connettore di amministrazione ha
istruzioni proprie (conferme esplicite, nome esatto per il cestino) e non
offre prompt né risorse. Il token OAuth è lo stesso; i metadati della risorsa
protetta sono due (/.well-known/oauth-protected-resource/mcp e …/mcp/admin).
La pagina «Collega Claude»¶
frontend/controllers/ClaudeController (indirizzo /claude, voce nel menu
laterale) è la guida per chi non è tecnico:
- indirizzo da copiare, per il connettore utente e, a chi accede al
pannello, per quello di amministrazione (
McpConnector::url(): daMCP_API_URL, oppure ricavato dall'indirizzo del frontend quando le due app stanno affiancate come in XAMPP; altrimenti un messaggio che rimanda all'amministratore); - passi per claude.ai e l'app per computer, il telefono e Claude Code;
- esempi di richieste, i flussi pronti (letti da
api/mcp/prompts.php) e come allegare una task; - l'elenco completo di ciò che Claude può fare, per area, letto da
api/mcp/tools.php(McpConnector::capabilities()), con l'indicazione di cosa legge, modifica o elimina: la pagina resta allineata al catalogo senza doverla aggiornare a mano; - sicurezza, revoca e problemi frequenti.
La guida in-app (argomento claude), il tour di benvenuto e la sezione «App
collegate» del profilo rimandano a questa pagina.
Il server MCP¶
Trasporto Streamable HTTP senza stato: ogni POST /mcp porta un messaggio
JSON-RPC e riceve la risposta in JSON. Non ci sono sessioni né flusso SSE:
GET e DELETE /mcp rispondono 405.
| Metodo | Risposta |
|---|---|
initialize |
versione del protocollo (2025-06-18 o 2025-03-26), capabilities.tools, serverInfo con la versione dal changelog, instructions sul dominio |
notifications/* |
202 senza corpo |
ping |
oggetto vuoto |
tools/list |
i tool del connettore: quelli utente su /mcp, quelli col tag Amministrazione su /mcp/admin |
tools/call |
esecuzione del tool |
prompts/list, prompts/get |
i flussi di lavoro pronti di api/mcp/prompts.php |
resources/list, resources/templates/list, resources/read |
task e allegati da allegare alla conversazione |
| altro | errore -32601 |
Gli errori dell'API (403, 404, 422 con i fields, regole di dominio) tornano
come risultato del tool con isError: true e lo stesso JSON delle risposte
HTTP: Claude li legge e corregge la richiesta. Gli errori JSON-RPC sono solo
quelli di protocollo.
Esecuzione di un tool¶
McpController::execute() imposta sulla request corrente parametri di query,
corpo e verbo HTTP del tool ($_SERVER['REQUEST_METHOD'], perché alcune
action scelgono in base al verbo, es. task/set-tag), esegue
Yii::$app->runAction('v1/<route>') e rimette tutto com'era. Request e
response restano gli stessi oggetti: sostituirli con set() cambierebbe anche
la definizione del componente.
L'autenticazione passa di nuovo dall'authenticator della v1, con lo stesso
header Authorization; il limite di richieste lo conta ogni esecuzione di un
tool, non il messaggio MCP.
- File in ingresso (allegati, import CSV, avatar): il tool riceve
filenameecontent_base64. Il contenuto si scrive in un file temporaneo esposto in$_FILES['file']contmp_resource, cosìUploadedFile::saveAs()lo copia invece di usaremove_uploaded_file. - File in uscita (download di un allegato, export CSV): le immagini
tornano come contenuto
image, i testi e i CSV cometext, il resto comeresourcein base64. - Allegati oltre 5 MB non entrano nella conversazione: il tool restituisce
download_url, un linkGET /mcp/files/<token>valido 15 minuti. Il token è casuale e sta solo nella cache, legato all'utente e all'allegato; aprendo il link il permessoboard.viewsi ricontrolla con quell'utente. Per altri file grandi (un export CSV enorme) escono solo i metadati. - Riferimenti per nome. Gli argomenti uuid che indicano una task, una
bacheca, un team o una persona accettano anche il nome con cui li chiamano
le persone: la sigla della task (
ABC-12), la sigla della bacheca (ABC), il nome del team, lo username;personaleindica la bacheca o il team personale dell'utente.McpCatalog::refKind()li riconosce dal percorso ({uuid}sotto/tasks,/boards,/teams,/users) o dal nome dell'argomento (taskUuid,board,team,assignees,presenter…) e li mette inrefs;McpController::refUuid()li risolve prima di chiamare l'action, cercando task, bacheche e team solo fra quelli visibili all'utente. Due team con lo stesso nome danno un errore che chiede l'uuid. - Date leggibili. L'API dà le scadenze come
2026-09-12 00:00:00e creazione e modifica come timestamp unix. Nella risposta dei tool ogni*_atintero diventa una data nello stesso formato, nel fuso dell'applicazione, così Claude non deve convertirla. L'API resta com'è. - Esito senza corpo. Un'action che risponde 204 diventa
{"done": true, "message": …}; per le eliminazioni il messaggio ricorda come ripristinare dal cestino. - Elenchi compatti. Gli elenchi di task (
list_my_tasks,list_board_tasks,list_subtasks,search) escono di default con i soli campi essenziali (uuid,key,title,status,priority,due_at,list,board,assignees,is_blocked,parent), tramite il parametrofieldsdell'API;fields=allli restituisce tutti. Il dettaglio sta inget_task. - Registro operazioni.
McpControllersegna la richiesta (params['mcpRequest']): le operazioni dei tool hanno originemcp(«Claude (MCP)») e l'autore diventa «mario via Claude» con OAuth (il nome dell'app registrata) o «mario via connettore MCP» con il token personale.
Prompt¶
I prompt sono flussi di lavoro pronti che l'utente sceglie dal client (in
Claude compaiono fra i comandi). Stanno in api/mcp/prompts.php, scritti a
mano: nome, descrizione, argomenti e un testo che dice a Claude quali tool
usare e in che ordine, chiedendo conferma prima di modificare.
| Prompt | Argomenti | Cosa fa |
|---|---|---|
lavora-sulla-task |
task (sigla o uuid) |
carica la task con tutto il contesto, propone un piano, la porta avanti (stato, checklist, sottotask) e alla fine commenta, registra le ore e la completa |
pianifica-settimana |
— | ritardi, scadenze della settimana, riunioni e assenze, e un piano giorno per giorno |
riepilogo-giornata |
— | notifiche non lette, task di oggi e in ritardo, riunioni del giorno |
prepara-riunione |
team |
punti da discutere dalle bacheche del team e ordine del giorno della riunione; ogni punto su una task lo presenta il suo assegnatario |
chiudi-giornata |
— | chiede su cosa si è lavorato, registra le ore non contate dal timer, spunta la checklist e commenta l'avanzamento |
riordina-backlog |
bacheca (sigla) |
propone priorità, date e stime per le task aperte senza scadenza, e segnala quelle senza descrizione, senza assegnatario o doppie |
Il commento di riepilogo di lavora-sulla-task ha un modello fisso, in
Markdown: Cosa è stato fatto, Come funziona, Cosa è cambiato, Sezioni toccate,
Cosa non funziona più. È scritto per chi usa GesTask: niente hash di commit né
percorsi di file. Non indica una versione e non aspetta un rilascio, perché il
prompt lo usa chiunque, non solo chi sviluppa GesTask. Le istruzioni del
server ripetono le cinque voci, così valgono anche quando il prompt non è
stato richiamato.
Il timer conta solo l'orario lavorativo (params['workingHours'], di norma
lun–ven 9–18), quindi una task lavorata fuori orario resta a zero minuti. Per
questo lavora-sulla-task, appena spostata la task in corso, controlla l'ora:
fuori orario lo segnala all'utente e, se conferma, annota l'ora di inizio e a
fine lavoro registra i minuti con create_task_worklog. Anche questa regola è
ripetuta nelle istruzioni del server. L'orario è scritto nel testo del prompt:
se cambia workingHours, va aggiornato anche lì.
Risorse¶
Le risorse si allegano alla conversazione (in Claude con @) invece di farle
leggere con un tool:
resources/listpropone le task aperte assegnate all'utente (al massimo 50);gestask://task/{task}(sigla o uuid): la task in JSON con sottotask, checklist, commenti, allegati e relazioni (get_taskconexpand);gestask://attachment/{uuid}: il file allegato, come testo o in base64; oltre 5 MB il link temporaneo.
Una risorsa inesistente o non accessibile risponde con l'errore -32002 e il
messaggio dell'API.
Descrizioni per Claude¶
Il campo x-mcp-description di un'operazione dello spec è il testo che Claude
legge per primo: quando usare il tool e cosa chiamare dopo. Va scritto per le
operazioni più usate; per le altre bastano summary e description.
Catalogo dei tool¶
Il catalogo nasce dallo spec api/web/openapi-v1.yaml:
- un tool per operazione, con nome dall'
operationIdin snake_case (listMyTasks→list_my_tasks); - descrizione da
x-mcp-description(se c'è),summaryedescription; - argomenti dai parametri di percorso e query e dal corpo, con i
$refrisolti; un{uuid}senza descrizione dice in quale percorso sta; annotations: GET in sola lettura, DELETE distruttivo, PUT idempotente;- action dalla rotta con lo stesso verbo e percorso in
routes-v1.php.
Il generatore fallisce se una rotta non ha l'operazione nello spec o
viceversa. McpCest::everyRouteIsATool verifica che api/mcp/tools.php sia
allineato.
Aggiungere una capacità¶
- Action v1 sottile sopra un servizio di
common/services. - Rotta in
api/config/routes-v1.phpe controller inApiMasterToken::MODULES, se nuovo. - Operazione nello spec, con
operationId,summary,description(il permesso richiesto) e gli schemi. Le operazioni del pannello vanno sotto il tagAmministrazione. php yii mcp/tools, poi i test.
mcp/tools usa symfony/yaml, presente solo fra le dipendenze di sviluppo: a
runtime il catalogo è un file PHP.
OAuth 2.1¶
claude.ai e Claude Desktop accettano solo connettori con OAuth. Il server supporta client pubblici con codice di autorizzazione e PKCE S256.
- Claude chiama
POST /mcpsenza token e riceve 401 conWWW-Authenticate: Bearer resource_metadata="<api>/.well-known/oauth-protected-resource/mcp". - Legge i metadati della risorsa e quelli del server di autorizzazione
(
/.well-known/oauth-authorization-server, sull'app api). - Si registra con
POST /oauth/register. - Apre
/oauth/authorizesul frontend: l'utente accede (con la verifica in due passaggi, se attiva) e acconsente. - Il frontend rimanda a Claude con
codeestate. -
Claude scambia il codice con
POST /oauth/token, inviando ilcode_verifier, e riceve access token (1 ora) e refresh token (30 giorni). -
Registrazione aperta (RFC 7591): non dà accesso, che arriva solo dal consenso. Sono ammessi solo indirizzi di ritorno
https:ohttpverso localhost; al massimo 20 registrazioni l'ora per indirizzo IP. - Consenso: dietro il login normale, compresa la verifica in due
passaggi; dopo l'accesso si torna alla pagina richiesta (
goBack). Il modulo invia alla stessa query string, che si rivalida: nessun parametro passa da campi nascosti. - Token:
gto_(access, 1 ora) egtr_(refresh, 30 giorni, ruota a ogni uso). Si salvano solo le impronte sha256. Codice e refresh valgono una volta: il rinnovo aggiorna la riga solo se il segreto usato è ancora quello in tabella, così due richieste concorrenti non ottengono entrambe un token. - Un'autorizzazione è una riga di
oauth_token, dal codice alla revoca. - Autenticazione:
MasterTokenAuthriconosce il prefissogto_e imposta l'utente. Un utente disattivato perde subito l'accesso. - Revoca: da Profilo › App collegate, o
POST /oauth/revoke(RFC 7009). - Riuso del refresh: chi ripresenta un refresh già sostituito
(
previous_refresh_hash) ne possiede una copia; l'autorizzazione intera si revoca e l'utente deve ricollegare l'app. - Client ID Metadata Documents: un
client_idche è un indirizzo https (con percorso) non va registrato.OAuthClientMetadatascarica il documento conOutboundUrlGuard(niente reti interne né redirect, 5 secondi, 10 KB), controlla che dichiari lo stessoclient_id, crea o aggiorna il client (oauth_client.metadata_url) e tiene il documento in cache un'ora. I metadati annuncianoclient_id_metadata_document_supported. La registrazione aperta resta per i client che non li usano.
Cosa concede l'utente (scope)¶
Nel consenso l'utente sceglie, per ogni app, fra tre livelli
(OAuthToken::SCOPES); lo scope chiesto dal client è solo la scelta proposta:
| Scope | Cosa permette |
|---|---|
read (Solo lettura) |
ogni GET, tranne l'amministrazione |
work (Lavoro quotidiano) |
ogni GET e le scritture dei controller task, task-part, leave, meeting, me, stats, info, search |
full (Tutto) |
tutto, compresa l'amministrazione |
OAuthToken::allows(route, verbo) decide. Il server MCP nasconde da
tools/list e da tools/call i tool non concessi, e MasterTokenAuth rifiuta
con 403 le action fuori scope anche nelle sotto-richieste. Il connettore di
amministrazione vuole full, e il consenso per …/mcp/admin non offre altra
scelta. Le autorizzazioni nate prima degli scope valgono full.
Token legato al connettore¶
Il client indica nel consenso il connettore per cui chiede il token
(resource, RFC 8707): si salva in oauth_token.resource e vale da lì in poi.
- il token di
/mcpnon apre/mcp/admin, e viceversa; - da solo non vale sull'API v1: le sotto-richieste dei tool passano perché il
server MCP le ha segnate come sue (
params['mcpRequest']); - al rinnovo, una
resourcediversa da quella concessa è uninvalid_target.
Un token senza resource (client che non la manda) vale come prima.
Operazioni da approvare¶
Le operazioni pesanti non le decide Claude:
- quali: tutte le scritture del connettore di amministrazione e, nel
connettore utente,
empty_trash,purge_trash_item,delete_board_list,remove_team_member,delete_team_role,replace_team_permissions,delete_webhook(McpController::APPROVAL_TOOLS); - come: alla prima chiamata il tool non parte. Nasce una richiesta in
mcp_approval(utente, tool, impronta degli argomenti, riepilogo leggibile, dieci minuti di validità) e Claude riceveapproval_requiredcon il link…/mcp-approval/view/<uuid>. L'utente lo apre in GesTask, dove è entrato con le sue credenziali, e approva o rifiuta (McpApprovalController); - dopo: la stessa chiamata, con gli stessi argomenti, trova l'approvazione e la consuma in modo atomico. Ogni approvazione vale una volta sola.
L'elicitation di MCP (il server che chiede conferma al client a metà di una chiamata) richiederebbe sessioni e uno stream SSE aperti, che il server senza stato non ha. Il link funziona con ogni client e mette la decisione nel posto in cui l'utente è autenticato, non nella conversazione che Claude potrebbe compilare da solo.
Peso del catalogo¶
claude.ai e Claude Desktop mettono l'intero elenco dei tool nel contesto di ogni conversazione, quindi il catalogo è tenuto leggero (circa 92 KB per i 144 tool dell'area utente):
- niente
outputSchemanéstructuredContent: Claude legge comunque il testo JSON, e gli schemi di uscita pesavano quanto quelli di ingresso; - niente
examplenegli schemi di ingresso (McpCatalog::compact()); - la descrizione usa
x-mcp-descriptional posto delsummary, che è già il titolo del tool, eannotationsnon ripete il titolo.
Prima di aggiungere testo a molte operazioni dello spec conviene misurare
l'elenco con tools/list.
Prestazioni¶
Il server MCP autentica una volta per messaggio: le sotto-richieste dei tool
trovano in params['mcpAuth'] l'identità già verificata (stesso token) e non
tornano al database. I limiti del token (scope, operazioni del token master)
si controllano comunque, rotta per rotta. Lo stato del messaggio si azzera
alla fine della richiesta (McpController::runAction).
Plugin per Claude Code¶
claude-plugin/gestask/ è il plugin, con il marketplace in
.claude-plugin/marketplace.json alla radice. Le istruzioni di installazione
arriveranno quando il plugin avrà il suo repository definitivo.
.mcp.jsoncollega il servergestaskall'indirizzo inGESTASK_MCP_URL;commands/contiene un comando per prompt (/gestask:lavora-sulla-task ABC-12, …), generato daapi/mcp/prompts.phpconphp yii mcp/plugin.McpCest::thePluginCommandsFollowThePromptsfallisce se non è allineato;- dopo un cambio si alza
versioninplugin.json.claude plugin validate ./claude-plugin/gestaskcontrolla il manifest.
Configurazione¶
| Parametro | Variabile | Uso |
|---|---|---|
params['mcp']['frontendUrl'] |
MCP_FRONTEND_URL |
indirizzo pubblico del frontend, per la pagina di consenso e i link del feed iCal. Obbligatorio |
params['mcp']['apiUrl'] |
MCP_API_URL |
indirizzo pubblico dell'API (es. dietro un proxy); vuoto = quello della richiesta |
params['mcp']['accessTokenTtl'] |
durata dell'access token, default 3600 s | |
params['mcp']['refreshTokenTtl'] |
durata del refresh token, default 30 giorni |
Le variabili arrivano dall'ambiente: in Docker da .env, in Apache con
SetEnv. Gli indirizzi vanno codificati (%20 per gli spazi).
La scoperta OAuth cerca /.well-known/... alla radice dell'host: con
l'app in una sottocartella (XAMPP) non funziona. In quel caso si collega il
connettore con il token personale:
claude mcp add --transport http gestask "<api>/mcp" \
--header "Authorization: Bearer <token>"
claude.ai e Claude Desktop raggiungono il server dai server di Anthropic: serve un indirizzo HTTPS pubblico.
Funzioni arrivate nell'API con il connettore¶
Per dare a Claude tutto ciò che un utente può fare, l'API ha ricevuto le
funzioni del frontend e del pannello che mancavano. La logica è stata spostata
in common e le pagine usano gli stessi servizi:
| Area | Endpoint | Servizio |
|---|---|---|
| Le mie task | me/tasks (filtri), me/tasks/stats, me/tasks/order, me/tasks/{uuid}/today |
MyTasks |
| Dashboard | me/dashboard, users/{uuid}/dashboard, teams/{uuid}/dashboard, dashboard/global |
DashboardStats |
| Ore | timesheet |
TimesheetService::forViewer() |
| Modelli | board-templates/{uuid}, boards/{uuid}/template, predefinito |
BoardTemplateService |
| Reazioni | comments/{uuid}/reactions |
TaskParts::react() |
| Ordine bacheche | teams/{uuid}/board-order |
BoardSettings::orderBoards() |
| Profilo | me/calendar-feed, me/avatar |
User, AvatarForm |
| Novità e guida | releases, guide |
ReleaseNotes, Guide |
| Amministrazione | admin/* |
AdminScope, AdminStats, UserAdministration, GlobalRoleManager, UserSearch, AuditLogSearch |
Restano fuori, di proposito, le credenziali: token personale e master, password e verifica in due passaggi (propri e altrui), log grezzi di sistema.