get_exam_history
Retrieve all completed practice exam attempts with scores, pass/fail status, and per-domain breakdowns. Compare your progress across attempts.
Instructions
View all completed practice exam attempts with scores, pass/fail status, and per-domain breakdowns. Compare your progress across attempts.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
No arguments | |||
Implementation Reference
- src/tools/get-exam-history.ts:7-83 (handler)The main tool handler 'get_exam_history'. It registers an MCP tool that retrieves all completed exam attempts from the database, formats them with scores, pass/fail status, domain breakdowns, improvement deltas, and an overall trend summary.
export function registerGetExamHistory(server: McpServer, db: Database.Database, userConfig: UserConfig): void { server.tool( 'get_exam_history', 'View all completed practice exam attempts with scores, pass/fail status, and per-domain breakdowns. Compare your progress across attempts.', {}, async () => { const userId = userConfig.userId; ensureUser(db, userId); const history = getExamHistory(db, userId); if (history.length === 0) { return { content: [{ type: 'text' as const, text: [ '═══ EXAM HISTORY ═══', '', 'No completed practice exams yet.', '', 'Use start_practice_exam to take your first 60-question practice exam.', 'Questions are weighted by domain — just like the real exam.', ].join('\n'), }], }; } const lines: string[] = [ '═══ EXAM HISTORY ═══', '', `Total Attempts: ${history.length}`, `Best Score: ${Math.max(...history.map(h => h.score))}/1000`, `Latest Score: ${history[0].score}/1000`, '', ]; for (const [i, attempt] of history.entries()) { const label = i === 0 ? ' (latest)' : ''; lines.push(`─── Attempt #${history.length - i}${label} ───`); lines.push(` Date: ${attempt.completedAt ?? attempt.startedAt}`); lines.push(` Score: ${attempt.score}/1000 ${attempt.passed ? '✅ PASSED' : '❌ FAILED'}`); lines.push(` Correct: ${attempt.correctAnswers}/${attempt.totalQuestions} (${Math.round((attempt.correctAnswers / attempt.totalQuestions) * 100)}%)`); lines.push(''); lines.push(' Domain Scores:'); const scores = attempt.domainScores; for (const key of Object.keys(scores).sort()) { const ds = scores[key]; lines.push(` D${ds.domainId}: ${ds.domainTitle} — ${ds.correctAnswers}/${ds.totalQuestions} (${ds.accuracyPercent}%)`); } // Show improvement from previous attempt if (i < history.length - 1) { const previous = history[i + 1]; const diff = attempt.score - previous.score; const arrow = diff > 0 ? '↑' : diff < 0 ? '↓' : '→'; lines.push(''); lines.push(` Change from previous: ${arrow} ${diff > 0 ? '+' : ''}${diff} points`); } lines.push(''); } // Trend summary if (history.length >= 2) { const latest = history[0].score; const first = history[history.length - 1].score; const totalImprovement = latest - first; lines.push('─── Overall Trend ───'); lines.push(` First attempt: ${first}/1000`); lines.push(` Latest attempt: ${latest}/1000`); lines.push(` Total improvement: ${totalImprovement > 0 ? '+' : ''}${totalImprovement} points`); } return { content: [{ type: 'text' as const, text: lines.join('\n') }] }; } ); } - src/db/exam-attempts.ts:76-81 (helper)Database query function that retrieves all completed exam attempts for a user, ordered by completion date descending.
export function getExamHistory(db: Database.Database, userId: string): readonly ExamAttempt[] { const rows = db.prepare( 'SELECT * FROM exam_attempts WHERE userId = ? AND completedAt IS NOT NULL ORDER BY completedAt DESC' ).all(userId) as Record<string, unknown>[]; return rows.map(rowToExamAttempt); } - src/types.ts:146-158 (schema)The ExamAttempt interface defining the shape of exam history data (id, userId, startedAt, completedAt, totalQuestions, correctAnswers, score, passed, questionIds, answeredQuestionIds, domainScores).
export interface ExamAttempt { readonly id: number; readonly userId: string; readonly startedAt: string; readonly completedAt: string | null; readonly totalQuestions: number; readonly correctAnswers: number; readonly score: number; readonly passed: boolean; readonly questionIds: readonly string[]; readonly answeredQuestionIds: readonly string[]; readonly domainScores: Readonly<Record<string, DomainExamScore>>; } - src/types.ts:160-167 (schema)The DomainExamScore interface used within ExamAttempt for per-domain breakdowns (domainId, domainTitle, totalQuestions, correctAnswers, accuracyPercent, weight).
export interface DomainExamScore { readonly domainId: number; readonly domainTitle: string; readonly totalQuestions: number; readonly correctAnswers: number; readonly accuracyPercent: number; readonly weight: number; } - src/tools/index.ts:23-42 (registration)The registerTools function imports and calls registerGetExamHistory as part of the central tool registration.
export function registerTools(server: McpServer, db: Database.Database, userConfig: UserConfig): void { registerSubmitAnswer(server, db, userConfig); registerGetProgress(server, db, userConfig); registerGetCurriculum(server, db, userConfig); registerGetSectionDetails(server, db, userConfig); registerGetPracticeQuestion(server, db, userConfig); registerStartAssessment(server, db, userConfig); registerGetWeakAreas(server, db, userConfig); registerGetStudyPlan(server, db, userConfig); registerScaffoldProject(server, db, userConfig); registerResetProgress(server, db, userConfig); registerStartPracticeExam(server, db, userConfig); registerSubmitExamAnswer(server, db, userConfig); registerGetExamHistory(server, db, userConfig); registerFollowUp(server, db, userConfig); registerStartCapstoneBuild(server, db, userConfig); registerCapstoneBuildStep(server, db, userConfig); registerCapstoneBuildStatus(server, db, userConfig); registerDashboard(server, db, userConfig); }