MCP Apps é um novo paradigma no MCP. A ideia é que não só respondes com dados de volta a partir de uma chamada de ferramenta, mas também forneces informação sobre como essa informação deve ser interagida. Isso significa que os resultados da ferramenta agora podem conter informação de interface de utilizador. Mas porque é que quereríamos isso? Bem, considera como fazes as coisas hoje em dia. É provável que estejas a consumir os resultados de um Servidor MCP colocando algum tipo de frontend à sua frente, esse é código que precisas de escrever e manter. Por vezes é isso que queres, mas outras vezes seria ótimo se pudesses simplesmente trazer um fragmento de informação que é auto-suficiente e que tem tudo, desde dados até à interface de utilizador.
Esta lição fornece orientações práticas sobre MCP Apps, como começar e como integrá-lo nas tuas Apps Web existentes. MCP Apps é uma adição muito nova ao Standard MCP.
No final desta lição, serás capaz de:
- Explicar o que são MCP Apps.
- Quando usar MCP Apps.
- Construir e integrar os teus próprios MCP Apps.
A ideia com MCP Apps é fornecer uma resposta que essencialmente é um componente para ser renderizado. Tal componente pode ter tanto elementos visuais como interatividade, por exemplo, cliques em botões, entrada do utilizador e mais. Vamos começar pelo lado do servidor e pelo nosso Servidor MCP. Para criar um componente MCP App precisas de criar uma ferramenta mas também o recurso de aplicação. Estas duas partes estão ligadas por um resourceUri.
Aqui está um exemplo. Vamos tentar visualizar o que está envolvido e que partes fazem o quê:
server.ts -- responsible for registering tools and the component as a UI component
src/
mcp-app.ts -- wiring up event handlers
mcp-app.html -- the user interface
Esta visualização descreve a arquitetura para criar um componente e a sua lógica.
flowchart LR
subgraph Backend[Backend: Servidor MCP]
T["Registar ferramentas: registerAppTool()"]
C["Registar recurso de componente: registerAppResource()"]
U[resourceUri]
T --- U
C --- U
end
subgraph Parent[Página Web Pai]
H[Aplicação anfitriã]
IFRAME[Contentor IFrame]
H -->|Injetar UI da App MCP| IFRAME
end
subgraph Frontend[Frontend: App MCP dentro do IFrame]
UI["Interface do utilizador: mcp-app.html"]
EH["Gestores de eventos: src/mcp-app.ts"]
UI --> EH
end
IFRAME --> UI
EH -->|Clique dispara chamada da ferramenta no servidor| T
T -->|Dados do resultado da ferramenta| EH
EH -->|Enviar mensagem para a página pai| H
Vamos tentar descrever as responsabilidades a seguir para backend e frontend respetivamente.
Há duas coisas que precisamos de realizar aqui:
- Registar as ferramentas com as quais queremos interagir.
- Definir o componente.
Registar a ferramenta
registerAppTool(
server,
"get-time",
{
title: "Get Time",
description: "Returns the current server time.",
inputSchema: {},
_meta: { ui: { resourceUri } }, // Liga esta ferramenta ao seu recurso de interface de utilizador
},
async () => {
const time = new Date().toISOString();
return { content: [{ type: "text", text: time }] };
},
);O código anterior descreve o comportamento, onde expõe uma ferramenta chamada get-time. Não recebe entradas mas acaba por produzir a hora atual. Temos a capacidade de definir um inputSchema para ferramentas onde precisamos de aceitar entrada do utilizador.
Registar o componente
No mesmo ficheiro, também precisamos de registar o componente:
const resourceUri = "ui://get-time/mcp-app.html";
// Regista o recurso, que retorna o HTML/JavaScript empacotado para a interface de utilizador.
registerAppResource(
server,
resourceUri,
resourceUri,
{ mimeType: RESOURCE_MIME_TYPE },
async () => {
const html = await fs.readFile(path.join(DIST_DIR, "mcp-app.html"), "utf-8");
return {
contents: [
{ uri: resourceUri, mimeType: RESOURCE_MIME_TYPE, text: html },
],
};
},
);Repara como mencionamos resourceUri para ligar o componente às suas ferramentas. Também é interessante o callback onde carregamos o ficheiro UI e retornamos o componente.
Tal como no backend, há duas partes aqui:
- Um frontend escrito em HTML puro.
- Código que trata eventos e o que fazer, por exemplo, chamar ferramentas ou enviar mensagens para a janela pai.
Interface do utilizador
Vamos olhar para a interface do utilizador.
<!-- mcp-app.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Get Time App</title>
</head>
<body>
<p>
<strong>Server Time:</strong> <code id="server-time">Loading...</code>
</p>
<button id="get-time-btn">Get Server Time</button>
<script type="module" src="/src/mcp-app.ts"></script>
</body>
</html>Ligação dos eventos
A última parte é a ligação dos eventos. Isso significa que identificamos qual a parte na UI que precisa de manipuladores de eventos e o que fazer se eventos forem disparados:
// mcp-app.ts
import { App } from "@modelcontextprotocol/ext-apps";
// Obter referências de elementos
const serverTimeEl = document.getElementById("server-time")!;
const getTimeBtn = document.getElementById("get-time-btn")!;
// Criar instância da aplicação
const app = new App({ name: "Get Time App", version: "1.0.0" });
// Tratar resultados da ferramenta do servidor. Definir antes de `app.connect()` para evitar
// perder o resultado inicial da ferramenta.
app.ontoolresult = (result) => {
const time = result.content?.find((c) => c.type === "text")?.text;
serverTimeEl.textContent = time ?? "[ERROR]";
};
// Ligar clique do botão
getTimeBtn.addEventListener("click", async () => {
// `app.callServerTool()` permite à interface pedir novos dados ao servidor
const result = await app.callServerTool({ name: "get-time", arguments: {} });
const time = result.content?.find((c) => c.type === "text")?.text;
serverTimeEl.textContent = time ?? "[ERROR]";
});
// Ligar ao host
app.connect();Como podes ver acima, este é código normal para ligar elementos do DOM a eventos. Vale a pena destacar a chamada a callServerTool que acaba por chamar uma ferramenta no backend.
Até agora, vimos um componente que tem um botão que, quando clicado, chama uma ferramenta. Vamos ver se podemos adicionar mais elementos UI como um campo de entrada e verificar se podemos enviar argumentos para uma ferramenta. Vamos implementar uma funcionalidade de FAQ. Aqui está como deve funcionar:
- Deve haver um botão e um elemento de entrada onde o utilizador digita uma palavra-chave para pesquisar, por exemplo "Shipping". Isto deve chamar uma ferramenta no backend que faça uma pesquisa nos dados do FAQ.
- Uma ferramenta que suporta a pesquisa FAQ mencionada.
Vamos adicionar o suporte necessário ao backend primeiro:
const faq: { [key: string]: string } = {
"shipping": "Our standard shipping time is 3-5 business days.",
"return policy": "You can return any item within 30 days of purchase.",
"warranty": "All products come with a 1-year warranty covering manufacturing defects.",
}
registerAppTool(
server,
"get-faq",
{
title: "Search FAQ",
description: "Searches the FAQ for relevant answers.",
inputSchema: zod.object({
query: zod.string().default("shipping"),
}),
_meta: { ui: { resourceUri: faqResourceUri } }, // Liga esta ferramenta ao seu recurso de interface de utilizador
},
async ({ query }) => {
const answer: string = faq[query.toLowerCase()] || "Sorry, I don't have an answer for that.";
return { content: [{ type: "text", text: answer }] };
},
);O que estamos a ver aqui é como preenchemos inputSchema e fornecemos um esquema zod assim:
inputSchema: zod.object({
query: zod.string().default("shipping"),
})No esquema acima declaramos que temos um parâmetro de entrada chamado query e que é opcional com um valor padrão de "shipping".
Ok, vamos avançar para mcp-app.html para ver que UI precisamos criar para isto:
<div class="faq">
<h1>FAQ response</h1>
<p>FAQ Response: <code id="faq-response">Loading...</code></p>
<input type="text" id="faq-query" placeholder="Enter FAQ query" />
<button id="get-faq-btn">Get FAQ Response</button>
</div>Ótimo, agora temos um elemento de entrada e um botão. Vamos para mcp-app.ts a seguir para ligar estes eventos:
const getFaqBtn = document.getElementById("get-faq-btn")!;
const faqQueryInput = document.getElementById("faq-query") as HTMLInputElement;
getFaqBtn.addEventListener("click", async () => {
const query = faqQueryInput.value;
const result = await app.callServerTool({ name: "get-faq", arguments: { query } });
const faq = result.content?.find((c) => c.type === "text")?.text;
faqResponseEl.textContent = faq ?? "[ERROR]";
});No código acima:
- Criamos referências aos elementos UI interativos.
- Tratamos um clique de botão para analisar o valor do elemento de entrada e também chamamos
app.callServerTool()comnameeargumentsonde este último passaquerycomo valor.
O que realmente acontece quando chamas callServerTool é que envia uma mensagem para a janela pai e essa janela acaba por chamar o Servidor MCP.
Ao experimentarmos isto, devemos agora ver o seguinte:
e aqui onde tentamos com uma entrada como "warranty"
Para executar este código, vai para a seção de código
O Visual Studio Code tem um ótimo suporte para MCP Apps e é provavelmente uma das maneiras mais fáceis de testar os teus MCP Apps. Para usar o Visual Studio Code, adiciona uma entrada de servidor em mcp.json assim:
"my-mcp-server-7178eca7": {
"url": "http://localhost:3001/mcp",
"type": "http"
}Depois inicia o servidor, deverás ser capaz de comunicar com o teu MCP App através da Janela de Chat, desde que tenhas o GitHub Copilot instalado.
Podes dispará-lo via um prompt, por exemplo "#get-faq":
e tal como quando o executaste através de um navegador web, ele renderiza da mesma forma assim:
Cria um jogo de pedra papel tesoura. Deve consistir nos seguintes:
UI:
- uma lista dropdown com opções
- um botão para submeter uma escolha
- um rótulo a mostrar quem escolheu o quê e quem ganhou
Servidor:
- deve ter uma ferramenta rock paper scissor que recebe "choice" como entrada. Deve também renderizar a escolha do computador e determinar o vencedor
Aprendemos sobre este novo paradigma MCP Apps. É um novo paradigma que permite aos Servidores MCP terem uma opinião não só sobre os dados mas também sobre como esses dados devem ser apresentados.
Adicionalmente, aprendemos que estes MCP Apps são alojados num IFrame e para comunicarem com os Servidores MCP precisam de enviar mensagens para a aplicação web pai. Há várias bibliotecas disponíveis tanto para JavaScript puro como para React e outras que facilitam esta comunicação.
Aqui está o que aprendeste:
- MCP Apps é um novo standard que pode ser útil quando queres enviar tanto dados como funcionalidades de UI.
- Este tipo de apps corre num IFrame por razões de segurança.
Aviso: Este documento foi traduzido utilizando o serviço de tradução automática Co-op Translator. Embora nos esforcemos pela precisão, por favor tenha em atenção que traduções automáticas podem conter erros ou imprecisões. O documento original na sua língua nativa deve ser considerado a fonte autoritativa. Para informações críticas, recomenda-se a tradução profissional humana. Não nos responsabilizamos por quaisquer mal-entendidos ou interpretações erradas resultantes do uso desta tradução.



