zotero-bridge 1.1.0 → 1.1.2
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/database.d.ts +48 -0
- package/dist/database.d.ts.map +1 -1
- package/dist/database.js +299 -0
- package/dist/database.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +336 -268
- package/dist/index.js.map +1 -1
- package/dist/pdf.d.ts.map +1 -1
- package/dist/pdf.js +3 -2
- package/dist/pdf.js.map +1 -1
- package/dist/tools.d.ts +114 -361
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +119 -326
- package/dist/tools.js.map +1 -1
- package/package.json +5 -5
- package/src/database.ts +335 -0
- package/src/index.ts +324 -346
- package/src/pdf.ts +3 -2
- package/src/tools.ts +122 -357
package/src/index.ts
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* SQLite database for collection management, tagging, PDF reading, and more.
|
|
8
8
|
*
|
|
9
9
|
* @author Combjellyshen
|
|
10
|
-
* @version 1.
|
|
10
|
+
* @version 1.1.0 (Consolidated tools)
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
@@ -22,55 +22,24 @@ import { ZoteroDatabase } from './database.js';
|
|
|
22
22
|
import { PDFProcessor } from './pdf.js';
|
|
23
23
|
import {
|
|
24
24
|
toolDefinitions,
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
createCollectionSchema,
|
|
28
|
-
renameCollectionSchema,
|
|
29
|
-
moveCollectionSchema,
|
|
30
|
-
deleteCollectionSchema,
|
|
31
|
-
getSubcollectionsSchema,
|
|
32
|
-
listTagsSchema,
|
|
33
|
-
addTagSchema,
|
|
34
|
-
removeTagSchema,
|
|
35
|
-
getItemTagsSchema,
|
|
36
|
-
createTagSchema,
|
|
25
|
+
manageCollectionSchema,
|
|
26
|
+
manageTagsSchema,
|
|
37
27
|
searchItemsSchema,
|
|
38
28
|
getItemDetailsSchema,
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
getCollectionItemsSchema,
|
|
42
|
-
getItemAbstractSchema,
|
|
43
|
-
setItemAbstractSchema,
|
|
44
|
-
getItemNotesSchema,
|
|
45
|
-
addItemNoteSchema,
|
|
46
|
-
extractPDFTextSchema,
|
|
47
|
-
getPDFSummarySchema,
|
|
48
|
-
getItemPDFsSchema,
|
|
49
|
-
searchPDFSchema,
|
|
50
|
-
generateAbstractFromPDFSchema,
|
|
51
|
-
getDatabaseInfoSchema,
|
|
52
|
-
rawQuerySchema,
|
|
53
|
-
// New schemas
|
|
54
|
-
findByDOISchema,
|
|
55
|
-
findByISBNSchema,
|
|
29
|
+
manageItemContentSchema,
|
|
30
|
+
managePDFSchema,
|
|
56
31
|
findByIdentifierSchema,
|
|
57
|
-
|
|
58
|
-
getAttachmentAnnotationsSchema,
|
|
59
|
-
getAnnotationsByTypeSchema,
|
|
60
|
-
getAnnotationsByColorSchema,
|
|
61
|
-
searchAnnotationsSchema,
|
|
32
|
+
getAnnotationsSchema,
|
|
62
33
|
searchFulltextSchema,
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
findSimilarByCreatorsSchema,
|
|
68
|
-
findSimilarByCollectionSchema
|
|
34
|
+
findRelatedItemsSchema,
|
|
35
|
+
getDatabaseInfoSchema,
|
|
36
|
+
rawQuerySchema,
|
|
37
|
+
libraryMaintenanceSchema
|
|
69
38
|
} from './tools.js';
|
|
70
39
|
|
|
71
40
|
// Server configuration
|
|
72
41
|
const SERVER_NAME = 'zotero-bridge';
|
|
73
|
-
const SERVER_VERSION = '1.
|
|
42
|
+
const SERVER_VERSION = '1.1.0';
|
|
74
43
|
|
|
75
44
|
// Parse command line arguments
|
|
76
45
|
function parseArgs(): { dbPath?: string; readonly: boolean } {
|
|
@@ -112,7 +81,7 @@ async function main() {
|
|
|
112
81
|
|
|
113
82
|
// Initialize database
|
|
114
83
|
const db = new ZoteroDatabase(dbPath, readonly);
|
|
115
|
-
await db.connect();
|
|
84
|
+
await db.connect();
|
|
116
85
|
const pdf = new PDFProcessor(db);
|
|
117
86
|
|
|
118
87
|
// Create MCP server
|
|
@@ -163,97 +132,93 @@ async function main() {
|
|
|
163
132
|
|
|
164
133
|
switch (name) {
|
|
165
134
|
// ============================================
|
|
166
|
-
// Collection
|
|
135
|
+
// Collection Management (Consolidated)
|
|
167
136
|
// ============================================
|
|
168
|
-
case '
|
|
169
|
-
const params =
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
137
|
+
case 'manage_collection': {
|
|
138
|
+
const params = manageCollectionSchema.parse(args);
|
|
139
|
+
|
|
140
|
+
switch (params.action) {
|
|
141
|
+
case 'list':
|
|
142
|
+
result = db.getCollections(params.libraryID);
|
|
143
|
+
break;
|
|
144
|
+
case 'get':
|
|
145
|
+
if (params.collectionID) {
|
|
146
|
+
result = db.getCollectionById(params.collectionID);
|
|
147
|
+
} else if (params.name) {
|
|
148
|
+
result = db.getCollectionByName(params.name, params.libraryID);
|
|
149
|
+
} else {
|
|
150
|
+
throw new Error('Either collectionID or name is required for get action');
|
|
151
|
+
}
|
|
152
|
+
break;
|
|
153
|
+
case 'create':
|
|
154
|
+
if (!params.name) throw new Error('Name is required for create action');
|
|
155
|
+
const collectionID = db.createCollection(params.name, params.parentCollectionID || null, params.libraryID);
|
|
156
|
+
result = { success: true, collectionID };
|
|
157
|
+
break;
|
|
158
|
+
case 'rename':
|
|
159
|
+
if (!params.collectionID || !params.newName) throw new Error('collectionID and newName are required');
|
|
160
|
+
result = { success: db.renameCollection(params.collectionID, params.newName) };
|
|
161
|
+
break;
|
|
162
|
+
case 'move':
|
|
163
|
+
if (!params.collectionID) throw new Error('collectionID is required');
|
|
164
|
+
result = { success: db.moveCollection(params.collectionID, params.parentCollectionID ?? null) };
|
|
165
|
+
break;
|
|
166
|
+
case 'delete':
|
|
167
|
+
if (!params.collectionID) throw new Error('collectionID is required');
|
|
168
|
+
result = { success: db.deleteCollection(params.collectionID) };
|
|
169
|
+
break;
|
|
170
|
+
case 'get_subcollections':
|
|
171
|
+
if (!params.collectionID) throw new Error('collectionID is required');
|
|
172
|
+
result = db.getSubcollections(params.collectionID);
|
|
173
|
+
break;
|
|
174
|
+
case 'add_item':
|
|
175
|
+
if (!params.itemID || !params.collectionID) throw new Error('itemID and collectionID are required');
|
|
176
|
+
result = { success: db.addItemToCollection(params.itemID, params.collectionID) };
|
|
177
|
+
break;
|
|
178
|
+
case 'remove_item':
|
|
179
|
+
if (!params.itemID || !params.collectionID) throw new Error('itemID and collectionID are required');
|
|
180
|
+
result = { success: db.removeItemFromCollection(params.itemID, params.collectionID) };
|
|
181
|
+
break;
|
|
182
|
+
case 'get_items':
|
|
183
|
+
if (!params.collectionID) throw new Error('collectionID is required');
|
|
184
|
+
result = db.getCollectionItems(params.collectionID);
|
|
185
|
+
break;
|
|
186
|
+
default:
|
|
187
|
+
throw new Error(`Unknown action: ${params.action}`);
|
|
182
188
|
}
|
|
183
189
|
break;
|
|
184
190
|
}
|
|
185
191
|
|
|
186
|
-
case 'create_collection': {
|
|
187
|
-
const params = createCollectionSchema.parse(args);
|
|
188
|
-
const collectionID = db.createCollection(
|
|
189
|
-
params.name,
|
|
190
|
-
params.parentCollectionID || null,
|
|
191
|
-
params.libraryID
|
|
192
|
-
);
|
|
193
|
-
result = { success: true, collectionID };
|
|
194
|
-
break;
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
case 'rename_collection': {
|
|
198
|
-
const params = renameCollectionSchema.parse(args);
|
|
199
|
-
const success = db.renameCollection(params.collectionID, params.newName);
|
|
200
|
-
result = { success };
|
|
201
|
-
break;
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
case 'move_collection': {
|
|
205
|
-
const params = moveCollectionSchema.parse(args);
|
|
206
|
-
const success = db.moveCollection(params.collectionID, params.newParentID);
|
|
207
|
-
result = { success };
|
|
208
|
-
break;
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
case 'delete_collection': {
|
|
212
|
-
const params = deleteCollectionSchema.parse(args);
|
|
213
|
-
const success = db.deleteCollection(params.collectionID);
|
|
214
|
-
result = { success };
|
|
215
|
-
break;
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
case 'get_subcollections': {
|
|
219
|
-
const params = getSubcollectionsSchema.parse(args);
|
|
220
|
-
result = db.getSubcollections(params.parentCollectionID);
|
|
221
|
-
break;
|
|
222
|
-
}
|
|
223
|
-
|
|
224
192
|
// ============================================
|
|
225
|
-
// Tag
|
|
193
|
+
// Tag Management (Consolidated)
|
|
226
194
|
// ============================================
|
|
227
|
-
case '
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
const params = createTagSchema.parse(args);
|
|
255
|
-
const tagID = db.createTag(params.name, params.type);
|
|
256
|
-
result = { success: true, tagID };
|
|
195
|
+
case 'manage_tags': {
|
|
196
|
+
const params = manageTagsSchema.parse(args);
|
|
197
|
+
|
|
198
|
+
switch (params.action) {
|
|
199
|
+
case 'list':
|
|
200
|
+
result = db.getTags();
|
|
201
|
+
break;
|
|
202
|
+
case 'get_item_tags':
|
|
203
|
+
if (!params.itemID) throw new Error('itemID is required');
|
|
204
|
+
result = db.getItemTags(params.itemID);
|
|
205
|
+
break;
|
|
206
|
+
case 'add':
|
|
207
|
+
if (!params.itemID || !params.tagName) throw new Error('itemID and tagName are required');
|
|
208
|
+
result = { success: db.addTagToItem(params.itemID, params.tagName, params.type) };
|
|
209
|
+
break;
|
|
210
|
+
case 'remove':
|
|
211
|
+
if (!params.itemID || !params.tagName) throw new Error('itemID and tagName are required');
|
|
212
|
+
result = { success: db.removeTagFromItem(params.itemID, params.tagName) };
|
|
213
|
+
break;
|
|
214
|
+
case 'create':
|
|
215
|
+
if (!params.tagName) throw new Error('tagName is required');
|
|
216
|
+
const tagID = db.createTag(params.tagName, params.type);
|
|
217
|
+
result = { success: true, tagID };
|
|
218
|
+
break;
|
|
219
|
+
default:
|
|
220
|
+
throw new Error(`Unknown action: ${params.action}`);
|
|
221
|
+
}
|
|
257
222
|
break;
|
|
258
223
|
}
|
|
259
224
|
|
|
@@ -272,125 +237,190 @@ async function main() {
|
|
|
272
237
|
result = db.getItemDetails(params.itemID);
|
|
273
238
|
} else if (params.itemKey) {
|
|
274
239
|
const item = db.getItemByKey(params.itemKey);
|
|
275
|
-
|
|
276
|
-
result = db.getItemDetails(item.itemID);
|
|
277
|
-
} else {
|
|
278
|
-
result = null;
|
|
279
|
-
}
|
|
240
|
+
result = item ? db.getItemDetails(item.itemID) : null;
|
|
280
241
|
} else {
|
|
281
242
|
throw new Error('Either itemID or itemKey is required');
|
|
282
243
|
}
|
|
283
244
|
break;
|
|
284
245
|
}
|
|
285
246
|
|
|
286
|
-
case 'add_item_to_collection': {
|
|
287
|
-
const params = addItemToCollectionSchema.parse(args);
|
|
288
|
-
const success = db.addItemToCollection(params.itemID, params.collectionID);
|
|
289
|
-
result = { success };
|
|
290
|
-
break;
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
case 'remove_item_from_collection': {
|
|
294
|
-
const params = removeItemFromCollectionSchema.parse(args);
|
|
295
|
-
const success = db.removeItemFromCollection(params.itemID, params.collectionID);
|
|
296
|
-
result = { success };
|
|
297
|
-
break;
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
case 'get_collection_items': {
|
|
301
|
-
const params = getCollectionItemsSchema.parse(args);
|
|
302
|
-
result = db.getCollectionItems(params.collectionID);
|
|
303
|
-
break;
|
|
304
|
-
}
|
|
305
|
-
|
|
306
247
|
// ============================================
|
|
307
|
-
// Abstract/Note
|
|
248
|
+
// Abstract/Note Management (Consolidated)
|
|
308
249
|
// ============================================
|
|
309
|
-
case '
|
|
310
|
-
const params =
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
result = { success: true, noteID };
|
|
250
|
+
case 'manage_item_content': {
|
|
251
|
+
const params = manageItemContentSchema.parse(args);
|
|
252
|
+
|
|
253
|
+
switch (params.action) {
|
|
254
|
+
case 'get_abstract':
|
|
255
|
+
result = { abstract: db.getItemAbstract(params.itemID) };
|
|
256
|
+
break;
|
|
257
|
+
case 'set_abstract':
|
|
258
|
+
if (!params.abstract) throw new Error('abstract is required');
|
|
259
|
+
result = { success: db.setItemAbstract(params.itemID, params.abstract) };
|
|
260
|
+
break;
|
|
261
|
+
case 'get_notes':
|
|
262
|
+
result = db.getItemNotes(params.itemID);
|
|
263
|
+
break;
|
|
264
|
+
case 'add_note':
|
|
265
|
+
if (!params.noteContent) throw new Error('noteContent is required');
|
|
266
|
+
const noteID = db.addItemNote(params.itemID, params.noteContent, params.noteTitle);
|
|
267
|
+
result = { success: true, noteID };
|
|
268
|
+
break;
|
|
269
|
+
default:
|
|
270
|
+
throw new Error(`Unknown action: ${params.action}`);
|
|
271
|
+
}
|
|
332
272
|
break;
|
|
333
273
|
}
|
|
334
274
|
|
|
335
275
|
// ============================================
|
|
336
|
-
// PDF
|
|
276
|
+
// PDF Management (Consolidated)
|
|
337
277
|
// ============================================
|
|
338
|
-
case '
|
|
339
|
-
const params =
|
|
340
|
-
|
|
278
|
+
case 'manage_pdf': {
|
|
279
|
+
const params = managePDFSchema.parse(args);
|
|
280
|
+
|
|
281
|
+
switch (params.action) {
|
|
282
|
+
case 'extract_text':
|
|
283
|
+
if (!params.attachmentItemID) throw new Error('attachmentItemID is required');
|
|
284
|
+
result = await pdf.extractTextFromAttachment(params.attachmentItemID);
|
|
285
|
+
break;
|
|
286
|
+
case 'get_summary':
|
|
287
|
+
if (!params.attachmentItemID) throw new Error('attachmentItemID is required');
|
|
288
|
+
result = await pdf.getPDFSummary(params.attachmentItemID);
|
|
289
|
+
break;
|
|
290
|
+
case 'list':
|
|
291
|
+
if (!params.parentItemID) throw new Error('parentItemID is required');
|
|
292
|
+
const attachments = db.getPDFAttachments(params.parentItemID);
|
|
293
|
+
result = attachments.map(att => ({
|
|
294
|
+
...att,
|
|
295
|
+
fullPath: db.getAttachmentPath(att.itemID)
|
|
296
|
+
}));
|
|
297
|
+
break;
|
|
298
|
+
case 'search':
|
|
299
|
+
if (!params.attachmentItemID || !params.query) throw new Error('attachmentItemID and query are required');
|
|
300
|
+
const content = await pdf.extractTextFromAttachment(params.attachmentItemID);
|
|
301
|
+
result = content ? pdf.searchInPDF(content, params.query, params.caseSensitive) : [];
|
|
302
|
+
break;
|
|
303
|
+
case 'generate_abstract':
|
|
304
|
+
if (!params.attachmentItemID) throw new Error('attachmentItemID is required');
|
|
305
|
+
const pdfContent = await pdf.extractTextFromAttachment(params.attachmentItemID);
|
|
306
|
+
if (!pdfContent) throw new Error('Could not extract PDF content');
|
|
307
|
+
const abstract = pdf.generateSimpleSummary(pdfContent, params.maxLength);
|
|
308
|
+
if (params.saveToItem) {
|
|
309
|
+
const attDetails = db.query(
|
|
310
|
+
'SELECT parentItemID FROM itemAttachments WHERE itemID = ?',
|
|
311
|
+
[params.attachmentItemID]
|
|
312
|
+
)[0] as { parentItemID: number } | undefined;
|
|
313
|
+
if (attDetails?.parentItemID) {
|
|
314
|
+
db.setItemAbstract(attDetails.parentItemID, abstract);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
result = { abstract, length: abstract.length };
|
|
318
|
+
break;
|
|
319
|
+
default:
|
|
320
|
+
throw new Error(`Unknown action: ${params.action}`);
|
|
321
|
+
}
|
|
341
322
|
break;
|
|
342
323
|
}
|
|
343
324
|
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
325
|
+
// ============================================
|
|
326
|
+
// Identifier Lookup (Consolidated)
|
|
327
|
+
// ============================================
|
|
328
|
+
case 'find_by_identifier': {
|
|
329
|
+
const params = findByIdentifierSchema.parse(args);
|
|
330
|
+
const identifier = params.identifier.trim();
|
|
331
|
+
let type = params.type;
|
|
332
|
+
|
|
333
|
+
// Auto-detect type
|
|
334
|
+
if (type === 'auto') {
|
|
335
|
+
if (/^10\.\d+\//.test(identifier) || /doi\.org/i.test(identifier)) {
|
|
336
|
+
type = 'doi';
|
|
337
|
+
} else if (/^(97[89])?\d{9}[\dXx]$/.test(identifier.replace(/[-\s]/g, ''))) {
|
|
338
|
+
type = 'isbn';
|
|
339
|
+
} else if (/^\d+$/.test(identifier) || /pubmed|pmid/i.test(identifier)) {
|
|
340
|
+
type = 'pmid';
|
|
341
|
+
} else if (/arxiv/i.test(identifier) || /^\d{4}\.\d{4,5}/.test(identifier)) {
|
|
342
|
+
type = 'arxiv';
|
|
343
|
+
} else if (/^https?:\/\//.test(identifier)) {
|
|
344
|
+
type = 'url';
|
|
345
|
+
} else {
|
|
346
|
+
type = 'doi'; // Default
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
result = db.findItemByIdentifier(identifier, type);
|
|
347
351
|
break;
|
|
348
352
|
}
|
|
349
353
|
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
354
|
+
// ============================================
|
|
355
|
+
// Annotations (Consolidated)
|
|
356
|
+
// ============================================
|
|
357
|
+
case 'get_annotations': {
|
|
358
|
+
const params = getAnnotationsSchema.parse(args);
|
|
359
|
+
|
|
360
|
+
if (params.searchQuery) {
|
|
361
|
+
result = db.searchAnnotations(params.searchQuery, params.itemID);
|
|
362
|
+
} else if (params.types && params.itemID) {
|
|
363
|
+
result = db.getAnnotationsByType(params.itemID, params.types);
|
|
364
|
+
} else if (params.colors && params.itemID) {
|
|
365
|
+
result = db.getAnnotationsByColor(params.itemID, params.colors);
|
|
366
|
+
} else if (params.attachmentID) {
|
|
367
|
+
result = db.getAttachmentAnnotations(params.attachmentID);
|
|
368
|
+
} else if (params.itemID) {
|
|
369
|
+
result = db.getItemAnnotations(params.itemID);
|
|
370
|
+
} else {
|
|
371
|
+
throw new Error('At least itemID, attachmentID, or searchQuery is required');
|
|
372
|
+
}
|
|
357
373
|
break;
|
|
358
374
|
}
|
|
359
375
|
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
376
|
+
// ============================================
|
|
377
|
+
// Fulltext Search (Consolidated)
|
|
378
|
+
// ============================================
|
|
379
|
+
case 'search_fulltext': {
|
|
380
|
+
const params = searchFulltextSchema.parse(args);
|
|
381
|
+
|
|
382
|
+
if (params.attachmentID && !params.query) {
|
|
383
|
+
// Get fulltext content
|
|
384
|
+
result = { content: db.getFulltextContent(params.attachmentID) };
|
|
385
|
+
} else if (params.query) {
|
|
386
|
+
// Search with context
|
|
387
|
+
result = db.searchFulltextWithContext(params.query, params.contextLength, params.libraryID);
|
|
365
388
|
} else {
|
|
366
|
-
|
|
389
|
+
throw new Error('Either query or attachmentID is required');
|
|
367
390
|
}
|
|
368
391
|
break;
|
|
369
392
|
}
|
|
370
393
|
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
394
|
+
// ============================================
|
|
395
|
+
// Related Items (Consolidated)
|
|
396
|
+
// ============================================
|
|
397
|
+
case 'find_related_items': {
|
|
398
|
+
const params = findRelatedItemsSchema.parse(args);
|
|
374
399
|
|
|
375
|
-
|
|
376
|
-
|
|
400
|
+
switch (params.method) {
|
|
401
|
+
case 'manual':
|
|
402
|
+
result = db.getRelatedItems(params.itemID);
|
|
403
|
+
break;
|
|
404
|
+
case 'tags':
|
|
405
|
+
result = db.findSimilarByTags(params.itemID, params.minSharedTags);
|
|
406
|
+
break;
|
|
407
|
+
case 'creators':
|
|
408
|
+
result = db.findSimilarByCreators(params.itemID);
|
|
409
|
+
break;
|
|
410
|
+
case 'collection':
|
|
411
|
+
result = db.findSimilarByCollection(params.itemID);
|
|
412
|
+
break;
|
|
413
|
+
case 'all':
|
|
414
|
+
result = {
|
|
415
|
+
manual: db.getRelatedItems(params.itemID),
|
|
416
|
+
byTags: db.findSimilarByTags(params.itemID, params.minSharedTags),
|
|
417
|
+
byCreators: db.findSimilarByCreators(params.itemID),
|
|
418
|
+
byCollection: db.findSimilarByCollection(params.itemID)
|
|
419
|
+
};
|
|
420
|
+
break;
|
|
421
|
+
default:
|
|
422
|
+
throw new Error(`Unknown method: ${params.method}`);
|
|
377
423
|
}
|
|
378
|
-
|
|
379
|
-
const abstract = pdf.generateSimpleSummary(content, params.maxLength);
|
|
380
|
-
|
|
381
|
-
if (params.saveToItem) {
|
|
382
|
-
// Get parent item ID from attachment
|
|
383
|
-
const attDetails = db.query(
|
|
384
|
-
'SELECT parentItemID FROM itemAttachments WHERE itemID = ?',
|
|
385
|
-
[params.attachmentItemID]
|
|
386
|
-
)[0] as { parentItemID: number } | undefined;
|
|
387
|
-
|
|
388
|
-
if (attDetails?.parentItemID) {
|
|
389
|
-
db.setItemAbstract(attDetails.parentItemID, abstract);
|
|
390
|
-
}
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
result = { abstract, length: abstract.length };
|
|
394
424
|
break;
|
|
395
425
|
}
|
|
396
426
|
|
|
@@ -411,115 +441,49 @@ async function main() {
|
|
|
411
441
|
|
|
412
442
|
case 'raw_query': {
|
|
413
443
|
const params = rawQuerySchema.parse(args);
|
|
414
|
-
|
|
415
|
-
// Security check - only allow SELECT queries
|
|
416
444
|
if (!params.sql.trim().toUpperCase().startsWith('SELECT')) {
|
|
417
445
|
throw new Error('Only SELECT queries are allowed');
|
|
418
446
|
}
|
|
419
|
-
|
|
420
447
|
result = db.query(params.sql, params.params);
|
|
421
448
|
break;
|
|
422
449
|
}
|
|
423
450
|
|
|
424
451
|
// ============================================
|
|
425
|
-
//
|
|
426
|
-
// ============================================
|
|
427
|
-
case 'find_by_doi': {
|
|
428
|
-
const params = findByDOISchema.parse(args);
|
|
429
|
-
result = db.findItemByDOI(params.doi);
|
|
430
|
-
break;
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
case 'find_by_isbn': {
|
|
434
|
-
const params = findByISBNSchema.parse(args);
|
|
435
|
-
result = db.findItemByISBN(params.isbn);
|
|
436
|
-
break;
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
case 'find_by_identifier': {
|
|
440
|
-
const params = findByIdentifierSchema.parse(args);
|
|
441
|
-
result = db.findItemByIdentifier(params.identifier, params.type);
|
|
442
|
-
break;
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
// ============================================
|
|
446
|
-
// Annotation Tools
|
|
452
|
+
// Library Maintenance (Consolidated)
|
|
447
453
|
// ============================================
|
|
448
|
-
case '
|
|
449
|
-
const params =
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
case 'search_fulltext': {
|
|
482
|
-
const params = searchFulltextSchema.parse(args);
|
|
483
|
-
result = db.searchFulltext(params.query, params.libraryID);
|
|
484
|
-
break;
|
|
485
|
-
}
|
|
486
|
-
|
|
487
|
-
case 'get_fulltext_content': {
|
|
488
|
-
const params = getFulltextContentSchema.parse(args);
|
|
489
|
-
result = { content: db.getFulltextContent(params.attachmentID) };
|
|
490
|
-
break;
|
|
491
|
-
}
|
|
492
|
-
|
|
493
|
-
case 'search_fulltext_with_context': {
|
|
494
|
-
const params = searchFulltextWithContextSchema.parse(args);
|
|
495
|
-
result = db.searchFulltextWithContext(params.query, params.contextLength, params.libraryID);
|
|
496
|
-
break;
|
|
497
|
-
}
|
|
498
|
-
|
|
499
|
-
// ============================================
|
|
500
|
-
// Related/Similar Items Tools
|
|
501
|
-
// ============================================
|
|
502
|
-
case 'get_related_items': {
|
|
503
|
-
const params = getRelatedItemsSchema.parse(args);
|
|
504
|
-
result = db.getRelatedItems(params.itemID);
|
|
505
|
-
break;
|
|
506
|
-
}
|
|
507
|
-
|
|
508
|
-
case 'find_similar_by_tags': {
|
|
509
|
-
const params = findSimilarByTagsSchema.parse(args);
|
|
510
|
-
result = db.findSimilarByTags(params.itemID, params.minSharedTags);
|
|
511
|
-
break;
|
|
512
|
-
}
|
|
513
|
-
|
|
514
|
-
case 'find_similar_by_creators': {
|
|
515
|
-
const params = findSimilarByCreatorsSchema.parse(args);
|
|
516
|
-
result = db.findSimilarByCreators(params.itemID);
|
|
517
|
-
break;
|
|
518
|
-
}
|
|
519
|
-
|
|
520
|
-
case 'find_similar_by_collection': {
|
|
521
|
-
const params = findSimilarByCollectionSchema.parse(args);
|
|
522
|
-
result = db.findSimilarByCollection(params.itemID);
|
|
454
|
+
case 'library_maintenance': {
|
|
455
|
+
const params = libraryMaintenanceSchema.parse(args);
|
|
456
|
+
|
|
457
|
+
switch (params.action) {
|
|
458
|
+
case 'find_duplicates':
|
|
459
|
+
result = db.findDuplicates(params.duplicateField, params.libraryID);
|
|
460
|
+
break;
|
|
461
|
+
case 'validate_attachments':
|
|
462
|
+
result = db.validateAttachments(params.itemID, params.checkAll);
|
|
463
|
+
break;
|
|
464
|
+
case 'get_valid_attachment':
|
|
465
|
+
if (!params.parentItemID) throw new Error('parentItemID is required');
|
|
466
|
+
result = db.getValidAttachment(params.parentItemID, params.contentType);
|
|
467
|
+
break;
|
|
468
|
+
case 'find_with_valid_pdf':
|
|
469
|
+
result = db.findItemsWithValidPDF({
|
|
470
|
+
title: params.title,
|
|
471
|
+
doi: params.doi,
|
|
472
|
+
requireValidPDF: params.requireValidPDF
|
|
473
|
+
});
|
|
474
|
+
break;
|
|
475
|
+
case 'cleanup_orphans':
|
|
476
|
+
result = db.deleteOrphanAttachments(params.dryRun);
|
|
477
|
+
break;
|
|
478
|
+
case 'merge_items':
|
|
479
|
+
if (!params.targetItemID || !params.sourceItemIDs) {
|
|
480
|
+
throw new Error('targetItemID and sourceItemIDs are required');
|
|
481
|
+
}
|
|
482
|
+
result = db.mergeItems(params.targetItemID, params.sourceItemIDs);
|
|
483
|
+
break;
|
|
484
|
+
default:
|
|
485
|
+
throw new Error(`Unknown action: ${params.action}`);
|
|
486
|
+
}
|
|
523
487
|
break;
|
|
524
488
|
}
|
|
525
489
|
|
|
@@ -569,54 +533,68 @@ async function main() {
|
|
|
569
533
|
console.error(`Database: ${db.getPath()}`);
|
|
570
534
|
}
|
|
571
535
|
|
|
572
|
-
// Helper function to convert Zod type to JSON Schema
|
|
573
|
-
function zodToJsonSchema(zodType:
|
|
574
|
-
//
|
|
575
|
-
|
|
576
|
-
|
|
536
|
+
// Helper function to convert Zod type to JSON Schema (Zod v4 compatible)
|
|
537
|
+
function zodToJsonSchema(zodType: any): Record<string, any> {
|
|
538
|
+
// Get the type name from _zod property or check type directly
|
|
539
|
+
const typeName = zodType?._zod?.def?.type || zodType?.constructor?.name || '';
|
|
540
|
+
|
|
541
|
+
// Unwrap optional types
|
|
542
|
+
if (typeName === 'optional' || zodType instanceof z.ZodOptional) {
|
|
543
|
+
const inner = zodType._zod?.def?.innerType || zodType._def?.innerType;
|
|
544
|
+
if (inner) return zodToJsonSchema(inner);
|
|
577
545
|
}
|
|
578
|
-
|
|
579
|
-
|
|
546
|
+
|
|
547
|
+
// Unwrap default types
|
|
548
|
+
if (typeName === 'default' || zodType instanceof z.ZodDefault) {
|
|
549
|
+
const inner = zodType._zod?.def?.innerType || zodType._def?.innerType;
|
|
550
|
+
if (inner) return zodToJsonSchema(inner);
|
|
580
551
|
}
|
|
581
|
-
|
|
582
|
-
|
|
552
|
+
|
|
553
|
+
// Unwrap nullable types
|
|
554
|
+
if (typeName === 'nullable' || zodType instanceof z.ZodNullable) {
|
|
555
|
+
const inner = zodType._zod?.def?.innerType || zodType._def?.innerType;
|
|
556
|
+
if (inner) return zodToJsonSchema(inner);
|
|
583
557
|
}
|
|
584
558
|
|
|
585
559
|
// Handle basic types
|
|
586
|
-
if (zodType instanceof z.ZodString) {
|
|
560
|
+
if (typeName === 'string' || zodType instanceof z.ZodString) {
|
|
587
561
|
return { type: 'string' };
|
|
588
562
|
}
|
|
589
|
-
if (zodType instanceof z.ZodNumber) {
|
|
563
|
+
if (typeName === 'number' || zodType instanceof z.ZodNumber) {
|
|
590
564
|
return { type: 'number' };
|
|
591
565
|
}
|
|
592
|
-
if (zodType instanceof z.ZodBoolean) {
|
|
566
|
+
if (typeName === 'boolean' || zodType instanceof z.ZodBoolean) {
|
|
593
567
|
return { type: 'boolean' };
|
|
594
568
|
}
|
|
595
569
|
|
|
596
|
-
// Handle
|
|
597
|
-
if (zodType instanceof z.
|
|
598
|
-
const
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
570
|
+
// Handle enum types (Zod v4 uses 'entries' instead of 'values')
|
|
571
|
+
if (typeName === 'enum' || zodType instanceof z.ZodEnum) {
|
|
572
|
+
const enumValues = zodType._zod?.def?.entries
|
|
573
|
+
|| zodType._def?.values
|
|
574
|
+
|| Object.keys(zodType._zod?.def?.entries || {});
|
|
575
|
+
return { type: 'string', enum: Array.isArray(enumValues) ? enumValues : Object.keys(enumValues) };
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
// Handle array types
|
|
579
|
+
if (typeName === 'array' || zodType instanceof z.ZodArray) {
|
|
580
|
+
const elementType = zodType._zod?.def?.element || zodType._def?.type;
|
|
581
|
+
if (elementType) {
|
|
582
|
+
const itemSchema = zodToJsonSchema(elementType);
|
|
583
|
+
if (Object.keys(itemSchema).length === 0) {
|
|
584
|
+
return { type: 'array', items: { type: 'string' } };
|
|
585
|
+
}
|
|
586
|
+
return { type: 'array', items: itemSchema };
|
|
606
587
|
}
|
|
607
|
-
return {
|
|
608
|
-
type: 'array',
|
|
609
|
-
items: itemSchema
|
|
610
|
-
};
|
|
588
|
+
return { type: 'array', items: { type: 'string' } };
|
|
611
589
|
}
|
|
612
590
|
|
|
613
591
|
// Handle object types
|
|
614
|
-
if (zodType instanceof z.ZodObject) {
|
|
592
|
+
if (typeName === 'object' || zodType instanceof z.ZodObject) {
|
|
615
593
|
return { type: 'object' };
|
|
616
594
|
}
|
|
617
595
|
|
|
618
|
-
// Handle ZodAny
|
|
619
|
-
if (zodType instanceof z.ZodAny) {
|
|
596
|
+
// Handle ZodAny
|
|
597
|
+
if (typeName === 'any' || zodType instanceof z.ZodAny) {
|
|
620
598
|
return {};
|
|
621
599
|
}
|
|
622
600
|
|