Commencer avec un exemple fonctionnel
Lancez l’exemple, puis adaptez-le
Créez l’API Orders, une base PostgreSQL et le serveur MCP de votre choix dans Adios. Nous importons le code, configurons des identifiants JWT distincts et lançons les aperçus de développement.
Lancer sur Adios →Pas encore de compte ? Créez-en un et revenez à cet exemple. Choisissez une équipe et examinez les ressources avant de lancer. L’exemple complet nécessite une offre payante avec une capacité suffisante pour deux espaces et une base de données.
Construisez-le étape par étape
Suivre le guide manuel
Concevez l’API et le schéma, ajoutez l’authentification JWT, écrivez les outils MCP et exécutez-les dans vos espaces de développement. Chaque étape comprend le code et les commandes.
Suivre les étapes manuelles →Explorez d’abord l’exemple en cours d’exécution. OAuth pour la connexion des utilisateurs et le déploiement en production viennent ensuite.
Commencer par l’API et la base de données
Créez un serveur MCP en Python qui consulte les commandes via une API REST. Vous ajouterez l’authentification JWT, testerez le serveur dans un workspace Adios et le déploierez.
Avant de commencer, vous aurez besoin d’une API en cours d’exécution et de sa clé de signature JWT. Le guide principal fournit une API de commandes d’exemple avec Node.js 24 et PostgreSQL, ainsi que les instructions pour démarrer son workspace de développement.
Le processus API écoute sur le port 8081 et MCP sur le port 8080 dans leurs workspaces. Les clients utilisent les URL HTTPS générées des aperçus. Gardez API_JWT_SECRET et MCP_JWT_SECRET séparés.
Créez un serveur MCP en Python
Dans python/, créez un environnement virtuel et installez requirements.txt. FastMCP génère le schéma depuis la signature de la fonction. Le gestionnaire valide l’identifiant, appelle la même API avec HTTPX et renvoie un dictionnaire typé. L’enveloppe ASGI vérifie le JWT client en préservant le cycle de vie de l’application SDK.
mcp==1.30.0
httpx==0.28.1
uvicorn==0.35.0
PyJWT==2.15.1import os
import re
import time
from urllib.parse import urlparse
import httpx
import jwt
import uvicorn
from mcp.server.fastmcp import Context, FastMCP
from mcp.server.transport_security import TransportSecuritySettings
from mcp.types import ToolAnnotations
from starlette.responses import JSONResponse
api_base = os.getenv("API_BASE_URL", "http://127.0.0.1:8081").rstrip("/")
api_secret = os.environ["API_JWT_SECRET"]
mcp_secret = os.environ["MCP_JWT_SECRET"]
if min(len(api_secret), len(mcp_secret)) < 32 or api_secret == mcp_secret:
raise ValueError("Use separate API and MCP signing secrets of at least 32 characters")
origin = os.getenv("PUBLIC_ORIGIN", "http://127.0.0.1:8080").rstrip("/")
mcp = FastMCP(
"orders-mcp", stateless_http=True, json_response=True,
transport_security=TransportSecuritySettings(
enable_dns_rebinding_protection=True,
allowed_hosts=[urlparse(origin).netloc, "127.0.0.1:8080", "localhost:8080"],
allowed_origins=[origin],
),
)
@mcp.tool(annotations=ToolAnnotations(readOnlyHint=True))
async def get_order(order_id: str, ctx: Context) -> dict[str, str]:
"""Read an order's shipping status from the Orders API."""
if not re.fullmatch(r"[a-zA-Z0-9-]{1,64}", order_id):
raise ValueError("Use an order ID containing letters, numbers, or hyphens")
# Identity comes from verified HTTP state, not a user-supplied tool argument.
subject = ctx.request_context.request.state.principal
now = int(time.time())
api_token = jwt.encode(
{"iss": "orders-demo", "aud": "orders-api", "sub": subject,
"scope": "orders:read", "iat": now, "exp": now + 300},
api_secret, algorithm="HS256",
)
try:
async with httpx.AsyncClient(timeout=5.0, follow_redirects=False) as client:
response = await client.get(
f"{api_base}/orders/{order_id}",
headers={"Authorization": f"Bearer {api_token}"},
)
response.raise_for_status()
data = response.json()
if not isinstance(data.get("id"), str) or not isinstance(data.get("status"), str):
raise ValueError("Unexpected API response")
return {"id": data["id"], "status": data["status"]}
except (httpx.HTTPError, ValueError, AttributeError):
raise ValueError("Could not read this order. Check its ID and your API access.") from None
@mcp.custom_route("/healthz", methods=["GET"])
async def health(_request):
return JSONResponse({"ok": True})
class JWTGate:
def __init__(self, application):
self.application = application
async def __call__(self, scope, receive, send):
if scope["type"] == "http" and scope["path"] == "/mcp":
headers = dict(scope["headers"])
try:
match = re.fullmatch(rb"Bearer (\S+)", headers.get(b"authorization", b""))
if not match:
raise ValueError("Missing token")
claims = jwt.decode(
match[1], mcp_secret, algorithms=["HS256"],
issuer="orders-demo", audience="orders-mcp",
options={"require": ["sub", "exp"]},
)
if not isinstance(claims["sub"], str) or not claims["sub"]:
raise ValueError("Missing subject")
if not isinstance(claims.get("scope"), str) or "orders:read" not in claims["scope"].split():
await JSONResponse({"error": "orders:read permission required"}, status_code=403)(scope, receive, send)
return
scope.setdefault("state", {})["principal"] = claims["sub"]
except (jwt.PyJWTError, ValueError):
await JSONResponse(
{"error": "Invalid or expired access token"}, status_code=401,
headers={"WWW-Authenticate": 'Bearer error="invalid_token"'},
)(scope, receive, send)
return
await self.application(scope, receive, send)
app = JWTGate(mcp.streamable_http_app())
if __name__ == "__main__":
uvicorn.run(app, host=os.getenv("HOST", "127.0.0.1"), port=int(os.getenv("PORT", "8080")))Utilisez l’import mcp.server.fastmcp du SDK officiel présenté ici. Cet exemple fixe la version v1 du SDK Python MCP ; les paquets aux noms similaires et les versions majeures plus récentes peuvent avoir d’autres API de configuration et d’autorisation.
Référence officielle : SDK Python MCP v1.
Tester dans un workspace Adios
Démarrez d’abord le workspace API du guide principal. Dans le manifeste de ce langage, définissez API_BASE_URL avec l’origine de l’aperçu de l’API et utilisez les secrets de signature de la même équipe de développement.
adios ws create --name orders-mcp-dev --json
export MCP_WORKSPACE_ID=YOUR_MCP_WORKSPACE_ID
(cd python && adios sync "$MCP_WORKSPACE_ID")
adios ws run start "$MCP_WORKSPACE_ID" --wait --json
adios ws get "$MCP_WORKSPACE_ID"Copiez l’origine générée de l’aperçu MCP dans PUBLIC_ORIGIN dans adios.yaml. Synchronisez et redémarrez avant les tests : le contrôle de l’hôte doit correspondre au nom réel de l’aperçu. Les sondes de santé peuvent fonctionner avant cette mise à jour.
(cd python && adios sync "$MCP_WORKSPACE_ID")
adios ws run restart "$MCP_WORKSPACE_ID" --wait --json
export MCP_URL=https://YOUR-MCP-PREVIEW-HOST/mcp
curl --fail https://YOUR-MCP-PREVIEW-HOST/healthz
curl -i "$MCP_URL"
# Expected: 401 without a JWT.
export MCP_ACCESS_TOKEN="$(node issue-token.mjs)"
python3.13 -m venv .client-venv
.client-venv/bin/pip install -r python/requirements.txt
.client-venv/bin/python check.py
# Expected result: {'id': 'demo-1001', 'status': 'shipped'}Examiner le test client
import asyncio
import os
from mcp import ClientSession
from mcp.client.streamable_http import streamablehttp_client
async def main():
url = os.getenv("MCP_URL", "http://127.0.0.1:8080/mcp")
headers = {"Authorization": "Bearer " + os.environ["MCP_ACCESS_TOKEN"]}
async with streamablehttp_client(url, headers=headers) as (read, write, _):
async with ClientSession(read, write) as session:
await session.initialize()
tools = await session.list_tools()
assert any(tool.name == "get_order" for tool in tools.tools)
result = await session.call_tool("get_order", {"order_id": "demo-1001"})
assert not result.isError, result
assert result.structuredContent == {"id": "demo-1001", "status": "shipped"}, result
print("MCP initialize, tools/list, and get_order passed:", result.structuredContent)
asyncio.run(main())Testez un JWT expiré, une audience incorrecte et l’absence de la portée orders:read. Un jeton demo-client ne doit pas pouvoir lire other-1002. L’API renvoie 404 pour la commande de cet autre tenant ; MCP renvoie une erreur d’outil.
Examinez les journaux de build et d’exécution dans le workspace. Après toute modification du code, synchronisez et redémarrez l’aperçu ; ce guide ne suppose pas de rechargement automatique.
adios ws run stop "$MCP_WORKSPACE_ID"Conserver la vérification JWT et prévoir OAuth séparément
Cette implémentation vérifie les JWT émis par l’opérateur. Elle n’implémente ni connexion utilisateur, ni consentement, ni renouvellement. Conservez les contrôles d’émetteur, d’audience, de signature, d’expiration, de portée et de tenant lors de l’ajout d’un fournisseur OAuth.
Pour une intégration destinée aux utilisateurs, suivez les exigences du guide principal concernant les métadonnées de ressource, la vérification des jetons et l’intégration des clients. Le contrôle d’accès du serveur de ressources JavaScript fourni est une implémentation distincte ; il ne suffit pas à activer OAuth sur ce serveur.
Utilisez la prise en charge de l’autorisation du serveur de ressources du SDK avec un vérificateur de jetons et les métadonnées du fournisseur. Transmettez l’identité vérifiée dans la requête API.
Revoir l’architecture OAuth communeDéployer ce serveur MCP sur Adios
Après vérification de l’aperçu du workspace, suivez le guide principal pour déployer la base de données et l’API de la version publiée. Utilisez les secrets de signature de l’équipe de publication et l’origine de cette API pour ce service MCP.
Déployer la base de données et l’API communesname: orders-mcp
region: de
replicas: 1
build_cmd: python -m venv .venv && .venv/bin/pip install -r requirements.txt
start_cmd: .venv/bin/python server.py
runtime:
name: python@3.13
port: 8080
health_path: /healthz
env:
HOST: 0.0.0.0
PORT: "8080"
PUBLIC_ORIGIN: https://mcp.example.com
API_BASE_URL: https://api.example.com
API_JWT_SECRET: secret://ORDERS_API_JWT_SECRET
MCP_JWT_SECRET: secret://ORDERS_MCP_JWT_SECRETDans ce manifeste, remplacez API_BASE_URL par l’origine HTTPS de l’API déployée et PUBLIC_ORIGIN par l’origine réelle du service MCP. Conservez les références aux secrets, le port 8080 et le chemin public de vérification de santé.
Si le nom d’hôte MCP par défaut n’est pas encore connu, récupérez-le lors du premier déploiement, mettez à jour PUBLIC_ORIGIN et redéployez avant de connecter un client. Le contrôle de l’hôte doit correspondre à la route réelle.
(cd python && adios up)
adios apps get orders-mcpadios up promotes a healthy release. It is a deployment command, not a local preview; verify the selected team and target before running it.
export MCP_ACCESS_TOKEN="$(node issue-token.mjs)"
export MCP_URL=https://YOUR-MCP-HOST/mcp
.client-venv/bin/python check.pyRégénérez le jeton de démonstration après 15 minutes. Confirmez l’appel à l’outil hébergé et les contrôles d’autorisation avant de partager l’endpoint.
Poursuivre le tutoriel commun
Une fois l’appel à l’outil hébergé réussi, suivez le guide principal pour connecter un client IA, vérifier les autorisations, examiner les journaux et exploiter le service.
Connecter et exploiter le serveur hébergé →Explorer une autre implémentation
JavaScript
Utilisez le SDK JavaScript, des schémas d’outils Zod et Express. Inclut l’exemple facultatif de serveur de ressources OAuth.
Ouvrir le guide JavaScriptGo 1.25Go
Utilisez le SDK Go, des structs typés d’entrée et de sortie et un middleware HTTP. Compilez et déployez un seul binaire de service.
Ouvrir le guide Go