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,1352 @@
|
|
|
1
|
+
"""MCP Server for Synapse SDK.
|
|
2
|
+
|
|
3
|
+
This module provides the FastMCP server instance and registers all tools,
|
|
4
|
+
resources, and prompts for interacting with Synapse infrastructure.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import sys
|
|
10
|
+
from typing import TYPE_CHECKING
|
|
11
|
+
|
|
12
|
+
try:
|
|
13
|
+
from mcp.server.fastmcp import FastMCP
|
|
14
|
+
except ImportError:
|
|
15
|
+
raise ImportError('MCP dependencies not installed. Install with: pip install synapse-sdk[mcp]')
|
|
16
|
+
|
|
17
|
+
from synapse_sdk.mcp.config import get_config_manager
|
|
18
|
+
|
|
19
|
+
if TYPE_CHECKING:
|
|
20
|
+
from synapse_sdk.mcp.config import ConfigManager
|
|
21
|
+
|
|
22
|
+
# Create the MCP server instance
|
|
23
|
+
mcp = FastMCP(
|
|
24
|
+
name='synapse',
|
|
25
|
+
instructions='Synapse SDK MCP Server - Manage plugins, executions, and deployments',
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _log(message: str) -> None:
|
|
30
|
+
"""Log to stderr (safe for MCP STDIO transport)."""
|
|
31
|
+
print(f'[synapse-mcp] {message}', file=sys.stderr)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _get_config() -> ConfigManager:
|
|
35
|
+
"""Get the config manager instance."""
|
|
36
|
+
return get_config_manager()
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
# =============================================================================
|
|
40
|
+
# Environment Tools
|
|
41
|
+
# =============================================================================
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@mcp.tool()
|
|
45
|
+
def switch_environment(name: str) -> dict:
|
|
46
|
+
"""Switch to a different environment (e.g., prod, test, demo, local).
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
name: The environment name to switch to
|
|
50
|
+
|
|
51
|
+
Returns:
|
|
52
|
+
Status of the switch operation with environment details
|
|
53
|
+
"""
|
|
54
|
+
config = _get_config()
|
|
55
|
+
|
|
56
|
+
if name not in config.list_environments():
|
|
57
|
+
return {
|
|
58
|
+
'success': False,
|
|
59
|
+
'error': f'Environment "{name}" not found',
|
|
60
|
+
'available_environments': config.list_environments(),
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
config.set_active_environment(name)
|
|
64
|
+
env = config.get_active_environment()
|
|
65
|
+
|
|
66
|
+
return {
|
|
67
|
+
'success': True,
|
|
68
|
+
'message': f'Switched to environment: {name}',
|
|
69
|
+
'environment': env.to_dict() if env else None,
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
@mcp.tool()
|
|
74
|
+
def list_environments() -> dict:
|
|
75
|
+
"""List all configured environments.
|
|
76
|
+
|
|
77
|
+
Returns:
|
|
78
|
+
List of environment names and their configuration status
|
|
79
|
+
"""
|
|
80
|
+
config = _get_config()
|
|
81
|
+
environments = []
|
|
82
|
+
|
|
83
|
+
for name in config.list_environments():
|
|
84
|
+
env = config.get_environment(name)
|
|
85
|
+
if env:
|
|
86
|
+
environments.append(env.to_dict())
|
|
87
|
+
|
|
88
|
+
return {
|
|
89
|
+
'environments': environments,
|
|
90
|
+
'active_environment': config.get_active_environment_name(),
|
|
91
|
+
'config_path': str(config.config_path),
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
@mcp.tool()
|
|
96
|
+
def get_current_environment() -> dict:
|
|
97
|
+
"""Get the currently active environment and its connection status.
|
|
98
|
+
|
|
99
|
+
Returns:
|
|
100
|
+
Current environment details including backend/agent availability
|
|
101
|
+
"""
|
|
102
|
+
config = _get_config()
|
|
103
|
+
env = config.get_active_environment()
|
|
104
|
+
|
|
105
|
+
if not env:
|
|
106
|
+
return {
|
|
107
|
+
'active': False,
|
|
108
|
+
'message': 'No environment is currently active. Use switch_environment() to select one.',
|
|
109
|
+
'available_environments': config.list_environments(),
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
# Test connections
|
|
113
|
+
backend_status = 'not_configured'
|
|
114
|
+
agent_status = 'not_configured'
|
|
115
|
+
|
|
116
|
+
if env.has_backend():
|
|
117
|
+
try:
|
|
118
|
+
client = config.get_backend_client()
|
|
119
|
+
if client:
|
|
120
|
+
backend_status = 'connected'
|
|
121
|
+
except Exception as e:
|
|
122
|
+
backend_status = f'error: {e}'
|
|
123
|
+
|
|
124
|
+
if env.has_agent():
|
|
125
|
+
try:
|
|
126
|
+
client = config.get_agent_client()
|
|
127
|
+
if client:
|
|
128
|
+
agent_status = 'connected'
|
|
129
|
+
except Exception as e:
|
|
130
|
+
agent_status = f'error: {e}'
|
|
131
|
+
|
|
132
|
+
return {
|
|
133
|
+
'active': True,
|
|
134
|
+
'environment': env.to_dict(),
|
|
135
|
+
'backend_status': backend_status,
|
|
136
|
+
'agent_status': agent_status,
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
@mcp.tool()
|
|
141
|
+
def add_environment(
|
|
142
|
+
name: str,
|
|
143
|
+
backend_url: str | None = None,
|
|
144
|
+
access_token: str | None = None,
|
|
145
|
+
tenant: str | None = None,
|
|
146
|
+
set_as_default: bool = False,
|
|
147
|
+
) -> dict:
|
|
148
|
+
"""Add or update an environment configuration.
|
|
149
|
+
|
|
150
|
+
After adding, use list_agents() to see available agents,
|
|
151
|
+
then select_agent() to configure the agent for this environment.
|
|
152
|
+
|
|
153
|
+
Args:
|
|
154
|
+
name: Environment name (e.g., 'prod', 'test', 'demo')
|
|
155
|
+
backend_url: Synapse backend API URL
|
|
156
|
+
access_token: Your API access token
|
|
157
|
+
tenant: Tenant identifier
|
|
158
|
+
set_as_default: Whether to set this as the default environment
|
|
159
|
+
|
|
160
|
+
Returns:
|
|
161
|
+
The created/updated environment configuration
|
|
162
|
+
"""
|
|
163
|
+
config = _get_config()
|
|
164
|
+
|
|
165
|
+
env = config.add_environment(
|
|
166
|
+
name=name,
|
|
167
|
+
backend_url=backend_url,
|
|
168
|
+
access_token=access_token,
|
|
169
|
+
tenant=tenant,
|
|
170
|
+
set_as_default=set_as_default,
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
return {
|
|
174
|
+
'success': True,
|
|
175
|
+
'message': f'Environment "{name}" added/updated. Use list_agents() and select_agent() to configure an agent.',
|
|
176
|
+
'environment': env.to_dict(),
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
@mcp.tool()
|
|
181
|
+
def list_agents() -> dict:
|
|
182
|
+
"""List available agents from the backend.
|
|
183
|
+
|
|
184
|
+
Fetches agents from the current environment's backend.
|
|
185
|
+
Use select_agent() to choose one for execution.
|
|
186
|
+
|
|
187
|
+
Returns:
|
|
188
|
+
List of available agents with their IDs, names, URLs, and status
|
|
189
|
+
"""
|
|
190
|
+
config = _get_config()
|
|
191
|
+
client = config.get_backend_client()
|
|
192
|
+
|
|
193
|
+
if not client:
|
|
194
|
+
return {
|
|
195
|
+
'success': False,
|
|
196
|
+
'error': 'No backend configured. Use add_environment() first with backend_url and access_token.',
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
try:
|
|
200
|
+
agents = client.list_agents()
|
|
201
|
+
agent_list = []
|
|
202
|
+
for agent in agents:
|
|
203
|
+
agent_list.append({
|
|
204
|
+
'id': agent.id,
|
|
205
|
+
'name': agent.name,
|
|
206
|
+
'url': agent.url,
|
|
207
|
+
'status': agent.status,
|
|
208
|
+
'is_connected': agent.is_connected,
|
|
209
|
+
})
|
|
210
|
+
|
|
211
|
+
env = config.get_active_environment()
|
|
212
|
+
return {
|
|
213
|
+
'success': True,
|
|
214
|
+
'agents': agent_list,
|
|
215
|
+
'current_agent_id': env.agent_id if env else None,
|
|
216
|
+
'hint': 'Use select_agent(agent_id) to select an agent for execution.',
|
|
217
|
+
}
|
|
218
|
+
except Exception as e:
|
|
219
|
+
return {'success': False, 'error': str(e)}
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
@mcp.tool()
|
|
223
|
+
def select_agent(agent_id: int) -> dict:
|
|
224
|
+
"""Select an agent for the current environment.
|
|
225
|
+
|
|
226
|
+
Fetches the agent details from the backend and saves
|
|
227
|
+
the agent URL and token for the current environment.
|
|
228
|
+
|
|
229
|
+
Args:
|
|
230
|
+
agent_id: The agent ID from list_agents()
|
|
231
|
+
|
|
232
|
+
Returns:
|
|
233
|
+
The selected agent configuration
|
|
234
|
+
"""
|
|
235
|
+
config = _get_config()
|
|
236
|
+
client = config.get_backend_client()
|
|
237
|
+
|
|
238
|
+
if not client:
|
|
239
|
+
return {
|
|
240
|
+
'success': False,
|
|
241
|
+
'error': 'No backend configured. Use add_environment() first.',
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
try:
|
|
245
|
+
agents = client.list_agents()
|
|
246
|
+
selected = None
|
|
247
|
+
for agent in agents:
|
|
248
|
+
if agent.id == agent_id:
|
|
249
|
+
selected = agent
|
|
250
|
+
break
|
|
251
|
+
|
|
252
|
+
if not selected:
|
|
253
|
+
return {
|
|
254
|
+
'success': False,
|
|
255
|
+
'error': f'Agent {agent_id} not found.',
|
|
256
|
+
'available_ids': [a.id for a in agents],
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
# Save to config
|
|
260
|
+
config.set_agent(
|
|
261
|
+
agent_id=selected.id,
|
|
262
|
+
agent_name=selected.name,
|
|
263
|
+
agent_url=selected.url,
|
|
264
|
+
agent_token=selected.token,
|
|
265
|
+
)
|
|
266
|
+
|
|
267
|
+
return {
|
|
268
|
+
'success': True,
|
|
269
|
+
'message': f'Agent "{selected.name}" selected for current environment.',
|
|
270
|
+
'agent': {
|
|
271
|
+
'id': selected.id,
|
|
272
|
+
'name': selected.name,
|
|
273
|
+
'url': selected.url,
|
|
274
|
+
'status': selected.status,
|
|
275
|
+
},
|
|
276
|
+
}
|
|
277
|
+
except Exception as e:
|
|
278
|
+
return {'success': False, 'error': str(e)}
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
@mcp.tool()
|
|
282
|
+
def clear_agent() -> dict:
|
|
283
|
+
"""Clear the agent configuration for the current environment.
|
|
284
|
+
|
|
285
|
+
Returns:
|
|
286
|
+
Status of the operation
|
|
287
|
+
"""
|
|
288
|
+
config = _get_config()
|
|
289
|
+
|
|
290
|
+
if config.clear_agent():
|
|
291
|
+
return {'success': True, 'message': 'Agent cleared for current environment.'}
|
|
292
|
+
else:
|
|
293
|
+
return {'success': False, 'error': 'No active environment.'}
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
# =============================================================================
|
|
297
|
+
# Plugin Tools
|
|
298
|
+
# =============================================================================
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
@mcp.tool()
|
|
302
|
+
def list_plugin_releases(limit: int = 20, offset: int = 0) -> dict:
|
|
303
|
+
"""List published plugin releases from the current environment.
|
|
304
|
+
|
|
305
|
+
Args:
|
|
306
|
+
limit: Maximum number of results to return
|
|
307
|
+
offset: Number of results to skip
|
|
308
|
+
|
|
309
|
+
Returns:
|
|
310
|
+
List of plugin releases
|
|
311
|
+
"""
|
|
312
|
+
config = _get_config()
|
|
313
|
+
client = config.get_agent_client()
|
|
314
|
+
|
|
315
|
+
if not client:
|
|
316
|
+
return {
|
|
317
|
+
'success': False,
|
|
318
|
+
'error': 'No agent configured for current environment. Use switch_environment() first.',
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
try:
|
|
322
|
+
releases, total = client.list_plugin_releases(
|
|
323
|
+
params={'limit': limit, 'offset': offset},
|
|
324
|
+
list_all=False,
|
|
325
|
+
)
|
|
326
|
+
return {
|
|
327
|
+
'success': True,
|
|
328
|
+
'releases': releases,
|
|
329
|
+
'total': total,
|
|
330
|
+
'limit': limit,
|
|
331
|
+
'offset': offset,
|
|
332
|
+
}
|
|
333
|
+
except Exception as e:
|
|
334
|
+
return {'success': False, 'error': str(e)}
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
@mcp.tool()
|
|
338
|
+
def get_plugin_release(lookup: str) -> dict:
|
|
339
|
+
"""Get details of a specific plugin release.
|
|
340
|
+
|
|
341
|
+
Args:
|
|
342
|
+
lookup: Plugin release ID or 'code:version' string
|
|
343
|
+
|
|
344
|
+
Returns:
|
|
345
|
+
Plugin release details
|
|
346
|
+
"""
|
|
347
|
+
config = _get_config()
|
|
348
|
+
client = config.get_agent_client()
|
|
349
|
+
|
|
350
|
+
if not client:
|
|
351
|
+
return {
|
|
352
|
+
'success': False,
|
|
353
|
+
'error': 'No agent configured for current environment.',
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
try:
|
|
357
|
+
release = client.get_plugin_release(lookup)
|
|
358
|
+
return {'success': True, 'release': release}
|
|
359
|
+
except Exception as e:
|
|
360
|
+
return {'success': False, 'error': str(e)}
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
@mcp.tool()
|
|
364
|
+
def discover_local_plugin(path: str) -> dict:
|
|
365
|
+
"""Discover actions in a local plugin directory.
|
|
366
|
+
|
|
367
|
+
Args:
|
|
368
|
+
path: Path to the plugin directory
|
|
369
|
+
|
|
370
|
+
Returns:
|
|
371
|
+
Plugin configuration and list of available actions
|
|
372
|
+
"""
|
|
373
|
+
from pathlib import Path
|
|
374
|
+
|
|
375
|
+
from synapse_sdk.plugins.discovery import PluginDiscovery
|
|
376
|
+
|
|
377
|
+
plugin_path = Path(path).expanduser().resolve()
|
|
378
|
+
|
|
379
|
+
if not plugin_path.exists():
|
|
380
|
+
return {'success': False, 'error': f'Path not found: {plugin_path}'}
|
|
381
|
+
|
|
382
|
+
try:
|
|
383
|
+
discovery = PluginDiscovery.from_path(plugin_path)
|
|
384
|
+
actions = discovery.list_actions()
|
|
385
|
+
|
|
386
|
+
action_details = []
|
|
387
|
+
for action_name in actions:
|
|
388
|
+
try:
|
|
389
|
+
action_config = discovery.get_action_config(action_name)
|
|
390
|
+
action_details.append({
|
|
391
|
+
'name': action_name,
|
|
392
|
+
'description': action_config.description,
|
|
393
|
+
'method': action_config.method.value if action_config.method else None,
|
|
394
|
+
})
|
|
395
|
+
except Exception:
|
|
396
|
+
action_details.append({'name': action_name})
|
|
397
|
+
|
|
398
|
+
return {
|
|
399
|
+
'success': True,
|
|
400
|
+
'path': str(plugin_path),
|
|
401
|
+
'plugin_config': discovery.to_config_dict(include_ui_schemas=False),
|
|
402
|
+
'actions': action_details,
|
|
403
|
+
}
|
|
404
|
+
except Exception as e:
|
|
405
|
+
return {'success': False, 'error': str(e)}
|
|
406
|
+
|
|
407
|
+
|
|
408
|
+
@mcp.tool()
|
|
409
|
+
def get_action_config(path: str, action: str) -> dict:
|
|
410
|
+
"""Get detailed configuration and parameter schema for a plugin action.
|
|
411
|
+
|
|
412
|
+
Args:
|
|
413
|
+
path: Path to the plugin directory
|
|
414
|
+
action: Name of the action
|
|
415
|
+
|
|
416
|
+
Returns:
|
|
417
|
+
Action configuration including parameter schema
|
|
418
|
+
"""
|
|
419
|
+
from pathlib import Path
|
|
420
|
+
|
|
421
|
+
from synapse_sdk.plugins.discovery import PluginDiscovery
|
|
422
|
+
|
|
423
|
+
plugin_path = Path(path).expanduser().resolve()
|
|
424
|
+
|
|
425
|
+
if not plugin_path.exists():
|
|
426
|
+
return {'success': False, 'error': f'Path not found: {plugin_path}'}
|
|
427
|
+
|
|
428
|
+
try:
|
|
429
|
+
discovery = PluginDiscovery.from_path(plugin_path)
|
|
430
|
+
|
|
431
|
+
if not discovery.has_action(action):
|
|
432
|
+
return {
|
|
433
|
+
'success': False,
|
|
434
|
+
'error': f'Action "{action}" not found',
|
|
435
|
+
'available_actions': discovery.list_actions(),
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
action_config = discovery.get_action_config(action)
|
|
439
|
+
params_model = discovery.get_action_params_model(action)
|
|
440
|
+
result_model = discovery.get_action_result_model(action)
|
|
441
|
+
|
|
442
|
+
params_schema = None
|
|
443
|
+
if params_model:
|
|
444
|
+
params_schema = params_model.model_json_schema()
|
|
445
|
+
|
|
446
|
+
result_schema = None
|
|
447
|
+
if result_model:
|
|
448
|
+
result_schema = result_model.model_json_schema()
|
|
449
|
+
|
|
450
|
+
return {
|
|
451
|
+
'success': True,
|
|
452
|
+
'action': action,
|
|
453
|
+
'config': {
|
|
454
|
+
'name': action_config.name,
|
|
455
|
+
'description': action_config.description,
|
|
456
|
+
'entrypoint': action_config.entrypoint,
|
|
457
|
+
'method': action_config.method.value if action_config.method else None,
|
|
458
|
+
},
|
|
459
|
+
'params_schema': params_schema,
|
|
460
|
+
'result_schema': result_schema,
|
|
461
|
+
}
|
|
462
|
+
except Exception as e:
|
|
463
|
+
return {'success': False, 'error': str(e)}
|
|
464
|
+
|
|
465
|
+
|
|
466
|
+
@mcp.tool()
|
|
467
|
+
def validate_plugin_config(path: str) -> dict:
|
|
468
|
+
"""Validate a plugin's config.yaml file.
|
|
469
|
+
|
|
470
|
+
Args:
|
|
471
|
+
path: Path to the plugin directory
|
|
472
|
+
|
|
473
|
+
Returns:
|
|
474
|
+
Validation result with any errors or warnings
|
|
475
|
+
"""
|
|
476
|
+
from pathlib import Path
|
|
477
|
+
|
|
478
|
+
import yaml
|
|
479
|
+
|
|
480
|
+
from synapse_sdk.plugins.discovery import PluginDiscovery
|
|
481
|
+
|
|
482
|
+
plugin_path = Path(path).expanduser().resolve()
|
|
483
|
+
config_file = plugin_path / 'config.yaml'
|
|
484
|
+
|
|
485
|
+
if not plugin_path.exists():
|
|
486
|
+
return {'success': False, 'error': f'Path not found: {plugin_path}'}
|
|
487
|
+
|
|
488
|
+
if not config_file.exists():
|
|
489
|
+
return {'success': False, 'error': f'config.yaml not found at: {config_file}'}
|
|
490
|
+
|
|
491
|
+
errors = []
|
|
492
|
+
warnings = []
|
|
493
|
+
|
|
494
|
+
# Check YAML syntax
|
|
495
|
+
try:
|
|
496
|
+
with open(config_file) as f:
|
|
497
|
+
raw_config = yaml.safe_load(f)
|
|
498
|
+
except yaml.YAMLError as e:
|
|
499
|
+
return {'success': False, 'valid': False, 'errors': [f'YAML syntax error: {e}']}
|
|
500
|
+
|
|
501
|
+
# Check required fields
|
|
502
|
+
if not raw_config:
|
|
503
|
+
errors.append('config.yaml is empty')
|
|
504
|
+
else:
|
|
505
|
+
if 'code' not in raw_config:
|
|
506
|
+
errors.append("Missing required field: 'code'")
|
|
507
|
+
if 'name' not in raw_config:
|
|
508
|
+
warnings.append("Missing recommended field: 'name'")
|
|
509
|
+
if 'version' not in raw_config:
|
|
510
|
+
warnings.append("Missing recommended field: 'version'")
|
|
511
|
+
if 'actions' not in raw_config or not raw_config.get('actions'):
|
|
512
|
+
errors.append('No actions defined')
|
|
513
|
+
|
|
514
|
+
# Try to load as a full plugin
|
|
515
|
+
try:
|
|
516
|
+
discovery = PluginDiscovery.from_path(plugin_path)
|
|
517
|
+
actions = discovery.list_actions()
|
|
518
|
+
|
|
519
|
+
# Validate each action
|
|
520
|
+
for action_name in actions:
|
|
521
|
+
try:
|
|
522
|
+
discovery.get_action_config(action_name)
|
|
523
|
+
except Exception as e:
|
|
524
|
+
errors.append(f"Action '{action_name}' config error: {e}")
|
|
525
|
+
|
|
526
|
+
try:
|
|
527
|
+
discovery.get_action_params_model(action_name)
|
|
528
|
+
except Exception as e:
|
|
529
|
+
warnings.append(f"Action '{action_name}' params model warning: {e}")
|
|
530
|
+
|
|
531
|
+
except Exception as e:
|
|
532
|
+
errors.append(f'Plugin discovery failed: {e}')
|
|
533
|
+
|
|
534
|
+
return {
|
|
535
|
+
'success': True,
|
|
536
|
+
'valid': len(errors) == 0,
|
|
537
|
+
'path': str(plugin_path),
|
|
538
|
+
'errors': errors,
|
|
539
|
+
'warnings': warnings,
|
|
540
|
+
'config': raw_config,
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
|
|
544
|
+
@mcp.tool()
|
|
545
|
+
def publish_plugin(path: str, version: str | None = None) -> dict:
|
|
546
|
+
"""Publish a local plugin to the registry.
|
|
547
|
+
|
|
548
|
+
Args:
|
|
549
|
+
path: Path to the plugin directory
|
|
550
|
+
version: Version string (overrides config.yaml version if provided)
|
|
551
|
+
|
|
552
|
+
Returns:
|
|
553
|
+
Published plugin release details
|
|
554
|
+
"""
|
|
555
|
+
from pathlib import Path
|
|
556
|
+
|
|
557
|
+
from synapse_sdk.plugins.discovery import PluginDiscovery
|
|
558
|
+
|
|
559
|
+
config = _get_config()
|
|
560
|
+
client = config.get_agent_client()
|
|
561
|
+
|
|
562
|
+
if not client:
|
|
563
|
+
return {
|
|
564
|
+
'success': False,
|
|
565
|
+
'error': 'No agent configured for current environment.',
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
plugin_path = Path(path).expanduser().resolve()
|
|
569
|
+
if not plugin_path.exists():
|
|
570
|
+
return {'success': False, 'error': f'Path not found: {plugin_path}'}
|
|
571
|
+
|
|
572
|
+
# Validate first
|
|
573
|
+
validation = validate_plugin_config(str(plugin_path))
|
|
574
|
+
if not validation.get('valid', False):
|
|
575
|
+
return {
|
|
576
|
+
'success': False,
|
|
577
|
+
'error': 'Plugin validation failed',
|
|
578
|
+
'validation_errors': validation.get('errors', []),
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
try:
|
|
582
|
+
discovery = PluginDiscovery.from_path(plugin_path)
|
|
583
|
+
plugin_config = discovery.config
|
|
584
|
+
|
|
585
|
+
# Use provided version or fall back to config
|
|
586
|
+
publish_version = version or plugin_config.version
|
|
587
|
+
if not publish_version:
|
|
588
|
+
return {
|
|
589
|
+
'success': False,
|
|
590
|
+
'error': 'No version specified. Provide version parameter or set in config.yaml',
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
result = client.publish_plugin_release(
|
|
594
|
+
plugin_path=str(plugin_path),
|
|
595
|
+
version=publish_version,
|
|
596
|
+
)
|
|
597
|
+
|
|
598
|
+
return {
|
|
599
|
+
'success': True,
|
|
600
|
+
'message': f'Plugin "{plugin_config.code}" version {publish_version} published',
|
|
601
|
+
'release': result,
|
|
602
|
+
}
|
|
603
|
+
except Exception as e:
|
|
604
|
+
return {'success': False, 'error': str(e)}
|
|
605
|
+
|
|
606
|
+
|
|
607
|
+
# =============================================================================
|
|
608
|
+
# Execution Tools
|
|
609
|
+
# =============================================================================
|
|
610
|
+
|
|
611
|
+
|
|
612
|
+
@mcp.tool()
|
|
613
|
+
def run_plugin(plugin: str, action: str, params: dict | None = None) -> dict:
|
|
614
|
+
"""Run a published plugin action via the agent.
|
|
615
|
+
|
|
616
|
+
Args:
|
|
617
|
+
plugin: Plugin code or 'code:version' string
|
|
618
|
+
action: Action name to execute
|
|
619
|
+
params: Parameters to pass to the action
|
|
620
|
+
|
|
621
|
+
Returns:
|
|
622
|
+
Execution result or job ID for async execution
|
|
623
|
+
"""
|
|
624
|
+
config = _get_config()
|
|
625
|
+
client = config.get_agent_client()
|
|
626
|
+
|
|
627
|
+
if not client:
|
|
628
|
+
return {
|
|
629
|
+
'success': False,
|
|
630
|
+
'error': 'No agent configured for current environment.',
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
try:
|
|
634
|
+
result = client.run_plugin_release(
|
|
635
|
+
lookup=plugin,
|
|
636
|
+
action=action,
|
|
637
|
+
params=params or {},
|
|
638
|
+
)
|
|
639
|
+
return {'success': True, 'result': result}
|
|
640
|
+
except Exception as e:
|
|
641
|
+
return {'success': False, 'error': str(e)}
|
|
642
|
+
|
|
643
|
+
|
|
644
|
+
@mcp.tool()
|
|
645
|
+
def run_debug_plugin(
|
|
646
|
+
path: str,
|
|
647
|
+
action: str,
|
|
648
|
+
params: dict | None = None,
|
|
649
|
+
) -> dict:
|
|
650
|
+
"""Run a local plugin for debugging via the agent.
|
|
651
|
+
|
|
652
|
+
This uploads the plugin code temporarily and runs it on the agent.
|
|
653
|
+
|
|
654
|
+
Args:
|
|
655
|
+
path: Path to the local plugin directory
|
|
656
|
+
action: Action name to execute
|
|
657
|
+
params: Parameters to pass to the action
|
|
658
|
+
|
|
659
|
+
Returns:
|
|
660
|
+
Execution result
|
|
661
|
+
"""
|
|
662
|
+
from pathlib import Path
|
|
663
|
+
|
|
664
|
+
config = _get_config()
|
|
665
|
+
client = config.get_agent_client()
|
|
666
|
+
|
|
667
|
+
if not client:
|
|
668
|
+
return {
|
|
669
|
+
'success': False,
|
|
670
|
+
'error': 'No agent configured for current environment.',
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
plugin_path = Path(path).expanduser().resolve()
|
|
674
|
+
if not plugin_path.exists():
|
|
675
|
+
return {'success': False, 'error': f'Path not found: {plugin_path}'}
|
|
676
|
+
|
|
677
|
+
try:
|
|
678
|
+
result = client.run_debug_plugin_release(
|
|
679
|
+
action=action,
|
|
680
|
+
params=params or {},
|
|
681
|
+
plugin_path=str(plugin_path),
|
|
682
|
+
)
|
|
683
|
+
return {'success': True, 'result': result}
|
|
684
|
+
except Exception as e:
|
|
685
|
+
return {'success': False, 'error': str(e)}
|
|
686
|
+
|
|
687
|
+
|
|
688
|
+
@mcp.tool()
|
|
689
|
+
def run_local_plugin(
|
|
690
|
+
path: str,
|
|
691
|
+
action: str,
|
|
692
|
+
params: dict | None = None,
|
|
693
|
+
mode: str = 'local',
|
|
694
|
+
) -> dict:
|
|
695
|
+
"""Run a local plugin directly (without agent).
|
|
696
|
+
|
|
697
|
+
Args:
|
|
698
|
+
path: Path to the local plugin directory
|
|
699
|
+
action: Action name to execute
|
|
700
|
+
params: Parameters to pass to the action
|
|
701
|
+
mode: Execution mode - 'local' (in-process), 'task' (Ray Actor), or 'job' (Ray Job)
|
|
702
|
+
|
|
703
|
+
Returns:
|
|
704
|
+
Execution result
|
|
705
|
+
"""
|
|
706
|
+
from pathlib import Path
|
|
707
|
+
|
|
708
|
+
from synapse_sdk.plugins.runner import run_plugin as sdk_run_plugin
|
|
709
|
+
|
|
710
|
+
plugin_path = Path(path).expanduser().resolve()
|
|
711
|
+
if not plugin_path.exists():
|
|
712
|
+
return {'success': False, 'error': f'Path not found: {plugin_path}'}
|
|
713
|
+
|
|
714
|
+
if mode not in ('local', 'task', 'job'):
|
|
715
|
+
return {'success': False, 'error': f'Invalid mode: {mode}. Use local, task, or job.'}
|
|
716
|
+
|
|
717
|
+
try:
|
|
718
|
+
result = sdk_run_plugin(
|
|
719
|
+
plugin_code=str(plugin_path),
|
|
720
|
+
action=action,
|
|
721
|
+
params=params or {},
|
|
722
|
+
mode=mode,
|
|
723
|
+
)
|
|
724
|
+
return {'success': True, 'result': result, 'mode': mode}
|
|
725
|
+
except Exception as e:
|
|
726
|
+
return {'success': False, 'error': str(e)}
|
|
727
|
+
|
|
728
|
+
|
|
729
|
+
# =============================================================================
|
|
730
|
+
# Jobs & Logs Tools
|
|
731
|
+
# =============================================================================
|
|
732
|
+
|
|
733
|
+
|
|
734
|
+
@mcp.tool()
|
|
735
|
+
def list_jobs(limit: int = 20, status: str | None = None) -> dict:
|
|
736
|
+
"""List jobs from the current environment.
|
|
737
|
+
|
|
738
|
+
Args:
|
|
739
|
+
limit: Maximum number of jobs to return
|
|
740
|
+
status: Filter by status (e.g., 'RUNNING', 'SUCCEEDED', 'FAILED', 'STOPPED', 'PENDING')
|
|
741
|
+
|
|
742
|
+
Returns:
|
|
743
|
+
List of jobs
|
|
744
|
+
"""
|
|
745
|
+
config = _get_config()
|
|
746
|
+
client = config.get_backend_client()
|
|
747
|
+
|
|
748
|
+
if not client:
|
|
749
|
+
return {
|
|
750
|
+
'success': False,
|
|
751
|
+
'error': 'No backend configured for current environment.',
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
env = config.get_active_environment()
|
|
755
|
+
if not env or not env.agent_id:
|
|
756
|
+
return {
|
|
757
|
+
'success': False,
|
|
758
|
+
'error': 'No agent selected. Use list_agents() and select_agent() first.',
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
try:
|
|
762
|
+
params = {'limit': limit, 'agent': env.agent_id}
|
|
763
|
+
if status:
|
|
764
|
+
params['status'] = status.lower()
|
|
765
|
+
|
|
766
|
+
result = client.list_jobs(params=params)
|
|
767
|
+
|
|
768
|
+
# Backend returns paginated response
|
|
769
|
+
jobs = result.get('results', []) if isinstance(result, dict) else result
|
|
770
|
+
|
|
771
|
+
return {'success': True, 'jobs': jobs, 'count': len(jobs)}
|
|
772
|
+
except Exception as e:
|
|
773
|
+
return {'success': False, 'error': str(e)}
|
|
774
|
+
|
|
775
|
+
|
|
776
|
+
@mcp.tool()
|
|
777
|
+
def get_job(job_id: str) -> dict:
|
|
778
|
+
"""Get details of a specific job.
|
|
779
|
+
|
|
780
|
+
Args:
|
|
781
|
+
job_id: The job ID (integer or UUID string)
|
|
782
|
+
|
|
783
|
+
Returns:
|
|
784
|
+
Job details including status, timestamps, and metadata
|
|
785
|
+
"""
|
|
786
|
+
config = _get_config()
|
|
787
|
+
client = config.get_backend_client()
|
|
788
|
+
|
|
789
|
+
if not client:
|
|
790
|
+
return {
|
|
791
|
+
'success': False,
|
|
792
|
+
'error': 'No backend configured for current environment.',
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
try:
|
|
796
|
+
job = client.get_job(job_id)
|
|
797
|
+
return {'success': True, 'job': job}
|
|
798
|
+
except Exception as e:
|
|
799
|
+
return {'success': False, 'error': str(e)}
|
|
800
|
+
|
|
801
|
+
|
|
802
|
+
@mcp.tool()
|
|
803
|
+
def get_job_logs(job_id: str, lines: int = 100) -> dict:
|
|
804
|
+
"""Get logs from a job.
|
|
805
|
+
|
|
806
|
+
Args:
|
|
807
|
+
job_id: The job ID (integer or UUID string)
|
|
808
|
+
lines: Number of log lines to return (tail)
|
|
809
|
+
|
|
810
|
+
Returns:
|
|
811
|
+
Job logs
|
|
812
|
+
"""
|
|
813
|
+
config = _get_config()
|
|
814
|
+
client = config.get_backend_client()
|
|
815
|
+
|
|
816
|
+
if not client:
|
|
817
|
+
return {
|
|
818
|
+
'success': False,
|
|
819
|
+
'error': 'No backend configured for current environment.',
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
try:
|
|
823
|
+
result = client.list_job_console_logs(job_id)
|
|
824
|
+
|
|
825
|
+
# Extract log entries from response
|
|
826
|
+
log_entries = result.get('results', []) if isinstance(result, dict) else result
|
|
827
|
+
|
|
828
|
+
# Format logs as text
|
|
829
|
+
log_lines = []
|
|
830
|
+
for entry in log_entries:
|
|
831
|
+
if isinstance(entry, dict):
|
|
832
|
+
message = entry.get('message', entry.get('log', str(entry)))
|
|
833
|
+
log_lines.append(message)
|
|
834
|
+
else:
|
|
835
|
+
log_lines.append(str(entry))
|
|
836
|
+
|
|
837
|
+
# Return last N lines
|
|
838
|
+
if len(log_lines) > lines:
|
|
839
|
+
log_lines = log_lines[-lines:]
|
|
840
|
+
|
|
841
|
+
logs = '\n'.join(log_lines)
|
|
842
|
+
return {'success': True, 'logs': logs, 'job_id': job_id}
|
|
843
|
+
except Exception as e:
|
|
844
|
+
return {'success': False, 'error': str(e)}
|
|
845
|
+
|
|
846
|
+
|
|
847
|
+
@mcp.tool()
|
|
848
|
+
def stop_job(job_id: str) -> dict:
|
|
849
|
+
"""Stop a running job.
|
|
850
|
+
|
|
851
|
+
Args:
|
|
852
|
+
job_id: The job ID (integer or UUID string) to stop
|
|
853
|
+
|
|
854
|
+
Returns:
|
|
855
|
+
Stop operation result
|
|
856
|
+
"""
|
|
857
|
+
config = _get_config()
|
|
858
|
+
client = config.get_backend_client()
|
|
859
|
+
|
|
860
|
+
if not client:
|
|
861
|
+
return {
|
|
862
|
+
'success': False,
|
|
863
|
+
'error': 'No backend configured for current environment.',
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
try:
|
|
867
|
+
# Update job status to stopped
|
|
868
|
+
result = client.update_job(int(job_id), {'status': 'stopped'})
|
|
869
|
+
return {'success': True, 'result': result, 'job_id': job_id}
|
|
870
|
+
except Exception as e:
|
|
871
|
+
return {'success': False, 'error': str(e)}
|
|
872
|
+
|
|
873
|
+
|
|
874
|
+
# =============================================================================
|
|
875
|
+
# Deployment Tools
|
|
876
|
+
# =============================================================================
|
|
877
|
+
|
|
878
|
+
|
|
879
|
+
@mcp.tool()
|
|
880
|
+
def list_serve_applications() -> dict:
|
|
881
|
+
"""List deployed Ray Serve applications.
|
|
882
|
+
|
|
883
|
+
Returns:
|
|
884
|
+
List of serve applications
|
|
885
|
+
"""
|
|
886
|
+
config = _get_config()
|
|
887
|
+
client = config.get_backend_client()
|
|
888
|
+
|
|
889
|
+
if not client:
|
|
890
|
+
return {
|
|
891
|
+
'success': False,
|
|
892
|
+
'error': 'No backend configured for current environment.',
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
try:
|
|
896
|
+
result = client.list_serve_applications()
|
|
897
|
+
|
|
898
|
+
# Backend returns paginated response
|
|
899
|
+
applications = result.get('results', []) if isinstance(result, dict) else result
|
|
900
|
+
|
|
901
|
+
return {'success': True, 'applications': applications}
|
|
902
|
+
except Exception as e:
|
|
903
|
+
return {'success': False, 'error': str(e)}
|
|
904
|
+
|
|
905
|
+
|
|
906
|
+
@mcp.tool()
|
|
907
|
+
def get_serve_application(name: str) -> dict:
|
|
908
|
+
"""Get details of a Ray Serve application.
|
|
909
|
+
|
|
910
|
+
Args:
|
|
911
|
+
name: Application name
|
|
912
|
+
|
|
913
|
+
Returns:
|
|
914
|
+
Application details
|
|
915
|
+
"""
|
|
916
|
+
config = _get_config()
|
|
917
|
+
client = config.get_agent_client()
|
|
918
|
+
|
|
919
|
+
if not client:
|
|
920
|
+
return {
|
|
921
|
+
'success': False,
|
|
922
|
+
'error': 'No agent configured for current environment.',
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
try:
|
|
926
|
+
application = client.get_serve_application(name)
|
|
927
|
+
return {'success': True, 'application': application}
|
|
928
|
+
except Exception as e:
|
|
929
|
+
return {'success': False, 'error': str(e)}
|
|
930
|
+
|
|
931
|
+
|
|
932
|
+
@mcp.tool()
|
|
933
|
+
def delete_serve_application(name: str) -> dict:
|
|
934
|
+
"""Delete a Ray Serve application.
|
|
935
|
+
|
|
936
|
+
Args:
|
|
937
|
+
name: Application name to delete
|
|
938
|
+
|
|
939
|
+
Returns:
|
|
940
|
+
Deletion result
|
|
941
|
+
"""
|
|
942
|
+
config = _get_config()
|
|
943
|
+
client = config.get_agent_client()
|
|
944
|
+
|
|
945
|
+
if not client:
|
|
946
|
+
return {
|
|
947
|
+
'success': False,
|
|
948
|
+
'error': 'No agent configured for current environment.',
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
try:
|
|
952
|
+
client.delete_serve_application(name)
|
|
953
|
+
return {'success': True, 'message': f'Application "{name}" deleted'}
|
|
954
|
+
except Exception as e:
|
|
955
|
+
return {'success': False, 'error': str(e)}
|
|
956
|
+
|
|
957
|
+
|
|
958
|
+
# =============================================================================
|
|
959
|
+
# Model Tools
|
|
960
|
+
# =============================================================================
|
|
961
|
+
|
|
962
|
+
|
|
963
|
+
@mcp.tool()
|
|
964
|
+
def list_models(limit: int = 20, offset: int = 0) -> dict:
|
|
965
|
+
"""List available models from the current environment.
|
|
966
|
+
|
|
967
|
+
Args:
|
|
968
|
+
limit: Maximum number of results
|
|
969
|
+
offset: Number of results to skip
|
|
970
|
+
|
|
971
|
+
Returns:
|
|
972
|
+
List of models
|
|
973
|
+
"""
|
|
974
|
+
config = _get_config()
|
|
975
|
+
client = config.get_backend_client()
|
|
976
|
+
|
|
977
|
+
if not client:
|
|
978
|
+
return {
|
|
979
|
+
'success': False,
|
|
980
|
+
'error': 'No backend configured for current environment.',
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
try:
|
|
984
|
+
result = client.list_models(params={'limit': limit, 'offset': offset})
|
|
985
|
+
return {'success': True, 'models': result}
|
|
986
|
+
except Exception as e:
|
|
987
|
+
return {'success': False, 'error': str(e)}
|
|
988
|
+
|
|
989
|
+
|
|
990
|
+
@mcp.tool()
|
|
991
|
+
def get_model(model_id: int) -> dict:
|
|
992
|
+
"""Get details of a specific model.
|
|
993
|
+
|
|
994
|
+
Args:
|
|
995
|
+
model_id: The model ID
|
|
996
|
+
|
|
997
|
+
Returns:
|
|
998
|
+
Model details
|
|
999
|
+
"""
|
|
1000
|
+
config = _get_config()
|
|
1001
|
+
client = config.get_backend_client()
|
|
1002
|
+
|
|
1003
|
+
if not client:
|
|
1004
|
+
return {
|
|
1005
|
+
'success': False,
|
|
1006
|
+
'error': 'No backend configured for current environment.',
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
try:
|
|
1010
|
+
model = client.get_model(model_id)
|
|
1011
|
+
return {'success': True, 'model': model}
|
|
1012
|
+
except Exception as e:
|
|
1013
|
+
return {'success': False, 'error': str(e)}
|
|
1014
|
+
|
|
1015
|
+
|
|
1016
|
+
# =============================================================================
|
|
1017
|
+
# MCP Resources
|
|
1018
|
+
# =============================================================================
|
|
1019
|
+
|
|
1020
|
+
|
|
1021
|
+
@mcp.resource('synapse://config')
|
|
1022
|
+
def resource_config() -> str:
|
|
1023
|
+
"""Current Synapse configuration (sanitized, no tokens)."""
|
|
1024
|
+
import json
|
|
1025
|
+
|
|
1026
|
+
config = _get_config()
|
|
1027
|
+
env_list = []
|
|
1028
|
+
|
|
1029
|
+
for name in config.list_environments():
|
|
1030
|
+
env = config.get_environment(name)
|
|
1031
|
+
if env:
|
|
1032
|
+
env_list.append({
|
|
1033
|
+
'name': name,
|
|
1034
|
+
'backend_url': env.backend_url,
|
|
1035
|
+
'agent_url': env.agent_url,
|
|
1036
|
+
'tenant': env.tenant,
|
|
1037
|
+
'has_access_token': bool(env.access_token),
|
|
1038
|
+
'has_agent_token': bool(env.agent_token),
|
|
1039
|
+
'plugin_paths': env.plugin_paths,
|
|
1040
|
+
})
|
|
1041
|
+
|
|
1042
|
+
return json.dumps(
|
|
1043
|
+
{
|
|
1044
|
+
'config_path': str(config.config_path),
|
|
1045
|
+
'default_environment': config._default_environment,
|
|
1046
|
+
'active_environment': config.get_active_environment_name(),
|
|
1047
|
+
'environments': env_list,
|
|
1048
|
+
},
|
|
1049
|
+
indent=2,
|
|
1050
|
+
)
|
|
1051
|
+
|
|
1052
|
+
|
|
1053
|
+
@mcp.resource('synapse://environments')
|
|
1054
|
+
def resource_environments() -> str:
|
|
1055
|
+
"""List of all configured environments."""
|
|
1056
|
+
import json
|
|
1057
|
+
|
|
1058
|
+
config = _get_config()
|
|
1059
|
+
environments = []
|
|
1060
|
+
|
|
1061
|
+
for name in config.list_environments():
|
|
1062
|
+
env = config.get_environment(name)
|
|
1063
|
+
if env:
|
|
1064
|
+
environments.append(env.to_dict())
|
|
1065
|
+
|
|
1066
|
+
return json.dumps(
|
|
1067
|
+
{
|
|
1068
|
+
'environments': environments,
|
|
1069
|
+
'active': config.get_active_environment_name(),
|
|
1070
|
+
},
|
|
1071
|
+
indent=2,
|
|
1072
|
+
)
|
|
1073
|
+
|
|
1074
|
+
|
|
1075
|
+
@mcp.resource('synapse://plugin/{path}/config')
|
|
1076
|
+
def resource_plugin_config(path: str) -> str:
|
|
1077
|
+
"""Plugin config.yaml content."""
|
|
1078
|
+
from pathlib import Path
|
|
1079
|
+
|
|
1080
|
+
import yaml
|
|
1081
|
+
|
|
1082
|
+
plugin_path = Path(path).expanduser().resolve()
|
|
1083
|
+
config_file = plugin_path / 'config.yaml'
|
|
1084
|
+
|
|
1085
|
+
if not config_file.exists():
|
|
1086
|
+
return f'Error: config.yaml not found at {config_file}'
|
|
1087
|
+
|
|
1088
|
+
with open(config_file) as f:
|
|
1089
|
+
return yaml.safe_dump(yaml.safe_load(f), default_flow_style=False)
|
|
1090
|
+
|
|
1091
|
+
|
|
1092
|
+
@mcp.resource('synapse://plugin/{path}/actions')
|
|
1093
|
+
def resource_plugin_actions(path: str) -> str:
|
|
1094
|
+
"""List of actions in a plugin."""
|
|
1095
|
+
import json
|
|
1096
|
+
from pathlib import Path
|
|
1097
|
+
|
|
1098
|
+
from synapse_sdk.plugins.discovery import PluginDiscovery
|
|
1099
|
+
|
|
1100
|
+
plugin_path = Path(path).expanduser().resolve()
|
|
1101
|
+
|
|
1102
|
+
if not plugin_path.exists():
|
|
1103
|
+
return f'Error: Path not found: {plugin_path}'
|
|
1104
|
+
|
|
1105
|
+
try:
|
|
1106
|
+
discovery = PluginDiscovery.from_path(plugin_path)
|
|
1107
|
+
actions = []
|
|
1108
|
+
|
|
1109
|
+
for action_name in discovery.list_actions():
|
|
1110
|
+
try:
|
|
1111
|
+
action_config = discovery.get_action_config(action_name)
|
|
1112
|
+
actions.append({
|
|
1113
|
+
'name': action_name,
|
|
1114
|
+
'description': action_config.description,
|
|
1115
|
+
'method': action_config.method.value if action_config.method else None,
|
|
1116
|
+
'entrypoint': action_config.entrypoint,
|
|
1117
|
+
})
|
|
1118
|
+
except Exception:
|
|
1119
|
+
actions.append({'name': action_name})
|
|
1120
|
+
|
|
1121
|
+
return json.dumps({'plugin': str(plugin_path), 'actions': actions}, indent=2)
|
|
1122
|
+
except Exception as e:
|
|
1123
|
+
return f'Error: {e}'
|
|
1124
|
+
|
|
1125
|
+
|
|
1126
|
+
@mcp.resource('synapse://plugin/{path}/action/{action}/schema')
|
|
1127
|
+
def resource_action_schema(path: str, action: str) -> str:
|
|
1128
|
+
"""Action parameter JSON schema."""
|
|
1129
|
+
import json
|
|
1130
|
+
from pathlib import Path
|
|
1131
|
+
|
|
1132
|
+
from synapse_sdk.plugins.discovery import PluginDiscovery
|
|
1133
|
+
|
|
1134
|
+
plugin_path = Path(path).expanduser().resolve()
|
|
1135
|
+
|
|
1136
|
+
if not plugin_path.exists():
|
|
1137
|
+
return f'Error: Path not found: {plugin_path}'
|
|
1138
|
+
|
|
1139
|
+
try:
|
|
1140
|
+
discovery = PluginDiscovery.from_path(plugin_path)
|
|
1141
|
+
|
|
1142
|
+
if not discovery.has_action(action):
|
|
1143
|
+
return f'Error: Action "{action}" not found. Available: {discovery.list_actions()}'
|
|
1144
|
+
|
|
1145
|
+
params_model = discovery.get_action_params_model(action)
|
|
1146
|
+
result_model = discovery.get_action_result_model(action)
|
|
1147
|
+
|
|
1148
|
+
schema = {
|
|
1149
|
+
'action': action,
|
|
1150
|
+
'params_schema': params_model.model_json_schema() if params_model else None,
|
|
1151
|
+
'result_schema': result_model.model_json_schema() if result_model else None,
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
return json.dumps(schema, indent=2)
|
|
1155
|
+
except Exception as e:
|
|
1156
|
+
return f'Error: {e}'
|
|
1157
|
+
|
|
1158
|
+
|
|
1159
|
+
# =============================================================================
|
|
1160
|
+
# MCP Prompts
|
|
1161
|
+
# =============================================================================
|
|
1162
|
+
|
|
1163
|
+
|
|
1164
|
+
@mcp.prompt()
|
|
1165
|
+
def debug_plugin(plugin_path: str, action: str = '') -> str:
|
|
1166
|
+
"""Guide through debugging a local plugin.
|
|
1167
|
+
|
|
1168
|
+
Args:
|
|
1169
|
+
plugin_path: Path to the local plugin directory
|
|
1170
|
+
action: Optional action name to focus on
|
|
1171
|
+
"""
|
|
1172
|
+
prompt = f"""You are helping debug a local Synapse plugin.
|
|
1173
|
+
|
|
1174
|
+
Plugin Path: {plugin_path}
|
|
1175
|
+
|
|
1176
|
+
Steps to debug:
|
|
1177
|
+
|
|
1178
|
+
1. First, discover the plugin to see available actions:
|
|
1179
|
+
Use the `discover_local_plugin` tool with path="{plugin_path}"
|
|
1180
|
+
|
|
1181
|
+
2. Validate the plugin configuration:
|
|
1182
|
+
Use the `validate_plugin_config` tool with path="{plugin_path}"
|
|
1183
|
+
|
|
1184
|
+
3. If there are validation errors, help fix them by examining the config.yaml
|
|
1185
|
+
|
|
1186
|
+
4. To test an action locally (without remote agent):
|
|
1187
|
+
Use the `run_local_plugin` tool with path="{plugin_path}", action="<action_name>", params={{...}}, mode="local"
|
|
1188
|
+
|
|
1189
|
+
5. To test via the remote agent (for debugging in prod-like environment):
|
|
1190
|
+
Use the `run_debug_plugin` tool with path="{plugin_path}", action="<action_name>", params={{...}}
|
|
1191
|
+
|
|
1192
|
+
6. Check job logs if execution fails:
|
|
1193
|
+
Use `list_jobs` to find recent jobs, then `get_job_logs` for details
|
|
1194
|
+
"""
|
|
1195
|
+
|
|
1196
|
+
if action:
|
|
1197
|
+
prompt += f"""
|
|
1198
|
+
Focus on action: {action}
|
|
1199
|
+
|
|
1200
|
+
Get the action schema:
|
|
1201
|
+
Use the `get_action_config` tool with path="{plugin_path}", action="{action}"
|
|
1202
|
+
"""
|
|
1203
|
+
|
|
1204
|
+
return prompt
|
|
1205
|
+
|
|
1206
|
+
|
|
1207
|
+
@mcp.prompt()
|
|
1208
|
+
def publish_plugin_workflow(plugin_path: str, version: str = '') -> str:
|
|
1209
|
+
"""Guide through publishing a plugin to the registry.
|
|
1210
|
+
|
|
1211
|
+
Args:
|
|
1212
|
+
plugin_path: Path to the local plugin directory
|
|
1213
|
+
version: Version to publish (optional)
|
|
1214
|
+
"""
|
|
1215
|
+
version_note = f'Version to publish: {version}' if version else 'Version: (will use config.yaml version)'
|
|
1216
|
+
|
|
1217
|
+
return f"""You are helping publish a Synapse plugin to the registry.
|
|
1218
|
+
|
|
1219
|
+
Plugin Path: {plugin_path}
|
|
1220
|
+
{version_note}
|
|
1221
|
+
|
|
1222
|
+
Pre-publish checklist:
|
|
1223
|
+
|
|
1224
|
+
1. Validate the plugin configuration:
|
|
1225
|
+
Use `validate_plugin_config` with path="{plugin_path}"
|
|
1226
|
+
- Ensure no errors (warnings are okay)
|
|
1227
|
+
- Verify 'code', 'version', and 'actions' are properly defined
|
|
1228
|
+
|
|
1229
|
+
2. Test the plugin locally:
|
|
1230
|
+
Use `run_local_plugin` with path="{plugin_path}" and test each action
|
|
1231
|
+
|
|
1232
|
+
3. Verify the current environment:
|
|
1233
|
+
Use `get_current_environment` to confirm you're publishing to the right registry
|
|
1234
|
+
|
|
1235
|
+
4. Publish the plugin:
|
|
1236
|
+
Use `publish_plugin` with path="{plugin_path}"{f', version="{version}"' if version else ''}
|
|
1237
|
+
|
|
1238
|
+
5. Verify the publication:
|
|
1239
|
+
Use `get_plugin_release` with the plugin code:version to confirm
|
|
1240
|
+
|
|
1241
|
+
Post-publish:
|
|
1242
|
+
- The plugin is now available in the registry
|
|
1243
|
+
- Others can run it using `run_plugin` with the plugin code
|
|
1244
|
+
"""
|
|
1245
|
+
|
|
1246
|
+
|
|
1247
|
+
@mcp.prompt()
|
|
1248
|
+
def diagnose_job(job_id: str) -> str:
|
|
1249
|
+
"""Help diagnose a failed or problematic job.
|
|
1250
|
+
|
|
1251
|
+
Args:
|
|
1252
|
+
job_id: The job ID to diagnose
|
|
1253
|
+
"""
|
|
1254
|
+
return f"""You are helping diagnose a Synapse job.
|
|
1255
|
+
|
|
1256
|
+
Job ID: {job_id}
|
|
1257
|
+
|
|
1258
|
+
Diagnostic steps:
|
|
1259
|
+
|
|
1260
|
+
1. Get job details:
|
|
1261
|
+
Use `get_job` with job_id="{job_id}"
|
|
1262
|
+
- Check status: PENDING, RUNNING, SUCCEEDED, FAILED, STOPPED
|
|
1263
|
+
- Note the plugin/action that was run
|
|
1264
|
+
- Check start/end times
|
|
1265
|
+
|
|
1266
|
+
2. Get job logs:
|
|
1267
|
+
Use `get_job_logs` with job_id="{job_id}", lines=200
|
|
1268
|
+
- Look for error messages, stack traces
|
|
1269
|
+
- Check for timeout issues
|
|
1270
|
+
- Look for resource constraints (OOM, CPU limits)
|
|
1271
|
+
|
|
1272
|
+
3. If the job is stuck RUNNING:
|
|
1273
|
+
- Check if it's actually making progress in logs
|
|
1274
|
+
- Consider using `stop_job` if it's hung
|
|
1275
|
+
|
|
1276
|
+
4. Common issues to check:
|
|
1277
|
+
- Plugin code errors (stack traces in logs)
|
|
1278
|
+
- Configuration errors (missing params, wrong types)
|
|
1279
|
+
- Resource limits (memory, CPU, GPU)
|
|
1280
|
+
- Network issues (can't reach external services)
|
|
1281
|
+
- Timeout (job took too long)
|
|
1282
|
+
|
|
1283
|
+
5. If you need to re-run with fixes:
|
|
1284
|
+
- For published plugins: use `run_plugin`
|
|
1285
|
+
- For local plugins: use `run_local_plugin` or `run_debug_plugin`
|
|
1286
|
+
"""
|
|
1287
|
+
|
|
1288
|
+
|
|
1289
|
+
@mcp.prompt()
|
|
1290
|
+
def setup_environment(env_name: str = 'prod') -> str:
|
|
1291
|
+
"""Guide through setting up a new Synapse environment.
|
|
1292
|
+
|
|
1293
|
+
Args:
|
|
1294
|
+
env_name: Name for the new environment (default: prod)
|
|
1295
|
+
"""
|
|
1296
|
+
return f"""You are helping set up a new Synapse environment.
|
|
1297
|
+
|
|
1298
|
+
Environment Name: {env_name}
|
|
1299
|
+
|
|
1300
|
+
Setup steps:
|
|
1301
|
+
|
|
1302
|
+
1. First, check existing environments:
|
|
1303
|
+
Use `list_environments` to see what's already configured
|
|
1304
|
+
|
|
1305
|
+
2. Gather the required information:
|
|
1306
|
+
- Backend URL: The Synapse API server URL (e.g., https://api.synapse.example.com)
|
|
1307
|
+
- Access Token: Your API access token
|
|
1308
|
+
- Tenant: Your tenant identifier (optional)
|
|
1309
|
+
|
|
1310
|
+
3. Add the environment:
|
|
1311
|
+
Use `add_environment` with:
|
|
1312
|
+
- name="{env_name}"
|
|
1313
|
+
- backend_url="<your_backend_url>"
|
|
1314
|
+
- access_token="<your_access_token>"
|
|
1315
|
+
- tenant="<your_tenant>" (if applicable)
|
|
1316
|
+
- set_as_default=True (if this should be the default)
|
|
1317
|
+
|
|
1318
|
+
4. Switch to the new environment:
|
|
1319
|
+
Use `switch_environment` with name="{env_name}"
|
|
1320
|
+
|
|
1321
|
+
5. Configure the agent (fetched from backend):
|
|
1322
|
+
- Use `list_agents` to see available agents
|
|
1323
|
+
- Use `select_agent(agent_id)` to choose one
|
|
1324
|
+
|
|
1325
|
+
6. Verify the connection:
|
|
1326
|
+
Use `get_current_environment` to check backend/agent status
|
|
1327
|
+
|
|
1328
|
+
7. Test basic functionality:
|
|
1329
|
+
- Use `list_plugin_releases` to verify backend connection
|
|
1330
|
+
- Use `list_jobs` to verify agent connection
|
|
1331
|
+
|
|
1332
|
+
Configuration file location: ~/.synapse/config.yaml
|
|
1333
|
+
"""
|
|
1334
|
+
|
|
1335
|
+
|
|
1336
|
+
# =============================================================================
|
|
1337
|
+
# Server Entry Point
|
|
1338
|
+
# =============================================================================
|
|
1339
|
+
|
|
1340
|
+
|
|
1341
|
+
def serve() -> None:
|
|
1342
|
+
"""Run the MCP server."""
|
|
1343
|
+
_log('Starting Synapse MCP server...')
|
|
1344
|
+
config = _get_config()
|
|
1345
|
+
_log(f'Config path: {config.config_path}')
|
|
1346
|
+
_log(f'Environments: {config.list_environments()}')
|
|
1347
|
+
_log(f'Active environment: {config.get_active_environment_name()}')
|
|
1348
|
+
mcp.run()
|
|
1349
|
+
|
|
1350
|
+
|
|
1351
|
+
if __name__ == '__main__':
|
|
1352
|
+
serve()
|