UNPKG

mcp-styledbook

Version:

Model Context Protocol para criação de ebooks estilizados com pesquisa profunda e formatação visual impactante

512 lines (459 loc) 16.8 kB
const express = require('express'); const fs = require('fs-extra'); const path = require('path'); const axios = require('axios'); const { McpServer } = require('@modelcontextprotocol/sdk/dist/cjs/server/mcp.js'); const { StdioServerTransport } = require('@modelcontextprotocol/sdk/dist/cjs/server/stdio.js'); const { HttpServerTransport } = require('@modelcontextprotocol/sdk/dist/cjs/server/streamableHttp.js'); const { z } = require('zod'); // Determina o tipo de transporte do ambiente const TRANSPORT = process.env.TRANSPORT || 'http'; const PORT = parseInt(process.env.PORT || '3001', 10); // Verifica se PORT é válido (entre 0 e 65535) if (isNaN(PORT) || PORT < 0 || PORT > 65535) { console.error('Porta inválida! Usando porta padrão 3001'); PORT = 3001; } // Middleware para processar JSON let app; if (TRANSPORT === 'http') { app = express(); app.use(express.json()); } // Implementação do servidor MCP const mcpServer = new McpServer({ name: "StyledBook", version: "1.0.3", description: "Cria ebooks estilizados a partir de temas com pesquisa profunda, estilo visceral e formatação visual impactante" }); // Função simplificada para pesquisa (mockada para testes) async function realizarPesquisa(tema) { console.log(`Realizando pesquisa sobre: ${tema}`); // Simula resultados de pesquisa return { resumo: [ { titulo: `Informações sobre ${tema}`, descricao: `Descrição sobre ${tema}`, url: `https://exemplo.com/${tema}` }, { titulo: `${tema} - Wikipedia`, descricao: `Artigo da Wikipedia sobre ${tema}`, url: `https://wikipedia.org/wiki/${tema}` } ], detalhado: [ { fonte: `Informações sobre ${tema}`, url: `https://exemplo.com/${tema}`, conteudo: `Este é um conteúdo detalhado sobre ${tema}. Inclui diversas informações relevantes para o assunto.` } ] }; } // Função simplificada para salvar imagens (mockada para testes) async function salvarImagemRelevante(tema, pasta) { console.log(`Simulando download de imagens para: ${tema}`); console.log(`Pasta destino: ${pasta}`); // Cria a pasta se não existir await fs.ensureDir(pasta); // Simula a criação de um arquivo imagem const mockImgPath = path.join(pasta, `${tema.replace(/[^a-zA-Z0-9]/g, '_')}_mock.txt`); await fs.writeFile(mockImgPath, `Esta é uma simulação de imagem para ${tema}`, {encoding: 'utf8'}); return 1; // Simula uma imagem baixada } // Função simulada para gerar PDF async function gerarPDF(arquivoMarkdown, arquivoPDF) { console.log(`Simulando geração de PDF de ${arquivoMarkdown} para ${arquivoPDF}`); // Cria um arquivo de texto plano simulando o PDF await fs.ensureDir(path.dirname(arquivoPDF)); await fs.writeFile(arquivoPDF, `Este é um PDF simulado gerado a partir de ${arquivoMarkdown}`, {encoding: 'utf8'}); return arquivoPDF; } // Formatação de saída para ferramentas MCP function formatarResposta(dados) { return JSON.stringify(dados, null, 2); } // Adiciona ferramenta: Pesquisar tema mcpServer.tool( "pesquisar_tema", { tema: z.string().describe("O tema a ser pesquisado") }, async ({tema}) => { try { const resultados = await realizarPesquisa(tema); return { content: [ { type: "text", text: formatarResposta({ resultados, mensagem: "Pesquisa realizada com sucesso" }) } ] }; } catch (error) { return { content: [ { type: "text", text: formatarResposta({ error: error.message }) } ] }; } } ); // Adiciona ferramenta: Criar planejamento mcpServer.tool( "criar_planejamento", { tema: z.string().describe("O tema do livro"), conteudoBase: z.any().describe("O conteúdo base obtido da pesquisa") }, async ({tema, conteudoBase}) => { try { return { content: [ { type: "text", text: formatarResposta({ tema: tema, conteudoBase: conteudoBase, instrucao: "Transforme este conteúdo em um planejamento com linguagem visceral, provocativa e criativa, como um manifesto revolucionário." }) } ] }; } catch (error) { return { content: [ { type: "text", text: formatarResposta({ error: error.message }) } ] }; } } ); // Adiciona ferramenta: Criar sumário mcpServer.tool( "criar_sumario", { tema: z.string().describe("O tema do livro"), planejamento: z.any().describe("O planejamento reimaginado"), numeroCapitulos: z.number().int().min(1).max(20).describe("Número de capítulos desejado") }, async ({tema, planejamento, numeroCapitulos}) => { try { // Gera um sumário de exemplo baseado no número de capítulos let sumarioGerado = `# ${tema.toUpperCase()}\n\n## Sumário\n\n`; for (let i = 1; i <= numeroCapitulos; i++) { sumarioGerado += `### CAPÍTULO ${i}: TÍTULO IMPACTANTE ${i}\n`; sumarioGerado += `- Tópico revolucionário ${i}.1\n`; sumarioGerado += `- Tópico provocativo ${i}.2\n`; sumarioGerado += `- Tópico visceral ${i}.3\n\n`; } // Configuração das pastas const pastaEbook = path.join(process.cwd(), 'ebook', tema.replace(/[^a-zA-Z0-9]/g, '_')); await fs.ensureDir(pastaEbook); // Salva o sumário gerado const arquivoSumario = path.join(pastaEbook, 'sumario.md'); await fs.writeFile(arquivoSumario, sumarioGerado, {encoding: 'utf8'}); return { content: [ { type: "text", text: formatarResposta({ tema, numeroCapitulos, sumarioGerado, arquivoSumario, mensagem: "Sumário criado com sucesso" }) } ] }; } catch (error) { return { content: [ { type: "text", text: formatarResposta({ error: error.message }) } ] }; } } ); // Adiciona ferramenta: Escrever capítulo mcpServer.tool( "escrever_capitulo", { tema: z.string().describe("O tema do livro"), tituloCapitulo: z.string().describe("O título do capítulo"), numeroCapitulo: z.number().int().min(1).describe("O número do capítulo"), conteudoBase: z.any().describe("O conteúdo base para o capítulo") }, async ({tema, tituloCapitulo, numeroCapitulo, conteudoBase}) => { try { // Configuração das pastas const pastaEbook = path.join(process.cwd(), 'ebook', tema.replace(/[^a-zA-Z0-9]/g, '_')); const pastaCapitulos = path.join(pastaEbook, 'capitulos'); // Cria a pasta se não existir await fs.ensureDir(pastaCapitulos); // Gera um texto simulado para o capítulo const textoCapitulo = `# CAPÍTULO ${numeroCapitulo}: ${tituloCapitulo.toUpperCase()}\n\n` + `Este é um conteúdo simulado para o capítulo ${numeroCapitulo} sobre ${tema}.\n\n` + `Aqui teríamos parágrafos viscerais e provocativos sobre o tema, baseados em ${conteudoBase.resumo ? conteudoBase.resumo.length : 0} fontes de pesquisa.\n\n` + `## Seção revolucionária 1\n\nTexto impactante que transformaria a visão do leitor.\n\n` + `## Seção visceral 2\n\nMais conteúdo provocativo que desafiaria o status quo.\n\n`; // Salva o capítulo const arquivoCapitulo = path.join(pastaCapitulos, `capitulo_${numeroCapitulo}.md`); await fs.writeFile(arquivoCapitulo, textoCapitulo, {encoding: 'utf8'}); // Simula download de uma imagem relevante const imagensDownload = await salvarImagemRelevante(tema, pastaCapitulos); return { content: [ { type: "text", text: formatarResposta({ tema, numeroCapitulo, tituloCapitulo, arquivoCapitulo, imagensDownload, mensagem: "Capítulo escrito com sucesso" }) } ] }; } catch (error) { return { content: [ { type: "text", text: formatarResposta({ error: error.message }) } ] }; } } ); // Adiciona ferramenta: Unir capítulos mcpServer.tool( "unir_capitulos", { tema: z.string().describe("O tema do livro"), numeroCapitulos: z.number().int().min(1).describe("O número total de capítulos") }, async ({tema, numeroCapitulos}) => { try { // Configuração das pastas const pastaEbook = path.join(process.cwd(), 'ebook', tema.replace(/[^a-zA-Z0-9]/g, '_')); const arquivoFinal = path.join(pastaEbook, 'ebookcompleto.md'); const pastaCapitulos = path.join(pastaEbook, 'capitulos'); // Verifica se a pasta de capítulos existe if (!await fs.pathExists(pastaCapitulos)) { await fs.ensureDir(pastaCapitulos); } // Verifica se o sumário existe, senão cria um sumário simples const caminhoSumario = path.join(pastaEbook, 'sumario.md'); let sumario = ''; if (await fs.pathExists(caminhoSumario)) { sumario = await fs.readFile(caminhoSumario, 'utf8'); } else { sumario = `# LIVRO SOBRE: ${tema.toUpperCase()}\n\n## Sumário\n\n`; for (let i = 1; i <= numeroCapitulos; i++) { sumario += `### CAPÍTULO ${i}\n\n`; } await fs.writeFile(caminhoSumario, sumario, {encoding: 'utf8'}); } let conteudoCompleto = sumario + '\n\n'; // Verifica cada capítulo e adiciona ao conteúdo completo for (let i = 1; i <= numeroCapitulos; i++) { const caminhoCapitulo = path.join(pastaCapitulos, `capitulo_${i}.md`); if (await fs.pathExists(caminhoCapitulo)) { const conteudoCapitulo = await fs.readFile(caminhoCapitulo, 'utf8'); conteudoCompleto += conteudoCapitulo + '\n\n'; } else { // Se o capítulo não existir, cria um conteúdo simulado const capituloSimulado = `# CAPÍTULO ${i}\n\nEste é um conteúdo simulado para o capítulo ${i}.\n\n`; conteudoCompleto += capituloSimulado; await fs.writeFile(caminhoCapitulo, capituloSimulado, {encoding: 'utf8'}); } } // Salva o arquivo completo await fs.writeFile(arquivoFinal, conteudoCompleto, {encoding: 'utf8'}); return { content: [ { type: "text", text: formatarResposta({ arquivoFinal, tamanho: conteudoCompleto.length, mensagem: "Ebook completo gerado com sucesso" }) } ] }; } catch (error) { return { content: [ { type: "text", text: formatarResposta({ error: error.message }) } ] }; } } ); // Adiciona ferramenta: Estilizar ebook mcpServer.tool( "estilizar_ebook", { tema: z.string().describe("O tema do livro"), paletaCores: z.array(z.string()).optional().describe("Paleta de cores para o ebook (hex)"), fonteTexto: z.string().optional().describe("Fonte para o texto principal"), fonteTitulos: z.string().optional().describe("Fonte para os títulos") }, async ({tema, paletaCores, fonteTexto, fonteTitulos}) => { try { const pastaEbook = path.join(process.cwd(), 'ebook', tema.replace(/[^a-zA-Z0-9]/g, '_')); const arquivoMarkdown = path.join(pastaEbook, 'ebookcompleto.md'); const arquivoPDF = path.join(pastaEbook, 'ebook_final.pdf'); // Verifica se o arquivo markdown existe if (!await fs.pathExists(arquivoMarkdown)) { // Se não existir, cria um conteúdo simulado const conteudoSimulado = `# LIVRO SOBRE ${tema.toUpperCase()}\n\nEste é um conteúdo simulado para o ebook.\n\n`; await fs.writeFile(arquivoMarkdown, conteudoSimulado, {encoding: 'utf8'}); } // Simula a geração do PDF await gerarPDF(arquivoMarkdown, arquivoPDF); return { content: [ { type: "text", text: formatarResposta({ arquivoPDF, mensagem: "PDF estilizado gerado com sucesso", estiloAplicado: { paleta: paletaCores || ["#1a1a1a", "#f5f5f5", "#3498db"], fonteTexto: fonteTexto || "Open Sans", fonteTitulos: fonteTitulos || "Montserrat" } }) } ] }; } catch (error) { return { content: [ { type: "text", text: formatarResposta({ error: error.message }) } ] }; } } ); // Adiciona ferramenta: Configurar parâmetros mcpServer.tool( "configurar_parametros_ebook", { tema: z.string().describe("O tema do livro"), estilo: z.enum(["formal", "informal", "técnico", "poético", "jornalístico", "acadêmico", "visceral"]).optional().describe("Estilo de escrita desejado"), numeroCapitulos: z.number().int().min(1).max(20).optional().describe("Número de capítulos desejados"), lingua: z.string().optional().describe("Língua em que o livro deve ser escrito") }, async ({tema, estilo, numeroCapitulos, lingua}) => { try { // Configuração padrão para parâmetros não especificados const config = { tema: tema, estilo: estilo || "visceral", numeroCapitulos: numeroCapitulos || 5, lingua: lingua || "português" }; const pastaEbook = path.join(process.cwd(), 'ebook', config.tema.replace(/[^a-zA-Z0-9]/g, '_')); await fs.ensureDir(pastaEbook); // Salva a configuração em um arquivo JSON const arquivoConfig = path.join(pastaEbook, 'config.json'); await fs.writeJson(arquivoConfig, config, {spaces: 2}); return { content: [ { type: "text", text: formatarResposta({ config, pastaEbook, mensagem: "Parâmetros configurados com sucesso" }) } ] }; } catch (error) { return { content: [ { type: "text", text: formatarResposta({ error: error.message }) } ] }; } } ); // Configurar e iniciar o servidor baseado no tipo de transporte async function iniciarServidor() { if (TRANSPORT === 'http') { // Rota para verificar se o servidor está rodando app.get('/', (req, res) => { res.json({ name: "StyledBook MCP", version: "1.0.3", status: "online", tools: Object.keys(mcpServer.tools) }); }); // Configuração do HTTP transport para o MCP const httpTransport = new HttpServerTransport({ basePath: '/mcp', app: app }); // Conectando o MCP server ao transport await mcpServer.connect(httpTransport); // Iniciando o servidor HTTP const serverInstance = app.listen(PORT, () => { console.log(`Servidor MCP StyledBook rodando na porta ${PORT}`); console.log(`Ferramentas disponíveis: ${Object.keys(mcpServer.tools).join(', ')}`); console.log(`URL do MCP: http://localhost:${PORT}/mcp`); }); return serverInstance; } else { // Usar stdio transport console.log(`Iniciando MCP StyledBook com transporte stdio`); console.log(`Ferramentas disponíveis: ${Object.keys(mcpServer.tools).join(', ')}`); // Inicia o servidor com stdio const stdioTransport = new StdioServerTransport(); await mcpServer.connect(stdioTransport); // Em modo stdio, o processo continuará em execução até ser encerrado return null; } } // Inicia o servidor iniciarServidor().catch(error => { console.error('Erro ao iniciar o servidor:', error); process.exit(1); }); // Exporta funções para uso como módulo module.exports = { mcpServer, iniciarServidor };