neurain 0.1.0-alpha.0

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.
Files changed (89) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/LICENSE +57 -0
  3. package/README.md +205 -0
  4. package/SECURITY.md +22 -0
  5. package/bin/neurain.mjs +7 -0
  6. package/docs/comparison-mem0.en.md +22 -0
  7. package/docs/connect-claude.en.md +48 -0
  8. package/docs/connect-claude.kr.md +51 -0
  9. package/docs/connect-codex.en.md +38 -0
  10. package/docs/connect-codex.kr.md +40 -0
  11. package/docs/connect-gemini.en.md +71 -0
  12. package/docs/connect-gemini.kr.md +71 -0
  13. package/docs/connect-runtime.en.md +61 -0
  14. package/docs/connect-runtime.kr.md +61 -0
  15. package/docs/development-status.en.md +157 -0
  16. package/docs/development-status.kr.md +157 -0
  17. package/docs/knowledge-os.en.md +105 -0
  18. package/docs/knowledge-os.kr.md +106 -0
  19. package/docs/pricing.en.md +14 -0
  20. package/docs/privacy-and-data-flow.en.md +25 -0
  21. package/docs/public-saas-readiness.en.md +39 -0
  22. package/docs/quickstart.en.md +64 -0
  23. package/docs/quickstart.kr.md +64 -0
  24. package/docs/release-checklist.en.md +38 -0
  25. package/docs/safety.en.md +36 -0
  26. package/docs/self-improvement-90-roadmap.en.md +429 -0
  27. package/docs/self-improvement-90-roadmap.kr.md +429 -0
  28. package/docs/self-improving-workflows.en.md +163 -0
  29. package/docs/self-improving-workflows.kr.md +163 -0
  30. package/docs/support.en.md +17 -0
  31. package/docs/troubleshooting.en.md +35 -0
  32. package/package.json +36 -0
  33. package/src/cli.mjs +261 -0
  34. package/src/core/adopt.mjs +304 -0
  35. package/src/core/answer_eval.mjs +450 -0
  36. package/src/core/capabilities.mjs +217 -0
  37. package/src/core/capture_durable.mjs +181 -0
  38. package/src/core/classify.mjs +237 -0
  39. package/src/core/compile_desk.mjs +324 -0
  40. package/src/core/complete.mjs +108 -0
  41. package/src/core/config.mjs +142 -0
  42. package/src/core/connect.mjs +355 -0
  43. package/src/core/curator.mjs +351 -0
  44. package/src/core/daemon.mjs +536 -0
  45. package/src/core/digest.mjs +155 -0
  46. package/src/core/doctor.mjs +115 -0
  47. package/src/core/durable.mjs +96 -0
  48. package/src/core/envelope.mjs +97 -0
  49. package/src/core/flush.mjs +190 -0
  50. package/src/core/fs.mjs +121 -0
  51. package/src/core/init.mjs +194 -0
  52. package/src/core/journal.mjs +269 -0
  53. package/src/core/labels.mjs +117 -0
  54. package/src/core/lessons.mjs +793 -0
  55. package/src/core/lifecycle.mjs +1138 -0
  56. package/src/core/link_check.mjs +180 -0
  57. package/src/core/live_cases.mjs +221 -0
  58. package/src/core/onboard.mjs +175 -0
  59. package/src/core/plan_receipt.mjs +177 -0
  60. package/src/core/plan_writeback.mjs +176 -0
  61. package/src/core/queue.mjs +62 -0
  62. package/src/core/queue_archive.mjs +87 -0
  63. package/src/core/queue_model.mjs +161 -0
  64. package/src/core/queue_write.mjs +28 -0
  65. package/src/core/recall.mjs +1802 -0
  66. package/src/core/recall_bench.mjs +275 -0
  67. package/src/core/recall_corpus.mjs +152 -0
  68. package/src/core/recall_facts.mjs +233 -0
  69. package/src/core/recall_intel.mjs +233 -0
  70. package/src/core/recall_lexical.mjs +269 -0
  71. package/src/core/recap.mjs +78 -0
  72. package/src/core/review_queue.mjs +131 -0
  73. package/src/core/review_worker.mjs +284 -0
  74. package/src/core/route.mjs +73 -0
  75. package/src/core/safety.mjs +57 -0
  76. package/src/core/scheduler.mjs +697 -0
  77. package/src/core/search.mjs +54 -0
  78. package/src/core/secret_scan.mjs +143 -0
  79. package/src/core/semantic.mjs +187 -0
  80. package/src/core/source_digest.mjs +56 -0
  81. package/src/core/source_digest_gen.mjs +311 -0
  82. package/src/core/stage.mjs +105 -0
  83. package/src/core/status.mjs +175 -0
  84. package/src/core/vault_state.mjs +115 -0
  85. package/src/core/watch.mjs +282 -0
  86. package/src/core/wiki_log.mjs +29 -0
  87. package/src/core/wrap.mjs +62 -0
  88. package/src/mcp/server.mjs +865 -0
  89. package/templates/starter-vault/README.md +9 -0
