Publier un serveur MCP sur npm et PyPI, étape par étape
Publiez un même serveur MCP sur les deux registres pour que n’importe quel client le lance avec npx ou uvx. Configurez package.json et pyproject.toml, vérifiez le tarball, testez avec MCP Inspector, publiez via la publication de confiance dans GitHub Actions, puis ajoutez des outils d’image et de vidéo via une API.
Vous avez créé un serveur MCP et il tourne sur votre ordinateur. Maintenant, un collègue, ou un inconnu sur internet, veut le faire tourner avec une seule ligne de configuration : pas de git clone, pas d’étape de build, pas de fil de discussion sur « quelle version de Node utilisez-vous ? ». C’est exactement ce qu’offre un registre. Publiez sur npm et les clients lanceront votre serveur avec npx. Publiez sur PyPI et ils le lanceront avec uvx. Si vous faites les deux, chaque client MCP, de Claude Desktop à Cursor en passant par VS Code, peut lancer votre serveur à partir d’un simple nom de package.
Ce guide suit le chemin dans l’ordre : structure du dépôt, package.json, pyproject.toml, tests en local, première publication manuelle, puis un workflow GitHub Actions qui livre les deux packages à partir d’un seul tag. Chaque étape suppose un serveur stdio qui fonctionne déjà en local.
💡 Avant de commencer : il vous faut Node 18+ pour npm ou Python 3.10+ pour PyPI, ainsi que des comptes gratuits sur npmjs.com et pypi.org. Activez l’authentification à deux facteurs sur les deux, car les deux registres l’exigent de tous ceux qui publient.
Pourquoi publier sur les deux registres
La plupart des serveurs MCP sont écrits dans un seul langage, généralement TypeScript ou Python, et y restent. Cela fonctionne jusqu’au jour où quelqu’un travaillant avec un autre environnement technique veut essayer le vôtre. Une équipe data en Python n’installera pas Node pour faire tourner un outil, et une équipe front-end ne mettra pas en place un environnement virtuel. Publier sur les deux registres supprime cet obstacle.
Deux publics, un seul serveur
Voici la comparaison des deux parcours côte à côte :
npm
PyPI
Commande d’exécution
npx -y your-package
uvx your-package
SDK officiel
@modelcontextprotocol/sdk
mcp (inclut FastMCP)
Manifeste
package.json
pyproject.toml
Ce qui est envoyé
Un tarball construit à partir de dist/
Une archive source plus un wheel
Commande de publication
npm publish
uv publish ou twine upload
Authentification en CI
Publication de confiance ou jeton granulaire
Publication de confiance ou jeton d’API
La configuration la plus propre est une implémentation par langage avec un seul contrat d’outils partagé. Les noms des outils, les schémas d’entrée et les descriptions restent identiques dans les deux packages, si bien qu’un prompt qui fonctionne avec la version npm se comporte de la même façon avec la version PyPI. Conservez ce contrat dans un petit fichier JSON du dépôt et faites comparer les deux builds à ce fichier par la CI.
Résistez à la tentation d’un simple wrapper Python qui appelle npx. Cela fonctionne jusqu’à ce que l’utilisateur n’ait pas Node installé, et il obtient alors une erreur que personne ne peut déchiffrer d’un coup d’œil.
Choisir la structure du package
Un seul dépôt avec deux dossiers simplifie la gestion des releases :
Chaque dossier est un package à part entière, avec son propre manifeste. Le workflow de publication utilisera plus tard working-directory pour les construire séparément, afin que rien ne passe d’un côté à l’autre.
Nommez une fois, vérifiez deux fois
Choisissez un seul nom et utilisez-le sur les deux registres. Les utilisateurs s’en souviendront, et les résultats de recherche seront cohérents.
npm : minuscules, compatible avec les URL, sans espaces. Un nom avec scope comme @yourscope/my-mcp-server évite les collisions et convient parfaitement aux serveurs MCP.
PyPI : les noms ne tiennent pas compte de la casse et traitent -, _ et . comme le même caractère, donc My_MCP.Server et my-mcp-server entrent en collision.
Disponibilité :npm view my-mcp-server renvoie une erreur 404 lorsque le nom est libre. Sur PyPI, ouvrez pypi.org/project/my-mcp-server/ : une erreur 404 signifie la même chose.
💡 Vérifiez les deux noms avant de rédiger le README autour de l’un d’eux. Découvrir le jour de la publication que le nom est déjà pris coûte une après-midi de renommage.
Rédigez un README qui sert de documentation
Les deux registres affichent votre README comme page du package, et pour beaucoup d’utilisateurs c’est la seule documentation qu’ils lisent. Incluez-y quatre éléments, dans cet ordre :
Une phrase sur ce que fait le serveur
Une configuration client à copier-coller pour npx et une autre pour uvx
Un tableau des outils, avec une ligne par outil
Chaque variable d’environnement lue par le serveur, indiquée comme obligatoire ou facultative
Publier le package npm
Configurer package.json
Trois champs déterminent si npx fonctionne réellement : bin, files et le shebang de votre fichier d’entrée.
{
"name": "@yourscope/my-mcp-server",
"version": "0.1.0",
"description": "MCP server that does one useful thing",
"type": "module",
"bin": { "my-mcp-server": "dist/index.js" },
"files": ["dist", "README.md", "LICENSE"],
"engines": { "node": ">=18" },
"scripts": {
"build": "tsc",
"prepublishOnly": "npm run build"
},
"dependencies": { "@modelcontextprotocol/sdk": "^1.0.0" },
"license": "MIT"
}
bin associe le nom de la commande au fichier d’entrée compilé. Sans lui, npx n’a rien à exécuter.
files est une liste blanche. Seuls dist/, le README et la licence entrent dans le tarball.
prepublishOnly relance le build juste avant chaque publication, afin de ne jamais livrer une sortie périmée.
Le shebang#!/usr/bin/env node doit être la première ligne de src/index.ts. TypeScript le conserve dans le fichier compilé.
Vérifier le tarball avant l’envoi
Lancez npm pack --dry-run et lisez la liste de fichiers qu’il affiche. Vous voulez dist/, le README, la licence et package.json. Vous ne voulez pas de .env, de fixtures de test, de source maps que vous n’aviez pas prévu de partager, ni d’un node_modules qui traîne.
💡 Un .env divulgué est l’erreur la plus courante lors d’une première publication, et une version publiée ne peut pas être retirée. La liste blanche files est votre filet de sécurité, gardez-la.
Effectuer la première publication
npm login
npm publish --access public
Les packages avec scope sont privés par défaut, c’est pourquoi --access public est indispensable lors de la première publication. Saisissez votre code à deux facteurs lorsqu’il vous est demandé. Puis vérifiez que cela fonctionne depuis un autre dossier :
cd $(mktemp -d)
npx -y @yourscope/my-mcp-server
Le processus doit démarrer et attendre une entrée sur stdin. Ce silence est normal, car un serveur stdio ne parle que lorsqu’un client lui adresse la parole.
Publier le package PyPI
Rédiger pyproject.toml
Le packaging Python tient dans un seul fichier. Cette version utilise hatchling comme backend de build :
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[project]
name = "my-mcp-server"
version = "0.1.0"
description = "MCP server that does one useful thing"
readme = "README.md"
requires-python = ">=3.10"
license = { text = "MIT" }
dependencies = ["mcp>=1.0"]
[project.scripts]
my-mcp-server = "my_mcp_server.server:main"
La table [project.scripts] équivaut à bin. Elle crée une commande à l’installation. Si le nom du script correspond au nom du package, uvx my-mcp-server fonctionne directement. S’il diffère, exécutez uvx --from my-mcp-server script-name.
Un serveur minimal utilisant FastMCP, issu du SDK Python officiel :
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("my-mcp-server")
@mcp.tool()
def ping() -> str:
"""Return pong so clients can confirm the server is alive."""
return "pong"
def main() -> None:
mcp.run() # stdio transport by default
Construire et vérifier les fichiers
cd python
uv build
uvx twine check dist/*
uv build écrit deux fichiers dans dist/ : une archive source (.tar.gz) et un wheel (.whl). Les wheels s’installent rapidement, car rien n’a besoin d’être compilé, ce qui explique pourquoi uvx et pip les privilégient. twine check confirme que le README s’affiche correctement comme page du package, de sorte que vous repérez des métadonnées cassées avant que PyPI ne le fasse.
Essayer TestPyPI, puis publier
TestPyPI est un site distinct, avec des comptes et des jetons distincts. Il existe pour qu’un premier envoi raté ne vous coûte rien.
L’index supplémentaire est important. Vos dépendances, y compris mcp, se trouvent sur le vrai PyPI, et TestPyPI ne les héberge pas. Une fois que l’installation de test fonctionne, publiez pour de bon :
uv publish --token <pypi-token>
uvx my-mcp-server
💡 Les versions sont définitives sur les deux registres. PyPI n’accepte jamais deux fois le même nom de fichier, même après une suppression, et npm refuse de réutiliser un numéro de version déjà publié. En cas de problème, incrémentez la version et publiez à nouveau.
Tester avant de livrer
Lancer MCP Inspector
MCP Inspector ouvre une page web locale où vous listez les outils, renseignez les arguments et lisez les réponses brutes. Pointez-le vers le build généré, puis vers le package exactement comme le lancerait un utilisateur :
La seconde commande est la plus importante. Elle teste le package installé plutôt que votre dossier de travail, de sorte que les fichiers manquants et les mauvais points d’entrée apparaissent ici et non dans un rapport de bug.
Garder stdout propre
En stdio, stdout transporte le protocole. Un console.log() ou un print() égaré injecte du texte dans le flux JSON-RPC, et le client se déconnecte avec une erreur d’analyse. Envoyez plutôt chaque ligne de log vers stderr : console.error() en Node, et print(..., file=sys.stderr) ou le module logging en Python.
Tester la configuration client
C’est la configuration que vos utilisateurs vont coller. Essayez les deux entrées dans un client réel :
Lancez la checklist de pré-publication depuis un shell propre dans un dossier temporaire. Une installation globale ou un node_modules situé à proximité peut masquer un fichier manquant pendant des semaines.
npm pack --dry-run ne liste que ce que vous voulez livrer
twine check dist/* passe sans avertissement
Inspector liste chaque outil via npx et via uvx
Rien n’écrit sur stdout en dehors des messages du protocole
Les blocs de configuration du README correspondent exactement à ce que vous venez de tester
Automatiser et versionner les releases
Publication de confiance, sans jeton stocké
Les deux registres permettent à un workflow GitHub Actions de publier via OpenID Connect. Vous enregistrez votre dépôt et le fichier de workflow côté registre, une seule fois, et le registre fait confiance à ce workflow précis à partir de là. Aucun jeton de longue durée ne réside dans les secrets de votre dépôt, il n’y a donc rien à divulguer ni à renouveler. Le workflow n’a besoin que de la permission id-token: write.
Sur PyPI, ajoutez un éditeur de confiance dans les paramètres de publication du projet. Un projet tout neuf peut utiliser un publisher en attente, afin que la première release puisse elle aussi provenir de la CI. Sur npm, ajoutez l’éditeur de confiance dans les paramètres du package. Les écrans de configuration évoluent de temps à autre, suivez donc les instructions actuelles.
Un tag, deux publications
Pousser un tag comme v0.1.0 déclenche les deux jobs en parallèle :
L’étape npm install -g npm@latest s’assure que le CLI npm est assez récent pour la publication de confiance. Ajoutez une étape needs: avec votre job de test si vous voulez qu’un build en échec bloque la release.
Un semver que les clients respectent
Les clients MCP et les agents qui les utilisent dépendent de vos noms d’outils et de vos schémas d’entrée, traitez-les donc comme votre API publique :
Changement
Incrément de version
Corriger un bug, sans modifier le schéma
Patch (0.1.1)
Ajouter un nouvel outil ou un argument facultatif
Mineure (0.2.0)
Renommer ou supprimer un outil, ou ajouter un argument obligatoire
Majeure (1.0.0)
Mettez les deux manifestes au même numéro dans un seul commit, puis taguez-le. Un petit script qui modifie package.json et pyproject.toml ensemble évite le classique décalage où npm affiche 1.2.0 et PyPI 1.1.0. Les utilisateurs qui veulent de la stabilité peuvent figer une version majeure dans leur configuration, par exemple @yourscope/my-mcp-server@1.
Une fois les deux packages en ligne, vous pouvez aussi référencer le serveur dans le registre officiel MCP. Celui-ci vérifie la propriété en lisant un champ mcpName dans package.json et une ligne mcp-name: correspondante dans le README PyPI, puis publie les métadonnées avec la commande mcp-publisher. Le registre évolue encore, lisez donc sa documentation actuelle avant de vous fier au format exact.
Ajouter des outils d’image et de vidéo
Un serveur publié devient plus utile dès qu’il peut créer quelque chose. La génération d’images et de vidéos figure parmi les outils les plus demandés, et l’API PicassoIA permet de les ajouter en quelques lignes. L’URL de base est https://api.picassoia.com/v1, l’authentification se fait par jeton Bearer commençant par pia_sk_, et les tâches sont asynchrones : vous créez une prédiction, vous l’interrogez, puis vous lisez le résultat.
Consultez la documentation de l’API pour connaître les champs de saisie exacts de chaque modèle, car la structure de output et les paramètres acceptés varient d’un modèle à l’autre.
Transmettez le jeton via la configuration client. Ne jamais intégrer un jeton dans le package. Lisez-le depuis l’environnement et laissez chaque utilisateur le renseigner dans sa configuration MCP :
💡 Consultez les conditions d’accès actuelles sur la page de l’API PicassoIA avant de promettre une utilisation gratuite dans votre README. La formulation des tarifs peut changer, et et vos utilisateurs vous demanderont des comptes sur ce que vous avez écrit.
Rédigez les notes de version avec un LLM. Un grand modèle de langage peut prendre en charge la corvée qui pousse les gens à négliger les changelogs. Voici un workflow rapide avec Claude Sonnet 5 :
Lancez git log v0.1.0..HEAD --oneline et copiez le résultat.
Ouvrez la page du modèle sur PicassoIA et collez le journal avec une consigne d’une ligne : regroupez les changements en Ajouts, Modifications et Corrections, en langage clair.
Indiquez-lui quels changements touchent aux noms d’outils ou aux schémas, afin qu’ils soient signalés comme ruptures.
Relisez le résultat en le comparant au diff, puis collez-le dans la release GitHub.
Pour un second avis sur votre pyproject.toml ou votre fichier de workflow, GPT 5.6 Sol est un bon relecteur pour les tâches de programmation.
Essayez par vous-même sur Picasso IA
Votre page de package mérite une vraie image d’en-tête et un court extrait de démonstration, pas une capture d’écran de terminal. Créez l’image d’en-tête avec Picasso IA Image, peaufinez les détails avec Picasso IA Image Editor Pro, puis animez la dernière image en un court clip avec Picasso IA Video.
Une première expérience simple :
Rédigez un prompt de 40 mots décrivant un bureau de développeur calme sous la lumière du matin
Générez trois variantes et gardez la plus nette
Enregistrez-la comme image d’en-tête dans votre README
Animez-la pour l’annonce de la release
Parcourez tous les modèles disponibles sur picassoia.com/en/all-models, choisissez celui qui correspond à votre style et publiez quelque chose qui vaut la peine d’être ouvert. Votre première release n’est qu’à un tag de distance.