Inizia con un esempio funzionante
Avvia l’esempio e personalizzalo
Crea l’API Orders, un database PostgreSQL e il server MCP che preferisci in Adios. Carichiamo il codice, configuriamo credenziali JWT separate e avviamo le anteprime di sviluppo.
Avvia su Adios →Non hai ancora un account? Creane uno e torna a questo esempio. Scegli un team e controlla le risorse prima di avviare. L’esempio completo richiede un piano a pagamento con spazio per due workspace e un database.
Costruiscilo passo dopo passo
Segui la guida manuale
Progetta l’API e lo schema, aggiungi l’autenticazione JWT, scrivi gli strumenti MCP ed eseguili nei tuoi workspace di sviluppo. Ogni passaggio include codice e comandi.
Segui i passaggi manuali →Esplora prima l’esempio in esecuzione. OAuth per l’accesso degli utenti e il deployment in produzione vengono dopo.
Iniziare dall’API e dal database
Crea un server MCP in Go che consulti gli ordini tramite un’API REST. Aggiungerai l’autenticazione JWT, proverai il server in un workspace Adios e lo distribuirai.
Prima di iniziare, ti servono un’API in esecuzione e la sua chiave di firma JWT. La guida principale include un’API degli ordini di esempio con Node.js 24 e PostgreSQL e le istruzioni per avviare il suo workspace di sviluppo.
Il processo API ascolta sulla porta 8081 e MCP sulla porta 8080 nei rispettivi workspace. I client usano gli URL HTTPS generati delle anteprime. Mantieni separati API_JWT_SECRET e MCP_JWT_SECRET.
Crea un server MCP in Go
In go/, l’SDK ufficiale ricava gli schemi dalle strutture di input e output. Registra lo strumento con mcp.AddTool, poi monta il suo handler Streamable HTTP dietro l’autenticazione. La richiesta API usa il contesto dello strumento, un timeout e un corpo di risposta di dimensione limitata.
Modulo Go e versione SDK fissata
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())
}Ogni richiesta crea un server stateless per il chiamante verificato e rifiuta ID non validi prima di chiamare l’API. readOnlyHint descrive lo strumento ai client; è un’indicazione, quindi l’API deve comunque applicare i permessi.
Riferimento ufficiale: SDK server MCP per Go.
Provare in un workspace Adios
Avvia prima il workspace API della guida principale. Nel manifesto di questo linguaggio, imposta API_BASE_URL sull’origine dell’anteprima dell’API e usa i segreti di firma dello stesso team di sviluppo.
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"Copia l’origine generata dell’anteprima MCP in PUBLIC_ORIGIN in adios.yaml. Sincronizza e riavvia prima dei test: il controllo dell’host deve corrispondere al nome effettivo dell’anteprima. Le sonde di stato possono funzionare prima di questo aggiornamento.
(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'}Esaminare il test del 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())Prova un JWT scaduto, un’audience errata e l’assenza dello scope orders:read. Un token demo-client non deve poter leggere other-1002. L’API restituisce 404 per l’ordine di quell’altro tenant; MCP restituisce un errore dello strumento.
Esamina i log di build ed esecuzione nel workspace. Dopo le modifiche al codice, sincronizza e riavvia l’anteprima; questa guida non presuppone il ricaricamento automatico.
adios ws run stop "$MCP_WORKSPACE_ID"Mantenere la verifica JWT e pianificare OAuth separatamente
Questa implementazione verifica i JWT emessi dall’operatore. Non implementa accesso degli utenti, consenso o rinnovo. Mantieni i controlli di emittente, audience, firma, scadenza, scope e tenant quando aggiungi un provider OAuth.
Per un’integrazione rivolta agli utenti, segui i requisiti della guida principale per i metadati della risorsa, la verifica dei token e l’integrazione dei client. Il controllo di accesso del server di risorse JavaScript incluso è un’implementazione separata; non attiva automaticamente OAuth su questo server.
Verifica i token del provider nel middleware prima del gestore MCP e trasmetti l’identità nel contesto della richiesta. Associa emittente e soggetto all’identità canonica dell’applicazione.
Rivedere l’architettura OAuth condivisaDistribuire questo server MCP su Adios
Dopo aver verificato l’anteprima del workspace, segui la guida principale per distribuire il database e l’API della versione pubblicata. Usa i segreti di firma del team di pubblicazione e l’origine di quell’API per questo servizio MCP.
Distribuire il database e l’API condivisiname: 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_SECRETIn questo manifesto, sostituisci API_BASE_URL con l’origine HTTPS dell’API distribuita e PUBLIC_ORIGIN con l’origine effettiva del servizio MCP. Mantieni i riferimenti ai segreti, la porta 8080 e il percorso pubblico di controllo dello stato.
Se il nome host MCP predefinito non è ancora noto, ricavalo dal primo deploy, aggiorna PUBLIC_ORIGIN e distribuisci nuovamente prima di collegare un client. Il controllo dell’host deve corrispondere alla route effettiva.
(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.pyRigenera il token dimostrativo dopo 15 minuti. Conferma la chiamata allo strumento ospitato e i controlli dei permessi prima di condividere l’endpoint.
Continuare il tutorial condiviso
Quando la chiamata allo strumento ospitato riesce, segui la guida principale per collegare un client IA, verificare i permessi, esaminare i log e gestire il servizio.
Collegare e gestire il server ospitato →Esplorare un’altra implementazione
JavaScript
Usa l’SDK JavaScript, schemi degli strumenti Zod ed Express. Include l’esempio facoltativo di server di risorse OAuth.
Aprire la guida JavaScriptPython 3.13Python
Usa FastMCP, strumenti tipizzati, HTTPX e un controllo JWT ASGI. Trova la configurazione e la distribuzione Python in un’unica guida.
Aprire la guida Python