@@ -0,0 +1,865 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
4
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
5
+ import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';
6
+ import { scanAdoption } from '../core/adopt.mjs';
7
+ import { evaluateAnswerQuality } from '../core/answer_eval.mjs';
8
+ import { listCapabilities } from '../core/capabilities.mjs';
9
+ import { curatorStatus, runCurator } from '../core/curator.mjs';
10
+ import { doctorCommand } from '../core/doctor.mjs';
11
+ import { absPath, compactStamp, ensureDir, safeResolve, timestamp } from '../core/fs.mjs';
12
+ import { listJournalEvents, verifyJournal } from '../core/journal.mjs';
13
+ import { evaluateLessonCandidateDetection, lessonCandidates, listLessons } from '../core/lessons.mjs';
14
+ import { buildLifecycleReport, evaluateLifecycleAutomation } from '../core/lifecycle.mjs';
15
+ import { buildLiveCaseScaffold } from '../core/live_cases.mjs';
16
+ import { buildRecap } from '../core/recap.mjs';
17
+ import { evaluateCrossHostRecall, evaluateSemanticRecall, hybridSearchRecall, rebuildRecall, recallStatus, searchRecall, semanticSearchRecall, verifyRecall } from '../core/recall.mjs';
18
+ import { buildReviewWorkerReport } from '../core/review_worker.mjs';
19
+ import { statusCommand } from '../core/status.mjs';
20
+ import { digestCommand } from '../core/digest.mjs';
21
+ import { queueCommand } from '../core/queue.mjs';
22
+ import { reviewQueueCommand } from '../core/review_queue.mjs';
23
+ import { routeCommand } from '../core/route.mjs';
24
+ import { planWritebackCommand } from '../core/plan_writeback.mjs';
25
+ import { flushCommand, sessionFlushCommand } from '../core/flush.mjs';
26
+ import { compileCommand } from '../core/compile_desk.mjs';
27
+ import { secretLike } from '../core/safety.mjs';
28
+ import { buildSchedulerTick, evaluateSchedulerTriggering } from '../core/scheduler.mjs';
29
+ import { searchRoot } from '../core/search.mjs';
30
+ import { buildWatchReport } from '../core/watch.mjs';
31
+ import { wrapCommand } from '../core/wrap.mjs';
32
+
33
+ export async function startMcpServer(args) {
34
+ const root = absPath(args.root || process.env.NEURAIN_ROOT || process.cwd());
35
+ const pkg = await import('../../package.json', { with: { type: 'json' } });
36
+ const server = new Server(
37
+ { name: 'neurain', version: pkg.default.version },
38
+ { capabilities: { tools: {} } }
39
+ );
40
+
41
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({
42
+ tools: [
43
+ {
44
+ name: 'neurain_status',
45
+ description: 'Read-only Neurain vault status and health summary.',
46
+ inputSchema: {
47
+ type: 'object',
48
+ properties: {
49
+ root: { type: 'string', description: 'Optional Neurain root folder. Defaults to server root.' },
50
+ },
51
+ },
52
+ },
53
+ {
54
+ name: 'neurain_search',
55
+ description: 'Read-only source-grounded local markdown search.',
56
+ inputSchema: {
57
+ type: 'object',
58
+ properties: {
59
+ query: { type: 'string' },
60
+ root: { type: 'string' },
61
+ top: { type: 'number' },
62
+ },
63
+ required: ['query'],
64
+ },
65
+ },
66
+ {
67
+ name: 'neurain_capture',
68
+ description: 'Append-only raw capture. Secret-like input is refused.',
69
+ inputSchema: {
70
+ type: 'object',
71
+ properties: {
72
+ text: { type: 'string' },
73
+ source: { type: 'string' },
74
+ root: { type: 'string' },
75
+ },
76
+ required: ['text'],
77
+ },
78
+ },
79
+ {
80
+ name: 'neurain_adopt_scan',
81
+ description: 'Read-only existing-folder adoption scan with risk recommendation.',
82
+ inputSchema: {
83
+ type: 'object',
84
+ properties: {
85
+ folder: { type: 'string' },
86
+ },
87
+ required: ['folder'],
88
+ },
89
+ },
90
+ {
91
+ name: 'neurain_journal_list',
92
+ description: 'Read-only event journal list. MCP cannot append journal events.',
93
+ inputSchema: {
94
+ type: 'object',
95
+ properties: {
96
+ root: { type: 'string' },
97
+ type: { type: 'string' },
98
+ top: { type: 'number' },
99
+ },
100
+ },
101
+ },
102
+ {
103
+ name: 'neurain_journal_verify',
104
+ description: 'Read-only event journal integrity check.',
105
+ inputSchema: {
106
+ type: 'object',
107
+ properties: {
108
+ root: { type: 'string' },
109
+ },
110
+ },
111
+ },
112
+ {
113
+ name: 'neurain_lifecycle_eval',
114
+ description: 'Read-only native lifecycle automation eval. Proves host lifecycle hook payloads map safely without storing prompt bodies, transcript paths, tool output, secrets, or private payloads. MCP cannot emit lifecycle events.',
115
+ inputSchema: {
116
+ type: 'object',
117
+ properties: {
118
+ root: { type: 'string' },
119
+ fixtureSize: { type: 'number' },
120
+ minCases: { type: 'number' },
121
+ caseFile: { type: 'string' },
122
+ },
123
+ },
124
+ },
125
+ {
126
+ name: 'neurain_lifecycle_report',
127
+ description: 'Read-only host lifecycle and session lineage report. MCP cannot emit lifecycle events.',
128
+ inputSchema: {
129
+ type: 'object',
130
+ properties: {
131
+ root: { type: 'string' },
132
+ host: { type: 'string' },
133
+ sessionId: { type: 'string' },
134
+ top: { type: 'number' },
135
+ },
136
+ },
137
+ },
138
+ {
139
+ name: 'neurain_lessons_list',
140
+ description: 'Read-only cover-level lesson list. Alpha never returns lesson bodies.',
141
+ inputSchema: {
142
+ type: 'object',
143
+ properties: {
144
+ root: { type: 'string' },
145
+ area: { type: 'string' },
146
+ includeBodies: { type: 'boolean' },
147
+ },
148
+ },
149
+ },
150
+ {
151
+ name: 'neurain_lessons_candidates',
152
+ description: 'Read-only lesson candidate preview. Does not promote or write receipts.',
153
+ inputSchema: {
154
+ type: 'object',
155
+ properties: {
156
+ root: { type: 'string' },
157
+ area: { type: 'string' },
158
+ top: { type: 'number' },
159
+ },
160
+ },
161
+ },
162
+ {
163
+ name: 'neurain_lessons_eval',
164
+ description: 'Read-only lesson candidate detection evaluation for precision, recall, false positives, and unsafe blocking.',
165
+ inputSchema: {
166
+ type: 'object',
167
+ properties: {
168
+ root: { type: 'string' },
169
+ fixtureSize: { type: 'number' },
170
+ minCases: { type: 'number' },
171
+ caseFile: { type: 'string' },
172
+ },
173
+ },
174
+ },
175
+ {
176
+ name: 'neurain_capabilities',
177
+ description: 'Read-only capability router with reason traces.',
178
+ inputSchema: {
179
+ type: 'object',
180
+ properties: {
181
+ query: { type: 'string' },
182
+ top: { type: 'number' },
183
+ },
184
+ },
185
+ },
186
+ {
187
+ name: 'neurain_recap',
188
+ description: 'Read-only deterministic session recap from local logs and area briefs.',
189
+ inputSchema: {
190
+ type: 'object',
191
+ properties: {
192
+ root: { type: 'string' },
193
+ area: { type: 'string' },
194
+ },
195
+ },
196
+ },
197
+ {
198
+ name: 'neurain_watch_report',
199
+ description: 'Read-only local signal watch report. Does not start a daemon or write events.',
200
+ inputSchema: {
201
+ type: 'object',
202
+ properties: {
203
+ root: { type: 'string' },
204
+ area: { type: 'string' },
205
+ top: { type: 'number' },
206
+ sinceMinutes: { type: 'number' },
207
+ },
208
+ },
209
+ },
210
+ {
211
+ name: 'neurain_review_worker',
212
+ description: 'Read-only review worker report from watch, journal, recap, and lesson signals. Does not call models or write.',
213
+ inputSchema: {
214
+ type: 'object',
215
+ properties: {
216
+ root: { type: 'string' },
217
+ area: { type: 'string' },
218
+ top: { type: 'number' },
219
+ sinceMinutes: { type: 'number' },
220
+ },
221
+ },
222
+ },
223
+ {
224
+ name: 'neurain_scheduler_tick',
225
+ description: 'Read-only one-shot scheduler tick. It decides whether review should run but does not install daemons or write.',
226
+ inputSchema: {
227
+ type: 'object',
228
+ properties: {
229
+ root: { type: 'string' },
230
+ area: { type: 'string' },
231
+ top: { type: 'number' },
232
+ sinceMinutes: { type: 'number' },
233
+ minTriggers: { type: 'number' },
234
+ minEvents: { type: 'number' },
235
+ },
236
+ },
237
+ },
238
+ {
239
+ name: 'neurain_scheduler_eval',
240
+ description: 'Read-only scheduler trigger evaluation for background review precision, recall, no-recursion, and private boundaries.',
241
+ inputSchema: {
242
+ type: 'object',
243
+ properties: {
244
+ root: { type: 'string' },
245
+ fixtureSize: { type: 'number' },
246
+ minCases: { type: 'number' },
247
+ caseFile: { type: 'string' },
248
+ },
249
+ },
250
+ },
251
+ {
252
+ name: 'neurain_curator_status',
253
+ description: 'Read-only lesson curator lifecycle status. Does not mutate lessons.',
254
+ inputSchema: {
255
+ type: 'object',
256
+ properties: {
257
+ root: { type: 'string' },
258
+ staleDays: { type: 'number' },
259
+ archiveDays: { type: 'number' },
260
+ },
261
+ },
262
+ },
263
+ {
264
+ name: 'neurain_curator_run_preview',
265
+ description: 'Read-only curator dry-run preview. Does not write snapshots, receipts, or lesson changes.',
266
+ inputSchema: {
267
+ type: 'object',
268
+ properties: {
269
+ root: { type: 'string' },
270
+ staleDays: { type: 'number' },
271
+ archiveDays: { type: 'number' },
272
+ },
273
+ },
274
+ },
275
+ {
276
+ name: 'neurain_recall_status',
277
+ description: 'Read-only recall index status. Markdown remains canonical.',
278
+ inputSchema: {
279
+ type: 'object',
280
+ properties: {
281
+ root: { type: 'string' },
282
+ },
283
+ },
284
+ },
285
+ {
286
+ name: 'neurain_recall_rebuild_preview',
287
+ description: 'Read-only recall rebuild dry-run. Does not write SQLite DB or receipts.',
288
+ inputSchema: {
289
+ type: 'object',
290
+ properties: {
291
+ root: { type: 'string' },
292
+ },
293
+ },
294
+ },
295
+ {
296
+ name: 'neurain_recall_search',
297
+ description: 'Read-only source-grounded recall search using SQLite FTS5 when available, otherwise markdown fallback.',
298
+ inputSchema: {
299
+ type: 'object',
300
+ properties: {
301
+ root: { type: 'string' },
302
+ query: { type: 'string' },
303
+ top: { type: 'number' },
304
+ host: { type: 'string' },
305
+ },
306
+ required: ['query'],
307
+ },
308
+ },
309
+ {
310
+ name: 'neurain_recall_verify',
311
+ description: 'Read-only recall index manifest verification against current markdown, events, and receipts.',
312
+ inputSchema: {
313
+ type: 'object',
314
+ properties: {
315
+ root: { type: 'string' },
316
+ },
317
+ },
318
+ },
319
+ {
320
+ name: 'neurain_recall_cross_host_eval',
321
+ description: 'Read-only cross-host recall evaluation using safe host-tagged journal events, fixtures, or reviewed case files.',
322
+ inputSchema: {
323
+ type: 'object',
324
+ properties: {
325
+ root: { type: 'string' },
326
+ top: { type: 'number' },
327
+ minCases: { type: 'number' },
328
+ fixtureSize: { type: 'number' },
329
+ privateProbes: { type: 'number' },
330
+ caseFile: { type: 'string' },
331
+ },
332
+ },
333
+ },
334
+ {
335
+ name: 'neurain_recall_semantic_search',
336
+ description: 'Read-only local lexical-semantic recall search (stemming, synonyms, fuzzy). Default provider is deterministic and needs no model call; markdown stays canonical.',
337
+ inputSchema: {
338
+ type: 'object',
339
+ properties: {
340
+ root: { type: 'string' },
341
+ query: { type: 'string' },
342
+ top: { type: 'number' },
343
+ host: { type: 'string' },
344
+ provider: { type: 'string' },
345
+ minScore: { type: 'number' },
346
+ },
347
+ required: ['query'],
348
+ },
349
+ },
350
+ {
351
+ name: 'neurain_recall_hybrid_search',
352
+ description: 'Read-only hybrid recall search: exact-token union local lexical-semantic. Never worse than exact-token, plus paraphrase catches. top is per-branch candidate depth, so returned union can exceed top. Deterministic default provider, no model call.',
353
+ inputSchema: {
354
+ type: 'object',
355
+ properties: {
356
+ root: { type: 'string' },
357
+ query: { type: 'string' },
358
+ top: { type: 'number' },
359
+ host: { type: 'string' },
360
+ provider: { type: 'string' },
361
+ minScore: { type: 'number' },
362
+ },
363
+ required: ['query'],
364
+ },
365
+ },
366
+ {
367
+ name: 'neurain_recall_semantic_eval',
368
+ description: 'Read-only semantic recall quality evaluation. Proves the local lexical-semantic layer recalls paraphrased queries that exact-token misses, with host isolation, private exclusion, no-answer abstention, rebuild equivalence, and target-root non-write.',
369
+ inputSchema: {
370
+ type: 'object',
371
+ properties: {
372
+ root: { type: 'string' },
373
+ top: { type: 'number' },
374
+ minCases: { type: 'number' },
375
+ fixtureSize: { type: 'number' },
376
+ caseFile: { type: 'string' },
377
+ provider: { type: 'string' },
378
+ },
379
+ },
380
+ },
381
+ {
382
+ name: 'neurain_answer_quality_eval',
383
+ description: 'Read-only answer-quality evaluation for faithfulness, citations, conflicts, abstention, private boundaries, and stale-source handling.',
384
+ inputSchema: {
385
+ type: 'object',
386
+ properties: {
387
+ root: { type: 'string' },
388
+ fixtureSize: { type: 'number' },
389
+ minCases: { type: 'number' },
390
+ },
391
+ },
392
+ },
393
+ {
394
+ name: 'neurain_live_cases_scaffold',
395
+ description: 'Read-only redacted E23 live-case scaffold. Returns hash-only source refs, stores no raw source text, and does not claim human-reviewed evidence.',
396
+ inputSchema: {
397
+ type: 'object',
398
+ properties: {
399
+ root: { type: 'string' },
400
+ sampleSize: { type: 'number' },
401
+ label: { type: 'string' },
402
+ },
403
+ },
404
+ },
405
+ {
406
+ name: 'neurain_wrap_preview',
407
+ description: 'Read-only wrap dry-run preview. No lesson promotion or durable wiki write.',
408
+ inputSchema: {
409
+ type: 'object',
410
+ properties: {
411
+ root: { type: 'string' },
412
+ area: { type: 'string' },
413
+ top: { type: 'number' },
414
+ },
415
+ },
416
+ },
417
+ {
418
+ name: 'neurain_session_status',
419
+ description: 'Read-only session status (boot summary, a single session, or all sessions). Never advances the digest pointer or runs maintenance.',
420
+ inputSchema: {
421
+ type: 'object',
422
+ properties: {
423
+ root: { type: 'string' },
424
+ sessionId: { type: 'string', description: 'Optional session id for per-session detail.' },
425
+ all: { type: 'boolean' },
426
+ staleDays: { type: 'number' },
427
+ },
428
+ },
429
+ },
430
+ {
431
+ name: 'neurain_digest_preview',
432
+ description: "Read-only 'since your last visit' digest for a session. Computes only; never advances the ack pointer.",
433
+ inputSchema: {
434
+ type: 'object',
435
+ properties: {
436
+ root: { type: 'string' },
437
+ session: { type: 'string' },
438
+ windowHours: { type: 'number' },
439
+ },
440
+ required: ['session'],
441
+ },
442
+ },
443
+ {
444
+ name: 'neurain_queue_view',
445
+ description: 'Read-only unified writeback queue view (list, stats, or lossless-check).',
446
+ inputSchema: {
447
+ type: 'object',
448
+ properties: {
449
+ root: { type: 'string' },
450
+ queue: { type: 'string' },
451
+ status: { type: 'string' },
452
+ pending: { type: 'boolean' },
453
+ stats: { type: 'boolean' },
454
+ losslessCheck: { type: 'boolean' },
455
+ },
456
+ },
457
+ },
458
+ {
459
+ name: 'neurain_review_queue',
460
+ description: 'Read-only review desk: safe vs needs-confirmation grouping, conflicts, and deep-compile candidates. Private items redact their paths.',
461
+ inputSchema: {
462
+ type: 'object',
463
+ properties: {
464
+ root: { type: 'string' },
465
+ sessionId: { type: 'string' },
466
+ top: { type: 'number' },
467
+ },
468
+ },
469
+ },
470
+ {
471
+ name: 'neurain_route',
472
+ description: 'Read-only source classification: area / domain / entity candidates, sensitivity, write intent, and a requires-user-decision flag. Writes nothing.',
473
+ inputSchema: {
474
+ type: 'object',
475
+ properties: {
476
+ root: { type: 'string' },
477
+ text: { type: 'string', description: 'Source text to classify.' },
478
+ sessionId: { type: 'string' },
479
+ area: { type: 'string' },
480
+ sensitivity: { type: 'string' },
481
+ intent: { type: 'string' },
482
+ title: { type: 'string' },
483
+ type: { type: 'string' },
484
+ },
485
+ required: ['text'],
486
+ },
487
+ },
488
+ {
489
+ name: 'neurain_plan_writeback',
490
+ description: 'Read-only writeback plan: target layers, confirmation gates, Light/Standard/Full scope, and queue metadata for a source (by --source-id or text). Writes nothing.',
491
+ inputSchema: {
492
+ type: 'object',
493
+ properties: {
494
+ root: { type: 'string' },
495
+ sourceId: { type: 'string', description: 'Existing raw-inbox envelope id to plan for.' },
496
+ text: { type: 'string', description: 'Source text to plan for when no sourceId is given.' },
497
+ sessionId: { type: 'string' },
498
+ area: { type: 'string' },
499
+ sensitivity: { type: 'string' },
500
+ intent: { type: 'string' },
501
+ title: { type: 'string' },
502
+ type: { type: 'string' },
503
+ },
504
+ },
505
+ },
506
+ {
507
+ name: 'neurain_flush',
508
+ description: 'Read-only flush plan: pending writeback-queue items grouped by Light/Standard/Full scope. Writes nothing.',
509
+ inputSchema: {
510
+ type: 'object',
511
+ properties: {
512
+ root: { type: 'string' },
513
+ queue: { type: 'string' },
514
+ },
515
+ },
516
+ },
517
+ {
518
+ name: 'neurain_session_flush',
519
+ description: 'Read-only session-scoped flush plan at a level (light/standard/full): allowed vs blocked items and target conflicts. Writes nothing.',
520
+ inputSchema: {
521
+ type: 'object',
522
+ properties: {
523
+ root: { type: 'string' },
524
+ sessionId: { type: 'string' },
525
+ level: { type: 'string', description: 'light | standard | full (default standard).' },
526
+ queue: { type: 'string' },
527
+ },
528
+ required: ['sessionId'],
529
+ },
530
+ },
531
+ {
532
+ name: 'neurain_compile',
533
+ description: 'Read-only compile desk (the !compile picker): priority-ranked candidates from the pending queue + source-digest manifest. Excludes private / needs-confirmation, reads no raw bodies, writes nothing.',
534
+ inputSchema: {
535
+ type: 'object',
536
+ properties: {
537
+ root: { type: 'string' },
538
+ target: { type: 'string', description: 'Optional target query to select a single source.' },
539
+ sessionId: { type: 'string' },
540
+ top: { type: 'number' },
541
+ manifest: { type: 'string' },
542
+ queue: { type: 'string' },
543
+ },
544
+ },
545
+ },
546
+ ],
547
+ }));
548
+
549
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
550
+ const name = request.params.name;
551
+ const input = request.params.arguments || {};
552
+ if (name === 'neurain_status') {
553
+ const out = await doctorCommand({ _: [scopedRoot(input.root, root)], json: true });
554
+ return textResult(out.payload);
555
+ }
556
+ if (name === 'neurain_search') {
557
+ const out = searchRoot(scopedRoot(input.root, root), String(input.query || ''), { top: Number(input.top || 10) });
558
+ return textResult(out);
559
+ }
560
+ if (name === 'neurain_capture') {
561
+ const out = capture(scopedRoot(input.root, root), String(input.text || ''), String(input.source || 'mcp'));
562
+ return textResult(out);
563
+ }
564
+ if (name === 'neurain_adopt_scan') {
565
+ const out = scanAdoption(scopedRoot(input.folder, root), { dryRun: true });
566
+ return textResult(out);
567
+ }
568
+ if (name === 'neurain_journal_list') {
569
+ return textResult(listJournalEvents(scopedRoot(input.root, root), { limit: Number(input.top || 20), type: input.type || '' }));
570
+ }
571
+ if (name === 'neurain_journal_verify') {
572
+ return textResult(verifyJournal(scopedRoot(input.root, root)));
573
+ }
574
+ if (name === 'neurain_lifecycle_eval') {
575
+ const caseFile = input.caseFile || '';
576
+ return textResult(evaluateLifecycleAutomation(scopedRoot(input.root, root), {
577
+ fixtureSize: positiveInteger(input.fixtureSize, 100, 'fixtureSize'),
578
+ minCases: positiveInteger(input.minCases, caseFile ? 1 : 100, 'minCases'),
579
+ caseFile,
580
+ }));
581
+ }
582
+ if (name === 'neurain_lifecycle_report') {
583
+ return textResult(buildLifecycleReport(scopedRoot(input.root, root), {
584
+ host: input.host || '',
585
+ sessionId: input.sessionId || '',
586
+ limit: Number(input.top || 500),
587
+ }));
588
+ }
589
+ if (name === 'neurain_lessons_list') {
590
+ const out = {
591
+ ok: true,
592
+ durable_write: false,
593
+ body_request_ignored: Boolean(input.includeBodies),
594
+ lessons: listLessons(scopedRoot(input.root, root), { includeBodies: false, area: input.area || '' }),
595
+ };
596
+ return textResult(out);
597
+ }
598
+ if (name === 'neurain_lessons_candidates') {
599
+ const out = {
600
+ ok: true,
601
+ durable_write: false,
602
+ candidates: lessonCandidates(scopedRoot(input.root, root), { limit: Number(input.top || 5), area: input.area || '' }),
603
+ };
604
+ return textResult(out);
605
+ }
606
+ if (name === 'neurain_lessons_eval') {
607
+ const fixtureSize = Number(input.fixtureSize || 100);
608
+ const counts = lessonEvalScaledCounts(fixtureSize);
609
+ return textResult(evaluateLessonCandidateDetection(scopedRoot(input.root, root), {
610
+ ...counts,
611
+ minCases: Number(input.minCases || 100),
612
+ }));
613
+ }
614
+ if (name === 'neurain_capabilities') {
615
+ const out = {
616
+ ok: true,
617
+ durable_write: false,
618
+ capabilities: listCapabilities({ query: String(input.query || ''), limit: Number(input.top || 10) }),
619
+ };
620
+ return textResult(out);
621
+ }
622
+ if (name === 'neurain_recap') {
623
+ return textResult(buildRecap(scopedRoot(input.root, root), { area: input.area || '' }));
624
+ }
625
+ if (name === 'neurain_watch_report') {
626
+ return textResult(buildWatchReport(scopedRoot(input.root, root), {
627
+ area: input.area || '',
628
+ top: Number(input.top || 10),
629
+ sinceMinutes: Number(input.sinceMinutes || 1440),
630
+ pollOnce: true,
631
+ }));
632
+ }
633
+ if (name === 'neurain_review_worker') {
634
+ return textResult(buildReviewWorkerReport(scopedRoot(input.root, root), {
635
+ area: input.area || '',
636
+ top: Number(input.top || 10),
637
+ sinceMinutes: Number(input.sinceMinutes || 1440),
638
+ }));
639
+ }
640
+ if (name === 'neurain_scheduler_tick') {
641
+ return textResult(buildSchedulerTick(scopedRoot(input.root, root), {
642
+ area: input.area || '',
643
+ top: Number(input.top || 10),
644
+ sinceMinutes: Number(input.sinceMinutes || 1440),
645
+ minTriggers: Number(input.minTriggers || 1),
646
+ minEvents: Number(input.minEvents || 1),
647
+ }));
648
+ }
649
+ if (name === 'neurain_scheduler_eval') {
650
+ const caseFile = input.caseFile || '';
651
+ return textResult(await evaluateSchedulerTriggering(scopedRoot(input.root, root), {
652
+ fixtureSize: positiveInteger(input.fixtureSize, 100, 'fixtureSize'),
653
+ minCases: positiveInteger(input.minCases, caseFile ? 1 : 100, 'minCases'),
654
+ caseFile,
655
+ }));
656
+ }
657
+ if (name === 'neurain_curator_status') {
658
+ return textResult(curatorStatus(scopedRoot(input.root, root), {
659
+ staleDays: Number(input.staleDays || 30),
660
+ archiveDays: Number(input.archiveDays || 90),
661
+ }));
662
+ }
663
+ if (name === 'neurain_curator_run_preview') {
664
+ return textResult(runCurator(scopedRoot(input.root, root), {
665
+ dryRun: true,
666
+ staleDays: Number(input.staleDays || 30),
667
+ archiveDays: Number(input.archiveDays || 90),
668
+ }));
669
+ }
670
+ if (name === 'neurain_recall_status') {
671
+ return textResult(await recallStatus(scopedRoot(input.root, root)));
672
+ }
673
+ if (name === 'neurain_recall_rebuild_preview') {
674
+ return textResult(await rebuildRecall(scopedRoot(input.root, root), { dryRun: true }));
675
+ }
676
+ if (name === 'neurain_recall_search') {
677
+ return textResult(await searchRecall(scopedRoot(input.root, root), String(input.query || ''), {
678
+ top: Number(input.top || 10),
679
+ host: input.host || '',
680
+ }));
681
+ }
682
+ if (name === 'neurain_recall_verify') {
683
+ return textResult(await verifyRecall(scopedRoot(input.root, root)));
684
+ }
685
+ if (name === 'neurain_recall_cross_host_eval') {
686
+ return textResult(await evaluateCrossHostRecall(scopedRoot(input.root, root), {
687
+ top: Number(input.top || 5),
688
+ minCases: Number(input.minCases || (input.caseFile ? 1 : 2)),
689
+ fixtureSize: Number(input.fixtureSize || 0),
690
+ privateProbeCount: Number(input.privateProbes || 20),
691
+ caseFile: input.caseFile || '',
692
+ }));
693
+ }
694
+ if (name === 'neurain_recall_semantic_search') {
695
+ return textResult(await semanticSearchRecall(scopedRoot(input.root, root), String(input.query || ''), {
696
+ top: Number(input.top || 10),
697
+ host: input.host || '',
698
+ provider: input.provider || 'local-lexical',
699
+ minScore: input.minScore !== undefined ? Number(input.minScore) : 0.34,
700
+ }));
701
+ }
702
+ if (name === 'neurain_recall_hybrid_search') {
703
+ return textResult(await hybridSearchRecall(scopedRoot(input.root, root), String(input.query || ''), {
704
+ top: Number(input.top || 10),
705
+ host: input.host || '',
706
+ provider: input.provider || 'local-lexical',
707
+ minScore: input.minScore !== undefined ? Number(input.minScore) : 0.34,
708
+ }));
709
+ }
710
+ if (name === 'neurain_recall_semantic_eval') {
711
+ const caseFile = input.caseFile || '';
712
+ return textResult(await evaluateSemanticRecall(scopedRoot(input.root, root), {
713
+ top: Number(input.top || 5),
714
+ minCases: Number(input.minCases || (caseFile ? 1 : 50)),
715
+ fixtureSize: Number(input.fixtureSize || 60),
716
+ caseFile,
717
+ provider: input.provider || 'local-lexical',
718
+ }));
719
+ }
720
+ if (name === 'neurain_answer_quality_eval') {
721
+ const fixtureSize = Number(input.fixtureSize || 120);
722
+ const counts = answerEvalScaledCounts(fixtureSize);
723
+ return textResult(await evaluateAnswerQuality(scopedRoot(input.root, root), {
724
+ ...(input.caseFile ? {} : counts),
725
+ caseFile: input.caseFile || '',
726
+ minCases: Number(input.minCases || (input.caseFile ? 1 : 50)),
727
+ }));
728
+ }
729
+ if (name === 'neurain_live_cases_scaffold') {
730
+ return textResult(buildLiveCaseScaffold(scopedRoot(input.root, root), {
731
+ sampleSize: Number(input.sampleSize || 12),
732
+ label: input.label || 'mcp-live-case',
733
+ write: false,
734
+ }));
735
+ }
736
+ if (name === 'neurain_wrap_preview') {
737
+ const out = await wrapCommand({ _: [scopedRoot(input.root, root)], top: input.top, area: input.area || '', json: true, 'dry-run': true });
738
+ return textResult(out.payload);
739
+ }
740
+ if (name === 'neurain_session_status') {
741
+ const out = await statusCommand({ _: [scopedRoot(input.root, root)], 'session-id': input.sessionId, all: input.all, 'stale-days': input.staleDays, json: true });
742
+ return textResult(out.payload);
743
+ }
744
+ if (name === 'neurain_digest_preview') {
745
+ const out = await digestCommand({ _: [scopedRoot(input.root, root)], session: input.session, 'window-hours': input.windowHours, json: true });
746
+ return textResult(out.payload);
747
+ }
748
+ if (name === 'neurain_queue_view') {
749
+ const out = await queueCommand({ _: [scopedRoot(input.root, root)], queue: input.queue, status: input.status, pending: input.pending, stats: input.stats, 'lossless-check': input.losslessCheck, json: true });
750
+ return textResult(out.payload);
751
+ }
752
+ if (name === 'neurain_review_queue') {
753
+ const out = await reviewQueueCommand({ _: [scopedRoot(input.root, root)], 'session-id': input.sessionId, top: input.top, json: true });
754
+ return textResult(out.payload);
755
+ }
756
+ if (name === 'neurain_route') {
757
+ const out = await routeCommand({ _: [scopedRoot(input.root, root)], text: input.text, 'session-id': input.sessionId, area: input.area, sensitivity: input.sensitivity, intent: input.intent, title: input.title, type: input.type, json: true });
758
+ return textResult(out.payload);
759
+ }
760
+ if (name === 'neurain_plan_writeback') {
761
+ const out = await planWritebackCommand({ _: [scopedRoot(input.root, root)], 'source-id': input.sourceId, text: input.text, 'session-id': input.sessionId, area: input.area, sensitivity: input.sensitivity, intent: input.intent, title: input.title, type: input.type, json: true });
762
+ return textResult(out.payload);
763
+ }
764
+ if (name === 'neurain_flush') {
765
+ const out = await flushCommand({ _: [scopedRoot(input.root, root)], queue: input.queue, json: true });
766
+ return textResult(out.payload);
767
+ }
768
+ if (name === 'neurain_session_flush') {
769
+ const out = await sessionFlushCommand({ _: [scopedRoot(input.root, root)], 'session-id': input.sessionId, level: input.level, queue: input.queue, json: true });
770
+ return textResult(out.payload);
771
+ }
772
+ if (name === 'neurain_compile') {
773
+ const out = await compileCommand({ _: [scopedRoot(input.root, root)], target: input.target, 'session-id': input.sessionId, top: input.top, manifest: input.manifest, queue: input.queue, json: true });
774
+ return textResult(out.payload);
775
+ }
776
+ throw new Error(`Unknown Neurain tool: ${name}`);
777
+ });
778
+
779
+ await server.connect(new StdioServerTransport());
780
+ }
781
+
782
+ function scopedRoot(requested, serverRoot) {
783
+ const base = absPath(serverRoot);
784
+ const target = requested ? path.resolve(base, String(requested)) : base;
785
+ if (target !== base && !target.startsWith(`${base}${path.sep}`)) {
786
+ throw new Error(`Path is outside configured Neurain root: ${requested}`);
787
+ }
788
+ return target;
789
+ }
790
+
791
+ function positiveInteger(value, fallback, label) {
792
+ if (value === undefined || value === null || value === '') return fallback;
793
+ const parsed = Number(value);
794
+ if (!Number.isInteger(parsed) || parsed < 1) throw new Error(`${label} must be a positive integer.`);
795
+ return parsed;
796
+ }
797
+
798
+ function capture(root, text, source) {
799
+ if (!text.trim()) throw new Error('capture text is empty');
800
+ const secret = secretLike(text);
801
+ if (secret) {
802
+ return {
803
+ ok: false,
804
+ refused: true,
805
+ reason: `secret-like content detected: ${secret}`,
806
+ durable_write: false,
807
+ };
808
+ }
809
+ const date = new Date().toISOString().slice(0, 10);
810
+ const rel = `raw/_inbox/mcp-${date}-${compactStamp()}.json`;
811
+ const abs = safeResolve(root, rel);
812
+ ensureDir(path.dirname(abs));
813
+ const envelope = {
814
+ source_type: 'mcp_capture',
815
+ source,
816
+ captured_at: timestamp(),
817
+ sensitivity: 'internal',
818
+ text,
819
+ };
820
+ fs.writeFileSync(abs, `${JSON.stringify(envelope, null, 2)}\n`);
821
+ return {
822
+ ok: true,
823
+ path: rel,
824
+ durable_write: false,
825
+ note: 'Captured as append-only raw source. No wiki or durable knowledge write was performed.',
826
+ };
827
+ }
828
+
829
+ function textResult(value) {
830
+ return {
831
+ content: [
832
+ {
833
+ type: 'text',
834
+ text: JSON.stringify(value, null, 2),
835
+ },
836
+ ],
837
+ };
838
+ }
839
+
840
+ function answerEvalScaledCounts(total) {
841
+ const count = Math.max(10, Math.min(Number(total || 120), 500));
842
+ const supportedCases = Math.max(1, Math.round(count * 0.42));
843
+ const conflictCases = Math.max(1, Math.round(count * 0.17));
844
+ const privateCases = Math.max(1, Math.round(count * 0.17));
845
+ const staleCases = Math.max(1, Math.round(count * 0.17));
846
+ const used = supportedCases + conflictCases + privateCases + staleCases;
847
+ return {
848
+ supportedCases,
849
+ conflictCases,
850
+ privateCases,
851
+ staleCases,
852
+ noAnswerCases: Math.max(1, count - used),
853
+ };
854
+ }
855
+
856
+ function lessonEvalScaledCounts(total) {
857
+ const count = Math.max(10, Math.min(Number(total || 100), 500));
858
+ const positiveCases = Math.max(1, Math.round(count * 0.4));
859
+ const unsafeCases = Math.max(1, Math.round(count * 0.1));
860
+ return {
861
+ positiveCases,
862
+ negativeCases: Math.max(1, count - positiveCases - unsafeCases),
863
+ unsafeCases,
864
+ };
865
+ }