Empezar con un ejemplo en ejecución
Lánzalo y adáptalo a tu proyecto
Crea la API Orders, una base PostgreSQL y el servidor MCP que elijas en Adios. Subimos el código, configuramos credenciales JWT independientes e iniciamos las vistas previas de desarrollo.
Lanzar en Adios →¿Aún no tienes cuenta? Crea una y vuelve a este ejemplo. Selecciona un equipo y revisa los recursos antes de lanzarlo. El ejemplo completo requiere un plan de pago con capacidad para dos espacios y una base de datos.
Créalo paso a paso
Seguir la guía manual
Diseña la API y el esquema, añade autenticación JWT, escribe las herramientas MCP y ejecútalas en tus espacios de desarrollo. Cada paso incluye código y comandos.
Seguir los pasos manuales →Explora primero el ejemplo en ejecución. OAuth para el acceso de usuarios y el despliegue en producción son pasos posteriores.
Empezar por la API y la base de datos
Crea un servidor MCP en Go que consulte pedidos a través de una API REST. Añadirás autenticación JWT, probarás el servidor en un workspace de Adios y lo desplegarás.
Antes de empezar, necesitas una API en ejecución y su clave de firma JWT. La guía principal incluye una API de pedidos de ejemplo con Node.js 24 y PostgreSQL, e instrucciones para iniciar su workspace de desarrollo.
El proceso API escucha en el puerto 8081 y MCP en el 8080 dentro de sus workspaces. Los clientes usan las URL HTTPS generadas de las vistas previas. Mantén API_JWT_SECRET y MCP_JWT_SECRET separados.
Crea un servidor MCP en Go
En go/, el SDK oficial deriva los esquemas de las estructuras de entrada y salida. Registra la herramienta con mcp.AddTool y monta su manejador Streamable HTTP detrás de la autenticación. La solicitud a la API usa el contexto de la herramienta, un tiempo límite y un cuerpo de respuesta de tamaño limitado.
Módulo Go y SDK con versión fijada
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())
}Cada solicitud crea un servidor sin estado para el solicitante verificado y rechaza IDs no válidos antes de llamar a la API. readOnlyHint describe la herramienta a los clientes; es una indicación, por lo que la API debe seguir aplicando los permisos.
Referencia oficial: SDK de servidor MCP para Go.
Probar en un workspace de Adios
Primero inicia el workspace API de la guía principal. En el manifiesto de este lenguaje, establece API_BASE_URL con el origen de la vista previa de la API y usa los secretos de firma del mismo equipo de desarrollo.
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 el origen generado de la vista previa MCP en PUBLIC_ORIGIN en adios.yaml. Sincroniza y reinicia antes de probar: la comprobación del host debe coincidir con el nombre real de la vista previa. Las sondas de salud pueden ejecutarse antes de esta actualización.
(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'}Inspeccionar la comprobación del cliente
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())Prueba un JWT caducado, una audiencia incorrecta y la ausencia del ámbito orders:read. Un token de demo-client no debe poder leer other-1002. La API devuelve 404 para el pedido de ese otro tenant; MCP devuelve un error de herramienta.
Revisa los registros de compilación y ejecución en el workspace. Tras cambiar el código, sincroniza y reinicia la vista previa; esta guía no presupone recarga automática.
adios ws run stop "$MCP_WORKSPACE_ID"Mantener la verificación JWT y planificar OAuth por separado
Esta implementación verifica los JWT emitidos por el operador. No implementa inicio de sesión de usuarios, consentimiento ni renovación. Conserva las comprobaciones de emisor, audiencia, firma, caducidad, ámbito y tenant al añadir un proveedor OAuth.
Para una integración con usuarios, sigue los requisitos de metadatos del recurso, verificación de tokens e incorporación de clientes de la guía principal. El control de acceso del servidor de recursos JavaScript incluido es una implementación independiente; no activa OAuth automáticamente en este servidor.
Verifica los tokens del proveedor en el middleware antes del controlador MCP y transmite la identidad en el contexto de la solicitud. Asocia el emisor y el sujeto con la identidad canónica de la aplicación.
Revisar la arquitectura OAuth compartidaDesplegar este servidor MCP en Adios
Tras verificar la vista previa del workspace, sigue la guía principal para desplegar la base de datos y la API de la versión publicada. Usa los secretos de firma del equipo de publicación y el origen de esa API para este servicio MCP.
Desplegar la base de datos y la API compartidasname: 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_SECRETEn este manifiesto, sustituye API_BASE_URL por el origen HTTPS de la API desplegada y PUBLIC_ORIGIN por el origen real del servicio MCP. Conserva las referencias a secretos, el puerto 8080 y la ruta pública de salud.
Si aún no conoces el nombre de host MCP predeterminado, obténlo del primer despliegue, actualiza PUBLIC_ORIGIN y vuelve a desplegar antes de conectar un cliente. La comprobación del host debe coincidir con la ruta real.
(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.pyRegenera el token de demostración al cabo de 15 minutos. Confirma la llamada a la herramienta alojada y las comprobaciones de permisos antes de compartir el endpoint.
Continuar el tutorial compartido
Cuando funcione la llamada a la herramienta alojada, sigue la guía principal para conectar un cliente de IA, comprobar los permisos, inspeccionar los registros y administrar el servicio.
Conectar y administrar el servidor alojado →Explorar otra implementación
JavaScript
Usa el SDK de JavaScript, esquemas de herramientas Zod y Express. Incluye el ejemplo opcional de servidor de recursos OAuth.
Abrir la guía de JavaScriptPython 3.13Python
Usa FastMCP, herramientas tipadas, HTTPX y un control JWT ASGI. Mantén la configuración y el despliegue de Python en una sola guía.
Abrir la guía de Python