Lesson 2 — Guides pas à pas

10-1. Le serveur MCP Google Sheets : comprendre, lancer, tester sur votre poste

8 min read

This content is not translated yet. Showing the original version.

Ce guide répond à : que fait mcp_server/main.py, ligne par ligne ? Comment l'installer et le lancer sur un Mac ou un PC Linux ? Comment le tester d'abord sans compte Google, puis sur un vrai classeur ?

Testé (13 octobre 2026), Python 3.14 : les tests simulés passent, et les 17 outils passent sur un vrai classeur, avec la vraie API Sheets, en local. Résultats réels à la partie 5.


Sommaire

  1. Ce que fait le serveur
  2. Les 17 outils
  3. Les contrôles de sécurité dans le code
  4. Installer sur votre poste
  5. Tester
  6. Le piège des formules : la locale du classeur
  7. Modifier ou ajouter un outil

1. Ce que fait le serveur

mcp_server/main.py est un serveur FastMCP (bibliothèque Python de référence pour MCP), exposé en HTTP sur /mcp. Il fait 3 choses :

  1. Vérifier le jeton de l'utilisateur (CompanyGoogleTokenVerifier). Google lui confirme :

    • le client OAuth qui a émis le jeton (aud) ;
    • les portées accordées ;
    • l'e-mail du compte.

    Le serveur refuse si l'un de ces points n'est pas celui attendu (partie 3). Le résultat est gardé 5 minutes, pour ne pas interroger Google à chaque requête.

  2. Exposer 17 outils. Chacun a :

    • une description et des paramètres typés, que le modèle lit ;
    • des annotations (lecture, écriture, destructif), dont Gemini Enterprise se sert pour demander confirmation.
  3. Appeler Google Sheets ou Drive avec le jeton de l'utilisateur (fonction google()). Les erreurs de Google sont traduites en messages clairs : 401 « reconnectez », 403 « pas les droits », 404 « introuvable », 429 « réessayez dans une minute ».

Chaque outil écrit une ligne de journal d'audit : qui, quel outil, quel classeur, quelle plage. Le contenu des cellules n'est jamais écrit.

Le serveur est sans état (stateless_http=True) : Cloud Run peut lancer plusieurs copies sans rien partager.

2. Les 17 outils

OutilRôleType
whoamiLe compte Google connecté (test de connexion)Lecture
search_spreadsheetsTrouver des classeurs par nom (Mon Drive et Drive partagés)Lecture
get_spreadsheet_infoTitre, lien, locale, onglets (taille, lignes figées)Lecture
read_rangeLire une plage : valeurs affichées, brutes, ou formulesLecture
read_rangesLire jusqu'à 20 plages en un appelLecture
find_in_sheetLes cellules qui contiennent un texteLecture
write_rangeÉcrire en remplaçant une plage (formules acceptées)Destructif
append_rowsAjouter des lignes à la fin d'un tableauÉcriture
clear_rangeEffacer les valeurs d'une plageDestructif
create_spreadsheetCréer un classeur, avec ses ongletsÉcriture
add_sheet / rename_sheetAjouter / renommer un ongletÉcriture
delete_sheetSupprimer un onglet et son contenuDestructif
insert_dimensionInsérer des lignes ou des colonnesÉcriture
delete_dimensionSupprimer des lignes ou des colonnesDestructif
format_rangeGras, italique, couleurs, format de nombre, alignementÉcriture
batch_updateToute l'API Sheets : tri, filtres, fusion, mise en forme conditionnelle, graphiques, validation, protection…Destructif

Les plages s'écrivent en notation A1 : Ventes!A1:D20, 'Mon onglet'!A:A, B2.

Pas d'outil qui supprime un fichier. Le serveur demande la portée drive.metadata.readonly, qui permet de chercher un classeur par son nom mais pas d'en lire le contenu ni de le supprimer. Supprimer ou vider des onglets et des plages reste possible (outils destructifs). Dans Gemini Enterprise, ils demandent une confirmation, et vous choisissez de les activer ou non (10-5).

3. Les contrôles de sécurité dans le code

ContrôleOùEffet
Démarrage refusé sans OAUTH_CLIENT_IDSdébut de main.pyUne erreur de configuration ne peut pas donner un serveur qui accepte n'importe quel jeton
Audience du jeton = un de nos clients OAuthGoogleTokenVerifier(audience=…)Un jeton émis pour une autre application est refusé (attaque du « confused deputy »)
Portée spreadsheets obligatoirerequired_scopesUn jeton sans accès Sheets est refusé
E-mail vérifié et du domaine autoriséCompanyGoogleTokenVerifierLes comptes Gmail personnels et les autres organisations sont refusés
Appels Google avec le jeton de l'utilisateurgoogle()Le serveur n'a aucun droit propre : les droits Drive de chacun s'appliquent
20 000 cellules maximum par lectureMAX_CELLS_PER_READNe pas envoyer un classeur entier au modèle (truncated: true)
Journal d'audit sans contenuaudit()Traçabilité sans fuite de données

4. Installer sur votre poste

Dans le dossier 10-mcp-google-sheets-gemini-enterprise :

python3 -m venv .venv && source .venv/bin/activate      # Python 3.12, 3.13 ou 3.14
pip install -r mcp_server/requirements.txt              # fastmcp, uvicorn, httpx : pas de watchdog

Attendu : Successfully installed fastmcp-4.1.0 ….

Tester aussi l'agent en local (facultatif) : il faut en plus google-adk. Sur le Mac Acme, google-adk tire watchdog, qui ne s'installe pas (voir 12-1-1, partie 2 du projet 09). Prenez l'environnement du projet 09, qui marche déjà, et ajoutez-y fastmcp :

