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
JavaScript
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
};