@nado-language/mcp 0.1.8 → 0.1.9
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/README.md +3 -3
- package/dist/nado-language-server.mjs +3 -3
- package/dist/nado-mcp-cli.mjs +86 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@ This stdio MCP server lets AI chat clients save and practice Nado Language study
|
|
|
6
6
|
|
|
7
7
|
- `nado_whoami`: validates the configured Nado account.
|
|
8
8
|
- `nado_save_flashcard`: saves a structured flashcard generated by the user's chat AI. Nado does not call AI for this path.
|
|
9
|
-
- `nado_save_study_item`: alias of `nado_save_flashcard` for natural memorization requests such as "암기할래", "외울래", "암기장에 넣어줘", and "단어장에 추가".
|
|
9
|
+
- `nado_save_study_item`: alias of `nado_save_flashcard` for natural memorization requests such as "hi 를 단어장에 저장해줘", "암기할래", "외울래", "암기장에 넣어줘", and "단어장에 추가".
|
|
10
10
|
- `nado_update_study_item`: updates an existing saved Nado card with a revised meaning, description/explanation, examples, variants, or context. Use it for requests like "hello 설명 추가", "description 추가", "뜻 수정", and "예문 추가".
|
|
11
11
|
- `nado_analyze_and_save_flashcard`: Pro/Admin only. Nado AI generates the learner definition, usage note, examples, and variants, then saves the flashcard.
|
|
12
12
|
- `nado_list_study_items`: loads saved flashcards for the authenticated user.
|
|
@@ -16,9 +16,9 @@ The default MCP path is designed to avoid double charging for AI. ChatGPT, Claud
|
|
|
16
16
|
|
|
17
17
|
Intent routing:
|
|
18
18
|
|
|
19
|
-
- If the user provides a new English item and says "나두 암기장", "nado 암기장", "암기할래", "외울래", "암기장에 추가", "단어장에 넣어줘", "remember this", or "add this to my flashcards", use `nado_save_flashcard` or `nado_save_study_item`. Do not create local Markdown files or Google Drive documents for these Nado requests.
|
|
19
|
+
- If the user provides a new English item and says "hi 를 단어장에 저장해줘", "나두 암기장", "nado 암기장", "암기할래", "외울래", "암기장에 추가", "단어장에 넣어줘", "remember this", or "add this to my flashcards", use `nado_save_flashcard` or `nado_save_study_item`. Do not create local Markdown files or Google Drive documents for these Nado requests.
|
|
20
20
|
- If the user asks to add or change description/설명/뜻/예문 on an already saved card, use `nado_update_study_item`.
|
|
21
|
-
- If the user asks to study already saved cards with phrases like "외울래", "암기 연습", "퀴즈 내줘", "쓰기연습", "영작하기", or "복습할래", use `nado_generate_practice`.
|
|
21
|
+
- If the user asks to study already saved cards with phrases like "암기장에 있는 단어로 작문연습하자", "저장된 단어로 작문연습", "외울래", "암기 연습", "퀴즈 내줘", "쓰기연습", "영작하기", or "복습할래", use `nado_generate_practice`. For 작문연습/쓰기연습/영작하기, set `mode` to `writing`.
|
|
22
22
|
- If the user wants to inspect saved items first, use `nado_list_study_items`.
|
|
23
23
|
|
|
24
24
|
## Authentication
|
|
@@ -109,12 +109,12 @@ const tools = [
|
|
|
109
109
|
},
|
|
110
110
|
{
|
|
111
111
|
name: 'nado_save_flashcard',
|
|
112
|
-
description: 'Save a structured flashcard to the user\'s Nado Language/Nado 암기장. Use this when the user gives a new English item and asks to memorize/save it, for example "나두 암기장에 추가해줘", "nado 암기장에 넣어줘", "암기장에 추가해줘", "암기할래", "외울래", "단어장에 넣어줘", "remember this", or "add this to my flashcards". Do not create local Markdown files or Google Drive documents for these Nado requests. This does not call Nado AI; the user/chat AI supplies the content quality.',
|
|
112
|
+
description: 'Save a structured flashcard to the user\'s Nado Language/Nado 암기장. Use this when the user gives a new English item and asks to memorize/save it, for example "hi 를 단어장에 저장해줘", "나두 암기장에 추가해줘", "nado 암기장에 넣어줘", "암기장에 추가해줘", "암기할래", "외울래", "단어장에 넣어줘", "remember this", or "add this to my flashcards". Do not create local Markdown files or Google Drive documents for these Nado requests. This does not call Nado AI; the user/chat AI supplies the content quality.',
|
|
113
113
|
inputSchema: saveFlashcardInputSchema,
|
|
114
114
|
},
|
|
115
115
|
{
|
|
116
116
|
name: 'nado_save_study_item',
|
|
117
|
-
description: 'Alias of nado_save_flashcard for Nado Language study-list and memorization intents. Prefer this or nado_save_flashcard when the user asks to save a specific word/phrase/sentence to Nado for later memorization, including Korean requests like "나두 암기장", "nado language", "암기할래", "외울래", "암기장에 넣어줘", or "단어장에 추가". Do not answer by editing local files or Drive docs when the user names Nado/Nado MCP. This free path does not call Nado AI.',
|
|
117
|
+
description: 'Alias of nado_save_flashcard for Nado Language study-list and memorization intents. Prefer this or nado_save_flashcard when the user asks to save a specific word/phrase/sentence to Nado for later memorization, including Korean requests like "hi 를 단어장에 저장해줘", "나두 암기장", "nado language", "암기할래", "외울래", "암기장에 넣어줘", or "단어장에 추가". Do not answer by editing local files or Drive docs when the user names Nado/Nado MCP. This free path does not call Nado AI.',
|
|
118
118
|
inputSchema: saveFlashcardInputSchema,
|
|
119
119
|
},
|
|
120
120
|
{
|
|
@@ -156,7 +156,7 @@ const tools = [
|
|
|
156
156
|
},
|
|
157
157
|
{
|
|
158
158
|
name: 'nado_generate_practice',
|
|
159
|
-
description: 'Generate English practice content using only the authenticated user saved Nado Language study cards. Use when the user wants to study/review already saved items: "외울래", "암기 연습", "퀴즈 내줘", "쓰기연습", "영작하기", "복습할래", "practice my saved words". If the user supplies a new English item to save, use nado_save_flashcard instead.',
|
|
159
|
+
description: 'Generate English practice content using only the authenticated user saved Nado Language study cards. Use when the user wants to study/review already saved items: "암기장에 있는 단어로 작문연습하자", "저장된 단어로 작문연습", "외울래", "암기 연습", "퀴즈 내줘", "쓰기연습", "영작하기", "복습할래", "practice my saved words". For 작문연습/쓰기연습/영작하기 requests, use mode "writing". If the user supplies a new English item to save, use nado_save_flashcard instead.',
|
|
160
160
|
inputSchema: {
|
|
161
161
|
type: 'object',
|
|
162
162
|
required: ['mode'],
|
package/dist/nado-mcp-cli.mjs
CHANGED
|
@@ -253,6 +253,10 @@ async function doctor() {
|
|
|
253
253
|
console.log(`Auth refresh token: ${auth.refreshToken ? 'present' : 'missing'}`);
|
|
254
254
|
console.log(`Auth email/password fallback: ${auth.email ? `configured for ${auth.email}` : 'missing'}`);
|
|
255
255
|
console.log(`Codex Desktop config: ${registrationText(codex)}`);
|
|
256
|
+
if (codex.issues?.length) {
|
|
257
|
+
console.log('Codex Desktop config issues:');
|
|
258
|
+
for (const issue of codex.issues) console.log(` - ${issue}`);
|
|
259
|
+
}
|
|
256
260
|
console.log(`Claude Desktop config: ${registrationText(claude)}`);
|
|
257
261
|
console.log(`OpenCode config: ${registrationText(opencode)}`);
|
|
258
262
|
console.log('');
|
|
@@ -329,13 +333,93 @@ function codexRegistrationStatus() {
|
|
|
329
333
|
|
|
330
334
|
try {
|
|
331
335
|
const text = readFileSync(configPath, 'utf8');
|
|
332
|
-
const
|
|
333
|
-
|
|
336
|
+
const sectionText = readTomlSection(text, `mcp_servers.${serverName}`);
|
|
337
|
+
const registered = Boolean(sectionText);
|
|
338
|
+
const issues = registered ? codexMcpSectionIssues(sectionText) : [];
|
|
339
|
+
return { path: configPath, fileExists: true, registered, issues };
|
|
334
340
|
} catch (error) {
|
|
335
341
|
return { path: configPath, fileExists: true, registered: false, error: error instanceof Error ? error.message : String(error) };
|
|
336
342
|
}
|
|
337
343
|
}
|
|
338
344
|
|
|
345
|
+
function readTomlSection(text, sectionName) {
|
|
346
|
+
const target = String(sectionName || '').trim();
|
|
347
|
+
const lines = String(text || '').split(/\r?\n/);
|
|
348
|
+
const output = [];
|
|
349
|
+
let inSection = false;
|
|
350
|
+
|
|
351
|
+
for (const line of lines) {
|
|
352
|
+
const header = line.match(/^\s*\[([^\]]+)]\s*$/);
|
|
353
|
+
if (header) {
|
|
354
|
+
if (inSection) break;
|
|
355
|
+
inSection = header[1] === target;
|
|
356
|
+
continue;
|
|
357
|
+
}
|
|
358
|
+
if (inSection) output.push(line);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
return inSection ? output.join('\n') : '';
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
function codexMcpSectionIssues(sectionText) {
|
|
365
|
+
const issues = [];
|
|
366
|
+
const command = tomlStringValue(sectionText, 'command');
|
|
367
|
+
const cwd = tomlStringValue(sectionText, 'cwd');
|
|
368
|
+
const args = tomlArrayValue(sectionText, 'args');
|
|
369
|
+
|
|
370
|
+
if (!command) {
|
|
371
|
+
issues.push('Missing command. Codex cannot start a stdio MCP server without command.');
|
|
372
|
+
} else if (path.isAbsolute(command) && !existsSync(command)) {
|
|
373
|
+
issues.push(`Command path does not exist: ${command}`);
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
if (!Array.isArray(args) || args.length === 0) {
|
|
377
|
+
issues.push('Missing args. Expected the Nado server script path in args.');
|
|
378
|
+
} else {
|
|
379
|
+
const serverArg = args[0];
|
|
380
|
+
if (typeof serverArg === 'string' && path.isAbsolute(serverArg) && !existsSync(serverArg)) {
|
|
381
|
+
issues.push(`Server script path does not exist: ${serverArg}`);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
if (cwd) {
|
|
386
|
+
const expandedCwd = expandHome(cwd);
|
|
387
|
+
const exists = existsSync(expandedCwd);
|
|
388
|
+
const detail = exists ? `configured as ${cwd}` : `configured as ${cwd}, which does not exist on this machine`;
|
|
389
|
+
issues.push(`Remove Working directory/cwd unless required; Nado MCP does not need it (${detail}).`);
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
return issues;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
function tomlRawValue(sectionText, key) {
|
|
396
|
+
const pattern = new RegExp(`^\\s*${escapeRegExp(key)}\\s*=\\s*(.+?)\\s*$`, 'm');
|
|
397
|
+
return sectionText.match(pattern)?.[1]?.trim() || '';
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
function tomlStringValue(sectionText, key) {
|
|
401
|
+
const raw = tomlRawValue(sectionText, key);
|
|
402
|
+
if (!raw) return '';
|
|
403
|
+
try {
|
|
404
|
+
const parsed = JSON.parse(raw);
|
|
405
|
+
return typeof parsed === 'string' ? parsed : '';
|
|
406
|
+
} catch {
|
|
407
|
+
const singleQuoted = raw.match(/^'([^']*)'$/);
|
|
408
|
+
return singleQuoted ? singleQuoted[1] : '';
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
function tomlArrayValue(sectionText, key) {
|
|
413
|
+
const raw = tomlRawValue(sectionText, key);
|
|
414
|
+
if (!raw) return null;
|
|
415
|
+
try {
|
|
416
|
+
const parsed = JSON.parse(raw);
|
|
417
|
+
return Array.isArray(parsed) ? parsed : null;
|
|
418
|
+
} catch {
|
|
419
|
+
return null;
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
339
423
|
function jsonRegistrationStatus(configPath, keyPath) {
|
|
340
424
|
if (!existsSync(configPath)) return { path: configPath, fileExists: false, registered: false };
|
|
341
425
|
|