Bem-vindos a mais um curso da Alura. Eu sou o Ricardo Bugan, Head de Produto e pessoa desenvolvedora, e serei seu instrutor neste curso, que é o segundo desta série, na qual abordamos o protocolo MCP e a criação de um servidor MCP, para que você tenha um servidor preparado para que as LLMs e agentes de pessoas usuárias interajam com seus sistemas.
Se você não viu o curso anterior, recomendamos que assista; o link está nesta página. Se você já viu, vamos continuar com o mesmo projeto, o projeto da Ermex Locadora. Nós vamos seguir de perto a especificação, entender como você pode se orientar nela e como ela está escrita, e exploraremos algumas features (funcionalidades) novas que não vimos no curso anterior.
No curso anterior, nós focamos muito em ferramentas, um dos conceitos do servidor.
Nesta aula, vamos focar em recursos e em prompts. Vamos entender para que servem, como registrá-los e como funcionam.
Também vamos abordar um complemento relacionado ao tema de ferramentas: paginação. Vamos entender como a paginação é implementada no protocolo MCP, pois a especificação dedica uma seção exclusiva a esse assunto. Veremos para que ela serve, como funciona, qual tipo de paginação devemos usar e, em seguida, vamos implementar isso no nosso sistema.
Faremos essa implementação considerando a arquitetura que temos: consumimos uma API REST e utilizamos o MCP no meio do caminho. Tudo precisa funcionar em conjunto, e veremos como adicionar paginação nessa arquitetura que não é uma aplicação única em execução.
Tudo isso veremos neste curso, e eu espero você lá.
Vamos iniciar o segundo curso, no qual abordamos servidores no ICP para criarmos, passo a passo, nosso servidor e tornarmos a aplicação AI First (IA primeiro), com a interface com agentes de IA pronta.
Se o curso anterior ainda não foi visto, recomendamos acessá-lo. Vamos retomar exatamente de onde finalizamos o curso anterior e continuar com o mesmo projeto. Na página deste curso na Alura há um link de pré-requisito para acompanhar até onde avançamos no projeto anterior e obter o contexto necessário para o início deste curso.
Caso o curso anterior tenha sido concluído, mas o projeto não esteja disponível localmente para continuidade, disponibilizaremos o link do GitHub nesta aula, na etapa de preparação do ambiente. Assim, será possível obter o estado final do curso anterior e o início deste, exatamente no ponto em que interrompemos o projeto e do qual vamos prosseguir.
Vamos começar explorando um dos problemas que podem surgir com a abordagem do curso anterior. Não cometemos erros; porém, conforme as aplicações crescem, precisamos responder a uma questão: como enviar uma resposta com o tamanho adequado para o agente? Vimos no curso anterior que podemos fazer uma requisição para a API e devolver toda a listagem de carros, por exemplo, aplicando filtros mais ou menos elaborados, para que o agente receba a lista conforme a necessidade do que a pessoa usuária está pedindo. Em alguns casos, porém, para determinados recursos, o tamanho dessa lista pode ser grande demais. Como reduzir o esforço que o agente terá para processar esses dados? Vamos iniciar este curso respondendo a essa pergunta, em continuidade direta ao anterior, discutindo paginação de APIs.
Retomando o contexto no qual paramos: implementamos a API, toda a parte de locação, de busca de carros e de locações, e criamos um conjunto de ferramentas que o agente pode utilizar. Encerramos com a listagem funcionando e o sistema operando corretamente. Em cenários muito grandes, entretanto, quando fazemos uma requisição GET, é comum obter uma lista de resultados extensa, especialmente em um contexto de e-commerce (comércio eletrônico). Enviar todos os produtos da loja para o agente revisar e filtrar por conta própria para a pessoa usuária consome muitos tokens (unidades de texto), torna a resposta mais lenta e piora a experiência, além de sobrecarregar o servidor com respostas de grande volume.
Suponhamos que tenhamos uma frota com 150 opções. Não é um número especialmente grande se comparado a outros cenários do dia a dia, mas tomemos-o como exemplo. Para cada carro, temos pelo menos o ID, o modelo, a marca, o preço, o ano, a quilometragem e a informação de disponibilidade. Cada carro é representado por um objeto com esse conjunto de informações. A resposta conterá 150 vezes esse objeto, e todas essas informações custam tokens (unidades de texto) para o agente no cliente processar, aplicar a filtragem e produzir a resposta para a pessoa usuária.
Já vimos uma primeira abordagem no curso anterior que ajuda a mitigar essa questão: os filtros. Dispomos de filtros de categoria, ano, marca e outros filtros básicos que reduzem o escopo da busca. Ainda assim, o número de resultados pode permanecer elevado. A paginação surge como solução, pois, mesmo após a filtragem, se o volume de respostas continuar grande — e com objetos de corpo extenso — precisamos paginar, enviando os dados gradualmente para que o agente os processe até encontrar as informações relevantes. Vamos utilizar paginação neste caso.
No contexto do protocolo MCP, que define o comportamento do cliente, da LLM e do servidor, o método de paginação recomendado é por cursor (cursor). Existem diferentes estratégias de paginação, sendo as duas principais por offset (deslocamento) ou por cursor (cursor). Para o servidor MCP, a recomendação é utilizar paginação por cursor (cursor). Aqui, em função da arquitetura que estamos utilizando, aplicaremos paginação em REST, alinhando-a a essa recomendação do protocolo MCP.
Podemos, por exemplo, deixar o próprio LLM (modelo de linguagem grande) definir o tamanho da página. Com isso, o sistema terá o cursor e instruirá: a partir desse cursor, retornar 3 resultados, 4 resultados, 20 resultados. Podemos deixar essa decisão na ponta, para o LLM (modelo de linguagem grande), ou fixá-la do lado do servidor.
Aqui, avaliamos o grau de facilidade que queremos oferecer para a pessoa usuária versus a capacidade operacional. Se permitirmos que o LLM (modelo de linguagem grande) defina qualquer tamanho de página e ele solicitar mil resultados por página, podemos incorrer em um gargalo de infraestrutura. Portanto, existem opções a considerar, e vale refletir sobre elas no momento da implementação.
A utilização de filtros já reduz o tamanho da busca. Se o resultado for muito grande, a aplicação de filtros ajuda bastante. A paginação adiciona uma camada a mais para facilitar e diminuir o tamanho do pacote de resposta para a pessoa usuária. Vamos utilizar paginação por cursor (cursor).
Nosso fluxo de requisição será o seguinte. Uma pessoa cliente, na ponta, interage com o MCP e com o agente, e solicita: queremos reservar um carro na Ermex, que é nosso caso de uso aqui. Essa pessoa envia uma mensagem ao LLM (modelo de linguagem grande) informando: “reservar um carro para a semana que vem”. Em seguida, a pessoa cliente envia uma requisição ao servidor pedindo a lista de veículos, que é o fluxo normal: para reservar um carro, precisamos saber qual carro será escolhido.
O servidor recebe a requisição e, se nenhum parâmetro de cursor (cursor) vier configurado — isto é, se a requisição chegar sem cursor (cursor) —, ele retornará a primeira página. Isso significa selecionar o primeiro conjunto de itens da lista e devolvê-los. Na resposta, o servidor incluirá também a indicação de onde começa a próxima página, isto é, qual é o cursor (cursor) para a continuação.
O LLM (modelo de linguagem grande) verificará se, na página recebida, já existem as informações necessárias. Se estiver tudo certo, a requisição pode ser encerrada, pois uma página foi suficiente. Caso contrário, o LLM (modelo de linguagem grande) solicitará os próximos carros a partir do cursor (cursor) devolvido, isto é, a partir do ponto específico da lista completa. Assim, ocorrerá esse vai e volta até obtermos a página desejada e definirmos o carro. Dessa forma, passamos, aos poucos, as informações do servidor para a pessoa cliente.
Para quem nunca trabalhou com paginação, o cursor (cursor) pode ser entendido como um índice que indica em qual linha do banco de dados paramos nossa resposta. Para que funcione bem, a lista no banco de dados precisa estar ordenada de alguma maneira. Normalmente, o cursor (cursor) é uma coluna no banco de dados. Pode ser uma coluna já existente, como o ID (se for sequencial), a data de criação (naturalmente ordenada), ou uma coluna criada especificamente para atuar como cursor (cursor) na paginação.
Partimos sempre do início: ordenamos toda a lista de carros, começamos sem cursor (cursor) e selecionamos, por exemplo, os dez primeiros itens para retornar. Se estivermos usando o ID como cursor (cursor), devolvemos os dados e informamos: a próxima página começa no ID 10 (em um cenário sequencial).
Esse método exige uma coluna ordenada e torna mais difícil avançar diretamente para uma página específica, porque podemos posicionar o cursor (cursor) e o tamanho da página em qualquer ponto; não há páginas pré-delimitadas. Além disso, o cursor (cursor) costuma ser opaco: em vez de ser 1, 2, 3, 4, 5, pode ser um hash (resumo criptográfico). Nesse caso, torna-se mais difícil escolher uma página específica. Se o cursor (cursor) não for opaco — por exemplo, um índice sequencial numérico —, fica mais simples saltar para um ponto específico da busca. No geral, porém, nós usamos cursores opacos, o que não facilita esse tipo de navegação direta.
Essas são algumas características da paginação por cursor (cursor), que utilizaremos por ser a abordagem sugerida e recomendada pela documentação do MCP, do próprio protocolo MCP. A recomendação ocorre porque a paginação por cursor (cursor) funciona muito bem com rolagem infinita. Muitos sistemas já a utilizam, pois rolagem infinita é uma feature (recurso) comum no front-end (camada de interface). De certa forma, é o que nosso agente precisará fazer: percorrer a lista de carros até encontrar o item desejado, recebendo os dados gradualmente para não sobrecarregar o servidor nem gastar tokens (unidades de texto) de maneira ineficiente por falta de paginação.
É isso que vamos implementar.
Vamos implementar o nosso cursor (cursor) na aplicação. Antes, vamos relembrar como está a aplicação para explicar onde estamos intervindo e como tudo vai funcionar.
Temos duas aplicações em execução: o nosso MCP Server, que estávamos criando (não é este projeto exibido, pois este é maior), e a nossa API REST em execução, que é a API com o serviço — este é o projeto que estamos modificando agora, aberto na tela. Como há dois pontos de intervenção e o MCP Server atua como um adapter (adaptador) do resultado da API REST, a paginação com cursor (cursor) deve ser implementada na API REST. Se apenas o MCP Server estiver em execução e ele acessar diretamente o banco de dados, a paginação precisará ser feita na camada que integra com o banco de dados, dentro do MCP Server. Neste caso, como o MCP é apenas um adaptador, precisamos primeiro alterar a API REST para, depois, ir ao projeto do MCP e ajustar como ele realiza a requisição.
A API REST já está em execução. Estamos com o Postman aberto; ao realizar uma requisição, tudo está correto e obtemos o resultado completo, isto é, todos os carros da nossa frota. O objetivo agora é implementar a paginação.
Um detalhe importante, antes de começar: é possível consultar a parte de paginação na especificação do Model Context Protocol, na seção de Server Features (recursos do servidor) e na seção de Utilities (utilidades). Lá há mais detalhes sobre como a paginação deve funcionar e sobre os formatos de requisição e resposta.
Indo para o código, já discutimos que precisamos fazer algumas coisas. Primeiro, precisamos ordenar todos os resultados de maneira consistente. Depois, precisamos escolher o tamanho da página e definir qual será o cursor (cursor), isto é, qual coluna utilizaremos. Precisamos selecionar um valor fácil de ordenar; esse campo será usado como token (token) ou como cursor (cursor) para a paginação. Para o exemplo, vamos utilizar o campo de preço por dia, referenciado como “price per day”. Vamos instruir o sistema a usar esse valor como token (token) e preparar tudo para utilizá-lo como cursor (cursor). Estamos usando token (token) e cursor (cursor) de forma intercambiável na explicação, mas não são a mesma coisa; o que estamos implementando é o cursor (cursor).
A primeira ação é garantir que, em toda requisição, os resultados venham ordenados por esse valor. No nosso serviço de listagem (onde utilizamos Prisma), temos o método de listagem que consulta o banco. Caso alguém não conheça o Prisma, a sintaxe pode causar alguma dificuldade, mas é possível consultar a documentação para mais detalhes; ele é bastante similar a outros ORMs (mapeadores objeto-relacional). No código, já existe a busca com a cláusula where (toda a filtragem está definida ali) e, em seguida, ocorre o findMany, que consulta o banco de dados usando essa cláusula.
No Prisma, podemos incluir outras propriedades nesse objeto. A propriedade que queremos agora é orderBy: para definir a ordenação. Como é possível ordenar por várias colunas ao mesmo tempo, orderBy geralmente recebe um objeto, no qual informamos o nome da coluna a ser ordenada. No nosso caso, utilizaremos “price per day” e vamos ordenar de forma ascendente, do menor para o maior. Para aplicar a ordenação, adicionamos o seguinte:
const cars = await prisma.car.findMany({
where,
orderBy: { pricePerDay: 'asc' }
});
Ao salvarmos e testarmos, o primeiro resultado, que antes apresentava “price per day” igual a 120, passa a apresentar 100. Em seguida, 120 e, depois, 130 — confirmando a ordenação. Primeira etapa concluída.
A próxima etapa é definir o tamanho da página. Como temos poucos resultados para visualizar, vamos trabalhar com duas unidades por página. Assim, toda vez que alguém consultar a API, serão retornados dois carros ao mesmo tempo; esse será o tamanho da página. Voltamos ao findMany (na função que busca no banco). Em SQL, utilizaríamos a cláusula LIMIT (limite) — por exemplo, 2, 10, 5, 50. No Prisma, utilizamos a propriedade take para informar a quantidade de registros a retornar. Vamos configurá-la para pegar dois. Implementamos essa limitação assim:
const cars = await prisma.car.findMany({
where,
orderBy: { pricePerDay: 'asc' },
take: 2
});
Esse parâmetro pode vir da requisição, pode ser enviado por uma LLM (modelo de linguagem), pode ser customizável ou fixo no código. Aqui, vamos deixá-lo fixo. Portanto, instruímos: ordenar por preço e retornar os dois primeiros resultados. Com isso, passamos a ver apenas o carro com valor 100 e o carro com valor 120; o de 130 não aparece. Esta é a nossa primeira página, o caso base. Se alguém fizer uma requisição sem nenhum cursor (cursor), este é o resultado que devemos devolver.
Agora, queremos que a requisição aceite um cursor (cursor). Teremos um parâmetro chamado cursor, com um valor que, neste caso, será o do último item da página (o carro mais caro naquela página). Portanto, para a página atual, o valor do cursor (cursor) será 120.
Devemos reenviar a busca para que a página retorne o carro com preço 130 na primeira posição, pois estamos avançando na nossa busca, na nossa lista de carros. Vamos implementar esse comportamento.
Voltando ao código para implementar o nosso cursor, precisamos entender como o serviço está funcionando e como as informações chegam para o cliente, isto é, para a nossa função. Aqui, temos a query que representa a consulta que chegou pela nossa rota, com uma lista de parâmetros definida no esquema. No nosso esquema listCarQuerySchema, temos a category e o parâmetro q. Portanto, há dois parâmetros que podem ser utilizados.
Agora, teremos um próximo parâmetro, o cursor. Vamos adicioná-lo para que o serviço esteja preparado para receber o valor que passamos na requisição. Ele também será uma string. Isso pode parecer estranho, pois o cursor precisa ser um número para ordenação e será utilizado como número. No entanto, como o estamos enviando pelo Postman como query param, ele chegará como texto, isto é, como string. Dado que ele virá na requisição como string, vamos validá-lo como string e também como opcional. Feito isso, na hora de utilizá-lo na função, vamos converter essa string em número. Dessa forma, o serviço poderá receber o cursor sem problemas. No esquema, adicionamos o novo parâmetro:
export const listCarQuerySchema = z.object({
category: z.string().optional(),
q: z.string().optional(),
cursor: z.string().optional()
});
No método de listagem, vamos adicionar mais uma cláusula where para realizar a validação. Vamos aproveitar a lógica já existente para category: se existir category, usamos; se existir cursor, usamos; se não existir, não usamos. Mantemos a mesma estrutura aplicada à category. Assim, dentro dos parênteses (), se na query houver um parâmetro chamado cursor, no where vamos especificar que pricePerDay seja maior que um valor. No Prisma, usamos gt (de greater than (maior que)), com dois pontos e o valor. O valor será o próprio cursor. Primeiro, implementamos o filtro dessa forma:
if (query.cursor) {
where.pricePerDay = {
gt: query.cursor
}
}
Sabemos que, desse jeito, ocorrerá um erro. Ao salvar e enviar a requisição, o servidor retorna erro, pois estamos usando o cursor como string. O erro informa que o operador gt não pode receber uma string; ele precisa de um número. Nesse ponto, vamos aplicar um parseFloat no cursor. Poderíamos usar parseInt, mas vamos transformar em float, porque estamos trabalhando com dinheiro e valores que podem ter casas decimais. Com isso, ele passará corretamente como número. Ajustamos o filtro:
if (query.cursor) {
where.pricePerDay = {
gt: parseFloat(query.cursor)
}
}
Agora, no pricePerDay, vemos 130, e 130 também no segundo resultado. Se enviarmos o cursor como 130, vamos receber o carro com 140; em seguida, o próximo carro virá com 150. Podemos pegar o último da página, que é o mais caro, e ir atualizando. Desse modo, avançamos pelas páginas. Ao chegar em 150, o 320 é o último. Se enviarmos 320 como cursor, chegaremos ao final da busca, porque nada será retornado — não há nenhum carro com valor maior que 320. Portanto, passamos página por página, apenas alterando o valor do cursor, que representa o ponto onde paramos.
O campo escolhido como cursor, pricePerDay, tem um problema. Queremos que você reflita e avalie qual é o problema desse campo específico utilizado como cursor. No próximo vídeo, traremos a resposta sobre qual é o problema potencial de usar esse campo, mas o mecanismo e a metodologia de funcionamento já estão implementados.
Faltam mais alguns ajustes. Precisamos alterar a resposta, porque, ao olhar o retorno, nós sabemos qual é o próximo cursor, mas precisamos informar isso ao cliente. Assim, depois de obter os carros (por exemplo, dois itens) e ordenar, o retorno não será apenas a lista de carros; devemos incluir alguns parâmetros adicionais. No retorno, teremos:
cursor: indicando onde a listagem parou, para quem fizer a requisição saber qual parâmetro deve enviar a seguir.Vamos enviar um parâmetro chamado cursor, cujo valor será o preço do último carro da lista. Para isso, podemos obter cars[cars.length - 1].pricePerDay. Como estamos utilizando pricePerDay como cursor, esse será o valor enviado. Implementamos a forma do retorno assim:
take: 2
});
return {
cars: cars.map(serializeCar),
cursor: cars[cars.length - 1].pricePerDay
}
Dessa maneira, o retorno já informa a lista de carros e o próximo cursor para o cliente. Ao visualizar a lista de carros e recolhê-la, vemos o cursor. Por exemplo, se o cursor vier como 130, ao enviarmos 130 na próxima busca, recebemos novamente a lista de carros e, então, o cursor será 150, e assim sucessivamente.
O mecanismo está funcionando. Agora, precisamos integrar isso ao nosso servidor MCP e ver tudo rodando de fato.
O curso MCP Server com TypeScript: paginação, recursos e prompts possui 131 minutos de vídeos, em um total de 42 atividades. Gostou? Conheça nossos outros cursos de Engenharia de LLMs & Agentes em Inteligência Artificial, ou leia nossos artigos de Inteligência Artificial.
Matricule-se e comece a estudar com a gente hoje! Conheça outros tópicos abordados durante o curso:
O Plano Plus evoluiu: agora com Luri para impulsionar sua carreira com os melhores cursos e acesso à maior comunidade tech.
2 anos de Alura
Matricule-se no plano PLUS 24 e garanta:
Jornada de estudos progressiva que te guia desde os fundamentos até a atuação prática. Você acompanha sua evolução, entende os próximos passos e se aprofunda nos conteúdos com quem é referência no mercado.
Back-end, Dados, Front-end, DevOps, Mobile, Gestão & Negócios, UX & Design, Cibersegurança, Cloud, Inteligência Artificial
Formações com mais de 1500 cursos atualizados e novos lançamentos semanais, em Programação, Inteligência Artificial, Front-end, UX & Design, Data Science, Mobile, DevOps e Inovação & Gestão.
A cada curso ou formação concluído, um novo certificado para turbinar seu currículo e LinkedIn.
Acesso à inteligência artificial da Alura.
No Discord, você participa de eventos exclusivos, pode tirar dúvidas em estudos colaborativos e ainda conta com mentorias em grupo com especialistas de diversas áreas.
Catálogo de tecnologia para quem é da área de Marketing
Faça parte da maior comunidade Dev do país e crie conexões com mais de 120 mil pessoas no Discord.
Acesso ilimitado ao catálogo de Imersões da Alura para praticar conhecimentos em diferentes áreas.
Explore um universo de possibilidades na palma da sua mão. Baixe as aulas para assistir offline, onde e quando quiser.
20% de desconto na Pós Tech
Luri Vision chegou no Plano Pro: a IA da Alura que enxerga suas dúvidas, acelera seu aprendizado e conta também com o Alura Língua que prepara você para competir no mercado internacional.
2 anos de Alura
Todos os benefícios do PLUS 24 e mais vantagens exclusivas:
Acesso ao catálogo da Casa do Código e leitura dentro da plataforma
Modo entrevista - Pratique situações reais e evolua com feedback personalizado
Envie imagens para a Luri e ela te ajuda a solucionar problemas, identificar erros, esclarecer gráficos, analisar design e muito mais.
Aprenda um novo idioma e expanda seus horizontes profissionais. Cursos de Inglês, Espanhol e Inglês para Devs, 100% focado em tecnologia.
Para quem quer atingir seus objetivos mais rápido: Luri Vision ilimitado, vagas de emprego exclusivas e mentorias para acelerar cada etapa da jornada.
2 anos de Alura
Todos os benefícios do PRO 24 e mais vantagens exclusivas:
Envie imagens para a Luri e ela te ajuda a solucionar problemas, identificar erros, esclarecer gráficos, analisar design e muito mais de forma ilimitada.
Lives CareerUp: eventos exclusivos com foco em empregabilidade e carreira
Mentorias de carreira: 2 encontros individuais com mentores do Talent Lab
Conecte-se ao mercado com mentoria individual personalizada, vagas exclusivas e networking estratégico que impulsionam sua carreira tech para o próximo nível.