#!/usr/bin/env node
/**
* VITI PPTX Generator - MCP Server
*
* Generates lesson presentations from markdown content.
*
* Tools:
* - generate_presentation: Create PPTX from markdown
* - get_template: Get markdown template example
* - get_config: View current configuration
* - update_instructor: Update instructor info
*/
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
CallToolRequestSchema,
ListToolsRequestSchema,
} from '@modelcontextprotocol/sdk/types.js';
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
import { generatePresentation, loadConfig, saveConfig } from './generator.js';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const OUTPUT_DIR = path.join(__dirname, 'output');
// Ensure output directory exists
if (!fs.existsSync(OUTPUT_DIR)) {
fs.mkdirSync(OUTPUT_DIR, { recursive: true });
}
// Create server instance
const server = new Server(
{ name: 'pptx-generator', version: '1.0.0' },
{ capabilities: { tools: {} } }
);
// Define available tools
server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [
{
name: 'generate_presentation',
description: 'Generate a PPTX presentation from markdown content. Returns path to the generated file.',
inputSchema: {
type: 'object',
properties: {
markdown: {
type: 'string',
description: 'Full markdown content including frontmatter (---) with discipline, type, module, lesson fields and slide sections marked with ## [type] Title'
},
filename: {
type: 'string',
description: 'Output filename without extension (e.g., "python-lesson-5")'
}
},
required: ['markdown', 'filename']
}
},
{
name: 'get_template',
description: 'Get an example markdown template showing the correct format for creating presentations',
inputSchema: {
type: 'object',
properties: {},
required: []
}
},
{
name: 'get_config',
description: 'Get current presentation configuration (colors, fonts, instructor info)',
inputSchema: {
type: 'object',
properties: {},
required: []
}
},
{
name: 'update_instructor',
description: 'Update instructor information in the configuration',
inputSchema: {
type: 'object',
properties: {
rank: { type: 'string', description: 'Military rank (e.g., "майор")' },
name: { type: 'string', description: 'Full name (e.g., "Дмитро УСТИНОВ")' }
},
required: []
}
},
{
name: 'list_presentations',
description: 'List all generated presentations in the output folder',
inputSchema: {
type: 'object',
properties: {},
required: []
}
}
]
}));
// Markdown template
const MARKDOWN_TEMPLATE = `---
discipline: Назва дисципліни
type: lecture
module: "1: Назва модуля"
lesson: "1.1: Назва заняття"
---
## [plan] План заняття
- Перша тема
- Друга тема
- Третя тема
## [divider] 🔹 Частина 1: Назва розділу
## [content] Заголовок слайду
- Перший пункт
- Другий пункт
- Третій пункт
## [code] Приклад коду
\`\`\`python
def hello():
print("Hello, World!")
\`\`\`
## [divider] 🔹 Частина 2: Інший розділ
## [content] Ще один слайд
- Інформація
- Деталі
`;
// Handle tool calls
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
try {
switch (name) {
case 'generate_presentation': {
const filename = args.filename.replace(/\.pptx$/i, '');
const outputPath = path.join(OUTPUT_DIR, `${filename}.pptx`);
await generatePresentation(args.markdown, outputPath, __dirname);
return {
content: [{
type: 'text',
text: `✅ Презентацію створено!\n\n📁 Файл: ${outputPath}\n\nМожете завантажити на Google Drive.`
}]
};
}
case 'get_template': {
return {
content: [{
type: 'text',
text: `# Шаблон Markdown для презентації\n\nВикористовуйте цей формат:\n\n\`\`\`markdown\n${MARKDOWN_TEMPLATE}\`\`\`\n\n## Типи слайдів:\n- \`[plan]\` - План заняття\n- \`[divider]\` - Розділювач\n- \`[content]\` - Контент з буллетами\n- \`[code]\` - Слайд з кодом\n\n## Типи занять (frontmatter type):\n- \`lecture\` - Лекційне заняття\n- \`practical\` - Практичне заняття\n- \`group\` - Групове заняття`
}]
};
}
case 'get_config': {
const config = loadConfig(__dirname);
return {
content: [{
type: 'text',
text: `# Поточна конфігурація\n\n\`\`\`json\n${JSON.stringify(config, null, 2)}\n\`\`\``
}]
};
}
case 'update_instructor': {
const config = loadConfig(__dirname);
if (args.rank) config.instructor.rank = args.rank;
if (args.name) config.instructor.name = args.name;
saveConfig(__dirname, config);
return {
content: [{
type: 'text',
text: `✅ Інформацію про викладача оновлено!\n\nЗвання: ${config.instructor.rank}\nІм'я: ${config.instructor.name}`
}]
};
}
case 'list_presentations': {
const files = fs.readdirSync(OUTPUT_DIR)
.filter(f => f.endsWith('.pptx'))
.map(f => {
const stat = fs.statSync(path.join(OUTPUT_DIR, f));
return `- ${f} (${(stat.size / 1024).toFixed(1)} KB, ${stat.mtime.toLocaleDateString()})`;
});
return {
content: [{
type: 'text',
text: files.length > 0
? `# Створені презентації\n\n📁 ${OUTPUT_DIR}\n\n${files.join('\n')}`
: `📁 Папка порожня: ${OUTPUT_DIR}`
}]
};
}
default:
throw new Error(`Unknown tool: ${name}`);
}
} catch (error) {
return {
content: [{
type: 'text',
text: `❌ Помилка: ${error.message}`
}],
isError: true
};
}
});
// Start server
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error('PPTX Generator MCP Server running...');
}
main().catch(console.error);