@telvok/librarian-mcp 1.5.4 → 2.0.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 (119) hide show
  1. package/dist/library/errors.d.ts +48 -0
  2. package/dist/library/errors.js +80 -0
  3. package/dist/library/schemas.d.ts +6 -6
  4. package/dist/library/storage.d.ts +2 -2
  5. package/dist/library/storage.js +2 -2
  6. package/dist/library 2/embeddings.d.ts +21 -0
  7. package/dist/library 2/embeddings.js +86 -0
  8. package/dist/library 2/manager.d.ts +42 -0
  9. package/dist/library 2/manager.js +218 -0
  10. package/dist/library 2/parsers/cursor.d.ts +15 -0
  11. package/dist/library 2/parsers/cursor.js +168 -0
  12. package/dist/library 2/parsers/index.d.ts +6 -0
  13. package/dist/library 2/parsers/index.js +5 -0
  14. package/dist/library 2/parsers/json.d.ts +11 -0
  15. package/dist/library 2/parsers/json.js +95 -0
  16. package/dist/library 2/parsers/jsonl.d.ts +14 -0
  17. package/dist/library 2/parsers/jsonl.js +85 -0
  18. package/dist/library 2/parsers/markdown.d.ts +15 -0
  19. package/dist/library 2/parsers/markdown.js +77 -0
  20. package/dist/library 2/parsers/sqlite.d.ts +8 -0
  21. package/dist/library 2/parsers/sqlite.js +123 -0
  22. package/dist/library 2/parsers/types.d.ts +21 -0
  23. package/dist/library 2/parsers/types.js +4 -0
  24. package/dist/library 2/query.d.ts +26 -0
  25. package/dist/library 2/query.js +104 -0
  26. package/dist/library 2/schemas.d.ts +324 -0
  27. package/dist/library 2/schemas.js +79 -0
  28. package/dist/library 2/storage.d.ts +22 -0
  29. package/dist/library 2/storage.js +36 -0
  30. package/dist/library 2/vector-index.d.ts +55 -0
  31. package/dist/library 2/vector-index.js +160 -0
  32. package/dist/server 2.js +199 -0
  33. package/dist/server.d 2.ts +2 -0
  34. package/dist/server.js +102 -54
  35. package/dist/tools/adopt.d.ts +1 -0
  36. package/dist/tools/adopt.js +37 -10
  37. package/dist/tools/auth.d.ts +69 -0
  38. package/dist/tools/auth.js +379 -0
  39. package/dist/tools/bounty-claim.d.ts +28 -0
  40. package/dist/tools/bounty-claim.js +92 -0
  41. package/dist/tools/bounty-create.d.ts +47 -0
  42. package/dist/tools/bounty-create.js +118 -0
  43. package/dist/tools/bounty-list.d.ts +50 -0
  44. package/dist/tools/bounty-list.js +116 -0
  45. package/dist/tools/bounty-submit.d.ts +34 -0
  46. package/dist/tools/bounty-submit.js +94 -0
  47. package/dist/tools/brief.d.ts +94 -0
  48. package/dist/tools/brief.js +234 -15
  49. package/dist/tools/delete.d.ts +87 -0
  50. package/dist/tools/delete.js +266 -0
  51. package/dist/tools/feedback.d.ts +27 -0
  52. package/dist/tools/feedback.js +98 -0
  53. package/dist/tools/help.d.ts +22 -0
  54. package/dist/tools/help.js +482 -0
  55. package/dist/tools/import-memories.d.ts +1 -0
  56. package/dist/tools/import-memories.js +18 -13
  57. package/dist/tools/index.d.ts +11 -0
  58. package/dist/tools/index.js +12 -0
  59. package/dist/tools/library-buy.d.ts +31 -0
  60. package/dist/tools/library-buy.js +104 -0
  61. package/dist/tools/library-download.d.ts +27 -0
  62. package/dist/tools/library-download.js +177 -0
  63. package/dist/tools/library-publish.d.ts +112 -0
  64. package/dist/tools/library-publish.js +387 -0
  65. package/dist/tools/library-search.d.ts +110 -0
  66. package/dist/tools/library-search.js +132 -0
  67. package/dist/tools/mark-hit.d.ts +1 -0
  68. package/dist/tools/mark-hit.js +83 -5
  69. package/dist/tools/my-books.d.ts +51 -0
  70. package/dist/tools/my-books.js +115 -0
  71. package/dist/tools/my-bounties.d.ts +43 -0
  72. package/dist/tools/my-bounties.js +126 -0
  73. package/dist/tools/rate-book.d.ts +40 -0
  74. package/dist/tools/rate-book.js +147 -0
  75. package/dist/tools/rebuild-index.d.ts +1 -0
  76. package/dist/tools/rebuild-index.js +40 -8
  77. package/dist/tools/record.d.ts +18 -0
  78. package/dist/tools/record.js +30 -26
  79. package/dist/tools/seller-analytics.d.ts +53 -0
  80. package/dist/tools/seller-analytics.js +180 -0
  81. package/dist/tools/sync.d.ts +55 -0
  82. package/dist/tools/sync.js +304 -0
  83. package/dist/tools/unsubscribe.d.ts +48 -0
  84. package/dist/tools/unsubscribe.js +120 -0
  85. package/dist/tools 2/adopt.d.ts +24 -0
  86. package/dist/tools 2/adopt.js +154 -0
  87. package/dist/tools 2/auth.d.ts +35 -0
  88. package/dist/tools 2/auth.js +229 -0
  89. package/dist/tools 2/brief.d.ts +56 -0
  90. package/dist/tools 2/brief.js +414 -0
  91. package/dist/tools 2/help.d.ts +21 -0
  92. package/dist/tools 2/help.js +267 -0
  93. package/dist/tools 2/import-memories.d.ts +32 -0
  94. package/dist/tools 2/import-memories.js +231 -0
  95. package/dist/tools 2/index.d.ts +12 -0
  96. package/dist/tools 2/index.js +12 -0
  97. package/dist/tools 2/mark-hit.d.ts +20 -0
  98. package/dist/tools 2/mark-hit.js +71 -0
  99. package/dist/tools 2/marketplace-buy.d.ts +30 -0
  100. package/dist/tools 2/marketplace-buy.js +97 -0
  101. package/dist/tools 2/marketplace-download.d.ts +26 -0
  102. package/dist/tools 2/marketplace-download.js +160 -0
  103. package/dist/tools 2/marketplace-publish.d.ts +111 -0
  104. package/dist/tools 2/marketplace-publish.js +377 -0
  105. package/dist/tools 2/marketplace-search.d.ts +57 -0
  106. package/dist/tools 2/marketplace-search.js +96 -0
  107. package/dist/tools 2/my-books.d.ts +50 -0
  108. package/dist/tools 2/my-books.js +107 -0
  109. package/dist/tools 2/rate-book.d.ts +39 -0
  110. package/dist/tools 2/rate-book.js +139 -0
  111. package/dist/tools 2/rebuild-index.d.ts +23 -0
  112. package/dist/tools 2/rebuild-index.js +107 -0
  113. package/dist/tools 2/record.d.ts +40 -0
  114. package/dist/tools 2/record.js +205 -0
  115. package/dist/tools 2/seller-analytics.d.ts +35 -0
  116. package/dist/tools 2/seller-analytics.js +102 -0
  117. package/dist/tools 2/sync.d.ts +54 -0
  118. package/dist/tools 2/sync.js +298 -0
  119. package/package.json +1 -1
