Skip to main content
Glama

MCP Server Fichador

by SaideLeon
index.ts11.7 kB
import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { CallToolRequestSchema, ErrorCode, ListToolsRequestSchema, McpError, } from '@modelcontextprotocol/sdk/types.js'; import axios from 'axios'; import { load } from 'cheerio'; import { writeFileSync, mkdirSync } from 'node:fs'; import path from 'path'; import {gerarResumoIA } from './ai/generate'; // Interfaces interface FormatarDataOptions { day: '2-digit'; month: 'short'; year: 'numeric'; } interface FichaLeitura { url: string; titulo: string; autor: string; imagens: { src: string; legenda: string }[]; resumo: string; citacao?: string; } interface ResultadoBusca { titulo: string; url: string; } interface ConteudoPagina { url: string; titulo: string; conteudo: string; imagens: { src: string; legenda: string }[]; autor: string; citacao: string; erro?: boolean; } // Utilitários function formatarData(date: Date): string { const options: FormatarDataOptions = { day: '2-digit', month: 'short', year: 'numeric' }; const formattedDate = date.toLocaleDateString('pt-BR', options).replace('.', '.'); return `${formattedDate}`; } // Funções de scraping async function rasparTodasPaginasBusca(query: string, todasPaginas: boolean = false): Promise<ResultadoBusca[]> { let pagina = 1; let resultados: ResultadoBusca[] = []; const urlsSet = new Set(); const encodedQuery = encodeURIComponent(query); while (true) { const url = pagina === 1 ? `https://www.todamateria.com.br/?s=${encodedQuery}` : `https://www.todamateria.com.br/page/${pagina}/?s=${encodedQuery}`; try { const { data: html } = await axios.get(url, { timeout: 10000, headers: { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36' } }); const $ = load(typeof html === 'string' ? html : String(html)); let encontrou = false; $('a.card-item').each((_, el) => { let href = $(el).attr('href'); const titulo = $(el).find('.card-title').text().trim() || $(el).attr('title') || ''; if (href && href.startsWith('/')) { href = 'https://www.todamateria.com.br' + href; } if (href && titulo.length > 0 && !urlsSet.has(href)) { resultados.push({ titulo, url: href }); urlsSet.add(href); encontrou = true; } }); if (!todasPaginas || !encontrou) break; pagina++; } catch (error) { console.error(`Erro ao buscar página ${pagina}:`, error); break; } } return resultados; } async function rasparConteudoPagina(url: string): Promise<ConteudoPagina> { try { const { data: html } = await axios.get(url, { timeout: 15000, headers: { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36', 'Referer': 'https://www.todamateria.com.br/' } }); const $ = load(typeof html === 'string' ? html : String(html)); const data = new Date(); const dataFormatada = formatarData(data); // Extrai título const titulo = $('h1').first().text().trim(); // Extrai parágrafos do conteúdo principal const paragrafos: string[] = []; $('.main-content article p, .main-content .content p, article .content p, article p').each((_, el) => { const txt = $(el).text().trim(); if (txt.length > 0) paragrafos.push(txt); }); // Se não encontrou nada, tenta pegar todos os <p> exceto os que estão em .sidebar, .footer, .ad-unit if (paragrafos.length === 0) { $('p').each((_, el) => { if ( $(el).parents('.sidebar, .footer, .ad-unit').length === 0 && $(el).text().trim().length > 0 ) { paragrafos.push($(el).text().trim()); } }); } // Extrai imagens const imagens: { src: string; legenda: string }[] = []; $('figure').each((_, fig) => { const img = $(fig).find('img').first(); let src = img.attr('src') || ''; if (src && src.startsWith('/')) src = 'https://www.todamateria.com.br' + src; const legenda = $(fig).find('figcaption').text().trim(); if (src) imagens.push({ src, legenda }); }); // Extrai autor let autor = $('.author-article--b__info__name').first().text().trim() || $('.autor, .author, .author-name').first().text().trim() || ''; // Extrai citação let citacao = ''; const citeCopy = $('#cite-copy .citation'); if (citeCopy.length > 0) { citacao = citeCopy.text().trim(); } citacao = `${citacao} ${dataFormatada}`; return { url, titulo, conteudo: paragrafos.join('\n\n'), imagens, autor, citacao }; } catch (error) { console.error(`Erro ao raspar conteúdo de ${url}:`, error); return { url, titulo: '', conteudo: '', imagens: [], autor: '', citacao: '', erro: true }; } } // Simulação da função de criação de ficha (seria substituída pela integração com IA) async function criarFichaLeitura(conteudo: ConteudoPagina, promptCustomizado?: string): Promise<FichaLeitura> { // Integrar com uma API de IA return { url: conteudo.url, titulo: conteudo.titulo, autor: conteudo.autor, imagens: conteudo.imagens, resumo: await gerarResumoIA(conteudo.conteudo, promptCustomizado), citacao: conteudo.citacao, }; } // Função principal do fichador async function fichador( termoBusca: string, todasPaginas: boolean, salvar: boolean = true, promptCustomizado?: string ): Promise<FichaLeitura[]> { console.log(`🔍 Buscando artigos para: ${termoBusca} (${todasPaginas ? 'todas as páginas' : 'apenas a primeira página'})`); let resultados: ResultadoBusca[] = []; try { resultados = await rasparTodasPaginasBusca(termoBusca, todasPaginas); } catch (erro) { console.error('❌ Erro ao buscar links:', erro); return []; } console.log(`🔗 ${resultados.length} links encontrados. Raspando conteúdos...`); const fichas: FichaLeitura[] = []; for (const { url } of resultados) { try { const conteudo = await rasparConteudoPagina(url); if (conteudo.erro) { console.log(`❌ Erro ao processar ${url}`); continue; } const ficha = await criarFichaLeitura(conteudo, promptCustomizado); fichas.push(ficha); console.log(`✅ Ficha criada para: ${ficha.titulo}`); } catch (erro) { console.error(`❌ Erro ao processar ${url}:`, erro); } } if (salvar) { try { mkdirSync('dados', { recursive: true }); writeFileSync( path.join('dados', `fichas-leitura-${termoBusca}.json`), JSON.stringify(fichas, null, 2), 'utf-8' ); console.log('💾 Fichas salvas em arquivo!'); } catch (erro) { console.error('❌ Erro ao salvar fichas:', erro); } } console.log('✅ Todas as fichas geradas!'); return fichas; } // Configuração do MCP Server const server = new Server({ name: 'fichador-server', version: '1.0.0', capabilities: { tools: {}, }, }); // Registra as ferramentas server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools: [ { name: 'buscar_artigos', description: 'Busca artigos no site todamateria.com.br e retorna os links encontrados', inputSchema: { type: 'object', properties: { termo_busca: { type: 'string', description: 'Termo para buscar nos artigos' }, todas_paginas: { type: 'boolean', description: 'Se deve buscar em todas as páginas ou apenas na primeira', default: false } }, required: ['termo_busca'] } }, { name: 'raspar_conteudo', description: 'Extrai o conteúdo completo de uma página específica', inputSchema: { type: 'object', properties: { url: { type: 'string', description: 'URL da página para extrair o conteúdo' } }, required: ['url'] } }, { name: 'criar_fichas_leitura', description: 'Busca artigos, extrai conteúdo e cria fichas de leitura completas', inputSchema: { type: 'object', properties: { termo_busca: { type: 'string', description: 'Termo para buscar nos artigos' }, todas_paginas: { type: 'boolean', description: 'Se deve buscar em todas as páginas ou apenas na primeira', default: false }, salvar: { type: 'boolean', description: 'Se deve salvar as fichas em arquivo JSON', default: true }, prompt_customizado: { type: 'string', description: 'Prompt customizado para geração das fichas (opcional)' } }, required: ['termo_busca'] } } ] }; }); // Manipula as chamadas das ferramentas server.setRequestHandler(CallToolRequestSchema, async (request) => { const { name, arguments: args } = request.params; try { switch (name) { case 'buscar_artigos': { const { termo_busca, todas_paginas = false } = args as { termo_busca: string; todas_paginas?: boolean; }; const resultados = await rasparTodasPaginasBusca(termo_busca, todas_paginas); return { content: [ { type: 'text', text: JSON.stringify({ total_encontrados: resultados.length, artigos: resultados }, null, 2) } ] }; } case 'raspar_conteudo': { const { url } = args as { url: string }; const conteudo = await rasparConteudoPagina(url); return { content: [ { type: 'text', text: JSON.stringify(conteudo, null, 2) } ] }; } case 'criar_fichas_leitura': { const { termo_busca, todas_paginas = false, salvar = true, prompt_customizado } = args as { termo_busca: string; todas_paginas?: boolean; salvar?: boolean; prompt_customizado?: string; }; const fichas = await fichador(termo_busca, todas_paginas, salvar, prompt_customizado); return { content: [ { type: 'text', text: JSON.stringify({ total_fichas: fichas.length, fichas: fichas }, null, 2) } ] }; } default: throw new McpError( ErrorCode.MethodNotFound, `Ferramenta desconhecida: ${name}` ); } } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Erro desconhecido'; throw new McpError(ErrorCode.InternalError, `Erro na execução: ${errorMessage}`); } }); // Inicia o servidor async function main() { const transport = new StdioServerTransport(); await server.connect(transport); console.error('MCP Server Fichador iniciado e conectado via stdio'); } main().catch((error) => { console.error('Erro ao iniciar o servidor:', error); process.exit(1); });

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/SaideLeon/MCPServerFichador'

If you have feedback or need assistance with the MCP directory API, please join our Discord server