tapps-agents 3.5.40__py3-none-any.whl → 3.6.0__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.
- tapps_agents/__init__.py +2 -2
- tapps_agents/agents/__init__.py +22 -22
- tapps_agents/agents/analyst/__init__.py +5 -5
- tapps_agents/agents/architect/__init__.py +5 -5
- tapps_agents/agents/architect/agent.py +1033 -1033
- tapps_agents/agents/architect/pattern_detector.py +75 -75
- tapps_agents/agents/cleanup/__init__.py +7 -7
- tapps_agents/agents/cleanup/agent.py +445 -445
- tapps_agents/agents/debugger/__init__.py +7 -7
- tapps_agents/agents/debugger/agent.py +310 -310
- tapps_agents/agents/debugger/error_analyzer.py +437 -437
- tapps_agents/agents/designer/__init__.py +5 -5
- tapps_agents/agents/designer/agent.py +786 -786
- tapps_agents/agents/designer/visual_designer.py +638 -638
- tapps_agents/agents/documenter/__init__.py +7 -7
- tapps_agents/agents/documenter/agent.py +531 -531
- tapps_agents/agents/documenter/doc_generator.py +472 -472
- tapps_agents/agents/documenter/doc_validator.py +393 -393
- tapps_agents/agents/documenter/framework_doc_updater.py +493 -493
- tapps_agents/agents/enhancer/__init__.py +7 -7
- tapps_agents/agents/evaluator/__init__.py +7 -7
- tapps_agents/agents/evaluator/agent.py +443 -443
- tapps_agents/agents/evaluator/priority_evaluator.py +641 -641
- tapps_agents/agents/evaluator/quality_analyzer.py +147 -147
- tapps_agents/agents/evaluator/report_generator.py +344 -344
- tapps_agents/agents/evaluator/usage_analyzer.py +192 -192
- tapps_agents/agents/evaluator/workflow_analyzer.py +189 -189
- tapps_agents/agents/implementer/__init__.py +7 -7
- tapps_agents/agents/implementer/agent.py +798 -798
- tapps_agents/agents/implementer/auto_fix.py +1119 -1119
- tapps_agents/agents/implementer/code_generator.py +73 -73
- tapps_agents/agents/improver/__init__.py +1 -1
- tapps_agents/agents/improver/agent.py +753 -753
- tapps_agents/agents/ops/__init__.py +1 -1
- tapps_agents/agents/ops/agent.py +619 -619
- tapps_agents/agents/ops/dependency_analyzer.py +600 -600
- tapps_agents/agents/orchestrator/__init__.py +5 -5
- tapps_agents/agents/orchestrator/agent.py +522 -522
- tapps_agents/agents/planner/__init__.py +7 -7
- tapps_agents/agents/planner/agent.py +1127 -1127
- tapps_agents/agents/reviewer/__init__.py +24 -24
- tapps_agents/agents/reviewer/agent.py +3513 -3513
- tapps_agents/agents/reviewer/aggregator.py +213 -213
- tapps_agents/agents/reviewer/batch_review.py +448 -448
- tapps_agents/agents/reviewer/cache.py +443 -443
- tapps_agents/agents/reviewer/context7_enhancer.py +630 -630
- tapps_agents/agents/reviewer/context_detector.py +203 -203
- tapps_agents/agents/reviewer/docker_compose_validator.py +158 -158
- tapps_agents/agents/reviewer/dockerfile_validator.py +176 -176
- tapps_agents/agents/reviewer/error_handling.py +126 -126
- tapps_agents/agents/reviewer/feedback_generator.py +490 -490
- tapps_agents/agents/reviewer/influxdb_validator.py +316 -316
- tapps_agents/agents/reviewer/issue_tracking.py +169 -169
- tapps_agents/agents/reviewer/library_detector.py +295 -295
- tapps_agents/agents/reviewer/library_patterns.py +268 -268
- tapps_agents/agents/reviewer/maintainability_scorer.py +593 -593
- tapps_agents/agents/reviewer/metric_strategies.py +276 -276
- tapps_agents/agents/reviewer/mqtt_validator.py +160 -160
- tapps_agents/agents/reviewer/output_enhancer.py +105 -105
- tapps_agents/agents/reviewer/pattern_detector.py +241 -241
- tapps_agents/agents/reviewer/performance_scorer.py +357 -357
- tapps_agents/agents/reviewer/phased_review.py +516 -516
- tapps_agents/agents/reviewer/progressive_review.py +435 -435
- tapps_agents/agents/reviewer/react_scorer.py +331 -331
- tapps_agents/agents/reviewer/score_constants.py +228 -228
- tapps_agents/agents/reviewer/score_validator.py +507 -507
- tapps_agents/agents/reviewer/scorer_registry.py +373 -373
- tapps_agents/agents/reviewer/scoring.py +1566 -1566
- tapps_agents/agents/reviewer/service_discovery.py +534 -534
- tapps_agents/agents/reviewer/tools/__init__.py +41 -41
- tapps_agents/agents/reviewer/tools/parallel_executor.py +581 -581
- tapps_agents/agents/reviewer/tools/ruff_grouping.py +250 -250
- tapps_agents/agents/reviewer/tools/scoped_mypy.py +284 -284
- tapps_agents/agents/reviewer/typescript_scorer.py +1142 -1142
- tapps_agents/agents/reviewer/validation.py +208 -208
- tapps_agents/agents/reviewer/websocket_validator.py +132 -132
- tapps_agents/agents/tester/__init__.py +7 -7
- tapps_agents/agents/tester/accessibility_auditor.py +309 -309
- tapps_agents/agents/tester/agent.py +1080 -1080
- tapps_agents/agents/tester/batch_generator.py +54 -54
- tapps_agents/agents/tester/context_learner.py +51 -51
- tapps_agents/agents/tester/coverage_analyzer.py +386 -386
- tapps_agents/agents/tester/coverage_test_generator.py +290 -290
- tapps_agents/agents/tester/debug_enhancer.py +238 -238
- tapps_agents/agents/tester/device_emulator.py +241 -241
- tapps_agents/agents/tester/integration_generator.py +62 -62
- tapps_agents/agents/tester/network_recorder.py +300 -300
- tapps_agents/agents/tester/performance_monitor.py +320 -320
- tapps_agents/agents/tester/test_fixer.py +316 -316
- tapps_agents/agents/tester/test_generator.py +632 -632
- tapps_agents/agents/tester/trace_manager.py +234 -234
- tapps_agents/agents/tester/visual_regression.py +291 -291
- tapps_agents/analysis/pattern_detector.py +36 -36
- tapps_agents/beads/hydration.py +213 -213
- tapps_agents/beads/parse.py +32 -32
- tapps_agents/beads/specs.py +206 -206
- tapps_agents/cli/__init__.py +9 -9
- tapps_agents/cli/__main__.py +8 -8
- tapps_agents/cli/base.py +478 -478
- tapps_agents/cli/command_classifier.py +72 -72
- tapps_agents/cli/commands/__init__.py +2 -2
- tapps_agents/cli/commands/analyst.py +173 -173
- tapps_agents/cli/commands/architect.py +109 -109
- tapps_agents/cli/commands/cleanup_agent.py +92 -92
- tapps_agents/cli/commands/common.py +126 -126
- tapps_agents/cli/commands/debugger.py +90 -90
- tapps_agents/cli/commands/designer.py +112 -112
- tapps_agents/cli/commands/documenter.py +136 -136
- tapps_agents/cli/commands/enhancer.py +110 -110
- tapps_agents/cli/commands/evaluator.py +255 -255
- tapps_agents/cli/commands/health.py +665 -665
- tapps_agents/cli/commands/implementer.py +301 -301
- tapps_agents/cli/commands/improver.py +91 -91
- tapps_agents/cli/commands/knowledge.py +111 -111
- tapps_agents/cli/commands/learning.py +172 -172
- tapps_agents/cli/commands/observability.py +283 -283
- tapps_agents/cli/commands/ops.py +135 -135
- tapps_agents/cli/commands/orchestrator.py +116 -116
- tapps_agents/cli/commands/planner.py +237 -237
- tapps_agents/cli/commands/reviewer.py +1872 -1872
- tapps_agents/cli/commands/status.py +285 -285
- tapps_agents/cli/commands/task.py +227 -219
- tapps_agents/cli/commands/tester.py +191 -191
- tapps_agents/cli/commands/top_level.py +3586 -3586
- tapps_agents/cli/feedback.py +936 -936
- tapps_agents/cli/formatters.py +608 -608
- tapps_agents/cli/help/__init__.py +7 -7
- tapps_agents/cli/help/static_help.py +425 -425
- tapps_agents/cli/network_detection.py +110 -110
- tapps_agents/cli/output_compactor.py +274 -274
- tapps_agents/cli/parsers/__init__.py +2 -2
- tapps_agents/cli/parsers/analyst.py +186 -186
- tapps_agents/cli/parsers/architect.py +167 -167
- tapps_agents/cli/parsers/cleanup_agent.py +228 -228
- tapps_agents/cli/parsers/debugger.py +116 -116
- tapps_agents/cli/parsers/designer.py +182 -182
- tapps_agents/cli/parsers/documenter.py +134 -134
- tapps_agents/cli/parsers/enhancer.py +113 -113
- tapps_agents/cli/parsers/evaluator.py +213 -213
- tapps_agents/cli/parsers/implementer.py +168 -168
- tapps_agents/cli/parsers/improver.py +132 -132
- tapps_agents/cli/parsers/ops.py +159 -159
- tapps_agents/cli/parsers/orchestrator.py +98 -98
- tapps_agents/cli/parsers/planner.py +145 -145
- tapps_agents/cli/parsers/reviewer.py +462 -462
- tapps_agents/cli/parsers/tester.py +124 -124
- tapps_agents/cli/progress_heartbeat.py +254 -254
- tapps_agents/cli/streaming_progress.py +336 -336
- tapps_agents/cli/utils/__init__.py +6 -6
- tapps_agents/cli/utils/agent_lifecycle.py +48 -48
- tapps_agents/cli/utils/error_formatter.py +82 -82
- tapps_agents/cli/utils/error_recovery.py +188 -188
- tapps_agents/cli/utils/output_handler.py +59 -59
- tapps_agents/cli/utils/prompt_enhancer.py +319 -319
- tapps_agents/cli/validators/__init__.py +9 -9
- tapps_agents/cli/validators/command_validator.py +81 -81
- tapps_agents/context7/__init__.py +112 -112
- tapps_agents/context7/agent_integration.py +869 -869
- tapps_agents/context7/analytics.py +382 -382
- tapps_agents/context7/analytics_dashboard.py +299 -299
- tapps_agents/context7/async_cache.py +681 -681
- tapps_agents/context7/backup_client.py +958 -958
- tapps_agents/context7/cache_locking.py +194 -194
- tapps_agents/context7/cache_metadata.py +214 -214
- tapps_agents/context7/cache_prewarm.py +488 -488
- tapps_agents/context7/cache_structure.py +168 -168
- tapps_agents/context7/cache_warming.py +604 -604
- tapps_agents/context7/circuit_breaker.py +376 -376
- tapps_agents/context7/cleanup.py +461 -461
- tapps_agents/context7/commands.py +858 -858
- tapps_agents/context7/credential_validation.py +276 -276
- tapps_agents/context7/cross_reference_resolver.py +168 -168
- tapps_agents/context7/cross_references.py +424 -424
- tapps_agents/context7/doc_manager.py +225 -225
- tapps_agents/context7/fuzzy_matcher.py +369 -369
- tapps_agents/context7/kb_cache.py +404 -404
- tapps_agents/context7/language_detector.py +219 -219
- tapps_agents/context7/library_detector.py +725 -725
- tapps_agents/context7/lookup.py +738 -738
- tapps_agents/context7/metadata.py +258 -258
- tapps_agents/context7/refresh_queue.py +300 -300
- tapps_agents/context7/security.py +373 -373
- tapps_agents/context7/staleness_policies.py +278 -278
- tapps_agents/context7/tiles_integration.py +47 -47
- tapps_agents/continuous_bug_fix/__init__.py +20 -20
- tapps_agents/continuous_bug_fix/bug_finder.py +306 -306
- tapps_agents/continuous_bug_fix/bug_fix_coordinator.py +177 -177
- tapps_agents/continuous_bug_fix/commit_manager.py +178 -178
- tapps_agents/continuous_bug_fix/continuous_bug_fixer.py +322 -322
- tapps_agents/continuous_bug_fix/proactive_bug_finder.py +285 -285
- tapps_agents/core/__init__.py +298 -298
- tapps_agents/core/adaptive_cache_config.py +432 -432
- tapps_agents/core/agent_base.py +647 -647
- tapps_agents/core/agent_cache.py +466 -466
- tapps_agents/core/agent_learning.py +1865 -1865
- tapps_agents/core/analytics_dashboard.py +563 -563
- tapps_agents/core/analytics_enhancements.py +597 -597
- tapps_agents/core/anonymization.py +274 -274
- tapps_agents/core/artifact_context_builder.py +293 -0
- tapps_agents/core/ast_parser.py +228 -228
- tapps_agents/core/async_file_ops.py +402 -402
- tapps_agents/core/best_practice_consultant.py +299 -299
- tapps_agents/core/brownfield_analyzer.py +299 -299
- tapps_agents/core/brownfield_review.py +541 -541
- tapps_agents/core/browser_controller.py +513 -513
- tapps_agents/core/capability_registry.py +418 -418
- tapps_agents/core/change_impact_analyzer.py +190 -190
- tapps_agents/core/checkpoint_manager.py +377 -377
- tapps_agents/core/code_generator.py +329 -329
- tapps_agents/core/code_validator.py +276 -276
- tapps_agents/core/command_registry.py +327 -327
- tapps_agents/core/config.py +33 -0
- tapps_agents/core/context_gathering/__init__.py +2 -2
- tapps_agents/core/context_gathering/repository_explorer.py +28 -28
- tapps_agents/core/context_intelligence/__init__.py +2 -2
- tapps_agents/core/context_intelligence/relevance_scorer.py +24 -24
- tapps_agents/core/context_intelligence/token_budget_manager.py +27 -27
- tapps_agents/core/context_manager.py +240 -240
- tapps_agents/core/cursor_feedback_monitor.py +146 -146
- tapps_agents/core/cursor_verification.py +290 -290
- tapps_agents/core/customization_loader.py +280 -280
- tapps_agents/core/customization_schema.py +260 -260
- tapps_agents/core/customization_template.py +238 -238
- tapps_agents/core/debug_logger.py +124 -124
- tapps_agents/core/design_validator.py +298 -298
- tapps_agents/core/diagram_generator.py +226 -226
- tapps_agents/core/docker_utils.py +232 -232
- tapps_agents/core/document_generator.py +617 -617
- tapps_agents/core/domain_detector.py +30 -30
- tapps_agents/core/error_envelope.py +454 -454
- tapps_agents/core/error_handler.py +270 -270
- tapps_agents/core/estimation_tracker.py +189 -189
- tapps_agents/core/eval_prompt_engine.py +116 -116
- tapps_agents/core/evaluation_base.py +119 -119
- tapps_agents/core/evaluation_models.py +320 -320
- tapps_agents/core/evaluation_orchestrator.py +225 -225
- tapps_agents/core/evaluators/__init__.py +7 -7
- tapps_agents/core/evaluators/architectural_evaluator.py +205 -205
- tapps_agents/core/evaluators/behavioral_evaluator.py +160 -160
- tapps_agents/core/evaluators/performance_profile_evaluator.py +160 -160
- tapps_agents/core/evaluators/security_posture_evaluator.py +148 -148
- tapps_agents/core/evaluators/spec_compliance_evaluator.py +181 -181
- tapps_agents/core/exceptions.py +107 -107
- tapps_agents/core/expert_config_generator.py +293 -293
- tapps_agents/core/export_schema.py +202 -202
- tapps_agents/core/external_feedback_models.py +102 -102
- tapps_agents/core/external_feedback_storage.py +213 -213
- tapps_agents/core/fallback_strategy.py +314 -314
- tapps_agents/core/feedback_analyzer.py +162 -162
- tapps_agents/core/feedback_collector.py +178 -178
- tapps_agents/core/git_operations.py +445 -445
- tapps_agents/core/hardware_profiler.py +151 -151
- tapps_agents/core/instructions.py +324 -324
- tapps_agents/core/io_guardrails.py +69 -69
- tapps_agents/core/issue_manifest.py +249 -249
- tapps_agents/core/issue_schema.py +139 -139
- tapps_agents/core/json_utils.py +128 -128
- tapps_agents/core/knowledge_graph.py +446 -446
- tapps_agents/core/language_detector.py +296 -296
- tapps_agents/core/learning_confidence.py +242 -242
- tapps_agents/core/learning_dashboard.py +246 -246
- tapps_agents/core/learning_decision.py +384 -384
- tapps_agents/core/learning_explainability.py +578 -578
- tapps_agents/core/learning_export.py +287 -287
- tapps_agents/core/learning_integration.py +228 -228
- tapps_agents/core/llm_behavior.py +232 -232
- tapps_agents/core/long_duration_support.py +786 -786
- tapps_agents/core/mcp_setup.py +106 -106
- tapps_agents/core/memory_integration.py +396 -396
- tapps_agents/core/meta_learning.py +666 -666
- tapps_agents/core/module_path_sanitizer.py +199 -199
- tapps_agents/core/multi_agent_orchestrator.py +382 -382
- tapps_agents/core/network_errors.py +125 -125
- tapps_agents/core/nfr_validator.py +336 -336
- tapps_agents/core/offline_mode.py +158 -158
- tapps_agents/core/output_contracts.py +300 -300
- tapps_agents/core/output_formatter.py +300 -300
- tapps_agents/core/path_normalizer.py +174 -174
- tapps_agents/core/path_validator.py +322 -322
- tapps_agents/core/pattern_library.py +250 -250
- tapps_agents/core/performance_benchmark.py +301 -301
- tapps_agents/core/performance_monitor.py +184 -184
- tapps_agents/core/playwright_mcp_controller.py +771 -771
- tapps_agents/core/policy_loader.py +135 -135
- tapps_agents/core/progress.py +166 -166
- tapps_agents/core/project_profile.py +354 -354
- tapps_agents/core/project_type_detector.py +454 -454
- tapps_agents/core/prompt_base.py +223 -223
- tapps_agents/core/prompt_learning/__init__.py +2 -2
- tapps_agents/core/prompt_learning/learning_loop.py +24 -24
- tapps_agents/core/prompt_learning/project_prompt_store.py +25 -25
- tapps_agents/core/prompt_learning/skills_prompt_analyzer.py +35 -35
- tapps_agents/core/prompt_optimization/__init__.py +6 -6
- tapps_agents/core/prompt_optimization/ab_tester.py +114 -114
- tapps_agents/core/prompt_optimization/correlation_analyzer.py +160 -160
- tapps_agents/core/prompt_optimization/progressive_refiner.py +129 -129
- tapps_agents/core/prompt_optimization/prompt_library.py +37 -37
- tapps_agents/core/requirements_evaluator.py +431 -431
- tapps_agents/core/resource_aware_executor.py +449 -449
- tapps_agents/core/resource_monitor.py +343 -343
- tapps_agents/core/resume_handler.py +298 -298
- tapps_agents/core/retry_handler.py +197 -197
- tapps_agents/core/review_checklists.py +479 -479
- tapps_agents/core/role_loader.py +201 -201
- tapps_agents/core/role_template_loader.py +201 -201
- tapps_agents/core/runtime_mode.py +60 -60
- tapps_agents/core/security_scanner.py +342 -342
- tapps_agents/core/skill_agent_registry.py +194 -194
- tapps_agents/core/skill_integration.py +208 -208
- tapps_agents/core/skill_loader.py +492 -492
- tapps_agents/core/skill_template.py +341 -341
- tapps_agents/core/skill_validator.py +478 -478
- tapps_agents/core/stack_analyzer.py +35 -35
- tapps_agents/core/startup.py +174 -174
- tapps_agents/core/storage_manager.py +397 -397
- tapps_agents/core/storage_models.py +166 -166
- tapps_agents/core/story_evaluator.py +410 -410
- tapps_agents/core/subprocess_utils.py +170 -170
- tapps_agents/core/task_duration.py +296 -296
- tapps_agents/core/task_memory.py +582 -582
- tapps_agents/core/task_state.py +226 -226
- tapps_agents/core/tech_stack_priorities.py +208 -208
- tapps_agents/core/temp_directory.py +194 -194
- tapps_agents/core/template_merger.py +600 -600
- tapps_agents/core/template_selector.py +280 -280
- tapps_agents/core/test_generator.py +286 -286
- tapps_agents/core/tiered_context.py +253 -253
- tapps_agents/core/token_monitor.py +345 -345
- tapps_agents/core/traceability.py +254 -254
- tapps_agents/core/trajectory_tracker.py +50 -50
- tapps_agents/core/unicode_safe.py +143 -143
- tapps_agents/core/unified_cache_config.py +170 -170
- tapps_agents/core/unified_state.py +324 -324
- tapps_agents/core/validate_cursor_setup.py +237 -237
- tapps_agents/core/validation_registry.py +136 -136
- tapps_agents/core/validators/__init__.py +4 -4
- tapps_agents/core/validators/python_validator.py +87 -87
- tapps_agents/core/verification_agent.py +90 -90
- tapps_agents/core/visual_feedback.py +644 -644
- tapps_agents/core/workflow_validator.py +197 -197
- tapps_agents/core/worktree.py +367 -367
- tapps_agents/docker/__init__.py +10 -10
- tapps_agents/docker/analyzer.py +186 -186
- tapps_agents/docker/debugger.py +229 -229
- tapps_agents/docker/error_patterns.py +216 -216
- tapps_agents/epic/__init__.py +22 -22
- tapps_agents/epic/beads_sync.py +115 -115
- tapps_agents/epic/markdown_sync.py +105 -105
- tapps_agents/epic/models.py +96 -96
- tapps_agents/experts/__init__.py +163 -163
- tapps_agents/experts/agent_integration.py +243 -243
- tapps_agents/experts/auto_generator.py +331 -331
- tapps_agents/experts/base_expert.py +536 -536
- tapps_agents/experts/builtin_registry.py +261 -261
- tapps_agents/experts/business_metrics.py +565 -565
- tapps_agents/experts/cache.py +266 -266
- tapps_agents/experts/confidence_breakdown.py +306 -306
- tapps_agents/experts/confidence_calculator.py +336 -336
- tapps_agents/experts/confidence_metrics.py +236 -236
- tapps_agents/experts/domain_config.py +311 -311
- tapps_agents/experts/domain_detector.py +550 -550
- tapps_agents/experts/domain_utils.py +84 -84
- tapps_agents/experts/expert_config.py +113 -113
- tapps_agents/experts/expert_engine.py +465 -465
- tapps_agents/experts/expert_registry.py +744 -744
- tapps_agents/experts/expert_synthesizer.py +70 -70
- tapps_agents/experts/governance.py +197 -197
- tapps_agents/experts/history_logger.py +312 -312
- tapps_agents/experts/knowledge/README.md +180 -180
- tapps_agents/experts/knowledge/accessibility/accessible-forms.md +331 -331
- tapps_agents/experts/knowledge/accessibility/aria-patterns.md +344 -344
- tapps_agents/experts/knowledge/accessibility/color-contrast.md +285 -285
- tapps_agents/experts/knowledge/accessibility/keyboard-navigation.md +332 -332
- tapps_agents/experts/knowledge/accessibility/screen-readers.md +282 -282
- tapps_agents/experts/knowledge/accessibility/semantic-html.md +355 -355
- tapps_agents/experts/knowledge/accessibility/testing-accessibility.md +369 -369
- tapps_agents/experts/knowledge/accessibility/wcag-2.1.md +296 -296
- tapps_agents/experts/knowledge/accessibility/wcag-2.2.md +211 -211
- tapps_agents/experts/knowledge/agent-learning/best-practices.md +715 -715
- tapps_agents/experts/knowledge/agent-learning/pattern-extraction.md +282 -282
- tapps_agents/experts/knowledge/agent-learning/prompt-optimization.md +320 -320
- tapps_agents/experts/knowledge/ai-frameworks/model-optimization.md +90 -90
- tapps_agents/experts/knowledge/ai-frameworks/openvino-patterns.md +260 -260
- tapps_agents/experts/knowledge/api-design-integration/api-gateway-patterns.md +309 -309
- tapps_agents/experts/knowledge/api-design-integration/api-security-patterns.md +521 -521
- tapps_agents/experts/knowledge/api-design-integration/api-versioning.md +421 -421
- tapps_agents/experts/knowledge/api-design-integration/async-protocol-patterns.md +61 -61
- tapps_agents/experts/knowledge/api-design-integration/contract-testing.md +221 -221
- tapps_agents/experts/knowledge/api-design-integration/external-api-integration.md +489 -489
- tapps_agents/experts/knowledge/api-design-integration/fastapi-patterns.md +360 -360
- tapps_agents/experts/knowledge/api-design-integration/fastapi-testing.md +262 -262
- tapps_agents/experts/knowledge/api-design-integration/graphql-patterns.md +582 -582
- tapps_agents/experts/knowledge/api-design-integration/grpc-best-practices.md +499 -499
- tapps_agents/experts/knowledge/api-design-integration/mqtt-patterns.md +455 -455
- tapps_agents/experts/knowledge/api-design-integration/rate-limiting.md +507 -507
- tapps_agents/experts/knowledge/api-design-integration/restful-api-design.md +618 -618
- tapps_agents/experts/knowledge/api-design-integration/websocket-patterns.md +480 -480
- tapps_agents/experts/knowledge/cloud-infrastructure/cloud-native-patterns.md +175 -175
- tapps_agents/experts/knowledge/cloud-infrastructure/container-health-checks.md +261 -261
- tapps_agents/experts/knowledge/cloud-infrastructure/containerization.md +222 -222
- tapps_agents/experts/knowledge/cloud-infrastructure/cost-optimization.md +122 -122
- tapps_agents/experts/knowledge/cloud-infrastructure/disaster-recovery.md +153 -153
- tapps_agents/experts/knowledge/cloud-infrastructure/dockerfile-patterns.md +285 -285
- tapps_agents/experts/knowledge/cloud-infrastructure/infrastructure-as-code.md +187 -187
- tapps_agents/experts/knowledge/cloud-infrastructure/kubernetes-patterns.md +253 -253
- tapps_agents/experts/knowledge/cloud-infrastructure/multi-cloud-strategies.md +155 -155
- tapps_agents/experts/knowledge/cloud-infrastructure/serverless-architecture.md +200 -200
- tapps_agents/experts/knowledge/code-quality-analysis/README.md +16 -16
- tapps_agents/experts/knowledge/code-quality-analysis/code-metrics.md +137 -137
- tapps_agents/experts/knowledge/code-quality-analysis/complexity-analysis.md +181 -181
- tapps_agents/experts/knowledge/code-quality-analysis/technical-debt-patterns.md +191 -191
- tapps_agents/experts/knowledge/data-privacy-compliance/anonymization.md +313 -313
- tapps_agents/experts/knowledge/data-privacy-compliance/ccpa.md +255 -255
- tapps_agents/experts/knowledge/data-privacy-compliance/consent-management.md +282 -282
- tapps_agents/experts/knowledge/data-privacy-compliance/data-minimization.md +275 -275
- tapps_agents/experts/knowledge/data-privacy-compliance/data-retention.md +297 -297
- tapps_agents/experts/knowledge/data-privacy-compliance/data-subject-rights.md +383 -383
- tapps_agents/experts/knowledge/data-privacy-compliance/encryption-privacy.md +285 -285
- tapps_agents/experts/knowledge/data-privacy-compliance/gdpr.md +344 -344
- tapps_agents/experts/knowledge/data-privacy-compliance/hipaa.md +385 -385
- tapps_agents/experts/knowledge/data-privacy-compliance/privacy-by-design.md +280 -280
- tapps_agents/experts/knowledge/database-data-management/acid-vs-cap.md +164 -164
- tapps_agents/experts/knowledge/database-data-management/backup-and-recovery.md +182 -182
- tapps_agents/experts/knowledge/database-data-management/data-modeling.md +172 -172
- tapps_agents/experts/knowledge/database-data-management/database-design.md +187 -187
- tapps_agents/experts/knowledge/database-data-management/flux-query-optimization.md +342 -342
- tapps_agents/experts/knowledge/database-data-management/influxdb-connection-patterns.md +432 -432
- tapps_agents/experts/knowledge/database-data-management/influxdb-patterns.md +442 -442
- tapps_agents/experts/knowledge/database-data-management/migration-strategies.md +216 -216
- tapps_agents/experts/knowledge/database-data-management/nosql-patterns.md +259 -259
- tapps_agents/experts/knowledge/database-data-management/scalability-patterns.md +184 -184
- tapps_agents/experts/knowledge/database-data-management/sql-optimization.md +175 -175
- tapps_agents/experts/knowledge/database-data-management/time-series-modeling.md +444 -444
- tapps_agents/experts/knowledge/development-workflow/README.md +16 -16
- tapps_agents/experts/knowledge/development-workflow/automation-best-practices.md +216 -216
- tapps_agents/experts/knowledge/development-workflow/build-strategies.md +198 -198
- tapps_agents/experts/knowledge/development-workflow/deployment-patterns.md +205 -205
- tapps_agents/experts/knowledge/development-workflow/git-workflows.md +205 -205
- tapps_agents/experts/knowledge/documentation-knowledge-management/README.md +16 -16
- tapps_agents/experts/knowledge/documentation-knowledge-management/api-documentation-patterns.md +231 -231
- tapps_agents/experts/knowledge/documentation-knowledge-management/documentation-standards.md +191 -191
- tapps_agents/experts/knowledge/documentation-knowledge-management/knowledge-management.md +171 -171
- tapps_agents/experts/knowledge/documentation-knowledge-management/technical-writing-guide.md +192 -192
- tapps_agents/experts/knowledge/observability-monitoring/alerting-patterns.md +461 -461
- tapps_agents/experts/knowledge/observability-monitoring/apm-tools.md +459 -459
- tapps_agents/experts/knowledge/observability-monitoring/distributed-tracing.md +367 -367
- tapps_agents/experts/knowledge/observability-monitoring/logging-strategies.md +478 -478
- tapps_agents/experts/knowledge/observability-monitoring/metrics-and-monitoring.md +510 -510
- tapps_agents/experts/knowledge/observability-monitoring/observability-best-practices.md +492 -492
- tapps_agents/experts/knowledge/observability-monitoring/open-telemetry.md +573 -573
- tapps_agents/experts/knowledge/observability-monitoring/slo-sli-sla.md +419 -419
- tapps_agents/experts/knowledge/performance/anti-patterns.md +284 -284
- tapps_agents/experts/knowledge/performance/api-performance.md +256 -256
- tapps_agents/experts/knowledge/performance/caching.md +327 -327
- tapps_agents/experts/knowledge/performance/database-performance.md +252 -252
- tapps_agents/experts/knowledge/performance/optimization-patterns.md +327 -327
- tapps_agents/experts/knowledge/performance/profiling.md +297 -297
- tapps_agents/experts/knowledge/performance/resource-management.md +293 -293
- tapps_agents/experts/knowledge/performance/scalability.md +306 -306
- tapps_agents/experts/knowledge/security/owasp-top10.md +209 -209
- tapps_agents/experts/knowledge/security/secure-coding-practices.md +207 -207
- tapps_agents/experts/knowledge/security/threat-modeling.md +220 -220
- tapps_agents/experts/knowledge/security/vulnerability-patterns.md +342 -342
- tapps_agents/experts/knowledge/software-architecture/docker-compose-patterns.md +314 -314
- tapps_agents/experts/knowledge/software-architecture/microservices-patterns.md +379 -379
- tapps_agents/experts/knowledge/software-architecture/service-communication.md +316 -316
- tapps_agents/experts/knowledge/testing/best-practices.md +310 -310
- tapps_agents/experts/knowledge/testing/coverage-analysis.md +293 -293
- tapps_agents/experts/knowledge/testing/mocking.md +256 -256
- tapps_agents/experts/knowledge/testing/test-automation.md +276 -276
- tapps_agents/experts/knowledge/testing/test-data.md +271 -271
- tapps_agents/experts/knowledge/testing/test-design-patterns.md +280 -280
- tapps_agents/experts/knowledge/testing/test-maintenance.md +236 -236
- tapps_agents/experts/knowledge/testing/test-strategies.md +311 -311
- tapps_agents/experts/knowledge/user-experience/information-architecture.md +325 -325
- tapps_agents/experts/knowledge/user-experience/interaction-design.md +363 -363
- tapps_agents/experts/knowledge/user-experience/prototyping.md +293 -293
- tapps_agents/experts/knowledge/user-experience/usability-heuristics.md +337 -337
- tapps_agents/experts/knowledge/user-experience/usability-testing.md +311 -311
- tapps_agents/experts/knowledge/user-experience/user-journeys.md +296 -296
- tapps_agents/experts/knowledge/user-experience/user-research.md +373 -373
- tapps_agents/experts/knowledge/user-experience/ux-principles.md +340 -340
- tapps_agents/experts/knowledge_freshness.py +321 -321
- tapps_agents/experts/knowledge_ingestion.py +438 -438
- tapps_agents/experts/knowledge_need_detector.py +93 -93
- tapps_agents/experts/knowledge_validator.py +382 -382
- tapps_agents/experts/observability.py +440 -440
- tapps_agents/experts/passive_notifier.py +238 -238
- tapps_agents/experts/proactive_orchestrator.py +32 -32
- tapps_agents/experts/rag_chunker.py +205 -205
- tapps_agents/experts/rag_embedder.py +152 -152
- tapps_agents/experts/rag_evaluation.py +299 -299
- tapps_agents/experts/rag_index.py +303 -303
- tapps_agents/experts/rag_metrics.py +293 -293
- tapps_agents/experts/rag_safety.py +263 -263
- tapps_agents/experts/report_generator.py +296 -296
- tapps_agents/experts/setup_wizard.py +441 -441
- tapps_agents/experts/simple_rag.py +431 -431
- tapps_agents/experts/vector_rag.py +354 -354
- tapps_agents/experts/weight_distributor.py +304 -304
- tapps_agents/health/__init__.py +24 -24
- tapps_agents/health/base.py +75 -75
- tapps_agents/health/checks/__init__.py +22 -22
- tapps_agents/health/checks/automation.py +127 -127
- tapps_agents/health/checks/context7_cache.py +210 -210
- tapps_agents/health/checks/environment.py +116 -116
- tapps_agents/health/checks/execution.py +170 -170
- tapps_agents/health/checks/knowledge_base.py +187 -187
- tapps_agents/health/checks/outcomes.py +324 -324
- tapps_agents/health/collector.py +280 -280
- tapps_agents/health/dashboard.py +137 -137
- tapps_agents/health/metrics.py +151 -151
- tapps_agents/health/orchestrator.py +271 -271
- tapps_agents/health/registry.py +166 -166
- tapps_agents/hooks/__init__.py +33 -33
- tapps_agents/hooks/config.py +140 -140
- tapps_agents/hooks/events.py +135 -135
- tapps_agents/hooks/executor.py +128 -128
- tapps_agents/hooks/manager.py +143 -143
- tapps_agents/integration/__init__.py +8 -8
- tapps_agents/integration/service_integrator.py +121 -121
- tapps_agents/integrations/__init__.py +10 -10
- tapps_agents/integrations/clawdbot.py +525 -525
- tapps_agents/integrations/memory_bridge.py +356 -356
- tapps_agents/mcp/__init__.py +18 -18
- tapps_agents/mcp/gateway.py +112 -112
- tapps_agents/mcp/servers/__init__.py +13 -13
- tapps_agents/mcp/servers/analysis.py +204 -204
- tapps_agents/mcp/servers/context7.py +198 -198
- tapps_agents/mcp/servers/filesystem.py +218 -218
- tapps_agents/mcp/servers/git.py +201 -201
- tapps_agents/mcp/tool_registry.py +115 -115
- tapps_agents/quality/__init__.py +54 -54
- tapps_agents/quality/coverage_analyzer.py +379 -379
- tapps_agents/quality/enforcement.py +82 -82
- tapps_agents/quality/gates/__init__.py +37 -37
- tapps_agents/quality/gates/approval_gate.py +255 -255
- tapps_agents/quality/gates/base.py +84 -84
- tapps_agents/quality/gates/exceptions.py +43 -43
- tapps_agents/quality/gates/policy_gate.py +195 -195
- tapps_agents/quality/gates/registry.py +239 -239
- tapps_agents/quality/gates/security_gate.py +156 -156
- tapps_agents/quality/quality_gates.py +369 -369
- tapps_agents/quality/secret_scanner.py +335 -335
- tapps_agents/session/__init__.py +19 -19
- tapps_agents/session/manager.py +256 -256
- tapps_agents/simple_mode/__init__.py +66 -66
- tapps_agents/simple_mode/agent_contracts.py +357 -357
- tapps_agents/simple_mode/beads_hooks.py +151 -151
- tapps_agents/simple_mode/code_snippet_handler.py +382 -382
- tapps_agents/simple_mode/documentation_manager.py +395 -395
- tapps_agents/simple_mode/documentation_reader.py +187 -187
- tapps_agents/simple_mode/file_inference.py +292 -292
- tapps_agents/simple_mode/framework_change_detector.py +268 -268
- tapps_agents/simple_mode/intent_parser.py +510 -510
- tapps_agents/simple_mode/learning_progression.py +358 -358
- tapps_agents/simple_mode/nl_handler.py +700 -700
- tapps_agents/simple_mode/onboarding.py +253 -253
- tapps_agents/simple_mode/orchestrators/__init__.py +38 -38
- tapps_agents/simple_mode/orchestrators/base.py +185 -185
- tapps_agents/simple_mode/orchestrators/breakdown_orchestrator.py +49 -49
- tapps_agents/simple_mode/orchestrators/brownfield_orchestrator.py +135 -135
- tapps_agents/simple_mode/orchestrators/build_orchestrator.py +2700 -2667
- tapps_agents/simple_mode/orchestrators/deliverable_checklist.py +349 -349
- tapps_agents/simple_mode/orchestrators/enhance_orchestrator.py +53 -53
- tapps_agents/simple_mode/orchestrators/epic_orchestrator.py +122 -122
- tapps_agents/simple_mode/orchestrators/explore_orchestrator.py +184 -184
- tapps_agents/simple_mode/orchestrators/fix_orchestrator.py +723 -723
- tapps_agents/simple_mode/orchestrators/plan_analysis_orchestrator.py +206 -206
- tapps_agents/simple_mode/orchestrators/pr_orchestrator.py +237 -237
- tapps_agents/simple_mode/orchestrators/refactor_orchestrator.py +222 -222
- tapps_agents/simple_mode/orchestrators/requirements_tracer.py +262 -262
- tapps_agents/simple_mode/orchestrators/resume_orchestrator.py +210 -210
- tapps_agents/simple_mode/orchestrators/review_orchestrator.py +161 -161
- tapps_agents/simple_mode/orchestrators/test_orchestrator.py +82 -82
- tapps_agents/simple_mode/output_aggregator.py +340 -340
- tapps_agents/simple_mode/result_formatters.py +598 -598
- tapps_agents/simple_mode/step_dependencies.py +382 -382
- tapps_agents/simple_mode/step_results.py +276 -276
- tapps_agents/simple_mode/streaming.py +388 -388
- tapps_agents/simple_mode/variations.py +129 -129
- tapps_agents/simple_mode/visual_feedback.py +238 -238
- tapps_agents/simple_mode/zero_config.py +274 -274
- tapps_agents/suggestions/__init__.py +8 -8
- tapps_agents/suggestions/inline_suggester.py +52 -52
- tapps_agents/templates/__init__.py +8 -8
- tapps_agents/templates/microservice_generator.py +274 -274
- tapps_agents/utils/env_validator.py +291 -291
- tapps_agents/workflow/__init__.py +171 -171
- tapps_agents/workflow/acceptance_verifier.py +132 -132
- tapps_agents/workflow/agent_handlers/__init__.py +41 -41
- tapps_agents/workflow/agent_handlers/analyst_handler.py +75 -75
- tapps_agents/workflow/agent_handlers/architect_handler.py +107 -107
- tapps_agents/workflow/agent_handlers/base.py +84 -84
- tapps_agents/workflow/agent_handlers/debugger_handler.py +100 -100
- tapps_agents/workflow/agent_handlers/designer_handler.py +110 -110
- tapps_agents/workflow/agent_handlers/documenter_handler.py +94 -94
- tapps_agents/workflow/agent_handlers/implementer_handler.py +235 -235
- tapps_agents/workflow/agent_handlers/ops_handler.py +62 -62
- tapps_agents/workflow/agent_handlers/orchestrator_handler.py +43 -43
- tapps_agents/workflow/agent_handlers/planner_handler.py +98 -98
- tapps_agents/workflow/agent_handlers/registry.py +119 -119
- tapps_agents/workflow/agent_handlers/reviewer_handler.py +119 -119
- tapps_agents/workflow/agent_handlers/tester_handler.py +69 -69
- tapps_agents/workflow/analytics_accessor.py +337 -337
- tapps_agents/workflow/analytics_alerts.py +416 -416
- tapps_agents/workflow/analytics_dashboard_cursor.py +281 -281
- tapps_agents/workflow/analytics_dual_write.py +103 -103
- tapps_agents/workflow/analytics_integration.py +119 -119
- tapps_agents/workflow/analytics_query_parser.py +278 -278
- tapps_agents/workflow/analytics_visualizer.py +259 -259
- tapps_agents/workflow/artifact_helper.py +204 -204
- tapps_agents/workflow/audit_logger.py +263 -263
- tapps_agents/workflow/auto_execution_config.py +340 -340
- tapps_agents/workflow/auto_progression.py +586 -586
- tapps_agents/workflow/branch_cleanup.py +349 -349
- tapps_agents/workflow/checkpoint.py +256 -256
- tapps_agents/workflow/checkpoint_manager.py +178 -178
- tapps_agents/workflow/code_artifact.py +179 -179
- tapps_agents/workflow/common_enums.py +96 -96
- tapps_agents/workflow/confirmation_handler.py +130 -130
- tapps_agents/workflow/context_analyzer.py +222 -222
- tapps_agents/workflow/context_artifact.py +230 -230
- tapps_agents/workflow/cursor_chat.py +94 -94
- tapps_agents/workflow/cursor_executor.py +2337 -2196
- tapps_agents/workflow/cursor_skill_helper.py +516 -516
- tapps_agents/workflow/dependency_resolver.py +244 -244
- tapps_agents/workflow/design_artifact.py +156 -156
- tapps_agents/workflow/detector.py +751 -751
- tapps_agents/workflow/direct_execution_fallback.py +301 -301
- tapps_agents/workflow/docs_artifact.py +168 -168
- tapps_agents/workflow/enforcer.py +389 -389
- tapps_agents/workflow/enhancement_artifact.py +142 -142
- tapps_agents/workflow/error_recovery.py +806 -806
- tapps_agents/workflow/event_bus.py +183 -183
- tapps_agents/workflow/event_log.py +612 -612
- tapps_agents/workflow/events.py +63 -63
- tapps_agents/workflow/exceptions.py +43 -43
- tapps_agents/workflow/execution_graph.py +498 -498
- tapps_agents/workflow/execution_plan.py +126 -126
- tapps_agents/workflow/file_utils.py +186 -186
- tapps_agents/workflow/gate_evaluator.py +182 -182
- tapps_agents/workflow/gate_integration.py +200 -200
- tapps_agents/workflow/graph_visualizer.py +130 -130
- tapps_agents/workflow/health_checker.py +206 -206
- tapps_agents/workflow/logging_helper.py +243 -243
- tapps_agents/workflow/manifest.py +582 -582
- tapps_agents/workflow/marker_writer.py +250 -250
- tapps_agents/workflow/message_formatter.py +188 -188
- tapps_agents/workflow/messaging.py +325 -325
- tapps_agents/workflow/metadata_models.py +91 -91
- tapps_agents/workflow/metrics_integration.py +226 -226
- tapps_agents/workflow/migration_utils.py +116 -116
- tapps_agents/workflow/models.py +148 -111
- tapps_agents/workflow/nlp_config.py +198 -198
- tapps_agents/workflow/nlp_error_handler.py +207 -207
- tapps_agents/workflow/nlp_executor.py +163 -163
- tapps_agents/workflow/nlp_parser.py +528 -528
- tapps_agents/workflow/observability_dashboard.py +451 -451
- tapps_agents/workflow/observer.py +170 -170
- tapps_agents/workflow/ops_artifact.py +257 -257
- tapps_agents/workflow/output_passing.py +214 -214
- tapps_agents/workflow/parallel_executor.py +463 -463
- tapps_agents/workflow/planning_artifact.py +179 -179
- tapps_agents/workflow/preset_loader.py +285 -285
- tapps_agents/workflow/preset_recommender.py +270 -270
- tapps_agents/workflow/progress_logger.py +145 -145
- tapps_agents/workflow/progress_manager.py +303 -303
- tapps_agents/workflow/progress_monitor.py +186 -186
- tapps_agents/workflow/progress_updates.py +423 -423
- tapps_agents/workflow/quality_artifact.py +158 -158
- tapps_agents/workflow/quality_loopback.py +101 -101
- tapps_agents/workflow/recommender.py +387 -387
- tapps_agents/workflow/remediation_loop.py +166 -166
- tapps_agents/workflow/result_aggregator.py +300 -300
- tapps_agents/workflow/review_artifact.py +185 -185
- tapps_agents/workflow/schema_validator.py +522 -522
- tapps_agents/workflow/session_handoff.py +178 -178
- tapps_agents/workflow/skill_invoker.py +648 -648
- tapps_agents/workflow/state_manager.py +756 -756
- tapps_agents/workflow/state_persistence_config.py +331 -331
- tapps_agents/workflow/status_monitor.py +449 -449
- tapps_agents/workflow/step_checkpoint.py +314 -314
- tapps_agents/workflow/step_details.py +201 -201
- tapps_agents/workflow/story_models.py +147 -147
- tapps_agents/workflow/streaming.py +416 -416
- tapps_agents/workflow/suggestion_engine.py +552 -552
- tapps_agents/workflow/testing_artifact.py +186 -186
- tapps_agents/workflow/timeline.py +158 -158
- tapps_agents/workflow/token_integration.py +209 -209
- tapps_agents/workflow/validation.py +217 -217
- tapps_agents/workflow/visual_feedback.py +391 -391
- tapps_agents/workflow/workflow_chain.py +95 -95
- tapps_agents/workflow/workflow_summary.py +219 -219
- tapps_agents/workflow/worktree_manager.py +724 -724
- {tapps_agents-3.5.40.dist-info → tapps_agents-3.6.0.dist-info}/METADATA +672 -672
- tapps_agents-3.6.0.dist-info/RECORD +758 -0
- {tapps_agents-3.5.40.dist-info → tapps_agents-3.6.0.dist-info}/licenses/LICENSE +22 -22
- tapps_agents/health/checks/outcomes.backup_20260204_064058.py +0 -324
- tapps_agents/health/checks/outcomes.backup_20260204_064256.py +0 -324
- tapps_agents/health/checks/outcomes.backup_20260204_064600.py +0 -324
- tapps_agents-3.5.40.dist-info/RECORD +0 -760
- {tapps_agents-3.5.40.dist-info → tapps_agents-3.6.0.dist-info}/WHEEL +0 -0
- {tapps_agents-3.5.40.dist-info → tapps_agents-3.6.0.dist-info}/entry_points.txt +0 -0
- {tapps_agents-3.5.40.dist-info → tapps_agents-3.6.0.dist-info}/top_level.txt +0 -0
|
@@ -1,958 +1,958 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Context7 Backup Client - HTTP fallback for Context7 API calls.
|
|
3
|
-
|
|
4
|
-
This module provides a backup mechanism for Context7 API calls when MCP Gateway
|
|
5
|
-
is not available. It implements the same pattern used in successful integration tests.
|
|
6
|
-
|
|
7
|
-
Pattern:
|
|
8
|
-
1. Prefer MCP Gateway (Cursor's MCP server) - no API key needed
|
|
9
|
-
2. Fallback to direct HTTP calls if MCP not available - requires CONTEXT7_API_KEY
|
|
10
|
-
"""
|
|
11
|
-
|
|
12
|
-
import logging
|
|
13
|
-
import os
|
|
14
|
-
import time
|
|
15
|
-
import urllib.parse
|
|
16
|
-
from typing import Any, Callable
|
|
17
|
-
|
|
18
|
-
import httpx
|
|
19
|
-
|
|
20
|
-
from ..core.debug_logger import write_debug_log
|
|
21
|
-
from ..mcp.gateway import MCPGateway
|
|
22
|
-
|
|
23
|
-
logger = logging.getLogger(__name__)
|
|
24
|
-
|
|
25
|
-
_CONTEXT7_QUOTA_EXCEEDED: bool = False
|
|
26
|
-
_CONTEXT7_QUOTA_MESSAGE: str | None = None
|
|
27
|
-
_CONTEXT7_NOT_AVAILABLE_WARNED: bool = False
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
def is_context7_quota_exceeded() -> bool:
|
|
31
|
-
"""Return True if a Context7 quota-exceeded response was observed in this process."""
|
|
32
|
-
return _CONTEXT7_QUOTA_EXCEEDED
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
def get_context7_quota_message() -> str | None:
|
|
36
|
-
"""Return the last Context7 quota-exceeded message observed in this process."""
|
|
37
|
-
return _CONTEXT7_QUOTA_MESSAGE
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
def _mark_context7_quota_exceeded(message: str) -> None:
|
|
41
|
-
"""Mark Context7 quota as exceeded (best-effort) to suppress repeated HTTP calls and log spam."""
|
|
42
|
-
global _CONTEXT7_QUOTA_EXCEEDED, _CONTEXT7_QUOTA_MESSAGE
|
|
43
|
-
_CONTEXT7_QUOTA_MESSAGE = message
|
|
44
|
-
if _CONTEXT7_QUOTA_EXCEEDED:
|
|
45
|
-
return
|
|
46
|
-
_CONTEXT7_QUOTA_EXCEEDED = True
|
|
47
|
-
logger.warning(
|
|
48
|
-
"Context7 API quota exceeded. Further Context7 HTTP fallback calls will be skipped for this run."
|
|
49
|
-
)
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
def _ensure_context7_api_key() -> str | None:
|
|
53
|
-
"""
|
|
54
|
-
Ensure Context7 API key is available in environment.
|
|
55
|
-
|
|
56
|
-
Checks environment variable first, then loads from encrypted storage if needed.
|
|
57
|
-
Automatically sets the environment variable if loaded from storage.
|
|
58
|
-
|
|
59
|
-
Returns:
|
|
60
|
-
API key string if available, None otherwise
|
|
61
|
-
"""
|
|
62
|
-
# #region agent log
|
|
63
|
-
from ..core.debug_logger import write_debug_log
|
|
64
|
-
write_debug_log(
|
|
65
|
-
{
|
|
66
|
-
"sessionId": "debug-session",
|
|
67
|
-
"runId": "run1",
|
|
68
|
-
"hypothesisId": "A",
|
|
69
|
-
"message": "_ensure_context7_api_key called",
|
|
70
|
-
"data": {"env_key_exists": os.getenv("CONTEXT7_API_KEY") is not None},
|
|
71
|
-
},
|
|
72
|
-
location="backup_client.py:_ensure_context7_api_key:entry",
|
|
73
|
-
)
|
|
74
|
-
# #endregion
|
|
75
|
-
|
|
76
|
-
# First check environment variable
|
|
77
|
-
api_key = os.getenv("CONTEXT7_API_KEY")
|
|
78
|
-
# #region agent log
|
|
79
|
-
write_debug_log(
|
|
80
|
-
{
|
|
81
|
-
"sessionId": "debug-session",
|
|
82
|
-
"runId": "run1",
|
|
83
|
-
"hypothesisId": "A",
|
|
84
|
-
"message": "Checked environment variable",
|
|
85
|
-
"data": {"api_key_from_env": api_key is not None, "key_length": len(api_key) if api_key else 0},
|
|
86
|
-
},
|
|
87
|
-
location="backup_client.py:_ensure_context7_api_key:env_check",
|
|
88
|
-
)
|
|
89
|
-
# #endregion
|
|
90
|
-
if api_key:
|
|
91
|
-
return api_key
|
|
92
|
-
|
|
93
|
-
# Try loading from encrypted storage
|
|
94
|
-
try:
|
|
95
|
-
from .security import APIKeyManager
|
|
96
|
-
|
|
97
|
-
key_manager = APIKeyManager()
|
|
98
|
-
api_key = key_manager.load_api_key("context7")
|
|
99
|
-
# #region agent log
|
|
100
|
-
write_debug_log(
|
|
101
|
-
{
|
|
102
|
-
"sessionId": "debug-session",
|
|
103
|
-
"runId": "run1",
|
|
104
|
-
"hypothesisId": "A",
|
|
105
|
-
"message": "Loaded from encrypted storage",
|
|
106
|
-
"data": {"api_key_loaded": api_key is not None, "key_length": len(api_key) if api_key else 0},
|
|
107
|
-
},
|
|
108
|
-
location="backup_client.py:_ensure_context7_api_key:storage_load",
|
|
109
|
-
)
|
|
110
|
-
# #endregion
|
|
111
|
-
|
|
112
|
-
if api_key:
|
|
113
|
-
# Set in environment for future use
|
|
114
|
-
os.environ["CONTEXT7_API_KEY"] = api_key
|
|
115
|
-
logger.debug("Loaded Context7 API key from encrypted storage")
|
|
116
|
-
# #region agent log
|
|
117
|
-
write_debug_log(
|
|
118
|
-
{
|
|
119
|
-
"sessionId": "debug-session",
|
|
120
|
-
"runId": "run1",
|
|
121
|
-
"hypothesisId": "A",
|
|
122
|
-
"message": "API key set in environment",
|
|
123
|
-
"data": {"env_set_success": True},
|
|
124
|
-
},
|
|
125
|
-
location="backup_client.py:_ensure_context7_api_key:env_set",
|
|
126
|
-
)
|
|
127
|
-
# #endregion
|
|
128
|
-
return api_key
|
|
129
|
-
except Exception as e:
|
|
130
|
-
logger.debug(f"Could not load API key from encrypted storage: {e}")
|
|
131
|
-
# #region agent log
|
|
132
|
-
write_debug_log(
|
|
133
|
-
{
|
|
134
|
-
"sessionId": "debug-session",
|
|
135
|
-
"runId": "run1",
|
|
136
|
-
"hypothesisId": "A",
|
|
137
|
-
"message": "Failed to load from storage",
|
|
138
|
-
"data": {"error": str(e)},
|
|
139
|
-
},
|
|
140
|
-
location="backup_client.py:_ensure_context7_api_key:error",
|
|
141
|
-
)
|
|
142
|
-
# #endregion
|
|
143
|
-
|
|
144
|
-
# #region agent log
|
|
145
|
-
write_debug_log(
|
|
146
|
-
{
|
|
147
|
-
"sessionId": "debug-session",
|
|
148
|
-
"runId": "run1",
|
|
149
|
-
"hypothesisId": "A",
|
|
150
|
-
"message": "Returning None - no API key available",
|
|
151
|
-
"data": {},
|
|
152
|
-
},
|
|
153
|
-
location="backup_client.py:_ensure_context7_api_key:return_none",
|
|
154
|
-
)
|
|
155
|
-
# #endregion
|
|
156
|
-
return None
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
def check_mcp_tools_available(gateway: MCPGateway | None = None) -> tuple[bool, str]:
|
|
160
|
-
"""
|
|
161
|
-
Check if Context7 MCP tools are available via Cursor's MCP server or local gateway.
|
|
162
|
-
|
|
163
|
-
R1: Improved detection to distinguish between Cursor MCP tools and local gateway.
|
|
164
|
-
|
|
165
|
-
Returns:
|
|
166
|
-
Tuple of (available: bool, source: str) where source is:
|
|
167
|
-
- "cursor_mcp" if in Cursor mode (tools available via Cursor's MCP server)
|
|
168
|
-
- "local_gateway" if tools registered in local MCPGateway
|
|
169
|
-
- "none" if not available
|
|
170
|
-
"""
|
|
171
|
-
# First, check if we're running in Cursor mode
|
|
172
|
-
from ..core.runtime_mode import is_cursor_mode
|
|
173
|
-
if is_cursor_mode():
|
|
174
|
-
# In Cursor mode, MCP tools are available through Cursor's MCP server
|
|
175
|
-
# Python code cannot directly call them, but AI assistant can
|
|
176
|
-
logger.debug("Running in Cursor mode - Context7 MCP tools available via Cursor's MCP server")
|
|
177
|
-
return True, "cursor_mcp"
|
|
178
|
-
|
|
179
|
-
# If not in Cursor mode, check the custom gateway registry
|
|
180
|
-
if gateway is None:
|
|
181
|
-
try:
|
|
182
|
-
gateway = MCPGateway()
|
|
183
|
-
except Exception:
|
|
184
|
-
return False, "none"
|
|
185
|
-
|
|
186
|
-
try:
|
|
187
|
-
# Check if Context7 MCP tools are registered in custom gateway
|
|
188
|
-
tools = gateway.list_available_tools()
|
|
189
|
-
tool_names = [tool.get("name", "") for tool in tools]
|
|
190
|
-
|
|
191
|
-
has_tools = (
|
|
192
|
-
"mcp_Context7_resolve-library-id" in tool_names
|
|
193
|
-
and "mcp_Context7_get-library-docs" in tool_names
|
|
194
|
-
)
|
|
195
|
-
return has_tools, "local_gateway" if has_tools else "none"
|
|
196
|
-
except Exception:
|
|
197
|
-
return False, "none"
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
def check_context7_api_available() -> bool:
|
|
201
|
-
"""
|
|
202
|
-
Check if Context7 API key is available (for fallback direct HTTP calls).
|
|
203
|
-
|
|
204
|
-
Automatically loads from encrypted storage if not in environment.
|
|
205
|
-
|
|
206
|
-
Returns True if API key is set, False otherwise.
|
|
207
|
-
"""
|
|
208
|
-
api_key = _ensure_context7_api_key()
|
|
209
|
-
return api_key is not None
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
def create_fallback_http_client() -> tuple[Callable[[str], dict[str, Any]], Callable[[str, str | None, str | None, int | None], Any] | None]:
|
|
213
|
-
"""
|
|
214
|
-
Create fallback HTTP client functions for direct API calls.
|
|
215
|
-
|
|
216
|
-
Only used if MCP Gateway tools are not available.
|
|
217
|
-
Requires CONTEXT7_API_KEY environment variable.
|
|
218
|
-
|
|
219
|
-
Returns:
|
|
220
|
-
Tuple of (resolve_library_client, get_docs_client) or (None, None) if API key not available
|
|
221
|
-
"""
|
|
222
|
-
# #region agent log
|
|
223
|
-
from ..core.debug_logger import write_debug_log
|
|
224
|
-
write_debug_log(
|
|
225
|
-
{
|
|
226
|
-
"sessionId": "debug-session",
|
|
227
|
-
"runId": "run1",
|
|
228
|
-
"hypothesisId": "B",
|
|
229
|
-
"message": "create_fallback_http_client called",
|
|
230
|
-
"data": {"api_key_at_creation": os.getenv("CONTEXT7_API_KEY") is not None},
|
|
231
|
-
},
|
|
232
|
-
location="backup_client.py:create_fallback_http_client:entry",
|
|
233
|
-
)
|
|
234
|
-
# #endregion
|
|
235
|
-
api_key = _ensure_context7_api_key()
|
|
236
|
-
# #region agent log
|
|
237
|
-
write_debug_log(
|
|
238
|
-
{
|
|
239
|
-
"sessionId": "debug-session",
|
|
240
|
-
"runId": "run1",
|
|
241
|
-
"hypothesisId": "B",
|
|
242
|
-
"message": "API key check after _ensure_context7_api_key",
|
|
243
|
-
"data": {"api_key_available": api_key is not None, "key_length": len(api_key) if api_key else 0, "env_key_after": os.getenv("CONTEXT7_API_KEY") is not None},
|
|
244
|
-
},
|
|
245
|
-
location="backup_client.py:create_fallback_http_client:api_key_check",
|
|
246
|
-
)
|
|
247
|
-
# #endregion
|
|
248
|
-
if not api_key:
|
|
249
|
-
# #region agent log
|
|
250
|
-
write_debug_log(
|
|
251
|
-
{
|
|
252
|
-
"sessionId": "debug-session",
|
|
253
|
-
"runId": "run1",
|
|
254
|
-
"hypothesisId": "B",
|
|
255
|
-
"message": "RETURNING None, None (no API key)",
|
|
256
|
-
"data": {},
|
|
257
|
-
},
|
|
258
|
-
location="backup_client.py:67",
|
|
259
|
-
)
|
|
260
|
-
# #endregion
|
|
261
|
-
return None, None
|
|
262
|
-
|
|
263
|
-
# Context7 API base URL - using correct API endpoint from documentation
|
|
264
|
-
BASE_URL = os.getenv("CONTEXT7_API_URL", "https://context7.com/api/v2")
|
|
265
|
-
|
|
266
|
-
def resolve_library_client(
|
|
267
|
-
library_name: str,
|
|
268
|
-
offline_mode: bool = False
|
|
269
|
-
) -> dict[str, Any]:
|
|
270
|
-
"""
|
|
271
|
-
Fallback HTTP client for library resolution.
|
|
272
|
-
Only used if MCP Gateway is not available.
|
|
273
|
-
|
|
274
|
-
Uses Context7 Search API: GET /api/v2/search?query={library_name}
|
|
275
|
-
|
|
276
|
-
Args:
|
|
277
|
-
library_name: Name of library to resolve
|
|
278
|
-
offline_mode: If True, return cached result or empty matches without network call
|
|
279
|
-
"""
|
|
280
|
-
# Check offline mode
|
|
281
|
-
from ..core.offline_mode import OfflineMode
|
|
282
|
-
|
|
283
|
-
if offline_mode or OfflineMode.is_offline():
|
|
284
|
-
return {
|
|
285
|
-
"success": False,
|
|
286
|
-
"error": "Offline mode",
|
|
287
|
-
"result": {"matches": []}
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
# Fast-fail if quota already exceeded (avoid repeated HTTP calls / log spam)
|
|
291
|
-
if is_context7_quota_exceeded():
|
|
292
|
-
msg = get_context7_quota_message() or "Monthly quota exceeded"
|
|
293
|
-
return {
|
|
294
|
-
"success": False,
|
|
295
|
-
"error": f"Context7 API quota exceeded: {msg}",
|
|
296
|
-
"result": {"matches": []},
|
|
297
|
-
}
|
|
298
|
-
# #region agent log
|
|
299
|
-
from ..core.debug_logger import write_debug_log
|
|
300
|
-
write_debug_log(
|
|
301
|
-
{
|
|
302
|
-
"sessionId": "debug-session",
|
|
303
|
-
"runId": "run1",
|
|
304
|
-
"hypothesisId": "F",
|
|
305
|
-
"message": "BEFORE API call",
|
|
306
|
-
"data": {"library": library_name, "api_key_set": api_key is not None},
|
|
307
|
-
},
|
|
308
|
-
location="backup_client.py:resolve_library_client",
|
|
309
|
-
)
|
|
310
|
-
# #endregion
|
|
311
|
-
try:
|
|
312
|
-
with httpx.Client(timeout=10.0) as client:
|
|
313
|
-
response = client.get(
|
|
314
|
-
f"{BASE_URL}/search",
|
|
315
|
-
headers={
|
|
316
|
-
"Authorization": f"Bearer {api_key}",
|
|
317
|
-
"Content-Type": "application/json",
|
|
318
|
-
},
|
|
319
|
-
params={"query": library_name},
|
|
320
|
-
)
|
|
321
|
-
# #region agent log
|
|
322
|
-
write_debug_log(
|
|
323
|
-
{
|
|
324
|
-
"sessionId": "debug-session",
|
|
325
|
-
"runId": "run1",
|
|
326
|
-
"hypothesisId": "F",
|
|
327
|
-
"message": "AFTER API call",
|
|
328
|
-
"data": {"status_code": response.status_code, "library": library_name},
|
|
329
|
-
},
|
|
330
|
-
location="backup_client.py:resolve_library_client",
|
|
331
|
-
)
|
|
332
|
-
# #endregion
|
|
333
|
-
|
|
334
|
-
if response.status_code == 200:
|
|
335
|
-
data = response.json()
|
|
336
|
-
# Format search results to match MCP tool response format
|
|
337
|
-
# Search API returns array of results, convert to matches format
|
|
338
|
-
results = data if isinstance(data, list) else data.get("results", [])
|
|
339
|
-
matches = []
|
|
340
|
-
for result in results:
|
|
341
|
-
matches.append({
|
|
342
|
-
"id": result.get("id"),
|
|
343
|
-
"title": result.get("title"),
|
|
344
|
-
"description": result.get("description"),
|
|
345
|
-
"benchmarkScore": result.get("benchmarkScore"),
|
|
346
|
-
})
|
|
347
|
-
# #region agent log
|
|
348
|
-
write_debug_log(
|
|
349
|
-
{
|
|
350
|
-
"sessionId": "debug-session",
|
|
351
|
-
"runId": "run1",
|
|
352
|
-
"hypothesisId": "F",
|
|
353
|
-
"message": "API SUCCESS",
|
|
354
|
-
"data": {"library": library_name, "matches_count": len(matches)},
|
|
355
|
-
},
|
|
356
|
-
location="backup_client.py:resolve_library_client",
|
|
357
|
-
)
|
|
358
|
-
# #endregion
|
|
359
|
-
return {
|
|
360
|
-
"success": True,
|
|
361
|
-
"result": {
|
|
362
|
-
"matches": matches,
|
|
363
|
-
},
|
|
364
|
-
}
|
|
365
|
-
elif response.status_code == 429:
|
|
366
|
-
# CRITICAL FIX: Quota exceeded - mark immediately and open circuit breaker
|
|
367
|
-
try:
|
|
368
|
-
error_data = response.json()
|
|
369
|
-
quota_message = error_data.get("message", "Daily quota exceeded")
|
|
370
|
-
except Exception:
|
|
371
|
-
quota_message = "Daily quota exceeded"
|
|
372
|
-
|
|
373
|
-
# Mark quota as exceeded globally (this prevents future API calls)
|
|
374
|
-
_mark_context7_quota_exceeded(quota_message)
|
|
375
|
-
|
|
376
|
-
# CRITICAL FIX: Open circuit breaker immediately on quota error
|
|
377
|
-
# This prevents subsequent parallel calls from attempting API requests
|
|
378
|
-
try:
|
|
379
|
-
from .circuit_breaker import get_context7_circuit_breaker, CircuitState
|
|
380
|
-
circuit_breaker = get_context7_circuit_breaker()
|
|
381
|
-
# Force open circuit breaker immediately (bypass threshold)
|
|
382
|
-
if hasattr(circuit_breaker, '_stats'):
|
|
383
|
-
circuit_breaker._stats.state = CircuitState.OPEN
|
|
384
|
-
circuit_breaker._stats.last_failure_time = time.time()
|
|
385
|
-
circuit_breaker._stats.last_state_change = time.time()
|
|
386
|
-
logger.warning(
|
|
387
|
-
f"Context7 circuit breaker opened immediately due to quota error (429). "
|
|
388
|
-
f"Subsequent requests will be rejected without API calls."
|
|
389
|
-
)
|
|
390
|
-
except Exception as cb_error:
|
|
391
|
-
logger.debug(f"Could not open circuit breaker on quota error: {cb_error}")
|
|
392
|
-
|
|
393
|
-
# #region agent log
|
|
394
|
-
write_debug_log(
|
|
395
|
-
{
|
|
396
|
-
"sessionId": "debug-session",
|
|
397
|
-
"runId": "run1",
|
|
398
|
-
"hypothesisId": "F",
|
|
399
|
-
"message": "API QUOTA EXCEEDED",
|
|
400
|
-
"data": {"status_code": 429, "library": library_name, "message": quota_message},
|
|
401
|
-
},
|
|
402
|
-
location="backup_client.py:resolve_library_client",
|
|
403
|
-
)
|
|
404
|
-
# #endregion
|
|
405
|
-
return {
|
|
406
|
-
"success": False,
|
|
407
|
-
"error": f"Context7 API quota exceeded: {quota_message}",
|
|
408
|
-
"result": {
|
|
409
|
-
"matches": [],
|
|
410
|
-
},
|
|
411
|
-
}
|
|
412
|
-
else:
|
|
413
|
-
# #region agent log
|
|
414
|
-
write_debug_log(
|
|
415
|
-
{
|
|
416
|
-
"sessionId": "debug-session",
|
|
417
|
-
"runId": "run1",
|
|
418
|
-
"hypothesisId": "F",
|
|
419
|
-
"message": "API ERROR",
|
|
420
|
-
"data": {"status_code": response.status_code, "library": library_name, "response_text": response.text[:200]},
|
|
421
|
-
},
|
|
422
|
-
location="backup_client.py:resolve_library_client",
|
|
423
|
-
)
|
|
424
|
-
# #endregion
|
|
425
|
-
return {
|
|
426
|
-
"success": False,
|
|
427
|
-
"error": f"API returned status {response.status_code}",
|
|
428
|
-
"result": {
|
|
429
|
-
"matches": [],
|
|
430
|
-
},
|
|
431
|
-
}
|
|
432
|
-
except httpx.ConnectError as e:
|
|
433
|
-
# #region agent log
|
|
434
|
-
write_debug_log(
|
|
435
|
-
{
|
|
436
|
-
"sessionId": "debug-session",
|
|
437
|
-
"runId": "run1",
|
|
438
|
-
"hypothesisId": "F",
|
|
439
|
-
"message": "CONNECTION ERROR",
|
|
440
|
-
"data": {"library": library_name, "error": str(e)},
|
|
441
|
-
},
|
|
442
|
-
location="backup_client.py:resolve_library_client",
|
|
443
|
-
)
|
|
444
|
-
# #endregion
|
|
445
|
-
|
|
446
|
-
# Record connection failure for offline mode detection
|
|
447
|
-
from ..core.offline_mode import OfflineMode
|
|
448
|
-
OfflineMode.record_connection_failure()
|
|
449
|
-
|
|
450
|
-
# Return error with context (don't raise exception to allow graceful fallback)
|
|
451
|
-
import uuid
|
|
452
|
-
request_id = str(uuid.uuid4())
|
|
453
|
-
return {
|
|
454
|
-
"success": False,
|
|
455
|
-
"error": "Context7 API endpoint not reachable",
|
|
456
|
-
"error_details": {
|
|
457
|
-
"operation": "Context7 library lookup",
|
|
458
|
-
"request_id": request_id,
|
|
459
|
-
"library": library_name,
|
|
460
|
-
"original_error": str(e),
|
|
461
|
-
},
|
|
462
|
-
"result": {
|
|
463
|
-
"matches": [],
|
|
464
|
-
},
|
|
465
|
-
}
|
|
466
|
-
except Exception as e:
|
|
467
|
-
# #region agent log
|
|
468
|
-
write_debug_log(
|
|
469
|
-
{
|
|
470
|
-
"sessionId": "debug-session",
|
|
471
|
-
"runId": "run1",
|
|
472
|
-
"hypothesisId": "F",
|
|
473
|
-
"message": "EXCEPTION",
|
|
474
|
-
"data": {"library": library_name, "error": str(e)},
|
|
475
|
-
},
|
|
476
|
-
location="backup_client.py:resolve_library_client",
|
|
477
|
-
)
|
|
478
|
-
# #endregion
|
|
479
|
-
return {
|
|
480
|
-
"success": False,
|
|
481
|
-
"error": str(e),
|
|
482
|
-
"result": {
|
|
483
|
-
"matches": [],
|
|
484
|
-
},
|
|
485
|
-
}
|
|
486
|
-
|
|
487
|
-
def get_docs_client(
|
|
488
|
-
context7_id: str, topic: str | None = None, mode: str = "code", page: int = 1
|
|
489
|
-
) -> dict[str, Any]:
|
|
490
|
-
"""
|
|
491
|
-
Fallback HTTP client for documentation fetch.
|
|
492
|
-
Only used if MCP Gateway is not available.
|
|
493
|
-
|
|
494
|
-
Uses Context7 Docs API: GET /api/v2/docs/{mode}/{library_id}?type=json&topic={topic}&page={page}
|
|
495
|
-
- mode: "code" or "info"
|
|
496
|
-
- type: "json" (for structured response) or "txt"
|
|
497
|
-
"""
|
|
498
|
-
# Fast-fail if quota already exceeded (avoid repeated HTTP calls / log spam)
|
|
499
|
-
if is_context7_quota_exceeded():
|
|
500
|
-
msg = get_context7_quota_message() or "Monthly quota exceeded"
|
|
501
|
-
return {"success": False, "error": f"Context7 API quota exceeded: {msg}", "result": {}}
|
|
502
|
-
|
|
503
|
-
try:
|
|
504
|
-
with httpx.Client(timeout=30.0) as client:
|
|
505
|
-
# Remove leading slash from context7_id if present (API expects format like "vercel/next.js")
|
|
506
|
-
library_id = context7_id.lstrip("/")
|
|
507
|
-
|
|
508
|
-
# Build endpoint: /api/v2/docs/{mode}/{library_id}
|
|
509
|
-
endpoint = f"{BASE_URL}/docs/{mode}/{library_id}"
|
|
510
|
-
|
|
511
|
-
# Build query parameters
|
|
512
|
-
params = {"type": "json", "page": page}
|
|
513
|
-
if topic:
|
|
514
|
-
params["topic"] = topic
|
|
515
|
-
|
|
516
|
-
response = client.get(
|
|
517
|
-
endpoint,
|
|
518
|
-
headers={
|
|
519
|
-
"Authorization": f"Bearer {api_key}",
|
|
520
|
-
"Content-Type": "application/json",
|
|
521
|
-
},
|
|
522
|
-
params=params,
|
|
523
|
-
)
|
|
524
|
-
|
|
525
|
-
if response.status_code == 200:
|
|
526
|
-
try:
|
|
527
|
-
data = response.json()
|
|
528
|
-
# Format to match MCP tool response format
|
|
529
|
-
# Context7 API returns snippets array, extract content
|
|
530
|
-
if isinstance(data, dict):
|
|
531
|
-
snippets = data.get("snippets", [])
|
|
532
|
-
# Combine snippets into markdown content
|
|
533
|
-
content_parts = []
|
|
534
|
-
for snippet in snippets:
|
|
535
|
-
if isinstance(snippet, dict):
|
|
536
|
-
# For code mode: extract code snippets
|
|
537
|
-
if mode == "code":
|
|
538
|
-
code_list = snippet.get("codeList", [])
|
|
539
|
-
for code_item in code_list:
|
|
540
|
-
code = code_item.get("code", "")
|
|
541
|
-
title = snippet.get("codeTitle", "")
|
|
542
|
-
if title:
|
|
543
|
-
content_parts.append(f"## {title}\n")
|
|
544
|
-
if code:
|
|
545
|
-
content_parts.append(f"```{code_item.get('language', '')}\n{code}\n```\n")
|
|
546
|
-
# For info mode: extract content
|
|
547
|
-
elif mode == "info":
|
|
548
|
-
content = snippet.get("content", "")
|
|
549
|
-
breadcrumb = snippet.get("breadcrumb", "")
|
|
550
|
-
if breadcrumb:
|
|
551
|
-
content_parts.append(f"## {breadcrumb}\n")
|
|
552
|
-
if content:
|
|
553
|
-
content_parts.append(f"{content}\n")
|
|
554
|
-
|
|
555
|
-
content = "\n".join(content_parts) if content_parts else response.text
|
|
556
|
-
|
|
557
|
-
return {
|
|
558
|
-
"success": True,
|
|
559
|
-
"result": {
|
|
560
|
-
"content": content,
|
|
561
|
-
},
|
|
562
|
-
}
|
|
563
|
-
# Fallback: return as string
|
|
564
|
-
return {
|
|
565
|
-
"success": True,
|
|
566
|
-
"result": {
|
|
567
|
-
"content": response.text,
|
|
568
|
-
},
|
|
569
|
-
}
|
|
570
|
-
except Exception:
|
|
571
|
-
return {
|
|
572
|
-
"success": True,
|
|
573
|
-
"result": {
|
|
574
|
-
"content": response.text,
|
|
575
|
-
},
|
|
576
|
-
}
|
|
577
|
-
elif response.status_code == 429:
|
|
578
|
-
# CRITICAL FIX: Quota exceeded - mark immediately and open circuit breaker
|
|
579
|
-
try:
|
|
580
|
-
error_data = response.json()
|
|
581
|
-
quota_message = error_data.get("message", "Daily quota exceeded")
|
|
582
|
-
except Exception:
|
|
583
|
-
quota_message = "Daily quota exceeded"
|
|
584
|
-
|
|
585
|
-
# Mark quota as exceeded globally (this prevents future API calls)
|
|
586
|
-
_mark_context7_quota_exceeded(quota_message)
|
|
587
|
-
|
|
588
|
-
# CRITICAL FIX: Open circuit breaker immediately on quota error
|
|
589
|
-
# This prevents subsequent parallel calls from attempting API requests
|
|
590
|
-
try:
|
|
591
|
-
from .circuit_breaker import get_context7_circuit_breaker, CircuitState
|
|
592
|
-
circuit_breaker = get_context7_circuit_breaker()
|
|
593
|
-
# Force open circuit breaker immediately (bypass threshold)
|
|
594
|
-
if hasattr(circuit_breaker, '_stats'):
|
|
595
|
-
circuit_breaker._stats.state = CircuitState.OPEN
|
|
596
|
-
circuit_breaker._stats.last_failure_time = time.time()
|
|
597
|
-
circuit_breaker._stats.last_state_change = time.time()
|
|
598
|
-
logger.warning(
|
|
599
|
-
f"Context7 circuit breaker opened immediately due to quota error (429). "
|
|
600
|
-
f"Subsequent requests will be rejected without API calls."
|
|
601
|
-
)
|
|
602
|
-
except Exception as cb_error:
|
|
603
|
-
logger.debug(f"Could not open circuit breaker on quota error: {cb_error}")
|
|
604
|
-
|
|
605
|
-
return {
|
|
606
|
-
"success": False,
|
|
607
|
-
"error": f"Context7 API quota exceeded: {quota_message}",
|
|
608
|
-
"result": {},
|
|
609
|
-
}
|
|
610
|
-
else:
|
|
611
|
-
return {
|
|
612
|
-
"success": False,
|
|
613
|
-
"error": f"API returned status {response.status_code}",
|
|
614
|
-
"result": {},
|
|
615
|
-
}
|
|
616
|
-
except Exception as e:
|
|
617
|
-
return {
|
|
618
|
-
"success": False,
|
|
619
|
-
"error": str(e),
|
|
620
|
-
"result": {},
|
|
621
|
-
}
|
|
622
|
-
|
|
623
|
-
return resolve_library_client, get_docs_client
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
def get_context7_client_with_fallback(
|
|
627
|
-
mcp_gateway: MCPGateway | None = None,
|
|
628
|
-
) -> tuple[MCPGateway | None, bool, str, Callable[[str], dict[str, Any]] | None, Callable[[str, str | None, str | None, int | None], dict[str, Any]] | None]:
|
|
629
|
-
"""
|
|
630
|
-
Get Context7 client with automatic fallback.
|
|
631
|
-
|
|
632
|
-
R1: Improved to distinguish between Cursor MCP tools and local gateway.
|
|
633
|
-
|
|
634
|
-
Pattern:
|
|
635
|
-
1. Prefer MCP Gateway (Cursor's MCP server) - no API key needed
|
|
636
|
-
2. Fallback to direct HTTP calls if MCP not available - requires CONTEXT7_API_KEY
|
|
637
|
-
|
|
638
|
-
Args:
|
|
639
|
-
mcp_gateway: Optional MCPGateway instance (creates new if None)
|
|
640
|
-
|
|
641
|
-
Returns:
|
|
642
|
-
Tuple of (gateway, use_mcp, mcp_source, resolve_client, get_docs_client):
|
|
643
|
-
- gateway: MCPGateway instance (may be None in Cursor mode)
|
|
644
|
-
- use_mcp: True if MCP tools are available, False if using fallback
|
|
645
|
-
- mcp_source: "cursor_mcp", "local_gateway", or "none"
|
|
646
|
-
- resolve_client: HTTP client function (only if use_mcp=False)
|
|
647
|
-
- get_docs_client: HTTP client function (only if use_mcp=False)
|
|
648
|
-
"""
|
|
649
|
-
if mcp_gateway is None:
|
|
650
|
-
try:
|
|
651
|
-
mcp_gateway = MCPGateway()
|
|
652
|
-
except Exception:
|
|
653
|
-
mcp_gateway = None
|
|
654
|
-
|
|
655
|
-
# Check if MCP tools are available (R1: improved detection)
|
|
656
|
-
mcp_available, mcp_source = check_mcp_tools_available(mcp_gateway)
|
|
657
|
-
|
|
658
|
-
# CRITICAL FIX: Python code cannot call Cursor's MCP tools directly
|
|
659
|
-
# Even though MCP tools are "available" in Cursor mode, Python code must use HTTP fallback
|
|
660
|
-
# Only the AI assistant (via Cursor chat) can use MCP tools directly
|
|
661
|
-
from ..core.runtime_mode import is_cursor_mode
|
|
662
|
-
# #region agent log
|
|
663
|
-
from ..core.debug_logger import write_debug_log
|
|
664
|
-
write_debug_log(
|
|
665
|
-
{
|
|
666
|
-
"sessionId": "debug-session",
|
|
667
|
-
"runId": "run2",
|
|
668
|
-
"hypothesisId": "F",
|
|
669
|
-
"message": "Before MCP fix check",
|
|
670
|
-
"data": {"mcp_available": mcp_available, "mcp_source": mcp_source, "is_cursor_mode": is_cursor_mode()},
|
|
671
|
-
},
|
|
672
|
-
location="backup_client.py:get_context7_client_with_fallback:before_fix",
|
|
673
|
-
)
|
|
674
|
-
# #endregion
|
|
675
|
-
if mcp_available and mcp_source == "cursor_mcp":
|
|
676
|
-
# In Cursor mode, MCP tools are available to AI assistant but NOT to Python code
|
|
677
|
-
# Force HTTP fallback for Python code execution
|
|
678
|
-
logger.debug(
|
|
679
|
-
"Context7 MCP tools available in Cursor but Python code cannot call them. "
|
|
680
|
-
"Using HTTP fallback with API key."
|
|
681
|
-
)
|
|
682
|
-
# #region agent log
|
|
683
|
-
write_debug_log(
|
|
684
|
-
{
|
|
685
|
-
"sessionId": "debug-session",
|
|
686
|
-
"runId": "run2",
|
|
687
|
-
"hypothesisId": "F",
|
|
688
|
-
"message": "FIX APPLIED: Forcing HTTP fallback for Cursor MCP",
|
|
689
|
-
"data": {"mcp_available_before": True, "mcp_available_after": False},
|
|
690
|
-
},
|
|
691
|
-
location="backup_client.py:get_context7_client_with_fallback:fix_applied",
|
|
692
|
-
)
|
|
693
|
-
# #endregion
|
|
694
|
-
mcp_available = False # Force HTTP fallback
|
|
695
|
-
|
|
696
|
-
if mcp_available:
|
|
697
|
-
# Use MCP - no API key needed!
|
|
698
|
-
# This path is only for local gateway (not Cursor MCP)
|
|
699
|
-
return mcp_gateway, True, mcp_source, None, None
|
|
700
|
-
|
|
701
|
-
# Fallback to direct HTTP - requires API key
|
|
702
|
-
api_available = check_context7_api_available()
|
|
703
|
-
# #region agent log
|
|
704
|
-
write_debug_log(
|
|
705
|
-
{
|
|
706
|
-
"sessionId": "debug-session",
|
|
707
|
-
"runId": "run2",
|
|
708
|
-
"hypothesisId": "F",
|
|
709
|
-
"message": "Using HTTP fallback path",
|
|
710
|
-
"data": {"mcp_available": mcp_available, "api_available": api_available},
|
|
711
|
-
},
|
|
712
|
-
location="backup_client.py:get_context7_client_with_fallback:http_fallback",
|
|
713
|
-
)
|
|
714
|
-
# #endregion
|
|
715
|
-
if not api_available:
|
|
716
|
-
# R2/R3: Neither MCP nor API key available - provide clear error message
|
|
717
|
-
from ..core.runtime_mode import is_cursor_mode
|
|
718
|
-
if is_cursor_mode():
|
|
719
|
-
# In Cursor mode, MCP tools should be available but Python can't call them directly
|
|
720
|
-
logger.debug(
|
|
721
|
-
"Context7 MCP tools available in Cursor but cannot be called from Python. "
|
|
722
|
-
"AI assistant should use MCP tools directly."
|
|
723
|
-
)
|
|
724
|
-
else:
|
|
725
|
-
# R3: Clear error message for headless mode (only warn once)
|
|
726
|
-
global _CONTEXT7_NOT_AVAILABLE_WARNED
|
|
727
|
-
if not _CONTEXT7_NOT_AVAILABLE_WARNED:
|
|
728
|
-
logger.warning(
|
|
729
|
-
"Context7 not available: MCP tools not found and CONTEXT7_API_KEY not set. "
|
|
730
|
-
"To enable Context7:\n"
|
|
731
|
-
" 1. Set CONTEXT7_API_KEY environment variable, OR\n"
|
|
732
|
-
" 2. Configure Context7 MCP server\n"
|
|
733
|
-
"Continuing without Context7 functionality."
|
|
734
|
-
)
|
|
735
|
-
_CONTEXT7_NOT_AVAILABLE_WARNED = True
|
|
736
|
-
return mcp_gateway, False, "none", None, None
|
|
737
|
-
|
|
738
|
-
resolve_client, get_docs_client = create_fallback_http_client()
|
|
739
|
-
# #region agent log
|
|
740
|
-
write_debug_log(
|
|
741
|
-
{
|
|
742
|
-
"sessionId": "debug-session",
|
|
743
|
-
"runId": "run2",
|
|
744
|
-
"hypothesisId": "F",
|
|
745
|
-
"message": "Returning HTTP fallback clients",
|
|
746
|
-
"data": {"use_mcp": False, "resolve_client_created": resolve_client is not None, "get_docs_client_created": get_docs_client is not None},
|
|
747
|
-
},
|
|
748
|
-
location="backup_client.py:get_context7_client_with_fallback:return_http",
|
|
749
|
-
)
|
|
750
|
-
# #endregion
|
|
751
|
-
return mcp_gateway, False, "none", resolve_client, get_docs_client
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
async def call_context7_resolve_with_fallback(
|
|
755
|
-
library_name: str,
|
|
756
|
-
mcp_gateway: MCPGateway | None = None,
|
|
757
|
-
) -> dict[str, Any]:
|
|
758
|
-
"""
|
|
759
|
-
Call Context7 resolve-library-id with automatic fallback.
|
|
760
|
-
|
|
761
|
-
R1: Improved to handle Cursor MCP tools vs local gateway distinction.
|
|
762
|
-
|
|
763
|
-
Args:
|
|
764
|
-
library_name: Library name to resolve
|
|
765
|
-
mcp_gateway: Optional MCPGateway instance
|
|
766
|
-
|
|
767
|
-
Returns:
|
|
768
|
-
Dictionary with resolve result (matches MCP tool response format)
|
|
769
|
-
"""
|
|
770
|
-
gateway, use_mcp, mcp_source, resolve_client, _ = get_context7_client_with_fallback(mcp_gateway)
|
|
771
|
-
# #region agent log
|
|
772
|
-
from ..core.debug_logger import write_debug_log
|
|
773
|
-
write_debug_log(
|
|
774
|
-
{
|
|
775
|
-
"sessionId": "debug-session",
|
|
776
|
-
"runId": "run1",
|
|
777
|
-
"hypothesisId": "D",
|
|
778
|
-
"message": "call_context7_resolve_with_fallback got clients",
|
|
779
|
-
"data": {"use_mcp": use_mcp, "resolve_client": resolve_client is not None, "library": library_name},
|
|
780
|
-
},
|
|
781
|
-
location="backup_client.py:call_context7_resolve_with_fallback",
|
|
782
|
-
)
|
|
783
|
-
# #endregion
|
|
784
|
-
if use_mcp:
|
|
785
|
-
# R1: Handle Cursor MCP vs local gateway differently
|
|
786
|
-
if mcp_source == "cursor_mcp":
|
|
787
|
-
# In Cursor mode, Python code cannot directly call MCP tools
|
|
788
|
-
# AI assistant should use MCP tools: mcp_Context7_resolve-library-id
|
|
789
|
-
if resolve_client:
|
|
790
|
-
# Allow HTTP fallback for CLI commands in Cursor mode
|
|
791
|
-
logger.debug(
|
|
792
|
-
f"Context7 MCP tools available in Cursor for library '{library_name}', "
|
|
793
|
-
f"but using HTTP fallback for Python code path. "
|
|
794
|
-
f"AI assistant should use MCP tools directly for better performance."
|
|
795
|
-
)
|
|
796
|
-
return resolve_client(library_name)
|
|
797
|
-
else:
|
|
798
|
-
# No HTTP fallback - indicate MCP tools should be used
|
|
799
|
-
logger.info(
|
|
800
|
-
f"Context7 MCP tools available in Cursor but cannot be called from Python. "
|
|
801
|
-
f"AI assistant should use MCP tools: mcp_Context7_resolve-library-id with libraryName='{library_name}'."
|
|
802
|
-
)
|
|
803
|
-
return {
|
|
804
|
-
"success": False,
|
|
805
|
-
"error": "Context7 MCP tools available in Cursor but require AI assistant to call them. "
|
|
806
|
-
"Use MCP tools directly: mcp_Context7_resolve-library-id",
|
|
807
|
-
"result": {"matches": []},
|
|
808
|
-
}
|
|
809
|
-
elif mcp_source == "local_gateway" and gateway:
|
|
810
|
-
# Local gateway has tools registered - try to call them
|
|
811
|
-
try:
|
|
812
|
-
result = await gateway.call_tool(
|
|
813
|
-
"mcp_Context7_resolve-library-id",
|
|
814
|
-
libraryName=library_name,
|
|
815
|
-
)
|
|
816
|
-
return result
|
|
817
|
-
except (ValueError, KeyError) as e:
|
|
818
|
-
# Tool not found - fall back to HTTP if available
|
|
819
|
-
logger.debug(
|
|
820
|
-
f"Context7 MCP tool not found in local gateway for library '{library_name}': {e}. "
|
|
821
|
-
f"Falling back to HTTP client if available."
|
|
822
|
-
)
|
|
823
|
-
if resolve_client:
|
|
824
|
-
return resolve_client(library_name)
|
|
825
|
-
return {
|
|
826
|
-
"success": False,
|
|
827
|
-
"error": f"Context7 MCP tool not found: {e}",
|
|
828
|
-
"result": {"matches": []},
|
|
829
|
-
}
|
|
830
|
-
except Exception as e:
|
|
831
|
-
# Other errors - try fallback
|
|
832
|
-
logger.debug(
|
|
833
|
-
f"Context7 MCP Gateway call failed for library '{library_name}': {e}. Trying fallback.",
|
|
834
|
-
exc_info=True
|
|
835
|
-
)
|
|
836
|
-
if resolve_client:
|
|
837
|
-
return resolve_client(library_name)
|
|
838
|
-
return {
|
|
839
|
-
"success": False,
|
|
840
|
-
"error": f"MCP Gateway call failed: {e}",
|
|
841
|
-
"result": {"matches": []},
|
|
842
|
-
}
|
|
843
|
-
elif resolve_client:
|
|
844
|
-
# Fallback: Use direct HTTP client
|
|
845
|
-
return resolve_client(library_name)
|
|
846
|
-
else:
|
|
847
|
-
logger.info(
|
|
848
|
-
f"Context7 not available for library '{library_name}': MCP tools not found and CONTEXT7_API_KEY not set. "
|
|
849
|
-
f"Continuing without Context7 documentation."
|
|
850
|
-
)
|
|
851
|
-
return {
|
|
852
|
-
"success": False,
|
|
853
|
-
"error": "Context7 not available: MCP tools not found and CONTEXT7_API_KEY not set",
|
|
854
|
-
"result": {"matches": []},
|
|
855
|
-
}
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
async def call_context7_get_docs_with_fallback(
|
|
859
|
-
context7_id: str,
|
|
860
|
-
topic: str | None = None,
|
|
861
|
-
mode: str = "code",
|
|
862
|
-
page: int = 1,
|
|
863
|
-
mcp_gateway: MCPGateway | None = None,
|
|
864
|
-
) -> dict[str, Any]:
|
|
865
|
-
"""
|
|
866
|
-
Call Context7 get-library-docs with automatic fallback.
|
|
867
|
-
|
|
868
|
-
R1: Improved to handle Cursor MCP tools vs local gateway distinction.
|
|
869
|
-
|
|
870
|
-
Args:
|
|
871
|
-
context7_id: Context7-compatible library ID
|
|
872
|
-
topic: Optional topic name
|
|
873
|
-
mode: "code" or "info" (default: "code")
|
|
874
|
-
page: Page number (default: 1)
|
|
875
|
-
mcp_gateway: Optional MCPGateway instance
|
|
876
|
-
|
|
877
|
-
Returns:
|
|
878
|
-
Dictionary with docs result (matches MCP tool response format)
|
|
879
|
-
"""
|
|
880
|
-
gateway, use_mcp, mcp_source, _, get_docs_client = get_context7_client_with_fallback(mcp_gateway)
|
|
881
|
-
|
|
882
|
-
if use_mcp:
|
|
883
|
-
# R1: Handle Cursor MCP vs local gateway differently
|
|
884
|
-
if mcp_source == "cursor_mcp":
|
|
885
|
-
# In Cursor mode, Python code cannot directly call MCP tools
|
|
886
|
-
# AI assistant should use MCP tools: mcp_Context7_get-library-docs
|
|
887
|
-
if get_docs_client:
|
|
888
|
-
# Allow HTTP fallback for CLI commands in Cursor mode
|
|
889
|
-
logger.debug(
|
|
890
|
-
f"Context7 MCP tools available in Cursor for library '{context7_id}' (topic: {topic}), "
|
|
891
|
-
f"but using HTTP fallback for Python code path. "
|
|
892
|
-
f"AI assistant should use MCP tools directly for better performance."
|
|
893
|
-
)
|
|
894
|
-
return get_docs_client(context7_id, topic, mode, page)
|
|
895
|
-
else:
|
|
896
|
-
# No HTTP fallback - indicate MCP tools should be used
|
|
897
|
-
logger.info(
|
|
898
|
-
f"Context7 MCP tools available in Cursor but cannot be called from Python. "
|
|
899
|
-
f"AI assistant should use MCP tools: mcp_Context7_get-library-docs with "
|
|
900
|
-
f"context7CompatibleLibraryID='{context7_id}', topic='{topic}', mode='{mode}', page={page}."
|
|
901
|
-
)
|
|
902
|
-
return {
|
|
903
|
-
"success": False,
|
|
904
|
-
"error": "Context7 MCP tools available in Cursor but require AI assistant to call them. "
|
|
905
|
-
"Use MCP tools directly: mcp_Context7_get-library-docs",
|
|
906
|
-
"result": {},
|
|
907
|
-
}
|
|
908
|
-
elif mcp_source == "local_gateway" and gateway:
|
|
909
|
-
# Local gateway has tools registered - try to call them
|
|
910
|
-
try:
|
|
911
|
-
result = await gateway.call_tool(
|
|
912
|
-
"mcp_Context7_get-library-docs",
|
|
913
|
-
context7CompatibleLibraryID=context7_id,
|
|
914
|
-
topic=topic,
|
|
915
|
-
mode=mode,
|
|
916
|
-
page=page,
|
|
917
|
-
)
|
|
918
|
-
return result
|
|
919
|
-
except (ValueError, KeyError) as e:
|
|
920
|
-
# Tool not found - fall back to HTTP if available
|
|
921
|
-
logger.debug(
|
|
922
|
-
f"Context7 MCP tool not found in local gateway for library '{context7_id}' (topic: {topic}): {e}. "
|
|
923
|
-
f"Falling back to HTTP client if available."
|
|
924
|
-
)
|
|
925
|
-
if get_docs_client:
|
|
926
|
-
return get_docs_client(context7_id, topic, mode, page)
|
|
927
|
-
return {
|
|
928
|
-
"success": False,
|
|
929
|
-
"error": f"Context7 MCP tool not found: {e}",
|
|
930
|
-
"result": {},
|
|
931
|
-
}
|
|
932
|
-
except Exception as e:
|
|
933
|
-
# Other errors - try fallback
|
|
934
|
-
logger.debug(
|
|
935
|
-
f"Context7 MCP Gateway call failed for library '{context7_id}' (topic: {topic}): {e}. Trying fallback.",
|
|
936
|
-
exc_info=True
|
|
937
|
-
)
|
|
938
|
-
if get_docs_client:
|
|
939
|
-
return get_docs_client(context7_id, topic, mode, page)
|
|
940
|
-
return {
|
|
941
|
-
"success": False,
|
|
942
|
-
"error": f"MCP Gateway call failed: {e}",
|
|
943
|
-
"result": {},
|
|
944
|
-
}
|
|
945
|
-
elif get_docs_client:
|
|
946
|
-
# Fallback: Use direct HTTP client
|
|
947
|
-
return get_docs_client(context7_id, topic, mode, page)
|
|
948
|
-
else:
|
|
949
|
-
logger.info(
|
|
950
|
-
f"Context7 not available for library '{context7_id}' (topic: {topic}): "
|
|
951
|
-
f"MCP tools not found and CONTEXT7_API_KEY not set. Continuing without Context7 documentation."
|
|
952
|
-
)
|
|
953
|
-
return {
|
|
954
|
-
"success": False,
|
|
955
|
-
"error": "Context7 not available: MCP tools not found and CONTEXT7_API_KEY not set",
|
|
956
|
-
"result": {},
|
|
957
|
-
}
|
|
958
|
-
|
|
1
|
+
"""
|
|
2
|
+
Context7 Backup Client - HTTP fallback for Context7 API calls.
|
|
3
|
+
|
|
4
|
+
This module provides a backup mechanism for Context7 API calls when MCP Gateway
|
|
5
|
+
is not available. It implements the same pattern used in successful integration tests.
|
|
6
|
+
|
|
7
|
+
Pattern:
|
|
8
|
+
1. Prefer MCP Gateway (Cursor's MCP server) - no API key needed
|
|
9
|
+
2. Fallback to direct HTTP calls if MCP not available - requires CONTEXT7_API_KEY
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import logging
|
|
13
|
+
import os
|
|
14
|
+
import time
|
|
15
|
+
import urllib.parse
|
|
16
|
+
from typing import Any, Callable
|
|
17
|
+
|
|
18
|
+
import httpx
|
|
19
|
+
|
|
20
|
+
from ..core.debug_logger import write_debug_log
|
|
21
|
+
from ..mcp.gateway import MCPGateway
|
|
22
|
+
|
|
23
|
+
logger = logging.getLogger(__name__)
|
|
24
|
+
|
|
25
|
+
_CONTEXT7_QUOTA_EXCEEDED: bool = False
|
|
26
|
+
_CONTEXT7_QUOTA_MESSAGE: str | None = None
|
|
27
|
+
_CONTEXT7_NOT_AVAILABLE_WARNED: bool = False
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def is_context7_quota_exceeded() -> bool:
|
|
31
|
+
"""Return True if a Context7 quota-exceeded response was observed in this process."""
|
|
32
|
+
return _CONTEXT7_QUOTA_EXCEEDED
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def get_context7_quota_message() -> str | None:
|
|
36
|
+
"""Return the last Context7 quota-exceeded message observed in this process."""
|
|
37
|
+
return _CONTEXT7_QUOTA_MESSAGE
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _mark_context7_quota_exceeded(message: str) -> None:
|
|
41
|
+
"""Mark Context7 quota as exceeded (best-effort) to suppress repeated HTTP calls and log spam."""
|
|
42
|
+
global _CONTEXT7_QUOTA_EXCEEDED, _CONTEXT7_QUOTA_MESSAGE
|
|
43
|
+
_CONTEXT7_QUOTA_MESSAGE = message
|
|
44
|
+
if _CONTEXT7_QUOTA_EXCEEDED:
|
|
45
|
+
return
|
|
46
|
+
_CONTEXT7_QUOTA_EXCEEDED = True
|
|
47
|
+
logger.warning(
|
|
48
|
+
"Context7 API quota exceeded. Further Context7 HTTP fallback calls will be skipped for this run."
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _ensure_context7_api_key() -> str | None:
|
|
53
|
+
"""
|
|
54
|
+
Ensure Context7 API key is available in environment.
|
|
55
|
+
|
|
56
|
+
Checks environment variable first, then loads from encrypted storage if needed.
|
|
57
|
+
Automatically sets the environment variable if loaded from storage.
|
|
58
|
+
|
|
59
|
+
Returns:
|
|
60
|
+
API key string if available, None otherwise
|
|
61
|
+
"""
|
|
62
|
+
# #region agent log
|
|
63
|
+
from ..core.debug_logger import write_debug_log
|
|
64
|
+
write_debug_log(
|
|
65
|
+
{
|
|
66
|
+
"sessionId": "debug-session",
|
|
67
|
+
"runId": "run1",
|
|
68
|
+
"hypothesisId": "A",
|
|
69
|
+
"message": "_ensure_context7_api_key called",
|
|
70
|
+
"data": {"env_key_exists": os.getenv("CONTEXT7_API_KEY") is not None},
|
|
71
|
+
},
|
|
72
|
+
location="backup_client.py:_ensure_context7_api_key:entry",
|
|
73
|
+
)
|
|
74
|
+
# #endregion
|
|
75
|
+
|
|
76
|
+
# First check environment variable
|
|
77
|
+
api_key = os.getenv("CONTEXT7_API_KEY")
|
|
78
|
+
# #region agent log
|
|
79
|
+
write_debug_log(
|
|
80
|
+
{
|
|
81
|
+
"sessionId": "debug-session",
|
|
82
|
+
"runId": "run1",
|
|
83
|
+
"hypothesisId": "A",
|
|
84
|
+
"message": "Checked environment variable",
|
|
85
|
+
"data": {"api_key_from_env": api_key is not None, "key_length": len(api_key) if api_key else 0},
|
|
86
|
+
},
|
|
87
|
+
location="backup_client.py:_ensure_context7_api_key:env_check",
|
|
88
|
+
)
|
|
89
|
+
# #endregion
|
|
90
|
+
if api_key:
|
|
91
|
+
return api_key
|
|
92
|
+
|
|
93
|
+
# Try loading from encrypted storage
|
|
94
|
+
try:
|
|
95
|
+
from .security import APIKeyManager
|
|
96
|
+
|
|
97
|
+
key_manager = APIKeyManager()
|
|
98
|
+
api_key = key_manager.load_api_key("context7")
|
|
99
|
+
# #region agent log
|
|
100
|
+
write_debug_log(
|
|
101
|
+
{
|
|
102
|
+
"sessionId": "debug-session",
|
|
103
|
+
"runId": "run1",
|
|
104
|
+
"hypothesisId": "A",
|
|
105
|
+
"message": "Loaded from encrypted storage",
|
|
106
|
+
"data": {"api_key_loaded": api_key is not None, "key_length": len(api_key) if api_key else 0},
|
|
107
|
+
},
|
|
108
|
+
location="backup_client.py:_ensure_context7_api_key:storage_load",
|
|
109
|
+
)
|
|
110
|
+
# #endregion
|
|
111
|
+
|
|
112
|
+
if api_key:
|
|
113
|
+
# Set in environment for future use
|
|
114
|
+
os.environ["CONTEXT7_API_KEY"] = api_key
|
|
115
|
+
logger.debug("Loaded Context7 API key from encrypted storage")
|
|
116
|
+
# #region agent log
|
|
117
|
+
write_debug_log(
|
|
118
|
+
{
|
|
119
|
+
"sessionId": "debug-session",
|
|
120
|
+
"runId": "run1",
|
|
121
|
+
"hypothesisId": "A",
|
|
122
|
+
"message": "API key set in environment",
|
|
123
|
+
"data": {"env_set_success": True},
|
|
124
|
+
},
|
|
125
|
+
location="backup_client.py:_ensure_context7_api_key:env_set",
|
|
126
|
+
)
|
|
127
|
+
# #endregion
|
|
128
|
+
return api_key
|
|
129
|
+
except Exception as e:
|
|
130
|
+
logger.debug(f"Could not load API key from encrypted storage: {e}")
|
|
131
|
+
# #region agent log
|
|
132
|
+
write_debug_log(
|
|
133
|
+
{
|
|
134
|
+
"sessionId": "debug-session",
|
|
135
|
+
"runId": "run1",
|
|
136
|
+
"hypothesisId": "A",
|
|
137
|
+
"message": "Failed to load from storage",
|
|
138
|
+
"data": {"error": str(e)},
|
|
139
|
+
},
|
|
140
|
+
location="backup_client.py:_ensure_context7_api_key:error",
|
|
141
|
+
)
|
|
142
|
+
# #endregion
|
|
143
|
+
|
|
144
|
+
# #region agent log
|
|
145
|
+
write_debug_log(
|
|
146
|
+
{
|
|
147
|
+
"sessionId": "debug-session",
|
|
148
|
+
"runId": "run1",
|
|
149
|
+
"hypothesisId": "A",
|
|
150
|
+
"message": "Returning None - no API key available",
|
|
151
|
+
"data": {},
|
|
152
|
+
},
|
|
153
|
+
location="backup_client.py:_ensure_context7_api_key:return_none",
|
|
154
|
+
)
|
|
155
|
+
# #endregion
|
|
156
|
+
return None
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def check_mcp_tools_available(gateway: MCPGateway | None = None) -> tuple[bool, str]:
|
|
160
|
+
"""
|
|
161
|
+
Check if Context7 MCP tools are available via Cursor's MCP server or local gateway.
|
|
162
|
+
|
|
163
|
+
R1: Improved detection to distinguish between Cursor MCP tools and local gateway.
|
|
164
|
+
|
|
165
|
+
Returns:
|
|
166
|
+
Tuple of (available: bool, source: str) where source is:
|
|
167
|
+
- "cursor_mcp" if in Cursor mode (tools available via Cursor's MCP server)
|
|
168
|
+
- "local_gateway" if tools registered in local MCPGateway
|
|
169
|
+
- "none" if not available
|
|
170
|
+
"""
|
|
171
|
+
# First, check if we're running in Cursor mode
|
|
172
|
+
from ..core.runtime_mode import is_cursor_mode
|
|
173
|
+
if is_cursor_mode():
|
|
174
|
+
# In Cursor mode, MCP tools are available through Cursor's MCP server
|
|
175
|
+
# Python code cannot directly call them, but AI assistant can
|
|
176
|
+
logger.debug("Running in Cursor mode - Context7 MCP tools available via Cursor's MCP server")
|
|
177
|
+
return True, "cursor_mcp"
|
|
178
|
+
|
|
179
|
+
# If not in Cursor mode, check the custom gateway registry
|
|
180
|
+
if gateway is None:
|
|
181
|
+
try:
|
|
182
|
+
gateway = MCPGateway()
|
|
183
|
+
except Exception:
|
|
184
|
+
return False, "none"
|
|
185
|
+
|
|
186
|
+
try:
|
|
187
|
+
# Check if Context7 MCP tools are registered in custom gateway
|
|
188
|
+
tools = gateway.list_available_tools()
|
|
189
|
+
tool_names = [tool.get("name", "") for tool in tools]
|
|
190
|
+
|
|
191
|
+
has_tools = (
|
|
192
|
+
"mcp_Context7_resolve-library-id" in tool_names
|
|
193
|
+
and "mcp_Context7_get-library-docs" in tool_names
|
|
194
|
+
)
|
|
195
|
+
return has_tools, "local_gateway" if has_tools else "none"
|
|
196
|
+
except Exception:
|
|
197
|
+
return False, "none"
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def check_context7_api_available() -> bool:
|
|
201
|
+
"""
|
|
202
|
+
Check if Context7 API key is available (for fallback direct HTTP calls).
|
|
203
|
+
|
|
204
|
+
Automatically loads from encrypted storage if not in environment.
|
|
205
|
+
|
|
206
|
+
Returns True if API key is set, False otherwise.
|
|
207
|
+
"""
|
|
208
|
+
api_key = _ensure_context7_api_key()
|
|
209
|
+
return api_key is not None
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def create_fallback_http_client() -> tuple[Callable[[str], dict[str, Any]], Callable[[str, str | None, str | None, int | None], Any] | None]:
|
|
213
|
+
"""
|
|
214
|
+
Create fallback HTTP client functions for direct API calls.
|
|
215
|
+
|
|
216
|
+
Only used if MCP Gateway tools are not available.
|
|
217
|
+
Requires CONTEXT7_API_KEY environment variable.
|
|
218
|
+
|
|
219
|
+
Returns:
|
|
220
|
+
Tuple of (resolve_library_client, get_docs_client) or (None, None) if API key not available
|
|
221
|
+
"""
|
|
222
|
+
# #region agent log
|
|
223
|
+
from ..core.debug_logger import write_debug_log
|
|
224
|
+
write_debug_log(
|
|
225
|
+
{
|
|
226
|
+
"sessionId": "debug-session",
|
|
227
|
+
"runId": "run1",
|
|
228
|
+
"hypothesisId": "B",
|
|
229
|
+
"message": "create_fallback_http_client called",
|
|
230
|
+
"data": {"api_key_at_creation": os.getenv("CONTEXT7_API_KEY") is not None},
|
|
231
|
+
},
|
|
232
|
+
location="backup_client.py:create_fallback_http_client:entry",
|
|
233
|
+
)
|
|
234
|
+
# #endregion
|
|
235
|
+
api_key = _ensure_context7_api_key()
|
|
236
|
+
# #region agent log
|
|
237
|
+
write_debug_log(
|
|
238
|
+
{
|
|
239
|
+
"sessionId": "debug-session",
|
|
240
|
+
"runId": "run1",
|
|
241
|
+
"hypothesisId": "B",
|
|
242
|
+
"message": "API key check after _ensure_context7_api_key",
|
|
243
|
+
"data": {"api_key_available": api_key is not None, "key_length": len(api_key) if api_key else 0, "env_key_after": os.getenv("CONTEXT7_API_KEY") is not None},
|
|
244
|
+
},
|
|
245
|
+
location="backup_client.py:create_fallback_http_client:api_key_check",
|
|
246
|
+
)
|
|
247
|
+
# #endregion
|
|
248
|
+
if not api_key:
|
|
249
|
+
# #region agent log
|
|
250
|
+
write_debug_log(
|
|
251
|
+
{
|
|
252
|
+
"sessionId": "debug-session",
|
|
253
|
+
"runId": "run1",
|
|
254
|
+
"hypothesisId": "B",
|
|
255
|
+
"message": "RETURNING None, None (no API key)",
|
|
256
|
+
"data": {},
|
|
257
|
+
},
|
|
258
|
+
location="backup_client.py:67",
|
|
259
|
+
)
|
|
260
|
+
# #endregion
|
|
261
|
+
return None, None
|
|
262
|
+
|
|
263
|
+
# Context7 API base URL - using correct API endpoint from documentation
|
|
264
|
+
BASE_URL = os.getenv("CONTEXT7_API_URL", "https://context7.com/api/v2")
|
|
265
|
+
|
|
266
|
+
def resolve_library_client(
|
|
267
|
+
library_name: str,
|
|
268
|
+
offline_mode: bool = False
|
|
269
|
+
) -> dict[str, Any]:
|
|
270
|
+
"""
|
|
271
|
+
Fallback HTTP client for library resolution.
|
|
272
|
+
Only used if MCP Gateway is not available.
|
|
273
|
+
|
|
274
|
+
Uses Context7 Search API: GET /api/v2/search?query={library_name}
|
|
275
|
+
|
|
276
|
+
Args:
|
|
277
|
+
library_name: Name of library to resolve
|
|
278
|
+
offline_mode: If True, return cached result or empty matches without network call
|
|
279
|
+
"""
|
|
280
|
+
# Check offline mode
|
|
281
|
+
from ..core.offline_mode import OfflineMode
|
|
282
|
+
|
|
283
|
+
if offline_mode or OfflineMode.is_offline():
|
|
284
|
+
return {
|
|
285
|
+
"success": False,
|
|
286
|
+
"error": "Offline mode",
|
|
287
|
+
"result": {"matches": []}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
# Fast-fail if quota already exceeded (avoid repeated HTTP calls / log spam)
|
|
291
|
+
if is_context7_quota_exceeded():
|
|
292
|
+
msg = get_context7_quota_message() or "Monthly quota exceeded"
|
|
293
|
+
return {
|
|
294
|
+
"success": False,
|
|
295
|
+
"error": f"Context7 API quota exceeded: {msg}",
|
|
296
|
+
"result": {"matches": []},
|
|
297
|
+
}
|
|
298
|
+
# #region agent log
|
|
299
|
+
from ..core.debug_logger import write_debug_log
|
|
300
|
+
write_debug_log(
|
|
301
|
+
{
|
|
302
|
+
"sessionId": "debug-session",
|
|
303
|
+
"runId": "run1",
|
|
304
|
+
"hypothesisId": "F",
|
|
305
|
+
"message": "BEFORE API call",
|
|
306
|
+
"data": {"library": library_name, "api_key_set": api_key is not None},
|
|
307
|
+
},
|
|
308
|
+
location="backup_client.py:resolve_library_client",
|
|
309
|
+
)
|
|
310
|
+
# #endregion
|
|
311
|
+
try:
|
|
312
|
+
with httpx.Client(timeout=10.0) as client:
|
|
313
|
+
response = client.get(
|
|
314
|
+
f"{BASE_URL}/search",
|
|
315
|
+
headers={
|
|
316
|
+
"Authorization": f"Bearer {api_key}",
|
|
317
|
+
"Content-Type": "application/json",
|
|
318
|
+
},
|
|
319
|
+
params={"query": library_name},
|
|
320
|
+
)
|
|
321
|
+
# #region agent log
|
|
322
|
+
write_debug_log(
|
|
323
|
+
{
|
|
324
|
+
"sessionId": "debug-session",
|
|
325
|
+
"runId": "run1",
|
|
326
|
+
"hypothesisId": "F",
|
|
327
|
+
"message": "AFTER API call",
|
|
328
|
+
"data": {"status_code": response.status_code, "library": library_name},
|
|
329
|
+
},
|
|
330
|
+
location="backup_client.py:resolve_library_client",
|
|
331
|
+
)
|
|
332
|
+
# #endregion
|
|
333
|
+
|
|
334
|
+
if response.status_code == 200:
|
|
335
|
+
data = response.json()
|
|
336
|
+
# Format search results to match MCP tool response format
|
|
337
|
+
# Search API returns array of results, convert to matches format
|
|
338
|
+
results = data if isinstance(data, list) else data.get("results", [])
|
|
339
|
+
matches = []
|
|
340
|
+
for result in results:
|
|
341
|
+
matches.append({
|
|
342
|
+
"id": result.get("id"),
|
|
343
|
+
"title": result.get("title"),
|
|
344
|
+
"description": result.get("description"),
|
|
345
|
+
"benchmarkScore": result.get("benchmarkScore"),
|
|
346
|
+
})
|
|
347
|
+
# #region agent log
|
|
348
|
+
write_debug_log(
|
|
349
|
+
{
|
|
350
|
+
"sessionId": "debug-session",
|
|
351
|
+
"runId": "run1",
|
|
352
|
+
"hypothesisId": "F",
|
|
353
|
+
"message": "API SUCCESS",
|
|
354
|
+
"data": {"library": library_name, "matches_count": len(matches)},
|
|
355
|
+
},
|
|
356
|
+
location="backup_client.py:resolve_library_client",
|
|
357
|
+
)
|
|
358
|
+
# #endregion
|
|
359
|
+
return {
|
|
360
|
+
"success": True,
|
|
361
|
+
"result": {
|
|
362
|
+
"matches": matches,
|
|
363
|
+
},
|
|
364
|
+
}
|
|
365
|
+
elif response.status_code == 429:
|
|
366
|
+
# CRITICAL FIX: Quota exceeded - mark immediately and open circuit breaker
|
|
367
|
+
try:
|
|
368
|
+
error_data = response.json()
|
|
369
|
+
quota_message = error_data.get("message", "Daily quota exceeded")
|
|
370
|
+
except Exception:
|
|
371
|
+
quota_message = "Daily quota exceeded"
|
|
372
|
+
|
|
373
|
+
# Mark quota as exceeded globally (this prevents future API calls)
|
|
374
|
+
_mark_context7_quota_exceeded(quota_message)
|
|
375
|
+
|
|
376
|
+
# CRITICAL FIX: Open circuit breaker immediately on quota error
|
|
377
|
+
# This prevents subsequent parallel calls from attempting API requests
|
|
378
|
+
try:
|
|
379
|
+
from .circuit_breaker import get_context7_circuit_breaker, CircuitState
|
|
380
|
+
circuit_breaker = get_context7_circuit_breaker()
|
|
381
|
+
# Force open circuit breaker immediately (bypass threshold)
|
|
382
|
+
if hasattr(circuit_breaker, '_stats'):
|
|
383
|
+
circuit_breaker._stats.state = CircuitState.OPEN
|
|
384
|
+
circuit_breaker._stats.last_failure_time = time.time()
|
|
385
|
+
circuit_breaker._stats.last_state_change = time.time()
|
|
386
|
+
logger.warning(
|
|
387
|
+
f"Context7 circuit breaker opened immediately due to quota error (429). "
|
|
388
|
+
f"Subsequent requests will be rejected without API calls."
|
|
389
|
+
)
|
|
390
|
+
except Exception as cb_error:
|
|
391
|
+
logger.debug(f"Could not open circuit breaker on quota error: {cb_error}")
|
|
392
|
+
|
|
393
|
+
# #region agent log
|
|
394
|
+
write_debug_log(
|
|
395
|
+
{
|
|
396
|
+
"sessionId": "debug-session",
|
|
397
|
+
"runId": "run1",
|
|
398
|
+
"hypothesisId": "F",
|
|
399
|
+
"message": "API QUOTA EXCEEDED",
|
|
400
|
+
"data": {"status_code": 429, "library": library_name, "message": quota_message},
|
|
401
|
+
},
|
|
402
|
+
location="backup_client.py:resolve_library_client",
|
|
403
|
+
)
|
|
404
|
+
# #endregion
|
|
405
|
+
return {
|
|
406
|
+
"success": False,
|
|
407
|
+
"error": f"Context7 API quota exceeded: {quota_message}",
|
|
408
|
+
"result": {
|
|
409
|
+
"matches": [],
|
|
410
|
+
},
|
|
411
|
+
}
|
|
412
|
+
else:
|
|
413
|
+
# #region agent log
|
|
414
|
+
write_debug_log(
|
|
415
|
+
{
|
|
416
|
+
"sessionId": "debug-session",
|
|
417
|
+
"runId": "run1",
|
|
418
|
+
"hypothesisId": "F",
|
|
419
|
+
"message": "API ERROR",
|
|
420
|
+
"data": {"status_code": response.status_code, "library": library_name, "response_text": response.text[:200]},
|
|
421
|
+
},
|
|
422
|
+
location="backup_client.py:resolve_library_client",
|
|
423
|
+
)
|
|
424
|
+
# #endregion
|
|
425
|
+
return {
|
|
426
|
+
"success": False,
|
|
427
|
+
"error": f"API returned status {response.status_code}",
|
|
428
|
+
"result": {
|
|
429
|
+
"matches": [],
|
|
430
|
+
},
|
|
431
|
+
}
|
|
432
|
+
except httpx.ConnectError as e:
|
|
433
|
+
# #region agent log
|
|
434
|
+
write_debug_log(
|
|
435
|
+
{
|
|
436
|
+
"sessionId": "debug-session",
|
|
437
|
+
"runId": "run1",
|
|
438
|
+
"hypothesisId": "F",
|
|
439
|
+
"message": "CONNECTION ERROR",
|
|
440
|
+
"data": {"library": library_name, "error": str(e)},
|
|
441
|
+
},
|
|
442
|
+
location="backup_client.py:resolve_library_client",
|
|
443
|
+
)
|
|
444
|
+
# #endregion
|
|
445
|
+
|
|
446
|
+
# Record connection failure for offline mode detection
|
|
447
|
+
from ..core.offline_mode import OfflineMode
|
|
448
|
+
OfflineMode.record_connection_failure()
|
|
449
|
+
|
|
450
|
+
# Return error with context (don't raise exception to allow graceful fallback)
|
|
451
|
+
import uuid
|
|
452
|
+
request_id = str(uuid.uuid4())
|
|
453
|
+
return {
|
|
454
|
+
"success": False,
|
|
455
|
+
"error": "Context7 API endpoint not reachable",
|
|
456
|
+
"error_details": {
|
|
457
|
+
"operation": "Context7 library lookup",
|
|
458
|
+
"request_id": request_id,
|
|
459
|
+
"library": library_name,
|
|
460
|
+
"original_error": str(e),
|
|
461
|
+
},
|
|
462
|
+
"result": {
|
|
463
|
+
"matches": [],
|
|
464
|
+
},
|
|
465
|
+
}
|
|
466
|
+
except Exception as e:
|
|
467
|
+
# #region agent log
|
|
468
|
+
write_debug_log(
|
|
469
|
+
{
|
|
470
|
+
"sessionId": "debug-session",
|
|
471
|
+
"runId": "run1",
|
|
472
|
+
"hypothesisId": "F",
|
|
473
|
+
"message": "EXCEPTION",
|
|
474
|
+
"data": {"library": library_name, "error": str(e)},
|
|
475
|
+
},
|
|
476
|
+
location="backup_client.py:resolve_library_client",
|
|
477
|
+
)
|
|
478
|
+
# #endregion
|
|
479
|
+
return {
|
|
480
|
+
"success": False,
|
|
481
|
+
"error": str(e),
|
|
482
|
+
"result": {
|
|
483
|
+
"matches": [],
|
|
484
|
+
},
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
def get_docs_client(
|
|
488
|
+
context7_id: str, topic: str | None = None, mode: str = "code", page: int = 1
|
|
489
|
+
) -> dict[str, Any]:
|
|
490
|
+
"""
|
|
491
|
+
Fallback HTTP client for documentation fetch.
|
|
492
|
+
Only used if MCP Gateway is not available.
|
|
493
|
+
|
|
494
|
+
Uses Context7 Docs API: GET /api/v2/docs/{mode}/{library_id}?type=json&topic={topic}&page={page}
|
|
495
|
+
- mode: "code" or "info"
|
|
496
|
+
- type: "json" (for structured response) or "txt"
|
|
497
|
+
"""
|
|
498
|
+
# Fast-fail if quota already exceeded (avoid repeated HTTP calls / log spam)
|
|
499
|
+
if is_context7_quota_exceeded():
|
|
500
|
+
msg = get_context7_quota_message() or "Monthly quota exceeded"
|
|
501
|
+
return {"success": False, "error": f"Context7 API quota exceeded: {msg}", "result": {}}
|
|
502
|
+
|
|
503
|
+
try:
|
|
504
|
+
with httpx.Client(timeout=30.0) as client:
|
|
505
|
+
# Remove leading slash from context7_id if present (API expects format like "vercel/next.js")
|
|
506
|
+
library_id = context7_id.lstrip("/")
|
|
507
|
+
|
|
508
|
+
# Build endpoint: /api/v2/docs/{mode}/{library_id}
|
|
509
|
+
endpoint = f"{BASE_URL}/docs/{mode}/{library_id}"
|
|
510
|
+
|
|
511
|
+
# Build query parameters
|
|
512
|
+
params = {"type": "json", "page": page}
|
|
513
|
+
if topic:
|
|
514
|
+
params["topic"] = topic
|
|
515
|
+
|
|
516
|
+
response = client.get(
|
|
517
|
+
endpoint,
|
|
518
|
+
headers={
|
|
519
|
+
"Authorization": f"Bearer {api_key}",
|
|
520
|
+
"Content-Type": "application/json",
|
|
521
|
+
},
|
|
522
|
+
params=params,
|
|
523
|
+
)
|
|
524
|
+
|
|
525
|
+
if response.status_code == 200:
|
|
526
|
+
try:
|
|
527
|
+
data = response.json()
|
|
528
|
+
# Format to match MCP tool response format
|
|
529
|
+
# Context7 API returns snippets array, extract content
|
|
530
|
+
if isinstance(data, dict):
|
|
531
|
+
snippets = data.get("snippets", [])
|
|
532
|
+
# Combine snippets into markdown content
|
|
533
|
+
content_parts = []
|
|
534
|
+
for snippet in snippets:
|
|
535
|
+
if isinstance(snippet, dict):
|
|
536
|
+
# For code mode: extract code snippets
|
|
537
|
+
if mode == "code":
|
|
538
|
+
code_list = snippet.get("codeList", [])
|
|
539
|
+
for code_item in code_list:
|
|
540
|
+
code = code_item.get("code", "")
|
|
541
|
+
title = snippet.get("codeTitle", "")
|
|
542
|
+
if title:
|
|
543
|
+
content_parts.append(f"## {title}\n")
|
|
544
|
+
if code:
|
|
545
|
+
content_parts.append(f"```{code_item.get('language', '')}\n{code}\n```\n")
|
|
546
|
+
# For info mode: extract content
|
|
547
|
+
elif mode == "info":
|
|
548
|
+
content = snippet.get("content", "")
|
|
549
|
+
breadcrumb = snippet.get("breadcrumb", "")
|
|
550
|
+
if breadcrumb:
|
|
551
|
+
content_parts.append(f"## {breadcrumb}\n")
|
|
552
|
+
if content:
|
|
553
|
+
content_parts.append(f"{content}\n")
|
|
554
|
+
|
|
555
|
+
content = "\n".join(content_parts) if content_parts else response.text
|
|
556
|
+
|
|
557
|
+
return {
|
|
558
|
+
"success": True,
|
|
559
|
+
"result": {
|
|
560
|
+
"content": content,
|
|
561
|
+
},
|
|
562
|
+
}
|
|
563
|
+
# Fallback: return as string
|
|
564
|
+
return {
|
|
565
|
+
"success": True,
|
|
566
|
+
"result": {
|
|
567
|
+
"content": response.text,
|
|
568
|
+
},
|
|
569
|
+
}
|
|
570
|
+
except Exception:
|
|
571
|
+
return {
|
|
572
|
+
"success": True,
|
|
573
|
+
"result": {
|
|
574
|
+
"content": response.text,
|
|
575
|
+
},
|
|
576
|
+
}
|
|
577
|
+
elif response.status_code == 429:
|
|
578
|
+
# CRITICAL FIX: Quota exceeded - mark immediately and open circuit breaker
|
|
579
|
+
try:
|
|
580
|
+
error_data = response.json()
|
|
581
|
+
quota_message = error_data.get("message", "Daily quota exceeded")
|
|
582
|
+
except Exception:
|
|
583
|
+
quota_message = "Daily quota exceeded"
|
|
584
|
+
|
|
585
|
+
# Mark quota as exceeded globally (this prevents future API calls)
|
|
586
|
+
_mark_context7_quota_exceeded(quota_message)
|
|
587
|
+
|
|
588
|
+
# CRITICAL FIX: Open circuit breaker immediately on quota error
|
|
589
|
+
# This prevents subsequent parallel calls from attempting API requests
|
|
590
|
+
try:
|
|
591
|
+
from .circuit_breaker import get_context7_circuit_breaker, CircuitState
|
|
592
|
+
circuit_breaker = get_context7_circuit_breaker()
|
|
593
|
+
# Force open circuit breaker immediately (bypass threshold)
|
|
594
|
+
if hasattr(circuit_breaker, '_stats'):
|
|
595
|
+
circuit_breaker._stats.state = CircuitState.OPEN
|
|
596
|
+
circuit_breaker._stats.last_failure_time = time.time()
|
|
597
|
+
circuit_breaker._stats.last_state_change = time.time()
|
|
598
|
+
logger.warning(
|
|
599
|
+
f"Context7 circuit breaker opened immediately due to quota error (429). "
|
|
600
|
+
f"Subsequent requests will be rejected without API calls."
|
|
601
|
+
)
|
|
602
|
+
except Exception as cb_error:
|
|
603
|
+
logger.debug(f"Could not open circuit breaker on quota error: {cb_error}")
|
|
604
|
+
|
|
605
|
+
return {
|
|
606
|
+
"success": False,
|
|
607
|
+
"error": f"Context7 API quota exceeded: {quota_message}",
|
|
608
|
+
"result": {},
|
|
609
|
+
}
|
|
610
|
+
else:
|
|
611
|
+
return {
|
|
612
|
+
"success": False,
|
|
613
|
+
"error": f"API returned status {response.status_code}",
|
|
614
|
+
"result": {},
|
|
615
|
+
}
|
|
616
|
+
except Exception as e:
|
|
617
|
+
return {
|
|
618
|
+
"success": False,
|
|
619
|
+
"error": str(e),
|
|
620
|
+
"result": {},
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
return resolve_library_client, get_docs_client
|
|
624
|
+
|
|
625
|
+
|
|
626
|
+
def get_context7_client_with_fallback(
|
|
627
|
+
mcp_gateway: MCPGateway | None = None,
|
|
628
|
+
) -> tuple[MCPGateway | None, bool, str, Callable[[str], dict[str, Any]] | None, Callable[[str, str | None, str | None, int | None], dict[str, Any]] | None]:
|
|
629
|
+
"""
|
|
630
|
+
Get Context7 client with automatic fallback.
|
|
631
|
+
|
|
632
|
+
R1: Improved to distinguish between Cursor MCP tools and local gateway.
|
|
633
|
+
|
|
634
|
+
Pattern:
|
|
635
|
+
1. Prefer MCP Gateway (Cursor's MCP server) - no API key needed
|
|
636
|
+
2. Fallback to direct HTTP calls if MCP not available - requires CONTEXT7_API_KEY
|
|
637
|
+
|
|
638
|
+
Args:
|
|
639
|
+
mcp_gateway: Optional MCPGateway instance (creates new if None)
|
|
640
|
+
|
|
641
|
+
Returns:
|
|
642
|
+
Tuple of (gateway, use_mcp, mcp_source, resolve_client, get_docs_client):
|
|
643
|
+
- gateway: MCPGateway instance (may be None in Cursor mode)
|
|
644
|
+
- use_mcp: True if MCP tools are available, False if using fallback
|
|
645
|
+
- mcp_source: "cursor_mcp", "local_gateway", or "none"
|
|
646
|
+
- resolve_client: HTTP client function (only if use_mcp=False)
|
|
647
|
+
- get_docs_client: HTTP client function (only if use_mcp=False)
|
|
648
|
+
"""
|
|
649
|
+
if mcp_gateway is None:
|
|
650
|
+
try:
|
|
651
|
+
mcp_gateway = MCPGateway()
|
|
652
|
+
except Exception:
|
|
653
|
+
mcp_gateway = None
|
|
654
|
+
|
|
655
|
+
# Check if MCP tools are available (R1: improved detection)
|
|
656
|
+
mcp_available, mcp_source = check_mcp_tools_available(mcp_gateway)
|
|
657
|
+
|
|
658
|
+
# CRITICAL FIX: Python code cannot call Cursor's MCP tools directly
|
|
659
|
+
# Even though MCP tools are "available" in Cursor mode, Python code must use HTTP fallback
|
|
660
|
+
# Only the AI assistant (via Cursor chat) can use MCP tools directly
|
|
661
|
+
from ..core.runtime_mode import is_cursor_mode
|
|
662
|
+
# #region agent log
|
|
663
|
+
from ..core.debug_logger import write_debug_log
|
|
664
|
+
write_debug_log(
|
|
665
|
+
{
|
|
666
|
+
"sessionId": "debug-session",
|
|
667
|
+
"runId": "run2",
|
|
668
|
+
"hypothesisId": "F",
|
|
669
|
+
"message": "Before MCP fix check",
|
|
670
|
+
"data": {"mcp_available": mcp_available, "mcp_source": mcp_source, "is_cursor_mode": is_cursor_mode()},
|
|
671
|
+
},
|
|
672
|
+
location="backup_client.py:get_context7_client_with_fallback:before_fix",
|
|
673
|
+
)
|
|
674
|
+
# #endregion
|
|
675
|
+
if mcp_available and mcp_source == "cursor_mcp":
|
|
676
|
+
# In Cursor mode, MCP tools are available to AI assistant but NOT to Python code
|
|
677
|
+
# Force HTTP fallback for Python code execution
|
|
678
|
+
logger.debug(
|
|
679
|
+
"Context7 MCP tools available in Cursor but Python code cannot call them. "
|
|
680
|
+
"Using HTTP fallback with API key."
|
|
681
|
+
)
|
|
682
|
+
# #region agent log
|
|
683
|
+
write_debug_log(
|
|
684
|
+
{
|
|
685
|
+
"sessionId": "debug-session",
|
|
686
|
+
"runId": "run2",
|
|
687
|
+
"hypothesisId": "F",
|
|
688
|
+
"message": "FIX APPLIED: Forcing HTTP fallback for Cursor MCP",
|
|
689
|
+
"data": {"mcp_available_before": True, "mcp_available_after": False},
|
|
690
|
+
},
|
|
691
|
+
location="backup_client.py:get_context7_client_with_fallback:fix_applied",
|
|
692
|
+
)
|
|
693
|
+
# #endregion
|
|
694
|
+
mcp_available = False # Force HTTP fallback
|
|
695
|
+
|
|
696
|
+
if mcp_available:
|
|
697
|
+
# Use MCP - no API key needed!
|
|
698
|
+
# This path is only for local gateway (not Cursor MCP)
|
|
699
|
+
return mcp_gateway, True, mcp_source, None, None
|
|
700
|
+
|
|
701
|
+
# Fallback to direct HTTP - requires API key
|
|
702
|
+
api_available = check_context7_api_available()
|
|
703
|
+
# #region agent log
|
|
704
|
+
write_debug_log(
|
|
705
|
+
{
|
|
706
|
+
"sessionId": "debug-session",
|
|
707
|
+
"runId": "run2",
|
|
708
|
+
"hypothesisId": "F",
|
|
709
|
+
"message": "Using HTTP fallback path",
|
|
710
|
+
"data": {"mcp_available": mcp_available, "api_available": api_available},
|
|
711
|
+
},
|
|
712
|
+
location="backup_client.py:get_context7_client_with_fallback:http_fallback",
|
|
713
|
+
)
|
|
714
|
+
# #endregion
|
|
715
|
+
if not api_available:
|
|
716
|
+
# R2/R3: Neither MCP nor API key available - provide clear error message
|
|
717
|
+
from ..core.runtime_mode import is_cursor_mode
|
|
718
|
+
if is_cursor_mode():
|
|
719
|
+
# In Cursor mode, MCP tools should be available but Python can't call them directly
|
|
720
|
+
logger.debug(
|
|
721
|
+
"Context7 MCP tools available in Cursor but cannot be called from Python. "
|
|
722
|
+
"AI assistant should use MCP tools directly."
|
|
723
|
+
)
|
|
724
|
+
else:
|
|
725
|
+
# R3: Clear error message for headless mode (only warn once)
|
|
726
|
+
global _CONTEXT7_NOT_AVAILABLE_WARNED
|
|
727
|
+
if not _CONTEXT7_NOT_AVAILABLE_WARNED:
|
|
728
|
+
logger.warning(
|
|
729
|
+
"Context7 not available: MCP tools not found and CONTEXT7_API_KEY not set. "
|
|
730
|
+
"To enable Context7:\n"
|
|
731
|
+
" 1. Set CONTEXT7_API_KEY environment variable, OR\n"
|
|
732
|
+
" 2. Configure Context7 MCP server\n"
|
|
733
|
+
"Continuing without Context7 functionality."
|
|
734
|
+
)
|
|
735
|
+
_CONTEXT7_NOT_AVAILABLE_WARNED = True
|
|
736
|
+
return mcp_gateway, False, "none", None, None
|
|
737
|
+
|
|
738
|
+
resolve_client, get_docs_client = create_fallback_http_client()
|
|
739
|
+
# #region agent log
|
|
740
|
+
write_debug_log(
|
|
741
|
+
{
|
|
742
|
+
"sessionId": "debug-session",
|
|
743
|
+
"runId": "run2",
|
|
744
|
+
"hypothesisId": "F",
|
|
745
|
+
"message": "Returning HTTP fallback clients",
|
|
746
|
+
"data": {"use_mcp": False, "resolve_client_created": resolve_client is not None, "get_docs_client_created": get_docs_client is not None},
|
|
747
|
+
},
|
|
748
|
+
location="backup_client.py:get_context7_client_with_fallback:return_http",
|
|
749
|
+
)
|
|
750
|
+
# #endregion
|
|
751
|
+
return mcp_gateway, False, "none", resolve_client, get_docs_client
|
|
752
|
+
|
|
753
|
+
|
|
754
|
+
async def call_context7_resolve_with_fallback(
|
|
755
|
+
library_name: str,
|
|
756
|
+
mcp_gateway: MCPGateway | None = None,
|
|
757
|
+
) -> dict[str, Any]:
|
|
758
|
+
"""
|
|
759
|
+
Call Context7 resolve-library-id with automatic fallback.
|
|
760
|
+
|
|
761
|
+
R1: Improved to handle Cursor MCP tools vs local gateway distinction.
|
|
762
|
+
|
|
763
|
+
Args:
|
|
764
|
+
library_name: Library name to resolve
|
|
765
|
+
mcp_gateway: Optional MCPGateway instance
|
|
766
|
+
|
|
767
|
+
Returns:
|
|
768
|
+
Dictionary with resolve result (matches MCP tool response format)
|
|
769
|
+
"""
|
|
770
|
+
gateway, use_mcp, mcp_source, resolve_client, _ = get_context7_client_with_fallback(mcp_gateway)
|
|
771
|
+
# #region agent log
|
|
772
|
+
from ..core.debug_logger import write_debug_log
|
|
773
|
+
write_debug_log(
|
|
774
|
+
{
|
|
775
|
+
"sessionId": "debug-session",
|
|
776
|
+
"runId": "run1",
|
|
777
|
+
"hypothesisId": "D",
|
|
778
|
+
"message": "call_context7_resolve_with_fallback got clients",
|
|
779
|
+
"data": {"use_mcp": use_mcp, "resolve_client": resolve_client is not None, "library": library_name},
|
|
780
|
+
},
|
|
781
|
+
location="backup_client.py:call_context7_resolve_with_fallback",
|
|
782
|
+
)
|
|
783
|
+
# #endregion
|
|
784
|
+
if use_mcp:
|
|
785
|
+
# R1: Handle Cursor MCP vs local gateway differently
|
|
786
|
+
if mcp_source == "cursor_mcp":
|
|
787
|
+
# In Cursor mode, Python code cannot directly call MCP tools
|
|
788
|
+
# AI assistant should use MCP tools: mcp_Context7_resolve-library-id
|
|
789
|
+
if resolve_client:
|
|
790
|
+
# Allow HTTP fallback for CLI commands in Cursor mode
|
|
791
|
+
logger.debug(
|
|
792
|
+
f"Context7 MCP tools available in Cursor for library '{library_name}', "
|
|
793
|
+
f"but using HTTP fallback for Python code path. "
|
|
794
|
+
f"AI assistant should use MCP tools directly for better performance."
|
|
795
|
+
)
|
|
796
|
+
return resolve_client(library_name)
|
|
797
|
+
else:
|
|
798
|
+
# No HTTP fallback - indicate MCP tools should be used
|
|
799
|
+
logger.info(
|
|
800
|
+
f"Context7 MCP tools available in Cursor but cannot be called from Python. "
|
|
801
|
+
f"AI assistant should use MCP tools: mcp_Context7_resolve-library-id with libraryName='{library_name}'."
|
|
802
|
+
)
|
|
803
|
+
return {
|
|
804
|
+
"success": False,
|
|
805
|
+
"error": "Context7 MCP tools available in Cursor but require AI assistant to call them. "
|
|
806
|
+
"Use MCP tools directly: mcp_Context7_resolve-library-id",
|
|
807
|
+
"result": {"matches": []},
|
|
808
|
+
}
|
|
809
|
+
elif mcp_source == "local_gateway" and gateway:
|
|
810
|
+
# Local gateway has tools registered - try to call them
|
|
811
|
+
try:
|
|
812
|
+
result = await gateway.call_tool(
|
|
813
|
+
"mcp_Context7_resolve-library-id",
|
|
814
|
+
libraryName=library_name,
|
|
815
|
+
)
|
|
816
|
+
return result
|
|
817
|
+
except (ValueError, KeyError) as e:
|
|
818
|
+
# Tool not found - fall back to HTTP if available
|
|
819
|
+
logger.debug(
|
|
820
|
+
f"Context7 MCP tool not found in local gateway for library '{library_name}': {e}. "
|
|
821
|
+
f"Falling back to HTTP client if available."
|
|
822
|
+
)
|
|
823
|
+
if resolve_client:
|
|
824
|
+
return resolve_client(library_name)
|
|
825
|
+
return {
|
|
826
|
+
"success": False,
|
|
827
|
+
"error": f"Context7 MCP tool not found: {e}",
|
|
828
|
+
"result": {"matches": []},
|
|
829
|
+
}
|
|
830
|
+
except Exception as e:
|
|
831
|
+
# Other errors - try fallback
|
|
832
|
+
logger.debug(
|
|
833
|
+
f"Context7 MCP Gateway call failed for library '{library_name}': {e}. Trying fallback.",
|
|
834
|
+
exc_info=True
|
|
835
|
+
)
|
|
836
|
+
if resolve_client:
|
|
837
|
+
return resolve_client(library_name)
|
|
838
|
+
return {
|
|
839
|
+
"success": False,
|
|
840
|
+
"error": f"MCP Gateway call failed: {e}",
|
|
841
|
+
"result": {"matches": []},
|
|
842
|
+
}
|
|
843
|
+
elif resolve_client:
|
|
844
|
+
# Fallback: Use direct HTTP client
|
|
845
|
+
return resolve_client(library_name)
|
|
846
|
+
else:
|
|
847
|
+
logger.info(
|
|
848
|
+
f"Context7 not available for library '{library_name}': MCP tools not found and CONTEXT7_API_KEY not set. "
|
|
849
|
+
f"Continuing without Context7 documentation."
|
|
850
|
+
)
|
|
851
|
+
return {
|
|
852
|
+
"success": False,
|
|
853
|
+
"error": "Context7 not available: MCP tools not found and CONTEXT7_API_KEY not set",
|
|
854
|
+
"result": {"matches": []},
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
|
|
858
|
+
async def call_context7_get_docs_with_fallback(
|
|
859
|
+
context7_id: str,
|
|
860
|
+
topic: str | None = None,
|
|
861
|
+
mode: str = "code",
|
|
862
|
+
page: int = 1,
|
|
863
|
+
mcp_gateway: MCPGateway | None = None,
|
|
864
|
+
) -> dict[str, Any]:
|
|
865
|
+
"""
|
|
866
|
+
Call Context7 get-library-docs with automatic fallback.
|
|
867
|
+
|
|
868
|
+
R1: Improved to handle Cursor MCP tools vs local gateway distinction.
|
|
869
|
+
|
|
870
|
+
Args:
|
|
871
|
+
context7_id: Context7-compatible library ID
|
|
872
|
+
topic: Optional topic name
|
|
873
|
+
mode: "code" or "info" (default: "code")
|
|
874
|
+
page: Page number (default: 1)
|
|
875
|
+
mcp_gateway: Optional MCPGateway instance
|
|
876
|
+
|
|
877
|
+
Returns:
|
|
878
|
+
Dictionary with docs result (matches MCP tool response format)
|
|
879
|
+
"""
|
|
880
|
+
gateway, use_mcp, mcp_source, _, get_docs_client = get_context7_client_with_fallback(mcp_gateway)
|
|
881
|
+
|
|
882
|
+
if use_mcp:
|
|
883
|
+
# R1: Handle Cursor MCP vs local gateway differently
|
|
884
|
+
if mcp_source == "cursor_mcp":
|
|
885
|
+
# In Cursor mode, Python code cannot directly call MCP tools
|
|
886
|
+
# AI assistant should use MCP tools: mcp_Context7_get-library-docs
|
|
887
|
+
if get_docs_client:
|
|
888
|
+
# Allow HTTP fallback for CLI commands in Cursor mode
|
|
889
|
+
logger.debug(
|
|
890
|
+
f"Context7 MCP tools available in Cursor for library '{context7_id}' (topic: {topic}), "
|
|
891
|
+
f"but using HTTP fallback for Python code path. "
|
|
892
|
+
f"AI assistant should use MCP tools directly for better performance."
|
|
893
|
+
)
|
|
894
|
+
return get_docs_client(context7_id, topic, mode, page)
|
|
895
|
+
else:
|
|
896
|
+
# No HTTP fallback - indicate MCP tools should be used
|
|
897
|
+
logger.info(
|
|
898
|
+
f"Context7 MCP tools available in Cursor but cannot be called from Python. "
|
|
899
|
+
f"AI assistant should use MCP tools: mcp_Context7_get-library-docs with "
|
|
900
|
+
f"context7CompatibleLibraryID='{context7_id}', topic='{topic}', mode='{mode}', page={page}."
|
|
901
|
+
)
|
|
902
|
+
return {
|
|
903
|
+
"success": False,
|
|
904
|
+
"error": "Context7 MCP tools available in Cursor but require AI assistant to call them. "
|
|
905
|
+
"Use MCP tools directly: mcp_Context7_get-library-docs",
|
|
906
|
+
"result": {},
|
|
907
|
+
}
|
|
908
|
+
elif mcp_source == "local_gateway" and gateway:
|
|
909
|
+
# Local gateway has tools registered - try to call them
|
|
910
|
+
try:
|
|
911
|
+
result = await gateway.call_tool(
|
|
912
|
+
"mcp_Context7_get-library-docs",
|
|
913
|
+
context7CompatibleLibraryID=context7_id,
|
|
914
|
+
topic=topic,
|
|
915
|
+
mode=mode,
|
|
916
|
+
page=page,
|
|
917
|
+
)
|
|
918
|
+
return result
|
|
919
|
+
except (ValueError, KeyError) as e:
|
|
920
|
+
# Tool not found - fall back to HTTP if available
|
|
921
|
+
logger.debug(
|
|
922
|
+
f"Context7 MCP tool not found in local gateway for library '{context7_id}' (topic: {topic}): {e}. "
|
|
923
|
+
f"Falling back to HTTP client if available."
|
|
924
|
+
)
|
|
925
|
+
if get_docs_client:
|
|
926
|
+
return get_docs_client(context7_id, topic, mode, page)
|
|
927
|
+
return {
|
|
928
|
+
"success": False,
|
|
929
|
+
"error": f"Context7 MCP tool not found: {e}",
|
|
930
|
+
"result": {},
|
|
931
|
+
}
|
|
932
|
+
except Exception as e:
|
|
933
|
+
# Other errors - try fallback
|
|
934
|
+
logger.debug(
|
|
935
|
+
f"Context7 MCP Gateway call failed for library '{context7_id}' (topic: {topic}): {e}. Trying fallback.",
|
|
936
|
+
exc_info=True
|
|
937
|
+
)
|
|
938
|
+
if get_docs_client:
|
|
939
|
+
return get_docs_client(context7_id, topic, mode, page)
|
|
940
|
+
return {
|
|
941
|
+
"success": False,
|
|
942
|
+
"error": f"MCP Gateway call failed: {e}",
|
|
943
|
+
"result": {},
|
|
944
|
+
}
|
|
945
|
+
elif get_docs_client:
|
|
946
|
+
# Fallback: Use direct HTTP client
|
|
947
|
+
return get_docs_client(context7_id, topic, mode, page)
|
|
948
|
+
else:
|
|
949
|
+
logger.info(
|
|
950
|
+
f"Context7 not available for library '{context7_id}' (topic: {topic}): "
|
|
951
|
+
f"MCP tools not found and CONTEXT7_API_KEY not set. Continuing without Context7 documentation."
|
|
952
|
+
)
|
|
953
|
+
return {
|
|
954
|
+
"success": False,
|
|
955
|
+
"error": "Context7 not available: MCP tools not found and CONTEXT7_API_KEY not set",
|
|
956
|
+
"result": {},
|
|
957
|
+
}
|
|
958
|
+
|