L'essentiel en une minute
Un serveur MCP est un programme qui met des outils, des données et des modèles de requêtes à la disposition d'une application d'IA, comme Claude, ChatGPT ou l'assistant de VS Code, selon un protocole commun : le Model Context Protocol. Vous l'écrivez une fois, et toute application compatible peut s'en servir.
Cet article explique le fonctionnement, puis montre comment écrire un serveur minimal en Python et le brancher à un client. Il s'appuie sur la spécification officielle, la documentation des SDK et celle des clients, consultées le 30 octobre 2026. Le code n'a pas été exécuté : c'est une synthèse de documentation, pas un retour de test.
Points clés
- Un serveur MCP expose trois types d'éléments : des outils que le modèle appelle, des ressources que l'application charge et des prompts que l'utilisateur choisit.
- La version en vigueur de la spécification est datée du 28 juillet 2026. Elle rend le protocole sans état : plus de poignée de main
initialize, plus de session HTTP.- En Python, le SDK officiel est le paquet
mcp, en version 2.3.0 sur PyPI. La classeFastMCPde la version 1 s'appelle désormaisMCPServer.- Deux transports sont standard : stdio pour un serveur local lancé par le client, Streamable HTTP pour un serveur distant.
- Un serveur MCP exécute du code avec vos droits. La spécification pose en principe que l'hôte obtienne le consentement explicite de l'utilisateur avant d'invoquer un outil ; le protocole ne peut pas l'imposer, c'est à l'hôte de l'appliquer.
Qu'est-ce qu'un serveur MCP ?
Un serveur MCP est la moitié « fournisseur » d'un échange entre une IA et un système extérieur. La documentation officielle définit MCP comme « un standard open source pour connecter des applications d'IA à des systèmes externes » et le compare à un port USB-C pour les applications d'IA (notre traduction, modelcontextprotocol.io, consulté le 30 octobre 2026).
L'idée répond à un problème d'intégration. Sans standard, chaque application d'IA doit écrire son propre connecteur pour chaque service. Avec MCP, un service publie un serveur, et toute application qui embarque un client MCP peut l'utiliser.
Le protocole est récent. Anthropic l'a publié en open source le 25 novembre 2024 (Anthropic). Le 9 décembre 2025, Anthropic l'a confié à l'Agentic AI Foundation, un fonds de la Linux Foundation cofondé avec Block et OpenAI. Le billet officiel précise que le modèle de gouvernance du projet reste inchangé (blog MCP, 9 décembre 2025).
Le même billet annonçait alors plus de 97 millions de téléchargements mensuels des SDK. Sept mois plus tard, le billet de la révision 2026-07-28 parle de près d'un demi-milliard de téléchargements par mois (blog MCP, 28 juillet 2026). Ce sont des chiffres du projet lui-même, que nous n'avons pas pu recouper.
Si vous découvrez le sujet par les agents, retenez qu'un agent IA est un modèle qui enchaîne des actions avec des outils ; notre article sur les agents IA détaille ce fonctionnement. MCP est la manière standard de lui fournir ces outils.
Hôte, client, serveur : l'architecture
Trois rôles se partagent le travail, et la spécification les nomme précisément. Les messages entre eux suivent le format JSON-RPC 2.0 (spécification MCP, version 2026-07-28) :
| Rôle | Définition de la spécification | Exemples |
|---|---|---|
| Hôte | L'application d'IA qui ouvre les connexions | Claude Desktop, Claude Code, VS Code, Cursor |
| Client | Le connecteur MCP placé dans l'hôte | Un client par serveur connecté |
| Serveur | Le service qui fournit contexte et capacités | Accès à des fichiers, à Git, à une API |
La documentation du SDK Python résume la répartition : l'hôte est ce à quoi l'utilisateur parle, il lance un client par serveur, et le serveur « ne parle jamais directement au modèle » (SDK Python, First steps). Le modèle décide, le client transmet, le serveur exécute.
Le serveur ne connaît donc pas le modèle utilisé : il répond à des requêtes standard comme tools/list ou tools/call, ce qui lui permet de fonctionner dans plusieurs applications.
Outils, ressources, prompts
Un serveur MCP expose trois primitives, et ce qui les distingue, c'est qui décide de s'en servir. La spécification les décrit ainsi : les ressources sont du contexte et des données, les prompts des modèles de messages pour l'utilisateur, les outils des fonctions que le modèle exécute (spécification MCP).
| Primitive | Contrôlée par | Équivalent web | Exemple |
|---|---|---|---|
| Outil (tool) | Le modèle | Un POST : une action, avec effets possibles | Créer un ticket, lancer une requête SQL |
| Ressource (resource) | L'application | Un GET : une lecture sans effet | Le contenu d'un fichier, une réponse d'API |
| Prompt | L'utilisateur | Aucun : une requête enregistrée | Une commande « / » dans un menu |
La colonne « équivalent web » reprend la comparaison de la documentation du SDK Python (First steps).
Côté client, la spécification 2026-07-28 ne liste qu'une fonction offerte aux serveurs : l'élicitation, qui permet à un serveur de demander une information à l'utilisateur. Depuis cette révision, le serveur ne l'envoie plus comme une requête : il la glisse dans un résultat « input_required », et le client relance l'appel avec la réponse (Elicitation). Les fonctions Roots et Sampling existent encore, mais elles sont dépréciées (voir plus bas).
stdio ou Streamable HTTP
Le choix du transport dépend d'une question : le serveur tourne-t-il sur la machine de l'utilisateur ou ailleurs ? La spécification définit deux transports standard (page des transports) :
| stdio | Streamable HTTP | |
|---|---|---|
| Principe | Le client lance le serveur en sous-processus et échange par l'entrée et la sortie standard | Chaque message du client est une requête HTTP POST vers une seule adresse, par exemple /mcp ; le serveur répond en JSON ou par un flux SSE |
| Usage type | Outil personnel, accès à des fichiers locaux | Serveur partagé, service en ligne |
| Port réseau | Aucun | Oui |
| Authentification | Identifiants passés par variables d'environnement | À mettre en place (OAuth prévu par la spécification) |
| Piège courant | Tout print() sur la sortie standard corrompt le protocole |
Oublier de valider l'en-tête Origin
|
L'ancien transport HTTP+SSE, apparu dans la révision 2024-11-05, est déprécié depuis la révision 2025-03-26 au profit de Streamable HTTP. La documentation de Claude Code le signale aussi comme déprécié.
Pour Streamable HTTP, la spécification impose trois règles de sécurité : le serveur doit valider l'en-tête Origin pour éviter les attaques par « DNS rebinding », devrait n'écouter que sur 127.0.0.1 en local, et devrait authentifier toutes les connexions (Streamable HTTP).
Ce qui change avec la spécification 2026-07-28
La révision du 28 juillet 2026 est la plus profonde depuis le lancement, et les guides publiés avant cette date ne la décrivent pas. Son journal des modifications liste ces changements majeurs (changelog 2026-07-28) :
-
Plus de poignée de main. L'échange
initializedisparaît. Chaque requête porte sa version de protocole et les capacités du client dans un champ_meta. -
Plus de session HTTP. L'en-tête
Mcp-Session-Idest supprimé. Un serveur qui doit garder un état entre deux appels renvoie un identifiant explicite, que le modèle repasse comme argument d'outil. -
Une nouvelle méthode obligatoire. Les serveurs doivent implémenter
server/discover, qui annonce les versions et capacités prises en charge. - Fini les requêtes envoyées par le serveur. Quand un serveur a besoin d'une information, il renvoie un résultat « input_required », et le client relance la requête avec la réponse.
- Les tâches longues passent en extension. Les « tasks » quittent le cœur du protocole pour une extension officielle optionnelle.
Plusieurs fonctions sont dépréciées : Roots, Sampling, Logging et l'enregistrement dynamique des clients OAuth. D'après le registre des dépréciations, leur retrait est possible au plus tôt dans la première révision publiée à partir du 28 juillet 2027.
La compatibilité mérite attention. Selon la page sur les versions, un client ancien qui parle à un serveur limité à la nouvelle révision échoue, et l'inverse aussi. Le SDK Python 2, dont la version 2.0.0 est parue sur PyPI le jour même de la révision, affirme servir les deux générations de clients (What's new in v2). Vérifiez-le avec vos propres clients avant de mettre à jour un serveur en production.
Tutoriel : créer un serveur MCP en Python
Avec le SDK officiel, un serveur MCP en Python tient en une trentaine de lignes, contrôles de sécurité compris. Nous construisons un serveur qui expose un seul outil : chercher un mot dans un dossier de notes Markdown, en lecture seule. Le code suit la documentation du SDK, mais il n'a pas été exécuté pour cet article.
Étape 1 : installer le SDK
Le paquet officiel s'appelle mcp sur PyPI. Sa version 2.3.0 date du 2 octobre 2026 (PyPI, consulté le 30 octobre 2026). La documentation recommande l'outil uv et l'option cli, qui ajoute la commande mcp (installation). uv add travaille dans un projet : la documentation de uv le crée avec uv init.
uv init notes-mcp && cd notes-mcp
uv add "mcp[cli]==2.3.0"
# ou, dans un environnement virtuel activé : pip install "mcp[cli]==2.3.0"
La documentation demande Python 3.11 ou plus (la fiche PyPI de la version 2.3.0 indique 3.10). Si vous partez de zéro, notre guide pour apprendre Python couvre l'installation. Un projet resté en version 1 doit noter que FastMCP s'appelle maintenant MCPServer et que l'ancien chemin d'import mcp.server.fastmcp a disparu.
Étape 2 : écrire le serveur
Créez un fichier server.py. Le nom de la fonction devient le nom de l'outil, sa docstring sa description, et les annotations de type son schéma d'entrée :
# server.py : serveur MCP minimal (exemple non exécuté, SDK mcp 2.3.0)
import os
from pathlib import Path
from mcp.server import MCPServer
from mcp.server.mcpserver.exceptions import ToolError
NOTES_DIR = Path(os.environ.get("NOTES_DIR", "~/notes")).expanduser().resolve()
MAX_RESULTATS = 20
mcp = MCPServer("Notes")
@mcp.tool()
def chercher_notes(requete: str) -> list[str]:
"""Cherche un mot dans les notes Markdown (lecture seule) et renvoie les fichiers trouvés."""
terme = requete.strip().lower()
if len(terme) < 2:
raise ToolError("La requête doit contenir au moins 2 caractères.")
if not NOTES_DIR.is_dir():
raise ToolError(f"Dossier introuvable : {NOTES_DIR}")
resultats: list[str] = []
for fichier in sorted(NOTES_DIR.rglob("*.md")):
if not fichier.resolve().is_relative_to(NOTES_DIR):
continue # ignore les liens symboliques qui sortent du dossier
texte = fichier.read_text(encoding="utf-8", errors="ignore")
if terme in texte.lower():
resultats.append(str(fichier.relative_to(NOTES_DIR)))
if len(resultats) >= MAX_RESULTATS:
break
return resultats
if __name__ == "__main__":
mcp.run()
Trois choix sont des mesures de sécurité : l'outil ne lit que dans NOTES_DIR, refuse les requêtes vides ou trop courtes et plafonne les résultats. ToolError renvoie au modèle une erreur d'outil exploitable.
Sans argument, mcp.run() démarre en stdio. La documentation insiste sur deux points : l'appel doit rester sous if __name__ == "__main__":, et rien d'autre que le SDK ne doit écrire sur la sortie standard (Running your server). Pour vos journaux, utilisez le module logging, qui écrit sur la sortie d'erreur.
Étape 3 : tester avec l'Inspector
Avant de brancher un client, la documentation conseille de lancer l'MCP Inspector, une interface web qui liste les outils et permet de les appeler à la main. Elle nécessite npx, donc Node.js :
uv run mcp dev server.py
# avec pip : mcp dev server.py, dans l'environnement virtuel activé
Si vous lancez python server.py directement, rien ne s'affiche et la commande ne rend pas la main. D'après la documentation, c'est normal : un serveur stdio attend qu'un client parle en premier.
Étape 4 : passer en HTTP si besoin
Pour exposer le même serveur sur le réseau, il suffit de changer l'appel final, puis de lancer le fichier avec uv run python server.py. Le serveur écoute alors par défaut sur 127.0.0.1, et l'adresse MCP devient http://127.0.0.1:3001/mcp :
if __name__ == "__main__":
mcp.run(transport="streamable-http", port=3001)
Attention : mcp run, utilisé dans les configurations ci-dessous, n'exécute pas le bloc if __name__ == "__main__": ; il appelle run() lui-même et ne transmet que l'option --transport, d'après la documentation du SDK. Avant d'ouvrir le serveur au-delà de votre machine, ajoutez une authentification et configurez la protection contre le DNS rebinding : la documentation renvoie pour cela à son option transport_security et à sa page de déploiement.
Brancher le serveur à Claude et à VS Code
Connecter un serveur MCP local revient toujours à donner au client la commande qui le lance. La documentation du SDK propose une commande unique, qui fonctionne depuis n'importe quel dossier (Connect to a real host). Le chemin de server.py et celui de NOTES_DIR doivent toujours être absolus : un chemin relatif est la première cause d'échec citée. Pour Claude Desktop, mettez aussi le chemin absolu de uv, car une application graphique lance le serveur avec un PATH réduit qui peut ne pas le contenir.
Claude Code. La commande claude mcp add enregistre le serveur. Tout ce qui suit -- est la commande de lancement. La documentation de Claude Code demande de placer une autre option, comme --transport stdio, entre --env et le nom du serveur :
claude mcp add --env NOTES_DIR=/chemin/absolu/notes --transport stdio notes \
-- uv run --with "mcp[cli]==2.3.0" mcp run /chemin/absolu/server.py
Tapez ensuite /mcp dans une session pour vérifier la connexion. Claude Code propose trois portées : locale (par défaut), projet (fichier .mcp.json versionné) et utilisateur. Pour un serveur HTTP, la forme est claude mcp add --transport http notes http://127.0.0.1:3001/mcp.
Claude Desktop. La commande uv run mcp install server.py -v NOTES_DIR=/chemin/absolu/notes écrit l'entrée dans claude_desktop_config.json, situé dans %APPDATA%\Claude\ sous Windows et ~/Library/Application Support/Claude/ sous macOS, d'après la documentation du SDK. Voici un exemple d'entrée (le contenu généré peut différer) :
{
"mcpServers": {
"Notes": {
"command": "/chemin/absolu/uv",
"args": ["run", "--frozen", "--with", "mcp[cli]==2.3.0", "mcp", "run", "/chemin/absolu/server.py"],
"env": { "NOTES_DIR": "/chemin/absolu/notes" }
}
}
}
Quittez complètement l'application, pas seulement sa fenêtre, puis relancez-la : Claude Desktop ne relit sa configuration qu'au démarrage.
VS Code. Le fichier .vscode/mcp.json utilise la clé servers au lieu de mcpServers, et chaque entrée déclare son type (référence de configuration de VS Code). La documentation du SDK précise qu'il faut le mode Agent de Copilot Chat :
{
"servers": {
"notes": {
"type": "stdio",
"command": "uv",
"args": ["run", "--with", "mcp[cli]==2.3.0", "mcp", "run", "/chemin/absolu/server.py"],
"env": { "NOTES_DIR": "/chemin/absolu/notes" }
}
}
}
Si votre éditeur n'est pas encore configuré, notre sélection d'extensions VS Code aide à partir sur de bonnes bases. Pour l'usage quotidien d'un assistant connecté à vos outils, voyez aussi notre article sur coder avec l'IA.
Les serveurs MCP utiles et gratuits
Avant d'écrire votre propre serveur, vérifiez qu'il n'existe pas déjà. Le dépôt officiel ne garde plus que sept serveurs de référence. Il en a archivé treize, dont GitHub, PostgreSQL, Slack et SQLite, et renvoie vers le registre MCP officiel pour le reste (dépôt modelcontextprotocol/servers, consulté le 30 octobre 2026).
| Serveur | Éditeur | Ce qu'il fait | Coût | Version relevée le 30 octobre 2026 |
|---|---|---|---|---|
| Filesystem | Projet MCP (référence) | Lecture et écriture de fichiers dans des dossiers autorisés | Gratuit, open source |
@modelcontextprotocol/server-filesystem 2.1.0 (npm) |
| Git | Projet MCP (référence) | Lire, chercher et modifier des dépôts Git | Gratuit, open source |
mcp-server-git 2026.10.28 (PyPI) |
| Fetch | Projet MCP (référence) | Récupérer une page web et la convertir pour le modèle | Gratuit, open source |
mcp-server-fetch 2026.10.28 (PyPI) |
| Time, Memory, Sequential Thinking, Everything | Projet MCP (référence) | Fuseaux horaires, mémoire en graphe, raisonnement par étapes, serveur de test | Gratuit, open source | Non relevée |
| GitHub MCP Server | GitHub | Dépôts, tickets, pull requests ; version distante https://api.githubcopilot.com/mcp/
|
Open source (MIT) ; compte GitHub requis | Non relevée |
| Playwright MCP | Microsoft | Piloter un navigateur via l'arbre d'accessibilité | Open source (Apache 2.0) |
@playwright/mcp 0.0.83 (npm) |
| Context7 | Upstash | Documentation à jour de bibliothèques pour l'agent | Open source (MIT) ; clé d'API gratuite recommandée |
@upstash/context7-mcp 4.4.4 (npm) |
Les versions viennent de PyPI et npm, les licences des dépôts GitHub. Un avertissement compte : le dépôt officiel présente ses serveurs comme des implémentations de référence, des « exemples pédagogiques » et non des « solutions prêtes pour la production » (notre traduction).
« Gratuit » ne veut pas dire « sans risque ». Le serveur GitHub, par exemple, permet d'activer seulement certains groupes d'outils avec l'option --toolsets, ce qui limite à la fois le risque et la taille du contexte. Notre sélection d'outils IA gratuits pour développeurs complète cette liste côté assistants.
Sécuriser un serveur MCP
La sécurité est le point faible de la plupart des tutoriels, alors que la spécification est explicite : « les outils représentent une exécution de code arbitraire », et l'un de ses principes veut que l'hôte obtienne le consentement explicite de l'utilisateur avant d'invoquer un outil (notre traduction, spécification MCP). Elle précise que le protocole ne peut pas imposer ces principes : c'est aux implémentations de le faire.
Ce que le serveur doit faire. La page de la spécification consacrée aux outils impose quatre règles aux serveurs : valider toutes les entrées, appliquer des contrôles d'accès, limiter le débit des appels et assainir les sorties (Tools, 2026-07-28). Elle recommande aussi qu'un humain puisse toujours refuser un appel d'outil.
L'injection de prompt. C'est le premier risque du Top 10 OWASP pour les applications LLM, édition 2025 (LLM01). Un serveur qui lit des pages web, des tickets ou des e-mails fait entrer dans le contexte du texte écrit par des inconnus, qui peut contenir des instructions. La documentation de Claude Code le dit : les serveurs qui récupèrent du contenu externe vous exposent à ce risque.
Les descriptions d'outils. La spécification demande de considérer les annotations d'outils comme non fiables, sauf si elles viennent d'un serveur de confiance. Une description piégée peut orienter le modèle : n'installez que des serveurs dont vous pouvez lire le code ou dont vous connaissez l'éditeur.
Les permissions excessives. Le risque LLM06 de l'OWASP, « Excessive Agency », a trois causes : trop de fonctions, trop de permissions, trop d'autonomie. Les parades proposées s'appliquent telles quelles à un serveur MCP : n'exposer que les outils nécessaires, donner des droits minimaux (un rôle de base de données en lecture seule, par exemple) et exiger une validation humaine pour les actions à fort impact (OWASP LLM06).
Les jetons d'accès. Le guide de sécurité de MCP interdit le « token passthrough » : un serveur ne doit accepter aucun jeton qui n'a pas été émis pour lui, et ne doit pas le transmettre tel quel à une API en aval (Security Best Practices). Côté poste de travail, ne versionnez jamais de clé : VS Code propose des variables inputs masquées, et sa documentation déconseille de committer jetons et en-têtes d'authentification.
Les serveurs locaux. Le même guide cite des commandes de démarrage piégées, capables d'exfiltrer une clé SSH. Il recommande stdio, un bac à sable, et un jeton d'autorisation si un serveur local écoute en HTTP.
L'OWASP prépare aussi un Top 10 propre à MCP, encore en bêta, qui place en tête la mauvaise gestion des jetons et l'exposition des secrets. Pour le contexte plus large des attaques, notre panorama des cyberattaques les plus fréquentes reste utile.
Quand ne pas utiliser MCP
MCP n'est pas toujours la bonne réponse, et certains éditeurs de serveurs le reconnaissent. Quatre cas où il vaut mieux s'en passer :
- Une seule application, un seul modèle. Si votre code appelle directement l'API d'un modèle, la fonction d'appel d'outils de cette API suffit. MCP sert d'abord à réutiliser un outil dans plusieurs hôtes.
- Un agent de code qui a déjà un terminal. Le dépôt de Playwright MCP note que les agents de code préfèrent de plus en plus une interface en ligne de commande, plus économe en tokens, car elle évite de charger de gros schémas d'outils dans le contexte (Playwright MCP).
- De gros volumes de données. Claude Code affiche un avertissement au-delà de 10 000 tokens de sortie d'un outil MCP et plafonne par défaut à 25 000 (documentation de Claude Code). Pour interroger une grosse base documentaire, un système de RAG est souvent plus adapté.
- Un service web classique. Si des humains ou d'autres programmes consomment votre service, une API REST reste la base. Notre tutoriel pour créer une API REST avec Node.js part de là, et un serveur MCP peut ensuite l'envelopper.
Pour choisir l'hôte lui-même, notre comparatif ChatGPT, Claude et Gemini pose les bases.
Ce que nous n'avons pas pu vérifier
Plusieurs points restent incertains au 30 octobre 2026 :
- L'exécution du code. Aucun exemple de cet article n'a été lancé. Le code suit la documentation du SDK Python 2, mais une différence de version peut casser un import.
- La compatibilité réelle des clients avec la révision 2026-07-28. Nous n'avons trouvé aucune liste officielle des hôtes qui la prennent en charge, ni testé un client ancien face au SDK 2.
- Les chiffres d'adoption. Les 97 millions (décembre 2025) puis près d'un demi-milliard (juillet 2026) de téléchargements mensuels viennent du projet MCP, sans source indépendante.
- Le contenu du registre officiel. Sa page ne chargeait pas la liste des serveurs lors de notre consultation.
Questions fréquentes
Un serveur MCP, c'est quoi en une phrase ?
C'est un programme qui expose des outils, des ressources et des prompts à une application d'IA selon le Model Context Protocol, un standard ouvert. L'application, appelée hôte, s'y connecte par un client MCP et laisse le modèle décider quand appeler les outils.
Un serveur MCP est-il gratuit ?
Le protocole et les SDK officiels sont open source, et les serveurs de référence comme Filesystem, Git ou Fetch sont gratuits. Certains serveurs d'éditeurs demandent un compte ou une clé d'API, comme Context7, dont la documentation recommande une clé gratuite pour des limites plus élevées. Le coût réel vient surtout des tokens consommés par le modèle.
Faut-il utiliser Claude pour profiter d'un serveur MCP ?
Non. Anthropic a créé MCP, mais le protocole est géré depuis décembre 2025 par l'Agentic AI Foundation. La documentation officielle cite Claude, ChatGPT, Visual Studio Code et Cursor parmi les applications compatibles.
Python ou TypeScript pour écrire un serveur MCP ?
Les deux disposent d'un SDK officiel. En Python, le paquet mcp est en version 2.3.0. En TypeScript, la ligne stable est la version 2, publiée en deux paquets, @modelcontextprotocol/server et @modelcontextprotocol/client, en version 2.4.0 sur npm le 30 octobre 2026. L'ancien paquet @modelcontextprotocol/sdk, en 1.32.2, reste maintenu au moins six mois après la sortie de la v2, selon le dépôt du SDK TypeScript. Choisissez le langage de votre équipe.
À propos de l'auteur
Abdelilah Ourti est ingénieur IA et architecte logiciel. Il conçoit et déploie des applications web et des systèmes d'IA, et publie sur NeurauTech des guides et des comparatifs destinés aux développeurs francophones. Ses autres articles sont réunis sur sa page auteur.
Méthode. Synthèse fondée exclusivement sur des sources publiques : spécification et guide de sécurité de modelcontextprotocol.io, documentation du SDK Python, documentation de Claude Code et de VS Code, fiches PyPI et npm, dépôts GitHub des serveurs cités, pages de l'OWASP. Les citations issues de sources en anglais sont traduites par nos soins. Le code n'a pas été exécuté et aucun test n'a été mené. Aucun lien n'est affilié et aucune relation commerciale ne lie l'auteur aux éditeurs mentionnés.
Une erreur ou une version périmée ? Écrivez à contact@neurautech.com : l'article sera corrigé et la date de mise à jour modifiée.
Dernière mise à jour : 30 octobre 2026.
Image : Marcus Urbenz (Unsplash License).


Top comments (0)