Comece com um exemplo em execução
Inicie o exemplo e adapte-o
Crie a API Orders, um banco PostgreSQL e o servidor MCP escolhido no Adios. Enviamos o código, configuramos credenciais JWT separadas e iniciamos as prévias de desenvolvimento.
Iniciar no Adios →Ainda não tem conta? Crie uma e volte a este exemplo. Escolha uma equipe e confira os recursos antes de iniciar. O exemplo completo requer um plano pago com capacidade para dois espaços e um banco de dados.
Crie passo a passo
Seguir o guia manual
Projete a API e o esquema, adicione autenticação JWT, escreva as ferramentas MCP e execute-as nos seus espaços de desenvolvimento. Cada etapa inclui código e comandos.
Seguir as etapas manuais →Explore primeiro o exemplo em execução. OAuth para login de usuários e a implantação em produção vêm depois.
Começar pela API e pelo banco de dados
Crie um servidor MCP em Go que consulte pedidos por uma API REST. Você adicionará autenticação JWT, testará o servidor em um workspace do Adios e fará o deploy.
Antes de começar, você precisa de uma API em execução e da sua chave de assinatura JWT. O guia principal inclui uma API de pedidos de exemplo com Node.js 24 e PostgreSQL, além das instruções para iniciar seu workspace de desenvolvimento.
O processo da API escuta na porta 8081 e o MCP na porta 8080 dentro dos workspaces. Os clientes usam as URLs HTTPS geradas das prévias. Mantenha API_JWT_SECRET e MCP_JWT_SECRET separados.
Crie um servidor MCP em Go
Em go/, o SDK oficial deriva os esquemas das estruturas de entrada e saída. Registre a ferramenta com mcp.AddTool e coloque seu handler Streamable HTTP atrás da autenticação. A solicitação à API usa o contexto da ferramenta, um tempo limite e um corpo de resposta com tamanho limitado.
Módulo Go e versão fixa do SDK
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 solicitação cria um servidor sem estado para o solicitante verificado e rejeita IDs inválidos antes de chamar a API. readOnlyHint descreve a ferramenta para os clientes; é uma indicação, portanto a API ainda precisa aplicar as permissões.
Referência oficial: SDK de servidor MCP para Go.
Testar em um workspace do Adios
Primeiro inicie o workspace da API do guia principal. No manifesto desta linguagem, defina API_BASE_URL com a origem da prévia da API e use os segredos de assinatura da mesma equipe de desenvolvimento.
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"Copie a origem gerada da prévia MCP para PUBLIC_ORIGIN em adios.yaml. Sincronize e reinicie antes de testar: a verificação de host deve corresponder ao nome real da prévia. As sondas de integridade podem funcionar antes dessa atualização.
(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'}Examinar a verificação do 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())Teste um JWT expirado, uma audiência incorreta e a ausência do escopo orders:read. Um token de demo-client não deve conseguir ler other-1002. A API retorna 404 para o pedido desse outro tenant; o MCP retorna um erro de ferramenta.
Revise os logs de build e execução no workspace. Após alterar o código, sincronize e reinicie a prévia; este guia não pressupõe recarga automática.
adios ws run stop "$MCP_WORKSPACE_ID"Manter a verificação JWT e planejar OAuth separadamente
Esta implementação verifica JWTs emitidos pelo operador. Ela não implementa login de usuários, consentimento nem renovação. Mantenha as verificações de emissor, audiência, assinatura, expiração, escopo e tenant ao adicionar um provedor OAuth.
Para uma integração voltada aos usuários, siga os requisitos do guia principal sobre metadados do recurso, verificação de tokens e integração dos clientes. O controle de acesso do servidor de recursos JavaScript incluído é uma implementação separada; ele não ativa OAuth automaticamente neste servidor.
Verifique os tokens do provedor no middleware antes do handler MCP e transmita a identidade no contexto da requisição. Associe o emissor e o sujeito à identidade canônica da aplicação.
Revisar a arquitetura OAuth compartilhadaFazer deploy deste servidor MCP no Adios
Após verificar a prévia do workspace, siga o guia principal para fazer o deploy do banco de dados e da API da versão publicada. Use os segredos de assinatura da equipe de publicação e a origem dessa API para este serviço MCP.
Fazer deploy do banco de dados e da API compartilhadosname: 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_SECRETNeste manifesto, substitua API_BASE_URL pela origem HTTPS da API implantada e PUBLIC_ORIGIN pela origem real do serviço MCP. Mantenha as referências aos segredos, a porta 8080 e o caminho público de verificação de integridade.
Se o nome de host padrão do MCP ainda não for conhecido, obtenha-o no primeiro deploy, atualize PUBLIC_ORIGIN e faça o deploy novamente antes de conectar um cliente. A verificação de host deve corresponder à rota 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.pyGere novamente o token de demonstração após 15 minutos. Confirme a chamada à ferramenta hospedada e as verificações de permissão antes de compartilhar o endpoint.
Continuar o tutorial compartilhado
Quando a chamada à ferramenta hospedada funcionar, siga o guia principal para conectar um cliente de IA, verificar permissões, examinar os logs e operar o serviço.
Conectar e operar o servidor hospedado →Explorar outra implementação
JavaScript
Use o SDK de JavaScript, esquemas de ferramentas Zod e Express. Inclui o exemplo opcional de servidor de recursos OAuth.
Abrir o guia de JavaScriptPython 3.13Python
Use FastMCP, ferramentas tipadas, HTTPX e um controle JWT ASGI. Mantenha a configuração e o deploy do Python em um só guia.
Abrir o guia de Python