devsper 2.1.6__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.
- devsper/__init__.py +14 -0
- devsper/agents/a2a/__init__.py +27 -0
- devsper/agents/a2a/client.py +126 -0
- devsper/agents/a2a/discovery.py +24 -0
- devsper/agents/a2a/server.py +128 -0
- devsper/agents/a2a/tool_adapter.py +68 -0
- devsper/agents/a2a/types.py +49 -0
- devsper/agents/agent.py +602 -0
- devsper/agents/critic.py +80 -0
- devsper/agents/message_bus.py +124 -0
- devsper/agents/roles.py +181 -0
- devsper/agents/run_agent.py +78 -0
- devsper/analytics/__init__.py +5 -0
- devsper/analytics/tool_analytics.py +78 -0
- devsper/audit/__init__.py +5 -0
- devsper/audit/logger.py +214 -0
- devsper/bus/__init__.py +29 -0
- devsper/bus/backends/__init__.py +5 -0
- devsper/bus/backends/base.py +38 -0
- devsper/bus/backends/memory.py +55 -0
- devsper/bus/backends/redis.py +146 -0
- devsper/bus/message.py +56 -0
- devsper/bus/schema_version.py +3 -0
- devsper/bus/topics.py +19 -0
- devsper/cache/__init__.py +6 -0
- devsper/cache/embedding_index.py +98 -0
- devsper/cache/hashing.py +24 -0
- devsper/cache/store.py +153 -0
- devsper/cache/task_cache.py +191 -0
- devsper/cli/__init__.py +6 -0
- devsper/cli/commands/reg.py +733 -0
- devsper/cli/github_oauth.py +157 -0
- devsper/cli/init.py +637 -0
- devsper/cli/main.py +2956 -0
- devsper/cli/run_progress.py +103 -0
- devsper/cli/ui/__init__.py +65 -0
- devsper/cli/ui/components.py +94 -0
- devsper/cli/ui/errors.py +104 -0
- devsper/cli/ui/logging.py +120 -0
- devsper/cli/ui/onboarding.py +102 -0
- devsper/cli/ui/progress.py +43 -0
- devsper/cli/ui/run_view.py +308 -0
- devsper/cli/ui/theme.py +40 -0
- devsper/cluster/__init__.py +29 -0
- devsper/cluster/election.py +84 -0
- devsper/cluster/local.py +97 -0
- devsper/cluster/node_info.py +77 -0
- devsper/cluster/registry.py +71 -0
- devsper/cluster/router.py +117 -0
- devsper/cluster/state_backend.py +105 -0
- devsper/compliance/__init__.py +5 -0
- devsper/compliance/pii.py +147 -0
- devsper/config/__init__.py +52 -0
- devsper/config/config_loader.py +121 -0
- devsper/config/defaults.py +77 -0
- devsper/config/resolver.py +342 -0
- devsper/config/schema.py +237 -0
- devsper/credentials/__init__.py +19 -0
- devsper/credentials/cli.py +197 -0
- devsper/credentials/migration.py +124 -0
- devsper/credentials/store.py +142 -0
- devsper/dashboard/__init__.py +9 -0
- devsper/dashboard/dashboard.py +87 -0
- devsper/dev/__init__.py +25 -0
- devsper/dev/builder.py +195 -0
- devsper/dev/debugger.py +95 -0
- devsper/dev/repo_index.py +138 -0
- devsper/dev/sandbox.py +203 -0
- devsper/dev/scaffold.py +122 -0
- devsper/embeddings/__init__.py +5 -0
- devsper/embeddings/service.py +36 -0
- devsper/explainability/__init__.py +14 -0
- devsper/explainability/decision_tree.py +104 -0
- devsper/explainability/rationale.py +38 -0
- devsper/explainability/simulation.py +56 -0
- devsper/hitl/__init__.py +13 -0
- devsper/hitl/approval.py +160 -0
- devsper/hitl/escalation.py +95 -0
- devsper/intelligence/__init__.py +9 -0
- devsper/intelligence/adaptation.py +88 -0
- devsper/intelligence/analysis/__init__.py +19 -0
- devsper/intelligence/analysis/analyzer.py +71 -0
- devsper/intelligence/analysis/cost_estimator.py +66 -0
- devsper/intelligence/analysis/formatter.py +103 -0
- devsper/intelligence/analysis/run_report.py +402 -0
- devsper/intelligence/learning_engine.py +92 -0
- devsper/intelligence/strategies/__init__.py +23 -0
- devsper/intelligence/strategies/base.py +14 -0
- devsper/intelligence/strategies/code_analysis_strategy.py +33 -0
- devsper/intelligence/strategies/data_science_strategy.py +33 -0
- devsper/intelligence/strategies/document_pipeline_strategy.py +33 -0
- devsper/intelligence/strategies/experiment_strategy.py +33 -0
- devsper/intelligence/strategies/research_strategy.py +34 -0
- devsper/intelligence/strategy_selector.py +84 -0
- devsper/intelligence/synthesis.py +132 -0
- devsper/intelligence/task_optimizer.py +92 -0
- devsper/knowledge/__init__.py +5 -0
- devsper/knowledge/extractor.py +204 -0
- devsper/knowledge/knowledge_graph.py +184 -0
- devsper/knowledge/query.py +285 -0
- devsper/memory/__init__.py +35 -0
- devsper/memory/consolidation.py +138 -0
- devsper/memory/embeddings.py +60 -0
- devsper/memory/memory_index.py +97 -0
- devsper/memory/memory_router.py +62 -0
- devsper/memory/memory_store.py +221 -0
- devsper/memory/memory_types.py +54 -0
- devsper/memory/namespaces.py +45 -0
- devsper/memory/scoring.py +77 -0
- devsper/memory/summarizer.py +52 -0
- devsper/nodes/__init__.py +5 -0
- devsper/nodes/controller.py +449 -0
- devsper/nodes/rpc.py +127 -0
- devsper/nodes/single.py +161 -0
- devsper/nodes/worker.py +506 -0
- devsper/orchestration/__init__.py +19 -0
- devsper/orchestration/meta_planner.py +239 -0
- devsper/orchestration/priority_queue.py +61 -0
- devsper/plugins/__init__.py +19 -0
- devsper/plugins/marketplace/__init__.py +0 -0
- devsper/plugins/plugin_loader.py +70 -0
- devsper/plugins/plugin_registry.py +34 -0
- devsper/plugins/registry.py +83 -0
- devsper/protocols/__init__.py +6 -0
- devsper/providers/__init__.py +17 -0
- devsper/providers/anthropic.py +84 -0
- devsper/providers/base.py +75 -0
- devsper/providers/complexity_router.py +94 -0
- devsper/providers/gemini.py +36 -0
- devsper/providers/github.py +180 -0
- devsper/providers/model_router.py +40 -0
- devsper/providers/openai.py +105 -0
- devsper/providers/router/__init__.py +21 -0
- devsper/providers/router/backends/__init__.py +19 -0
- devsper/providers/router/backends/anthropic_backend.py +111 -0
- devsper/providers/router/backends/custom_backend.py +138 -0
- devsper/providers/router/backends/gemini_backend.py +89 -0
- devsper/providers/router/backends/github_backend.py +165 -0
- devsper/providers/router/backends/ollama_backend.py +104 -0
- devsper/providers/router/backends/openai_backend.py +142 -0
- devsper/providers/router/backends/vllm_backend.py +35 -0
- devsper/providers/router/base.py +60 -0
- devsper/providers/router/factory.py +92 -0
- devsper/providers/router/legacy.py +101 -0
- devsper/providers/router/router.py +135 -0
- devsper/reasoning/__init__.py +12 -0
- devsper/reasoning/graph.py +59 -0
- devsper/reasoning/nodes.py +20 -0
- devsper/reasoning/store.py +67 -0
- devsper/runtime/__init__.py +12 -0
- devsper/runtime/health.py +88 -0
- devsper/runtime/replay.py +53 -0
- devsper/runtime/replay_engine.py +142 -0
- devsper/runtime/run_history.py +204 -0
- devsper/runtime/telemetry.py +116 -0
- devsper/runtime/visualize.py +58 -0
- devsper/sandbox/__init__.py +13 -0
- devsper/sandbox/sandbox.py +161 -0
- devsper/swarm/checkpointer.py +65 -0
- devsper/swarm/executor.py +558 -0
- devsper/swarm/map_reduce.py +44 -0
- devsper/swarm/planner.py +197 -0
- devsper/swarm/prefetcher.py +91 -0
- devsper/swarm/scheduler.py +153 -0
- devsper/swarm/speculation.py +47 -0
- devsper/swarm/swarm.py +562 -0
- devsper/tools/__init__.py +33 -0
- devsper/tools/base.py +29 -0
- devsper/tools/code_intelligence/__init__.py +13 -0
- devsper/tools/code_intelligence/api_surface_extractor.py +73 -0
- devsper/tools/code_intelligence/architecture_analyzer.py +65 -0
- devsper/tools/code_intelligence/codebase_indexer.py +71 -0
- devsper/tools/code_intelligence/dependency_graph_builder.py +67 -0
- devsper/tools/code_intelligence/design_pattern_detector.py +62 -0
- devsper/tools/code_intelligence/large_function_detector.py +68 -0
- devsper/tools/code_intelligence/module_responsibility_mapper.py +56 -0
- devsper/tools/code_intelligence/parallel_codebase_analysis.py +44 -0
- devsper/tools/code_intelligence/refactor_candidate_detector.py +81 -0
- devsper/tools/code_intelligence/repository_semantic_index.py +61 -0
- devsper/tools/code_intelligence/test_coverage_estimator.py +62 -0
- devsper/tools/coding/__init__.py +12 -0
- devsper/tools/coding/analyze_code_complexity.py +48 -0
- devsper/tools/coding/dependency_analyzer.py +42 -0
- devsper/tools/coding/extract_functions.py +38 -0
- devsper/tools/coding/format_python.py +50 -0
- devsper/tools/coding/generate_docstrings.py +40 -0
- devsper/tools/coding/generate_unit_tests.py +42 -0
- devsper/tools/coding/lint_python.py +51 -0
- devsper/tools/coding/refactor_function.py +41 -0
- devsper/tools/coding/repo_structure_map.py +54 -0
- devsper/tools/coding/run_python.py +53 -0
- devsper/tools/data/__init__.py +12 -0
- devsper/tools/data/column_type_detection.py +64 -0
- devsper/tools/data/csv_summary.py +52 -0
- devsper/tools/data/dataframe_filter.py +51 -0
- devsper/tools/data/dataframe_groupby.py +47 -0
- devsper/tools/data/dataframe_stats.py +38 -0
- devsper/tools/data/dataset_sampling.py +55 -0
- devsper/tools/data/dataset_schema.py +45 -0
- devsper/tools/data/json_pretty_print.py +37 -0
- devsper/tools/data/json_query.py +46 -0
- devsper/tools/data/missing_value_report.py +47 -0
- devsper/tools/data_science/__init__.py +13 -0
- devsper/tools/data_science/correlation_heatmap.py +72 -0
- devsper/tools/data_science/dataset_bias_detector.py +49 -0
- devsper/tools/data_science/dataset_distribution_report.py +64 -0
- devsper/tools/data_science/dataset_drift_detector.py +64 -0
- devsper/tools/data_science/dataset_outlier_detector.py +65 -0
- devsper/tools/data_science/dataset_profile.py +76 -0
- devsper/tools/data_science/distributed_dataset_processor.py +54 -0
- devsper/tools/data_science/feature_engineering_suggestions.py +69 -0
- devsper/tools/data_science/feature_importance_estimator.py +82 -0
- devsper/tools/data_science/model_input_validator.py +59 -0
- devsper/tools/data_science/time_series_analyzer.py +57 -0
- devsper/tools/documents/__init__.py +11 -0
- devsper/tools/documents/_docproc.py +56 -0
- devsper/tools/documents/document_to_markdown.py +29 -0
- devsper/tools/documents/extract_document_images.py +39 -0
- devsper/tools/documents/extract_document_text.py +29 -0
- devsper/tools/documents/extract_equations.py +36 -0
- devsper/tools/documents/extract_tables.py +47 -0
- devsper/tools/documents/summarize_document.py +42 -0
- devsper/tools/documents/write_latex_document.py +133 -0
- devsper/tools/documents/write_markdown_document.py +89 -0
- devsper/tools/documents/write_word_document.py +149 -0
- devsper/tools/experiments/__init__.py +13 -0
- devsper/tools/experiments/bootstrap_estimator.py +54 -0
- devsper/tools/experiments/experiment_report_generator.py +50 -0
- devsper/tools/experiments/experiment_tracker.py +36 -0
- devsper/tools/experiments/grid_search_runner.py +50 -0
- devsper/tools/experiments/model_benchmark_runner.py +45 -0
- devsper/tools/experiments/monte_carlo_experiment.py +38 -0
- devsper/tools/experiments/parameter_sweep_runner.py +51 -0
- devsper/tools/experiments/result_comparator.py +58 -0
- devsper/tools/experiments/simulation_runner.py +43 -0
- devsper/tools/experiments/statistical_significance_test.py +56 -0
- devsper/tools/experiments/swarm_map_reduce.py +42 -0
- devsper/tools/filesystem/__init__.py +12 -0
- devsper/tools/filesystem/append_file.py +42 -0
- devsper/tools/filesystem/file_hash.py +40 -0
- devsper/tools/filesystem/file_line_count.py +36 -0
- devsper/tools/filesystem/file_metadata.py +38 -0
- devsper/tools/filesystem/file_preview.py +55 -0
- devsper/tools/filesystem/find_large_files.py +50 -0
- devsper/tools/filesystem/list_directory.py +39 -0
- devsper/tools/filesystem/read_file.py +35 -0
- devsper/tools/filesystem/search_files.py +60 -0
- devsper/tools/filesystem/write_file.py +41 -0
- devsper/tools/flagship/__init__.py +15 -0
- devsper/tools/flagship/distributed_document_analysis.py +77 -0
- devsper/tools/flagship/docproc_corpus_pipeline.py +91 -0
- devsper/tools/flagship/repository_semantic_map.py +99 -0
- devsper/tools/flagship/research_graph_builder.py +111 -0
- devsper/tools/flagship/swarm_experiment_runner.py +86 -0
- devsper/tools/knowledge/__init__.py +10 -0
- devsper/tools/knowledge/citation_graph_builder.py +69 -0
- devsper/tools/knowledge/concept_frequency_analyzer.py +74 -0
- devsper/tools/knowledge/corpus_builder.py +66 -0
- devsper/tools/knowledge/cross_document_entity_linker.py +71 -0
- devsper/tools/knowledge/document_corpus_summary.py +68 -0
- devsper/tools/knowledge/document_topic_extractor.py +58 -0
- devsper/tools/knowledge/knowledge_graph_extractor.py +58 -0
- devsper/tools/knowledge/timeline_extractor.py +59 -0
- devsper/tools/math/__init__.py +12 -0
- devsper/tools/math/calculate_expression.py +52 -0
- devsper/tools/math/correlation.py +44 -0
- devsper/tools/math/distribution_summary.py +39 -0
- devsper/tools/math/histogram.py +53 -0
- devsper/tools/math/linear_regression.py +47 -0
- devsper/tools/math/matrix_multiply.py +38 -0
- devsper/tools/math/mean_std.py +35 -0
- devsper/tools/math/monte_carlo_simulation.py +43 -0
- devsper/tools/math/polynomial_fit.py +40 -0
- devsper/tools/math/random_sample.py +36 -0
- devsper/tools/mcp/__init__.py +23 -0
- devsper/tools/mcp/adapter.py +53 -0
- devsper/tools/mcp/client.py +235 -0
- devsper/tools/mcp/discovery.py +53 -0
- devsper/tools/memory/__init__.py +16 -0
- devsper/tools/memory/delete_memory.py +25 -0
- devsper/tools/memory/list_memory.py +34 -0
- devsper/tools/memory/search_memory.py +36 -0
- devsper/tools/memory/store_memory.py +47 -0
- devsper/tools/memory/summarize_memory.py +41 -0
- devsper/tools/memory/tag_memory.py +47 -0
- devsper/tools/pipelines.py +92 -0
- devsper/tools/registry.py +39 -0
- devsper/tools/research/__init__.py +12 -0
- devsper/tools/research/arxiv_download.py +55 -0
- devsper/tools/research/arxiv_search.py +58 -0
- devsper/tools/research/citation_extractor.py +35 -0
- devsper/tools/research/duckduckgo_search.py +42 -0
- devsper/tools/research/paper_metadata_extractor.py +45 -0
- devsper/tools/research/paper_summarizer.py +41 -0
- devsper/tools/research/research_question_generator.py +39 -0
- devsper/tools/research/topic_cluster.py +46 -0
- devsper/tools/research/web_search.py +47 -0
- devsper/tools/research/wikipedia_lookup.py +50 -0
- devsper/tools/research_advanced/__init__.py +14 -0
- devsper/tools/research_advanced/citation_context_extractor.py +60 -0
- devsper/tools/research_advanced/literature_review_generator.py +79 -0
- devsper/tools/research_advanced/methodology_extractor.py +58 -0
- devsper/tools/research_advanced/paper_contribution_extractor.py +50 -0
- devsper/tools/research_advanced/paper_dataset_identifier.py +49 -0
- devsper/tools/research_advanced/paper_method_comparator.py +62 -0
- devsper/tools/research_advanced/paper_similarity_search.py +69 -0
- devsper/tools/research_advanced/paper_trend_analyzer.py +69 -0
- devsper/tools/research_advanced/parallel_document_analyzer.py +56 -0
- devsper/tools/research_advanced/research_gap_finder.py +71 -0
- devsper/tools/research_advanced/research_topic_mapper.py +69 -0
- devsper/tools/research_advanced/swarm_literature_review.py +58 -0
- devsper/tools/scoring/__init__.py +52 -0
- devsper/tools/scoring/report.py +44 -0
- devsper/tools/scoring/scorer.py +39 -0
- devsper/tools/scoring/selector.py +61 -0
- devsper/tools/scoring/store.py +267 -0
- devsper/tools/selector.py +130 -0
- devsper/tools/system/__init__.py +12 -0
- devsper/tools/system/cpu_usage.py +22 -0
- devsper/tools/system/disk_usage.py +35 -0
- devsper/tools/system/environment_variables.py +29 -0
- devsper/tools/system/memory_usage.py +23 -0
- devsper/tools/system/pip_install.py +44 -0
- devsper/tools/system/pip_search.py +29 -0
- devsper/tools/system/process_list.py +34 -0
- devsper/tools/system/python_package_list.py +40 -0
- devsper/tools/system/run_shell_command.py +51 -0
- devsper/tools/system/system_info.py +26 -0
- devsper/tools/tool_runner.py +122 -0
- devsper/tui/__init__.py +5 -0
- devsper/tui/activity_feed_view.py +73 -0
- devsper/tui/adaptive_tasks_view.py +75 -0
- devsper/tui/agent_role_view.py +35 -0
- devsper/tui/app.py +395 -0
- devsper/tui/dashboard_screen.py +290 -0
- devsper/tui/dev_view.py +99 -0
- devsper/tui/inject_screen.py +73 -0
- devsper/tui/knowledge_graph_view.py +46 -0
- devsper/tui/layout.py +43 -0
- devsper/tui/logs_view.py +83 -0
- devsper/tui/memory_view.py +58 -0
- devsper/tui/performance_view.py +33 -0
- devsper/tui/reasoning_graph_view.py +39 -0
- devsper/tui/results_view.py +139 -0
- devsper/tui/swarm_view.py +37 -0
- devsper/tui/task_detail_screen.py +55 -0
- devsper/tui/task_view.py +103 -0
- devsper/types/event.py +97 -0
- devsper/types/exceptions.py +21 -0
- devsper/types/swarm.py +41 -0
- devsper/types/task.py +80 -0
- devsper/upgrade/__init__.py +21 -0
- devsper/upgrade/changelog.py +124 -0
- devsper/upgrade/cli.py +145 -0
- devsper/upgrade/installer.py +103 -0
- devsper/upgrade/notifier.py +52 -0
- devsper/upgrade/version_check.py +121 -0
- devsper/utils/event_logger.py +88 -0
- devsper/utils/http.py +43 -0
- devsper/utils/models.py +54 -0
- devsper/visualization/__init__.py +5 -0
- devsper/visualization/dag_export.py +67 -0
- devsper/workflow/__init__.py +18 -0
- devsper/workflow/conditions.py +157 -0
- devsper/workflow/context.py +108 -0
- devsper/workflow/loader.py +156 -0
- devsper/workflow/resolver.py +109 -0
- devsper/workflow/runner.py +562 -0
- devsper/workflow/schema.py +63 -0
- devsper/workflow/validator.py +128 -0
- devsper-2.1.6.dist-info/METADATA +346 -0
- devsper-2.1.6.dist-info/RECORD +375 -0
- devsper-2.1.6.dist-info/WHEEL +4 -0
- devsper-2.1.6.dist-info/entry_points.txt +3 -0
- devsper-2.1.6.dist-info/licenses/LICENSE +639 -0
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"""Compute histogram (count per bin) for a list of numbers."""
|
|
2
|
+
|
|
3
|
+
from collections import defaultdict
|
|
4
|
+
|
|
5
|
+
from devsper.tools.base import Tool
|
|
6
|
+
from devsper.tools.registry import register
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class HistogramTool(Tool):
|
|
10
|
+
"""Compute histogram: bin edges and counts. Optional number of bins."""
|
|
11
|
+
|
|
12
|
+
name = "histogram"
|
|
13
|
+
description = "Compute histogram of values. Returns bin ranges and counts."
|
|
14
|
+
input_schema = {
|
|
15
|
+
"type": "object",
|
|
16
|
+
"properties": {
|
|
17
|
+
"values": {"type": "array", "description": "List of numbers"},
|
|
18
|
+
"bins": {"type": "integer", "description": "Number of bins (default 10)"},
|
|
19
|
+
},
|
|
20
|
+
"required": ["values"],
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
def run(self, **kwargs) -> str:
|
|
24
|
+
values = kwargs.get("values")
|
|
25
|
+
bins = kwargs.get("bins", 10)
|
|
26
|
+
if not isinstance(values, list):
|
|
27
|
+
return "Error: values must be an array"
|
|
28
|
+
if not isinstance(bins, int) or bins < 1:
|
|
29
|
+
bins = 10
|
|
30
|
+
try:
|
|
31
|
+
nums = [float(v) for v in values]
|
|
32
|
+
except (TypeError, ValueError):
|
|
33
|
+
return "Error: all elements must be numbers"
|
|
34
|
+
if not nums:
|
|
35
|
+
return "Error: empty list"
|
|
36
|
+
lo = min(nums)
|
|
37
|
+
hi = max(nums)
|
|
38
|
+
if hi == lo:
|
|
39
|
+
return f"All values equal: {lo}\ncount: {len(nums)}"
|
|
40
|
+
width = (hi - lo) / bins
|
|
41
|
+
counts = [0] * bins
|
|
42
|
+
for v in nums:
|
|
43
|
+
idx = min(int((v - lo) / width), bins - 1) if width > 0 else 0
|
|
44
|
+
counts[idx] += 1
|
|
45
|
+
lines = []
|
|
46
|
+
for i in range(bins):
|
|
47
|
+
left = lo + i * width
|
|
48
|
+
right = lo + (i + 1) * width
|
|
49
|
+
lines.append(f"[{left:.2f}, {right:.2f}): {counts[i]}")
|
|
50
|
+
return "\n".join(lines)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
register(HistogramTool())
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"""Simple linear regression: y = mx + b from lists of x and y."""
|
|
2
|
+
|
|
3
|
+
from devsper.tools.base import Tool
|
|
4
|
+
from devsper.tools.registry import register
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class LinearRegressionTool(Tool):
|
|
8
|
+
"""Fit y = mx + b to x and y lists. Returns slope and intercept."""
|
|
9
|
+
|
|
10
|
+
name = "linear_regression"
|
|
11
|
+
description = "Fit linear regression to x and y data. Returns slope m and intercept b."
|
|
12
|
+
input_schema = {
|
|
13
|
+
"type": "object",
|
|
14
|
+
"properties": {
|
|
15
|
+
"x": {"type": "array", "description": "List of x values (numbers)"},
|
|
16
|
+
"y": {"type": "array", "description": "List of y values (numbers)"},
|
|
17
|
+
},
|
|
18
|
+
"required": ["x", "y"],
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
def run(self, **kwargs) -> str:
|
|
22
|
+
x = kwargs.get("x")
|
|
23
|
+
y = kwargs.get("y")
|
|
24
|
+
if not isinstance(x, list) or not isinstance(y, list):
|
|
25
|
+
return "Error: x and y must be arrays"
|
|
26
|
+
if len(x) != len(y) or len(x) < 2:
|
|
27
|
+
return "Error: x and y must have same length >= 2"
|
|
28
|
+
try:
|
|
29
|
+
import numpy as np
|
|
30
|
+
X = np.array(x, dtype=float)
|
|
31
|
+
Y = np.array(y, dtype=float)
|
|
32
|
+
n = len(X)
|
|
33
|
+
sx = X.sum()
|
|
34
|
+
sy = Y.sum()
|
|
35
|
+
sxy = (X * Y).sum()
|
|
36
|
+
sxx = (X * X).sum()
|
|
37
|
+
denom = n * sxx - sx * sx
|
|
38
|
+
if abs(denom) < 1e-12:
|
|
39
|
+
return "Error: singular (collinear) data"
|
|
40
|
+
m = (n * sxy - sx * sy) / denom
|
|
41
|
+
b = (sy - m * sx) / n
|
|
42
|
+
return f"slope m = {m}\nintercept b = {b}\ny = {m}*x + {b}"
|
|
43
|
+
except (ImportError, ValueError, TypeError) as e:
|
|
44
|
+
return f"Error: {e}"
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
register(LinearRegressionTool())
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"""Multiply two matrices (lists of lists). Uses numpy if available."""
|
|
2
|
+
|
|
3
|
+
from devsper.tools.base import Tool
|
|
4
|
+
from devsper.tools.registry import register
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class MatrixMultiplyTool(Tool):
|
|
8
|
+
"""Multiply two matrices given as JSON arrays. Uses numpy."""
|
|
9
|
+
|
|
10
|
+
name = "matrix_multiply"
|
|
11
|
+
description = "Multiply two matrices (list of lists). Requires numpy."
|
|
12
|
+
input_schema = {
|
|
13
|
+
"type": "object",
|
|
14
|
+
"properties": {
|
|
15
|
+
"matrix_a": {"type": "array", "description": "First matrix (list of rows)"},
|
|
16
|
+
"matrix_b": {"type": "array", "description": "Second matrix (list of rows)"},
|
|
17
|
+
},
|
|
18
|
+
"required": ["matrix_a", "matrix_b"],
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
def run(self, **kwargs) -> str:
|
|
22
|
+
a = kwargs.get("matrix_a")
|
|
23
|
+
b = kwargs.get("matrix_b")
|
|
24
|
+
if not isinstance(a, list) or not isinstance(b, list):
|
|
25
|
+
return "Error: matrix_a and matrix_b must be arrays"
|
|
26
|
+
try:
|
|
27
|
+
import numpy as np
|
|
28
|
+
A = np.array(a)
|
|
29
|
+
B = np.array(b)
|
|
30
|
+
C = A @ B
|
|
31
|
+
return str(C.tolist())
|
|
32
|
+
except ImportError:
|
|
33
|
+
return "Error: numpy required"
|
|
34
|
+
except Exception as e:
|
|
35
|
+
return f"Error: {e}"
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
register(MatrixMultiplyTool())
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"""Compute mean and standard deviation of a list of numbers."""
|
|
2
|
+
|
|
3
|
+
from devsper.tools.base import Tool
|
|
4
|
+
from devsper.tools.registry import register
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class MeanStdTool(Tool):
|
|
8
|
+
"""Compute mean and standard deviation of a list of numbers."""
|
|
9
|
+
|
|
10
|
+
name = "mean_std"
|
|
11
|
+
description = "Compute mean and standard deviation of a list of numbers."
|
|
12
|
+
input_schema = {
|
|
13
|
+
"type": "object",
|
|
14
|
+
"properties": {"values": {"type": "array", "description": "List of numbers"}},
|
|
15
|
+
"required": ["values"],
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
def run(self, **kwargs) -> str:
|
|
19
|
+
values = kwargs.get("values")
|
|
20
|
+
if not isinstance(values, list):
|
|
21
|
+
return "Error: values must be an array"
|
|
22
|
+
try:
|
|
23
|
+
nums = [float(v) for v in values]
|
|
24
|
+
except (TypeError, ValueError):
|
|
25
|
+
return "Error: all elements must be numbers"
|
|
26
|
+
if not nums:
|
|
27
|
+
return "Error: empty list"
|
|
28
|
+
n = len(nums)
|
|
29
|
+
mean = sum(nums) / n
|
|
30
|
+
variance = sum((x - mean) ** 2 for x in nums) / n
|
|
31
|
+
std = variance ** 0.5
|
|
32
|
+
return f"mean = {mean}\nstd = {std}\nn = {n}"
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
register(MeanStdTool())
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"""Simple Monte Carlo: sample random values and report mean/std."""
|
|
2
|
+
|
|
3
|
+
import random
|
|
4
|
+
|
|
5
|
+
from devsper.tools.base import Tool
|
|
6
|
+
from devsper.tools.registry import register
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class MonteCarloSimulationTool(Tool):
|
|
10
|
+
"""Run a simple Monte Carlo: N samples from uniform(a,b), return mean and std."""
|
|
11
|
+
|
|
12
|
+
name = "monte_carlo_simulation"
|
|
13
|
+
description = "Monte Carlo: N uniform samples in [a,b]. Returns mean and std."
|
|
14
|
+
input_schema = {
|
|
15
|
+
"type": "object",
|
|
16
|
+
"properties": {
|
|
17
|
+
"n": {"type": "integer", "description": "Number of samples"},
|
|
18
|
+
"a": {"type": "number", "description": "Lower bound"},
|
|
19
|
+
"b": {"type": "number", "description": "Upper bound"},
|
|
20
|
+
},
|
|
21
|
+
"required": ["n", "a", "b"],
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
def run(self, **kwargs) -> str:
|
|
25
|
+
n = kwargs.get("n")
|
|
26
|
+
a = kwargs.get("a")
|
|
27
|
+
b = kwargs.get("b")
|
|
28
|
+
if not isinstance(n, int) or n < 1:
|
|
29
|
+
return "Error: n must be a positive integer"
|
|
30
|
+
try:
|
|
31
|
+
a, b = float(a), float(b)
|
|
32
|
+
except (TypeError, ValueError):
|
|
33
|
+
return "Error: a and b must be numbers"
|
|
34
|
+
if a >= b:
|
|
35
|
+
return "Error: a must be less than b"
|
|
36
|
+
values = [random.uniform(a, b) for _ in range(n)]
|
|
37
|
+
mean = sum(values) / n
|
|
38
|
+
variance = sum((x - mean) ** 2 for x in values) / n
|
|
39
|
+
std = variance ** 0.5
|
|
40
|
+
return f"n = {n}\nmean = {mean}\nstd = {std}"
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
register(MonteCarloSimulationTool())
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"""Fit a polynomial of given degree to x, y data. Uses numpy."""
|
|
2
|
+
|
|
3
|
+
from devsper.tools.base import Tool
|
|
4
|
+
from devsper.tools.registry import register
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class PolynomialFitTool(Tool):
|
|
8
|
+
"""Fit polynomial coefficients to x and y. Returns coefficients (highest degree first)."""
|
|
9
|
+
|
|
10
|
+
name = "polynomial_fit"
|
|
11
|
+
description = "Fit polynomial of given degree to x, y. Returns coefficients."
|
|
12
|
+
input_schema = {
|
|
13
|
+
"type": "object",
|
|
14
|
+
"properties": {
|
|
15
|
+
"x": {"type": "array", "description": "x values"},
|
|
16
|
+
"y": {"type": "array", "description": "y values"},
|
|
17
|
+
"degree": {"type": "integer", "description": "Polynomial degree (default 2)"},
|
|
18
|
+
},
|
|
19
|
+
"required": ["x", "y"],
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
def run(self, **kwargs) -> str:
|
|
23
|
+
x = kwargs.get("x")
|
|
24
|
+
y = kwargs.get("y")
|
|
25
|
+
degree = kwargs.get("degree", 2)
|
|
26
|
+
if not isinstance(x, list) or not isinstance(y, list):
|
|
27
|
+
return "Error: x and y must be arrays"
|
|
28
|
+
if len(x) != len(y) or len(x) < 2:
|
|
29
|
+
return "Error: x and y must have same length >= 2"
|
|
30
|
+
if not isinstance(degree, int) or degree < 1:
|
|
31
|
+
degree = 2
|
|
32
|
+
try:
|
|
33
|
+
import numpy as np
|
|
34
|
+
coefs = np.polyfit(x, y, degree)
|
|
35
|
+
return "coefficients (high to low): " + str(coefs.tolist())
|
|
36
|
+
except (ImportError, Exception) as e:
|
|
37
|
+
return f"Error: {e}"
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
register(PolynomialFitTool())
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"""Draw a random sample from a list of values."""
|
|
2
|
+
|
|
3
|
+
import random
|
|
4
|
+
|
|
5
|
+
from devsper.tools.base import Tool
|
|
6
|
+
from devsper.tools.registry import register
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class RandomSampleTool(Tool):
|
|
10
|
+
"""Return k random items from a list (without replacement)."""
|
|
11
|
+
|
|
12
|
+
name = "random_sample"
|
|
13
|
+
description = "Draw k random elements from a list without replacement."
|
|
14
|
+
input_schema = {
|
|
15
|
+
"type": "object",
|
|
16
|
+
"properties": {
|
|
17
|
+
"values": {"type": "array", "description": "List of values"},
|
|
18
|
+
"k": {"type": "integer", "description": "Number of items to sample"},
|
|
19
|
+
},
|
|
20
|
+
"required": ["values", "k"],
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
def run(self, **kwargs) -> str:
|
|
24
|
+
values = kwargs.get("values")
|
|
25
|
+
k = kwargs.get("k")
|
|
26
|
+
if not isinstance(values, list):
|
|
27
|
+
return "Error: values must be an array"
|
|
28
|
+
if not isinstance(k, int) or k < 1:
|
|
29
|
+
return "Error: k must be a positive integer"
|
|
30
|
+
if k > len(values):
|
|
31
|
+
return "Error: k cannot exceed list length"
|
|
32
|
+
sample = random.sample(values, k)
|
|
33
|
+
return str(sample)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
register(RandomSampleTool())
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""
|
|
2
|
+
MCP (Model Context Protocol) integration: client, tool adapter, discovery.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from devsper.config.schema import MCPServerConfig
|
|
6
|
+
from devsper.tools.mcp.client import (
|
|
7
|
+
MCPClient,
|
|
8
|
+
MCPToolDefinition,
|
|
9
|
+
)
|
|
10
|
+
from devsper.tools.mcp.adapter import MCPToolAdapter
|
|
11
|
+
from devsper.tools.mcp.discovery import (
|
|
12
|
+
discover_mcp_tools,
|
|
13
|
+
register_mcp_server,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
__all__ = [
|
|
17
|
+
"MCPClient",
|
|
18
|
+
"MCPToolAdapter",
|
|
19
|
+
"MCPServerConfig",
|
|
20
|
+
"MCPToolDefinition",
|
|
21
|
+
"discover_mcp_tools",
|
|
22
|
+
"register_mcp_server",
|
|
23
|
+
]
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"""
|
|
2
|
+
MCPToolAdapter: wrap an MCP tool as a devsper Tool for the tool registry.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
|
|
7
|
+
from devsper.tools.base import Tool
|
|
8
|
+
from devsper.tools.registry import register
|
|
9
|
+
|
|
10
|
+
from devsper.tools.mcp.client import MCPClient, MCPToolDefinition
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class MCPToolAdapter(Tool):
|
|
14
|
+
"""
|
|
15
|
+
Wraps an MCPToolDefinition as a devsper Tool so it appears in the
|
|
16
|
+
existing tool registry and selection pipeline unchanged.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
def __init__(
|
|
20
|
+
self,
|
|
21
|
+
server_name: str,
|
|
22
|
+
definition: MCPToolDefinition,
|
|
23
|
+
client: MCPClient,
|
|
24
|
+
) -> None:
|
|
25
|
+
# "{server_name}.{tool_name}" to avoid collisions
|
|
26
|
+
self.name = f"{server_name}.{definition.name}"
|
|
27
|
+
self.description = definition.description or f"MCP tool: {definition.name}"
|
|
28
|
+
self.category = "mcp"
|
|
29
|
+
self.input_schema = definition.input_schema
|
|
30
|
+
self._client = client
|
|
31
|
+
self._mcp_tool_name = definition.name
|
|
32
|
+
# Register in tool registry on adapter creation
|
|
33
|
+
register(self)
|
|
34
|
+
|
|
35
|
+
def run(self, **kwargs) -> str:
|
|
36
|
+
"""Execute the MCP tool via client.call_tool; sync wrapper around async."""
|
|
37
|
+
try:
|
|
38
|
+
loop = asyncio.get_event_loop()
|
|
39
|
+
except RuntimeError:
|
|
40
|
+
loop = asyncio.new_event_loop()
|
|
41
|
+
asyncio.set_event_loop(loop)
|
|
42
|
+
if loop.is_running():
|
|
43
|
+
# If we're already inside an async context, run in a new loop (e.g. sync call from sync code)
|
|
44
|
+
import concurrent.futures
|
|
45
|
+
with concurrent.futures.ThreadPoolExecutor(max_workers=1) as pool:
|
|
46
|
+
future = pool.submit(
|
|
47
|
+
asyncio.run,
|
|
48
|
+
self._client.call_tool(self._mcp_tool_name, kwargs),
|
|
49
|
+
)
|
|
50
|
+
return future.result()
|
|
51
|
+
return loop.run_until_complete(
|
|
52
|
+
self._client.call_tool(self._mcp_tool_name, kwargs)
|
|
53
|
+
)
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
"""
|
|
2
|
+
MCP client: connect to MCP servers over stdio, HTTP, or SSE.
|
|
3
|
+
Implements initialize handshake, tools/list, tools/call.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import asyncio
|
|
7
|
+
import json
|
|
8
|
+
import os
|
|
9
|
+
from dataclasses import dataclass, field
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
12
|
+
from devsper.protocols import MCP_PROTOCOL_VERSION
|
|
13
|
+
from devsper.types.exceptions import MCPToolError
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass
|
|
17
|
+
class MCPToolDefinition:
|
|
18
|
+
"""One tool as returned by MCP tools/list."""
|
|
19
|
+
name: str
|
|
20
|
+
description: str
|
|
21
|
+
input_schema: dict # JSON Schema as returned by MCP server
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _next_id() -> int:
|
|
25
|
+
"""Simple monotonic id for JSON-RPC requests."""
|
|
26
|
+
_next_id._n = getattr(_next_id, "_n", 0) + 1
|
|
27
|
+
return _next_id._n
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class MCPClient:
|
|
31
|
+
"""
|
|
32
|
+
Connects to an MCP server over stdio, HTTP, or SSE transport.
|
|
33
|
+
Implements MCP protocol: initialize handshake, tools/list, tools/call.
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
def __init__(
|
|
37
|
+
self,
|
|
38
|
+
name: str,
|
|
39
|
+
transport: str,
|
|
40
|
+
*,
|
|
41
|
+
command: list[str] | None = None,
|
|
42
|
+
url: str | None = None,
|
|
43
|
+
env: dict[str, str] | None = None,
|
|
44
|
+
timeout_seconds: int = 30,
|
|
45
|
+
auto_reconnect: bool = True,
|
|
46
|
+
) -> None:
|
|
47
|
+
self.name = name
|
|
48
|
+
self.transport = transport
|
|
49
|
+
self.command = command or []
|
|
50
|
+
self.url = (url or "").rstrip("/")
|
|
51
|
+
self.env = dict(env or {})
|
|
52
|
+
self.timeout_seconds = timeout_seconds
|
|
53
|
+
self.auto_reconnect = auto_reconnect
|
|
54
|
+
self._server_capabilities: dict[str, Any] = {}
|
|
55
|
+
self._stdio_process: asyncio.subprocess.Process | None = None
|
|
56
|
+
self._stdio_reader: asyncio.StreamReader | None = None
|
|
57
|
+
self._stdio_writer: asyncio.StreamWriter | None = None
|
|
58
|
+
self._connected = False
|
|
59
|
+
|
|
60
|
+
async def connect(self) -> None:
|
|
61
|
+
"""Send initialize request, await initialized notification, cache server capabilities."""
|
|
62
|
+
if self.transport == "stdio":
|
|
63
|
+
await self._connect_stdio()
|
|
64
|
+
elif self.transport in ("http", "sse"):
|
|
65
|
+
await self._connect_http_sse()
|
|
66
|
+
else:
|
|
67
|
+
raise ValueError(f"Unsupported MCP transport: {self.transport}")
|
|
68
|
+
|
|
69
|
+
req = {
|
|
70
|
+
"jsonrpc": "2.0",
|
|
71
|
+
"id": _next_id(),
|
|
72
|
+
"method": "initialize",
|
|
73
|
+
"params": {
|
|
74
|
+
"protocolVersion": MCP_PROTOCOL_VERSION,
|
|
75
|
+
"capabilities": {},
|
|
76
|
+
"clientInfo": {"name": "devsper", "version": "1.10.5"},
|
|
77
|
+
},
|
|
78
|
+
}
|
|
79
|
+
resp = await self._request(req)
|
|
80
|
+
if "result" in resp:
|
|
81
|
+
self._server_capabilities = resp["result"].get("capabilities", {})
|
|
82
|
+
# Send initialized notification (no response expected)
|
|
83
|
+
await self._send_notification("notifications/initialized", {})
|
|
84
|
+
self._connected = True
|
|
85
|
+
|
|
86
|
+
async def _connect_stdio(self) -> None:
|
|
87
|
+
if not self.command:
|
|
88
|
+
raise ValueError("stdio transport requires command")
|
|
89
|
+
env = os.environ.copy()
|
|
90
|
+
env.update(self.env)
|
|
91
|
+
self._stdio_process = await asyncio.create_subprocess_exec(
|
|
92
|
+
*self.command,
|
|
93
|
+
stdin=asyncio.subprocess.PIPE,
|
|
94
|
+
stdout=asyncio.subprocess.PIPE,
|
|
95
|
+
stderr=asyncio.subprocess.DEVNULL,
|
|
96
|
+
env=env,
|
|
97
|
+
)
|
|
98
|
+
assert self._stdio_process.stdin and self._stdio_process.stdout
|
|
99
|
+
self._stdio_reader = self._stdio_process.stdout
|
|
100
|
+
self._stdio_writer = self._stdio_process.stdin
|
|
101
|
+
|
|
102
|
+
async def _connect_http_sse(self) -> None:
|
|
103
|
+
if not self.url:
|
|
104
|
+
raise ValueError("http/sse transport requires url")
|
|
105
|
+
# HTTP: no persistent connection for initialize; we'll use POST per request
|
|
106
|
+
self._connected = True
|
|
107
|
+
|
|
108
|
+
def _get_http_client(self):
|
|
109
|
+
import httpx
|
|
110
|
+
return httpx.AsyncClient(timeout=float(self.timeout_seconds)) # type: ignore[return-value]
|
|
111
|
+
|
|
112
|
+
async def _request(self, req: dict) -> dict:
|
|
113
|
+
if self.transport == "stdio":
|
|
114
|
+
return await self._request_stdio(req)
|
|
115
|
+
return await self._request_http(req)
|
|
116
|
+
|
|
117
|
+
async def _request_stdio(self, req: dict) -> dict:
|
|
118
|
+
if not self._stdio_writer or not self._stdio_reader:
|
|
119
|
+
raise RuntimeError("stdio not connected")
|
|
120
|
+
msg = json.dumps(req) + "\n"
|
|
121
|
+
self._stdio_writer.write(msg.encode("utf-8"))
|
|
122
|
+
await self._stdio_writer.drain()
|
|
123
|
+
line = await asyncio.wait_for(
|
|
124
|
+
self._stdio_reader.readline(),
|
|
125
|
+
timeout=self.timeout_seconds,
|
|
126
|
+
)
|
|
127
|
+
if not line:
|
|
128
|
+
raise ConnectionError("MCP server closed stdin")
|
|
129
|
+
data = json.loads(line.decode("utf-8").strip())
|
|
130
|
+
if "error" in data:
|
|
131
|
+
raise MCPToolError(data["error"].get("message", str(data["error"])))
|
|
132
|
+
return data
|
|
133
|
+
|
|
134
|
+
async def _request_http(self, req: dict) -> dict:
|
|
135
|
+
import httpx
|
|
136
|
+
client = self._get_http_client()
|
|
137
|
+
try:
|
|
138
|
+
# MCP over HTTP: POST JSON-RPC to endpoint
|
|
139
|
+
endpoint = self.url if self.url.endswith("/") else f"{self.url}/"
|
|
140
|
+
r = await client.post(endpoint, json=req)
|
|
141
|
+
r.raise_for_status()
|
|
142
|
+
out = r.json()
|
|
143
|
+
if "error" in out:
|
|
144
|
+
raise MCPToolError(out["error"].get("message", str(out["error"])))
|
|
145
|
+
return out
|
|
146
|
+
except httpx.HTTPError as e:
|
|
147
|
+
raise MCPToolError(str(e))
|
|
148
|
+
finally:
|
|
149
|
+
await client.aclose()
|
|
150
|
+
|
|
151
|
+
async def _send_notification(self, method: str, params: dict) -> None:
|
|
152
|
+
"""Send a notification (no id, no response)."""
|
|
153
|
+
msg = {"jsonrpc": "2.0", "method": method, "params": params or {}}
|
|
154
|
+
if self.transport == "stdio" and self._stdio_writer:
|
|
155
|
+
self._stdio_writer.write((json.dumps(msg) + "\n").encode("utf-8"))
|
|
156
|
+
await self._stdio_writer.drain()
|
|
157
|
+
elif self.transport in ("http", "sse"):
|
|
158
|
+
async with self._get_http_client() as client:
|
|
159
|
+
await client.post(
|
|
160
|
+
self.url if self.url.endswith("/") else f"{self.url}/",
|
|
161
|
+
json=msg,
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
async def list_tools(self) -> list[MCPToolDefinition]:
|
|
165
|
+
"""Send tools/list request; return list of MCPToolDefinition."""
|
|
166
|
+
req = {
|
|
167
|
+
"jsonrpc": "2.0",
|
|
168
|
+
"id": _next_id(),
|
|
169
|
+
"method": "tools/list",
|
|
170
|
+
"params": {},
|
|
171
|
+
}
|
|
172
|
+
resp = await self._request(req)
|
|
173
|
+
result = resp.get("result") or {}
|
|
174
|
+
tools_data = result.get("tools", [])
|
|
175
|
+
return [
|
|
176
|
+
MCPToolDefinition(
|
|
177
|
+
name=t.get("name", ""),
|
|
178
|
+
description=t.get("description", ""),
|
|
179
|
+
input_schema=t.get("inputSchema", {}),
|
|
180
|
+
)
|
|
181
|
+
for t in tools_data
|
|
182
|
+
]
|
|
183
|
+
|
|
184
|
+
async def call_tool(self, name: str, arguments: dict) -> str:
|
|
185
|
+
"""Send tools/call request; extract text from result.content; on error raise MCPToolError."""
|
|
186
|
+
req = {
|
|
187
|
+
"jsonrpc": "2.0",
|
|
188
|
+
"id": _next_id(),
|
|
189
|
+
"method": "tools/call",
|
|
190
|
+
"params": {"name": name, "arguments": arguments or {}},
|
|
191
|
+
}
|
|
192
|
+
resp = await self._request(req)
|
|
193
|
+
result = resp.get("result")
|
|
194
|
+
if result is None:
|
|
195
|
+
raise MCPToolError(resp.get("error", {}).get("message", "Unknown error"))
|
|
196
|
+
if result.get("isError"):
|
|
197
|
+
content = result.get("content", [])
|
|
198
|
+
parts = [
|
|
199
|
+
c.get("text", str(c))
|
|
200
|
+
for c in content
|
|
201
|
+
if isinstance(c, dict)
|
|
202
|
+
]
|
|
203
|
+
raise MCPToolError("\n".join(parts) if parts else "Tool returned error")
|
|
204
|
+
content = result.get("content") or []
|
|
205
|
+
texts = [
|
|
206
|
+
c.get("text", "") if isinstance(c, dict) else str(c)
|
|
207
|
+
for c in content
|
|
208
|
+
]
|
|
209
|
+
return "\n".join(texts)
|
|
210
|
+
|
|
211
|
+
async def disconnect(self) -> None:
|
|
212
|
+
"""Close connection."""
|
|
213
|
+
if self.transport == "stdio" and self._stdio_writer:
|
|
214
|
+
try:
|
|
215
|
+
self._stdio_writer.close()
|
|
216
|
+
await self._stdio_writer.wait_closed()
|
|
217
|
+
except Exception:
|
|
218
|
+
pass
|
|
219
|
+
self._stdio_writer = None
|
|
220
|
+
self._stdio_reader = None
|
|
221
|
+
if self._stdio_process and self._stdio_process.returncode is None:
|
|
222
|
+
self._stdio_process.terminate()
|
|
223
|
+
try:
|
|
224
|
+
await asyncio.wait_for(self._stdio_process.wait(), timeout=2.0)
|
|
225
|
+
except asyncio.TimeoutError:
|
|
226
|
+
self._stdio_process.kill()
|
|
227
|
+
self._stdio_process = None
|
|
228
|
+
self._connected = False
|
|
229
|
+
|
|
230
|
+
async def reconnect(self) -> None:
|
|
231
|
+
"""Disconnect and connect again."""
|
|
232
|
+
await self.disconnect()
|
|
233
|
+
await self.connect()
|
|
234
|
+
|
|
235
|
+
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"""
|
|
2
|
+
MCP discovery: connect to server, list tools, wrap as MCPToolAdapter and optionally register.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from devsper.config.schema import MCPServerConfig
|
|
6
|
+
from devsper.tools.mcp.client import MCPClient, MCPToolDefinition
|
|
7
|
+
from devsper.tools.mcp.adapter import MCPToolAdapter
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def _config_to_client(config: MCPServerConfig) -> MCPClient:
|
|
11
|
+
"""Build MCPClient from config model."""
|
|
12
|
+
return MCPClient(
|
|
13
|
+
name=config.name,
|
|
14
|
+
transport=config.transport,
|
|
15
|
+
command=config.command,
|
|
16
|
+
url=config.url,
|
|
17
|
+
env=config.env,
|
|
18
|
+
timeout_seconds=config.timeout_seconds,
|
|
19
|
+
auto_reconnect=config.auto_reconnect,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
async def discover_mcp_tools_async(server_config: MCPServerConfig) -> list[MCPToolAdapter]:
|
|
24
|
+
"""Connect client, list tools, wrap each as MCPToolAdapter, return list."""
|
|
25
|
+
client = _config_to_client(server_config)
|
|
26
|
+
await client.connect()
|
|
27
|
+
definitions = await client.list_tools()
|
|
28
|
+
adapters = [
|
|
29
|
+
MCPToolAdapter(server_config.name, defn, client)
|
|
30
|
+
for defn in definitions
|
|
31
|
+
]
|
|
32
|
+
return adapters
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def discover_mcp_tools(server_config: MCPServerConfig) -> list[MCPToolAdapter]:
|
|
36
|
+
"""Synchronous wrapper: connect, list tools, wrap as MCPToolAdapter."""
|
|
37
|
+
import asyncio
|
|
38
|
+
return asyncio.run(discover_mcp_tools_async(server_config))
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
async def register_mcp_server_async(server_config: MCPServerConfig) -> int:
|
|
42
|
+
"""Discover MCP tools and register each adapter in the tool registry. Return count."""
|
|
43
|
+
adapters = await discover_mcp_tools_async(server_config)
|
|
44
|
+
from devsper.tools.registry import register
|
|
45
|
+
for adapter in adapters:
|
|
46
|
+
register(adapter)
|
|
47
|
+
return len(adapters)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def register_mcp_server(server_config: MCPServerConfig) -> int:
|
|
51
|
+
"""Synchronous: discover and register MCP tools; return count registered."""
|
|
52
|
+
import asyncio
|
|
53
|
+
return asyncio.run(register_mcp_server_async(server_config))
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""Memory tools: store, search, list, delete, tag, summarize memory."""
|
|
2
|
+
|
|
3
|
+
from devsper.tools.memory.store_memory import StoreMemoryTool
|
|
4
|
+
from devsper.tools.memory.search_memory import SearchMemoryTool
|
|
5
|
+
from devsper.tools.memory.list_memory import ListMemoryTool
|
|
6
|
+
from devsper.tools.memory.delete_memory import DeleteMemoryTool
|
|
7
|
+
from devsper.tools.memory.tag_memory import TagMemoryTool
|
|
8
|
+
from devsper.tools.memory.summarize_memory import SummarizeMemoryTool
|
|
9
|
+
from devsper.tools.registry import register
|
|
10
|
+
|
|
11
|
+
register(StoreMemoryTool())
|
|
12
|
+
register(SearchMemoryTool())
|
|
13
|
+
register(ListMemoryTool())
|
|
14
|
+
register(DeleteMemoryTool())
|
|
15
|
+
register(TagMemoryTool())
|
|
16
|
+
register(SummarizeMemoryTool())
|