skill-seekers 2.7.3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. skill_seekers/__init__.py +22 -0
  2. skill_seekers/cli/__init__.py +39 -0
  3. skill_seekers/cli/adaptors/__init__.py +120 -0
  4. skill_seekers/cli/adaptors/base.py +221 -0
  5. skill_seekers/cli/adaptors/claude.py +485 -0
  6. skill_seekers/cli/adaptors/gemini.py +453 -0
  7. skill_seekers/cli/adaptors/markdown.py +269 -0
  8. skill_seekers/cli/adaptors/openai.py +503 -0
  9. skill_seekers/cli/ai_enhancer.py +310 -0
  10. skill_seekers/cli/api_reference_builder.py +373 -0
  11. skill_seekers/cli/architectural_pattern_detector.py +525 -0
  12. skill_seekers/cli/code_analyzer.py +1462 -0
  13. skill_seekers/cli/codebase_scraper.py +1225 -0
  14. skill_seekers/cli/config_command.py +563 -0
  15. skill_seekers/cli/config_enhancer.py +431 -0
  16. skill_seekers/cli/config_extractor.py +871 -0
  17. skill_seekers/cli/config_manager.py +452 -0
  18. skill_seekers/cli/config_validator.py +394 -0
  19. skill_seekers/cli/conflict_detector.py +528 -0
  20. skill_seekers/cli/constants.py +72 -0
  21. skill_seekers/cli/dependency_analyzer.py +757 -0
  22. skill_seekers/cli/doc_scraper.py +2332 -0
  23. skill_seekers/cli/enhance_skill.py +488 -0
  24. skill_seekers/cli/enhance_skill_local.py +1096 -0
  25. skill_seekers/cli/enhance_status.py +194 -0
  26. skill_seekers/cli/estimate_pages.py +433 -0
  27. skill_seekers/cli/generate_router.py +1209 -0
  28. skill_seekers/cli/github_fetcher.py +534 -0
  29. skill_seekers/cli/github_scraper.py +1466 -0
  30. skill_seekers/cli/guide_enhancer.py +723 -0
  31. skill_seekers/cli/how_to_guide_builder.py +1267 -0
  32. skill_seekers/cli/install_agent.py +461 -0
  33. skill_seekers/cli/install_skill.py +178 -0
  34. skill_seekers/cli/language_detector.py +614 -0
  35. skill_seekers/cli/llms_txt_detector.py +60 -0
  36. skill_seekers/cli/llms_txt_downloader.py +104 -0
  37. skill_seekers/cli/llms_txt_parser.py +150 -0
  38. skill_seekers/cli/main.py +558 -0
  39. skill_seekers/cli/markdown_cleaner.py +132 -0
  40. skill_seekers/cli/merge_sources.py +806 -0
  41. skill_seekers/cli/package_multi.py +77 -0
  42. skill_seekers/cli/package_skill.py +241 -0
  43. skill_seekers/cli/pattern_recognizer.py +1825 -0
  44. skill_seekers/cli/pdf_extractor_poc.py +1166 -0
  45. skill_seekers/cli/pdf_scraper.py +617 -0
  46. skill_seekers/cli/quality_checker.py +519 -0
  47. skill_seekers/cli/rate_limit_handler.py +438 -0
  48. skill_seekers/cli/resume_command.py +160 -0
  49. skill_seekers/cli/run_tests.py +230 -0
  50. skill_seekers/cli/setup_wizard.py +93 -0
  51. skill_seekers/cli/split_config.py +390 -0
  52. skill_seekers/cli/swift_patterns.py +560 -0
  53. skill_seekers/cli/test_example_extractor.py +1081 -0
  54. skill_seekers/cli/test_unified_simple.py +179 -0
  55. skill_seekers/cli/unified_codebase_analyzer.py +572 -0
  56. skill_seekers/cli/unified_scraper.py +932 -0
  57. skill_seekers/cli/unified_skill_builder.py +1605 -0
  58. skill_seekers/cli/upload_skill.py +162 -0
  59. skill_seekers/cli/utils.py +432 -0
  60. skill_seekers/mcp/__init__.py +33 -0
  61. skill_seekers/mcp/agent_detector.py +316 -0
  62. skill_seekers/mcp/git_repo.py +273 -0
  63. skill_seekers/mcp/server.py +231 -0
  64. skill_seekers/mcp/server_fastmcp.py +1249 -0
  65. skill_seekers/mcp/server_legacy.py +2302 -0
  66. skill_seekers/mcp/source_manager.py +285 -0
  67. skill_seekers/mcp/tools/__init__.py +115 -0
  68. skill_seekers/mcp/tools/config_tools.py +251 -0
  69. skill_seekers/mcp/tools/packaging_tools.py +826 -0
  70. skill_seekers/mcp/tools/scraping_tools.py +842 -0
  71. skill_seekers/mcp/tools/source_tools.py +828 -0
  72. skill_seekers/mcp/tools/splitting_tools.py +212 -0
  73. skill_seekers/py.typed +0 -0
  74. skill_seekers-2.7.3.dist-info/METADATA +2027 -0
  75. skill_seekers-2.7.3.dist-info/RECORD +79 -0
  76. skill_seekers-2.7.3.dist-info/WHEEL +5 -0
  77. skill_seekers-2.7.3.dist-info/entry_points.txt +19 -0
  78. skill_seekers-2.7.3.dist-info/licenses/LICENSE +21 -0
  79. skill_seekers-2.7.3.dist-info/top_level.txt +1 -0
