intelligent-composer-mcp-server.ts•23.4 kB
#!/usr/bin/env node
/**
* Intelligent EuConquisto Composer MCP Server
* Advanced AI-driven content creation with educational best practices
*/
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 { readFileSync } from 'fs';
import { resolve, dirname } from 'path';
import { fileURLToPath } from 'url';
import { StableBrowserManager, BrowserSession } from './stable-browser-manager.js';
import { EnhancedNLPWidgetParser, ParseOptions, EnhancedParsingResult } from './enhanced-nlp-widget-parser.js';
import { registerAllPlugins } from './content-element-plugins/index.js';
const currentDir = dirname(fileURLToPath(import.meta.url));
interface CompositionData {
version: string;
metadata: {
title: string;
description: string;
thumb?: string | null;
tags: string[];
};
interface: {
content_language: string;
index_option: string;
font_family: string;
show_summary: string;
finish_btn: string;
};
structure: any[];
assets: any[];
}
class IntelligentComposerMCPServer {
private server: Server;
private jwtToken!: string;
private baseURL = 'https://composer.euconquisto.com/#/embed';
private orgId = '36c92686-c494-ec11-a22a-dc984041c95d';
private browserManager: StableBrowserManager;
private intelligentParser: EnhancedNLPWidgetParser;
constructor() {
this.server = new Server(
{
name: 'intelligent-composer-mcp-server',
version: '2.0.0',
},
{
capabilities: {
tools: {},
},
}
);
this.browserManager = new StableBrowserManager();
this.intelligentParser = new EnhancedNLPWidgetParser();
// Register all content element plugins
registerAllPlugins();
this.loadJWTToken();
this.setupToolHandlers();
}
private loadJWTToken() {
try {
const tokenPath = resolve(currentDir, '..', 'correct-jwt-new.txt');
this.jwtToken = readFileSync(tokenPath, 'utf8').trim();
} catch (error) {
throw new Error('Failed to load JWT token: ' + error);
}
}
private setupToolHandlers() {
this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [
{
name: 'create-intelligent-composition',
description: 'Create a composition using AI-driven content analysis and educational best practices',
inputSchema: {
type: 'object',
properties: {
prompt: {
type: 'string',
description: 'Natural language description of the educational content to create',
},
title: {
type: 'string',
description: 'Title for the composition',
},
description: {
type: 'string',
description: 'Optional description for the composition',
default: '',
},
targetAudience: {
type: 'string',
enum: ['elementary', 'middle', 'high', 'college', 'adult', 'professional'],
description: 'Target audience for the content',
default: 'adult',
},
complexity: {
type: 'string',
enum: ['basic', 'intermediate', 'advanced'],
description: 'Content complexity level',
default: 'intermediate',
},
engagementLevel: {
type: 'string',
enum: ['low', 'medium', 'high'],
description: 'Desired level of interactivity and engagement',
default: 'medium',
},
maxDuration: {
type: 'number',
description: 'Maximum learning duration in minutes',
default: 30,
},
prioritizeInteractivity: {
type: 'boolean',
description: 'Whether to prioritize interactive elements',
default: false,
},
},
required: ['prompt', 'title'],
},
},
{
name: 'analyze-content-structure',
description: 'Analyze content and provide educational insights without creating a composition',
inputSchema: {
type: 'object',
properties: {
content: {
type: 'string',
description: 'Content to analyze',
},
title: {
type: 'string',
description: 'Optional title for context',
},
},
required: ['content'],
},
},
{
name: 'suggest-content-improvements',
description: 'Analyze existing content and suggest improvements based on educational best practices',
inputSchema: {
type: 'object',
properties: {
currentContent: {
type: 'string',
description: 'Current content to improve',
},
learningObjectives: {
type: 'array',
items: { type: 'string' },
description: 'Specific learning objectives to meet',
},
targetAudience: {
type: 'string',
enum: ['elementary', 'middle', 'high', 'college', 'adult', 'professional'],
description: 'Target audience',
default: 'adult',
},
},
required: ['currentContent'],
},
},
{
name: 'preview-composition',
description: 'Preview a composition by URL with intelligent analysis',
inputSchema: {
type: 'object',
properties: {
url: {
type: 'string',
description: 'The composition URL to preview and analyze',
},
},
required: ['url'],
},
},
],
}));
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
try {
switch (name) {
case 'create-intelligent-composition':
return await this.createIntelligentComposition(args);
case 'analyze-content-structure':
return await this.analyzeContentStructure(args);
case 'suggest-content-improvements':
return await this.suggestContentImprovements(args);
case 'preview-composition':
return await this.previewComposition(args);
default:
throw new McpError(ErrorCode.MethodNotFound, `Tool ${name} not found`);
}
} catch (error) {
throw new McpError(ErrorCode.InternalError, `Tool execution failed: ${error}`);
}
});
}
private async createIntelligentComposition(args: any) {
const {
prompt,
title,
description = '',
targetAudience = 'adult',
complexity = 'intermediate',
engagementLevel = 'medium',
maxDuration = 30,
prioritizeInteractivity = false,
} = args;
console.log('🧠 Creating intelligent composition...');
console.log(`📋 Parameters:
- Title: ${title}
- Target Audience: ${targetAudience}
- Complexity: ${complexity}
- Engagement Level: ${engagementLevel}
- Max Duration: ${maxDuration} minutes
- Prioritize Interactivity: ${prioritizeInteractivity}`);
// Step 1: Intelligent content analysis
const parseOptions: ParseOptions = {
enablePluginSystem: true,
applyEducationalBestPractices: true,
optimizeEngagement: engagementLevel === 'high',
maxWidgets: Math.ceil(maxDuration / 2), // ~2 minutes per widget
targetComplexity: complexity as any,
prioritizeInteractivity,
};
const analysisResult = this.intelligentParser.parseContentIntelligently(
prompt,
title,
parseOptions
);
console.log(`📊 Analysis Complete:
- Generated ${analysisResult.widgets.length} widgets
- Educational insights: ${analysisResult.contentInsights.length}
- Recommendations: ${analysisResult.recommendations.length}`);
// Step 2: Create composition with browser automation
return await this.browserManager.withStableSession(async (session: BrowserSession) => {
// Navigate and create new composition
await this.browserManager.safeClick(session.page, 'button:has-text("Nova Composição")');
await session.page.waitForTimeout(2000);
// Create composition data with intelligent widgets
const compositionData: CompositionData = {
version: '1.1',
metadata: {
title,
description: description || `AI-generated educational content about ${title}`,
thumb: null,
tags: this.extractTags(analysisResult),
},
interface: {
content_language: 'pt_br',
index_option: 'buttons',
font_family: 'Lato',
show_summary: analysisResult.educationalContext.estimatedDuration > 20 ? 'enabled' : 'disabled',
finish_btn: 'enabled',
},
structure: analysisResult.widgets,
assets: [],
};
// Set composition in localStorage
await session.page.evaluate((data: any) => {
// @ts-ignore - localStorage exists in browser context
localStorage.setItem('rdp-composer-data', JSON.stringify(data));
}, compositionData);
// Save the composition
await this.browserManager.safeClick(session.page, 'button:has-text("Salvar")');
await session.page.waitForTimeout(3000);
const savedURL = await session.page.url();
// Format comprehensive response
return {
content: [
{
type: 'text',
text: this.formatIntelligentCompositionResult(analysisResult, savedURL, title),
},
],
};
}, { headless: false, slowMo: 200 });
}
private async analyzeContentStructure(args: any) {
const { content, title } = args;
console.log('🔍 Analyzing content structure...');
const analysisResult = this.intelligentParser.parseContentIntelligently(
content,
title,
{
enablePluginSystem: true,
applyEducationalBestPractices: true,
optimizeEngagement: true,
}
);
return {
content: [
{
type: 'text',
text: this.formatContentAnalysis(analysisResult),
},
],
};
}
private async suggestContentImprovements(args: any) {
const { currentContent, learningObjectives = [], targetAudience = 'adult' } = args;
console.log('💡 Generating content improvement suggestions...');
// Analyze current content
const analysisResult = this.intelligentParser.parseContentIntelligently(
currentContent,
undefined,
{
enablePluginSystem: true,
applyEducationalBestPractices: true,
optimizeEngagement: true,
targetComplexity: 'intermediate',
}
);
// Generate specific improvement suggestions
const improvements = this.generateImprovementSuggestions(
analysisResult,
learningObjectives,
targetAudience
);
return {
content: [
{
type: 'text',
text: this.formatImprovementSuggestions(improvements, analysisResult),
},
],
};
}
private async previewComposition(args: any) {
const { url } = args;
try {
// Extract composition data from URL
const urlData = this.extractCompositionFromURL(url);
if (!urlData) {
throw new Error('Could not extract composition data from URL');
}
const composition = JSON.parse(urlData);
// Analyze the composition structure
const widgets = composition.structure || [];
const analysis = this.analyzeExistingComposition(widgets);
return {
content: [
{
type: 'text',
text: this.formatCompositionPreview(composition, analysis),
},
],
};
} catch (error) {
throw new Error(`Failed to preview composition: ${error}`);
}
}
// Helper methods for formatting and analysis
private formatIntelligentCompositionResult(
result: EnhancedParsingResult,
url: string,
title: string
): string {
const widgetSummary = this.summarizeWidgets(result.widgets);
const insightsSummary = this.summarizeInsights(result.contentInsights);
return `🎯 **Intelligent Composition Created Successfully!**
🔗 **Composition URL:** ${url}
📋 **Composition Details:**
• **Title:** ${title}
• **Widgets Generated:** ${result.widgets.length}
• **Estimated Duration:** ${result.educationalContext.estimatedDuration} minutes
• **Target Audience:** ${result.educationalContext.targetAudience}
• **Complexity Level:** ${result.educationalContext.contentComplexity}
• **Engagement Level:** ${result.educationalContext.engagementLevel}
🧩 **Widget Breakdown:**
${widgetSummary}
🧠 **Educational Insights:**
${insightsSummary}
💡 **Recommendations:**
${result.recommendations.map(rec => `• ${rec}`).join('\n')}
📚 **Learning Objectives:**
${result.educationalContext.learningObjectives.map(obj => `• ${obj}`).join('\n') || '• Interactive learning experience'}
⭐ **Quality Score:** ${this.calculateQualityScore(result)}/100`;
}
private formatContentAnalysis(result: EnhancedParsingResult): string {
return `📊 **Content Structure Analysis**
🎯 **Educational Context:**
• **Target Audience:** ${result.educationalContext.targetAudience}
• **Complexity:** ${result.educationalContext.contentComplexity}
• **Estimated Duration:** ${result.educationalContext.estimatedDuration} minutes
• **Engagement Level:** ${result.educationalContext.engagementLevel}
🧩 **Recommended Content Structure:**
${this.summarizeWidgets(result.widgets)}
🔍 **Content Insights:**
${this.summarizeInsights(result.contentInsights)}
📈 **Recommended Learning Sequence:**
${result.learningSequence.map((type, i) => `${i + 1}. ${this.getWidgetDescription(type)}`).join('\n')}
💡 **Optimization Suggestions:**
${result.recommendations.map(rec => `• ${rec}`).join('\n')}`;
}
private formatImprovementSuggestions(improvements: any[], result: EnhancedParsingResult): string {
return `💡 **Content Improvement Suggestions**
🔍 **Current Analysis:**
• **Quality Score:** ${this.calculateQualityScore(result)}/100
• **Widget Count:** ${result.widgets.length}
• **Engagement Elements:** ${result.widgets.filter(w => ['quiz-1', 'flashcards-1', 'hotspot-1'].includes(w.type)).length}
🚀 **Priority Improvements:**
${improvements.filter(imp => imp.priority === 'high').map(imp => `• **${imp.title}:** ${imp.description}`).join('\n')}
📈 **Additional Enhancements:**
${improvements.filter(imp => imp.priority === 'medium').map(imp => `• ${imp.title}: ${imp.description}`).join('\n')}
⚡ **Quick Wins:**
${improvements.filter(imp => imp.priority === 'low').map(imp => `• ${imp.description}`).join('\n')}
🎯 **Educational Impact:**
${result.contentInsights.filter(insight => insight.impact === 'high').map(insight => `• ${insight.message}`).join('\n')}`;
}
private formatCompositionPreview(composition: any, analysis: any): string {
return `👀 **Composition Preview & Analysis**
📋 **Basic Information:**
• **Title:** ${composition.metadata?.title || 'Untitled'}
• **Description:** ${composition.metadata?.description || 'No description'}
• **Version:** ${composition.version || 'Unknown'}
🧩 **Structure Analysis:**
• **Total Widgets:** ${composition.structure?.length || 0}
• **Widget Types:** ${analysis.widgetTypes.join(', ')}
• **Interactive Elements:** ${analysis.interactiveCount}
• **Engagement Score:** ${analysis.engagementScore}/100
📊 **Educational Assessment:**
• **Complexity Level:** ${analysis.complexityLevel}
• **Learning Patterns:** ${analysis.learningPatterns.join(', ')}
• **Content Distribution:** ${analysis.contentDistribution}
💡 **Improvement Opportunities:**
${analysis.suggestions.map((suggestion: string) => `• ${suggestion}`).join('\n')}`;
}
// Analysis helper methods
private summarizeWidgets(widgets: any[]): string {
const widgetCounts: Record<string, number> = {};
widgets.forEach(widget => {
const type = widget.type;
widgetCounts[type] = (widgetCounts[type] || 0) + 1;
});
return Object.entries(widgetCounts)
.map(([type, count]) => `• ${this.getWidgetDescription(type)}: ${count}`)
.join('\n');
}
private summarizeInsights(insights: any[]): string {
if (insights.length === 0) return '• All educational best practices are being followed';
return insights
.map(insight => `• **${insight.type.toUpperCase()}:** ${insight.message}`)
.join('\n');
}
private getWidgetDescription(type: string): string {
const descriptions: Record<string, string> = {
'head-1': 'Header Section',
'text-1': 'Text Content',
'quiz-1': 'Multiple Choice Quiz',
'video-1': 'Video Content',
'flashcards-1': 'Interactive Flashcards',
'hotspot-1': 'Interactive Hotspot',
'gallery-1': 'Image Gallery',
'list-1': 'Structured List',
};
return descriptions[type] || type.replace('-', ' ').replace(/\b\w/g, l => l.toUpperCase());
}
private calculateQualityScore(result: EnhancedParsingResult): number {
let score = 70; // Base score
// Widget variety bonus
const uniqueTypes = new Set(result.widgets.map(w => w.type));
score += Math.min(uniqueTypes.size * 5, 15);
// Interactive content bonus
const interactiveCount = result.widgets.filter(w =>
['quiz-1', 'flashcards-1', 'hotspot-1'].includes(w.type)
).length;
score += Math.min(interactiveCount * 3, 10);
// Educational insights penalty
const highImpactIssues = result.contentInsights.filter(insight => insight.impact === 'high').length;
score -= highImpactIssues * 5;
return Math.max(0, Math.min(100, score));
}
private extractTags(result: EnhancedParsingResult): string[] {
const tags = [];
// Add audience tag
tags.push(result.educationalContext.targetAudience);
// Add complexity tag
tags.push(result.educationalContext.contentComplexity);
// Add widget type tags
const uniqueTypes = new Set(result.widgets.map(w => w.type));
if (uniqueTypes.has('quiz-1')) tags.push('assessment');
if (uniqueTypes.has('video-1')) tags.push('multimedia');
if (uniqueTypes.has('flashcards-1')) tags.push('memorization');
// Add AI-generated tag
tags.push('ai-generated');
return tags;
}
private generateImprovementSuggestions(
result: EnhancedParsingResult,
objectives: string[],
audience: string
): any[] {
const improvements = [];
// Check for missing assessments
const hasQuiz = result.widgets.some(w => w.type === 'quiz-1');
if (!hasQuiz && objectives.length > 0) {
improvements.push({
title: 'Add Knowledge Assessment',
description: 'Include quiz questions to verify learning objectives are met',
priority: 'high',
});
}
// Check for multimedia content
const hasVideo = result.widgets.some(w => w.type === 'video-1');
if (!hasVideo && result.educationalContext.contentComplexity !== 'basic') {
improvements.push({
title: 'Add Visual Content',
description: 'Include video explanations for complex concepts',
priority: 'medium',
});
}
// Check for interactive elements
const interactiveCount = result.widgets.filter(w =>
['quiz-1', 'flashcards-1', 'hotspot-1'].includes(w.type)
).length;
if (interactiveCount < 2) {
improvements.push({
title: 'Increase Interactivity',
description: 'Add more interactive elements to maintain engagement',
priority: 'medium',
});
}
return improvements;
}
private analyzeExistingComposition(widgets: any[]): any {
const widgetTypes = widgets.map(w => w.type);
const uniqueTypes = [...new Set(widgetTypes)];
const interactiveCount = widgets.filter(w =>
['quiz-1', 'flashcards-1', 'hotspot-1'].includes(w.type)
).length;
return {
widgetTypes: uniqueTypes,
interactiveCount,
engagementScore: Math.min(100, (interactiveCount / widgets.length) * 100 + 50),
complexityLevel: widgets.length > 10 ? 'advanced' : widgets.length > 5 ? 'intermediate' : 'basic',
learningPatterns: this.identifyLearningPatterns(widgets),
contentDistribution: this.analyzeContentDistribution(widgets),
suggestions: this.generateCompositionSuggestions(widgets),
};
}
private identifyLearningPatterns(widgets: any[]): string[] {
const patterns = [];
if (widgets.some(w => w.type === 'quiz-1')) patterns.push('Assessment');
if (widgets.some(w => w.type === 'video-1')) patterns.push('Visual Learning');
if (widgets.some(w => w.type === 'flashcards-1')) patterns.push('Memorization');
if (widgets.some(w => w.type === 'text-1')) patterns.push('Reading');
return patterns;
}
private analyzeContentDistribution(widgets: any[]): string {
const total = widgets.length;
const interactive = widgets.filter(w => ['quiz-1', 'flashcards-1', 'hotspot-1'].includes(w.type)).length;
const content = widgets.filter(w => ['text-1', 'video-1'].includes(w.type)).length;
const interactivePercent = Math.round((interactive / total) * 100);
const contentPercent = Math.round((content / total) * 100);
return `${interactivePercent}% Interactive, ${contentPercent}% Content`;
}
private generateCompositionSuggestions(widgets: any[]): string[] {
const suggestions = [];
const hasQuiz = widgets.some(w => w.type === 'quiz-1');
if (!hasQuiz) suggestions.push('Add knowledge check questions');
const hasVideo = widgets.some(w => w.type === 'video-1');
if (!hasVideo) suggestions.push('Consider adding video explanations');
const textCount = widgets.filter(w => w.type === 'text-1').length;
if (textCount > widgets.length * 0.7) suggestions.push('Break up long text sections with interactive elements');
return suggestions;
}
private extractCompositionFromURL(url: string): string | null {
try {
const match = url.match(/\/composer\/([A-Za-z0-9+/=]+)$/);
if (!match) return null;
const encodedData = match[1];
const buffer = Buffer.from(encodedData, 'base64');
const { gunzipSync } = require('zlib');
const decompressed = gunzipSync(buffer);
return decompressed.toString();
} catch (error) {
return null;
}
}
async run() {
const transport = new StdioServerTransport();
await this.server.connect(transport);
}
}
const server = new IntelligentComposerMCPServer();
server.run().catch(console.error);