source ../09-agents-multi-projets-gemini-enterprise/.venv/bin/activate
pip install fastmcp==4.1.0

Sur un PC Linux, il suffit de faire pip install -r agent/requirements.txt dans le même .venv.

Le déploiement ne demande rien de plus : les images sont construites par Cloud Build dans Google Cloud (10-2).

5. Tester

5.1 Sans compte Google (2 secondes)

python tests/test_server.py

Ce test lance le serveur en HTTP, comme sur Cloud Run. Un client MCP l'appelle, et Google est remplacé par un faux Google en mémoire.

Résultat obtenu :

  ok  notation A1
  ok  rejeté : jeton invalide
  ok  rejeté : jeton émis pour une autre application (audience)
  ok  rejeté : compte hors domaine acme.example
  ok  rejeté : scope Sheets absent
  ok  accepté : alice@acme.example
Outils (17) : add_sheet, append_rows, batch_update, clear_range, create_spreadsheet, …
  ok  search, info, read_range, read_ranges, find_in_sheet
  ok  write, append, clear, create, add/rename/delete sheet, insert/delete rows-cols, format, batch_update
  ok  erreur Google traduite en message clair (404)

OK : tous les tests passent (23 appels Google simulés, tous avec le jeton de l'utilisateur).

5.2 Sur un vrai classeur, en local

Il faut :

  • un jeton utilisateur qui a la portée Sheets. Comment l'obtenir : 10-8, partie 2 ;
  • un classeur que ce compte peut modifier.

Le serveur doit accepter le client OAuth qui a émis ce jeton. Donnez-le-lui en local seulement :

export USER_TOKEN=<le jeton>
AUD=$(curl -s "https://oauth2.googleapis.com/tokeninfo?access_token=$USER_TOKEN" | python3 -c 'import json,sys; print(json.load(sys.stdin)["aud"])')
OAUTH_CLIENT_IDS=$AUD ALLOWED_DOMAINS=acme.example PORT=8091 python mcp_server/main.py &
MCP_URL=http://localhost:8091/mcp SPREADSHEET_ID=<ID du classeur> python tests/test_reel.py

Le test travaille dans un onglet temporaire test-mcp-<heure>, qu'il supprime à la fin : votre classeur est rendu intact.

Résultat obtenu (classeur de test « Produit, Prix, Stock ») :

tools/list : 17 outils
whoami                : sheets-utilisateur-test@…
search_spreadsheets   : 1 classeur(s), dont le classeur de test
get_spreadsheet_info  : « MCP Sheets - test Acme (projet 10) », locale fr_FR, onglets ['Untitled']
read_range            : [['Produit', 'Prix', 'Stock'], ['Vélo', '299', '12'], ['Tente', '149', '30'], ['Sac à dos', '39', '80']]
read_ranges           : 2 plages
find_in_sheet         : [{'cell': 'Untitled!A3', 'value': 'Tente'}]
add_sheet             : test-mcp-232344
write_range           : 9 cellules (formules =B2*12/10 : valables dans toutes les locales)
append_rows           : 'test-mcp-232344'!A4:C4
format_range          : en-tête en gras + fond, format monétaire
batch_update (tri)    : [['Vélo', 299, 358.8], ['Tente', 149, 178.8], ['Sac', 39, 46.8]]
read_range (FORMULA)  : [['=B2*12/10']]
insert/delete_dimension : 2 lignes insérées puis supprimées
clear_range           : colonne C effacée
rename_sheet          : test-mcp-232344-r
delete_sheet          : test-mcp-232344-r (classeur rendu intact)
create_spreadsheet    : refusé pour ce compte (accès refusé : …)
OK : test réel terminé

create_spreadsheet est refusé ici parce que le compte de test est un compte de service, qui n'a pas d'espace Drive. Avec un vrai compte utilisateur, le classeur est créé et son lien est renvoyé.

5.3 Avec MCP Inspector (interface graphique, facultatif)

npx @modelcontextprotocol/inspector
  • Transport : Streamable HTTP ; URL : http://localhost:8091/mcp ;
  • en-tête Authorization : Bearer <le jeton> ;
  • Connect → Tools → List Tools : les 17 outils apparaissent, et on peut les appeler un par un.

6. Le piège des formules : la locale du classeur

Constaté en testant : dans un classeur en fr_FR (le cas chez Acme), =B2*1.2 donne #ERROR! (Formula parse error.). L'API lit les formules dans la langue du classeur :

Locale (get_spreadsheet_info → locale)DécimaleSéparateur d'argumentsExemple
fr_FR,;=SOMME(B2:B9;C2) · =B2*1,2
en_US.,=SUM(B2:B9,C2) · =B2*1.2

La consigne est maintenant dans les instructions du serveur et dans la description du paramètre values : le modèle la lit et adapte ses formules. Le test réel utilise =B2*12/10, qui marche dans toutes les locales.

7. Modifier ou ajouter un outil

  1. Ajoutez une fonction @mcp.tool(annotations={...}) dans mcp_server/main.py. Sa docstring et ses Field(description=…) sont ce que lit le modèle. Utilisez RO, WRITE ou DESTRUCTIVE pour les annotations.
  2. Appelez Google avec await google("GET"|"POST"|…, url, …). Le jeton de l'utilisateur est ajouté tout seul.
  3. Ajoutez audit("nom_outil", …) : jamais de contenu de cellule.
  4. Testez : python tests/test_server.py, puis python tests/test_reel.py.
  5. Régénérez la liste publiée dans Agent Registry : python scripts/gen_toolspec.py.
  6. Redéployez : ./scripts/deployer.sh mcp. Dans Gemini Enterprise : Actions → Reload custom actions, puis activez le nouvel outil.
Next