synapse-sdk 1.0.0a11__py3-none-any.whl → 2026.1.1b2__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.
Potentially problematic release.
This version of synapse-sdk might be problematic. Click here for more details.
- synapse_sdk/__init__.py +24 -0
- synapse_sdk/cli/__init__.py +9 -8
- synapse_sdk/cli/agent/__init__.py +25 -0
- synapse_sdk/cli/agent/config.py +104 -0
- synapse_sdk/cli/agent/select.py +197 -0
- synapse_sdk/cli/auth.py +104 -0
- synapse_sdk/cli/main.py +1025 -0
- synapse_sdk/cli/plugin/__init__.py +58 -0
- synapse_sdk/cli/plugin/create.py +566 -0
- synapse_sdk/cli/plugin/job.py +196 -0
- synapse_sdk/cli/plugin/publish.py +322 -0
- synapse_sdk/cli/plugin/run.py +131 -0
- synapse_sdk/cli/plugin/test.py +200 -0
- synapse_sdk/clients/README.md +239 -0
- synapse_sdk/clients/__init__.py +5 -0
- synapse_sdk/clients/_template.py +266 -0
- synapse_sdk/clients/agent/__init__.py +84 -29
- synapse_sdk/clients/agent/async_ray.py +289 -0
- synapse_sdk/clients/agent/container.py +83 -0
- synapse_sdk/clients/agent/plugin.py +101 -0
- synapse_sdk/clients/agent/ray.py +296 -39
- synapse_sdk/clients/backend/__init__.py +152 -12
- synapse_sdk/clients/backend/annotation.py +164 -22
- synapse_sdk/clients/backend/core.py +101 -0
- synapse_sdk/clients/backend/data_collection.py +292 -0
- synapse_sdk/clients/backend/hitl.py +87 -0
- synapse_sdk/clients/backend/integration.py +374 -46
- synapse_sdk/clients/backend/ml.py +134 -22
- synapse_sdk/clients/backend/models.py +247 -0
- synapse_sdk/clients/base.py +538 -59
- synapse_sdk/clients/exceptions.py +35 -7
- synapse_sdk/clients/pipeline/__init__.py +5 -0
- synapse_sdk/clients/pipeline/client.py +636 -0
- synapse_sdk/clients/protocols.py +178 -0
- synapse_sdk/clients/utils.py +86 -8
- synapse_sdk/clients/validation.py +58 -0
- synapse_sdk/enums.py +76 -0
- synapse_sdk/exceptions.py +168 -0
- synapse_sdk/integrations/__init__.py +74 -0
- synapse_sdk/integrations/_base.py +119 -0
- synapse_sdk/integrations/_context.py +53 -0
- synapse_sdk/integrations/ultralytics/__init__.py +78 -0
- synapse_sdk/integrations/ultralytics/_callbacks.py +126 -0
- synapse_sdk/integrations/ultralytics/_patches.py +124 -0
- synapse_sdk/loggers.py +476 -95
- synapse_sdk/mcp/MCP.md +69 -0
- synapse_sdk/mcp/__init__.py +48 -0
- synapse_sdk/mcp/__main__.py +6 -0
- synapse_sdk/mcp/config.py +349 -0
- synapse_sdk/mcp/prompts/__init__.py +4 -0
- synapse_sdk/mcp/resources/__init__.py +4 -0
- synapse_sdk/mcp/server.py +1352 -0
- synapse_sdk/mcp/tools/__init__.py +6 -0
- synapse_sdk/plugins/__init__.py +133 -9
- synapse_sdk/plugins/action.py +229 -0
- synapse_sdk/plugins/actions/__init__.py +82 -0
- synapse_sdk/plugins/actions/dataset/__init__.py +37 -0
- synapse_sdk/plugins/actions/dataset/action.py +471 -0
- synapse_sdk/plugins/actions/export/__init__.py +55 -0
- synapse_sdk/plugins/actions/export/action.py +183 -0
- synapse_sdk/plugins/actions/export/context.py +59 -0
- synapse_sdk/plugins/actions/inference/__init__.py +84 -0
- synapse_sdk/plugins/actions/inference/action.py +285 -0
- synapse_sdk/plugins/actions/inference/context.py +81 -0
- synapse_sdk/plugins/actions/inference/deployment.py +322 -0
- synapse_sdk/plugins/actions/inference/serve.py +252 -0
- synapse_sdk/plugins/actions/train/__init__.py +54 -0
- synapse_sdk/plugins/actions/train/action.py +326 -0
- synapse_sdk/plugins/actions/train/context.py +57 -0
- synapse_sdk/plugins/actions/upload/__init__.py +49 -0
- synapse_sdk/plugins/actions/upload/action.py +165 -0
- synapse_sdk/plugins/actions/upload/context.py +61 -0
- synapse_sdk/plugins/config.py +98 -0
- synapse_sdk/plugins/context/__init__.py +109 -0
- synapse_sdk/plugins/context/env.py +113 -0
- synapse_sdk/plugins/datasets/__init__.py +113 -0
- synapse_sdk/plugins/datasets/converters/__init__.py +76 -0
- synapse_sdk/plugins/datasets/converters/base.py +347 -0
- synapse_sdk/plugins/datasets/converters/yolo/__init__.py +9 -0
- synapse_sdk/plugins/datasets/converters/yolo/from_dm.py +468 -0
- synapse_sdk/plugins/datasets/converters/yolo/to_dm.py +381 -0
- synapse_sdk/plugins/datasets/formats/__init__.py +82 -0
- synapse_sdk/plugins/datasets/formats/dm.py +351 -0
- synapse_sdk/plugins/datasets/formats/yolo.py +240 -0
- synapse_sdk/plugins/decorators.py +83 -0
- synapse_sdk/plugins/discovery.py +790 -0
- synapse_sdk/plugins/docs/ACTION_DEV_GUIDE.md +933 -0
- synapse_sdk/plugins/docs/ARCHITECTURE.md +1225 -0
- synapse_sdk/plugins/docs/LOGGING_SYSTEM.md +683 -0
- synapse_sdk/plugins/docs/OVERVIEW.md +531 -0
- synapse_sdk/plugins/docs/PIPELINE_GUIDE.md +145 -0
- synapse_sdk/plugins/docs/README.md +513 -0
- synapse_sdk/plugins/docs/STEP.md +656 -0
- synapse_sdk/plugins/enums.py +70 -10
- synapse_sdk/plugins/errors.py +92 -0
- synapse_sdk/plugins/executors/__init__.py +43 -0
- synapse_sdk/plugins/executors/local.py +99 -0
- synapse_sdk/plugins/executors/ray/__init__.py +18 -0
- synapse_sdk/plugins/executors/ray/base.py +282 -0
- synapse_sdk/plugins/executors/ray/job.py +298 -0
- synapse_sdk/plugins/executors/ray/jobs_api.py +511 -0
- synapse_sdk/plugins/executors/ray/packaging.py +137 -0
- synapse_sdk/plugins/executors/ray/pipeline.py +792 -0
- synapse_sdk/plugins/executors/ray/task.py +257 -0
- synapse_sdk/plugins/models/__init__.py +26 -0
- synapse_sdk/plugins/models/logger.py +173 -0
- synapse_sdk/plugins/models/pipeline.py +25 -0
- synapse_sdk/plugins/pipelines/__init__.py +81 -0
- synapse_sdk/plugins/pipelines/action_pipeline.py +417 -0
- synapse_sdk/plugins/pipelines/context.py +107 -0
- synapse_sdk/plugins/pipelines/display.py +311 -0
- synapse_sdk/plugins/runner.py +114 -0
- synapse_sdk/plugins/schemas/__init__.py +19 -0
- synapse_sdk/plugins/schemas/results.py +152 -0
- synapse_sdk/plugins/steps/__init__.py +63 -0
- synapse_sdk/plugins/steps/base.py +128 -0
- synapse_sdk/plugins/steps/context.py +90 -0
- synapse_sdk/plugins/steps/orchestrator.py +128 -0
- synapse_sdk/plugins/steps/registry.py +103 -0
- synapse_sdk/plugins/steps/utils/__init__.py +20 -0
- synapse_sdk/plugins/steps/utils/logging.py +85 -0
- synapse_sdk/plugins/steps/utils/timing.py +71 -0
- synapse_sdk/plugins/steps/utils/validation.py +68 -0
- synapse_sdk/plugins/templates/__init__.py +50 -0
- synapse_sdk/plugins/templates/base/.gitignore.j2 +26 -0
- synapse_sdk/plugins/templates/base/.synapseignore.j2 +11 -0
- synapse_sdk/plugins/templates/base/README.md.j2 +26 -0
- synapse_sdk/plugins/templates/base/plugin/__init__.py.j2 +1 -0
- synapse_sdk/plugins/templates/base/pyproject.toml.j2 +14 -0
- synapse_sdk/plugins/templates/base/requirements.txt.j2 +1 -0
- synapse_sdk/plugins/templates/custom/plugin/main.py.j2 +18 -0
- synapse_sdk/plugins/templates/data_validation/plugin/validate.py.j2 +32 -0
- synapse_sdk/plugins/templates/export/plugin/export.py.j2 +36 -0
- synapse_sdk/plugins/templates/neural_net/plugin/inference.py.j2 +36 -0
- synapse_sdk/plugins/templates/neural_net/plugin/train.py.j2 +33 -0
- synapse_sdk/plugins/templates/post_annotation/plugin/post_annotate.py.j2 +32 -0
- synapse_sdk/plugins/templates/pre_annotation/plugin/pre_annotate.py.j2 +32 -0
- synapse_sdk/plugins/templates/smart_tool/plugin/auto_label.py.j2 +44 -0
- synapse_sdk/plugins/templates/upload/plugin/upload.py.j2 +35 -0
- synapse_sdk/plugins/testing/__init__.py +25 -0
- synapse_sdk/plugins/testing/sample_actions.py +98 -0
- synapse_sdk/plugins/types.py +206 -0
- synapse_sdk/plugins/upload.py +595 -64
- synapse_sdk/plugins/utils.py +325 -37
- synapse_sdk/shared/__init__.py +25 -0
- synapse_sdk/utils/__init__.py +1 -0
- synapse_sdk/utils/auth.py +74 -0
- synapse_sdk/utils/file/__init__.py +58 -0
- synapse_sdk/utils/file/archive.py +449 -0
- synapse_sdk/utils/file/checksum.py +167 -0
- synapse_sdk/utils/file/download.py +286 -0
- synapse_sdk/utils/file/io.py +129 -0
- synapse_sdk/utils/file/requirements.py +36 -0
- synapse_sdk/utils/network.py +168 -0
- synapse_sdk/utils/storage/__init__.py +238 -0
- synapse_sdk/utils/storage/config.py +188 -0
- synapse_sdk/utils/storage/errors.py +52 -0
- synapse_sdk/utils/storage/providers/__init__.py +13 -0
- synapse_sdk/utils/storage/providers/base.py +76 -0
- synapse_sdk/utils/storage/providers/gcs.py +168 -0
- synapse_sdk/utils/storage/providers/http.py +250 -0
- synapse_sdk/utils/storage/providers/local.py +126 -0
- synapse_sdk/utils/storage/providers/s3.py +177 -0
- synapse_sdk/utils/storage/providers/sftp.py +208 -0
- synapse_sdk/utils/storage/registry.py +125 -0
- synapse_sdk/utils/websocket.py +99 -0
- synapse_sdk-2026.1.1b2.dist-info/METADATA +715 -0
- synapse_sdk-2026.1.1b2.dist-info/RECORD +172 -0
- {synapse_sdk-1.0.0a11.dist-info → synapse_sdk-2026.1.1b2.dist-info}/WHEEL +1 -1
- synapse_sdk-2026.1.1b2.dist-info/licenses/LICENSE +201 -0
- locale/en/LC_MESSAGES/messages.mo +0 -0
- locale/en/LC_MESSAGES/messages.po +0 -39
- locale/ko/LC_MESSAGES/messages.mo +0 -0
- locale/ko/LC_MESSAGES/messages.po +0 -34
- synapse_sdk/cli/create_plugin.py +0 -10
- synapse_sdk/clients/agent/core.py +0 -7
- synapse_sdk/clients/agent/service.py +0 -15
- synapse_sdk/clients/backend/dataset.py +0 -51
- synapse_sdk/clients/ray/__init__.py +0 -6
- synapse_sdk/clients/ray/core.py +0 -22
- synapse_sdk/clients/ray/serve.py +0 -20
- synapse_sdk/i18n.py +0 -35
- synapse_sdk/plugins/categories/__init__.py +0 -0
- synapse_sdk/plugins/categories/base.py +0 -235
- synapse_sdk/plugins/categories/data_validation/__init__.py +0 -0
- synapse_sdk/plugins/categories/data_validation/actions/__init__.py +0 -0
- synapse_sdk/plugins/categories/data_validation/actions/validation.py +0 -10
- synapse_sdk/plugins/categories/data_validation/templates/config.yaml +0 -3
- synapse_sdk/plugins/categories/data_validation/templates/plugin/__init__.py +0 -0
- synapse_sdk/plugins/categories/data_validation/templates/plugin/validation.py +0 -5
- synapse_sdk/plugins/categories/decorators.py +0 -13
- synapse_sdk/plugins/categories/export/__init__.py +0 -0
- synapse_sdk/plugins/categories/export/actions/__init__.py +0 -0
- synapse_sdk/plugins/categories/export/actions/export.py +0 -10
- synapse_sdk/plugins/categories/import/__init__.py +0 -0
- synapse_sdk/plugins/categories/import/actions/__init__.py +0 -0
- synapse_sdk/plugins/categories/import/actions/import.py +0 -10
- synapse_sdk/plugins/categories/neural_net/__init__.py +0 -0
- synapse_sdk/plugins/categories/neural_net/actions/__init__.py +0 -0
- synapse_sdk/plugins/categories/neural_net/actions/deployment.py +0 -45
- synapse_sdk/plugins/categories/neural_net/actions/inference.py +0 -18
- synapse_sdk/plugins/categories/neural_net/actions/test.py +0 -10
- synapse_sdk/plugins/categories/neural_net/actions/train.py +0 -143
- synapse_sdk/plugins/categories/neural_net/templates/config.yaml +0 -12
- synapse_sdk/plugins/categories/neural_net/templates/plugin/__init__.py +0 -0
- synapse_sdk/plugins/categories/neural_net/templates/plugin/inference.py +0 -4
- synapse_sdk/plugins/categories/neural_net/templates/plugin/test.py +0 -2
- synapse_sdk/plugins/categories/neural_net/templates/plugin/train.py +0 -14
- synapse_sdk/plugins/categories/post_annotation/__init__.py +0 -0
- synapse_sdk/plugins/categories/post_annotation/actions/__init__.py +0 -0
- synapse_sdk/plugins/categories/post_annotation/actions/post_annotation.py +0 -10
- synapse_sdk/plugins/categories/post_annotation/templates/config.yaml +0 -3
- synapse_sdk/plugins/categories/post_annotation/templates/plugin/__init__.py +0 -0
- synapse_sdk/plugins/categories/post_annotation/templates/plugin/post_annotation.py +0 -3
- synapse_sdk/plugins/categories/pre_annotation/__init__.py +0 -0
- synapse_sdk/plugins/categories/pre_annotation/actions/__init__.py +0 -0
- synapse_sdk/plugins/categories/pre_annotation/actions/pre_annotation.py +0 -10
- synapse_sdk/plugins/categories/pre_annotation/templates/config.yaml +0 -3
- synapse_sdk/plugins/categories/pre_annotation/templates/plugin/__init__.py +0 -0
- synapse_sdk/plugins/categories/pre_annotation/templates/plugin/pre_annotation.py +0 -3
- synapse_sdk/plugins/categories/registry.py +0 -16
- synapse_sdk/plugins/categories/smart_tool/__init__.py +0 -0
- synapse_sdk/plugins/categories/smart_tool/actions/__init__.py +0 -0
- synapse_sdk/plugins/categories/smart_tool/actions/auto_label.py +0 -37
- synapse_sdk/plugins/categories/smart_tool/templates/config.yaml +0 -7
- synapse_sdk/plugins/categories/smart_tool/templates/plugin/__init__.py +0 -0
- synapse_sdk/plugins/categories/smart_tool/templates/plugin/auto_label.py +0 -11
- synapse_sdk/plugins/categories/templates.py +0 -32
- synapse_sdk/plugins/cli/__init__.py +0 -21
- synapse_sdk/plugins/cli/publish.py +0 -37
- synapse_sdk/plugins/cli/run.py +0 -67
- synapse_sdk/plugins/exceptions.py +0 -22
- synapse_sdk/plugins/models.py +0 -121
- synapse_sdk/plugins/templates/cookiecutter.json +0 -11
- synapse_sdk/plugins/templates/hooks/post_gen_project.py +0 -3
- synapse_sdk/plugins/templates/hooks/pre_prompt.py +0 -21
- synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/.env +0 -24
- synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/.env.dist +0 -24
- synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/.gitignore +0 -27
- synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/.pre-commit-config.yaml +0 -7
- synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/README.md +0 -5
- synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/config.yaml +0 -6
- synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/main.py +0 -4
- synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/plugin/__init__.py +0 -0
- synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/pyproject.toml +0 -13
- synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/requirements.txt +0 -1
- synapse_sdk/shared/enums.py +0 -8
- synapse_sdk/utils/debug.py +0 -5
- synapse_sdk/utils/file.py +0 -87
- synapse_sdk/utils/module_loading.py +0 -29
- synapse_sdk/utils/pydantic/__init__.py +0 -0
- synapse_sdk/utils/pydantic/config.py +0 -4
- synapse_sdk/utils/pydantic/errors.py +0 -33
- synapse_sdk/utils/pydantic/validators.py +0 -7
- synapse_sdk/utils/storage.py +0 -91
- synapse_sdk/utils/string.py +0 -11
- synapse_sdk-1.0.0a11.dist-info/LICENSE +0 -21
- synapse_sdk-1.0.0a11.dist-info/METADATA +0 -43
- synapse_sdk-1.0.0a11.dist-info/RECORD +0 -111
- {synapse_sdk-1.0.0a11.dist-info → synapse_sdk-2026.1.1b2.dist-info}/entry_points.txt +0 -0
- {synapse_sdk-1.0.0a11.dist-info → synapse_sdk-2026.1.1b2.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"""Synapse SDK MCP Server.
|
|
2
|
+
|
|
3
|
+
This module provides an MCP (Model Context Protocol) server that enables
|
|
4
|
+
AI assistants to interact with Synapse infrastructure for plugin development,
|
|
5
|
+
execution, debugging, and deployment.
|
|
6
|
+
|
|
7
|
+
Usage:
|
|
8
|
+
# Initialize config (uses existing ~/.synapse/credentials if available)
|
|
9
|
+
synapse mcp init
|
|
10
|
+
|
|
11
|
+
# Run via CLI
|
|
12
|
+
synapse mcp serve
|
|
13
|
+
|
|
14
|
+
# Or run directly
|
|
15
|
+
python -m synapse_sdk.mcp
|
|
16
|
+
|
|
17
|
+
# Cursor: Add to ~/.cursor/mcp.json:
|
|
18
|
+
{
|
|
19
|
+
"mcpServers": {
|
|
20
|
+
"synapse": {
|
|
21
|
+
"command": "uvx",
|
|
22
|
+
"args": ["--from", "synapse-sdk[mcp]", "synapse", "mcp", "serve"]
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
# Claude Code:
|
|
28
|
+
claude mcp add synapse -- uvx --from 'synapse-sdk[mcp]' synapse mcp serve
|
|
29
|
+
|
|
30
|
+
Configuration (~/.synapse/config.yaml):
|
|
31
|
+
default_environment: profile_1
|
|
32
|
+
|
|
33
|
+
environments:
|
|
34
|
+
profile_1:
|
|
35
|
+
backend_url: https://api.synapse.sh
|
|
36
|
+
access_token: your-token
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
from synapse_sdk.mcp.config import ConfigManager, EnvironmentConfig, get_config_manager
|
|
40
|
+
from synapse_sdk.mcp.server import mcp, serve
|
|
41
|
+
|
|
42
|
+
__all__ = [
|
|
43
|
+
'mcp',
|
|
44
|
+
'serve',
|
|
45
|
+
'ConfigManager',
|
|
46
|
+
'EnvironmentConfig',
|
|
47
|
+
'get_config_manager',
|
|
48
|
+
]
|
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
"""MCP Server configuration and environment management."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import sys
|
|
7
|
+
from dataclasses import dataclass, field
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass
|
|
13
|
+
class EnvironmentConfig:
|
|
14
|
+
"""Configuration for a single environment."""
|
|
15
|
+
|
|
16
|
+
name: str
|
|
17
|
+
backend_url: str | None = None
|
|
18
|
+
access_token: str | None = None
|
|
19
|
+
tenant: str | None = None
|
|
20
|
+
# Agent info is fetched from backend, but cached here after selection
|
|
21
|
+
agent_id: int | None = None
|
|
22
|
+
agent_name: str | None = None
|
|
23
|
+
agent_url: str | None = None
|
|
24
|
+
agent_token: str | None = None
|
|
25
|
+
plugin_paths: list[str] = field(default_factory=list)
|
|
26
|
+
|
|
27
|
+
def has_backend(self) -> bool:
|
|
28
|
+
"""Check if backend is configured."""
|
|
29
|
+
return bool(self.backend_url and self.access_token)
|
|
30
|
+
|
|
31
|
+
def has_agent(self) -> bool:
|
|
32
|
+
"""Check if agent is configured."""
|
|
33
|
+
return bool(self.agent_url and self.agent_token)
|
|
34
|
+
|
|
35
|
+
def to_dict(self, include_secrets: bool = False) -> dict[str, Any]:
|
|
36
|
+
"""Convert to dictionary, optionally hiding secrets."""
|
|
37
|
+
result = {
|
|
38
|
+
'name': self.name,
|
|
39
|
+
'backend_url': self.backend_url,
|
|
40
|
+
'tenant': self.tenant,
|
|
41
|
+
'agent_id': self.agent_id,
|
|
42
|
+
'agent_name': self.agent_name,
|
|
43
|
+
'agent_url': self.agent_url,
|
|
44
|
+
'plugin_paths': self.plugin_paths,
|
|
45
|
+
'has_backend': self.has_backend(),
|
|
46
|
+
'has_agent': self.has_agent(),
|
|
47
|
+
}
|
|
48
|
+
if include_secrets:
|
|
49
|
+
result['access_token'] = self.access_token
|
|
50
|
+
result['agent_token'] = self.agent_token
|
|
51
|
+
else:
|
|
52
|
+
result['access_token'] = '***' if self.access_token else None
|
|
53
|
+
result['agent_token'] = '***' if self.agent_token else None
|
|
54
|
+
return result
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class ConfigManager:
|
|
58
|
+
"""Manages MCP server configuration and environment switching."""
|
|
59
|
+
|
|
60
|
+
DEFAULT_CONFIG_PATH = Path.home() / '.synapse' / 'config.json'
|
|
61
|
+
|
|
62
|
+
def __init__(self, config_path: Path | str | None = None):
|
|
63
|
+
self.config_path = Path(config_path) if config_path else self.DEFAULT_CONFIG_PATH
|
|
64
|
+
self._environments: dict[str, EnvironmentConfig] = {}
|
|
65
|
+
self._default_env: str | None = None
|
|
66
|
+
self._active_env: str | None = None
|
|
67
|
+
self._backend_client: Any = None
|
|
68
|
+
self._agent_client: Any = None
|
|
69
|
+
self._load_config()
|
|
70
|
+
|
|
71
|
+
def _load_config(self) -> None:
|
|
72
|
+
"""Load configuration from JSON file."""
|
|
73
|
+
if not self.config_path.exists():
|
|
74
|
+
self._log(f'Config file not found: {self.config_path}')
|
|
75
|
+
return
|
|
76
|
+
|
|
77
|
+
try:
|
|
78
|
+
with open(self.config_path) as f:
|
|
79
|
+
data = json.load(f) or {}
|
|
80
|
+
|
|
81
|
+
self._default_env = data.get('default_environment')
|
|
82
|
+
|
|
83
|
+
for name, env_data in data.get('environments', {}).items():
|
|
84
|
+
if env_data is None:
|
|
85
|
+
env_data = {}
|
|
86
|
+
self._environments[name] = EnvironmentConfig(
|
|
87
|
+
name=name,
|
|
88
|
+
backend_url=env_data.get('backend_url'),
|
|
89
|
+
access_token=env_data.get('access_token'),
|
|
90
|
+
tenant=env_data.get('tenant'),
|
|
91
|
+
agent_id=env_data.get('agent_id'),
|
|
92
|
+
agent_name=env_data.get('agent_name'),
|
|
93
|
+
agent_url=env_data.get('agent_url'),
|
|
94
|
+
agent_token=env_data.get('agent_token'),
|
|
95
|
+
plugin_paths=env_data.get('plugin_paths', []),
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
# Set active environment to default
|
|
99
|
+
if self._default_env and self._default_env in self._environments:
|
|
100
|
+
self._active_env = self._default_env
|
|
101
|
+
|
|
102
|
+
self._log(f'Loaded {len(self._environments)} environments from {self.config_path}')
|
|
103
|
+
|
|
104
|
+
except Exception as e:
|
|
105
|
+
self._log(f'Error loading config: {e}')
|
|
106
|
+
|
|
107
|
+
def _save_config(self) -> None:
|
|
108
|
+
"""Save current configuration to JSON file."""
|
|
109
|
+
data = {
|
|
110
|
+
'default_environment': self._default_env,
|
|
111
|
+
'environments': {},
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
for name, env in self._environments.items():
|
|
115
|
+
env_data: dict[str, Any] = {}
|
|
116
|
+
if env.backend_url:
|
|
117
|
+
env_data['backend_url'] = env.backend_url
|
|
118
|
+
if env.access_token:
|
|
119
|
+
env_data['access_token'] = env.access_token
|
|
120
|
+
if env.tenant:
|
|
121
|
+
env_data['tenant'] = env.tenant
|
|
122
|
+
if env.agent_id:
|
|
123
|
+
env_data['agent_id'] = env.agent_id
|
|
124
|
+
if env.agent_name:
|
|
125
|
+
env_data['agent_name'] = env.agent_name
|
|
126
|
+
if env.agent_url:
|
|
127
|
+
env_data['agent_url'] = env.agent_url
|
|
128
|
+
if env.agent_token:
|
|
129
|
+
env_data['agent_token'] = env.agent_token
|
|
130
|
+
if env.plugin_paths:
|
|
131
|
+
env_data['plugin_paths'] = env.plugin_paths
|
|
132
|
+
data['environments'][name] = env_data
|
|
133
|
+
|
|
134
|
+
# Ensure directory exists
|
|
135
|
+
self.config_path.parent.mkdir(parents=True, exist_ok=True)
|
|
136
|
+
|
|
137
|
+
with open(self.config_path, 'w') as f:
|
|
138
|
+
json.dump(data, f, indent=2)
|
|
139
|
+
|
|
140
|
+
# Set restrictive permissions (owner read/write only)
|
|
141
|
+
self.config_path.chmod(0o600)
|
|
142
|
+
|
|
143
|
+
def _log(self, message: str) -> None:
|
|
144
|
+
"""Log to stderr (safe for MCP STDIO transport)."""
|
|
145
|
+
print(f'[synapse-mcp] {message}', file=sys.stderr)
|
|
146
|
+
|
|
147
|
+
def _clear_clients(self) -> None:
|
|
148
|
+
"""Clear cached client instances."""
|
|
149
|
+
self._backend_client = None
|
|
150
|
+
self._agent_client = None
|
|
151
|
+
|
|
152
|
+
# Environment management
|
|
153
|
+
|
|
154
|
+
def list_environments(self) -> list[str]:
|
|
155
|
+
"""List all configured environment names."""
|
|
156
|
+
return list(self._environments.keys())
|
|
157
|
+
|
|
158
|
+
def get_environment(self, name: str) -> EnvironmentConfig | None:
|
|
159
|
+
"""Get environment configuration by name."""
|
|
160
|
+
return self._environments.get(name)
|
|
161
|
+
|
|
162
|
+
def get_active_environment(self) -> EnvironmentConfig | None:
|
|
163
|
+
"""Get the currently active environment."""
|
|
164
|
+
if self._active_env:
|
|
165
|
+
return self._environments.get(self._active_env)
|
|
166
|
+
return None
|
|
167
|
+
|
|
168
|
+
def get_active_environment_name(self) -> str | None:
|
|
169
|
+
"""Get the name of the currently active environment."""
|
|
170
|
+
return self._active_env
|
|
171
|
+
|
|
172
|
+
def set_active_environment(self, name: str) -> bool:
|
|
173
|
+
"""Set the active environment by name."""
|
|
174
|
+
if name not in self._environments:
|
|
175
|
+
return False
|
|
176
|
+
self._active_env = name
|
|
177
|
+
self._clear_clients()
|
|
178
|
+
self._log(f'Switched to environment: {name}')
|
|
179
|
+
return True
|
|
180
|
+
|
|
181
|
+
def add_environment(
|
|
182
|
+
self,
|
|
183
|
+
name: str,
|
|
184
|
+
backend_url: str | None = None,
|
|
185
|
+
access_token: str | None = None,
|
|
186
|
+
tenant: str | None = None,
|
|
187
|
+
plugin_paths: list[str] | None = None,
|
|
188
|
+
set_as_default: bool = False,
|
|
189
|
+
) -> EnvironmentConfig:
|
|
190
|
+
"""Add or update an environment configuration.
|
|
191
|
+
|
|
192
|
+
Note: Agent is configured separately via set_agent() after fetching from backend.
|
|
193
|
+
"""
|
|
194
|
+
# Preserve existing agent config if updating
|
|
195
|
+
existing = self._environments.get(name)
|
|
196
|
+
env = EnvironmentConfig(
|
|
197
|
+
name=name,
|
|
198
|
+
backend_url=backend_url,
|
|
199
|
+
access_token=access_token,
|
|
200
|
+
tenant=tenant,
|
|
201
|
+
agent_id=existing.agent_id if existing else None,
|
|
202
|
+
agent_name=existing.agent_name if existing else None,
|
|
203
|
+
agent_url=existing.agent_url if existing else None,
|
|
204
|
+
agent_token=existing.agent_token if existing else None,
|
|
205
|
+
plugin_paths=plugin_paths or [],
|
|
206
|
+
)
|
|
207
|
+
self._environments[name] = env
|
|
208
|
+
|
|
209
|
+
if set_as_default or not self._default_env:
|
|
210
|
+
self._default_env = name
|
|
211
|
+
|
|
212
|
+
if not self._active_env:
|
|
213
|
+
self._active_env = name
|
|
214
|
+
|
|
215
|
+
self._save_config()
|
|
216
|
+
self._log(f'Added environment: {name}')
|
|
217
|
+
return env
|
|
218
|
+
|
|
219
|
+
def set_agent(
|
|
220
|
+
self,
|
|
221
|
+
agent_id: int,
|
|
222
|
+
agent_name: str | None = None,
|
|
223
|
+
agent_url: str | None = None,
|
|
224
|
+
agent_token: str | None = None,
|
|
225
|
+
) -> bool:
|
|
226
|
+
"""Set the agent for the active environment.
|
|
227
|
+
|
|
228
|
+
Args:
|
|
229
|
+
agent_id: Agent ID from backend
|
|
230
|
+
agent_name: Agent name
|
|
231
|
+
agent_url: Agent URL
|
|
232
|
+
agent_token: Agent authentication token
|
|
233
|
+
|
|
234
|
+
Returns:
|
|
235
|
+
True if successful, False if no active environment
|
|
236
|
+
"""
|
|
237
|
+
env = self.get_active_environment()
|
|
238
|
+
if not env:
|
|
239
|
+
return False
|
|
240
|
+
|
|
241
|
+
env.agent_id = agent_id
|
|
242
|
+
env.agent_name = agent_name
|
|
243
|
+
env.agent_url = agent_url
|
|
244
|
+
env.agent_token = agent_token
|
|
245
|
+
self._clear_clients()
|
|
246
|
+
self._save_config()
|
|
247
|
+
self._log(f'Set agent {agent_id} ({agent_name}) for environment: {env.name}')
|
|
248
|
+
return True
|
|
249
|
+
|
|
250
|
+
def clear_agent(self) -> bool:
|
|
251
|
+
"""Clear the agent for the active environment."""
|
|
252
|
+
env = self.get_active_environment()
|
|
253
|
+
if not env:
|
|
254
|
+
return False
|
|
255
|
+
|
|
256
|
+
env.agent_id = None
|
|
257
|
+
env.agent_name = None
|
|
258
|
+
env.agent_url = None
|
|
259
|
+
env.agent_token = None
|
|
260
|
+
self._clear_clients()
|
|
261
|
+
self._save_config()
|
|
262
|
+
self._log(f'Cleared agent for environment: {env.name}')
|
|
263
|
+
return True
|
|
264
|
+
|
|
265
|
+
def remove_environment(self, name: str) -> bool:
|
|
266
|
+
"""Remove an environment configuration."""
|
|
267
|
+
if name not in self._environments:
|
|
268
|
+
return False
|
|
269
|
+
|
|
270
|
+
del self._environments[name]
|
|
271
|
+
|
|
272
|
+
if self._default_env == name:
|
|
273
|
+
self._default_env = next(iter(self._environments), None)
|
|
274
|
+
|
|
275
|
+
if self._active_env == name:
|
|
276
|
+
self._active_env = self._default_env
|
|
277
|
+
self._clear_clients()
|
|
278
|
+
|
|
279
|
+
self._save_config()
|
|
280
|
+
self._log(f'Removed environment: {name}')
|
|
281
|
+
return True
|
|
282
|
+
|
|
283
|
+
# Client access
|
|
284
|
+
|
|
285
|
+
def get_backend_client(self):
|
|
286
|
+
"""Get BackendClient for the active environment."""
|
|
287
|
+
env = self.get_active_environment()
|
|
288
|
+
if not env or not env.has_backend():
|
|
289
|
+
return None
|
|
290
|
+
|
|
291
|
+
if self._backend_client is None:
|
|
292
|
+
from synapse_sdk.clients.backend import BackendClient
|
|
293
|
+
|
|
294
|
+
self._backend_client = BackendClient(
|
|
295
|
+
base_url=env.backend_url,
|
|
296
|
+
access_token=env.access_token,
|
|
297
|
+
tenant=env.tenant,
|
|
298
|
+
)
|
|
299
|
+
|
|
300
|
+
return self._backend_client
|
|
301
|
+
|
|
302
|
+
def get_agent_client(self):
|
|
303
|
+
"""Get AgentClient for the active environment."""
|
|
304
|
+
env = self.get_active_environment()
|
|
305
|
+
if not env or not env.has_agent():
|
|
306
|
+
return None
|
|
307
|
+
|
|
308
|
+
if self._agent_client is None:
|
|
309
|
+
from synapse_sdk.clients import AgentClient
|
|
310
|
+
|
|
311
|
+
self._agent_client = AgentClient(
|
|
312
|
+
base_url=env.agent_url,
|
|
313
|
+
agent_token=env.agent_token,
|
|
314
|
+
tenant=env.tenant,
|
|
315
|
+
)
|
|
316
|
+
|
|
317
|
+
return self._agent_client
|
|
318
|
+
|
|
319
|
+
# Status
|
|
320
|
+
|
|
321
|
+
def get_status(self) -> dict[str, Any]:
|
|
322
|
+
"""Get current configuration status."""
|
|
323
|
+
env = self.get_active_environment()
|
|
324
|
+
return {
|
|
325
|
+
'config_path': str(self.config_path),
|
|
326
|
+
'config_exists': self.config_path.exists(),
|
|
327
|
+
'environments': self.list_environments(),
|
|
328
|
+
'default_environment': self._default_env,
|
|
329
|
+
'active_environment': self._active_env,
|
|
330
|
+
'active_env_details': env.to_dict() if env else None,
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
# Global config manager instance
|
|
335
|
+
_config_manager: ConfigManager | None = None
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
def get_config_manager() -> ConfigManager:
|
|
339
|
+
"""Get the global ConfigManager instance."""
|
|
340
|
+
global _config_manager
|
|
341
|
+
if _config_manager is None:
|
|
342
|
+
_config_manager = ConfigManager()
|
|
343
|
+
return _config_manager
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
def reset_config_manager() -> None:
|
|
347
|
+
"""Reset the global ConfigManager instance (for testing)."""
|
|
348
|
+
global _config_manager
|
|
349
|
+
_config_manager = None
|