@noesis-brain/mcp-server 2.1.1 → 2.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent/runner.d.ts +51 -0
- package/dist/agent/runner.d.ts.map +1 -1
- package/dist/agent/runner.js +58 -2
- package/dist/agent/runner.js.map +1 -1
- package/dist/api/NoesisClient.d.ts +19 -0
- package/dist/api/NoesisClient.d.ts.map +1 -1
- package/dist/api/NoesisClient.js +44 -0
- package/dist/api/NoesisClient.js.map +1 -1
- package/dist/tools/exam/examNoteTemplate.d.ts +32 -0
- package/dist/tools/exam/examNoteTemplate.d.ts.map +1 -0
- package/dist/tools/exam/examNoteTemplate.js +110 -0
- package/dist/tools/exam/examNoteTemplate.js.map +1 -0
- package/dist/tools/exam/types.d.ts +89 -0
- package/dist/tools/exam/types.d.ts.map +1 -0
- package/dist/tools/exam/types.js +14 -0
- package/dist/tools/exam/types.js.map +1 -0
- package/dist/tools/exam.d.ts +12 -0
- package/dist/tools/exam.d.ts.map +1 -0
- package/dist/tools/exam.js +46 -0
- package/dist/tools/exam.js.map +1 -0
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +2 -0
- package/dist/tools/index.js.map +1 -1
- package/package.json +3 -2
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
const CHOICE_LABELS = ['A', 'B', 'C', 'D', 'E', 'F'];
|
|
2
|
+
const ROMAN = ['I', 'II', 'III', 'IV', 'V'];
|
|
3
|
+
export function buildAnchorText(questionId, blankIndex) {
|
|
4
|
+
if (typeof blankIndex === 'number' && blankIndex > 0) {
|
|
5
|
+
return `Answer ${questionId}.${blankIndex}:`;
|
|
6
|
+
}
|
|
7
|
+
return `Answer ${questionId}:`;
|
|
8
|
+
}
|
|
9
|
+
function renderQuestion(q) {
|
|
10
|
+
switch (q.type) {
|
|
11
|
+
case 'translation':
|
|
12
|
+
return renderTranslation(q);
|
|
13
|
+
case 'sentence-completion':
|
|
14
|
+
return renderSentenceCompletion(q);
|
|
15
|
+
case 'vocab-in-context':
|
|
16
|
+
return renderVocabInContext(q);
|
|
17
|
+
case 'multiple-choice':
|
|
18
|
+
return renderMultipleChoice(q);
|
|
19
|
+
case 'calculation':
|
|
20
|
+
return renderCalculation(q);
|
|
21
|
+
case 'short-answer':
|
|
22
|
+
return renderShortAnswer(q);
|
|
23
|
+
case 'fill-in-the-blank':
|
|
24
|
+
return renderFillInBlank(q);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
function renderTranslation(q) {
|
|
28
|
+
return `**${q.questionNum}.** ${q.zhPrompt}\n\n> **${buildAnchorText(q.id)}** _______________________`;
|
|
29
|
+
}
|
|
30
|
+
function renderSentenceCompletion(q) {
|
|
31
|
+
const choices = q.choices
|
|
32
|
+
.map((c, i) => `- (${CHOICE_LABELS[i]}) ${c}`)
|
|
33
|
+
.join('\n');
|
|
34
|
+
return `**${q.questionNum}.** ${q.sentenceWithBlank}\n\n${choices}\n\n> **${buildAnchorText(q.id)}** _ (A/B/C/D)`;
|
|
35
|
+
}
|
|
36
|
+
function renderVocabInContext(q) {
|
|
37
|
+
const sentence = q.sentence.replace(new RegExp(`\\b${q.underlinedWord.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\b`, 'i'), (match) => `<u>${match}</u>`);
|
|
38
|
+
const choices = q.choices
|
|
39
|
+
.map((c, i) => `- (${CHOICE_LABELS[i]}) ${c}`)
|
|
40
|
+
.join('\n');
|
|
41
|
+
return `**${q.questionNum}.** ${sentence}\n\n*The underlined word is closest in meaning to:*\n\n${choices}\n\n> **${buildAnchorText(q.id)}** _ (A/B/C/D)`;
|
|
42
|
+
}
|
|
43
|
+
function renderMultipleChoice(q) {
|
|
44
|
+
const choices = q.choices
|
|
45
|
+
.map((c, i) => `- (${CHOICE_LABELS[i]}) ${c}`)
|
|
46
|
+
.join('\n');
|
|
47
|
+
const letterHint = q.choices
|
|
48
|
+
.map((_, i) => CHOICE_LABELS[i])
|
|
49
|
+
.filter(Boolean)
|
|
50
|
+
.join('/');
|
|
51
|
+
return `**${q.questionNum}.** ${q.prompt}\n\n${choices}\n\n> **${buildAnchorText(q.id)}** _ (${letterHint})`;
|
|
52
|
+
}
|
|
53
|
+
function renderCalculation(q) {
|
|
54
|
+
const unitHint = q.unit ? ` ${q.unit}` : '';
|
|
55
|
+
return `**${q.questionNum}.** ${q.prompt}\n\n> **${buildAnchorText(q.id)}** _______________________${unitHint}`;
|
|
56
|
+
}
|
|
57
|
+
function renderShortAnswer(q) {
|
|
58
|
+
return `**${q.questionNum}.** ${q.prompt}\n\n> **${buildAnchorText(q.id)}** _______________________`;
|
|
59
|
+
}
|
|
60
|
+
function renderFillInBlank(q) {
|
|
61
|
+
const blankCount = countBlanks(q.prompt);
|
|
62
|
+
if (blankCount <= 1) {
|
|
63
|
+
return `**${q.questionNum}.** ${q.prompt}\n\n> **${buildAnchorText(q.id)}** _______________________`;
|
|
64
|
+
}
|
|
65
|
+
const answerLines = Array.from({ length: blankCount }, (_, i) => {
|
|
66
|
+
const oneBased = i + 1;
|
|
67
|
+
return `> **${buildAnchorText(q.id, oneBased)}** _______________________`;
|
|
68
|
+
}).join('\n');
|
|
69
|
+
return `**${q.questionNum}.** ${q.prompt}\n\n${answerLines}`;
|
|
70
|
+
}
|
|
71
|
+
function countBlanks(prompt) {
|
|
72
|
+
return (prompt.match(/_{3,}/g) ?? []).length;
|
|
73
|
+
}
|
|
74
|
+
function renderSection(section, sectionNum) {
|
|
75
|
+
const numeral = ROMAN[sectionNum - 1] || String(sectionNum);
|
|
76
|
+
const pointsLabel = `${section.points} point${section.points !== 1 ? 's' : ''}`;
|
|
77
|
+
const body = section.questions.map(renderQuestion).join('\n\n');
|
|
78
|
+
return `## ${numeral}. ${section.title} (${pointsLabel})\n\n*${section.instructions}*\n\n${body}`;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Build the markdown representation of an exam for storage as a note, plus
|
|
82
|
+
* the anchor list the backend needs to pre-create one text_note marker per
|
|
83
|
+
* question. Mirrors the frontend's buildExamMarkdown exactly (no
|
|
84
|
+
* referenceImageDataUri support here — that's an AI-generated-source feature
|
|
85
|
+
* not needed for hand-authored exams created via create_exam_note).
|
|
86
|
+
*/
|
|
87
|
+
export function buildExamMarkdown(examData) {
|
|
88
|
+
const header = `# ${examData.title}\n\n*${examData.date} — ${examData.totalPoints} points total*\n\n**Name:** _________________________ **Score:** _______ / ${examData.totalPoints}`;
|
|
89
|
+
const sectionsMd = examData.sections
|
|
90
|
+
.map((section, i) => renderSection(section, i + 1))
|
|
91
|
+
.join('\n\n---\n\n');
|
|
92
|
+
const markdown = `${header}\n\n---\n\n${sectionsMd}\n`;
|
|
93
|
+
const anchors = [];
|
|
94
|
+
for (const section of examData.sections) {
|
|
95
|
+
for (const q of section.questions) {
|
|
96
|
+
if (q.type === 'fill-in-the-blank') {
|
|
97
|
+
const blanks = countBlanks(q.prompt);
|
|
98
|
+
if (blanks > 1) {
|
|
99
|
+
for (let i = 1; i <= blanks; i++) {
|
|
100
|
+
anchors.push({ id: q.id, anchorText: buildAnchorText(q.id, i), blankIndex: i });
|
|
101
|
+
}
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
anchors.push({ id: q.id, anchorText: buildAnchorText(q.id) });
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return { markdown, anchors };
|
|
109
|
+
}
|
|
110
|
+
//# sourceMappingURL=examNoteTemplate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"examNoteTemplate.js","sourceRoot":"","sources":["../../../src/tools/exam/examNoteTemplate.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ExamData shape accepted by the backend's POST /api/notes/from-exam.
|
|
3
|
+
*
|
|
4
|
+
* Ported from the frontend repo's
|
|
5
|
+
* src/frontend/src/components/Chat/tools/exam/types.ts — kept as a verbatim
|
|
6
|
+
* copy (zero external imports there, so this is safe) rather than shared via
|
|
7
|
+
* a package, since the MCP server is a separate git repo/npm package with no
|
|
8
|
+
* dependency path to the frontend. Keep in sync by hand if the frontend
|
|
9
|
+
* shape changes; the backend's validateExamData
|
|
10
|
+
* (src/backend/services/examSources/examDataValidator.ts) is the real,
|
|
11
|
+
* authoritative gate either way.
|
|
12
|
+
*/
|
|
13
|
+
export interface TranslationQuestion {
|
|
14
|
+
type: 'translation';
|
|
15
|
+
id: string;
|
|
16
|
+
questionNum: number;
|
|
17
|
+
zhPrompt: string;
|
|
18
|
+
answerEn: string;
|
|
19
|
+
sourceWord?: string;
|
|
20
|
+
}
|
|
21
|
+
export interface SentenceCompletionQuestion {
|
|
22
|
+
type: 'sentence-completion';
|
|
23
|
+
id: string;
|
|
24
|
+
questionNum: number;
|
|
25
|
+
sentenceWithBlank: string;
|
|
26
|
+
choices: string[];
|
|
27
|
+
correctIndex: number;
|
|
28
|
+
sourceWord?: string;
|
|
29
|
+
}
|
|
30
|
+
export interface VocabInContextQuestion {
|
|
31
|
+
type: 'vocab-in-context';
|
|
32
|
+
id: string;
|
|
33
|
+
questionNum: number;
|
|
34
|
+
sentence: string;
|
|
35
|
+
underlinedWord: string;
|
|
36
|
+
choices: string[];
|
|
37
|
+
correctIndex: number;
|
|
38
|
+
sourceWord?: string;
|
|
39
|
+
}
|
|
40
|
+
export interface MultipleChoiceQuestion {
|
|
41
|
+
type: 'multiple-choice';
|
|
42
|
+
id: string;
|
|
43
|
+
questionNum: number;
|
|
44
|
+
prompt: string;
|
|
45
|
+
choices: string[];
|
|
46
|
+
correctIndex: number;
|
|
47
|
+
explanation?: string;
|
|
48
|
+
}
|
|
49
|
+
export interface CalculationQuestion {
|
|
50
|
+
type: 'calculation';
|
|
51
|
+
id: string;
|
|
52
|
+
questionNum: number;
|
|
53
|
+
prompt: string;
|
|
54
|
+
answer: string;
|
|
55
|
+
acceptedForms?: string[];
|
|
56
|
+
tolerance?: number;
|
|
57
|
+
unit?: string;
|
|
58
|
+
explanation?: string;
|
|
59
|
+
}
|
|
60
|
+
export interface ShortAnswerQuestion {
|
|
61
|
+
type: 'short-answer';
|
|
62
|
+
id: string;
|
|
63
|
+
questionNum: number;
|
|
64
|
+
prompt: string;
|
|
65
|
+
referenceAnswer: string;
|
|
66
|
+
rubric?: string;
|
|
67
|
+
}
|
|
68
|
+
export interface FillInBlankQuestion {
|
|
69
|
+
type: 'fill-in-the-blank';
|
|
70
|
+
id: string;
|
|
71
|
+
questionNum: number;
|
|
72
|
+
prompt: string;
|
|
73
|
+
answer: string;
|
|
74
|
+
acceptedForms?: string[];
|
|
75
|
+
}
|
|
76
|
+
export type ExamQuestion = TranslationQuestion | SentenceCompletionQuestion | VocabInContextQuestion | MultipleChoiceQuestion | CalculationQuestion | ShortAnswerQuestion | FillInBlankQuestion;
|
|
77
|
+
export interface ExamSection {
|
|
78
|
+
title: string;
|
|
79
|
+
instructions: string;
|
|
80
|
+
points: number;
|
|
81
|
+
questions: ExamQuestion[];
|
|
82
|
+
}
|
|
83
|
+
export interface ExamData {
|
|
84
|
+
title: string;
|
|
85
|
+
date: string;
|
|
86
|
+
sections: ExamSection[];
|
|
87
|
+
totalPoints: number;
|
|
88
|
+
}
|
|
89
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/tools/exam/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,aAAa,CAAC;IACpB,EAAE,EAAE,MAAM,CAAC;IACX,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,0BAA0B;IACzC,IAAI,EAAE,qBAAqB,CAAC;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,WAAW,EAAE,MAAM,CAAC;IACpB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,sBAAsB;IACrC,IAAI,EAAE,kBAAkB,CAAC;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,cAAc,EAAE,MAAM,CAAC;IACvB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,sBAAsB;IACrC,IAAI,EAAE,iBAAiB,CAAC;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,aAAa,CAAC;IACpB,EAAE,EAAE,MAAM,CAAC;IACX,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;IACzB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,cAAc,CAAC;IACrB,EAAE,EAAE,MAAM,CAAC;IACX,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,eAAe,EAAE,MAAM,CAAC;IACxB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,mBAAmB,CAAC;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;CAC1B;AAED,MAAM,MAAM,YAAY,GACpB,mBAAmB,GACnB,0BAA0B,GAC1B,sBAAsB,GACtB,sBAAsB,GACtB,mBAAmB,GACnB,mBAAmB,GACnB,mBAAmB,CAAC;AAExB,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,YAAY,EAAE,CAAC;CAC3B;AAED,MAAM,WAAW,QAAQ;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,WAAW,EAAE,CAAC;IACxB,WAAW,EAAE,MAAM,CAAC;CACrB"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ExamData shape accepted by the backend's POST /api/notes/from-exam.
|
|
3
|
+
*
|
|
4
|
+
* Ported from the frontend repo's
|
|
5
|
+
* src/frontend/src/components/Chat/tools/exam/types.ts — kept as a verbatim
|
|
6
|
+
* copy (zero external imports there, so this is safe) rather than shared via
|
|
7
|
+
* a package, since the MCP server is a separate git repo/npm package with no
|
|
8
|
+
* dependency path to the frontend. Keep in sync by hand if the frontend
|
|
9
|
+
* shape changes; the backend's validateExamData
|
|
10
|
+
* (src/backend/services/examSources/examDataValidator.ts) is the real,
|
|
11
|
+
* authoritative gate either way.
|
|
12
|
+
*/
|
|
13
|
+
export {};
|
|
14
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/tools/exam/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Exam creation tool for the md-manager MCP server.
|
|
3
|
+
*
|
|
4
|
+
* Creates a real, DB-backed exam note (fillable answer markers, hidden answer
|
|
5
|
+
* key, gradeable via the app's own grading pipeline) from a hand-authored
|
|
6
|
+
* ExamData object — the same shape the app's AI exam generator produces, so
|
|
7
|
+
* the resulting note is indistinguishable from one created in-app.
|
|
8
|
+
*/
|
|
9
|
+
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
10
|
+
import type { NoesisClient } from '../api/NoesisClient.js';
|
|
11
|
+
export declare function registerExamTools(server: McpServer, client: NoesisClient): void;
|
|
12
|
+
//# sourceMappingURL=exam.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"exam.d.ts","sourceRoot":"","sources":["../../src/tools/exam.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACzE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAuB3D,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,YAAY,GAAG,IAAI,CAuB/E"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Exam creation tool for the md-manager MCP server.
|
|
3
|
+
*
|
|
4
|
+
* Creates a real, DB-backed exam note (fillable answer markers, hidden answer
|
|
5
|
+
* key, gradeable via the app's own grading pipeline) from a hand-authored
|
|
6
|
+
* ExamData object — the same shape the app's AI exam generator produces, so
|
|
7
|
+
* the resulting note is indistinguishable from one created in-app.
|
|
8
|
+
*/
|
|
9
|
+
import { z } from 'zod';
|
|
10
|
+
// Top-level shape is strongly typed; sections[].questions[] is intentionally
|
|
11
|
+
// loose (z.record(z.any())) rather than re-encoding the 7-question-type
|
|
12
|
+
// discriminated union a third time (it already lives in the frontend's
|
|
13
|
+
// types.ts and the backend's examDataValidator.ts) — the backend's
|
|
14
|
+
// validateExamData is the real gate and feedback loop (a malformed shape
|
|
15
|
+
// comes back as a descriptive error with a retry hint, not a silent failure).
|
|
16
|
+
const examSectionShape = z.object({
|
|
17
|
+
title: z.string().describe('Section heading, e.g. "Technical Architecture"'),
|
|
18
|
+
instructions: z.string().describe('One-line instructions shown under the section heading'),
|
|
19
|
+
points: z.number().describe('Total points for this section (individual questions do not carry their own point value)'),
|
|
20
|
+
questions: z.array(z.record(z.any())).min(1).describe('Array of question objects. Each needs at minimum {type, id, questionNum}. ' +
|
|
21
|
+
'Supported types: translation {zhPrompt, answerEn}, sentence-completion {sentenceWithBlank, choices[], correctIndex}, ' +
|
|
22
|
+
'vocab-in-context {sentence, underlinedWord, choices[], correctIndex}, multiple-choice {prompt, choices[], correctIndex}, ' +
|
|
23
|
+
'calculation {prompt, answer, acceptedForms?, tolerance?, unit?}, short-answer {prompt, referenceAnswer, rubric?}, ' +
|
|
24
|
+
'fill-in-the-blank {prompt (with "____" placeholders), answer, acceptedForms?}. ' +
|
|
25
|
+
'Question ids must be unique across the whole exam.'),
|
|
26
|
+
});
|
|
27
|
+
export function registerExamTools(server, client) {
|
|
28
|
+
// ─── create_exam_note ────────────────────────────────────────
|
|
29
|
+
server.tool('create_exam_note', 'Create a real, DB-backed exam note — fillable answer-marker boxes at each question, a hidden answer key, and gradeable via the app\'s own grading pipeline (POST /:id/exam/grade). This is the actual "exam" note type the app recognizes (NoteTypeBadge, grading UI) — not just markdown that looks like one. On a malformed examData, returns a descriptive error including a retry hint; fix the shape and call again.', {
|
|
30
|
+
title: z.string().describe('Exam title, shown as the note\'s H1 heading'),
|
|
31
|
+
date: z.string().describe('Date string shown under the title, e.g. "2026-07-31"'),
|
|
32
|
+
totalPoints: z.number().describe('Must equal the sum of all sections\' points'),
|
|
33
|
+
sections: z.array(examSectionShape).min(1).describe('At least one section, each with at least one question'),
|
|
34
|
+
}, async (args) => {
|
|
35
|
+
const examData = args;
|
|
36
|
+
const result = await client.createExamFromData(examData);
|
|
37
|
+
const questionCount = Object.keys(result.questionMarks).length;
|
|
38
|
+
return {
|
|
39
|
+
content: [{
|
|
40
|
+
type: 'text',
|
|
41
|
+
text: `Created exam note #${result.noteId}: "${examData.title}" (${examData.totalPoints} points, ${questionCount} question${questionCount === 1 ? '' : 's'} across ${examData.sections.length} section${examData.sections.length === 1 ? '' : 's'}). Fillable answer markers and the hidden answer key are already in place — open the note in the app to answer, then grade via the app's exam UI (or POST /api/notes/${result.noteId}/exam/grade).`
|
|
42
|
+
}]
|
|
43
|
+
};
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=exam.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"exam.js","sourceRoot":"","sources":["../../src/tools/exam.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAKxB,6EAA6E;AAC7E,wEAAwE;AACxE,uEAAuE;AACvE,mEAAmE;AACnE,yEAAyE;AACzE,8EAA8E;AAC9E,MAAM,gBAAgB,GAAG,CAAC,CAAC,MAAM,CAAC;IAChC,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,gDAAgD,CAAC;IAC5E,YAAY,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,uDAAuD,CAAC;IAC1F,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,yFAAyF,CAAC;IACtH,SAAS,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CACnD,4EAA4E;QAC5E,uHAAuH;QACvH,2HAA2H;QAC3H,oHAAoH;QACpH,iFAAiF;QACjF,oDAAoD,CACrD;CACF,CAAC,CAAC;AAEH,MAAM,UAAU,iBAAiB,CAAC,MAAiB,EAAE,MAAoB;IACvE,gEAAgE;IAChE,MAAM,CAAC,IAAI,CACT,kBAAkB,EAClB,2ZAA2Z,EAC3Z;QACE,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,6CAA6C,CAAC;QACzE,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,sDAAsD,CAAC;QACjF,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,6CAA6C,CAAC;QAC/E,QAAQ,EAAE,CAAC,CAAC,KAAK,CAAC,gBAAgB,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,uDAAuD,CAAC;KAC7G,EACD,KAAK,EAAE,IAAI,EAAE,EAAE;QACb,MAAM,QAAQ,GAAG,IAAgB,CAAC;QAClC,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,kBAAkB,CAAC,QAAQ,CAAC,CAAC;QACzD,MAAM,aAAa,GAAG,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC,MAAM,CAAC;QAC/D,OAAO;YACL,OAAO,EAAE,CAAC;oBACR,IAAI,EAAE,MAAM;oBACZ,IAAI,EAAE,sBAAsB,MAAM,CAAC,MAAM,MAAM,QAAQ,CAAC,KAAK,MAAM,QAAQ,CAAC,WAAW,YAAY,aAAa,YAAY,aAAa,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,WAAW,QAAQ,CAAC,QAAQ,CAAC,MAAM,WAAW,QAAQ,CAAC,QAAQ,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,wKAAwK,MAAM,CAAC,MAAM,eAAe;iBACtb,CAAC;SACH,CAAC;IACJ,CAAC,CACF,CAAC;AACJ,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/tools/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAOH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACzE,OAAO,EAAE,YAAY,EAAyC,KAAK,IAAI,EAA2D,MAAM,wBAAwB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/tools/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAOH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACzE,OAAO,EAAE,YAAY,EAAyC,KAAK,IAAI,EAA2D,MAAM,wBAAwB,CAAC;AAsIjK;;;;;;;;;;;;GAYG;AACH,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,KAAK,CAAC;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,GAC1C;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAejD;AAqBD,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,YAAY,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,SAAS,EAAE,QAAQ,EAAE,YAAY,GAAG,IAAI,CA64F7E;AA0DD;;;;GAIG;AACH,KAAK,YAAY,GAAG;IAClB,EAAE,EAAE,MAAM,CAAC;IACX,aAAa,EAAE,MAAM,CAAC;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,QAAQ,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,IAAI,CAAC;IACnC,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;CACjC,CAAC;AAEF,sFAAsF;AACtF,wBAAgB,cAAc,CAAC,CAAC,EAAE,IAAI,EAAE,YAAY,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,YAAY,CAYxF;AAED;;;;GAIG;AACH,wBAAsB,iBAAiB,CACrC,SAAS,EAAE,MAAM,EAAE,EACnB,MAAM,EAAE,OAAO,EACf,MAAM,EAAE,YAAY,EACpB,KAAK,GAAE,OAAe,EACtB,kBAAkB,GAAE,OAAe,GAClC,OAAO,CAAC;IAAE,OAAO,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAAC,OAAO,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC,CAigBhF;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAI9F;AA+DD;;;GAGG;AACH,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,MAAM,EACf,OAAO,EAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAA;CAAE,GACrE,MAAM,CA2FR;AAED;;GAEG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,MAAM,GAAG;IACvD,WAAW,EAAE,MAAM,CAAC;IACpB,EAAE,EAAE,MAAM,GAAG,IAAI,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;CACd,CAqBA;AA4SD;;;GAGG;AACH,wBAAgB,YAAY,CAAC,YAAY,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,MAAM,CAY/E"}
|
package/dist/tools/index.js
CHANGED
|
@@ -13,6 +13,7 @@ import { initEmbeddingService, generateEmbedding, generateEmbeddingsBatch } from
|
|
|
13
13
|
import { SyncStateManager, determineSyncDirection } from './SyncStateManager.js';
|
|
14
14
|
import { registerNaviTools } from './navis.js';
|
|
15
15
|
import { registerBacklogTools } from './backlog.js';
|
|
16
|
+
import { registerExamTools } from './exam.js';
|
|
16
17
|
import { suggestOtherOsPath } from '../utils/suggestPath.js';
|
|
17
18
|
import { diff3Merge, diffPatch } from 'node-diff3';
|
|
18
19
|
// ============================================
|
|
@@ -185,6 +186,7 @@ export function registerTools(server, services) {
|
|
|
185
186
|
// Register Navi management tools
|
|
186
187
|
registerNaviTools(server, client);
|
|
187
188
|
registerBacklogTools(server, client);
|
|
189
|
+
registerExamTools(server, client);
|
|
188
190
|
// Register search_notes tool
|
|
189
191
|
server.tool('search_notes', 'Search your knowledge base using full-text search. Returns relevant notes ranked by BM25 relevance.', {
|
|
190
192
|
query: z.string().describe('Search query (natural language or keywords)'),
|