Receitas
Falar com uma API de terceiros
Uma conexão que alguém cadastra numa tela — URL, token, Testar — e o cliente que o código pede, que leva a credencial e só fala com aquele host.
ctx --pack · est. 925 tokens est. · llms.txt da receita
O token do serviço fiscal está numa variável de ambiente, a URL do servidor MCP em outra, e no dia em que uma delas muda alguém faz deploy de novo. A alternativa é a tela que toda aplicação de gestão acaba tendo: a lista das coisas de fora com que esta fala, com o segredo selado e um botão que diz se ainda funciona.
trilha add connections1. Os tipos#
A receita escreve o internal/conexoes com dois tipos. Cada um diz quais autenticações aceita e como é testado:
var Tipos = []trilha.ConnectionKind{
{Key: "api", Label: "{{.T.conn_api}}", Auth: []string{"none", "bearer", "header", "basic"},
// Any answer below 400 passes: the point is "does the credential
// open the door", not what is behind it.
Test: trilha.TestHTTP("GET", "/")},
{Key: "mcp", Label: "{{.T.conn_mcp}}", Auth: []string{"none", "bearer", "header"},
Test: testarMCP},
}({{.T.…}} é a tabela de palavras da receita; no seu projeto já vem como "APIs" e "Servidores MCP".)
O teste do MCP é o handshake e a lista de ferramentas, pelo cliente da própria conexão, para o token ir junto sem ser copiado para um mapa de cabeçalhos:
func testarMCP(ctx context.Context, conn trilha.Connection) error {
cli, err := mcp.Dial(ctx, mcp.HTTPWith(conn.URL, conn.Client(0), nil))
if err != nil {
return err
}
defer cli.Close()
_, err = cli.ListTools(ctx)
return err
}2. A tela#
/conexoes é o ui.ConnectionsPanel: a lista agrupada por tipo com o badge do último teste, e o formulário. Salvar, Testar e Apagar são três POSTs para o mesmo caminho com um _action oculto, e a página os lê assim:
case "test":
res, err := conns.Test(c, c.Form("id"))
if err != nil {
return err
}
if res.OK {
c.Flash(ui.FlashSuccess, "{{.T.conn_test_ok}}")
} else {
c.Flash(ui.FlashError, "{{.T.conn_test_failed}} " + res.Message)
}O segredo é digitado uma vez. Na edição o campo volta vazio com "deixe em branco para manter", e a trilha de auditoria (connection.save, connection.test) diz quem mudou o quê sem nunca levar o valor. Guarde a pasta: quem chega nela pode apontar a sua aplicação para um servidor próprio.
3. Usar#
Onde a aplicação chama o serviço, ela pede o cliente em vez de ler uma variável:
func Chamar(c *trilha.Ctx, id, path string) (*http.Response, error) {
conn, err := Conexoes.Get(c, id)
if err != nil {
return nil, err
}
cli, err := Conexoes.Client(c, id)
if err != nil {
return nil, err
}
req, err := http.NewRequestWithContext(c.Context(), http.MethodGet, conn.URL+path, nil)
if err != nil {
return nil, err
}
return cli.Do(req)
}O cliente já tem o Authorization (ou o cabeçalho nomeado, ou o par basic), os cabeçalhos fixos e um timeout — e só fala com o host da conexão. Uma requisição montada para outro domínio, ou um redirect que aponte para lá, é recusada antes de qualquer byte sair: o token não consegue seguir um Location que outra pessoa controla.
No que reparar#
- Em produção a URL tem de ser pública.
http://127.0.0.1e nomes.internalsão recusados peloSavecom a mensagem no campo; emdevpassam com aviso, porque é lá que o serviço em teste mora. - O store é memória na receita: as conexões duram o processo. Uma tabela é uma linha por conexão,
headersem JSON esecretselado peloSecret.Value()— a referência tem a interface. - Tipo sem
Testnão tem botão. Escreva o teste no dia em que souber o que "funciona" significa para aquele serviço; um verde falso é pior que badge nenhum.