Tutorial: Goose con un server MCP read-only su metadati FHIR anonimizzati

Goose di Block configurato con un server MCP custom che espone in sola lettura i metadati di un bundle FHIR anonimizzato. Provider LLM locale via Ollama, isolamento della directory, uso di dataset di test.

Open SourceAITutorial Open SourceAIAgenticTutorialGooseMCPSanitàFHIR

Note preliminari

Contenuto fornito “as-is”, non adatto a dati clinici reali senza valutazione DPIA. Prima di qualunque prova:

  • Lavorate esclusivamente su dataset di test o dati anonimizzati secondo una procedura documentata e validata.
  • Backup della cartella di lavoro prima di ogni sessione.
  • Nessun endpoint cloud su PHI (Protected Health Information): il modello deve essere locale (Ollama, vLLM) anche in fase di prova. Un errore di configurazione con dati reali è un data breach.
  • Non inserite secrets nei prompt o nei file di configurazione versionati.
  • MCP e Goose sono tecnologie in rapida evoluzione: verificate la documentazione ufficiale della versione installata, soprattutto per la sintassi dei server.
  • Questa integrazione non costituisce software medicale. Non usatela per finalità diagnostiche, di triage o per supporto decisionale clinico.

Cosa è Goose e cosa è MCP

Goose (block.github.io/goose) è un agent desktop e CLI open source rilasciato da Block a gennaio 2025, licenza Apache 2.0. La caratteristica chiave ai nostri fini è l’adesione al Model Context Protocol (MCP), lo standard pubblicato da Anthropic a fine 2024 come protocollo di interoperabilità tra agent LLM e tool/fonti dati. Un server MCP espone capability (resources, tools, prompts); Goose le consuma via JSON-RPC.

Per un’azienda sanitaria o per una software house che sviluppa prodotti sanitari, la combinazione apre una possibilità concreta: sperimentare un agent sopra i propri dati senza far uscire i dati dall’infrastruttura, e senza reimplementare integrazioni ad hoc.

Caso d’uso: esplorare un bundle FHIR anonimizzato di esempio

Scenario: un team di sviluppo sta progettando un modulo che interroga una cartella contenente bundle FHIR anonimizzati a scopo di test. Vogliamo verificare se un agent può aiutare lo sviluppatore a fare domande esplorative (conteggi per tipo di risorsa, distribuzione delle osservazioni, conformità allo schema minimo richiesto) senza scrivere per primo un tool ad-hoc.

1. Preparazione dei dati di test

Usate solo dati sintetici o pubblicamente rilasciati a scopo di sviluppo. Il progetto Synthea (Apache 2.0, synthetichealth.github.io/synthea) genera bundle FHIR di pazienti fittizi. In alternativa, adattate i vostri dataset applicando una procedura di anonimizzazione certificata internamente.

mkdir -p ~/fhir-lab/input ~/fhir-lab/reports
# Copiate qui solo JSON di test o sintetici:
cp /path/al/bundle-sintetico.json ~/fhir-lab/input/
# Read-only per sicurezza:
chmod 444 ~/fhir-lab/input/*.json

2. Server MCP minimale, read-only, in Python

Un server MCP che espone solo due tool: list_bundles (elenca i file JSON nella directory) e bundle_metadata (restituisce conteggi e tipi di risorsa per un bundle, senza contenuti clinici). Il server non legge mai i campi anagrafici.

# ~/fhir-lab/fhir_mcp_server.py
import json, os
from pathlib import Path
from mcp.server.fastmcp import FastMCP  # dipende dalla versione dell'SDK

BASE = Path(os.environ["FHIR_LAB_DIR"])  # obbligatoria, no default rischiosi
assert BASE.is_dir(), "FHIR_LAB_DIR non valida"

app = FastMCP("fhir-lab-readonly")

@app.tool()
def list_bundles() -> list[str]:
    """Elenca i file .json nella directory di input."""
    return sorted(p.name for p in (BASE / "input").glob("*.json"))

@app.tool()
def bundle_metadata(filename: str) -> dict:
    """Conteggi aggregati dei resourceType in un bundle. Nessun contenuto clinico."""
    p = (BASE / "input" / filename).resolve()
    if BASE / "input" not in p.parents:
        raise ValueError("path traversal")
    bundle = json.loads(p.read_text())
    counts = {}
    for entry in bundle.get("entry", []):
        rt = entry.get("resource", {}).get("resourceType", "Unknown")
        counts[rt] = counts.get(rt, 0) + 1
    return {"filename": filename, "totalEntries": sum(counts.values()), "byType": counts}

if __name__ == "__main__":
    app.run()

Tre elementi di sicurezza elementare:

  • BASE richiesta via env: niente path hardcoded, niente fallback.
  • Check anti path-traversal: il parametro filename non può uscire dalla directory dichiarata.
  • Funzioni read-only: nessuna funzione mutante esposta al modello.

Installazione delle dipendenze in un virtualenv isolato:

python3 -m venv ~/fhir-lab/.venv
source ~/fhir-lab/.venv/bin/activate
pip install "mcp[cli]"   # nome del pacchetto dipende dalla release

3. Configurare Goose perché usi il server e un modello locale

# Provider locale via Ollama:
ollama pull qwen2.5:14b   # o altro modello adeguato

# Configurazione Goose (il comando esatto può variare tra versioni):
goose configure
# Scegliere: provider = Ollama; model = qwen2.5:14b

Collegare il server MCP al profilo Goose (sintassi indicativa — adattare al formato config.yaml della versione installata):

extensions:
  fhir-lab:
    type: stdio
    command: /home/utente/fhir-lab/.venv/bin/python
    args: ["/home/utente/fhir-lab/fhir_mcp_server.py"]
    env:
      FHIR_LAB_DIR: "/home/utente/fhir-lab"

4. Sessione con Goose

cd ~/fhir-lab
goose session

Prompt utili nel REPL:

  • “Elenca i bundle disponibili con il tool list_bundles.”
  • “Per ciascun bundle, mostra il conteggio per resourceType usando bundle_metadata. Riassumi in una tabella.”
  • “Quali bundle non contengono Observation? Restituisci la lista.”

L’agente chiama i tool MCP per rispondere: nessun dato clinico entra nel prompt, solo i metadati che il server espone deliberatamente.

Limiti e avvertenze

  • Anonimizzazione ≠ sicurezza: un dataset “anonimizzato” può contenere PHI per re-identificazione indiretta. Usate procedure documentate (pseudonimizzazione, generalizzazione, k-anonymity o equivalenti) prima di considerarlo utilizzabile in un esperimento AI.
  • Il server MCP è parte del vostro TCB: un bug che aggira il check di path-traversal, o un tool “mutante” aggiunto per comodità, apre una superficie di attacco su dati sensibili. Trattate il codice del server come trattate quello che scrive su DB clinici.
  • Il modello locale non è “senza rischi”: le tracce delle sessioni Goose (home dell’utente, shell history, swap) possono comunque contenere metadati interrogati. Proteggete la workstation come un client clinico.
  • Non è software medicale: anche esempi che sembrano innocui (conteggi per tipo) non sono validati clinicamente. Qualsiasi uso diagnostico o assistenziale richiede un percorso regolatorio (MDR, ISO 13485, IEC 62304) estraneo al perimetro di questo tutorial.

Link: block.github.io/goosemodelcontextprotocol.iosynthetichealth.github.io/synthea


Stefano Noferi — Founder e CEO/CTO di noze
Tech Entrepreneur — AI Governance & Security Architect

Vuoi supporto? Sei sotto attacco? Stato dei servizi
Vuoi supporto? Sei sotto attacco? Stato dei servizi