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 Go 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 Go
Dans go/, le SDK officiel déduit les schémas des structures d’entrée et de sortie. Enregistrez l’outil avec mcp.AddTool, puis placez son gestionnaire Streamable HTTP derrière l’authentification. La requête API utilise le contexte de l’outil, un délai limite et un corps de réponse de taille limitée.
Module Go et version du SDK fixée
module example.com/orders-mcp
go 1.25.0
require (
github.com/golang-jwt/jwt/v5 v5.3.1
github.com/modelcontextprotocol/go-sdk v1.8.0
)
require (
github.com/google/jsonschema-go v0.4.3 // indirect
github.com/segmentio/asm v1.1.3 // indirect
github.com/segmentio/encoding v0.5.4 // indirect
github.com/yosida95/uritemplate/v3 v3.0.2 // indirect
golang.org/x/oauth2 v0.35.0 // indirect
golang.org/x/sync v0.20.0 // indirect
golang.org/x/sys v0.41.0 // indirect
golang.org/x/time v0.15.0 // indirect
)package main
import (
"context"
"encoding/json"
"errors"
"fmt"
"io"
"log"
"net/http"
"net/url"
"os"
"regexp"
"strings"
"time"
"github.com/golang-jwt/jwt/v5"
"github.com/modelcontextprotocol/go-sdk/mcp"
)
type OrderInput struct {
OrderID string `json:"order_id" jsonschema:"Order ID, using letters, numbers, or hyphens"`
}
type Order struct {
ID string `json:"id"`
Status string `json:"status"`
}
func envOr(key, fallback string) string {
if value := os.Getenv(key); value != "" {
return value
}
return fallback
}
func main() {
apiSecret, mcpSecret := os.Getenv("API_JWT_SECRET"), os.Getenv("MCP_JWT_SECRET")
if len(apiSecret) < 32 || len(mcpSecret) < 32 || apiSecret == mcpSecret {
log.Fatal("Use separate API and MCP signing secrets of at least 32 characters")
}
apiBase, err := url.Parse(envOr("API_BASE_URL", "http://127.0.0.1:8081"))
if err != nil || apiBase.Host == "" {
log.Fatal("Set a valid API_BASE_URL")
}
origin, err := url.Parse(envOr("PUBLIC_ORIGIN", "http://127.0.0.1:8080"))
if err != nil || origin.Host == "" {
log.Fatal("Set a valid PUBLIC_ORIGIN")
}
client := &http.Client{Timeout: 5 * time.Second, CheckRedirect: func(*http.Request, []*http.Request) error { return errors.New("redirects are not allowed") }}
validID := regexp.MustCompile(`^[a-zA-Z0-9-]{1,64}$`)
type principalKey struct{}
createServer := func(subject string) *mcp.Server {
server := mcp.NewServer(&mcp.Implementation{Name: "orders-mcp", Version: "1.0.0"}, nil)
mcp.AddTool(server, &mcp.Tool{Name: "get_order", Description: "Read an order's shipping status from the Orders API.", Annotations: &mcp.ToolAnnotations{ReadOnlyHint: true}},
func(ctx context.Context, _ *mcp.CallToolRequest, input OrderInput) (*mcp.CallToolResult, Order, error) {
if !validID.MatchString(input.OrderID) {
return nil, Order{}, errors.New("invalid order ID")
}
// Carry the verified caller in a new JWT intended for the API.
now := time.Now()
apiToken, err := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
"iss": "orders-demo", "aud": "orders-api", "sub": subject,
"scope": "orders:read", "iat": now.Unix(), "exp": now.Add(5 * time.Minute).Unix(),
}).SignedString([]byte(apiSecret))
if err != nil {
return nil, Order{}, errors.New("could not authorize API request")
}
endpoint := apiBase.ResolveReference(&url.URL{Path: "/orders/" + input.OrderID})
req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint.String(), nil)
if err != nil {
return nil, Order{}, errors.New("could not prepare API request")
}
req.Header.Set("Authorization", "Bearer "+apiToken)
res, err := client.Do(req)
if err != nil {
return nil, Order{}, errors.New("could not read order")
}
defer res.Body.Close()
var order Order
if res.StatusCode != http.StatusOK || json.NewDecoder(io.LimitReader(res.Body, 65536)).Decode(&order) != nil || order.ID == "" || order.Status == "" {
return nil, Order{}, errors.New("could not read order; check its ID and API access")
}
return nil, order, nil
})
return server
}
handler := mcp.NewStreamableHTTPHandler(func(r *http.Request) *mcp.Server {
subject, _ := r.Context().Value(principalKey{}).(string)
return createServer(subject)
}, &mcp.StreamableHTTPOptions{Stateless: true, JSONResponse: true})
mux := http.NewServeMux()
mux.HandleFunc("GET /healthz", func(w http.ResponseWriter, _ *http.Request) {
w.Header().Set("Content-Type", "application/json")
fmt.Fprint(w, `{"ok":true}`)
})
mux.HandleFunc("/mcp", func(w http.ResponseWriter, r *http.Request) {
if (r.Host != origin.Host && r.Host != "127.0.0.1:8080" && r.Host != "localhost:8080") || (r.Header.Get("Origin") != "" && r.Header.Get("Origin") != origin.Scheme+"://"+origin.Host) {
http.Error(w, "Invalid host or origin", http.StatusForbidden)
return
}
header := r.Header.Get("Authorization")
if !strings.HasPrefix(header, "Bearer ") {
http.Error(w, "Missing access token", http.StatusUnauthorized)
return
}
token, err := jwt.Parse(strings.TrimPrefix(header, "Bearer "), func(*jwt.Token) (any, error) { return []byte(mcpSecret), nil },
jwt.WithValidMethods([]string{"HS256"}), jwt.WithIssuer("orders-demo"), jwt.WithAudience("orders-mcp"), jwt.WithExpirationRequired())
if err != nil || !token.Valid {
http.Error(w, "Invalid or expired access token", http.StatusUnauthorized)
return
}
claims, ok := token.Claims.(jwt.MapClaims)
subject, subjectErr := claims.GetSubject()
if !ok || subjectErr != nil || subject == "" {
http.Error(w, "Missing subject", http.StatusUnauthorized)
return
}
scopes, _ := claims["scope"].(string)
permitted := false
for _, scope := range strings.Fields(scopes) {
if scope == "orders:read" {
permitted = true
}
}
if !permitted {
http.Error(w, "orders:read permission required", http.StatusForbidden)
return
}
r.Body = http.MaxBytesReader(w, r.Body, 65536)
handler.ServeHTTP(w, r.WithContext(context.WithValue(r.Context(), principalKey{}, subject)))
})
httpServer := &http.Server{Addr: envOr("HOST", "127.0.0.1") + ":" + envOr("PORT", "8080"), Handler: mux, ReadHeaderTimeout: 5 * time.Second, IdleTimeout: 60 * time.Second}
log.Fatal(httpServer.ListenAndServe())
}Chaque requête crée un serveur sans état pour l’appelant vérifié et rejette les identifiants invalides avant d’appeler l’API. readOnlyHint décrit l’outil aux clients ; ce n’est qu’une indication, donc l’API doit toujours faire respecter les permissions.
Référence officielle : SDK serveur MCP pour Go.
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 go && 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 go && 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.
Vérifiez les jetons du fournisseur dans le middleware avant le gestionnaire MCP et transmettez l’identité dans le contexte de la requête. Associez l’émetteur et le sujet à l’identité canonique de l’application.
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: go build -o orders-mcp .
start_cmd: ./orders-mcp
runtime:
name: go@1.25
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 go && 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 JavaScriptPython 3.13Python
Utilisez FastMCP, des outils typés, HTTPX et un contrôle JWT ASGI. Retrouvez la configuration et le déploiement Python dans un seul guide.
Ouvrir le guide Python