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 JavaScript 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 JavaScript
Em javascript/, instale as dependências com npm ci. Registre get_order com um esquema Zod de entrada, verifique o JWT do cliente, chame a API com limite de cinco segundos e retorne texto e dados estruturados. Cada POST usa um novo transporte, sem estado de sessão MCP compartilhado.
Dependências e comando de inicialização
{
"name": "adios-orders-mcp-example",
"version": "1.0.0",
"private": true,
"type": "module",
"scripts": {
"start": "node server.mjs"
},
"dependencies": {
"@modelcontextprotocol/sdk": "1.32.1",
"express": "^5.1.0",
"jose": "^6.1.0",
"zod": "^4.1.0"
}
}import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
import express from "express";
import { jwtVerify, SignJWT } from "jose";
import { z } from "zod";
import { installOAuthGate } from "./oauth.mjs";
const apiBase = new URL(process.env.API_BASE_URL || "http://127.0.0.1:8081");
const apiSecret = process.env.API_JWT_SECRET;
const mcpSecret = process.env.MCP_JWT_SECRET;
const authMode = process.env.MCP_AUTH_MODE || "jwt";
if (!["jwt", "oauth"].includes(authMode))
throw new Error("Unknown MCP_AUTH_MODE");
if (
!apiSecret ||
apiSecret.length < 32 ||
(authMode === "jwt" && (!mcpSecret || mcpSecret.length < 32))
) {
throw new Error(
"Set distinct API_JWT_SECRET and MCP_JWT_SECRET values of at least 32 characters",
);
}
if (apiSecret === mcpSecret)
throw new Error("Use separate API and MCP credentials");
const apiKey = new TextEncoder().encode(apiSecret);
const origin = new URL(process.env.PUBLIC_ORIGIN || "http://127.0.0.1:8080");
const app = express();
app.get("/healthz", (_req, res) => res.json({ ok: true }));
app.use("/mcp", (req, res, next) => {
if (
![origin.host, "127.0.0.1:8080", "localhost:8080"].includes(
req.headers.host,
)
) {
return res.status(403).json({ error: "Invalid host" });
}
if (req.headers.origin && req.headers.origin !== origin.origin) {
return res.status(403).json({ error: "Invalid origin" });
}
next();
});
if (authMode === "oauth") {
installOAuthGate(app, origin.origin);
} else {
app.use("/mcp", async (req, res, next) => {
try {
const match = /^Bearer (\S+)$/.exec(req.headers.authorization || "");
if (!match) throw new Error("Missing token");
const { payload } = await jwtVerify(
match[1],
new TextEncoder().encode(mcpSecret),
{
issuer: "orders-demo",
audience: "orders-mcp",
algorithms: ["HS256"],
requiredClaims: ["sub", "exp"],
},
);
if (typeof payload.sub !== "string" || !payload.sub)
throw new Error("Missing subject");
if (
typeof payload.scope !== "string" ||
!payload.scope.split(" ").includes("orders:read")
) {
return res
.status(403)
.json({ error: "orders:read permission required" });
}
res.locals.principal = { subject: payload.sub };
next();
} catch {
res.set("WWW-Authenticate", 'Bearer error="invalid_token"');
res.status(401).json({ error: "Invalid or expired access token" });
}
});
}
app.use(express.json({ limit: "64kb" }));
function createServer(subject) {
const server = new McpServer({ name: "orders-mcp", version: "1.0.0" });
server.registerTool(
"get_order",
{
description: "Read an order's shipping status from the Orders API.",
inputSchema: { order_id: z.string().regex(/^[a-zA-Z0-9-]{1,64}$/) },
annotations: { readOnlyHint: true },
},
async ({ order_id }) => {
try {
// A new API-audience JWT carries the verified caller, never tool input.
const apiToken = await new SignJWT({ scope: "orders:read" })
.setProtectedHeader({ alg: "HS256", typ: "JWT" })
.setIssuer("orders-demo")
.setAudience("orders-api")
.setSubject(subject)
.setIssuedAt()
.setExpirationTime("5m")
.sign(apiKey);
const response = await fetch(new URL(`/orders/${order_id}`, apiBase), {
headers: { Authorization: `Bearer ${apiToken}` },
signal: AbortSignal.timeout(5000),
redirect: "error",
});
if (!response.ok) throw new Error("API lookup failed");
const order = z
.object({ id: z.string(), status: z.string() })
.parse(await response.json());
return {
content: [{ type: "text", text: JSON.stringify(order) }],
structuredContent: order,
};
} catch {
return {
isError: true,
content: [
{
type: "text",
text: "Could not read this order. Check its ID and your API access.",
},
],
};
}
},
);
return server;
}
app.post("/mcp", async (req, res) => {
const server = createServer(res.locals.principal.subject);
const transport = new StreamableHTTPServerTransport({
sessionIdGenerator: undefined,
enableJsonResponse: true,
});
res.on("close", () => {
void transport.close();
void server.close();
});
try {
await server.connect(transport);
await transport.handleRequest(req, res, req.body);
} catch {
if (!res.headersSent) res.status(500).json({ error: "MCP request failed" });
}
});
// This stateless example does not keep an SSE stream or session to delete.
app.get("/mcp", (_req, res) => res.sendStatus(405));
app.delete("/mcp", (_req, res) => res.sendStatus(405));
app.listen(Number(process.env.PORT || 8080), process.env.HOST || "127.0.0.1");O arquivo importado oauth.mjs está incluído no download. O modo padrão valida um JWT assinado; a seção OAuth deste guia explica como mudar de modo. Mantenha as verificações de host e origem e defina PUBLIC_ORIGIN com a origem pública real ao hospedá-lo.
Referência oficial: SDK de servidor MCP para JavaScript.
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 javascript && 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 javascript && 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"Adicionar OAuth ao servidor JavaScript
Controle OAuth opcional em JavaScript
O servidor JavaScript para download inclui este controle de recurso protegido. Ele verifica tokens JWT RS256 com o JWKS público do provedor e publica metadados MCP. Exige uma audiência igual à URL MCP completa e uma declaração scope separada por espaços contendo orders:read.
import { createRemoteJWKSet, jwtVerify } from "jose";
// This is a resource server, not an OAuth authorization server.
// Configure a provider that issues RS256 JWT access tokens for this resource.
export function installOAuthGate(app, publicOrigin) {
const issuer = process.env.OAUTH_ISSUER;
const jwksURL = process.env.OAUTH_JWKS_URL;
if (
!issuer ||
!jwksURL ||
new URL(issuer).protocol !== "https:" ||
new URL(jwksURL).protocol !== "https:"
) {
throw new Error(
"Set HTTPS OAUTH_ISSUER and OAUTH_JWKS_URL from your provider",
);
}
const resource = `${publicOrigin}/mcp`;
const metadataURL = `${publicOrigin}/.well-known/oauth-protected-resource/mcp`;
const keys = createRemoteJWKSet(new URL(jwksURL));
app.get("/.well-known/oauth-protected-resource/mcp", (_req, res) => {
res.json({
resource,
authorization_servers: [issuer],
scopes_supported: ["orders:read"],
bearer_methods_supported: ["header"],
});
});
app.use("/mcp", async (req, res, next) => {
const match = /^Bearer (\S+)$/.exec(req.headers.authorization || "");
if (!match) {
res.set(
"WWW-Authenticate",
`Bearer resource_metadata="${metadataURL}", scope="orders:read"`,
);
return res.status(401).json({ error: "Sign in through your MCP client" });
}
try {
const { payload } = await jwtVerify(match[1], keys, {
issuer,
audience: resource,
algorithms: ["RS256"],
requiredClaims: ["sub", "exp"],
});
if (typeof payload.sub !== "string" || !payload.sub)
throw new Error("Missing subject");
const scopes =
typeof payload.scope === "string" ? payload.scope.split(" ") : [];
if (!scopes.includes("orders:read")) {
res.set(
"WWW-Authenticate",
`Bearer error="insufficient_scope", scope="orders:read", resource_metadata="${metadataURL}"`,
);
return res
.status(403)
.json({ error: "orders:read permission required" });
}
// The handler carries this identity in a distinct API-audience JWT.
res.locals.principal = { subject: payload.sub, scopes };
next();
} catch {
res.set(
"WWW-Authenticate",
`Bearer error="invalid_token", resource_metadata="${metadataURL}"`,
);
res.status(401).json({ error: "Invalid or expired access token" });
}
});
}Configure o provedor primeiro: registre o recurso, permita seu escopo, aceite código de autorização com PKCE e organize o cadastro de clientes. Use as URLs exatas do emissor e do JWKS presentes nos metadados. Este middleware não implementa login, consentimento, cadastro nem emissão de tokens. Tokens opacos exigem introspecção; outros algoritmos ou declarações de escopo precisam de um verificador adaptado.
env:
HOST: 0.0.0.0
PORT: "8080"
PUBLIC_ORIGIN: https://YOUR-MCP-HOST
API_BASE_URL: https://YOUR-API-HOST
API_JWT_SECRET: secret://ORDERS_API_JWT_SECRET
MCP_AUTH_MODE: oauth
OAUTH_ISSUER: https://YOUR-AUTHORIZATION-SERVER
OAUTH_JWKS_URL: https://YOUR-AUTHORIZATION-SERVER/YOUR-JWKS-PATH
# Remove MCP_JWT_SECRET in OAuth mode.O controle registra o sujeito verificado em res.locals.principal. O handler o leva em um novo JWT com audiência API, e a API busca seu tenant em principals. Provisione os sujeitos reais do provedor nessa tabela; os dados de exemplo só incluem demo-client e other-client. Controle o provisionamento e nunca deixe o solicitante atribuir seu próprio tenant.
Fazer 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: npm ci
start_cmd: npm start
runtime:
name: node@24
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 javascript && 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
Python
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 PythonGo 1.25Go
Use o SDK de Go, structs tipadas de entrada e saída e middleware HTTP. Compile e faça o deploy de um único binário de serviço.
Abrir o guia de Go