@@ -0,0 +1,160 @@
1
+ import * as fs from 'fs/promises';
2
+ import * as path from 'path';
3
+ import { getLibraryPath } from './storage.js';
4
+ import { getEmbedding, chunkText, cosineSimilarity, EMBEDDING_MODEL_ID } from './embeddings.js';
5
+ // ============================================================================
6
+ // Constants
7
+ // ============================================================================
8
+ const INDEX_FILENAME = 'index.json';
9
+ const CURRENT_VERSION = 1;
10
+ // ============================================================================
11
+ // Index File Operations
12
+ // ============================================================================
13
+ /**
14
+ * Get path to the index file.
15
+ */
16
+ function getIndexPath() {
17
+ return path.join(getLibraryPath(), INDEX_FILENAME);
18
+ }
19
+ /**
20
+ * Load the vector index from disk.
21
+ * Returns empty index if file doesn't exist or is invalid.
22
+ */
23
+ export async function loadIndex() {
24
+ const indexPath = getIndexPath();
25
+ try {
26
+ const data = await fs.readFile(indexPath, 'utf-8');
27
+ const index = JSON.parse(data);
28
+ // Validate structure
29
+ if (!index.version || !Array.isArray(index.entries)) {
30
+ return createEmptyIndex();
31
+ }
32
+ return index;
33
+ }
34
+ catch {
35
+ // File doesn't exist or is invalid
36
+ return createEmptyIndex();
37
+ }
38
+ }
39
+ /**
40
+ * Save the vector index to disk.
41
+ */
42
+ export async function saveIndex(index) {
43
+ const indexPath = getIndexPath();
44
+ // Update metadata
45
+ index.rebuilt = new Date().toISOString();
46
+ index.modelId = EMBEDDING_MODEL_ID;
47
+ // Ensure directory exists
48
+ await fs.mkdir(path.dirname(indexPath), { recursive: true });
49
+ // Write atomically by writing to temp file first
50
+ const tempPath = indexPath + '.tmp';
51
+ await fs.writeFile(tempPath, JSON.stringify(index, null, 2), 'utf-8');
52
+ await fs.rename(tempPath, indexPath);
53
+ }
54
+ /**
55
+ * Create an empty index.
56
+ */
57
+ function createEmptyIndex() {
58
+ return {
59
+ version: CURRENT_VERSION,
60
+ rebuilt: '',
61
+ modelId: EMBEDDING_MODEL_ID,
62
+ entries: [],
63
+ };
64
+ }
65
+ // ============================================================================
66
+ // Index Operations
67
+ // ============================================================================
68
+ /**
69
+ * Add or update an entry in the index.
70
+ * Chunks the content and generates embeddings for each chunk.
71
+ */
72
+ export async function addToIndex(index, entryPath, title, content) {
73
+ // Remove any existing entries for this path
74
+ index.entries = index.entries.filter(e => e.path !== entryPath);
75
+ // Chunk the content
76
+ const chunks = chunkText(content);
77
+ // Generate embeddings for each chunk
78
+ for (let i = 0; i < chunks.length; i++) {
79
+ const chunk = chunks[i];
80
+ try {
81
+ const embedding = await getEmbedding(chunk);
82
+ index.entries.push({
83
+ path: entryPath,
84
+ title,
85
+ embedding,
86
+ chunk: i,
87
+ preview: chunk.slice(0, 100) + (chunk.length > 100 ? '...' : ''),
88
+ });
89
+ }
90
+ catch (error) {
91
+ // Log but don't fail - entry will still be searchable via keywords
92
+ console.error(`Failed to embed chunk ${i} for ${entryPath}:`, error);
93
+ }
94
+ }
95
+ }
96
+ /**
97
+ * Remove an entry from the index.
98
+ */
99
+ export function removeFromIndex(index, entryPath) {
100
+ index.entries = index.entries.filter(e => e.path !== entryPath);
101
+ }
102
+ // ============================================================================
103
+ // Semantic Search
104
+ // ============================================================================
105
+ /**
106
+ * Search the index for entries semantically similar to the query.
107
+ * Returns paths ranked by similarity, deduped to best chunk per entry.
108
+ */
109
+ export async function semanticSearch(index, query, limit = 5) {
110
+ if (index.entries.length === 0) {
111
+ return [];
112
+ }
113
+ // Get query embedding
114
+ const queryEmbedding = await getEmbedding(query);
115
+ // Score all entries
116
+ const scored = index.entries.map(entry => ({
117
+ ...entry,
118
+ similarity: cosineSimilarity(queryEmbedding, entry.embedding),
119
+ }));
120
+ // Dedupe by path - keep the chunk with highest similarity
121
+ const byPath = new Map();
122
+ for (const entry of scored) {
123
+ const existing = byPath.get(entry.path);
124
+ if (!existing || entry.similarity > existing.similarity) {
125
+ byPath.set(entry.path, entry);
126
+ }
127
+ }
128
+ // Sort by similarity descending and apply limit
129
+ const results = [...byPath.values()]
130
+ .sort((a, b) => b.similarity - a.similarity)
131
+ .slice(0, limit)
132
+ .map(entry => ({
133
+ path: entry.path,
134
+ title: entry.title,
135
+ similarity: entry.similarity,
136
+ preview: entry.preview,
137
+ }));
138
+ return results;
139
+ }
140
+ // ============================================================================
141
+ // Index Health
142
+ // ============================================================================
143
+ /**
144
+ * Check if the index might be stale (model changed).
145
+ */
146
+ export function isIndexStale(index) {
147
+ return index.modelId !== EMBEDDING_MODEL_ID;
148
+ }
149
+ /**
150
+ * Get index statistics.
151
+ */
152
+ export function getIndexStats(index) {
153
+ const uniquePaths = new Set(index.entries.map(e => e.path));
154
+ return {
155
+ entryCount: uniquePaths.size,
156
+ chunkCount: index.entries.length,
157
+ modelId: index.modelId,
158
+ rebuilt: index.rebuilt,
159
+ };
160
+ }
@@ -0,0 +1,199 @@
1
+ #!/usr/bin/env node
2
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
3
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
4
+ import { CallToolRequestSchema, ListToolsRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
5
+ import { briefTool } from './tools/brief.js';
6
+ import { recordTool } from './tools/record.js';
7
+ import { adoptTool } from './tools/adopt.js';
8
+ import { markHitTool } from './tools/mark-hit.js';
9
+ import { importMemoriesTool } from './tools/import-memories.js';
10
+ import { rebuildIndexTool } from './tools/rebuild-index.js';
11
+ import { authTool } from './tools/auth.js';
12
+ import { marketplaceSearchTool } from './tools/marketplace-search.js';
13
+ import { marketplaceBuyTool } from './tools/marketplace-buy.js';
14
+ import { marketplaceDownloadTool } from './tools/marketplace-download.js';
15
+ import { marketplacePublishTool } from './tools/marketplace-publish.js';
16
+ import { myBooksTool } from './tools/my-books.js';
17
+ import { syncTool } from './tools/sync.js';
18
+ import { sellerAnalyticsTool } from './tools/seller-analytics.js';
19
+ import { rateBookTool } from './tools/rate-book.js';
20
+ import { helpTool } from './tools/help.js';
21
+ const server = new Server({
22
+ name: 'librarian',
23
+ version: '1.0.0',
24
+ }, {
25
+ capabilities: {
26
+ tools: {},
27
+ },
28
+ });
29
+ // List available tools
30
+ server.setRequestHandler(ListToolsRequestSchema, async () => {
31
+ return {
32
+ tools: [
33
+ {
34
+ name: briefTool.name,
35
+ description: briefTool.description,
36
+ inputSchema: briefTool.inputSchema,
37
+ },
38
+ {
39
+ name: recordTool.name,
40
+ description: recordTool.description,
41
+ inputSchema: recordTool.inputSchema,
42
+ },
43
+ {
44
+ name: adoptTool.name,
45
+ description: adoptTool.description,
46
+ inputSchema: adoptTool.inputSchema,
47
+ },
48
+ {
49
+ name: markHitTool.name,
50
+ description: markHitTool.description,
51
+ inputSchema: markHitTool.inputSchema,
52
+ },
53
+ {
54
+ name: importMemoriesTool.name,
55
+ description: importMemoriesTool.description,
56
+ inputSchema: importMemoriesTool.inputSchema,
57
+ },
58
+ {
59
+ name: rebuildIndexTool.name,
60
+ description: rebuildIndexTool.description,
61
+ inputSchema: rebuildIndexTool.inputSchema,
62
+ },
63
+ {
64
+ name: authTool.name,
65
+ description: authTool.description,
66
+ inputSchema: authTool.inputSchema,
67
+ },
68
+ {
69
+ name: marketplaceSearchTool.name,
70
+ description: marketplaceSearchTool.description,
71
+ inputSchema: marketplaceSearchTool.inputSchema,
72
+ },
73
+ {
74
+ name: marketplaceBuyTool.name,
75
+ description: marketplaceBuyTool.description,
76
+ inputSchema: marketplaceBuyTool.inputSchema,
77
+ },
78
+ {
79
+ name: marketplaceDownloadTool.name,
80
+ description: marketplaceDownloadTool.description,
81
+ inputSchema: marketplaceDownloadTool.inputSchema,
82
+ },
83
+ {
84
+ name: marketplacePublishTool.name,
85
+ description: marketplacePublishTool.description,
86
+ inputSchema: marketplacePublishTool.inputSchema,
87
+ },
88
+ {
89
+ name: myBooksTool.name,
90
+ description: myBooksTool.description,
91
+ inputSchema: myBooksTool.inputSchema,
92
+ },
93
+ {
94
+ name: syncTool.name,
95
+ description: syncTool.description,
96
+ inputSchema: syncTool.inputSchema,
97
+ },
98
+ {
99
+ name: sellerAnalyticsTool.name,
100
+ description: sellerAnalyticsTool.description,
101
+ inputSchema: sellerAnalyticsTool.inputSchema,
102
+ },
103
+ {
104
+ name: rateBookTool.name,
105
+ description: rateBookTool.description,
106
+ inputSchema: rateBookTool.inputSchema,
107
+ },
108
+ {
109
+ name: helpTool.name,
110
+ description: helpTool.description,
111
+ inputSchema: helpTool.inputSchema,
112
+ },
113
+ ],
114
+ };
115
+ });
116
+ // Handle tool calls
117
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
118
+ const { name, arguments: args } = request.params;
119
+ try {
120
+ let result;
121
+ switch (name) {
122
+ case 'brief':
123
+ result = await briefTool.handler(args);
124
+ break;
125
+ case 'record':
126
+ result = await recordTool.handler(args);
127
+ break;
128
+ case 'adopt':
129
+ result = await adoptTool.handler(args);
130
+ break;
131
+ case 'mark_hit':
132
+ result = await markHitTool.handler(args);
133
+ break;
134
+ case 'import_memories':
135
+ result = await importMemoriesTool.handler(args);
136
+ break;
137
+ case 'rebuild_index':
138
+ result = await rebuildIndexTool.handler(args);
139
+ break;
140
+ case 'auth':
141
+ result = await authTool.handler(args);
142
+ break;
143
+ case 'marketplace_search':
144
+ result = await marketplaceSearchTool.handler(args);
145
+ break;
146
+ case 'marketplace_buy':
147
+ result = await marketplaceBuyTool.handler(args);
148
+ break;
149
+ case 'marketplace_download':
150
+ result = await marketplaceDownloadTool.handler(args);
151
+ break;
152
+ case 'marketplace_publish':
153
+ result = await marketplacePublishTool.handler(args);
154
+ break;
155
+ case 'my_books':
156
+ result = await myBooksTool.handler(args);
157
+ break;
158
+ case 'sync':
159
+ result = await syncTool.handler(args);
160
+ break;
161
+ case 'seller_analytics':
162
+ result = await sellerAnalyticsTool.handler();
163
+ break;
164
+ case 'rate_book':
165
+ result = await rateBookTool.handler(args);
166
+ break;
167
+ case 'help':
168
+ result = await helpTool.handler(args);
169
+ break;
170
+ default:
171
+ throw new Error(`Unknown tool: ${name}`);
172
+ }
173
+ return {
174
+ content: [
175
+ {
176
+ type: 'text',
177
+ text: JSON.stringify(result, null, 2),
178
+ },
179
+ ],
180
+ };
181
+ }
182
+ catch (error) {
183
+ const message = error instanceof Error ? error.message : String(error);
184
+ return {
185
+ content: [
186
+ {
187
+ type: 'text',
188
+ text: JSON.stringify({ error: message }),
189
+ },
190
+ ],
191
+ isError: true,
192
+ };
193
+ }
194
+ });
195
+ async function main() {
196
+ const transport = new StdioServerTransport();
197
+ await server.connect(transport);
198
+ }
199
+ main().catch(console.error);
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/server.js CHANGED
@@ -2,12 +2,85 @@
2
2
  import { Server } from '@modelcontextprotocol/sdk/server/index.js';
