Zotero MCP: Connects your Zotero research library with Claude and other AI assistants via the Model Context Protocol to discuss papers, get summaries, analyze citations, and more.
This report presents the forensic synthetic code analysis of 54yyyu/zotero-mcp, a Python project with 4,267 GitHub stars. SynthScan v2.0 examined 46,571 lines of code across 125 source files, recording 2016 pattern matches distributed across 15 syntactic categories. The overall adjusted score of 70.4 places this repository in the Strong AI signal band.
The scanner applied 160+ deterministic lexical heuristics, multi-line block detectors, abstract syntax tree depth profilers, and a cross-file Jaccard similarity matrix to construct a statistically normalised synthetic code estimate. All matches are individually weighted by severity coefficient and contextual multiplier before summation, and the resulting headline score is temporally discounted to account for the repository's development history relative to the commercial emergence of large language model coding tooling (November 2022 onward).
This chart maps the temporal evolution of the adjusted synthetic code score across successive scan runs. An upward trajectory indicates ongoing incorporation of AI-generated code or expanding LLM-assisted scaffolding; a stable or declining trajectory may reflect active human refactoring, code removal, or the adoption of stricter authorship policies. The dashed secondary line (right axis) independently tracks total raw pattern hit count, which can diverge from the normalised score when codebase size changes significantly between scans.
Classifies detected patterns by their diagnostic confidence and structural impact. CRITICAL patterns (coefficient 10) represent definitive synthetic signatures — hallucinated imports, explicit LLM attribution metadata — virtually never produced by human authors. HIGH (5) indicates strong structural tells such as cross-file repetition or cross-linguistic idioms. MEDIUM (2) covers recognisable conversational padding and AI-specific vocabulary. LOW (1) captures subtle indicators like tautological comments and generic boilerplate that require density to carry independent signal.
This horizontal bar chart decomposes the repository's raw synthetic code score by top-level directory, allowing you to pinpoint precisely which modules or components carry the highest AI authorship density. Directories with disproportionately high scores relative to their size warrant targeted manual review: concentrated AI signatures often trace back to mass-generated configuration layers, auto-ported test suites, LLM-scaffolded boilerplate classes, or entire subsystems authored under heavy copilot assistance. Use this view to prioritise your human code-review effort.
The scanner identified 2016 distinct pattern matches across 15 syntactic categories. Each entry below represents a discrete location in the source code where the engine recorded a statistically significant AI authorship indicator. Expand any category row to inspect the individual file paths, line numbers, code snippets, and the lexical context (CODE, COMMENT, or STRING) in which each match was detected.
Reading the findings table: The Severity column indicates the diagnostic confidence level (CRITICAL / HIGH / MEDIUM / LOW). The Context column identifies whether the match occurred inside executable code, an inline comment, or a string literal — comment-context matches receive a ×1.5 weight because LLMs systematically over-annotate. The ⚡ bolt icon marks clustered matches: three or more patterns within a 10-line window, each receiving an additional ×1.5 density multiplier as dense clusters constitute far stronger evidence of synthetic authorship than isolated hits.
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| MEDIUM⚡ | tests/test_page_layout.py | 48 | # --------------------------------------------------------------------------- | COMMENT |
| MEDIUM⚡ | tests/test_page_layout.py | 50 | # --------------------------------------------------------------------------- | COMMENT |
| MEDIUM⚡ | tests/test_page_layout.py | 110 | # --------------------------------------------------------------------------- | COMMENT |
| MEDIUM⚡ | tests/test_page_layout.py | 112 | # --------------------------------------------------------------------------- | COMMENT |
| MEDIUM⚡ | tests/test_page_layout.py | 212 | # --------------------------------------------------------------------------- | COMMENT |
| MEDIUM⚡ | tests/test_page_layout.py | 214 | # --------------------------------------------------------------------------- | COMMENT |
| MEDIUM | tests/test_page_layout.py | 23 | # --------------------------------------------------------------------------- | COMMENT |
| MEDIUM | tests/test_page_layout.py | 25 | # --------------------------------------------------------------------------- | COMMENT |
| MEDIUM | tests/test_page_layout.py | 136 | # --------------------------------------------------------------------------- | COMMENT |
| MEDIUM | tests/test_page_layout.py | 138 | # --------------------------------------------------------------------------- | COMMENT |
| MEDIUM | tests/test_page_layout.py | 301 | # --------------------------------------------------------------------------- | COMMENT |
| MEDIUM | tests/test_page_layout.py | 303 | # --------------------------------------------------------------------------- | COMMENT |
| MEDIUM | tests/test_page_layout.py | 560 | # --------------------------------------------------------------------------- | COMMENT |
| MEDIUM | tests/test_page_layout.py | 562 | # --------------------------------------------------------------------------- | COMMENT |
| MEDIUM⚡ | tests/test_scite.py | 37 | # --------------------------------------------------------------------------- | COMMENT |
| MEDIUM⚡ | tests/test_scite.py | 39 | # --------------------------------------------------------------------------- | COMMENT |
| MEDIUM⚡ | tests/test_scite.py | 58 | # --------------------------------------------------------------------------- | COMMENT |
| MEDIUM⚡ | tests/test_scite.py | 60 | # --------------------------------------------------------------------------- | COMMENT |
| MEDIUM⚡ | tests/test_citation_import.py | 85 | # --------------------------------------------------------------------------- | COMMENT |
| MEDIUM⚡ | tests/test_citation_import.py | 87 | # --------------------------------------------------------------------------- | COMMENT |
| MEDIUM | tests/test_citation_import.py | 16 | # --------------------------------------------------------------------------- | COMMENT |
| MEDIUM | tests/test_citation_import.py | 18 | # --------------------------------------------------------------------------- | COMMENT |
| MEDIUM | tests/test_citation_import.py | 128 | # --------------------------------------------------------------------------- | COMMENT |
| MEDIUM | tests/test_citation_import.py | 130 | # --------------------------------------------------------------------------- | COMMENT |
| MEDIUM | tests/test_citation_import.py | 169 | # --------------------------------------------------------------------------- | COMMENT |
| MEDIUM | tests/test_citation_import.py | 171 | # --------------------------------------------------------------------------- | COMMENT |
| MEDIUM | tests/test_citation_import.py | 205 | # --------------------------------------------------------------------------- | COMMENT |
| MEDIUM | tests/test_citation_import.py | 207 | # --------------------------------------------------------------------------- | COMMENT |
| MEDIUM⚡ | tests/test_citation_import.py | 388 | # --------------------------------------------------------------------------- | STRING |
| MEDIUM⚡ | tests/test_citation_import.py | 390 | # --------------------------------------------------------------------------- | STRING |
| MEDIUM⚡ | tests/test_citation_import.py | 422 | # --------------------------------------------------------------------------- | STRING |
| MEDIUM⚡ | tests/test_citation_import.py | 424 | # --------------------------------------------------------------------------- | STRING |
| MEDIUM | tests/test_citation_import.py | 298 | # --------------------------------------------------------------------------- | STRING |
| MEDIUM | tests/test_citation_import.py | 300 | # --------------------------------------------------------------------------- | STRING |
| MEDIUM⚡ | tests/test_shared_helpers.py | 11 | # --------------------------------------------------------------------------- | COMMENT |
| MEDIUM⚡ | tests/test_shared_helpers.py | 13 | # --------------------------------------------------------------------------- | COMMENT |
| MEDIUM⚡ | tests/test_shared_helpers.py | 57 | # --------------------------------------------------------------------------- | COMMENT |
| MEDIUM⚡ | tests/test_shared_helpers.py | 59 | # --------------------------------------------------------------------------- | COMMENT |
| MEDIUM⚡ | tests/test_shared_helpers.py | 79 | # --------------------------------------------------------------------------- | COMMENT |
| MEDIUM⚡ | tests/test_shared_helpers.py | 81 | # --------------------------------------------------------------------------- | COMMENT |
| MEDIUM⚡ | tests/test_shared_helpers.py | 109 | # --------------------------------------------------------------------------- | COMMENT |
| MEDIUM⚡ | tests/test_shared_helpers.py | 111 | # --------------------------------------------------------------------------- | COMMENT |
| MEDIUM⚡ | tests/test_shared_helpers.py | 138 | # --------------------------------------------------------------------------- | COMMENT |
| MEDIUM⚡ | tests/test_shared_helpers.py | 140 | # --------------------------------------------------------------------------- | COMMENT |
| MEDIUM⚡ | tests/test_shared_helpers.py | 188 | # --------------------------------------------------------------------------- | COMMENT |
| MEDIUM⚡ | tests/test_shared_helpers.py | 190 | # --------------------------------------------------------------------------- | COMMENT |
| MEDIUM⚡ | tests/test_shared_helpers.py | 287 | # --------------------------------------------------------------------------- | COMMENT |
| MEDIUM⚡ | tests/test_shared_helpers.py | 289 | # --------------------------------------------------------------------------- | COMMENT |
| MEDIUM⚡ | tests/test_shared_helpers.py | 308 | # --------------------------------------------------------------------------- | COMMENT |
| MEDIUM⚡ | tests/test_shared_helpers.py | 310 | # --------------------------------------------------------------------------- | COMMENT |
| MEDIUM | tests/test_shared_helpers.py | 248 | # --------------------------------------------------------------------------- | COMMENT |
| MEDIUM | tests/test_shared_helpers.py | 250 | # --------------------------------------------------------------------------- | COMMENT |
| MEDIUM⚡ | tests/test_add_by_url.py | 109 | # --------------------------------------------------------------------------- | COMMENT |
| MEDIUM⚡ | tests/test_add_by_url.py | 111 | # --------------------------------------------------------------------------- | COMMENT |
| MEDIUM⚡ | tests/test_add_by_url.py | 268 | # --------------------------------------------------------------------------- | COMMENT |
| MEDIUM⚡ | tests/test_add_by_url.py | 270 | # --------------------------------------------------------------------------- | COMMENT |
| MEDIUM⚡ | tests/test_add_by_url.py | 292 | # --------------------------------------------------------------------------- | COMMENT |
| MEDIUM⚡ | tests/test_add_by_url.py | 294 | # --------------------------------------------------------------------------- | COMMENT |
| MEDIUM⚡ | tests/test_add_by_url.py | 434 | # --------------------------------------------------------------------------- | COMMENT |
| MEDIUM⚡ | tests/test_add_by_url.py | 436 | # --------------------------------------------------------------------------- | COMMENT |
| 434 more matches not shown… | ||||
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| LOW⚡ | tests/test_page_layout.py | 53 | def test_figure_caption_with_colon(self): | CODE |
| LOW⚡ | tests/test_page_layout.py | 60 | def test_figure_caption_with_period(self): | CODE |
| LOW⚡ | tests/test_page_layout.py | 66 | def test_fig_abbreviation_with_panel_letter(self): | CODE |
| LOW⚡ | tests/test_page_layout.py | 78 | def test_supplementary_table_caption(self): | CODE |
| LOW⚡ | tests/test_page_layout.py | 84 | def test_extended_data_figure_caption(self): | CODE |
| LOW⚡ | tests/test_page_layout.py | 90 | def test_in_text_reference_rejected(self): | CODE |
| LOW⚡ | tests/test_page_layout.py | 94 | def test_sentence_starting_with_figure_rejected(self): | CODE |
| LOW⚡ | tests/test_page_layout.py | 104 | def test_caption_is_case_insensitive(self): | CODE |
| LOW⚡ | tests/test_page_layout.py | 180 | def test_merges_vertically_adjacent_fragments(self): | CODE |
| LOW⚡ | tests/test_page_layout.py | 189 | def test_keeps_distant_regions_separate(self): | CODE |
| LOW⚡ | tests/test_page_layout.py | 197 | def test_dedupes_same_area_keeping_table(self): | CODE |
| LOW⚡ | tests/test_page_layout.py | 221 | def test_figure_caption_below_figure(self): | CODE |
| LOW⚡ | tests/test_page_layout.py | 231 | def test_table_caption_above_table(self): | CODE |
| LOW⚡ | tests/test_page_layout.py | 240 | def test_caption_too_far_not_attached(self): | CODE |
| LOW⚡ | tests/test_page_layout.py | 250 | def test_two_column_caption_not_cross_attached(self): | CODE |
| LOW⚡ | tests/test_page_layout.py | 260 | def test_caption_attaches_to_nearest_of_two_stacked_figures(self): | CODE |
| LOW⚡ | tests/test_page_layout.py | 706 | def test_rejects_invalid_page_number(self, monkeypatch, fake_zot): | CODE |
| LOW⚡ | tests/test_page_layout.py | 716 | def test_requires_a_zotero_client(self, monkeypatch): | CODE |
| LOW⚡ | tests/test_page_layout.py | 726 | def test_works_with_local_client_only(self, monkeypatch, fake_zot): | CODE |
| LOW | tests/test_page_layout.py | 155 | def test_drops_full_page_background(self): | CODE |
| LOW | tests/test_page_layout.py | 164 | def test_merges_overlapping_fragments(self): | CODE |
| LOW | tests/test_page_layout.py | 274 | def test_each_caption_attaches_to_one_region_only(self): | CODE |
| LOW | tests/test_page_layout.py | 287 | def test_region_without_caption_gets_low_confidence(self): | CODE |
| LOW | tests/test_page_layout.py | 317 | def test_detects_raster_image_with_caption(self): | CODE |
| LOW | tests/test_page_layout.py | 348 | def test_detects_vector_drawing(self): | CODE |
| LOW | tests/test_page_layout.py | 370 | def test_detects_table_keeping_table_source(self): | CODE |
| LOW | tests/test_page_layout.py | 400 | def test_text_only_page_returns_no_regions(self): | CODE |
| LOW | tests/test_page_layout.py | 418 | def test_page_out_of_range_returns_error(self): | CODE |
| LOW | tests/test_page_layout.py | 431 | def test_full_page_scan_returns_warning(self): | CODE |
| LOW | tests/test_page_layout.py | 451 | def test_missing_cluster_drawings_adds_warning(self, monkeypatch): | CODE |
| LOW | tests/test_page_layout.py | 471 | def test_regions_have_sequential_ids_and_normalized_bboxes(self): | CODE |
| LOW | tests/test_page_layout.py | 496 | def test_rotated_page_bboxes_stay_normalized(self): | CODE |
| LOW | tests/test_page_layout.py | 518 | def test_cropped_page_bboxes_stay_normalized(self): | CODE |
| LOW | tests/test_page_layout.py | 549 | def test_invalid_pdf_returns_error(self): | CODE |
| LOW | tests/test_page_layout.py | 611 | def test_happy_path_renders_regions_table(self, monkeypatch, fake_zot): | CODE |
| LOW | tests/test_page_layout.py | 648 | def test_no_regions_returns_guidance(self, monkeypatch, fake_zot): | CODE |
| LOW | tests/test_page_layout.py | 659 | def test_warnings_are_included(self, monkeypatch, fake_zot): | CODE |
| LOW | tests/test_page_layout.py | 673 | def test_detection_error_is_returned(self, monkeypatch, fake_zot): | CODE |
| LOW | tests/test_page_layout.py | 684 | def test_rejects_non_pdf_attachment(self, monkeypatch, fake_zot): | CODE |
| LOW | tests/test_page_layout.py | 695 | def test_rejects_non_attachment_item(self, monkeypatch, fake_zot): | CODE |
| LOW | tests/test_identifier_fields_markdown.py | 61 | def test_isbn_omitted_when_empty(self): | CODE |
| LOW | tests/test_identifier_fields_markdown.py | 66 | def test_isbn_omitted_when_missing(self): | CODE |
| LOW | tests/test_identifier_fields_markdown.py | 78 | def test_issn_omitted_when_empty(self): | CODE |
| LOW⚡ | tests/test_place_field_reads.py | 57 | def test_place_rendered_for_book(self): | CODE |
| LOW⚡ | tests/test_place_field_reads.py | 63 | def test_place_rendered_for_book_section(self): | CODE |
| LOW⚡ | tests/test_place_field_reads.py | 68 | def test_place_rendered_for_thesis(self): | CODE |
| LOW⚡ | tests/test_place_field_reads.py | 73 | def test_place_without_publisher(self): | CODE |
| LOW⚡ | tests/test_place_field_reads.py | 79 | def test_place_omitted_when_empty(self): | CODE |
| LOW⚡ | tests/test_place_field_reads.py | 85 | def test_place_omitted_when_missing(self): | CODE |
| LOW⚡ | tests/test_place_field_reads.py | 102 | def test_address_rendered_for_book(self): | CODE |
| LOW⚡ | tests/test_place_field_reads.py | 107 | def test_address_rendered_for_book_section(self): | CODE |
| LOW⚡ | tests/test_place_field_reads.py | 112 | def test_address_omitted_when_empty(self): | CODE |
| LOW⚡ | tests/test_place_field_reads.py | 117 | def test_address_omitted_when_missing(self): | CODE |
| LOW⚡ | tests/test_place_field_reads.py | 127 | def test_place_in_fetch_metadata(self, monkeypatch): | CODE |
| LOW⚡ | tests/test_scite.py | 42 | def test_get_papers_batch_posts_bare_doi_array(monkeypatch): | CODE |
| LOW⚡ | tests/test_scite.py | 63 | def test_enrich_items_matches_lowercased_scite_keys(monkeypatch): | CODE |
| LOW | tests/test_scite.py | 98 | def test_check_retractions_flags_uppercase_doi(monkeypatch, dummy_ctx, fake_zot): | CODE |
| LOW⚡ | tests/test_citation_import.py | 90 | def test_parses_single_article(self): | CODE |
| LOW⚡ | tests/test_citation_import.py | 141 | def test_multiple_authors_split_on_and(self): | CODE |
| LOW⚡ | tests/test_citation_import.py | 147 | def test_corporate_author_with_llc(self): | CODE |
| 787 more matches not shown… | ||||
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| LOW⚡ | tests/test_ollama_embedding.py | 70 | except Exception: | CODE |
| LOW | tests/test_description_tokens.py | 95 | except Exception: | CODE |
| LOW | src/zotero_mcp/better_bibtex_client.py | 110 | except Exception: | CODE |
| LOW | src/zotero_mcp/better_bibtex_client.py | 134 | except Exception as e: | CODE |
| LOW | src/zotero_mcp/better_bibtex_client.py | 178 | except Exception as e: | CODE |
| LOW | src/zotero_mcp/better_bibtex_client.py | 232 | except Exception as e: | CODE |
| MEDIUM | src/zotero_mcp/better_bibtex_client.py | 233 | print(f"Error searching for cite keys: {e}") | CODE |
| LOW | src/zotero_mcp/better_bibtex_client.py | 292 | except Exception as e: | CODE |
| MEDIUM | src/zotero_mcp/better_bibtex_client.py | 293 | print(f"Error exporting BibTeX: {e}") | CODE |
| LOW⚡ | src/zotero_mcp/better_bibtex_client.py | 369 | except Exception as e: | CODE |
| MEDIUM⚡ | src/zotero_mcp/better_bibtex_client.py | 370 | print(f"Error processing annotation: {e}") | CODE |
| LOW | src/zotero_mcp/pdf_utils.py | 829 | except Exception: | CODE |
| LOW | src/zotero_mcp/client.py | 215 | except Exception: | CODE |
| LOW | src/zotero_mcp/client.py | 386 | except Exception: | CODE |
| LOW | src/zotero_mcp/client.py | 534 | except Exception: | CODE |
| LOW | src/zotero_mcp/client.py | 580 | except Exception as exc: | CODE |
| LOW | src/zotero_mcp/client.py | 607 | except Exception as exc: | CODE |
| LOW | src/zotero_mcp/client.py | 631 | except Exception as e: | CODE |
| MEDIUM⚡ | src/zotero_mcp/setup_helper.py | 436 | print(f"Error saving semantic search config: {e}") | CODE |
| MEDIUM | src/zotero_mcp/setup_helper.py | 94 | print(f"Error searching for zotero-mcp: {e}") | CODE |
| MEDIUM | src/zotero_mcp/setup_helper.py | 469 | print(f"Error: Config file at {config_path} is not valid JSON. Creating new config.") | CODE |
| MEDIUM | src/zotero_mcp/setup_helper.py | 533 | print(f"Error writing config file: {str(e)}") | CODE |
| MEDIUM | src/zotero_mcp/setup_helper.py | 640 | print("Error: Could not find zotero-mcp executable.") | CODE |
| MEDIUM | src/zotero_mcp/setup_helper.py | 654 | print("Error: Could not determine Claude Desktop config path.") | CODE |
| LOW⚡ | src/zotero_mcp/setup_helper.py | 435 | except Exception as e: | CODE |
| LOW | src/zotero_mcp/setup_helper.py | 93 | except Exception as e: | CODE |
| LOW | src/zotero_mcp/setup_helper.py | 451 | except Exception as e: | CODE |
| LOW | src/zotero_mcp/setup_helper.py | 532 | except Exception as e: | CODE |
| LOW | src/zotero_mcp/setup_helper.py | 551 | except Exception: | CODE |
| LOW | src/zotero_mcp/setup_helper.py | 735 | except Exception: | CODE |
| LOW | src/zotero_mcp/setup_helper.py | 781 | except Exception as e: | CODE |
| LOW | src/zotero_mcp/pdfannots_downloader.py | 183 | except Exception as e: | CODE |
| MEDIUM | src/zotero_mcp/pdfannots_downloader.py | 184 | print(f"Error downloading pdfannots2json: {e}") | CODE |
| MEDIUM⚡ | src/zotero_mcp/cli_standalone.py | 416 | print("Error: --openai-batch requires ZOTERO_EMBEDDING_MODEL=openai", file=sys.stderr) | CODE |
| MEDIUM⚡ | src/zotero_mcp/cli_standalone.py | 422 | print("Error: --fulltext requires local mode (ZOTERO_LOCAL=true).", file=sys.stderr) | CODE |
| MEDIUM⚡ | src/zotero_mcp/cli_standalone.py | 432 | print(f"Error: {stats['error']}", file=sys.stderr) | CODE |
| MEDIUM | src/zotero_mcp/cli_standalone.py | 102 | print(f"Error: invalid JSON in --conditions: {e}", file=sys.stderr) | CODE |
| MEDIUM | src/zotero_mcp/cli_standalone.py | 115 | print(f"Error: invalid JSON in --filters: {e}", file=sys.stderr) | CODE |
| MEDIUM | src/zotero_mcp/cli_standalone.py | 208 | print("Error: provide note text via --text TEXT or --text - (reads stdin)", | CODE |
| MEDIUM | src/zotero_mcp/cli_standalone.py | 344 | print(f"Error: invalid JSON in --creators: {e}", file=sys.stderr) | CODE |
| MEDIUM | src/zotero_mcp/cli_standalone.py | 870 | print(f"Error: {e}", file=sys.stderr) | CODE |
| LOW | src/zotero_mcp/cli_standalone.py | 869 | except Exception as e: | CODE |
| LOW | src/zotero_mcp/_app.py | 42 | except Exception: | CODE |
| LOW | src/zotero_mcp/_app.py | 76 | except Exception as e: | CODE |
| MEDIUM | src/zotero_mcp/_app.py | 73 | def _background_update(): | CODE |
| LOW⚡ | src/zotero_mcp/updater.py | 71 | except Exception: | CODE |
| LOW⚡ | src/zotero_mcp/updater.py | 87 | except Exception: | CODE |
| LOW⚡ | src/zotero_mcp/updater.py | 207 | except Exception as e: | CODE |
| LOW⚡ | src/zotero_mcp/updater.py | 217 | except Exception as e: | CODE |
| LOW⚡ | src/zotero_mcp/updater.py | 227 | except Exception as e: | CODE |
| LOW | src/zotero_mcp/updater.py | 41 | except Exception: | CODE |
| LOW | src/zotero_mcp/updater.py | 116 | except Exception: | CODE |
| LOW | src/zotero_mcp/updater.py | 141 | except Exception: | CODE |
| LOW | src/zotero_mcp/updater.py | 162 | except Exception as e: | CODE |
| LOW | src/zotero_mcp/updater.py | 175 | except Exception as e: | CODE |
| LOW | src/zotero_mcp/updater.py | 256 | except Exception as e: | CODE |
| LOW | src/zotero_mcp/updater.py | 268 | except Exception as e: | CODE |
| LOW | src/zotero_mcp/updater.py | 281 | except Exception as e: | CODE |
| LOW | src/zotero_mcp/updater.py | 333 | except Exception: | CODE |
| LOW | src/zotero_mcp/updater.py | 363 | except Exception as e: | CODE |
| 303 more matches not shown… | ||||
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| LOW | tests/test_shared_helpers.py | 4 | CODE | |
| LOW | tests/test_shared_helpers.py | 4 | CODE | |
| LOW | tests/test_add_by_url.py | 7 | CODE | |
| LOW | tests/test_delete_item.py | 9 | CODE | |
| LOW | tests/test_add_by_isbn.py | 8 | CODE | |
| LOW | tests/test_add_by_isbn.py | 10 | CODE | |
| LOW | tests/test_add_from_file.py | 5 | CODE | |
| LOW | tests/test_add_from_file.py | 6 | CODE | |
| LOW | tests/test_add_from_file.py | 6 | CODE | |
| LOW | tests/test_add_from_file.py | 9 | CODE | |
| LOW | tests/test_add_from_file.py | 9 | CODE | |
| LOW | tests/test_update_item.py | 3 | CODE | |
| LOW | tests/test_token_optimization.py | 4 | CODE | |
| LOW | tests/test_token_optimization.py | 6 | CODE | |
| LOW | tests/test_v021_fixes.py | 6 | CODE | |
| LOW | tests/test_v021_fixes.py | 8 | CODE | |
| LOW | tests/test_v021_fixes.py | 10 | CODE | |
| LOW | tests/test_tag_filter_normalization.py | 10 | CODE | |
| LOW | tests/test_add_by_doi.py | 4 | CODE | |
| LOW | tests/test_api_lock_bounded.py | 14 | CODE | |
| LOW | tests/test_search_improvements.py | 3 | CODE | |
| LOW | tests/test_search_improvements.py | 4 | CODE | |
| LOW | tests/test_search_improvements.py | 5 | CODE | |
| LOW | tests/test_zotero_ft_cache.py | 22 | CODE | |
| LOW | tests/test_pdf_cascade.py | 4 | CODE | |
| LOW | tests/test_pdf_cascade.py | 6 | CODE | |
| LOW | tests/test_pdf_cascade.py | 17 | CODE | |
| LOW | tests/test_webdav.py | 3 | CODE | |
| LOW | tests/test_item_related.py | 3 | CODE | |
| LOW | tests/test_cli_standalone.py | 3 | CODE | |
| LOW | tests/test_regression_bugs.py | 8 | CODE | |
| LOW | tests/test_duplicates.py | 3 | CODE | |
| LOW | tests/test_duplicates.py | 5 | CODE | |
| LOW | tests/test_pdf_outline.py | 6 | CODE | |
| LOW | tests/test_pdf_outline.py | 8 | CODE | |
| LOW | tests/test_pdf_outline.py | 8 | CODE | |
| LOW | src/zotero_mcp/webdav.py | 3 | CODE | |
| LOW | src/zotero_mcp/server.py | 13 | CODE | |
| LOW | src/zotero_mcp/server.py | 16 | CODE | |
| LOW | src/zotero_mcp/server.py | 19 | CODE | |
| LOW | src/zotero_mcp/server.py | 19 | CODE | |
| LOW | src/zotero_mcp/server.py | 19 | CODE | |
| LOW | src/zotero_mcp/server.py | 19 | CODE | |
| LOW | src/zotero_mcp/server.py | 19 | CODE | |
| LOW | src/zotero_mcp/server.py | 19 | CODE | |
| LOW | src/zotero_mcp/server.py | 19 | CODE | |
| LOW | src/zotero_mcp/server.py | 19 | CODE | |
| LOW | src/zotero_mcp/server.py | 19 | CODE | |
| LOW | src/zotero_mcp/server.py | 30 | CODE | |
| LOW | src/zotero_mcp/server.py | 30 | CODE | |
| LOW | src/zotero_mcp/server.py | 30 | CODE | |
| LOW | src/zotero_mcp/server.py | 30 | CODE | |
| LOW | src/zotero_mcp/server.py | 38 | CODE | |
| LOW | src/zotero_mcp/server.py | 38 | CODE | |
| LOW | src/zotero_mcp/server.py | 38 | CODE | |
| LOW | src/zotero_mcp/server.py | 38 | CODE | |
| LOW | src/zotero_mcp/server.py | 38 | CODE | |
| LOW | src/zotero_mcp/server.py | 38 | CODE | |
| LOW | src/zotero_mcp/server.py | 38 | CODE | |
| LOW | src/zotero_mcp/server.py | 38 | CODE | |
| 90 more matches not shown… | ||||
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| LOW | src/zotero_mcp/webdav.py | 222 | CODE | |
| LOW | src/zotero_mcp/better_bibtex_client.py | 113 | CODE | |
| LOW | src/zotero_mcp/better_bibtex_client.py | 236 | CODE | |
| LOW | src/zotero_mcp/client.py | 365 | CODE | |
| LOW | src/zotero_mcp/client.py | 467 | CODE | |
| LOW | src/zotero_mcp/setup_helper.py | 146 | CODE | |
| LOW | src/zotero_mcp/setup_helper.py | 408 | CODE | |
| LOW | src/zotero_mcp/setup_helper.py | 456 | CODE | |
| LOW | src/zotero_mcp/setup_helper.py | 582 | CODE | |
| LOW | src/zotero_mcp/pdfannots_downloader.py | 133 | CODE | |
| LOW | src/zotero_mcp/cli_standalone.py | 86 | CODE | |
| LOW | src/zotero_mcp/cli_standalone.py | 130 | CODE | |
| LOW | src/zotero_mcp/cli_standalone.py | 195 | CODE | |
| LOW | src/zotero_mcp/cli_standalone.py | 241 | CODE | |
| LOW | src/zotero_mcp/cli_standalone.py | 397 | CODE | |
| LOW | src/zotero_mcp/updater.py | 45 | CODE | |
| LOW | src/zotero_mcp/updater.py | 122 | CODE | |
| LOW | src/zotero_mcp/updater.py | 288 | CODE | |
| LOW | src/zotero_mcp/local_db.py | 565 | CODE | |
| LOW | src/zotero_mcp/local_db.py | 761 | CODE | |
| LOW | src/zotero_mcp/epub_utils.py | 469 | CODE | |
| LOW | src/zotero_mcp/epub_utils.py | 701 | CODE | |
| LOW | src/zotero_mcp/cli.py | 101 | CODE | |
| LOW | src/zotero_mcp/cli.py | 277 | CODE | |
| LOW | src/zotero_mcp/utils.py | 22 | CODE | |
| LOW | src/zotero_mcp/chroma_client.py | 852 | CODE | |
| LOW | src/zotero_mcp/chroma_client.py | 446 | CODE | |
| LOW | src/zotero_mcp/chroma_client.py | 537 | CODE | |
| LOW | src/zotero_mcp/chroma_client.py | 684 | CODE | |
| LOW | src/zotero_mcp/citation_import.py | 153 | CODE | |
| LOW | src/zotero_mcp/citation_import.py | 180 | CODE | |
| LOW | src/zotero_mcp/citation_import.py | 339 | CODE | |
| LOW | src/zotero_mcp/citation_import.py | 525 | CODE | |
| LOW | src/zotero_mcp/pdf_layout.py | 138 | CODE | |
| LOW | src/zotero_mcp/pdf_layout.py | 216 | CODE | |
| LOW | src/zotero_mcp/pdf_layout.py | 312 | CODE | |
| LOW | src/zotero_mcp/semantic_search.py | 211 | CODE | |
| LOW | src/zotero_mcp/semantic_search.py | 256 | CODE | |
| LOW | src/zotero_mcp/semantic_search.py | 756 | CODE | |
| LOW | src/zotero_mcp/semantic_search.py | 1380 | CODE | |
| LOW | src/zotero_mcp/semantic_search.py | 1505 | CODE | |
| LOW | src/zotero_mcp/semantic_search.py | 1851 | CODE | |
| LOW | src/zotero_mcp/semantic_search.py | 2153 | CODE | |
| LOW | src/zotero_mcp/semantic_search.py | 2218 | CODE | |
| LOW | src/zotero_mcp/tools/write.py | 190 | CODE | |
| LOW | src/zotero_mcp/tools/write.py | 378 | CODE | |
| LOW | src/zotero_mcp/tools/write.py | 456 | CODE | |
| LOW | src/zotero_mcp/tools/write.py | 709 | CODE | |
| LOW | src/zotero_mcp/tools/write.py | 776 | CODE | |
| LOW | src/zotero_mcp/tools/write.py | 891 | CODE | |
| LOW | src/zotero_mcp/tools/write.py | 1198 | CODE | |
| LOW | src/zotero_mcp/tools/write.py | 1712 | CODE | |
| LOW | src/zotero_mcp/tools/write.py | 2079 | CODE | |
| LOW | src/zotero_mcp/tools/write.py | 2209 | CODE | |
| LOW | src/zotero_mcp/tools/write.py | 2516 | CODE | |
| LOW | src/zotero_mcp/tools/write.py | 2831 | CODE | |
| LOW | src/zotero_mcp/tools/write.py | 3098 | CODE | |
| LOW | src/zotero_mcp/tools/discovery.py | 152 | CODE | |
| LOW | src/zotero_mcp/tools/discovery.py | 288 | CODE | |
| LOW | src/zotero_mcp/tools/retrieval.py | 116 | CODE | |
| 30 more matches not shown… | ||||
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| MEDIUM | tests/test_regression_bugs.py | 193 | # Create a dummy file | COMMENT |
| MEDIUM | src/zotero_mcp/client.py | 202 | # Create a local client - library_id 0 is the default for local. | COMMENT |
| MEDIUM | src/zotero_mcp/pdfannots_downloader.py | 155 | # Create a temporary directory for the download | COMMENT |
| MEDIUM | src/zotero_mcp/_app.py | 86 | # Create an MCP server (fastmcp 2.14+ no longer accepts `dependencies`) | COMMENT |
| MEDIUM | src/zotero_mcp/tools/write.py | 2590 | # Create the metadata item. With if_exists='file' and a known DOI, | COMMENT |
| MEDIUM | src/zotero_mcp/tools/write.py | 2604 | # Create a basic item | COMMENT |
| MEDIUM | src/zotero_mcp/tools/retrieval.py | 368 | # Create a mapping of collection IDs to their data | COMMENT |
| MEDIUM | src/zotero_mcp/tools/retrieval.py | 371 | # Create a mapping of parent to child collections | COMMENT |
| MEDIUM | src/zotero_mcp/tools/annotations.py | 1440 | # Create the annotation using web client | COMMENT |
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| LOW⚡ | docs/integration-test-plan.md | 491 | ### Step 1: Verify all per-phase cleanups were done | COMMENT |
| LOW⚡ | docs/integration-test-plan.md | 499 | ### Step 2: Identify test-created items | COMMENT |
| LOW⚡ | docs/integration-test-plan.md | 505 | ### Step 3: Tell the user to delete manually | COMMENT |
| LOW | src/zotero_mcp/better_bibtex_client.py | 251 | # Step 1: Get citation key from item key. BBT's JSON-RPC expects | COMMENT |
| LOW | src/zotero_mcp/better_bibtex_client.py | 266 | # Step 2: Export BibTeX using the citation key. | COMMENT |
| LOW⚡ | src/zotero_mcp/tools/write.py | 2319 | # Step 4: Consolidate collections | COMMENT |
| LOW⚡ | src/zotero_mcp/tools/write.py | 2326 | # Step 5: Re-parent children (skip duplicate attachments) | COMMENT |
| LOW⚡ | src/zotero_mcp/tools/write.py | 2364 | # Step 6: Trash duplicates (move to Zotero Trash, NOT permanent delete) | COMMENT |
| LOW | src/zotero_mcp/tools/write.py | 2308 | # Step 3: Consolidate tags | COMMENT |
| LOW⚡ | src/zotero_mcp/tools/annotations.py | 664 | # Step 2: Batch-fetch the grandparents (papers) | COMMENT |
| LOW⚡ | src/zotero_mcp/tools/annotations.py | 688 | # Step 3: Map attachment keys to paper titles | COMMENT |
| LOW | src/zotero_mcp/tools/annotations.py | 633 | # Step 1: Batch-fetch the immediate parents (attachments) | COMMENT |
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| MEDIUM | tests/test_fulltext_web_mode.py | 279 | # --------- Integration tests: update_database orchestration ---------- | COMMENT |
| MEDIUM⚡ | tests/test_trashed_collections.py | 23 | # Test scaffolding | COMMENT |
| MEDIUM | src/zotero_mcp/setup_helper.py | 172 | # Choose embedding model | COMMENT |
| MEDIUM | src/zotero_mcp/chroma_client.py | 878 | # Pick the embedding model. config.json is the richer, authoritative source | COMMENT |
| MEDIUM | src/zotero_mcp/semantic_search.py | 1923 | # Truncate to fit the configured embedding model's token limit | COMMENT |
| MEDIUM | src/zotero_mcp/tools/search.py | 1058 | # guard, but neither loads an embedding model or a Zotero client. | COMMENT |
| MEDIUM | src/zotero_mcp/tools/search.py | 1072 | # Read status without loading any embedding model (fast, no network). | COMMENT |
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| HIGH | README.md | 268 | "ZOTERO_API_KEY": "YOUR_API_KEY", | CODE |
| HIGH | README.md | 282 | - Click [here](https://www.zotero.org/settings/security#applications) to generate a Zotero API key and replace `YOUR_ | CODE |
| HIGH | README.md | 353 | zotero-mcp setup --no-local --api-key YOUR_API_KEY --library-id YOUR_LIBRARY_ID | CODE |
| HIGH | docs/getting-started.md | 45 | export ZOTERO_API_KEY=your_api_key | CODE |
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| LOW | src/zotero_mcp/pdfannots_downloader.py | 69 | # Check if we have a URL for this platform/architecture | COMMENT |
| LOW⚡ | src/zotero_mcp/updater.py | 62 | # Check if we're in a uv-managed project | COMMENT |
| LOW⚡ | src/zotero_mcp/updater.py | 77 | # Check if we're in a uv virtual environment | COMMENT |
| LOW | src/zotero_mcp/updater.py | 101 | # Check if pipx is available | COMMENT |
| LOW | src/zotero_mcp/updater.py | 437 | # Check if update is needed | COMMENT |
| LOW | src/zotero_mcp/cli.py | 430 | # Check if installed via uv | COMMENT |
| LOW | src/zotero_mcp/chroma_client.py | 496 | # Check if the stored config matches what we want; if not, recreate. | COMMENT |
| LOW | src/zotero_mcp/tools/write.py | 2774 | # Check if relation already exists (match both URI prefix variants) | COMMENT |
| LOW | src/zotero_mcp/tools/annotations.py | 167 | # Check if Zotero with Better BibTeX is running | COMMENT |
| LOW | src/zotero_mcp/tools/annotations.py | 1045 | # Check if creation was successful | COMMENT |
| LOW | src/zotero_mcp/tools/annotations.py | 1443 | # Check if creation was successful | COMMENT |
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| LOW | src/zotero_mcp/better_bibtex_client.py | 251 | # Step 1: Get citation key from item key. BBT's JSON-RPC expects | COMMENT |
| LOW | src/zotero_mcp/better_bibtex_client.py | 266 | # Step 2: Export BibTeX using the citation key. | COMMENT |
| LOW⚡ | src/zotero_mcp/tools/write.py | 2319 | # Step 4: Consolidate collections | COMMENT |
| LOW⚡ | src/zotero_mcp/tools/write.py | 2326 | # Step 5: Re-parent children (skip duplicate attachments) | COMMENT |
| LOW⚡ | src/zotero_mcp/tools/write.py | 2364 | # Step 6: Trash duplicates (move to Zotero Trash, NOT permanent delete) | COMMENT |
| LOW | src/zotero_mcp/tools/write.py | 2308 | # Step 3: Consolidate tags | COMMENT |
| LOW⚡ | src/zotero_mcp/tools/annotations.py | 664 | # Step 2: Batch-fetch the grandparents (papers) | COMMENT |
| LOW⚡ | src/zotero_mcp/tools/annotations.py | 688 | # Step 3: Map attachment keys to paper titles | COMMENT |
| LOW | src/zotero_mcp/tools/annotations.py | 633 | # Step 1: Batch-fetch the immediate parents (attachments) | COMMENT |
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| LOW | src/zotero_mcp/client.py | 100 | def set_active_library(library_id: str, library_type: str) -> None: | CODE |
| LOW | src/zotero_mcp/updater.py | 23 | logger = logging.getLogger(__name__) | CODE |
| LOW | src/zotero_mcp/local_db.py | 20 | logger = logging.getLogger(__name__) | CODE |
| LOW | src/zotero_mcp/chroma_client.py | 27 | logger = logging.getLogger(__name__) | CODE |
| LOW⚡ | src/zotero_mcp/scite_client.py | 19 | logger = logging.getLogger(__name__) | CODE |
| LOW | src/zotero_mcp/semantic_search.py | 35 | logger = logging.getLogger(__name__) | CODE |
| LOW⚡ | src/zotero_mcp/tools/scite.py | 28 | logger = logging.getLogger(__name__) | CODE |
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| LOW⚡ | tests/test_place_field_reads.py | 128 | fake_data = { | CODE |
| LOW | tests/test_place_field_reads.py | 143 | return {"key": key, "data": fake_data} | CODE |
| LOW | tests/test_add_by_isbn.py | 105 | "authors": ["Jane Doe"], | CODE |
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| LOW | tests/test_update_item.py | 13 | CODE | |
| LOW | tests/test_update_item.py | 59 | CODE | |
| LOW | tests/test_update_item.py | 86 | CODE | |
| LOW | tests/test_update_item.py | 122 | CODE | |
| LOW | src/zotero_mcp/utils.py | 161 | CODE | |
| LOW | src/zotero_mcp/tools/write.py | 1712 | CODE |
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| LOW | src/zotero_mcp/client.py | 21 | ) | COMMENT |
| LOW | src/zotero_mcp/chroma_client.py | 161 | persisted collection's config (see OpenAIEmbeddingFunction for details). | COMMENT |
| LOW | src/zotero_mcp/chroma_client.py | 181 | V2_PREFIX_TOKEN_BUDGET = 20 | COMMENT |
| LOW | src/zotero_mcp/chroma_client.py | 881 | # The env var only fills the gap when config.json is absent or left at the | COMMENT |