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.
294 lines (293 loc) • 12.5 kB
JavaScript
;
/**
* @moduleName: List Quizzes 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
* @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, enhanced-mcp.test.js, test-complete-workflow.js
* @testType: integration, e2e
* @testFramework: custom MCP protocol testing
* @briefDescription: Focused MCP tool for listing quizzes with pagination and filtering capabilities
* @methods: execute, validateInput, formatResponse, buildQuery
* @contributors: GitHub Copilot
* @examples:
* - const tool = new ListQuizzesTool(); await tool.execute(args, context);
* - MCP call: {"method": "tools/call", "params": {"name": "list_quizzes", "arguments": {...}}}
* @vulnerabilitiesAssessment: Input validation, pagination limits, SQL injection prevention
*/
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.ListQuizzesTool = void 0;
class ListQuizzesTool {
constructor() {
this.definition = {
name: 'list_quizzes',
description: 'List all available quizzes with pagination and filtering support',
inputSchema: {
type: 'object',
properties: {
limit: {
type: 'number',
description: 'Number of quizzes to return (max 100)',
minimum: 1,
maximum: 100,
default: 10,
},
offset: {
type: 'number',
description: 'Number of quizzes to skip for pagination',
minimum: 0,
default: 0,
},
category: {
type: 'string',
description: 'Filter by quiz category',
maxLength: 100,
},
difficulty: {
type: 'string',
description: 'Filter by difficulty level',
enum: ['easy', 'medium', 'hard'],
},
search: {
type: 'string',
description: 'Search quiz titles and descriptions',
maxLength: 200,
},
sortBy: {
type: 'string',
description: 'Field to sort by',
enum: ['title', 'createdAt', 'difficulty'],
default: 'createdAt',
},
sortOrder: {
type: 'string',
description: 'Sort order',
enum: ['asc', 'desc'],
default: 'desc',
},
},
},
};
}
async execute(args, context) {
try {
console.log('📋 Listing quizzes with filters:', args);
// Validate and set defaults
const filters = this.validateAndNormalizeInput(args);
// Get quizzes from database with safe access
const { getSafeDataSource, isDatabaseAvailable, initializeDatabaseSafe } = await Promise.resolve().then(() => __importStar(require('../../../database/config')));
const { Quiz } = await Promise.resolve().then(() => __importStar(require('../../../database/entities/Quiz')));
// Try to get existing database connection or initialize safely
let dataSource = getSafeDataSource();
if (!dataSource) {
console.log('🔄 Database not initialized - attempting safe initialization...');
dataSource = await initializeDatabaseSafe();
}
// If database is still not available, return fallback response
if (!dataSource || !isDatabaseAvailable()) {
console.warn('⚠️ Database not available - returning empty quiz list');
return {
content: [
{
type: 'text',
text: JSON.stringify({
quizzes: [],
total: 0,
pagination: {
limit: filters.limit,
offset: filters.offset,
totalPages: 0,
currentPage: 1,
},
message: 'Database not initialized - no quizzes available',
lastUpdated: new Date().toISOString(),
}, null, 2),
},
],
isError: false,
};
}
const quizRepository = dataSource.getRepository(Quiz);
// Build query with filters
let queryBuilder = quizRepository.createQueryBuilder('quiz');
if (filters.category) {
queryBuilder = queryBuilder.where('quiz.category = :category', {
category: filters.category,
});
}
if (filters.difficulty) {
queryBuilder = queryBuilder.andWhere('quiz.difficulty = :difficulty', {
difficulty: filters.difficulty,
});
}
// Tags filtering can be added later if needed in metadata
const allQuizzes = await queryBuilder.getMany();
// Apply client-side filtering for complex cases
const filteredQuizzes = allQuizzes;
const totalCount = filteredQuizzes.length;
// Apply pagination
const paginatedQuizzes = filteredQuizzes.slice(filters.offset, filters.offset + filters.limit);
console.log(`✅ Found ${totalCount} quizzes, returning ${paginatedQuizzes.length}`);
return this.formatResponse({
quizzes: paginatedQuizzes,
pagination: {
total: totalCount,
limit: filters.limit,
offset: filters.offset,
hasMore: filters.offset + filters.limit < totalCount,
},
filters: filters,
});
}
catch (error) {
console.error('❌ Quiz listing failed:', error);
return {
content: [
{
type: 'text',
text: `❌ Quiz listing failed: ${error instanceof Error ? error.message : 'Unknown error'}`,
},
],
isError: true,
};
}
}
validateAndNormalizeInput(args) {
return {
limit: Math.min(args.limit || 10, 100),
offset: Math.max(args.offset || 0, 0),
category: args.category || '',
difficulty: args.difficulty || '',
search: args.search || '',
sortBy: args.sortBy || 'createdAt',
sortOrder: args.sortOrder || 'desc',
};
}
applyFilters(quizzes, filters) {
let filtered = quizzes;
// Category filter
if (filters.category) {
filtered = filtered.filter(quiz => { var _a, _b; return ((_b = (_a = quiz.metadata) === null || _a === void 0 ? void 0 : _a.category) === null || _b === void 0 ? void 0 : _b.toLowerCase()) === filters.category.toLowerCase(); });
}
// Difficulty filter
if (filters.difficulty) {
filtered = filtered.filter(quiz => { var _a, _b; return ((_b = (_a = quiz.metadata) === null || _a === void 0 ? void 0 : _a.difficulty) === null || _b === void 0 ? void 0 : _b.toLowerCase()) === filters.difficulty.toLowerCase(); });
}
// Search filter
if (filters.search) {
const searchTerm = filters.search.toLowerCase();
filtered = filtered.filter(quiz => quiz.title.toLowerCase().includes(searchTerm) ||
quiz.description.toLowerCase().includes(searchTerm));
}
// Sorting
filtered.sort((a, b) => {
let aValue = a[filters.sortBy];
let bValue = b[filters.sortBy];
if (filters.sortBy === 'createdAt') {
aValue = new Date(aValue).getTime();
bValue = new Date(bValue).getTime();
}
else if (typeof aValue === 'string') {
aValue = aValue.toLowerCase();
bValue = bValue.toLowerCase();
}
if (filters.sortOrder === 'asc') {
return aValue > bValue ? 1 : -1;
}
else {
return aValue < bValue ? 1 : -1;
}
});
return filtered;
}
getMockQuizzes() {
// TODO: Replace with actual database query
return [
{
id: 'quiz-1',
title: 'JavaScript Fundamentals',
description: 'Test your JavaScript knowledge',
questionsCount: 10,
metadata: { category: 'programming', difficulty: 'easy' },
createdAt: '2025-07-20T10:00:00Z',
},
{
id: 'quiz-2',
title: 'Advanced TypeScript',
description: 'Advanced TypeScript concepts and patterns',
questionsCount: 15,
metadata: { category: 'programming', difficulty: 'hard' },
createdAt: '2025-07-22T14:30:00Z',
},
{
id: 'quiz-3',
title: 'React Hooks Deep Dive',
description: 'Master React hooks with practical examples',
questionsCount: 12,
metadata: { category: 'frontend', difficulty: 'medium' },
createdAt: '2025-07-24T09:15:00Z',
},
];
}
formatResponse(result) {
return {
content: [
{
type: 'text',
text: JSON.stringify({
success: true,
message: `📋 Found ${result.pagination.total} quizzes`,
data: result,
}, null, 2),
},
],
_meta: {
toolName: 'list_quizzes',
executionTime: Date.now(),
resultCount: result.pagination.total,
},
};
}
}
exports.ListQuizzesTool = ListQuizzesTool;