zotero-bridge 1.1.0 → 1.1.1
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 +303 -251
- package/dist/index.js.map +1 -1
- package/dist/tools.d.ts +123 -284
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +119 -326
- package/dist/tools.js.map +1 -1
- package/package.json +2 -2
- package/src/database.ts +335 -0
- package/src/index.ts +290 -328
- package/src/tools.ts +122 -357
package/dist/index.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* SQLite database for collection management, tagging, PDF reading, and more.
|
|
7
7
|
*
|
|
8
8
|
* @author Combjellyshen
|
|
9
|
-
* @version 1.
|
|
9
|
+
* @version 1.1.0 (Consolidated tools)
|
|
10
10
|
*/
|
|
11
11
|
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
12
12
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
@@ -14,12 +14,10 @@ import { CallToolRequestSchema, ListToolsRequestSchema, } from '@modelcontextpro
|
|
|
14
14
|
import { z } from 'zod';
|
|
15
15
|
import { ZoteroDatabase } from './database.js';
|
|
16
16
|
import { PDFProcessor } from './pdf.js';
|
|
17
|
-
import { toolDefinitions,
|
|
18
|
-
// New schemas
|
|
19
|
-
findByDOISchema, findByISBNSchema, findByIdentifierSchema, getItemAnnotationsSchema, getAttachmentAnnotationsSchema, getAnnotationsByTypeSchema, getAnnotationsByColorSchema, searchAnnotationsSchema, searchFulltextSchema, getFulltextContentSchema, searchFulltextWithContextSchema, getRelatedItemsSchema, findSimilarByTagsSchema, findSimilarByCreatorsSchema, findSimilarByCollectionSchema } from './tools.js';
|
|
17
|
+
import { toolDefinitions, manageCollectionSchema, manageTagsSchema, searchItemsSchema, getItemDetailsSchema, manageItemContentSchema, managePDFSchema, findByIdentifierSchema, getAnnotationsSchema, searchFulltextSchema, findRelatedItemsSchema, getDatabaseInfoSchema, rawQuerySchema, libraryMaintenanceSchema } from './tools.js';
|
|
20
18
|
// Server configuration
|
|
21
19
|
const SERVER_NAME = 'zotero-bridge';
|
|
22
|
-
const SERVER_VERSION = '1.
|
|
20
|
+
const SERVER_VERSION = '1.1.0';
|
|
23
21
|
// Parse command line arguments
|
|
24
22
|
function parseArgs() {
|
|
25
23
|
const args = process.argv.slice(2);
|
|
@@ -58,7 +56,7 @@ async function main() {
|
|
|
58
56
|
const { dbPath, readonly } = parseArgs();
|
|
59
57
|
// Initialize database
|
|
60
58
|
const db = new ZoteroDatabase(dbPath, readonly);
|
|
61
|
-
await db.connect();
|
|
59
|
+
await db.connect();
|
|
62
60
|
const pdf = new PDFProcessor(db);
|
|
63
61
|
// Create MCP server
|
|
64
62
|
const server = new Server({
|
|
@@ -99,84 +97,104 @@ async function main() {
|
|
|
99
97
|
let result;
|
|
100
98
|
switch (name) {
|
|
101
99
|
// ============================================
|
|
102
|
-
// Collection
|
|
100
|
+
// Collection Management (Consolidated)
|
|
103
101
|
// ============================================
|
|
104
|
-
case '
|
|
105
|
-
const params =
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
102
|
+
case 'manage_collection': {
|
|
103
|
+
const params = manageCollectionSchema.parse(args);
|
|
104
|
+
switch (params.action) {
|
|
105
|
+
case 'list':
|
|
106
|
+
result = db.getCollections(params.libraryID);
|
|
107
|
+
break;
|
|
108
|
+
case 'get':
|
|
109
|
+
if (params.collectionID) {
|
|
110
|
+
result = db.getCollectionById(params.collectionID);
|
|
111
|
+
}
|
|
112
|
+
else if (params.name) {
|
|
113
|
+
result = db.getCollectionByName(params.name, params.libraryID);
|
|
114
|
+
}
|
|
115
|
+
else {
|
|
116
|
+
throw new Error('Either collectionID or name is required for get action');
|
|
117
|
+
}
|
|
118
|
+
break;
|
|
119
|
+
case 'create':
|
|
120
|
+
if (!params.name)
|
|
121
|
+
throw new Error('Name is required for create action');
|
|
122
|
+
const collectionID = db.createCollection(params.name, params.parentCollectionID || null, params.libraryID);
|
|
123
|
+
result = { success: true, collectionID };
|
|
124
|
+
break;
|
|
125
|
+
case 'rename':
|
|
126
|
+
if (!params.collectionID || !params.newName)
|
|
127
|
+
throw new Error('collectionID and newName are required');
|
|
128
|
+
result = { success: db.renameCollection(params.collectionID, params.newName) };
|
|
129
|
+
break;
|
|
130
|
+
case 'move':
|
|
131
|
+
if (!params.collectionID)
|
|
132
|
+
throw new Error('collectionID is required');
|
|
133
|
+
result = { success: db.moveCollection(params.collectionID, params.parentCollectionID ?? null) };
|
|
134
|
+
break;
|
|
135
|
+
case 'delete':
|
|
136
|
+
if (!params.collectionID)
|
|
137
|
+
throw new Error('collectionID is required');
|
|
138
|
+
result = { success: db.deleteCollection(params.collectionID) };
|
|
139
|
+
break;
|
|
140
|
+
case 'get_subcollections':
|
|
141
|
+
if (!params.collectionID)
|
|
142
|
+
throw new Error('collectionID is required');
|
|
143
|
+
result = db.getSubcollections(params.collectionID);
|
|
144
|
+
break;
|
|
145
|
+
case 'add_item':
|
|
146
|
+
if (!params.itemID || !params.collectionID)
|
|
147
|
+
throw new Error('itemID and collectionID are required');
|
|
148
|
+
result = { success: db.addItemToCollection(params.itemID, params.collectionID) };
|
|
149
|
+
break;
|
|
150
|
+
case 'remove_item':
|
|
151
|
+
if (!params.itemID || !params.collectionID)
|
|
152
|
+
throw new Error('itemID and collectionID are required');
|
|
153
|
+
result = { success: db.removeItemFromCollection(params.itemID, params.collectionID) };
|
|
154
|
+
break;
|
|
155
|
+
case 'get_items':
|
|
156
|
+
if (!params.collectionID)
|
|
157
|
+
throw new Error('collectionID is required');
|
|
158
|
+
result = db.getCollectionItems(params.collectionID);
|
|
159
|
+
break;
|
|
160
|
+
default:
|
|
161
|
+
throw new Error(`Unknown action: ${params.action}`);
|
|
119
162
|
}
|
|
120
163
|
break;
|
|
121
164
|
}
|
|
122
|
-
case 'create_collection': {
|
|
123
|
-
const params = createCollectionSchema.parse(args);
|
|
124
|
-
const collectionID = db.createCollection(params.name, params.parentCollectionID || null, params.libraryID);
|
|
125
|
-
result = { success: true, collectionID };
|
|
126
|
-
break;
|
|
127
|
-
}
|
|
128
|
-
case 'rename_collection': {
|
|
129
|
-
const params = renameCollectionSchema.parse(args);
|
|
130
|
-
const success = db.renameCollection(params.collectionID, params.newName);
|
|
131
|
-
result = { success };
|
|
132
|
-
break;
|
|
133
|
-
}
|
|
134
|
-
case 'move_collection': {
|
|
135
|
-
const params = moveCollectionSchema.parse(args);
|
|
136
|
-
const success = db.moveCollection(params.collectionID, params.newParentID);
|
|
137
|
-
result = { success };
|
|
138
|
-
break;
|
|
139
|
-
}
|
|
140
|
-
case 'delete_collection': {
|
|
141
|
-
const params = deleteCollectionSchema.parse(args);
|
|
142
|
-
const success = db.deleteCollection(params.collectionID);
|
|
143
|
-
result = { success };
|
|
144
|
-
break;
|
|
145
|
-
}
|
|
146
|
-
case 'get_subcollections': {
|
|
147
|
-
const params = getSubcollectionsSchema.parse(args);
|
|
148
|
-
result = db.getSubcollections(params.parentCollectionID);
|
|
149
|
-
break;
|
|
150
|
-
}
|
|
151
165
|
// ============================================
|
|
152
|
-
// Tag
|
|
166
|
+
// Tag Management (Consolidated)
|
|
153
167
|
// ============================================
|
|
154
|
-
case '
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
168
|
+
case 'manage_tags': {
|
|
169
|
+
const params = manageTagsSchema.parse(args);
|
|
170
|
+
switch (params.action) {
|
|
171
|
+
case 'list':
|
|
172
|
+
result = db.getTags();
|
|
173
|
+
break;
|
|
174
|
+
case 'get_item_tags':
|
|
175
|
+
if (!params.itemID)
|
|
176
|
+
throw new Error('itemID is required');
|
|
177
|
+
result = db.getItemTags(params.itemID);
|
|
178
|
+
break;
|
|
179
|
+
case 'add':
|
|
180
|
+
if (!params.itemID || !params.tagName)
|
|
181
|
+
throw new Error('itemID and tagName are required');
|
|
182
|
+
result = { success: db.addTagToItem(params.itemID, params.tagName, params.type) };
|
|
183
|
+
break;
|
|
184
|
+
case 'remove':
|
|
185
|
+
if (!params.itemID || !params.tagName)
|
|
186
|
+
throw new Error('itemID and tagName are required');
|
|
187
|
+
result = { success: db.removeTagFromItem(params.itemID, params.tagName) };
|
|
188
|
+
break;
|
|
189
|
+
case 'create':
|
|
190
|
+
if (!params.tagName)
|
|
191
|
+
throw new Error('tagName is required');
|
|
192
|
+
const tagID = db.createTag(params.tagName, params.type);
|
|
193
|
+
result = { success: true, tagID };
|
|
194
|
+
break;
|
|
195
|
+
default:
|
|
196
|
+
throw new Error(`Unknown action: ${params.action}`);
|
|
197
|
+
}
|
|
180
198
|
break;
|
|
181
199
|
}
|
|
182
200
|
// ============================================
|
|
@@ -194,108 +212,195 @@ async function main() {
|
|
|
194
212
|
}
|
|
195
213
|
else if (params.itemKey) {
|
|
196
214
|
const item = db.getItemByKey(params.itemKey);
|
|
197
|
-
|
|
198
|
-
result = db.getItemDetails(item.itemID);
|
|
199
|
-
}
|
|
200
|
-
else {
|
|
201
|
-
result = null;
|
|
202
|
-
}
|
|
215
|
+
result = item ? db.getItemDetails(item.itemID) : null;
|
|
203
216
|
}
|
|
204
217
|
else {
|
|
205
218
|
throw new Error('Either itemID or itemKey is required');
|
|
206
219
|
}
|
|
207
220
|
break;
|
|
208
221
|
}
|
|
209
|
-
case 'add_item_to_collection': {
|
|
210
|
-
const params = addItemToCollectionSchema.parse(args);
|
|
211
|
-
const success = db.addItemToCollection(params.itemID, params.collectionID);
|
|
212
|
-
result = { success };
|
|
213
|
-
break;
|
|
214
|
-
}
|
|
215
|
-
case 'remove_item_from_collection': {
|
|
216
|
-
const params = removeItemFromCollectionSchema.parse(args);
|
|
217
|
-
const success = db.removeItemFromCollection(params.itemID, params.collectionID);
|
|
218
|
-
result = { success };
|
|
219
|
-
break;
|
|
220
|
-
}
|
|
221
|
-
case 'get_collection_items': {
|
|
222
|
-
const params = getCollectionItemsSchema.parse(args);
|
|
223
|
-
result = db.getCollectionItems(params.collectionID);
|
|
224
|
-
break;
|
|
225
|
-
}
|
|
226
222
|
// ============================================
|
|
227
|
-
// Abstract/Note
|
|
223
|
+
// Abstract/Note Management (Consolidated)
|
|
228
224
|
// ============================================
|
|
229
|
-
case '
|
|
230
|
-
const params =
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
225
|
+
case 'manage_item_content': {
|
|
226
|
+
const params = manageItemContentSchema.parse(args);
|
|
227
|
+
switch (params.action) {
|
|
228
|
+
case 'get_abstract':
|
|
229
|
+
result = { abstract: db.getItemAbstract(params.itemID) };
|
|
230
|
+
break;
|
|
231
|
+
case 'set_abstract':
|
|
232
|
+
if (!params.abstract)
|
|
233
|
+
throw new Error('abstract is required');
|
|
234
|
+
result = { success: db.setItemAbstract(params.itemID, params.abstract) };
|
|
235
|
+
break;
|
|
236
|
+
case 'get_notes':
|
|
237
|
+
result = db.getItemNotes(params.itemID);
|
|
238
|
+
break;
|
|
239
|
+
case 'add_note':
|
|
240
|
+
if (!params.noteContent)
|
|
241
|
+
throw new Error('noteContent is required');
|
|
242
|
+
const noteID = db.addItemNote(params.itemID, params.noteContent, params.noteTitle);
|
|
243
|
+
result = { success: true, noteID };
|
|
244
|
+
break;
|
|
245
|
+
default:
|
|
246
|
+
throw new Error(`Unknown action: ${params.action}`);
|
|
247
|
+
}
|
|
249
248
|
break;
|
|
250
249
|
}
|
|
251
250
|
// ============================================
|
|
252
|
-
// PDF
|
|
251
|
+
// PDF Management (Consolidated)
|
|
253
252
|
// ============================================
|
|
254
|
-
case '
|
|
255
|
-
const params =
|
|
256
|
-
|
|
253
|
+
case 'manage_pdf': {
|
|
254
|
+
const params = managePDFSchema.parse(args);
|
|
255
|
+
switch (params.action) {
|
|
256
|
+
case 'extract_text':
|
|
257
|
+
if (!params.attachmentItemID)
|
|
258
|
+
throw new Error('attachmentItemID is required');
|
|
259
|
+
result = await pdf.extractTextFromAttachment(params.attachmentItemID);
|
|
260
|
+
break;
|
|
261
|
+
case 'get_summary':
|
|
262
|
+
if (!params.attachmentItemID)
|
|
263
|
+
throw new Error('attachmentItemID is required');
|
|
264
|
+
result = await pdf.getPDFSummary(params.attachmentItemID);
|
|
265
|
+
break;
|
|
266
|
+
case 'list':
|
|
267
|
+
if (!params.parentItemID)
|
|
268
|
+
throw new Error('parentItemID is required');
|
|
269
|
+
const attachments = db.getPDFAttachments(params.parentItemID);
|
|
270
|
+
result = attachments.map(att => ({
|
|
271
|
+
...att,
|
|
272
|
+
fullPath: db.getAttachmentPath(att.itemID)
|
|
273
|
+
}));
|
|
274
|
+
break;
|
|
275
|
+
case 'search':
|
|
276
|
+
if (!params.attachmentItemID || !params.query)
|
|
277
|
+
throw new Error('attachmentItemID and query are required');
|
|
278
|
+
const content = await pdf.extractTextFromAttachment(params.attachmentItemID);
|
|
279
|
+
result = content ? pdf.searchInPDF(content, params.query, params.caseSensitive) : [];
|
|
280
|
+
break;
|
|
281
|
+
case 'generate_abstract':
|
|
282
|
+
if (!params.attachmentItemID)
|
|
283
|
+
throw new Error('attachmentItemID is required');
|
|
284
|
+
const pdfContent = await pdf.extractTextFromAttachment(params.attachmentItemID);
|
|
285
|
+
if (!pdfContent)
|
|
286
|
+
throw new Error('Could not extract PDF content');
|
|
287
|
+
const abstract = pdf.generateSimpleSummary(pdfContent, params.maxLength);
|
|
288
|
+
if (params.saveToItem) {
|
|
289
|
+
const attDetails = db.query('SELECT parentItemID FROM itemAttachments WHERE itemID = ?', [params.attachmentItemID])[0];
|
|
290
|
+
if (attDetails?.parentItemID) {
|
|
291
|
+
db.setItemAbstract(attDetails.parentItemID, abstract);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
result = { abstract, length: abstract.length };
|
|
295
|
+
break;
|
|
296
|
+
default:
|
|
297
|
+
throw new Error(`Unknown action: ${params.action}`);
|
|
298
|
+
}
|
|
257
299
|
break;
|
|
258
300
|
}
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
301
|
+
// ============================================
|
|
302
|
+
// Identifier Lookup (Consolidated)
|
|
303
|
+
// ============================================
|
|
304
|
+
case 'find_by_identifier': {
|
|
305
|
+
const params = findByIdentifierSchema.parse(args);
|
|
306
|
+
const identifier = params.identifier.trim();
|
|
307
|
+
let type = params.type;
|
|
308
|
+
// Auto-detect type
|
|
309
|
+
if (type === 'auto') {
|
|
310
|
+
if (/^10\.\d+\//.test(identifier) || /doi\.org/i.test(identifier)) {
|
|
311
|
+
type = 'doi';
|
|
312
|
+
}
|
|
313
|
+
else if (/^(97[89])?\d{9}[\dXx]$/.test(identifier.replace(/[-\s]/g, ''))) {
|
|
314
|
+
type = 'isbn';
|
|
315
|
+
}
|
|
316
|
+
else if (/^\d+$/.test(identifier) || /pubmed|pmid/i.test(identifier)) {
|
|
317
|
+
type = 'pmid';
|
|
318
|
+
}
|
|
319
|
+
else if (/arxiv/i.test(identifier) || /^\d{4}\.\d{4,5}/.test(identifier)) {
|
|
320
|
+
type = 'arxiv';
|
|
321
|
+
}
|
|
322
|
+
else if (/^https?:\/\//.test(identifier)) {
|
|
323
|
+
type = 'url';
|
|
324
|
+
}
|
|
325
|
+
else {
|
|
326
|
+
type = 'doi'; // Default
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
result = db.findItemByIdentifier(identifier, type);
|
|
262
330
|
break;
|
|
263
331
|
}
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
332
|
+
// ============================================
|
|
333
|
+
// Annotations (Consolidated)
|
|
334
|
+
// ============================================
|
|
335
|
+
case 'get_annotations': {
|
|
336
|
+
const params = getAnnotationsSchema.parse(args);
|
|
337
|
+
if (params.searchQuery) {
|
|
338
|
+
result = db.searchAnnotations(params.searchQuery, params.itemID);
|
|
339
|
+
}
|
|
340
|
+
else if (params.types && params.itemID) {
|
|
341
|
+
result = db.getAnnotationsByType(params.itemID, params.types);
|
|
342
|
+
}
|
|
343
|
+
else if (params.colors && params.itemID) {
|
|
344
|
+
result = db.getAnnotationsByColor(params.itemID, params.colors);
|
|
345
|
+
}
|
|
346
|
+
else if (params.attachmentID) {
|
|
347
|
+
result = db.getAttachmentAnnotations(params.attachmentID);
|
|
348
|
+
}
|
|
349
|
+
else if (params.itemID) {
|
|
350
|
+
result = db.getItemAnnotations(params.itemID);
|
|
351
|
+
}
|
|
352
|
+
else {
|
|
353
|
+
throw new Error('At least itemID, attachmentID, or searchQuery is required');
|
|
354
|
+
}
|
|
271
355
|
break;
|
|
272
356
|
}
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
357
|
+
// ============================================
|
|
358
|
+
// Fulltext Search (Consolidated)
|
|
359
|
+
// ============================================
|
|
360
|
+
case 'search_fulltext': {
|
|
361
|
+
const params = searchFulltextSchema.parse(args);
|
|
362
|
+
if (params.attachmentID && !params.query) {
|
|
363
|
+
// Get fulltext content
|
|
364
|
+
result = { content: db.getFulltextContent(params.attachmentID) };
|
|
365
|
+
}
|
|
366
|
+
else if (params.query) {
|
|
367
|
+
// Search with context
|
|
368
|
+
result = db.searchFulltextWithContext(params.query, params.contextLength, params.libraryID);
|
|
278
369
|
}
|
|
279
370
|
else {
|
|
280
|
-
|
|
371
|
+
throw new Error('Either query or attachmentID is required');
|
|
281
372
|
}
|
|
282
373
|
break;
|
|
283
374
|
}
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
375
|
+
// ============================================
|
|
376
|
+
// Related Items (Consolidated)
|
|
377
|
+
// ============================================
|
|
378
|
+
case 'find_related_items': {
|
|
379
|
+
const params = findRelatedItemsSchema.parse(args);
|
|
380
|
+
switch (params.method) {
|
|
381
|
+
case 'manual':
|
|
382
|
+
result = db.getRelatedItems(params.itemID);
|
|
383
|
+
break;
|
|
384
|
+
case 'tags':
|
|
385
|
+
result = db.findSimilarByTags(params.itemID, params.minSharedTags);
|
|
386
|
+
break;
|
|
387
|
+
case 'creators':
|
|
388
|
+
result = db.findSimilarByCreators(params.itemID);
|
|
389
|
+
break;
|
|
390
|
+
case 'collection':
|
|
391
|
+
result = db.findSimilarByCollection(params.itemID);
|
|
392
|
+
break;
|
|
393
|
+
case 'all':
|
|
394
|
+
result = {
|
|
395
|
+
manual: db.getRelatedItems(params.itemID),
|
|
396
|
+
byTags: db.findSimilarByTags(params.itemID, params.minSharedTags),
|
|
397
|
+
byCreators: db.findSimilarByCreators(params.itemID),
|
|
398
|
+
byCollection: db.findSimilarByCollection(params.itemID)
|
|
399
|
+
};
|
|
400
|
+
break;
|
|
401
|
+
default:
|
|
402
|
+
throw new Error(`Unknown method: ${params.method}`);
|
|
297
403
|
}
|
|
298
|
-
result = { abstract, length: abstract.length };
|
|
299
404
|
break;
|
|
300
405
|
}
|
|
301
406
|
// ============================================
|
|
@@ -314,7 +419,6 @@ async function main() {
|
|
|
314
419
|
}
|
|
315
420
|
case 'raw_query': {
|
|
316
421
|
const params = rawQuerySchema.parse(args);
|
|
317
|
-
// Security check - only allow SELECT queries
|
|
318
422
|
if (!params.sql.trim().toUpperCase().startsWith('SELECT')) {
|
|
319
423
|
throw new Error('Only SELECT queries are allowed');
|
|
320
424
|
}
|
|
@@ -322,90 +426,41 @@ async function main() {
|
|
|
322
426
|
break;
|
|
323
427
|
}
|
|
324
428
|
// ============================================
|
|
325
|
-
//
|
|
429
|
+
// Library Maintenance (Consolidated)
|
|
326
430
|
// ============================================
|
|
327
|
-
case '
|
|
328
|
-
const params =
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
case 'get_annotations_by_color': {
|
|
361
|
-
const params = getAnnotationsByColorSchema.parse(args);
|
|
362
|
-
result = db.getAnnotationsByColor(params.itemID, params.colors);
|
|
363
|
-
break;
|
|
364
|
-
}
|
|
365
|
-
case 'search_annotations': {
|
|
366
|
-
const params = searchAnnotationsSchema.parse(args);
|
|
367
|
-
result = db.searchAnnotations(params.query, params.itemID);
|
|
368
|
-
break;
|
|
369
|
-
}
|
|
370
|
-
// ============================================
|
|
371
|
-
// Fulltext Search Tools
|
|
372
|
-
// ============================================
|
|
373
|
-
case 'search_fulltext': {
|
|
374
|
-
const params = searchFulltextSchema.parse(args);
|
|
375
|
-
result = db.searchFulltext(params.query, params.libraryID);
|
|
376
|
-
break;
|
|
377
|
-
}
|
|
378
|
-
case 'get_fulltext_content': {
|
|
379
|
-
const params = getFulltextContentSchema.parse(args);
|
|
380
|
-
result = { content: db.getFulltextContent(params.attachmentID) };
|
|
381
|
-
break;
|
|
382
|
-
}
|
|
383
|
-
case 'search_fulltext_with_context': {
|
|
384
|
-
const params = searchFulltextWithContextSchema.parse(args);
|
|
385
|
-
result = db.searchFulltextWithContext(params.query, params.contextLength, params.libraryID);
|
|
386
|
-
break;
|
|
387
|
-
}
|
|
388
|
-
// ============================================
|
|
389
|
-
// Related/Similar Items Tools
|
|
390
|
-
// ============================================
|
|
391
|
-
case 'get_related_items': {
|
|
392
|
-
const params = getRelatedItemsSchema.parse(args);
|
|
393
|
-
result = db.getRelatedItems(params.itemID);
|
|
394
|
-
break;
|
|
395
|
-
}
|
|
396
|
-
case 'find_similar_by_tags': {
|
|
397
|
-
const params = findSimilarByTagsSchema.parse(args);
|
|
398
|
-
result = db.findSimilarByTags(params.itemID, params.minSharedTags);
|
|
399
|
-
break;
|
|
400
|
-
}
|
|
401
|
-
case 'find_similar_by_creators': {
|
|
402
|
-
const params = findSimilarByCreatorsSchema.parse(args);
|
|
403
|
-
result = db.findSimilarByCreators(params.itemID);
|
|
404
|
-
break;
|
|
405
|
-
}
|
|
406
|
-
case 'find_similar_by_collection': {
|
|
407
|
-
const params = findSimilarByCollectionSchema.parse(args);
|
|
408
|
-
result = db.findSimilarByCollection(params.itemID);
|
|
431
|
+
case 'library_maintenance': {
|
|
432
|
+
const params = libraryMaintenanceSchema.parse(args);
|
|
433
|
+
switch (params.action) {
|
|
434
|
+
case 'find_duplicates':
|
|
435
|
+
result = db.findDuplicates(params.duplicateField, params.libraryID);
|
|
436
|
+
break;
|
|
437
|
+
case 'validate_attachments':
|
|
438
|
+
result = db.validateAttachments(params.itemID, params.checkAll);
|
|
439
|
+
break;
|
|
440
|
+
case 'get_valid_attachment':
|
|
441
|
+
if (!params.parentItemID)
|
|
442
|
+
throw new Error('parentItemID is required');
|
|
443
|
+
result = db.getValidAttachment(params.parentItemID, params.contentType);
|
|
444
|
+
break;
|
|
445
|
+
case 'find_with_valid_pdf':
|
|
446
|
+
result = db.findItemsWithValidPDF({
|
|
447
|
+
title: params.title,
|
|
448
|
+
doi: params.doi,
|
|
449
|
+
requireValidPDF: params.requireValidPDF
|
|
450
|
+
});
|
|
451
|
+
break;
|
|
452
|
+
case 'cleanup_orphans':
|
|
453
|
+
result = db.deleteOrphanAttachments(params.dryRun);
|
|
454
|
+
break;
|
|
455
|
+
case 'merge_items':
|
|
456
|
+
if (!params.targetItemID || !params.sourceItemIDs) {
|
|
457
|
+
throw new Error('targetItemID and sourceItemIDs are required');
|
|
458
|
+
}
|
|
459
|
+
result = db.mergeItems(params.targetItemID, params.sourceItemIDs);
|
|
460
|
+
break;
|
|
461
|
+
default:
|
|
462
|
+
throw new Error(`Unknown action: ${params.action}`);
|
|
463
|
+
}
|
|
409
464
|
break;
|
|
410
465
|
}
|
|
411
466
|
default:
|
|
@@ -470,27 +525,24 @@ function zodToJsonSchema(zodType) {
|
|
|
470
525
|
if (zodType instanceof z.ZodBoolean) {
|
|
471
526
|
return { type: 'boolean' };
|
|
472
527
|
}
|
|
473
|
-
// Handle
|
|
528
|
+
// Handle enum types
|
|
529
|
+
if (zodType instanceof z.ZodEnum) {
|
|
530
|
+
return { type: 'string', enum: zodType._def.values };
|
|
531
|
+
}
|
|
532
|
+
// Handle array types
|
|
474
533
|
if (zodType instanceof z.ZodArray) {
|
|
475
534
|
const elementType = zodType._def.type;
|
|
476
535
|
const itemSchema = zodToJsonSchema(elementType);
|
|
477
|
-
// If items schema is empty (from ZodAny), use a more explicit schema
|
|
478
536
|
if (Object.keys(itemSchema).length === 0) {
|
|
479
|
-
return {
|
|
480
|
-
type: 'array',
|
|
481
|
-
items: { type: 'string' } // Default to string for any type to ensure valid JSON Schema
|
|
482
|
-
};
|
|
537
|
+
return { type: 'array', items: { type: 'string' } };
|
|
483
538
|
}
|
|
484
|
-
return {
|
|
485
|
-
type: 'array',
|
|
486
|
-
items: itemSchema
|
|
487
|
-
};
|
|
539
|
+
return { type: 'array', items: itemSchema };
|
|
488
540
|
}
|
|
489
541
|
// Handle object types
|
|
490
542
|
if (zodType instanceof z.ZodObject) {
|
|
491
543
|
return { type: 'object' };
|
|
492
544
|
}
|
|
493
|
-
// Handle ZodAny
|
|
545
|
+
// Handle ZodAny
|
|
494
546
|
if (zodType instanceof z.ZodAny) {
|
|
495
547
|
return {};
|
|
496
548
|
}
|