@@ -0,0 +1,1249 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Skill Seeker MCP Server (FastMCP Implementation)
4
+
5
+ Modern, decorator-based MCP server using FastMCP for simplified tool registration.
6
+ Provides 21 tools for generating Claude AI skills from documentation.
7
+
8
+ This is a streamlined alternative to server.py (2200 lines → 708 lines, 68% reduction).
9
+ All tool implementations are delegated to modular tool files in tools/ directory.
10
+
11
+ **Architecture:**
12
+ - FastMCP server with decorator-based tool registration
13
+ - 21 tools organized into 5 categories:
14
+ * Config tools (3): generate_config, list_configs, validate_config
15
+ * Scraping tools (8): estimate_pages, scrape_docs, scrape_github, scrape_pdf, scrape_codebase, detect_patterns, extract_test_examples, build_how_to_guides, extract_config_patterns
16
+ * Packaging tools (4): package_skill, upload_skill, enhance_skill, install_skill
17
+ * Splitting tools (2): split_config, generate_router
18
+ * Source tools (4): fetch_config, submit_config, add_config_source, list_config_sources, remove_config_source
19
+
20
+ **Usage:**
21
+ # Stdio transport (default, backward compatible)
22
+ python -m skill_seekers.mcp.server_fastmcp
23
+
24
+ # HTTP transport (new)
25
+ python -m skill_seekers.mcp.server_fastmcp --http
26
+ python -m skill_seekers.mcp.server_fastmcp --http --port 8080
27
+
28
+ **MCP Integration:**
29
+ Stdio (default):
30
+ {
31
+ "mcpServers": {
32
+ "skill-seeker": {
33
+ "command": "python",
34
+ "args": ["-m", "skill_seekers.mcp.server_fastmcp"]
35
+ }
36
+ }
37
+ }
38
+
39
+ HTTP (alternative):
40
+ {
41
+ "mcpServers": {
42
+ "skill-seeker": {
43
+ "url": "http://localhost:8000/sse"
44
+ }
45
+ }
46
+ }
47
+ """
48
+
49
+ import argparse
50
+ import logging
51
+ import sys
52
+
53
+ # Import FastMCP
54
+ MCP_AVAILABLE = False
55
+ FastMCP = None
56
+
57
+ try:
58
+ from mcp.server import FastMCP
59
+
60
+ MCP_AVAILABLE = True
61
+ except ImportError as e:
62
+ # Only exit if running as main module, not when importing for tests
63
+ if __name__ == "__main__":
64
+ print("āŒ Error: mcp package not installed")
65
+ print("Install with: pip install mcp")
66
+ print(f"Import error: {e}")
67
+ sys.exit(1)
68
+
69
+ # Import all tool implementations
70
+ try:
71
+ from .tools import (
72
+ add_config_source_impl,
73
+ build_how_to_guides_impl,
74
+ detect_patterns_impl,
75
+ enhance_skill_impl,
76
+ # Scraping tools
77
+ estimate_pages_impl,
78
+ extract_config_patterns_impl,
79
+ extract_test_examples_impl,
80
+ # Source tools
81
+ fetch_config_impl,
82
+ # Config tools
83
+ generate_config_impl,
84
+ generate_router_impl,
85
+ install_skill_impl,
86
+ list_config_sources_impl,
87
+ list_configs_impl,
88
+ # Packaging tools
89
+ package_skill_impl,
90
+ remove_config_source_impl,
91
+ scrape_codebase_impl,
92
+ scrape_docs_impl,
93
+ scrape_github_impl,
94
+ scrape_pdf_impl,
95
+ # Splitting tools
96
+ split_config_impl,
97
+ submit_config_impl,
98
+ upload_skill_impl,
99
+ validate_config_impl,
100
+ )
101
+ except ImportError:
102
+ # Fallback for direct script execution
103
+ import os
104
+
105
+ sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
106
+ from tools import (
107
+ add_config_source_impl,
108
+ build_how_to_guides_impl,
109
+ detect_patterns_impl,
110
+ enhance_skill_impl,
111
+ estimate_pages_impl,
112
+ extract_config_patterns_impl,
113
+ extract_test_examples_impl,
114
+ fetch_config_impl,
115
+ generate_config_impl,
116
+ generate_router_impl,
117
+ install_skill_impl,
118
+ list_config_sources_impl,
119
+ list_configs_impl,
120
+ package_skill_impl,
121
+ remove_config_source_impl,
122
+ scrape_codebase_impl,
123
+ scrape_docs_impl,
124
+ scrape_github_impl,
125
+ scrape_pdf_impl,
126
+ split_config_impl,
127
+ submit_config_impl,
128
+ upload_skill_impl,
129
+ validate_config_impl,
130
+ )
131
+
132
+ # Initialize FastMCP server
133
+ mcp = None
134
+ if MCP_AVAILABLE and FastMCP is not None:
135
+ mcp = FastMCP(
136
+ name="skill-seeker",
137
+ instructions="Skill Seeker MCP Server - Generate Claude AI skills from documentation",
138
+ )
139
+
140
+
141
+ # Helper decorator for tests (when MCP is not available)
142
+ def safe_tool_decorator(*args, **kwargs):
143
+ """Decorator that works when mcp is None (for testing)"""
144
+ if mcp is not None:
145
+ return mcp.tool(*args, **kwargs)
146
+ else:
147
+ # Return a pass-through decorator for testing
148
+ def wrapper(func):
149
+ return func
150
+
151
+ return wrapper
152
+
153
+
154
+ # ============================================================================
155
+ # CONFIG TOOLS (3 tools)
156
+ # ============================================================================
157
+
158
+
159
+ @safe_tool_decorator(
160
+ description="Generate a config file for documentation scraping. Interactively creates a JSON config for any documentation website."
161
+ )
162
+ async def generate_config(
163
+ name: str,
164
+ url: str,
165
+ description: str,
166
+ max_pages: int = 100,
167
+ unlimited: bool = False,
168
+ rate_limit: float = 0.5,
169
+ ) -> str:
170
+ """
171
+ Generate a config file for documentation scraping.
172
+
173
+ Args:
174
+ name: Skill name (lowercase, alphanumeric, hyphens, underscores)
175
+ url: Base documentation URL (must include http:// or https://)
176
+ description: Description of when to use this skill
177
+ max_pages: Maximum pages to scrape (default: 100, use -1 for unlimited)
178
+ unlimited: Remove all limits - scrape all pages (default: false). Overrides max_pages.
179
+ rate_limit: Delay between requests in seconds (default: 0.5)
180
+
181
+ Returns:
182
+ Success message with config path and next steps, or error message.
183
+ """
184
+ args = {
185
+ "name": name,
186
+ "url": url,
187
+ "description": description,
188
+ "max_pages": max_pages,
189
+ "unlimited": unlimited,
190
+ "rate_limit": rate_limit,
191
+ }
192
+ result = await generate_config_impl(args)
193
+ # Extract text from TextContent objects
194
+ if isinstance(result, list) and result:
195
+ return result[0].text if hasattr(result[0], "text") else str(result[0])
196
+ return str(result)
197
+
198
+
199
+ @safe_tool_decorator(description="List all available preset configurations.")
200
+ async def list_configs() -> str:
201
+ """
202
+ List all available preset configurations.
203
+
204
+ Returns:
205
+ List of available configs with categories and descriptions.
206
+ """
207
+ result = await list_configs_impl({})
208
+ if isinstance(result, list) and result:
209
+ return result[0].text if hasattr(result[0], "text") else str(result[0])
210
+ return str(result)
211
+
212
+
213
+ @safe_tool_decorator(description="Validate a config file for errors.")
214
+ async def validate_config(config_path: str) -> str:
215
+ """
216
+ Validate a config file for errors.
217
+
218
+ Args:
219
+ config_path: Path to config JSON file
220
+
221
+ Returns:
222
+ Validation result with any errors or success message.
223
+ """
224
+ result = await validate_config_impl({"config_path": config_path})
225
+ if isinstance(result, list) and result:
226
+ return result[0].text if hasattr(result[0], "text") else str(result[0])
227
+ return str(result)
228
+
229
+
230
+ # ============================================================================
231
+ # SCRAPING TOOLS (4 tools)
232
+ # ============================================================================
233
+
234
+
235
+ @safe_tool_decorator(
236
+ description="Estimate how many pages will be scraped from a config. Fast preview without downloading content."
237
+ )
238
+ async def estimate_pages(
239
+ config_path: str,
240
+ max_discovery: int = 1000,
241
+ unlimited: bool = False,
242
+ ) -> str:
243
+ """
244
+ Estimate how many pages will be scraped from a config.
245
+
246
+ Args:
247
+ config_path: Path to config JSON file (e.g., configs/react.json)
248
+ max_discovery: Maximum pages to discover during estimation (default: 1000, use -1 for unlimited)
249
+ unlimited: Remove discovery limit - estimate all pages (default: false). Overrides max_discovery.
250
+
251
+ Returns:
252
+ Estimation results with page count and recommendations.
253
+ """
254
+ args = {
255
+ "config_path": config_path,
256
+ "max_discovery": max_discovery,
257
+ "unlimited": unlimited,
258
+ }
259
+ result = await estimate_pages_impl(args)
260
+ if isinstance(result, list) and result:
261
+ return result[0].text if hasattr(result[0], "text") else str(result[0])
262
+ return str(result)
263
+
264
+
265
+ @safe_tool_decorator(
266
+ description="Scrape documentation and build Claude skill. Supports both single-source (legacy) and unified multi-source configs. Creates SKILL.md and reference files. Automatically detects llms.txt files for 10x faster processing. Falls back to HTML scraping if not available."
267
+ )
268
+ async def scrape_docs(
269
+ config_path: str,
270
+ unlimited: bool = False,
271
+ enhance_local: bool = False,
272
+ skip_scrape: bool = False,
273
+ dry_run: bool = False,
274
+ merge_mode: str | None = None,
275
+ ) -> str:
276
+ """
277
+ Scrape documentation and build Claude skill.
278
+
279
+ Args:
280
+ config_path: Path to config JSON file (e.g., configs/react.json or configs/godot_unified.json)
281
+ unlimited: Remove page limit - scrape all pages (default: false). Overrides max_pages in config.
282
+ enhance_local: Open terminal for local enhancement with Claude Code (default: false)
283
+ skip_scrape: Skip scraping, use cached data (default: false)
284
+ dry_run: Preview what will be scraped without saving (default: false)
285
+ merge_mode: Override merge mode for unified configs: 'rule-based' or 'claude-enhanced' (default: from config)
286
+
287
+ Returns:
288
+ Scraping results with file paths and statistics.
289
+ """
290
+ args = {
291
+ "config_path": config_path,
292
+ "unlimited": unlimited,
293
+ "enhance_local": enhance_local,
294
+ "skip_scrape": skip_scrape,
295
+ "dry_run": dry_run,
296
+ }
297
+ if merge_mode:
298
+ args["merge_mode"] = merge_mode
299
+ result = await scrape_docs_impl(args)
300
+ if isinstance(result, list) and result:
301
+ return result[0].text if hasattr(result[0], "text") else str(result[0])
302
+ return str(result)
303
+
304
+
305
+ @safe_tool_decorator(
306
+ description="Scrape GitHub repository and build Claude skill. Extracts README, Issues, Changelog, Releases, and code structure."
307
+ )
308
+ async def scrape_github(
309
+ repo: str | None = None,
310
+ config_path: str | None = None,
311
+ name: str | None = None,
312
+ description: str | None = None,
313
+ token: str | None = None,
314
+ no_issues: bool = False,
315
+ no_changelog: bool = False,
316
+ no_releases: bool = False,
317
+ max_issues: int = 100,
318
+ scrape_only: bool = False,
319
+ ) -> str:
320
+ """
321
+ Scrape GitHub repository and build Claude skill.
322
+
323
+ Args:
324
+ repo: GitHub repository (owner/repo, e.g., facebook/react)
325
+ config_path: Path to GitHub config JSON file (e.g., configs/react_github.json)
326
+ name: Skill name (default: repo name)
327
+ description: Skill description
328
+ token: GitHub personal access token (or use GITHUB_TOKEN env var)
329
+ no_issues: Skip GitHub issues extraction (default: false)
330
+ no_changelog: Skip CHANGELOG extraction (default: false)
331
+ no_releases: Skip releases extraction (default: false)
332
+ max_issues: Maximum issues to fetch (default: 100)
333
+ scrape_only: Only scrape, don't build skill (default: false)
334
+
335
+ Returns:
336
+ GitHub scraping results with file paths.
337
+ """
338
+ args = {}
339
+ if repo:
340
+ args["repo"] = repo
341
+ if config_path:
342
+ args["config_path"] = config_path
343
+ if name:
344
+ args["name"] = name
345
+ if description:
346
+ args["description"] = description
347
+ if token:
348
+ args["token"] = token
349
+ args["no_issues"] = no_issues
350
+ args["no_changelog"] = no_changelog
351
+ args["no_releases"] = no_releases
352
+ args["max_issues"] = max_issues
353
+ args["scrape_only"] = scrape_only
354
+
355
+ result = await scrape_github_impl(args)
356
+ if isinstance(result, list) and result:
357
+ return result[0].text if hasattr(result[0], "text") else str(result[0])
358
+ return str(result)
359
+
360
+
361
+ @safe_tool_decorator(
362
+ description="Scrape PDF documentation and build Claude skill. Extracts text, code, and images from PDF files."
363
+ )
364
+ async def scrape_pdf(
365
+ config_path: str | None = None,
366
+ pdf_path: str | None = None,
367
+ name: str | None = None,
368
+ description: str | None = None,
369
+ from_json: str | None = None,
370
+ ) -> str:
371
+ """
372
+ Scrape PDF documentation and build Claude skill.
373
+
374
+ Args:
375
+ config_path: Path to PDF config JSON file (e.g., configs/manual_pdf.json)
376
+ pdf_path: Direct PDF path (alternative to config_path)
377
+ name: Skill name (required with pdf_path)
378
+ description: Skill description (optional)
379
+ from_json: Build from extracted JSON file (e.g., output/manual_extracted.json)
380
+
381
+ Returns:
382
+ PDF scraping results with file paths.
383
+ """
384
+ args = {}
385
+ if config_path:
386
+ args["config_path"] = config_path
387
+ if pdf_path:
388
+ args["pdf_path"] = pdf_path
389
+ if name:
390
+ args["name"] = name
391
+ if description:
392
+ args["description"] = description
393
+ if from_json:
394
+ args["from_json"] = from_json
395
+
396
+ result = await scrape_pdf_impl(args)
397
+ if isinstance(result, list) and result:
398
+ return result[0].text if hasattr(result[0], "text") else str(result[0])
399
+ return str(result)
400
+
401
+
402
+ @safe_tool_decorator(
403
+ description="Analyze local codebase and extract code knowledge. Walks directory tree, analyzes code files, extracts signatures, docstrings, and optionally generates API reference documentation and dependency graphs."
404
+ )
405
+ async def scrape_codebase(
406
+ directory: str,
407
+ output: str = "output/codebase/",
408
+ depth: str = "deep",
409
+ languages: str = "",
410
+ file_patterns: str = "",
411
+ build_api_reference: bool = False,
412
+ build_dependency_graph: bool = False,
413
+ ) -> str:
414
+ """
415
+ Analyze local codebase and extract code knowledge.
416
+
417
+ Args:
418
+ directory: Directory to analyze (required)
419
+ output: Output directory for results (default: output/codebase/)
420
+ depth: Analysis depth - surface, deep, full (default: deep)
421
+ languages: Comma-separated languages to analyze (e.g., "Python,JavaScript,C++")
422
+ file_patterns: Comma-separated file patterns (e.g., "*.py,src/**/*.js")
423
+ build_api_reference: Generate API reference markdown (default: false)
424
+ build_dependency_graph: Generate dependency graph and detect circular dependencies (default: false)
425
+
426
+ Returns:
427
+ Codebase analysis results with file paths.
428
+ """
429
+ args = {
430
+ "directory": directory,
431
+ "output": output,
432
+ "depth": depth,
433
+ "languages": languages,
434
+ "file_patterns": file_patterns,
435
+ "build_api_reference": build_api_reference,
436
+ "build_dependency_graph": build_dependency_graph,
437
+ }
438
+
439
+ result = await scrape_codebase_impl(args)
440
+ if isinstance(result, list) and result:
441
+ return result[0].text if hasattr(result[0], "text") else str(result[0])
442
+ return str(result)
443
+
444
+
445
+ @safe_tool_decorator(
446
+ description="Detect design patterns in source code (Singleton, Factory, Observer, Strategy, Decorator, Builder, Adapter, Command, Template Method, Chain of Responsibility). Supports 9 languages: Python, JavaScript, TypeScript, C++, C, C#, Go, Rust, Java, Ruby, PHP."
447
+ )
448
+ async def detect_patterns(
449
+ file: str = "",
450
+ directory: str = "",
451
+ output: str = "",
452
+ depth: str = "deep",
453
+ json: bool = False,
454
+ ) -> str:
455
+ """
456
+ Detect design patterns in source code.
457
+
458
+ Analyzes source files or directories to identify common design patterns.
459
+ Provides confidence scores and evidence for each detected pattern.
460
+
461
+ Args:
462
+ file: Single file to analyze (optional)
463
+ directory: Directory to analyze all source files (optional)
464
+ output: Output directory for JSON results (optional)
465
+ depth: Detection depth - surface (fast), deep (balanced), full (thorough). Default: deep
466
+ json: Output JSON format instead of human-readable (default: false)
467
+
468
+ Returns:
469
+ Pattern detection results with confidence scores and evidence.
470
+
471
+ Example:
472
+ detect_patterns(file="src/database.py", depth="deep")
473
+ detect_patterns(directory="src/", output="patterns/", json=true)
474
+ """
475
+ args = {
476
+ "file": file,
477
+ "directory": directory,
478
+ "output": output,
479
+ "depth": depth,
480
+ "json": json,
481
+ }
482
+
483
+ result = await detect_patterns_impl(args)
484
+ if isinstance(result, list) and result:
485
+ return result[0].text if hasattr(result[0], "text") else str(result[0])
486
+ return str(result)
487
+
488
+
489
+ @safe_tool_decorator(
490
+ description="Extract usage examples from test files. Analyzes test files to extract real API usage patterns including instantiation, method calls, configs, setup patterns, and workflows. Supports 9 languages (Python AST-based, others regex-based)."
491
+ )
492
+ async def extract_test_examples(
493
+ file: str = "",
494
+ directory: str = "",
495
+ language: str = "",
496
+ min_confidence: float = 0.5,
497
+ max_per_file: int = 10,
498
+ json: bool = False,
499
+ markdown: bool = False,
500
+ ) -> str:
501
+ """
502
+ Extract usage examples from test files.
503
+
504
+ Analyzes test files to extract real API usage patterns including:
505
+ - Object instantiation with real parameters
506
+ - Method calls with expected behaviors
507
+ - Configuration examples
508
+ - Setup patterns from fixtures/setUp()
509
+ - Multi-step workflows from integration tests
510
+
511
+ Supports 9 languages: Python (AST-based), JavaScript, TypeScript, Go, Rust, Java, C#, PHP, Ruby.
512
+
513
+ Args:
514
+ file: Single test file to analyze (optional)
515
+ directory: Directory containing test files (optional)
516
+ language: Filter by language (python, javascript, etc.)
517
+ min_confidence: Minimum confidence threshold 0.0-1.0 (default: 0.5)
518
+ max_per_file: Maximum examples per file (default: 10)
519
+ json: Output JSON format (default: false)
520
+ markdown: Output Markdown format (default: false)
521
+
522
+ Examples:
523
+ extract_test_examples(directory="tests/", language="python")
524
+ extract_test_examples(file="tests/test_scraper.py", json=true)
525
+ """
526
+ args = {
527
+ "file": file,
528
+ "directory": directory,
529
+ "language": language,
530
+ "min_confidence": min_confidence,
531
+ "max_per_file": max_per_file,
532
+ "json": json,
533
+ "markdown": markdown,
534
+ }
535
+
536
+ result = await extract_test_examples_impl(args)
537
+ if isinstance(result, list) and result:
538
+ return result[0].text if hasattr(result[0], "text") else str(result[0])
539
+ return str(result)
540
+
541
+
542
+ @safe_tool_decorator(
543
+ description="Build how-to guides from workflow test examples. Transforms workflow examples extracted from test files into step-by-step educational guides with prerequisites, verification points, and troubleshooting tips."
544
+ )
545
+ async def build_how_to_guides(
546
+ input: str,
547
+ output: str = "output/codebase/tutorials",
548
+ group_by: str = "ai-tutorial-group",
549
+ no_ai: bool = False,
550
+ json_output: bool = False,
551
+ ) -> str:
552
+ """
553
+ Build how-to guides from workflow test examples.
554
+
555
+ Transforms workflow examples extracted from test files into step-by-step
556
+ educational guides. Automatically groups related workflows, extracts steps,
557
+ and generates comprehensive markdown guides.
558
+
559
+ Features:
560
+ - Python AST-based step extraction (heuristic for other languages)
561
+ - 4 grouping strategies: ai-tutorial-group, file-path, test-name, complexity
562
+ - Detects prerequisites, setup code, and verification points
563
+ - Generates troubleshooting tips and next steps
564
+
565
+ Args:
566
+ input: Path to test_examples.json from extract_test_examples
567
+ output: Output directory for guides (default: output/codebase/tutorials)
568
+ group_by: Grouping strategy - ai-tutorial-group, file-path, test-name, complexity (default: ai-tutorial-group)
569
+ no_ai: Disable AI enhancement for grouping (default: false)
570
+ json_output: Output JSON format alongside markdown (default: false)
571
+
572
+ Examples:
573
+ build_how_to_guides(input="output/codebase/test_examples/test_examples.json")
574
+ build_how_to_guides(input="examples.json", group_by="file-path", no_ai=true)
575
+ """
576
+ args = {
577
+ "input": input,
578
+ "output": output,
579
+ "group_by": group_by,
580
+ "no_ai": no_ai,
581
+ "json_output": json_output,
582
+ }
583
+
584
+ result = await build_how_to_guides_impl(args)
585
+ if isinstance(result, list) and result:
586
+ return result[0].text if hasattr(result[0], "text") else str(result[0])
587
+ return str(result)
588
+
589
+
590
+ @safe_tool_decorator(
591
+ description="Extract configuration patterns from config files (C3.4) with optional AI enhancement. Analyzes config files, detects patterns (database, API, logging, etc.), generates documentation, and optionally enhances with AI insights (security analysis, best practices, migration suggestions). Supports 9 formats."
592
+ )
593
+ async def extract_config_patterns(
594
+ directory: str,
595
+ output: str = "output/codebase/config_patterns",
596
+ max_files: int = 100,
597
+ enhance: bool = False,
598
+ enhance_local: bool = False,
599
+ ai_mode: str = "none",
600
+ json: bool = True,
601
+ markdown: bool = True,
602
+ ) -> str:
603
+ """
604
+ Extract configuration patterns from config files with optional AI enhancement.
605
+
606
+ Analyzes configuration files in the codebase to extract settings,
607
+ detect common patterns, and generate comprehensive documentation.
608
+
609
+ **AI Enhancement (NEW)**: Optional AI-powered insights including:
610
+ - Explanations of what each config does
611
+ - Best practice suggestions
612
+ - Security analysis (hardcoded secrets, exposed credentials)
613
+ - Migration suggestions (consolidation opportunities)
614
+ - Context-aware documentation
615
+
616
+ Supports 9 config formats: JSON, YAML, TOML, ENV, INI, Python modules,
617
+ JavaScript/TypeScript configs, Dockerfile, Docker Compose.
618
+
619
+ Detects 7 common patterns:
620
+ - Database configuration (host, port, credentials)
621
+ - API configuration (endpoints, keys, timeouts)
622
+ - Logging configuration (level, format, handlers)
623
+ - Cache configuration (backend, TTL, keys)
624
+ - Email configuration (SMTP, credentials)
625
+ - Authentication configuration (providers, secrets)
626
+ - Server configuration (host, port, workers)
627
+
628
+ Args:
629
+ directory: Directory to analyze (required)
630
+ output: Output directory for results (default: output/codebase/config_patterns)
631
+ max_files: Maximum config files to process (default: 100)
632
+ enhance: Enable AI enhancement - API mode (default: false, requires ANTHROPIC_API_KEY)
633
+ enhance_local: Enable AI enhancement - LOCAL mode (default: false, uses Claude Code CLI)
634
+ ai_mode: AI enhancement mode - auto, api, local, none (default: none)
635
+ json: Output JSON format (default: true)
636
+ markdown: Output Markdown format (default: true)
637
+
638
+ Returns:
639
+ Config extraction results with patterns, settings, and optional AI insights.
640
+
641
+ Examples:
642
+ extract_config_patterns(directory=".")
643
+ extract_config_patterns(directory="/path/to/repo", max_files=50)
644
+ extract_config_patterns(directory=".", enhance_local=true) # With AI enhancement (LOCAL mode)
645
+ extract_config_patterns(directory=".", ai_mode="api") # With AI enhancement (API mode)
646
+ """
647
+ args = {
648
+ "directory": directory,
649
+ "output": output,
650
+ "max_files": max_files,
651
+ "enhance": enhance,
652
+ "enhance_local": enhance_local,
653
+ "ai_mode": ai_mode,
654
+ "json": json,
655
+ "markdown": markdown,
656
+ }
657
+
658
+ result = await extract_config_patterns_impl(args)
659
+ if isinstance(result, list) and result:
660
+ return result[0].text if hasattr(result[0], "text") else str(result[0])
661
+ return str(result)
662
+
663
+
664
+ # ============================================================================
665
+ # PACKAGING TOOLS (4 tools)
666
+ # ============================================================================
667
+
668
+
669
+ @safe_tool_decorator(
670
+ description="Package skill directory into platform-specific format (ZIP for Claude/OpenAI/Markdown, tar.gz for Gemini). Supports all platforms: claude, gemini, openai, markdown. Automatically uploads if platform API key is set."
671
+ )
672
+ async def package_skill(
673
+ skill_dir: str,
674
+ target: str = "claude",
675
+ auto_upload: bool = True,
676
+ ) -> str:
677
+ """
678
+ Package skill directory for target LLM platform.
679
+
680
+ Args:
681
+ skill_dir: Path to skill directory to package (e.g., output/react/)
682
+ target: Target platform (default: 'claude'). Options: claude, gemini, openai, markdown
683
+ auto_upload: Auto-upload after packaging if API key is available (default: true). Requires platform-specific API key: ANTHROPIC_API_KEY, GOOGLE_API_KEY, or OPENAI_API_KEY.
684
+
685
+ Returns:
686
+ Packaging results with file path and platform info.
687
+ """
688
+ args = {
689
+ "skill_dir": skill_dir,
690
+ "target": target,
691
+ "auto_upload": auto_upload,
692
+ }
693
+ result = await package_skill_impl(args)
694
+ if isinstance(result, list) and result:
695
+ return result[0].text if hasattr(result[0], "text") else str(result[0])
696
+ return str(result)
697
+
698
+
699
+ @safe_tool_decorator(
700
+ description="Upload skill package to target LLM platform API. Requires platform-specific API key. Supports: claude (Anthropic Skills API), gemini (Google Files API), openai (Assistants API). Does NOT support markdown."
701
+ )
702
+ async def upload_skill(
703
+ skill_zip: str,
704
+ target: str = "claude",
705
+ api_key: str | None = None,
706
+ ) -> str:
707
+ """
708
+ Upload skill package to target platform.
709
+
710
+ Args:
711
+ skill_zip: Path to skill package (.zip or .tar.gz, e.g., output/react.zip)
712
+ target: Target platform (default: 'claude'). Options: claude, gemini, openai
713
+ api_key: Optional API key (uses env var if not provided: ANTHROPIC_API_KEY, GOOGLE_API_KEY, or OPENAI_API_KEY)
714
+
715
+ Returns:
716
+ Upload results with skill ID and platform URL.
717
+ """
718
+ args = {
719
+ "skill_zip": skill_zip,
720
+ "target": target,
721
+ }
722
+ if api_key:
723
+ args["api_key"] = api_key
724
+
725
+ result = await upload_skill_impl(args)
726
+ if isinstance(result, list) and result:
727
+ return result[0].text if hasattr(result[0], "text") else str(result[0])
728
+ return str(result)
729
+
730
+
731
+ @safe_tool_decorator(
732
+ description="Enhance SKILL.md with AI using target platform's model. Local mode uses Claude Code Max (no API key). API mode uses platform API (requires key). Transforms basic templates into comprehensive 500+ line guides with examples."
733
+ )
734
+ async def enhance_skill(
735
+ skill_dir: str,
736
+ target: str = "claude",
737
+ mode: str = "local",
738
+ api_key: str | None = None,
739
+ ) -> str:
740
+ """
741
+ Enhance SKILL.md with AI.
742
+
743
+ Args:
744
+ skill_dir: Path to skill directory containing SKILL.md (e.g., output/react/)
745
+ target: Target platform (default: 'claude'). Options: claude, gemini, openai
746
+ mode: Enhancement mode (default: 'local'). Options: local (Claude Code, no API), api (uses platform API)
747
+ api_key: Optional API key for 'api' mode (uses env var if not provided: ANTHROPIC_API_KEY, GOOGLE_API_KEY, or OPENAI_API_KEY)
748
+
749
+ Returns:
750
+ Enhancement results with backup location.
751
+ """
752
+ args = {
753
+ "skill_dir": skill_dir,
754
+ "target": target,
755
+ "mode": mode,
756
+ }
757
+ if api_key:
758
+ args["api_key"] = api_key
759
+
760
+ result = await enhance_skill_impl(args)
761
+ if isinstance(result, list) and result:
762
+ return result[0].text if hasattr(result[0], "text") else str(result[0])
763
+ return str(result)
764
+
765
+
766
+ @safe_tool_decorator(
767
+ description="Complete one-command workflow: fetch config → scrape docs → AI enhance (MANDATORY) → package → upload. Enhancement required for quality (3/10→9/10). Takes 20-45 min depending on config size. Supports multiple LLM platforms: claude (default), gemini, openai, markdown. Auto-uploads if platform API key is set."
768
+ )
769
+ async def install_skill(
770
+ config_name: str | None = None,
771
+ config_path: str | None = None,
772
+ destination: str = "output",
773
+ auto_upload: bool = True,
774
+ unlimited: bool = False,
775
+ dry_run: bool = False,
776
+ target: str = "claude",
777
+ ) -> str:
778
+ """
779
+ Complete one-command workflow to install a skill.
780
+
781
+ Args:
782
+ config_name: Config name from API (e.g., 'react', 'django'). Mutually exclusive with config_path. Tool will fetch this config from the official API before scraping.
783
+ config_path: Path to existing config JSON file (e.g., 'configs/custom.json'). Mutually exclusive with config_name. Use this if you already have a config file.
784
+ destination: Output directory for skill files (default: 'output')
785
+ auto_upload: Auto-upload after packaging (requires platform API key). Default: true. Set to false to skip upload.
786
+ unlimited: Remove page limits during scraping (default: false). WARNING: Can take hours for large sites.
787
+ dry_run: Preview workflow without executing (default: false). Shows all phases that would run.
788
+ target: Target LLM platform (default: 'claude'). Options: claude, gemini, openai, markdown. Requires corresponding API key: ANTHROPIC_API_KEY, GOOGLE_API_KEY, or OPENAI_API_KEY.
789
+
790
+ Returns:
791
+ Workflow results with all phase statuses.
792
+ """
793
+ args = {
794
+ "destination": destination,
795
+ "auto_upload": auto_upload,
796
+ "unlimited": unlimited,
797
+ "dry_run": dry_run,
798
+ "target": target,
799
+ }
800
+ if config_name:
801
+ args["config_name"] = config_name
802
+ if config_path:
803
+ args["config_path"] = config_path
804
+
805
+ result = await install_skill_impl(args)
806
+ if isinstance(result, list) and result:
807
+ return result[0].text if hasattr(result[0], "text") else str(result[0])
808
+ return str(result)
809
+
810
+
811
+ # ============================================================================
812
+ # SPLITTING TOOLS (2 tools)
813
+ # ============================================================================
814
+
815
+
816
+ @safe_tool_decorator(
817
+ description="Split large configs into multiple focused skills. Supports documentation (10K+ pages) and unified multi-source configs. Auto-detects config type and recommends best strategy."
818
+ )
819
+ async def split_config(
820
+ config_path: str,
821
+ strategy: str = "auto",
822
+ target_pages: int = 5000,
823
+ dry_run: bool = False,
824
+ ) -> str:
825
+ """
826
+ Split large configs into multiple skills.
827
+
828
+ Supports:
829
+ - Documentation configs: Split by categories, size, or create router skills
830
+ - Unified configs: Split by source type (documentation, github, pdf)
831
+
832
+ Args:
833
+ config_path: Path to config JSON file (e.g., configs/godot.json or configs/react_unified.json)
834
+ strategy: Split strategy: auto, none, source, category, router, size (default: auto). 'source' is for unified configs.
835
+ target_pages: Target pages per skill for doc configs (default: 5000)
836
+ dry_run: Preview without saving files (default: false)
837
+
838
+ Returns:
839
+ Splitting results with generated config paths.
840
+ """
841
+ args = {
842
+ "config_path": config_path,
843
+ "strategy": strategy,
844
+ "target_pages": target_pages,
845
+ "dry_run": dry_run,
846
+ }
847
+ result = await split_config_impl(args)
848
+ if isinstance(result, list) and result:
849
+ return result[0].text if hasattr(result[0], "text") else str(result[0])
850
+ return str(result)
851
+
852
+
853
+ @safe_tool_decorator(
854
+ description="Generate router/hub skill for split documentation. Creates intelligent routing to sub-skills."
855
+ )
856
+ async def generate_router(
857
+ config_pattern: str,
858
+ router_name: str | None = None,
859
+ ) -> str:
860
+ """
861
+ Generate router/hub skill for split documentation.
862
+
863
+ Args:
864
+ config_pattern: Config pattern for sub-skills (e.g., 'configs/godot-*.json')
865
+ router_name: Router skill name (optional, inferred from configs)
866
+
867
+ Returns:
868
+ Router generation results with file paths.
869
+ """
870
+ args = {"config_pattern": config_pattern}
871
+ if router_name:
872
+ args["router_name"] = router_name
873
+
874
+ result = await generate_router_impl(args)
875
+ if isinstance(result, list) and result:
876
+ return result[0].text if hasattr(result[0], "text") else str(result[0])
877
+ return str(result)
878
+
879
+
880
+ # ============================================================================
881
+ # SOURCE TOOLS (5 tools)
882
+ # ============================================================================
883
+
884
+
885
+ @safe_tool_decorator(
886
+ description="Fetch config from API, git URL, or registered source. Supports three modes: (1) Named source from registry, (2) Direct git URL, (3) API (default). List available configs or download a specific one by name."
887
+ )
888
+ async def fetch_config(
889
+ config_name: str | None = None,
890
+ destination: str = "configs",
891
+ list_available: bool = False,
892
+ category: str | None = None,
893
+ git_url: str | None = None,
894
+ source: str | None = None,
895
+ branch: str = "main",
896
+ token: str | None = None,
897
+ refresh: bool = False,
898
+ ) -> str:
899
+ """
900
+ Fetch config from API, git URL, or registered source.
901
+
902
+ Args:
903
+ config_name: Name of the config to download (e.g., 'react', 'django', 'godot'). Required for git modes. Omit to list all available configs in API mode.
904
+ destination: Directory to save the config file (default: 'configs/')
905
+ list_available: List all available configs from the API (only works in API mode, default: false)
906
+ category: Filter configs by category when listing in API mode (e.g., 'web-frameworks', 'game-engines', 'devops')
907
+ git_url: Git repository URL containing configs. If provided, fetches from git instead of API. Supports HTTPS and SSH URLs. Example: 'https://github.com/myorg/configs.git'
908
+ source: Named source from registry (highest priority). Use add_config_source to register sources first. Example: 'team', 'company'
909
+ branch: Git branch to use (default: 'main'). Only used with git_url or source.
910
+ token: Authentication token for private repos (optional). Prefer using environment variables (GITHUB_TOKEN, GITLAB_TOKEN, etc.).
911
+ refresh: Force refresh cached git repository (default: false). Deletes cache and re-clones. Only used with git modes.
912
+
913
+ Returns:
914
+ Fetch results with config path or list of available configs.
915
+ """
916
+ args = {
917
+ "destination": destination,
918
+ "list_available": list_available,
919
+ "branch": branch,
920
+ "refresh": refresh,
921
+ }
922
+ if config_name:
923
+ args["config_name"] = config_name
924
+ if category:
925
+ args["category"] = category
926
+ if git_url:
927
+ args["git_url"] = git_url
928
+ if source:
929
+ args["source"] = source
930
+ if token:
931
+ args["token"] = token
932
+
933
+ result = await fetch_config_impl(args)
934
+ if isinstance(result, list) and result:
935
+ return result[0].text if hasattr(result[0], "text") else str(result[0])
936
+ return str(result)
937
+
938
+
939
+ @safe_tool_decorator(
940
+ description="Submit a custom config file to the community. Validates config (legacy or unified format) and creates a GitHub issue in skill-seekers-configs repo for review."
941
+ )
942
+ async def submit_config(
943
+ config_path: str | None = None,
944
+ config_json: str | None = None,
945
+ testing_notes: str | None = None,
946
+ github_token: str | None = None,
947
+ ) -> str:
948
+ """
949
+ Submit a custom config file to the community.
950
+
951
+ Args:
952
+ config_path: Path to config JSON file to submit (e.g., 'configs/myframework.json')
953
+ config_json: Config JSON as string (alternative to config_path)
954
+ testing_notes: Notes about testing (e.g., 'Tested with 20 pages, works well')
955
+ github_token: GitHub personal access token (or use GITHUB_TOKEN env var)
956
+
957
+ Returns:
958
+ Submission results with GitHub issue URL.
959
+ """
960
+ args = {}
961
+ if config_path:
962
+ args["config_path"] = config_path
963
+ if config_json:
964
+ args["config_json"] = config_json
965
+ if testing_notes:
966
+ args["testing_notes"] = testing_notes
967
+ if github_token:
968
+ args["github_token"] = github_token
969
+
970
+ result = await submit_config_impl(args)
971
+ if isinstance(result, list) and result:
972
+ return result[0].text if hasattr(result[0], "text") else str(result[0])
973
+ return str(result)
974
+
975
+
976
+ @safe_tool_decorator(
977
+ description="Register a git repository as a config source. Allows fetching configs from private/team repos. Use this to set up named sources that can be referenced by fetch_config. Supports GitHub, GitLab, Gitea, Bitbucket, and custom git servers."
978
+ )
979
+ async def add_config_source(
980
+ name: str,
981
+ git_url: str,
982
+ source_type: str = "github",
983
+ token_env: str | None = None,
984
+ branch: str = "main",
985
+ priority: int = 100,
986
+ enabled: bool = True,
987
+ ) -> str:
988
+ """
989
+ Register a git repository as a config source.
990
+
991
+ Args:
992
+ name: Source identifier (lowercase, alphanumeric, hyphens/underscores allowed). Example: 'team', 'company-internal', 'my_configs'
993
+ git_url: Git repository URL (HTTPS or SSH). Example: 'https://github.com/myorg/configs.git' or 'git@github.com:myorg/configs.git'
994
+ source_type: Source type (default: 'github'). Options: 'github', 'gitlab', 'gitea', 'bitbucket', 'custom'
995
+ token_env: Environment variable name for auth token (optional). Auto-detected if not provided. Example: 'GITHUB_TOKEN', 'GITLAB_TOKEN', 'MY_CUSTOM_TOKEN'
996
+ branch: Git branch to use (default: 'main'). Example: 'main', 'master', 'develop'
997
+ priority: Source priority (lower = higher priority, default: 100). Used for conflict resolution when same config exists in multiple sources.
998
+ enabled: Whether source is enabled (default: true)
999
+
1000
+ Returns:
1001
+ Registration results with source details.
1002
+ """
1003
+ args = {
1004
+ "name": name,
1005
+ "git_url": git_url,
1006
+ "source_type": source_type,
1007
+ "branch": branch,
1008
+ "priority": priority,
1009
+ "enabled": enabled,
1010
+ }
1011
+ if token_env:
1012
+ args["token_env"] = token_env
1013
+
1014
+ result = await add_config_source_impl(args)
1015
+ if isinstance(result, list) and result:
1016
+ return result[0].text if hasattr(result[0], "text") else str(result[0])
1017
+ return str(result)
1018
+
1019
+
1020
+ @safe_tool_decorator(
1021
+ description="List all registered config sources. Shows git repositories that have been registered with add_config_source. Use this to see available sources for fetch_config."
1022
+ )
1023
+ async def list_config_sources(enabled_only: bool = False) -> str:
1024
+ """
1025
+ List all registered config sources.
1026
+
1027
+ Args:
1028
+ enabled_only: Only show enabled sources (default: false)
1029
+
1030
+ Returns:
1031
+ List of registered sources with details.
1032
+ """
1033
+ result = await list_config_sources_impl({"enabled_only": enabled_only})
1034
+ if isinstance(result, list) and result:
1035
+ return result[0].text if hasattr(result[0], "text") else str(result[0])
1036
+ return str(result)
1037
+
1038
+
1039
+ @safe_tool_decorator(
1040
+ description="Remove a registered config source. Deletes the source from the registry. Does not delete cached git repository data."
1041
+ )
1042
+ async def remove_config_source(name: str) -> str:
1043
+ """
1044
+ Remove a registered config source.
1045
+
1046
+ Args:
1047
+ name: Source identifier to remove. Example: 'team', 'company-internal'
1048
+
1049
+ Returns:
1050
+ Removal results with success/error message.
1051
+ """
1052
+ result = await remove_config_source_impl({"name": name})
1053
+ if isinstance(result, list) and result:
1054
+ return result[0].text if hasattr(result[0], "text") else str(result[0])
1055
+ return str(result)
1056
+
1057
+
1058
+ # ============================================================================
1059
+ # MAIN ENTRY POINT
1060
+ # ============================================================================
1061
+
1062
+
1063
+ def parse_args():
1064
+ """Parse command-line arguments."""
1065
+ parser = argparse.ArgumentParser(
1066
+ description="Skill Seeker MCP Server - Generate Claude AI skills from documentation",
1067
+ formatter_class=argparse.RawDescriptionHelpFormatter,
1068
+ epilog="""
1069
+ Transport Modes:
1070
+ stdio (default): Standard input/output communication for Claude Desktop
1071
+ http: HTTP server with SSE for web-based MCP clients
1072
+
1073
+ Examples:
1074
+ # Stdio transport (default, backward compatible)
1075
+ python -m skill_seekers.mcp.server_fastmcp
1076
+
1077
+ # HTTP transport on default port 8000
1078
+ python -m skill_seekers.mcp.server_fastmcp --http
1079
+
1080
+ # HTTP transport on custom port
1081
+ python -m skill_seekers.mcp.server_fastmcp --http --port 8080
1082
+
1083
+ # Debug logging
1084
+ python -m skill_seekers.mcp.server_fastmcp --http --log-level DEBUG
1085
+ """,
1086
+ )
1087
+
1088
+ parser.add_argument(
1089
+ "--http",
1090
+ action="store_true",
1091
+ help="Use HTTP transport instead of stdio (default: stdio)",
1092
+ )
1093
+
1094
+ parser.add_argument(
1095
+ "--port",
1096
+ type=int,
1097
+ default=8000,
1098
+ help="Port for HTTP server (default: 8000)",
1099
+ )
1100
+
1101
+ parser.add_argument(
1102
+ "--host",
1103
+ type=str,
1104
+ default="127.0.0.1",
1105
+ help="Host for HTTP server (default: 127.0.0.1)",
1106
+ )
1107
+
1108
+ parser.add_argument(
1109
+ "--log-level",
1110
+ type=str,
1111
+ default="INFO",
1112
+ choices=["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"],
1113
+ help="Logging level (default: INFO)",
1114
+ )
1115
+
1116
+ return parser.parse_args()
1117
+
1118
+
1119
+ def setup_logging(log_level: str):
1120
+ """Configure logging."""
1121
+ logging.basicConfig(
1122
+ level=getattr(logging, log_level),
1123
+ format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
1124
+ )
1125
+
1126
+
1127
+ async def run_http_server(host: str, port: int):
1128
+ """Run the MCP server with HTTP transport using uvicorn."""
1129
+ try:
1130
+ import uvicorn
1131
+ except ImportError:
1132
+ logging.error("āŒ Error: uvicorn package not installed")
1133
+ logging.error("Install with: pip install uvicorn")
1134
+ sys.exit(1)
1135
+
1136
+ try:
1137
+ # Get the SSE Starlette app from FastMCP
1138
+ app = mcp.sse_app()
1139
+
1140
+ # Add CORS middleware for cross-origin requests
1141
+ try:
1142
+ from starlette.middleware.cors import CORSMiddleware
1143
+
1144
+ app.add_middleware(
1145
+ CORSMiddleware,
1146
+ allow_origins=["*"],
1147
+ allow_credentials=True,
1148
+ allow_methods=["*"],
1149
+ allow_headers=["*"],
1150
+ )
1151
+ logging.info("āœ“ CORS middleware enabled")
1152
+ except ImportError:
1153
+ logging.warning("⚠ CORS middleware not available (starlette not installed)")
1154
+
1155
+ # Add health check endpoint
1156
+ from starlette.responses import JSONResponse
1157
+ from starlette.routing import Route
1158
+
1159
+ async def health_check(_request):
1160
+ """Health check endpoint."""
1161
+ return JSONResponse(
1162
+ {
1163
+ "status": "healthy",
1164
+ "server": "skill-seeker-mcp",
1165
+ "version": "2.1.1",
1166
+ "transport": "http",
1167
+ "endpoints": {
1168
+ "health": "/health",
1169
+ "sse": "/sse",
1170
+ "messages": "/messages/",
1171
+ },
1172
+ }
1173
+ )
1174
+
1175
+ # Add route before the catch-all SSE route
1176
+ app.routes.insert(0, Route("/health", health_check, methods=["GET"]))
1177
+
1178
+ logging.info("šŸš€ Starting Skill Seeker MCP Server (HTTP mode)")
1179
+ logging.info(f"šŸ“” Server URL: http://{host}:{port}")
1180
+ logging.info(f"šŸ”— SSE Endpoint: http://{host}:{port}/sse")
1181
+ logging.info(f"šŸ’š Health Check: http://{host}:{port}/health")
1182
+ logging.info(f"šŸ“ Messages: http://{host}:{port}/messages/")
1183
+ logging.info("")
1184
+ logging.info("Claude Desktop Configuration (HTTP):")
1185
+ logging.info("{")
1186
+ logging.info(' "mcpServers": {')
1187
+ logging.info(' "skill-seeker": {')
1188
+ logging.info(f' "url": "http://{host}:{port}/sse"')
1189
+ logging.info(" }")
1190
+ logging.info(" }")
1191
+ logging.info("}")
1192
+ logging.info("")
1193
+ logging.info("Press Ctrl+C to stop the server")
1194
+
1195
+ # Run the uvicorn server
1196
+ config = uvicorn.Config(
1197
+ app=app,
1198
+ host=host,
1199
+ port=port,
1200
+ log_level=logging.getLogger().level,
1201
+ access_log=True,
1202
+ )
1203
+ server = uvicorn.Server(config)
1204
+ await server.serve()
1205
+
1206
+ except Exception as e:
1207
+ logging.error(f"āŒ Failed to start HTTP server: {e}")
1208
+ import traceback
1209
+
1210
+ traceback.print_exc()
1211
+ sys.exit(1)
1212
+
1213
+
1214
+ def main():
1215
+ """Run the MCP server with stdio or HTTP transport."""
1216
+ import asyncio
1217
+
1218
+ # Check if MCP is available
1219
+ if not MCP_AVAILABLE or mcp is None:
1220
+ print("āŒ Error: mcp package not installed or FastMCP not available")
1221
+ print("Install with: pip install mcp>=1.25")
1222
+ sys.exit(1)
1223
+
1224
+ # Parse command-line arguments
1225
+ args = parse_args()
1226
+
1227
+ # Setup logging
1228
+ setup_logging(args.log_level)
1229
+
1230
+ if args.http:
1231
+ # HTTP transport mode
1232
+ logging.info(f"🌐 Using HTTP transport on {args.host}:{args.port}")
1233
+ try:
1234
+ asyncio.run(run_http_server(args.host, args.port))
1235
+ except KeyboardInterrupt:
1236
+ logging.info("\nšŸ‘‹ Server stopped by user")
1237
+ sys.exit(0)
1238
+ else:
1239
+ # Stdio transport mode (default, backward compatible)
1240
+ logging.info("šŸ“ŗ Using stdio transport (default)")
1241
+ try:
1242
+ asyncio.run(mcp.run_stdio_async())
1243
+ except KeyboardInterrupt:
1244
+ logging.info("\nšŸ‘‹ Server stopped by user")
1245
+ sys.exit(0)
1246
+
1247
+
1248
+ if __name__ == "__main__":
1249
+ main()