cicada-mcp 0.1.4__tar.gz → 0.1.7__tar.gz

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.

Potentially problematic release.


This version of cicada-mcp might be problematic. Click here for more details.

Files changed (101) hide show
  1. {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/PKG-INFO +30 -3
  2. {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/README.md +29 -2
  3. {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/cicada/indexer.py +60 -42
  4. {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/cicada/install.py +22 -5
  5. {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/cicada/mcp_server.py +7 -23
  6. {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/cicada/setup.py +23 -9
  7. {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/cicada_mcp.egg-info/PKG-INFO +30 -3
  8. {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/cicada_mcp.egg-info/SOURCES.txt +1 -1
  9. {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/cicada_mcp.egg-info/entry_points.txt +1 -0
  10. {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/pyproject.toml +4 -2
  11. {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/tests/test_indexer_comprehensive.py +5 -5
  12. {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/tests/test_install_comprehensive.py +19 -5
  13. {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/tests/test_install_detection.py +29 -3
  14. {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/tests/test_keyword_search.py +10 -2
  15. cicada_mcp-0.1.7/tests/test_mcp_e2e.py +773 -0
  16. {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/tests/test_setup.py +24 -4
  17. cicada_mcp-0.1.4/cicada/keyword_extractor.py +0 -364
  18. {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/LICENSE +0 -0
  19. {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/cicada/__init__.py +0 -0
  20. {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/cicada/clean.py +0 -0
  21. {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/cicada/command_logger.py +0 -0
  22. {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/cicada/dead_code_analyzer.py +0 -0
  23. {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/cicada/extractors/__init__.py +0 -0
  24. {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/cicada/extractors/base.py +0 -0
  25. {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/cicada/extractors/call.py +0 -0
  26. {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/cicada/extractors/dependency.py +0 -0
  27. {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/cicada/extractors/doc.py +0 -0
  28. {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/cicada/extractors/function.py +0 -0
  29. {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/cicada/extractors/module.py +0 -0
  30. {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/cicada/extractors/spec.py +0 -0
  31. {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/cicada/find_dead_code.py +0 -0
  32. {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/cicada/formatter.py +0 -0
  33. {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/cicada/git_helper.py +0 -0
  34. {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/cicada/keyword_search.py +0 -0
  35. {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/cicada/lightweight_keyword_extractor.py +0 -0
  36. {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/cicada/mcp_tools.py +0 -0
  37. {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/cicada/parser.py +0 -0
  38. {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/cicada/pr_finder.py +0 -0
  39. {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/cicada/pr_indexer/__init__.py +0 -0
  40. {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/cicada/pr_indexer/cli.py +0 -0
  41. {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/cicada/pr_indexer/github_api_client.py +0 -0
  42. {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/cicada/pr_indexer/indexer.py +0 -0
  43. {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/cicada/pr_indexer/line_mapper.py +0 -0
  44. {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/cicada/pr_indexer/pr_index_builder.py +0 -0
  45. {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/cicada/utils/__init__.py +0 -0
  46. {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/cicada/utils/call_site_formatter.py +0 -0
  47. {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/cicada/utils/function_grouper.py +0 -0
  48. {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/cicada/utils/hash_utils.py +0 -0
  49. {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/cicada/utils/index_utils.py +0 -0
  50. {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/cicada/utils/path_utils.py +0 -0
  51. {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/cicada/utils/signature_builder.py +0 -0
  52. {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/cicada/utils/storage.py +0 -0
  53. {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/cicada/utils/subprocess_runner.py +0 -0
  54. {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/cicada/utils/text_utils.py +0 -0
  55. {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/cicada/version_check.py +0 -0
  56. {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/cicada_mcp.egg-info/dependency_links.txt +0 -0
  57. {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/cicada_mcp.egg-info/requires.txt +0 -0
  58. {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/cicada_mcp.egg-info/top_level.txt +0 -0
  59. {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/setup.cfg +0 -0
  60. {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/tests/test_acceptance.py +0 -0
  61. {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/tests/test_call_sites.py +0 -0
  62. {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/tests/test_claude_md_update.py +0 -0
  63. {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/tests/test_clean.py +0 -0
  64. {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/tests/test_command_logger.py +0 -0
  65. {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/tests/test_dead_code_analyzer.py +0 -0
  66. {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/tests/test_e2e.py +0 -0
  67. {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/tests/test_find_dead_code.py +0 -0
  68. {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/tests/test_formatter.py +0 -0
  69. {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/tests/test_git_extended_history.py +0 -0
  70. {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/tests/test_git_helper.py +0 -0
  71. {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/tests/test_github_api_client.py +0 -0
  72. {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/tests/test_gitignore_integration.py +0 -0
  73. {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/tests/test_hash_utils.py +0 -0
  74. {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/tests/test_index_utils.py +0 -0
  75. {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/tests/test_install_summary.py +0 -0
  76. {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/tests/test_intelligent_mcp_config.py +0 -0
  77. {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/tests/test_line_mapper.py +0 -0
  78. {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/tests/test_mcp_git_integration.py +0 -0
  79. {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/tests/test_mcp_logging_integration.py +0 -0
  80. {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/tests/test_mcp_pr_tools.py +0 -0
  81. {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/tests/test_mcp_server_core.py +0 -0
  82. {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/tests/test_mcp_server_formatting.py +0 -0
  83. {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/tests/test_mcp_server_pr_history.py +0 -0
  84. {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/tests/test_module_usage_categories.py +0 -0
  85. {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/tests/test_multi_editor_setup.py +0 -0
  86. {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/tests/test_parser.py +0 -0
  87. {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/tests/test_parser_comprehensive.py +0 -0
  88. {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/tests/test_path_utils.py +0 -0
  89. {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/tests/test_pr_finder.py +0 -0
  90. {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/tests/test_pr_index_builder.py +0 -0
  91. {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/tests/test_pr_indexer_integration.py +0 -0
  92. {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/tests/test_pr_indexer_unit.py +0 -0
  93. {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/tests/test_search_function.py +0 -0
  94. {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/tests/test_server_cli.py +0 -0
  95. {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/tests/test_signal_handling.py +0 -0
  96. {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/tests/test_stdout_redirect.py +0 -0
  97. {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/tests/test_storage.py +0 -0
  98. {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/tests/test_subprocess_runner.py +0 -0
  99. {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/tests/test_test_files_filter.py +0 -0
  100. {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/tests/test_version_check.py +0 -0
  101. {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/tests/test_with_aliases.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cicada-mcp
3
- Version: 0.1.4
3
+ Version: 0.1.7
4
4
  Summary: An Elixir module search MCP server
5
5
  Author-email: wende <wende@hey.com>
6
6
  Maintainer-email: wende <wende@hey.com>
@@ -138,7 +138,7 @@ cicada claude # or: cicada cursor, cicada vs
138
138
 
139
139
  **Available commands after installation:**
140
140
  - `cicada [claude|cursor|vs]` - One-command setup per project
141
- - `cicada-server` - MCP server (auto-started by editor)
141
+ - `cicada-mcp` - MCP server (auto-started by editor)
142
142
  - `cicada-index` - Re-index code with custom options (medium/large spaCy models)
143
143
  - `cicada-index-pr` - Index pull requests for PR attribution
144
144
  - `cicada-install` - Legacy setup (creates `.cicada/` in repo)
@@ -169,6 +169,33 @@ uvx --from git+https://github.com/wende/cicada.git@latest cicada vs
169
169
 
170
170
  Once you're convinced, install permanently with `uv tool install` above!
171
171
 
172
+ ### Quick Setup for Cursor and Claude Code
173
+
174
+ **For Cursor:**
175
+
176
+ Click the install button at the top of this README or visit:
177
+ [![Install MCP Server](https://cursor.com/deeplink/mcp-install-dark.svg)](https://cursor.com/en-US/install-mcp?name=cicada&config=eyJjb21tYW5kIjoidXZ4IC0tZnJvbSBnaXQraHR0cHM6Ly9naXRodWIuY29tL3dlbmRlL2NpY2FkYS5naXRAbGF0ZXN0IGNpY2FkYS1zZXJ2ZXIgLiJ9)
178
+
179
+ **For Claude Code:**
180
+
181
+ ```bash
182
+ # Option 1: Using claude mcp add command
183
+ claude mcp add cicada -- uvx --from git+https://github.com/wende/cicada.git@latest cicada-mcp ./path/to/your/codebase
184
+
185
+ # Option 2: Using setup script
186
+ uvx --from git+https://github.com/wende/cicada.git@latest cicada claude
187
+ ```
188
+
189
+ **Then for both editors,** run these commands in your codebase to generate keyword lookup and GitHub PR lookup databases:
190
+
191
+ ```bash
192
+ # Generate keyword lookup database
193
+ uvx --from git+https://github.com/wende/cicada.git@latest cicada-index .
194
+
195
+ # Generate GitHub PR lookup database
196
+ uvx --from git+https://github.com/wende/cicada.git@latest cicada-index-pr .
197
+ ```
198
+
172
199
  ---
173
200
 
174
201
  ## Quick Start
@@ -221,7 +248,7 @@ your-project/
221
248
  {
222
249
  "mcpServers": {
223
250
  "cicada": {
224
- "command": "cicada-server",
251
+ "command": "cicada-mcp",
225
252
  "env": {
226
253
  "CICADA_REPO_PATH": "/path/to/project",
227
254
  "CICADA_CONFIG_DIR": "/home/user/.cicada/projects/<hash>"
@@ -94,7 +94,7 @@ cicada claude # or: cicada cursor, cicada vs
94
94
 
95
95
  **Available commands after installation:**
96
96
  - `cicada [claude|cursor|vs]` - One-command setup per project
97
- - `cicada-server` - MCP server (auto-started by editor)
97
+ - `cicada-mcp` - MCP server (auto-started by editor)
98
98
  - `cicada-index` - Re-index code with custom options (medium/large spaCy models)
99
99
  - `cicada-index-pr` - Index pull requests for PR attribution
100
100
  - `cicada-install` - Legacy setup (creates `.cicada/` in repo)
@@ -125,6 +125,33 @@ uvx --from git+https://github.com/wende/cicada.git@latest cicada vs
125
125
 
126
126
  Once you're convinced, install permanently with `uv tool install` above!
127
127
 
128
+ ### Quick Setup for Cursor and Claude Code
129
+
130
+ **For Cursor:**
131
+
132
+ Click the install button at the top of this README or visit:
133
+ [![Install MCP Server](https://cursor.com/deeplink/mcp-install-dark.svg)](https://cursor.com/en-US/install-mcp?name=cicada&config=eyJjb21tYW5kIjoidXZ4IC0tZnJvbSBnaXQraHR0cHM6Ly9naXRodWIuY29tL3dlbmRlL2NpY2FkYS5naXRAbGF0ZXN0IGNpY2FkYS1zZXJ2ZXIgLiJ9)
134
+
135
+ **For Claude Code:**
136
+
137
+ ```bash
138
+ # Option 1: Using claude mcp add command
139
+ claude mcp add cicada -- uvx --from git+https://github.com/wende/cicada.git@latest cicada-mcp ./path/to/your/codebase
140
+
141
+ # Option 2: Using setup script
142
+ uvx --from git+https://github.com/wende/cicada.git@latest cicada claude
143
+ ```
144
+
145
+ **Then for both editors,** run these commands in your codebase to generate keyword lookup and GitHub PR lookup databases:
146
+
147
+ ```bash
148
+ # Generate keyword lookup database
149
+ uvx --from git+https://github.com/wende/cicada.git@latest cicada-index .
150
+
151
+ # Generate GitHub PR lookup database
152
+ uvx --from git+https://github.com/wende/cicada.git@latest cicada-index-pr .
153
+ ```
154
+
128
155
  ---
129
156
 
130
157
  ## Quick Start
@@ -177,7 +204,7 @@ your-project/
177
204
  {
178
205
  "mcpServers": {
179
206
  "cicada": {
180
- "command": "cicada-server",
207
+ "command": "cicada-mcp",
181
208
  "env": {
182
209
  "CICADA_REPO_PATH": "/path/to/project",
183
210
  "CICADA_CONFIG_DIR": "/home/user/.cicada/projects/<hash>"
@@ -102,7 +102,8 @@ class ElixirIndexer:
102
102
  if not repo_path_obj.exists():
103
103
  raise ValueError(f"Repository path does not exist: {repo_path_obj}")
104
104
 
105
- print(f"Indexing repository: {repo_path_obj}")
105
+ if self.verbose:
106
+ print(f"Indexing repository: {repo_path_obj}")
106
107
 
107
108
  # Set up signal handlers for graceful interruption
108
109
  signal.signal(signal.SIGINT, self._handle_interrupt)
@@ -118,20 +119,22 @@ class ElixirIndexer:
118
119
  )
119
120
 
120
121
  keyword_extractor = LightweightKeywordExtractor(
121
- verbose=True, model_size=spacy_model
122
+ verbose=self.verbose, model_size=spacy_model
122
123
  )
123
124
  except Exception as e:
124
- print(f"Warning: Could not initialize keyword extractor: {e}")
125
- print("Continuing without keyword extraction...")
125
+ if self.verbose:
126
+ print(f"Warning: Could not initialize keyword extractor: {e}")
127
+ print("Continuing without keyword extraction...")
126
128
  extract_keywords = False
127
129
 
128
130
  # Find all Elixir files
129
131
  elixir_files = self._find_elixir_files(repo_path_obj)
130
132
  total_files = len(elixir_files)
131
133
 
132
- print(f"Found {total_files} Elixir files")
133
- if extract_keywords:
134
- print("Keyword extraction enabled")
134
+ if self.verbose:
135
+ print(f"Found {total_files} Elixir files")
136
+ if extract_keywords:
137
+ print("Keyword extraction enabled")
135
138
 
136
139
  # Parse all files
137
140
  all_modules = {}
@@ -222,7 +225,10 @@ class ElixirIndexer:
222
225
  files_processed += 1
223
226
 
224
227
  # Progress reporting
225
- if files_processed % self.PROGRESS_REPORT_INTERVAL == 0:
228
+ if (
229
+ self.verbose
230
+ and files_processed % self.PROGRESS_REPORT_INTERVAL == 0
231
+ ):
226
232
  print(f" Processed {files_processed}/{total_files} files...")
227
233
 
228
234
  # Check for interruption after each file
@@ -230,7 +236,8 @@ class ElixirIndexer:
230
236
  break
231
237
 
232
238
  except Exception as e:
233
- print(f" Skipping {file_path}: {e}")
239
+ if self.verbose:
240
+ print(f" Skipping {file_path}: {e}")
234
241
  # Check for interruption even after error
235
242
  if self._check_and_report_interruption(files_processed, total_files):
236
243
  break
@@ -258,12 +265,14 @@ class ElixirIndexer:
258
265
  from cicada.utils.path_utils import ensure_gitignore_has_cicada
259
266
 
260
267
  if ensure_gitignore_has_cicada(repo_path_obj):
261
- print("✓ Added .cicada/ to .gitignore")
268
+ if self.verbose:
269
+ print("✓ Added .cicada/ to .gitignore")
262
270
 
263
271
  save_index(index, output_path_obj, create_dirs=True)
264
272
 
265
273
  # Compute and save hashes for all PROCESSED files for future incremental updates
266
- print("Computing file hashes for incremental updates...")
274
+ if self.verbose:
275
+ print("Computing file hashes for incremental updates...")
267
276
  # Only hash files that were actually processed
268
277
  processed_files = [
269
278
  str(f.relative_to(repo_path_obj)) for f in elixir_files[:files_processed]
@@ -272,30 +281,31 @@ class ElixirIndexer:
272
281
  save_file_hashes(str(output_path_obj.parent), file_hashes)
273
282
 
274
283
  # Report completion status
275
- if self._interrupted:
276
- print(f"\n✓ Partial index saved!")
277
- print(
278
- f" Processed: {files_processed}/{total_files} files ({files_processed/total_files*100:.1f}%)"
279
- )
280
- print(f" Modules: {len(all_modules)}")
281
- print(f" Functions: {total_functions}")
282
- print(
283
- f"\n💡 Run the command again to continue indexing remaining {total_files - files_processed} file(s)"
284
- )
285
- else:
286
- print(f"\nIndexing complete!")
287
- print(f" Modules: {len(all_modules)}")
288
- print(f" Functions: {total_functions}")
284
+ if self.verbose:
285
+ if self._interrupted:
286
+ print(f"\n✓ Partial index saved!")
287
+ print(
288
+ f" Processed: {files_processed}/{total_files} files ({files_processed/total_files*100:.1f}%)"
289
+ )
290
+ print(f" Modules: {len(all_modules)}")
291
+ print(f" Functions: {total_functions}")
292
+ print(
293
+ f"\n💡 Run the command again to continue indexing remaining {total_files - files_processed} file(s)"
294
+ )
295
+ else:
296
+ print(f"\nIndexing complete!")
297
+ print(f" Modules: {len(all_modules)}")
298
+ print(f" Functions: {total_functions}")
289
299
 
290
- # Report keyword extraction failures if any
291
- if extract_keywords and keyword_extraction_failures > 0:
292
- print(
293
- f"\n⚠️ Warning: Keyword extraction failed for {keyword_extraction_failures} module(s) or function(s)"
294
- )
295
- print(" Some documentation may not be indexed for keyword search.")
300
+ # Report keyword extraction failures if any
301
+ if extract_keywords and keyword_extraction_failures > 0:
302
+ print(
303
+ f"\n⚠️ Warning: Keyword extraction failed for {keyword_extraction_failures} module(s) or function(s)"
304
+ )
305
+ print(" Some documentation may not be indexed for keyword search.")
296
306
 
297
- print(f"\nIndex saved to: {output_path_obj}")
298
- print(f"Hashes saved to: {output_path_obj.parent}/hashes.json")
307
+ print(f"\nIndex saved to: {output_path_obj}")
308
+ print(f"Hashes saved to: {output_path_obj.parent}/hashes.json")
299
309
 
300
310
  return index
301
311
 
@@ -351,7 +361,8 @@ class ElixirIndexer:
351
361
  str(repo_path_obj), str(output_path_obj), extract_keywords, spacy_model
352
362
  )
353
363
 
354
- print(f"Performing incremental index of: {repo_path_obj}")
364
+ if self.verbose:
365
+ print(f"Performing incremental index of: {repo_path_obj}")
355
366
 
356
367
  # Set up signal handlers for graceful interruption
357
368
  signal.signal(signal.SIGINT, self._handle_interrupt)
@@ -364,7 +375,8 @@ class ElixirIndexer:
364
375
  relative_files = [str(f.relative_to(repo_path_obj)) for f in elixir_files]
365
376
 
366
377
  # Detect file changes
367
- print("Detecting file changes...")
378
+ if self.verbose:
379
+ print("Detecting file changes...")
368
380
  new_files, modified_files, deleted_files = detect_file_changes(
369
381
  relative_files, existing_hashes, str(repo_path_obj)
370
382
  )
@@ -377,10 +389,14 @@ class ElixirIndexer:
377
389
  print("No changes detected. Index is up to date.")
378
390
  return existing_index
379
391
 
380
- print(f"Changes detected:")
381
- print(f" New files: {len(new_files)}")
382
- print(f" Modified files: {len(modified_files)}")
383
- print(f" Deleted files: {len(deleted_files)}")
392
+ if self.verbose:
393
+ print(f"Changes detected:")
394
+ if self.verbose:
395
+ print(f" New files: {len(new_files)}")
396
+ if self.verbose:
397
+ print(f" Modified files: {len(modified_files)}")
398
+ if self.verbose:
399
+ print(f" Deleted files: {len(deleted_files)}")
384
400
 
385
401
  if files_to_process:
386
402
  print(f"\nProcessing {len(files_to_process)} changed file(s)...")
@@ -394,7 +410,7 @@ class ElixirIndexer:
394
410
  )
395
411
 
396
412
  keyword_extractor = LightweightKeywordExtractor(
397
- verbose=True, model_size=spacy_model
413
+ verbose=self.verbose, model_size=spacy_model
398
414
  )
399
415
  except Exception as e:
400
416
  print(f"Warning: Could not initialize keyword extractor: {e}")
@@ -502,13 +518,15 @@ class ElixirIndexer:
502
518
  }
503
519
 
504
520
  # Merge with existing index
505
- print("\nMerging with existing index...")
521
+ if self.verbose:
522
+ print("\nMerging with existing index...")
506
523
  merged_index = merge_indexes_incremental(
507
524
  existing_index, new_index, deleted_files
508
525
  )
509
526
 
510
527
  # Update hashes for all current files
511
- print("Updating file hashes...")
528
+ if self.verbose:
529
+ print("Updating file hashes...")
512
530
  updated_hashes = dict(existing_hashes)
513
531
 
514
532
  # Compute hashes only for files that were actually processed
@@ -251,7 +251,15 @@ def detect_installation_method():
251
251
  ".local/share/uv/tools" in script_path_str
252
252
  or ".local/bin/cicada-" in script_path_str
253
253
  ):
254
- # Installed via uv tool install
254
+ # Installed via uv tool install - check for cicada-mcp first
255
+ if shutil.which("cicada-mcp"):
256
+ return (
257
+ "cicada-mcp",
258
+ [],
259
+ None,
260
+ "uv tool install (ensure ~/.local/bin is in PATH)",
261
+ )
262
+ # Fall back to cicada-server for backwards compatibility
255
263
  return (
256
264
  "cicada-server",
257
265
  [],
@@ -259,7 +267,11 @@ def detect_installation_method():
259
267
  "uv tool install (ensure ~/.local/bin is in PATH)",
260
268
  )
261
269
 
262
- # Check if cicada-server is in PATH (from uv tool install)
270
+ # Check if cicada-mcp is in PATH first (from uv tool install)
271
+ if shutil.which("cicada-mcp"):
272
+ return ("cicada-mcp", [], None, "uv tool install (permanent, fast)")
273
+
274
+ # Fall back to cicada-server for backwards compatibility
263
275
  if shutil.which("cicada-server"):
264
276
  return ("cicada-server", [], None, "uv tool install (permanent, fast)")
265
277
 
@@ -279,8 +291,13 @@ def check_tools_in_path():
279
291
  """Check if cicada tools are in PATH."""
280
292
  import shutil
281
293
 
282
- tools = ["cicada-server", "cicada-index"]
294
+ # Check for cicada-mcp (new) or cicada-server (backwards compat)
295
+ has_mcp_server = shutil.which("cicada-mcp") or shutil.which("cicada-server")
296
+ tools = ["cicada-index"]
283
297
  visible_tools = [tool for tool in tools if shutil.which(tool)]
298
+ if has_mcp_server:
299
+ visible_tools.insert(0, "cicada-mcp/cicada-server")
300
+ tools.insert(0, "cicada-mcp/cicada-server")
284
301
 
285
302
  if len(visible_tools) == len(tools):
286
303
  return "all_visible"
@@ -351,8 +368,8 @@ def create_mcp_config(repo_path, _cicada_dir, _python_bin):
351
368
  print(f"✓ MCP configuration updated at {mcp_config_path}")
352
369
 
353
370
  # Show what was configured
354
- if command == "cicada-server":
355
- print("✅ Using 'cicada-server' command (fast, no paths needed)")
371
+ if command in ("cicada-mcp", "cicada-server"):
372
+ print(f"✅ Using '{command}' command (fast, no paths needed)")
356
373
  else:
357
374
  print(f"ℹ️ Using Python: {command}")
358
375
 
@@ -1496,43 +1496,27 @@ def _auto_setup_if_needed():
1496
1496
  # Already set up, nothing to do
1497
1497
  return
1498
1498
 
1499
- # Setup needed - create storage and index
1500
- print("=" * 60, file=sys.stderr)
1501
- print("Cicada: First-time setup detected", file=sys.stderr)
1502
- print("=" * 60, file=sys.stderr)
1503
- print(file=sys.stderr)
1504
-
1499
+ # Setup needed - create storage and index (silent mode)
1505
1500
  # Validate it's an Elixir project
1506
1501
  if not (repo_path / "mix.exs").exists():
1507
1502
  print(
1508
- f"Error: {repo_path} does not appear to be an Elixir project",
1503
+ f"Error: {repo_path} does not appear to be an Elixir project (mix.exs not found)",
1509
1504
  file=sys.stderr,
1510
1505
  )
1511
- print("(mix.exs not found)", file=sys.stderr)
1512
1506
  sys.exit(1)
1513
1507
 
1514
1508
  try:
1515
1509
  # Create storage directory
1516
1510
  storage_dir = create_storage_dir(repo_path)
1517
- print(f"Repository: {repo_path}", file=sys.stderr)
1518
- print(f"Storage: {storage_dir}", file=sys.stderr)
1519
- print(file=sys.stderr)
1520
-
1521
- # Index repository
1522
- index_repository(repo_path)
1523
- print(file=sys.stderr)
1524
1511
 
1525
- # Create config.yaml
1526
- create_config_yaml(repo_path, storage_dir)
1527
- print(file=sys.stderr)
1512
+ # Index repository (silent mode)
1513
+ index_repository(repo_path, verbose=False)
1528
1514
 
1529
- print("=" * 60, file=sys.stderr)
1530
- print("✓ Setup Complete! Starting server...", file=sys.stderr)
1531
- print("=" * 60, file=sys.stderr)
1532
- print(file=sys.stderr)
1515
+ # Create config.yaml (silent mode)
1516
+ create_config_yaml(repo_path, storage_dir, verbose=False)
1533
1517
 
1534
1518
  except Exception as e:
1535
- print(f"Error during auto-setup: {e}", file=sys.stderr)
1519
+ print(f"Cicada auto-setup error: {e}", file=sys.stderr)
1536
1520
  sys.exit(1)
1537
1521
 
1538
1522
 
@@ -102,9 +102,15 @@ def get_mcp_config_for_editor(
102
102
  # Detect installation method
103
103
  import shutil
104
104
 
105
+ # Check for cicada-mcp first (new name), fall back to cicada-server (backwards compat)
106
+ has_cicada_mcp = shutil.which("cicada-mcp") is not None
105
107
  has_cicada_server = shutil.which("cicada-server") is not None
106
108
 
107
- if has_cicada_server:
109
+ if has_cicada_mcp:
110
+ command = "cicada-mcp"
111
+ args = []
112
+ cwd = None
113
+ elif has_cicada_server:
108
114
  command = "cicada-server"
109
115
  args = []
110
116
  cwd = None
@@ -157,13 +163,16 @@ def get_mcp_config_for_editor(
157
163
  return config_path, config
158
164
 
159
165
 
160
- def create_config_yaml(repo_path: Path, storage_dir: Path) -> None:
166
+ def create_config_yaml(
167
+ repo_path: Path, storage_dir: Path, verbose: bool = True
168
+ ) -> None:
161
169
  """
162
170
  Create config.yaml in storage directory.
163
171
 
164
172
  Args:
165
173
  repo_path: Path to the repository
166
174
  storage_dir: Path to the storage directory
175
+ verbose: Whether to print progress messages (default: True)
167
176
  """
168
177
  config_path = get_config_path(repo_path)
169
178
  index_path = get_index_path(repo_path)
@@ -178,22 +187,24 @@ storage:
178
187
  with open(config_path, "w") as f:
179
188
  f.write(config_content)
180
189
 
181
- print(f"✓ Config file created at {config_path}")
190
+ if verbose:
191
+ print(f"✓ Config file created at {config_path}")
182
192
 
183
193
 
184
- def index_repository(repo_path: Path) -> None:
194
+ def index_repository(repo_path: Path, verbose: bool = True) -> None:
185
195
  """
186
196
  Index the repository with keyword extraction enabled.
187
197
 
188
198
  Args:
189
199
  repo_path: Path to the repository
200
+ verbose: Whether to print progress messages (default: True)
190
201
 
191
202
  Raises:
192
203
  Exception: If indexing fails
193
204
  """
194
205
  try:
195
206
  index_path = get_index_path(repo_path)
196
- indexer = ElixirIndexer(verbose=True)
207
+ indexer = ElixirIndexer(verbose=verbose)
197
208
 
198
209
  # Index with keyword extraction enabled by default
199
210
  # Note: Using 'small' model for compatibility with uvx
@@ -205,10 +216,12 @@ def index_repository(repo_path: Path) -> None:
205
216
  spacy_model="small",
206
217
  )
207
218
 
208
- print(f"✓ Repository indexed at {index_path}")
219
+ if verbose:
220
+ print(f"✓ Repository indexed at {index_path}")
209
221
  except Exception as e:
210
- print(f"Error: Failed to index repository: {e}")
211
- print("Please check that the repository contains valid Elixir files.")
222
+ if verbose:
223
+ print(f"Error: Failed to index repository: {e}")
224
+ print("Please check that the repository contains valid Elixir files.")
212
225
  raise
213
226
 
214
227
 
@@ -275,7 +288,8 @@ def setup(editor: EditorType, repo_path: Path | None = None) -> None:
275
288
  import shutil
276
289
  from cicada import __version__
277
290
 
278
- if not shutil.which("cicada-server"):
291
+ # Check for either cicada-mcp or cicada-server (backwards compat)
292
+ if not (shutil.which("cicada-mcp") or shutil.which("cicada-server")):
279
293
  print("💡 Tip: For best experience, install Cicada permanently:")
280
294
  print(
281
295
  f" uv tool install git+https://github.com/wende/cicada.git@v{__version__}"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cicada-mcp
3
- Version: 0.1.4
3
+ Version: 0.1.7
4
4
  Summary: An Elixir module search MCP server
5
5
  Author-email: wende <wende@hey.com>
6
6
  Maintainer-email: wende <wende@hey.com>
@@ -138,7 +138,7 @@ cicada claude # or: cicada cursor, cicada vs
138
138
 
139
139
  **Available commands after installation:**
140
140
  - `cicada [claude|cursor|vs]` - One-command setup per project
141
- - `cicada-server` - MCP server (auto-started by editor)
141
+ - `cicada-mcp` - MCP server (auto-started by editor)
142
142
  - `cicada-index` - Re-index code with custom options (medium/large spaCy models)
143
143
  - `cicada-index-pr` - Index pull requests for PR attribution
144
144
  - `cicada-install` - Legacy setup (creates `.cicada/` in repo)
@@ -169,6 +169,33 @@ uvx --from git+https://github.com/wende/cicada.git@latest cicada vs
169
169
 
170
170
  Once you're convinced, install permanently with `uv tool install` above!
171
171
 
172
+ ### Quick Setup for Cursor and Claude Code
173
+
174
+ **For Cursor:**
175
+
176
+ Click the install button at the top of this README or visit:
177
+ [![Install MCP Server](https://cursor.com/deeplink/mcp-install-dark.svg)](https://cursor.com/en-US/install-mcp?name=cicada&config=eyJjb21tYW5kIjoidXZ4IC0tZnJvbSBnaXQraHR0cHM6Ly9naXRodWIuY29tL3dlbmRlL2NpY2FkYS5naXRAbGF0ZXN0IGNpY2FkYS1zZXJ2ZXIgLiJ9)
178
+
179
+ **For Claude Code:**
180
+
181
+ ```bash
182
+ # Option 1: Using claude mcp add command
183
+ claude mcp add cicada -- uvx --from git+https://github.com/wende/cicada.git@latest cicada-mcp ./path/to/your/codebase
184
+
185
+ # Option 2: Using setup script
186
+ uvx --from git+https://github.com/wende/cicada.git@latest cicada claude
187
+ ```
188
+
189
+ **Then for both editors,** run these commands in your codebase to generate keyword lookup and GitHub PR lookup databases:
190
+
191
+ ```bash
192
+ # Generate keyword lookup database
193
+ uvx --from git+https://github.com/wende/cicada.git@latest cicada-index .
194
+
195
+ # Generate GitHub PR lookup database
196
+ uvx --from git+https://github.com/wende/cicada.git@latest cicada-index-pr .
197
+ ```
198
+
172
199
  ---
173
200
 
174
201
  ## Quick Start
@@ -221,7 +248,7 @@ your-project/
221
248
  {
222
249
  "mcpServers": {
223
250
  "cicada": {
224
- "command": "cicada-server",
251
+ "command": "cicada-mcp",
225
252
  "env": {
226
253
  "CICADA_REPO_PATH": "/path/to/project",
227
254
  "CICADA_CONFIG_DIR": "/home/user/.cicada/projects/<hash>"
@@ -10,7 +10,6 @@ cicada/formatter.py
10
10
  cicada/git_helper.py
11
11
  cicada/indexer.py
12
12
  cicada/install.py
13
- cicada/keyword_extractor.py
14
13
  cicada/keyword_search.py
15
14
  cicada/lightweight_keyword_extractor.py
16
15
  cicada/mcp_server.py
@@ -71,6 +70,7 @@ tests/test_install_summary.py
71
70
  tests/test_intelligent_mcp_config.py
72
71
  tests/test_keyword_search.py
73
72
  tests/test_line_mapper.py
73
+ tests/test_mcp_e2e.py
74
74
  tests/test_mcp_git_integration.py
75
75
  tests/test_mcp_logging_integration.py
76
76
  tests/test_mcp_pr_tools.py
@@ -5,4 +5,5 @@ cicada-find-dead-code = cicada.find_dead_code:main
5
5
  cicada-index = cicada.indexer:main
6
6
  cicada-index-pr = cicada.pr_indexer:main
7
7
  cicada-install = cicada.install:main
8
+ cicada-mcp = cicada.mcp_server:main
8
9
  cicada-server = cicada.mcp_server:main
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "cicada-mcp"
7
- version = "0.1.4"
7
+ version = "0.1.7"
8
8
  description = "An Elixir module search MCP server"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -59,7 +59,8 @@ Changelog = "https://github.com/wende/cicada/blob/main/CHANGELOG.md"
59
59
  Documentation = "https://github.com/wende/cicada#readme"
60
60
 
61
61
  [project.scripts]
62
- cicada-server = "cicada.mcp_server:main"
62
+ cicada-mcp = "cicada.mcp_server:main"
63
+ cicada-server = "cicada.mcp_server:main" # Backwards compatibility
63
64
  cicada = "cicada.setup:main"
64
65
  cicada-install = "cicada.install:main"
65
66
  cicada-clean = "cicada.clean:main"
@@ -84,6 +85,7 @@ asyncio_mode = "auto"
84
85
  asyncio_default_fixture_loop_scope = "function"
85
86
  filterwarnings = [
86
87
  "ignore:Importing 'parser.split_arg_string' is deprecated.*:DeprecationWarning",
88
+ "ignore:coroutine 'async_main' was never awaited:RuntimeWarning",
87
89
  ]
88
90
 
89
91
  [tool.coverage.run]
@@ -421,7 +421,7 @@ class TestElixirIndexerKeywordExtraction:
421
421
  MockKeywordExtractor,
422
422
  )
423
423
 
424
- indexer = ElixirIndexer()
424
+ indexer = ElixirIndexer(verbose=True)
425
425
 
426
426
  # Create a test file with documentation
427
427
  test_file = tmp_path / "test.ex"
@@ -572,7 +572,7 @@ class TestElixirIndexerProgressReporting:
572
572
 
573
573
  def test_progress_reporting_multiple_files(self, tmp_path, capsys):
574
574
  """Test that progress is reported every 10 files"""
575
- indexer = ElixirIndexer()
575
+ indexer = ElixirIndexer(verbose=True)
576
576
 
577
577
  # Create 15 test files
578
578
  for i in range(15):
@@ -688,7 +688,7 @@ class TestElixirIndexerGitignoreIntegration:
688
688
 
689
689
  def test_first_run_creates_gitignore_entry(self, tmp_path, capsys):
690
690
  """Test that first run adds .cicada/ to .gitignore"""
691
- indexer = ElixirIndexer()
691
+ indexer = ElixirIndexer(verbose=True)
692
692
 
693
693
  # Create a .gitignore file
694
694
  gitignore = tmp_path / ".gitignore"
@@ -743,7 +743,7 @@ class TestElixirIndexerSignalHandling:
743
743
  self, tmp_path, monkeypatch, capsys
744
744
  ):
745
745
  """Test that interrupted indexing saves partial progress"""
746
- indexer = ElixirIndexer()
746
+ indexer = ElixirIndexer(verbose=True)
747
747
 
748
748
  # Create multiple test files
749
749
  for i in range(5):
@@ -973,7 +973,7 @@ class TestElixirIndexerAdditionalEdgeCases:
973
973
  BrokenKeywordExtractor,
974
974
  )
975
975
 
976
- indexer = ElixirIndexer()
976
+ indexer = ElixirIndexer(verbose=True)
977
977
 
978
978
  test_file = tmp_path / "test.ex"
979
979
  test_file.write_text(