Repository navigation
Home > MCP 🚧
Important
Em desenvolvimento (2.0). Ainda não está numa versão lançada e pode mudar.
Vale para a 2.0 · Delphi XE em diante e Lazarus/FPC 3.2 em diante · Nível: intermediário
Em uma frase: o MCP é o protocolo que assistentes de IA usam para chamar ferramentas, ler
recursos e pedir prompts de um programa seu; o TRALMCPServer declara tudo isso e o
TRALMCPModule (HTTP) ou o TRALMCPStdio (console) o entrega.
Você vai precisar: o pacote RALModuleMCPPackage (paleta RAL - Modules; o cliente
TRALMCPClient fica em RAL - Client), que requer o RALModuleJSONRPCPackage (ver JSON-RPC). Para
HTTP, um motor; para stdio, só um programa de console.
Warning
O RAL fala só a revisão 2026-07-28 do MCP, que não tem estado: não há initialize nem
sessão, e cada pedido leva a versão e as capacidades do cliente. Um cliente que fale só
revisões anteriores recebe o erro UnsupportedProtocolVersion (-32022). O MCP Inspector e
os clientes de IA que você já tem podem ainda falar essas revisões antigas e não conseguir
conectar enquanto não migrarem.
O TRALMCPServer não conhece transporte: guarda o que o servidor oferece e responde às mensagens.
| propriedade | |
|---|---|
Tools |
as ferramentas que o modelo pode chamar |
Resources / ResourceTemplates
|
conteúdos que o cliente lê por URI, fixos ou por modelo (file:///docs/{path}) |
Prompts |
modelos de prompt que o usuário escolhe, com argumentos |
OnComplete |
completa argumentos de prompts e recursos (até 100 valores) |
ServerName, ServerVersion, ServerTitle, ServerDescription, WebsiteURL
|
o que o servidor diz de si (serverInfo) |
Instructions |
texto que ensina o modelo a usar o servidor |
CacheTTL / CacheScope
|
por quanto tempo (ms; 0 = nada) e para quem (mcsPrivate, mcsPublic) o cliente pode guardar listas e leituras |
PageSize |
itens por página nas listas; 0 = tudo numa página |
StateKey / StateLife
|
segredo (HMAC) e validade (s, 600 por padrão) do estado dos pedidos de dados; ver abaixo |
TaskTTL, TaskPollInterval, TaskStore
|
tarefas; ver Tarefas |
uses RALMCPServer;
Servidor := TRALMCPServer.Create(Self);
Servidor.ServerName := 'minha-loja';
Servidor.ServerVersion := '1.0';
Servidor.Instructions := 'Use "estoque" para consultar o saldo de um produto.';
Ferramenta := Servidor.Tools.AddTool('estoque', 'Saldo de um produto',
{$IFDEF FPC}@{$ENDIF}Estoque);
Ferramenta.Params.AddParam('codigo', mptString, 'Código do produto');
Ferramenta.Params.AddParam('deposito', mptInteger, 'Número do depósito', False);
Ferramenta.Hints := [thReadOnly, thIdempotent];
procedure TForm1.Estoque(Sender: TObject; ACall: TRALMCPCall; AResult: TRALMCPToolResult);
begin
ACall.Progress(1, 2, 'consultando');
AResult.AddText('saldo de ' + ACall.Argument('codigo').AsString + ': 42');
end;| propriedade da ferramenta | |
|---|---|
Name |
1 a 128 caracteres: letras, dígitos, _, - e .; único e com distinção de maiúsculas |
Description, Title
|
para o modelo e para pessoas |
Params |
parâmetros declarados (Name, ParamType, Required, Description, Values = enum); deles sai o inputSchema, e o RAL confere o pedido |
InputSchema |
um JSON Schema escrito à mão, no lugar do gerado por Params
|
Header (do parâmetro) |
o x-mcp-header: o cliente copia o valor para o cabeçalho Mcp-Param-<Header>; só para texto, inteiro e booleano que não sejam segredo |
OutputSchema |
o schema do conteúdo estruturado; com ele a ferramenta tem de chamar AResult.SetStructured
|
Hints |
thReadOnly, thDestructive, thIdempotent, thOpenWorld; os clientes os tratam como não confiáveis |
Roles |
papéis (vírgula = qualquer um) que o usuário autenticado precisa; sem eles a ferramenta nem aparece |
RunAsTask |
roda como tarefa |
No OnCall, o ACall traz Argument('nome') (nil se faltar), Arguments, ClientName,
HasCapability('elicitation') e Cancelled (o chamador foi embora ou cancelou: pare o trabalho).
ACall.Progress(atual, total, mensagem) manda notifications/progress e
ACall.Log('info', '"texto"') manda notifications/message (o dado é texto JSON); os dois só
saem se o cliente pediu e o transporte envia ao vivo.
| método | |
|---|---|
AddText |
bloco de texto |
AddImage(base64, mime), AddAudio(base64, mime)
|
imagem e áudio |
AddResourceLink(uri, nome, mime, descrição) |
aponta um recurso |
AddResourceText(uri, mime, texto), AddResourceBlob(uri, mime, base64)
|
recurso embutido |
SetStructured(TRALJSON) |
conteúdo estruturado (passa a ser dele) e espelhado num bloco de texto |
Fail('mensagem') |
marca isError: a ferramenta falhou na execução e o modelo lê a mensagem, ao contrário de um erro de protocolo |
Ferramenta.OutputSchema := '{"type":"object","properties":{"saldo":{"type":"number"}}}';
procedure TForm1.Saldo(Sender: TObject; ACall: TRALMCPCall; AResult: TRALMCPToolResult);
begin
if ACall.Argument('codigo').AsString = '' then
AResult.Fail('código vazio')
else
AResult.SetStructured(TRALJSON.ParseJSON('{"saldo":42}'));
end;Servidor.Resources.AddResource('file:///config.json', 'config', {$IFDEF FPC}@{$ENDIF}LerConfig,
'application/json');
Servidor.ResourceTemplates.AddTemplate('file:///docs/{path}', 'docs',
{$IFDEF FPC}@{$ENDIF}LerDoc);
Servidor.Prompts.AddPrompt('revisar', 'Revisa um código',
{$IFDEF FPC}@{$ENDIF}Revisar).Arguments.AddArgument('codigo', True);
procedure TForm1.LerConfig(Sender: TObject; ACall: TRALMCPCall;
AContents: TRALMCPResourceContents);
begin
AContents.AddText('{"modo":"teste"}', 'application/json');
end;
procedure TForm1.Revisar(Sender: TObject; ACall: TRALMCPCall; AResult: TRALMCPPromptResult);
begin
AResult.AddText('user', 'Revise este código: ' + ACall.Argument('codigo').AsString);
end;No modelo de URI, o OnRead recebe em AVariables os valores das variáveis (path).
TRALMCPResourceContents tem AddText e AddBlob; TRALMCPPromptResult tem AddText(papel, texto),
AddMessage e Description.
Como não há sessão, uma ferramenta que precisa de uma resposta do usuário devolve um pedido
(resultType: input_required) e o cliente chama de novo com a resposta (multi round-trip
request). A ferramenta roda outra vez, então ela decide pelo InputResponse:
procedure TForm1.Entrar(Sender: TObject; ACall: TRALMCPCall; AResult: TRALMCPToolResult);
var
Resposta: TRALJSON;
begin
Resposta := ACall.InputResponse('usuario');
if Resposta = nil then
begin
ACall.Elicit('usuario', 'Qual o seu nome?',
TRALJSONObject(TRALJSON.ParseJSON(
'{"type":"object","properties":{"nome":{"type":"string"}},"required":["nome"]}')));
ACall.SetState('passo1'); // volta, assinado, na próxima rodada
Exit;
end;
AResult.AddText('olá ' + Resposta.AsObject.Get('content').AsObject.Get('nome').AsString
+ ' (' + ACall.State + ')');
end;-
Elicit(chave, mensagem, schema)pede um formulário;RequestInput(chave, objeto)faz qualquer pedido (elicitation/create,sampling/createMessage,roots/list). Sem a capacidade declarada pelo cliente o RAL levanta Missing capability (-32021). -
InputRequireddiz se a rodada já pede dados;InputResponse(chave)devolve a resposta (nil se não veio). -
SetState/State: o servidor assina o estado com HMAC-SHA256 (preso ao método e ao nome da ferramenta), o cliente o devolve sem mudar, e um estado adulterado ou vencido (StateLife) é Invalid params. SemStateKeya chave é aleatória por processo; atrás de um balanceador, ou entre reinícios, todos os servidores devem usar a mesmaStateKey.
Para um programa de console que o cliente de IA inicia: uma mensagem JSON-RPC por linha na entrada padrão, uma resposta por linha na saída padrão. Cada linha é respondida em sua própria thread, para que um cancelamento alcance uma chamada em andamento (uma chamada cancelada não responde nada).
program MeuMCP;
{$IFDEF FPC}{$MODE DELPHI}{$ENDIF}
{$APPTYPE CONSOLE}
uses
{$IFDEF FPC}{$IFDEF UNIX}cthreads,{$ENDIF}{$ENDIF}
Classes, RALMCPServer, RALMCPStdio;
var
Servidor: TRALMCPServer;
Stdio: TRALMCPStdio;
begin
Servidor := TRALMCPServer.Create(nil);
Stdio := TRALMCPStdio.Create(nil);
try
// ... Servidor.Tools.AddTool(...) como acima
Stdio.Server := Servidor;
Stdio.Run; // até a entrada acabar
finally
Stdio.Free;
Servidor.Free;
end;
end.Nada além das mensagens MCP pode ir para a saída padrão (WriteLn do seu código estraga o
protocolo): escreva diagnósticos em ErrOutput, que é para onde o RAL manda os erros internos.
Run(AEntrada, ASaida) aceita streams, útil em teste. Para o cliente iniciar o executável:
{
"mcpServers": {
"minha-loja": {
"command": "C:\\apps\\MeuMCP.exe",
"args": []
}
}
}uses RALMCPModule;
Modulo := TRALMCPModule.Create(Self);
Modulo.MCPServer := Servidor;
Modulo.Server := ServidorHTTP; // o TRALServer
Modulo.Path := '/mcp'; // o padrão
ServidorHTTP.Active := True;| propriedade | |
|---|---|
MCPServer |
o TRALMCPServer que responde |
Path |
rota do endpoint (/mcp); só aceita POST: GET e DELETE respondem 405 |
AllowedOrigins |
origens (esquema://host[:porta]) que podem chamar de um navegador; vazia = só o próprio host e localhost. Outra origem recebe 403
|
AuthorizationServers, ResourceURL, Scopes
|
autorização |
- A resposta é JSON; vira SSE (fluxo de eventos) quando o pedido traz
progressTokenoulogLevelno_meta, e sempre emsubscriptions/listen. Isso exige um motor que envie ao vivo (veja a tabela em Resposta em fluxo); nos outros,ProgresseLognão chegam antes do fim. - O cliente manda
MCP-Protocol-Version,Mcp-MethodeMcp-Name(o nome da ferramenta, do recurso ou do prompt) e osMcp-Param-*dos parâmetros comHeader. O RAL confere os cabeçalhos com o corpo e, se divergem, responde o erro -32020 (header mismatch).
Para exigir login, ligue ao servidor HTTP um plugin de autenticação (por exemplo
TRALServerOAuth2, ver Autenticação) e preencha no módulo:
Modulo.AuthorizationServers.Add('https://auth.exemplo.com');
Modulo.ResourceURL := 'https://api.exemplo.com/mcp'; // o "audience" dos tokens (RFC 8707)
Modulo.Scopes.Add('mcp:tools');
OAuth2.ResourceMetadata := Modulo.ResourceMetadataURL;Com AuthorizationServers, o módulo serve o metadado do recurso protegido (RFC 9728) em
/.well-known/oauth-protected-resource, que o cliente de IA lê para descobrir onde obter o token;
ResourceMetadataURL é o endereço desse documento, que o TRALServerOAuth2 informa no
WWW-Authenticate do 401. As Roles das ferramentas valem para o usuário autenticado.
Clientes que abrem subscriptions/listen recebem avisos quando algo muda. No servidor:
Servidor.Tools.AddTool(...);
Servidor.NotifyToolsChanged; // a lista de ferramentas mudou
Servidor.NotifyResourcesChanged; // a lista de recursos mudou
Servidor.NotifyPromptsChanged; // a lista de prompts mudou
Servidor.NotifyResourceUpdated('file:///config.json'); // o conteúdo mudouListening diz se há alguém ouvindo e StopListens encerra as conexões (ao desligar o servidor).
A extensão io.modelcontextprotocol/tasks roda uma chamada longa em segundo plano: marque
Ferramenta.RunAsTask := True. Se o cliente declara a extensão, tools/call responde na hora com a
tarefa (resultType: task, com taskId) e a ferramenta roda numa thread; o cliente acompanha por
tasks/get, responde pedidos de dados por tasks/update e para por tasks/cancel (ACall.Cancelled
fica verdadeiro). Quem não declara a extensão recebe a resposta quando a chamada termina.
| propriedade | |
|---|---|
TaskTTL |
quanto tempo a tarefa é guardada, em ms desde a criação (1 h por padrão) |
TaskPollInterval |
intervalo de consulta sugerido ao cliente, em ms (1000) |
TaskStore |
um TRALKeyValueStore onde o resumo das tarefas também fica, para que tasks/get funcione em qualquer servidor que o compartilhe; sem ele, só na memória |
Para chamar um servidor MCP de dentro do seu programa: ligue-o a um TRALClient (motor,
BaseURL, TLS e autenticação).
| membro | |
|---|---|
Client, Path
|
o cliente HTTP e a rota do endpoint (/mcp) |
Capabilities |
JSON das capacidades declaradas em todo pedido ('{"elicitation":{}}') |
ClientName, ClientVersion
|
o clientInfo
|
Discover |
resultado de server/discover
|
ListTools, ListResources, ListPrompts, ListAll
|
todas as páginas |
CallTool(nome, argumentos) |
chama a ferramenta; responde os pedidos de dados por OnInputRequest (até MaxRounds rodadas, 8) |
ReadResource(uri), GetPrompt(nome, argumentos)
|
leem recurso e prompt |
Request(método, params, nome) |
qualquer método |
OnNotification |
recebe o progresso e os logs que chegam antes da resposta |
Os resultados são objetos JSON que você libera; um erro de protocolo levanta ERALJSONRPCError.
uses RALClient, RALMCPClient, RALJson;
Cliente.EngineType := 'Indy';
Cliente.BaseURL.Text := 'http://127.0.0.1:8080';
MCP := TRALMCPClient.Create(Self);
MCP.Client := Cliente;
MCP.Capabilities := '{"elicitation":{}}';
MCP.OnInputRequest := {$IFDEF FPC}@{$ENDIF}Pergunta;
Args := TRALJSON.ParseJSON('{"codigo":"A1"}');
try
R := MCP.CallTool('estoque', Args);
try
Texto := R.Find('content').AsArray.Items[0].AsObject.Get('text').AsString;
finally
R.Free;
end;
finally
Args.Free;
end;
procedure TForm1.Pergunta(Sender: TObject; const AKey: StringRAL; ARequest: TRALJSON;
var AResponse: TRALJSON);
begin
AResponse := TRALJSON.ParseJSON('{"action":"accept","content":{"nome":"ana"}}');
end;| sintoma | causa |
|---|---|
| o cliente de IA ou o MCP Inspector não conecta (-32022) | ele fala uma revisão anterior; o RAL só fala 2026-07-28
|
| 403 vindo de um navegador | a origem não está em AllowedOrigins
|
| 405 | GET ou DELETE no endpoint; só POST |
| -32020 | um cabeçalho Mcp-* não bate com o corpo |
Progress/Log só chegam no fim |
o motor não envia ao vivo, ou o cliente não mandou progressToken/logLevel
|
| stdio "não responde" ou quebra | o programa escreveu na saída padrão; use ErrOutput
|
| o estado de um pedido de dados é recusado | o servidor reiniciou ou há vários sem a mesma StateKey, ou passou o StateLife
|
| Missing capability (-32021) | o cliente não declarou elicitation/sampling/roots
|
Começar
Servidor
- Rotas e parâmetros
- Rotas avançadas: tipos, grupos, versões 🚧
- Ler o pedido e responder
- Objetos no corpo 🚧
- Resposta em fluxo 🚧
- Plugins 🚧
- Serviços (contêiner de dependências) 🚧
- Barramento de eventos 🚧
- Autenticação
- Banco de dados (DBWare)
- Servir site e arquivos
- JSON-RPC 2.0 🚧
- Servidor MCP (IA) 🚧
-
Protocol Buffers 🚧
- gRPC 🚧
- WebSocket 🚧
- Hubs (WebSocket) 🚧
- Swagger e Postman
- Servidor SOAP 🚧
- WS-Security 🚧
- HTTPS
- Criptografia ponto a ponto
- Segurança para produção
- Colocar no ar
- Desempenho
- Testes em memória 🚧
MQTT 🚧
- O que é e quando usar 🚧
- Cliente MQTT 🚧
- Broker MQTT 🚧
- MQTT-SN 🚧
- Ponte REST 🚧
- Segurança 🚧
- Rede ruim 🚧
E-mail 🚧
- Mensagens 🚧
- Enviar 🚧
- Receber 🚧
- Ler a caixa postal 🚧
- Verificar o que chega 🚧
- Servir caixas postais 🚧
- S/MIME 🚧
- OpenPGP 🚧
Chatbot 🚧
- O robô e os canais 🚧
- Telegram 🚧
- Gateways REST (WhatsApp não oficial) 🚧
- Discord 🚧
- Slack 🚧
- Microsoft Teams 🚧
- Conversa em etapas 🚧
Cliente
Motores
- Qual motor escolher?
- Compatibilidade
- Recursos
- QUIC
- Motor NgHttp2 (HTTP/2) 🚧
- Motor XSocket 🚧
- Motor AdvancedHTTP (experimental) 🚧
Avançado
Ajuda
- Problemas comuns
- Atualizar o Indy
- Várias versões do Delphi
- Vídeos e guias
- Módulos de terceiros
- Licenças
- Contribuir
- Roadmap e changelog
🚧 = em desenvolvimento, pode mudar