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.
268 lines (267 loc) • 11.7 kB
JavaScript
;
/**
* @moduleName: Get Quiz Tool - MCP Tool Implementation
* @ve // Get quiz from database with questions
const { getDataSource } = await import('../../../database/config');
const { Quiz } = await import('../../../database/entities/Quiz');
const Question = await import('../../../database/entities/Question');n: 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
* @dependency: typeorm
* @interModuleDependency: ../../types/mcp-types, ./create-quiz
* @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: Tested in test-all-tools.js, test-complete-workflow.js, enhanced-mcp.test.js
* @testType: integration, e2e
* @testFramework: custom MCP protocol testing
* @briefDescription: Focused MCP tool for retrieving specific quiz data with optional question inclusion
* @methods: execute, validateInput, formatResponse, findQuizById
* @contributors: GitHub Copilot
* @examples:
* - const tool = new GetQuizTool(); await tool.execute({quizId: "123"}, context);
* - MCP call: {"method": "tools/call", "params": {"name": "get_quiz", "arguments": {"quizId": "123"}}}
* @vulnerabilitiesAssessment: UUID validation, data sanitization, access control ready
*/
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.GetQuizTool = void 0;
class GetQuizTool {
constructor() {
this.definition = {
name: 'get_quiz',
description: 'Retrieve a specific quiz by ID with optional question details',
inputSchema: {
type: 'object',
properties: {
quizId: {
type: 'string',
description: 'Unique identifier of the quiz to retrieve',
pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$',
},
includeQuestions: {
type: 'boolean',
description: 'Whether to include full question details',
default: true,
},
includeMetadata: {
type: 'boolean',
description: 'Whether to include quiz metadata and analytics',
default: true,
},
},
required: ['quizId'],
},
};
}
async execute(args, context) {
try {
// Validate input first
this.validateInput(args);
console.log('🔍 Retrieving quiz:', args.quizId);
// Get quiz from database with questions
const { getSafeDataSource, initializeDatabase } = await Promise.resolve().then(() => __importStar(require('../../../database/config')));
const { Quiz } = await Promise.resolve().then(() => __importStar(require('../../../database/entities/Quiz')));
const Question = await Promise.resolve().then(() => __importStar(require('../../../database/entities/Question')));
let dataSource = getSafeDataSource();
if (!dataSource) {
// Initialize database if not available
dataSource = await initializeDatabase();
}
const quizRepository = dataSource.getRepository(Quiz);
const quiz = await quizRepository.findOne({
where: { id: args.quizId },
relations: ['questions'],
});
if (!quiz) {
return {
content: [
{
type: 'text',
text: JSON.stringify({
success: false,
error: 'Quiz not found',
message: `❌ Quiz with ID ${args.quizId} does not exist`,
}, null, 2),
},
],
isError: true,
};
}
console.log('✅ Quiz retrieved successfully:', quiz.title);
return this.formatResponse(quiz);
}
catch (error) {
console.error('❌ Quiz retrieval failed:', error);
return {
content: [
{
type: 'text',
text: `❌ Quiz retrieval failed: ${error instanceof Error ? error.message : 'Unknown error'}`,
},
],
isError: true,
};
}
}
validateInput(args) {
var _a;
if (!args || !((_a = args.quizId) === null || _a === void 0 ? void 0 : _a.trim())) {
throw new Error('Quiz ID is required');
}
// Validate UUID format
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
if (!uuidRegex.test(args.quizId)) {
throw new Error('Invalid quiz ID format. Must be a valid UUID.');
}
}
async findQuizById(quizId, options) {
// TODO: Replace with actual database query
// const quiz = await context.dataSource
// .getRepository(Quiz)
// .findOne({
// where: { id: quizId },
// relations: options.includeQuestions ? ['questions'] : [],
// });
// Mock data for now
const mockQuizzes = {
'quiz-1': {
id: 'quiz-1',
title: 'JavaScript Fundamentals',
description: 'Test your JavaScript knowledge with these fundamental questions',
questionsCount: 3,
createdAt: '2025-07-20T10:00:00Z',
updatedAt: '2025-07-20T10:00:00Z',
metadata: {
category: 'programming',
difficulty: 'easy',
tags: ['javascript', 'basics', 'programming'],
estimatedTime: 15,
},
questions: options.includeQuestions
? [
{
id: 'q1',
questionNumber: 1,
question: 'What is the correct way to declare a variable in JavaScript?',
options: ['var x = 5;', 'variable x = 5;', 'v x = 5;', 'declare x = 5;'],
correctAnswer: 'var x = 5;',
explanation: 'In JavaScript, variables are declared using var, let, or const keywords.',
},
{
id: 'q2',
questionNumber: 2,
question: 'Which method is used to add an element to the end of an array?',
options: ['push()', 'pop()', 'shift()', 'unshift()'],
correctAnswer: 'push()',
explanation: 'The push() method adds one or more elements to the end of an array.',
},
{
id: 'q3',
questionNumber: 3,
question: 'What does === operator do in JavaScript?',
options: ['Assignment', 'Strict equality', 'Loose equality', 'Not equal'],
correctAnswer: 'Strict equality',
explanation: 'The === operator checks for strict equality without type coercion.',
},
]
: undefined,
},
'quiz-2': {
id: 'quiz-2',
title: 'Advanced TypeScript',
description: 'Advanced TypeScript concepts and patterns',
questionsCount: 5,
createdAt: '2025-07-22T14:30:00Z',
updatedAt: '2025-07-22T14:30:00Z',
metadata: {
category: 'programming',
difficulty: 'hard',
tags: ['typescript', 'advanced', 'types'],
estimatedTime: 30,
},
questions: options.includeQuestions
? [
{
id: 'q1-ts',
questionNumber: 1,
question: 'What is a generic in TypeScript?',
options: [
'A type that can work with any data type',
'A specific data type',
'A function parameter',
'A class method',
],
correctAnswer: 'A type that can work with any data type',
explanation: 'Generics provide a way to make components work with any data type.',
},
]
: undefined,
},
};
return mockQuizzes[quizId] || null;
}
formatResponse(quiz) {
const responseData = {
success: true,
message: `🎯 Quiz "${quiz.title}" retrieved successfully`,
data: {
quiz: {
...quiz,
// Remove questions from summary if they weren't requested
...(quiz.questions ? {} : { questions: undefined }),
},
},
};
return {
content: [
{
type: 'text',
text: JSON.stringify(responseData, null, 2),
},
],
_meta: {
toolName: 'get_quiz',
executionTime: Date.now(),
quizId: quiz.id,
includeQuestions: !!quiz.questions,
},
};
}
}
exports.GetQuizTool = GetQuizTool;