mcp-quiz-server
Version:
🧠 AI-Powered Quiz Management via Model Context Protocol (MCP) - Create, manage, and take quizzes directly from VS Code, Claude, and other AI agents.
218 lines (217 loc) • 9.28 kB
JavaScript
;
/**
* @moduleName: Create Quiz Tool - MCP Tool Implementation
* @version: 2.0.0
* @since: 2025-07-25
* @lastUpdated: 2025-07-25
* @projectSummary: Enhanced MCP Quiz Server - Modular Architecture
* @techStack: TypeScript, JSON-RPC 2.0, MCP Protocol, TypeORM, UUID
* @dependency: typeorm, uuid
* @interModuleDependency: ../../types/mcp-types, ../../security/input-validator, ../../utils/database-manager
* @requirementsTraceability:
* {@link Requirements.REQ_API_001} (Quiz CRUD Operations)
* {@link Requirements.REQ_MCP_002} (Dynamic Tool Registry)
* {@link Requirements.REQ_MCP_004} (Quiz Management Tools)
* @testCoverage: Extensively tested in test-all-tools.js, test-debug-create.js, test-complete-workflow.js
* @testType: integration, e2e
* @testFramework: custom MCP protocol testing
* @briefDescription: Focused MCP tool for creating new quizzes with database persistence and validation
* @methods: execute, validateInput, formatResponse
* @contributors: GitHub Copilot
* @examples:
* - const tool = new CreateQuizTool(); await tool.execute(args, context);
* - MCP call: {"method": "tools/call", "params": {"name": "create_quiz", "arguments": {...}}}
* @vulnerabilitiesAssessment: Input validation, SQL injection prevention, UUID security
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.CreateQuizTool = void 0;
const uuid_1 = require("uuid");
// Import database entities and configuration
const config_1 = require("../../../database/config");
const Question_1 = require("../../../database/entities/Question");
const Quiz_1 = require("../../../database/entities/Quiz");
class CreateQuizTool {
constructor() {
this.definition = {
name: 'create_quiz',
description: 'Create a new quiz with database persistence and unique ID generation',
inputSchema: {
type: 'object',
properties: {
title: {
type: 'string',
description: 'The title of the quiz',
minLength: 1,
maxLength: 200,
},
description: {
type: 'string',
description: 'Optional quiz description',
maxLength: 1000,
},
questions: {
type: 'array',
description: 'Array of quiz questions',
minItems: 1,
maxItems: 100,
items: {
type: 'object',
properties: {
question: { type: 'string', minLength: 1, maxLength: 500 },
options: {
type: 'array',
items: { type: 'string', maxLength: 200 },
minItems: 2,
maxItems: 10,
},
answer: { type: 'string', minLength: 1, maxLength: 200 },
explanation: { type: 'string', maxLength: 1000 },
},
required: ['question', 'options', 'answer'],
},
},
metadata: {
type: 'object',
properties: {
category: { type: 'string', maxLength: 100 },
difficulty: { type: 'string', enum: ['easy', 'medium', 'hard'] },
tags: {
type: 'array',
items: { type: 'string', maxLength: 50 },
maxItems: 10,
},
},
},
},
required: ['title', 'questions'],
},
};
}
async execute(args, context) {
// Validate input arguments first - let validation errors bubble up
this.validateInput(args);
console.log('🔨 Creating new quiz:', args.title);
try {
// Generate unique quiz ID
const quizId = (0, uuid_1.v4)();
const now = new Date();
// Create quiz entity (simplified for now - will use proper entities later)
const quizData = {
id: quizId,
title: args.title,
description: args.description || '',
questions: args.questions.map((q, index) => ({
id: (0, uuid_1.v4)(),
questionNumber: index + 1,
question: q.question,
options: q.options,
correctAnswer: q.answer,
explanation: q.explanation || '',
})),
metadata: args.metadata || {},
createdAt: now,
updatedAt: now,
};
// Save to database using TypeORM
let dataSource = (0, config_1.getSafeDataSource)();
if (!dataSource) {
// Initialize database if not available
dataSource = await (0, config_1.initializeDatabase)();
}
const quizRepository = dataSource.getRepository(Quiz_1.Quiz);
const questionRepository = dataSource.getRepository(Question_1.Question);
// Create quiz entity
const quiz = quizRepository.create({
id: quizId,
title: args.title,
description: args.description || '',
category: args.category || 'general',
difficulty: args.difficulty || 'medium',
timeLimit: args.timeLimit || 0,
metadata: args.metadata || {},
createdAt: now,
updatedAt: now,
});
// Save quiz first
const savedQuiz = await quizRepository.save(quiz);
// Create and save questions
const questionEntities = args.questions.map((q, index) => {
return questionRepository.create({
quizId: savedQuiz.id,
question: q.question,
options: q.options,
answer: q.answer,
explanation: q.explanation || '',
order: index + 1,
});
});
await questionRepository.save(questionEntities);
console.log('✅ Quiz created successfully:', quizId);
return this.formatResponse({
quizId,
questionsCount: args.questions.length,
createdAt: now.toISOString(),
title: args.title,
});
}
catch (error) {
console.error('❌ Quiz creation failed:', error);
return {
content: [
{
type: 'text',
text: `❌ Quiz creation failed: ${error instanceof Error ? error.message : 'Unknown error'}`,
},
],
isError: true,
};
}
}
validateInput(args) {
var _a;
if (!args) {
throw new Error('Arguments are required');
}
if (!((_a = args.title) === null || _a === void 0 ? void 0 : _a.trim())) {
throw new Error('Quiz title is required and cannot be empty');
}
if (!Array.isArray(args.questions) || args.questions.length === 0) {
throw new Error('At least one question is required');
}
// Validate each question
args.questions.forEach((question, index) => {
var _a, _b;
if (!((_a = question.question) === null || _a === void 0 ? void 0 : _a.trim())) {
throw new Error(`Question ${index + 1}: Question text is required`);
}
if (!Array.isArray(question.options) || question.options.length < 2) {
throw new Error(`Question ${index + 1}: At least 2 options are required`);
}
if (!((_b = question.answer) === null || _b === void 0 ? void 0 : _b.trim())) {
throw new Error(`Question ${index + 1}: Answer is required`);
}
if (!question.options.includes(question.answer)) {
throw new Error(`Question ${index + 1}: Answer must be one of the provided options`);
}
});
}
formatResponse(result) {
return {
content: [
{
type: 'text',
text: JSON.stringify({
success: true,
message: '🎉 Quiz created successfully!',
data: result,
}, null, 2),
},
],
_meta: {
toolName: 'create_quiz',
executionTime: Date.now(),
},
};
}
}
exports.CreateQuizTool = CreateQuizTool;