3
3
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
4
4
  import { CallToolRequestSchema, ListToolsRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
5
+ import { LibrarianError } from './library/errors.js';
5
6
  import { briefTool } from './tools/brief.js';
6
7
  import { recordTool } from './tools/record.js';
7
8
  import { adoptTool } from './tools/adopt.js';
8
9
  import { markHitTool } from './tools/mark-hit.js';
9
10
  import { importMemoriesTool } from './tools/import-memories.js';
10
11
  import { rebuildIndexTool } from './tools/rebuild-index.js';
12
+ import { authTool } from './tools/auth.js';
13
+ import { librarySearchTool } from './tools/library-search.js';
14
+ import { libraryBuyTool } from './tools/library-buy.js';
15
+ import { libraryDownloadTool } from './tools/library-download.js';
16
+ import { libraryPublishTool } from './tools/library-publish.js';
17
+ import { myBooksTool } from './tools/my-books.js';
18
+ import { syncTool } from './tools/sync.js';
19
+ import { sellerAnalyticsTool } from './tools/seller-analytics.js';
20
+ import { rateBookTool } from './tools/rate-book.js';
21
+ import { helpTool } from './tools/help.js';
22
+ import { feedbackTool } from './tools/feedback.js';
23
+ import { bountyCreateTool } from './tools/bounty-create.js';
24
+ import { bountyListTool } from './tools/bounty-list.js';
25
+ import { bountyClaimTool } from './tools/bounty-claim.js';
26
+ import { bountySubmitTool } from './tools/bounty-submit.js';
27
+ import { myBountiesTool } from './tools/my-bounties.js';
28
+ import { deleteTool } from './tools/delete.js';
29
+ import { unsubscribeTool } from './tools/unsubscribe.js';
30
+ const allTools = [
31
+ // Core tools — local knowledge management
32
+ { tool: briefTool, group: 'core' },
33
+ { tool: recordTool, group: 'core' },
34
+ { tool: adoptTool, group: 'core' },
35
+ { tool: markHitTool, group: 'core' },
36
+ { tool: importMemoriesTool, group: 'core' },
37
+ { tool: rebuildIndexTool, group: 'core' },
38
+ { tool: deleteTool, group: 'core' },
39
+ // Marketplace tools — cloud features
40
+ { tool: librarySearchTool, group: 'marketplace' },
41
+ { tool: libraryBuyTool, group: 'marketplace' },
42
+ { tool: libraryDownloadTool, group: 'marketplace' },
43
+ { tool: libraryPublishTool, group: 'marketplace' },
44
+ { tool: myBooksTool, group: 'marketplace' },
45
+ { tool: syncTool, group: 'marketplace' },
46
+ { tool: sellerAnalyticsTool, group: 'marketplace' },
47
+ { tool: rateBookTool, group: 'marketplace' },
48
+ { tool: bountyCreateTool, group: 'marketplace' },
49
+ { tool: bountyListTool, group: 'marketplace' },
50
+ { tool: bountyClaimTool, group: 'marketplace' },
51
+ { tool: bountySubmitTool, group: 'marketplace' },
52
+ { tool: myBountiesTool, group: 'marketplace' },
53
+ { tool: unsubscribeTool, group: 'marketplace' },
54
+ // Both — shared across core and marketplace
55
+ { tool: authTool, group: 'both' },
56
+ { tool: helpTool, group: 'both' },
57
+ { tool: feedbackTool, group: 'both' },
58
+ ];
59
+ function parseServerMode() {
60
+ const serverArg = process.argv.find(a => a.startsWith('--server='));
61
+ if (!serverArg)
62
+ return 'all';
63
+ const mode = serverArg.split('=')[1];
64
+ if (mode === 'core' || mode === 'marketplace')
65
+ return mode;
66
+ console.error(`Unknown --server mode: "${mode}". Valid: core, marketplace. Defaulting to all.`);
67
+ return 'all';
68
+ }
69
+ const serverMode = parseServerMode();
70
+ function isToolIncluded(entry) {
71
+ if (serverMode === 'all')
72
+ return true;
73
+ return entry.group === serverMode || entry.group === 'both';
74
+ }
75
+ const enabledTools = allTools.filter(isToolIncluded);
76
+ // Build lookup map for call handler
77
+ const toolMap = new Map();
78
+ for (const entry of enabledTools) {
79
+ toolMap.set(entry.tool.name, entry.tool);
80
+ }
81
+ // ---------------------------------------------------------------------------
82
+ // MCP Server
83
+ // ---------------------------------------------------------------------------
11
84
  const server = new Server({
12
85
  name: 'librarian',
13
86
  version: '1.0.0',
@@ -19,67 +92,29 @@ const server = new Server({
19
92
  // List available tools
20
93
  server.setRequestHandler(ListToolsRequestSchema, async () => {
21
94
  return {
22
- tools: [
23
- {
24
- name: briefTool.name,
25
- description: briefTool.description,
26
- inputSchema: briefTool.inputSchema,
27
- },
28
- {
29
- name: recordTool.name,
30
- description: recordTool.description,
31
- inputSchema: recordTool.inputSchema,
32
- },
33
- {
34
- name: adoptTool.name,
35
- description: adoptTool.description,
36
- inputSchema: adoptTool.inputSchema,
37
- },
38
- {
39
- name: markHitTool.name,
40
- description: markHitTool.description,
41
- inputSchema: markHitTool.inputSchema,
42
- },
43
- {
44
- name: importMemoriesTool.name,
45
- description: importMemoriesTool.description,
46
- inputSchema: importMemoriesTool.inputSchema,
47
- },
48
- {
49
- name: rebuildIndexTool.name,
50
- description: rebuildIndexTool.description,
51
- inputSchema: rebuildIndexTool.inputSchema,
52
- },
53
- ],
95
+ tools: enabledTools.map(({ tool }) => {
96
+ const entry = {
97
+ name: tool.name,
98
+ description: tool.description,
99
+ inputSchema: tool.inputSchema,
100
+ };
101
+ if (tool.title)
102
+ entry.title = tool.title;
103
+ if (tool.outputSchema)
104
+ entry.outputSchema = tool.outputSchema;
105
+ return entry;
106
+ }),
54
107
  };
55
108
  });
56
109
  // Handle tool calls
57
110
  server.setRequestHandler(CallToolRequestSchema, async (request) => {
58
111
  const { name, arguments: args } = request.params;
59
112
  try {
60
- let result;
61
- switch (name) {
62
- case 'brief':
63
- result = await briefTool.handler(args);
64
- break;
65
- case 'record':
66
- result = await recordTool.handler(args);
67
- break;
68
- case 'adopt':
69
- result = await adoptTool.handler(args);
70
- break;
71
- case 'mark_hit':
72
- result = await markHitTool.handler(args);
73
- break;
74
- case 'import_memories':
75
- result = await importMemoriesTool.handler(args);
76
- break;
77
- case 'rebuild_index':
78
- result = await rebuildIndexTool.handler(args);
79
- break;
80
- default:
81
- throw new Error(`Unknown tool: ${name}`);
113
+ const tool = toolMap.get(name);
114
+ if (!tool) {
115
+ throw new Error(`Unknown tool: ${name}`);
82
116
  }
117
+ const result = await tool.handler(args);
83
118
  return {
84
119
  content: [
85
120
  {
@@ -90,6 +125,19 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
90
125
  };
91
126
  }
92
127
  catch (error) {
128
+ // Handle LibrarianError with error codes
129
+ if (error instanceof LibrarianError) {
130
+ return {
131
+ content: [
132
+ {
133
+ type: 'text',
134
+ text: JSON.stringify(error.toJSON()),
135
+ },
136
+ ],
137
+ isError: true,
138
+ };
139
+ }
140
+ // Handle generic errors
93
141
  const message = error instanceof Error ? error.message : String(error);
94
142
  return {
95
143
  content: [
@@ -5,6 +5,7 @@ export interface AdoptResult {
5
5
  }
6
6
  export declare const adoptTool: {
7
7
  name: string;
8
+ title: string;
8
9
  description: string;
9
10
  inputSchema: {
10
11
  type: "object";
@@ -1,20 +1,29 @@
1
1
  import * as fs from 'fs/promises';
2
2
  import * as path from 'path';
3
3
  import matter from 'gray-matter';
4
- import { getLibraryPath, getImportedPath, getLocalPath } from '../library/storage.js';
4
+ import { getLibraryPath, getImportedPath, getLocalPath, getPackagesPath } from '../library/storage.js';
5
5
  // ============================================================================
6
6
  // Tool Definition
7
7
  // ============================================================================
8
8
  export const adoptTool = {
9
9
  name: 'adopt',
10
- description: `Make imported knowledge ours.
10
+ title: 'Adopt Imported Entry',
11
+ description: `Move an imported/packages entry to local library for editing.
11
12
 
12
- When an entry from an imported package proves useful, adopt it into our
13
- local library. It graduates from "their knowledge" to "our knowledge" -
14
- now we can edit and evolve it.
13
+ USE THIS TOOL WHEN:
14
+ - An imported entry is useful but needs customization
15
+ - Want to evolve third-party knowledge with our learnings
16
+ - Entry from packages/ or imported/ should become our own
17
+
18
+ Copies to local/ where we can edit it. Original stays in place.
19
+
20
+ TRIGGER PATTERNS:
21
+ - Entry from brief() needs edits → adopt({ path: "..." })
22
+ - "Make that entry ours" → adopt({ path: "...", title: "Our version" })
23
+ - Want to customize purchased content → adopt({ path: "packages/..." })
15
24
 
16
25
  Examples:
17
- - adopt({ path: "imported/stripe-patterns/webhook-idempotency" })
26
+ - adopt({ path: "packages/stripe-patterns/webhook-idempotency" })
18
27
  - adopt({ path: "imported/auth-patterns/token-refresh", title: "Our token refresh" })`,
19
28
  inputSchema: {
20
29
  type: 'object',
@@ -37,22 +46,40 @@ Examples:
37
46
  }
38
47
  const libraryPath = getLibraryPath();
39
48
  const importedPath = getImportedPath(libraryPath);
49
+ const packagesPath = getPackagesPath(libraryPath);
40
50
  const localPath = getLocalPath(libraryPath);
41
- // Normalize path: strip "imported/" prefix if present, add .md if missing
51
+ // Normalize path: strip "imported/" or "packages/" prefix if present, add .md if missing
42
52
  let normalizedPath = entryPath;
43
53
  if (normalizedPath.startsWith('imported/')) {
44
54
  normalizedPath = normalizedPath.slice('imported/'.length);
45
55
  }
56
+ else if (normalizedPath.startsWith('packages/')) {
57
+ normalizedPath = normalizedPath.slice('packages/'.length);
58
+ }
46
59
  if (!normalizedPath.endsWith('.md')) {
47
60
  normalizedPath += '.md';
48
61
  }
49
- const sourcePath = path.join(importedPath, normalizedPath);
50
- // Check if source exists
62
+ // Try both imported/ and packages/ paths
63
+ let sourcePath = path.resolve(importedPath, normalizedPath);
64
+ // Prevent path traversal
65
+ if (!sourcePath.startsWith(path.resolve(importedPath)) && !sourcePath.startsWith(path.resolve(packagesPath))) {
66
+ throw new Error('Invalid path: must be within imported/ or packages/');
67
+ }
51
68
  try {
52
69
  await fs.access(sourcePath);
53
70
  }
54
71
  catch {
55
- throw new Error(`Entry not found: ${entryPath}`);
72
+ // Try packages path (for library downloads)
73
+ sourcePath = path.resolve(packagesPath, normalizedPath);
74
+ if (!sourcePath.startsWith(path.resolve(packagesPath))) {
75
+ throw new Error('Invalid path: must be within imported/ or packages/');
76
+ }
77
+ try {
78
+ await fs.access(sourcePath);
79
+ }
80
+ catch {
81
+ throw new Error(`Entry not found: ${entryPath}`);
82
+ }
56
83
  }
57
84
  // Read source file
58
85
  const content = await fs.readFile(sourcePath, 'utf-8');