model-compose 0.1.7__tar.gz → 0.1.8__tar.gz
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.
- {model_compose-0.1.7/src/model_compose.egg-info → model_compose-0.1.8}/PKG-INFO +1 -1
- {model_compose-0.1.7 → model_compose-0.1.8}/pyproject.toml +1 -1
- {model_compose-0.1.7 → model_compose-0.1.8}/src/mindor/core/component/engine/shell.py +16 -5
- model_compose-0.1.8/src/mindor/core/controller/engine/mcp_server.py +158 -0
- {model_compose-0.1.7 → model_compose-0.1.8}/src/mindor/core/controller/webui/gradio.py +11 -11
- model_compose-0.1.8/src/mindor/core/gateway/engine/ssh_tunnel.py +25 -0
- model_compose-0.1.8/src/mindor/core/utils/ssh_client.py +4 -0
- {model_compose-0.1.7 → model_compose-0.1.8}/src/mindor/core/utils/template.py +26 -13
- {model_compose-0.1.7 → model_compose-0.1.8}/src/mindor/core/workflow/schema.py +83 -44
- {model_compose-0.1.7 → model_compose-0.1.8}/src/mindor/dsl/schema/gateway/gateway.py +2 -1
- {model_compose-0.1.7 → model_compose-0.1.8}/src/mindor/dsl/schema/gateway/impl/__init__.py +1 -0
- model_compose-0.1.8/src/mindor/dsl/schema/gateway/impl/ssh_tunnel.py +7 -0
- {model_compose-0.1.7 → model_compose-0.1.8}/src/mindor/dsl/schema/gateway/impl/types.py +1 -0
- {model_compose-0.1.7 → model_compose-0.1.8}/src/mindor/dsl/schema/workflow.py +11 -1
- {model_compose-0.1.7 → model_compose-0.1.8/src/model_compose.egg-info}/PKG-INFO +1 -1
- {model_compose-0.1.7 → model_compose-0.1.8}/src/model_compose.egg-info/SOURCES.txt +3 -0
- model_compose-0.1.7/src/mindor/core/controller/engine/mcp_server.py +0 -93
- {model_compose-0.1.7 → model_compose-0.1.8}/LICENSE +0 -0
- {model_compose-0.1.7 → model_compose-0.1.8}/README.md +0 -0
- {model_compose-0.1.7 → model_compose-0.1.8}/setup.cfg +0 -0
- {model_compose-0.1.7 → model_compose-0.1.8}/src/mindor/__init__.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.8}/src/mindor/cli/__init__.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.8}/src/mindor/cli/compose.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.8}/src/mindor/core/__init__.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.8}/src/mindor/core/component/__init__.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.8}/src/mindor/core/component/component.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.8}/src/mindor/core/component/engine/__init__.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.8}/src/mindor/core/component/engine/base.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.8}/src/mindor/core/component/engine/context.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.8}/src/mindor/core/component/engine/http_client.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.8}/src/mindor/core/component/engine/http_server.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.8}/src/mindor/core/component/engine/mcp_client.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.8}/src/mindor/core/component/engine/mcp_server.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.8}/src/mindor/core/compose/__init__.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.8}/src/mindor/core/compose/compose.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.8}/src/mindor/core/compose/manager.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.8}/src/mindor/core/controller/__init__.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.8}/src/mindor/core/controller/controller.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.8}/src/mindor/core/controller/engine/__init__.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.8}/src/mindor/core/controller/engine/base.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.8}/src/mindor/core/controller/engine/http_server.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.8}/src/mindor/core/controller/runner/__init__.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.8}/src/mindor/core/controller/runner/client.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.8}/src/mindor/core/controller/runner/http_client.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.8}/src/mindor/core/controller/runner/mcp_client.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.8}/src/mindor/core/controller/runner/runner.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.8}/src/mindor/core/controller/webui/__init__.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.8}/src/mindor/core/controller/webui/webui.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.8}/src/mindor/core/gateway/__init__.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.8}/src/mindor/core/gateway/engine/__init__.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.8}/src/mindor/core/gateway/engine/base.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.8}/src/mindor/core/gateway/engine/http_tunnel.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.8}/src/mindor/core/gateway/gateway.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.8}/src/mindor/core/listener/__init__.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.8}/src/mindor/core/listener/engine/__init__.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.8}/src/mindor/core/listener/engine/base.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.8}/src/mindor/core/listener/engine/http_callback.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.8}/src/mindor/core/listener/listener.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.8}/src/mindor/core/runtime/__init__.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.8}/src/mindor/core/runtime/env.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.8}/src/mindor/core/services/__init__.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.8}/src/mindor/core/services/async_service.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.8}/src/mindor/core/utils/__init__.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.8}/src/mindor/core/utils/expiring.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.8}/src/mindor/core/utils/http_client.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.8}/src/mindor/core/utils/http_request.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.8}/src/mindor/core/utils/image.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.8}/src/mindor/core/utils/mcp_client.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.8}/src/mindor/core/utils/streaming.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.8}/src/mindor/core/utils/time.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.8}/src/mindor/core/utils/workqueue.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.8}/src/mindor/core/workflow/__init__.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.8}/src/mindor/core/workflow/context.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.8}/src/mindor/core/workflow/job.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.8}/src/mindor/core/workflow/workflow.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.8}/src/mindor/dsl/__init__.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.8}/src/mindor/dsl/loader.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.8}/src/mindor/dsl/schema/__init__.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.8}/src/mindor/dsl/schema/action/__init__.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.8}/src/mindor/dsl/schema/action/action.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.8}/src/mindor/dsl/schema/action/impl/__init__.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.8}/src/mindor/dsl/schema/action/impl/common.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.8}/src/mindor/dsl/schema/action/impl/http_client.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.8}/src/mindor/dsl/schema/action/impl/http_server.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.8}/src/mindor/dsl/schema/action/impl/mcp_client.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.8}/src/mindor/dsl/schema/action/impl/mcp_server.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.8}/src/mindor/dsl/schema/action/impl/shell.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.8}/src/mindor/dsl/schema/component/__init__.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.8}/src/mindor/dsl/schema/component/component.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.8}/src/mindor/dsl/schema/component/impl/__init__.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.8}/src/mindor/dsl/schema/component/impl/common.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.8}/src/mindor/dsl/schema/component/impl/http_client.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.8}/src/mindor/dsl/schema/component/impl/http_server.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.8}/src/mindor/dsl/schema/component/impl/mcp_client.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.8}/src/mindor/dsl/schema/component/impl/mcp_server.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.8}/src/mindor/dsl/schema/component/impl/shell.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.8}/src/mindor/dsl/schema/component/impl/types.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.8}/src/mindor/dsl/schema/compose.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.8}/src/mindor/dsl/schema/controller/__init__.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.8}/src/mindor/dsl/schema/controller/controller.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.8}/src/mindor/dsl/schema/controller/impl/__init__.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.8}/src/mindor/dsl/schema/controller/impl/common.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.8}/src/mindor/dsl/schema/controller/impl/http_server.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.8}/src/mindor/dsl/schema/controller/impl/mcp_server.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.8}/src/mindor/dsl/schema/controller/impl/types.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.8}/src/mindor/dsl/schema/controller/impl/webui.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.8}/src/mindor/dsl/schema/gateway/__init__.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.8}/src/mindor/dsl/schema/gateway/impl/common.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.8}/src/mindor/dsl/schema/gateway/impl/http_tunnel.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.8}/src/mindor/dsl/schema/listener/__init__.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.8}/src/mindor/dsl/schema/listener/impl/__init__.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.8}/src/mindor/dsl/schema/listener/impl/common.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.8}/src/mindor/dsl/schema/listener/impl/http_callback.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.8}/src/mindor/dsl/schema/listener/impl/types.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.8}/src/mindor/dsl/schema/listener/listener.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.8}/src/mindor/dsl/schema/runtime/__init__.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.8}/src/mindor/dsl/schema/runtime/impl/__init__.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.8}/src/mindor/dsl/schema/runtime/impl/common.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.8}/src/mindor/dsl/schema/runtime/impl/docker.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.8}/src/mindor/dsl/schema/runtime/impl/types.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.8}/src/mindor/dsl/schema/runtime/runtime.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.8}/src/model_compose.egg-info/dependency_links.txt +0 -0
- {model_compose-0.1.7 → model_compose-0.1.8}/src/model_compose.egg-info/entry_points.txt +0 -0
- {model_compose-0.1.7 → model_compose-0.1.8}/src/model_compose.egg-info/requires.txt +0 -0
- {model_compose-0.1.7 → model_compose-0.1.8}/src/model_compose.egg-info/top_level.txt +0 -0
- {model_compose-0.1.7 → model_compose-0.1.8}/tests/test_cli.py +0 -0
|
@@ -3,6 +3,7 @@ from mindor.dsl.schema.component import ShellComponentConfig
|
|
|
3
3
|
from mindor.dsl.schema.action import ActionConfig, ShellActionConfig
|
|
4
4
|
from .base import ComponentEngine, ComponentType, ComponentEngineMap, ActionConfig
|
|
5
5
|
from .context import ComponentContext
|
|
6
|
+
from asyncio.subprocess import Process
|
|
6
7
|
import asyncio, os
|
|
7
8
|
|
|
8
9
|
class ShellAction:
|
|
@@ -32,16 +33,26 @@ class ShellAction:
|
|
|
32
33
|
try:
|
|
33
34
|
stdout, stderr = await asyncio.wait_for(process.communicate(), timeout=timeout)
|
|
34
35
|
except asyncio.TimeoutError:
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
raise RuntimeError(f"Command timed out: {' '.join(command)}")
|
|
36
|
+
if await self._kill_process(process):
|
|
37
|
+
raise RuntimeError(f"Command timed out: {' '.join(command)}")
|
|
38
38
|
|
|
39
39
|
return {
|
|
40
|
-
"exit_code": process.returncode,
|
|
41
40
|
"stdout": stdout.decode().strip(),
|
|
42
|
-
"stderr": stderr.decode().strip()
|
|
41
|
+
"stderr": stderr.decode().strip(),
|
|
42
|
+
"exit_code": process.returncode
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
+
async def _kill_process(self, process: Process) -> bool:
|
|
46
|
+
if process.returncode is None:
|
|
47
|
+
process.kill()
|
|
48
|
+
try:
|
|
49
|
+
await process.wait()
|
|
50
|
+
except Exception as e:
|
|
51
|
+
pass
|
|
52
|
+
return True
|
|
53
|
+
else:
|
|
54
|
+
return False
|
|
55
|
+
|
|
45
56
|
def _resolve_working_directory(self) -> str:
|
|
46
57
|
working_dir = self.config.working_dir
|
|
47
58
|
if working_dir:
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
from typing import Type, Union, Literal, Optional, Dict, List, Tuple, Set, Annotated, Callable, Awaitable, Any
|
|
2
|
+
from mindor.dsl.schema.controller import McpServerControllerConfig
|
|
3
|
+
from mindor.dsl.schema.component import ComponentConfig
|
|
4
|
+
from mindor.dsl.schema.listener import ListenerConfig
|
|
5
|
+
from mindor.dsl.schema.gateway import GatewayConfig
|
|
6
|
+
from mindor.dsl.schema.workflow import WorkflowConfig, WorkflowVariableConfig
|
|
7
|
+
from mindor.core.workflow.schema import WorkflowSchema, create_workflow_schema
|
|
8
|
+
from mindor.core.utils.streaming import StreamResource
|
|
9
|
+
from .base import ControllerEngine, ControllerType, ControllerEngineMap, TaskState
|
|
10
|
+
from mcp.server.fastmcp.server import FastMCP
|
|
11
|
+
from mcp.types import TextContent, ImageContent, AudioContent
|
|
12
|
+
import uvicorn, re, json
|
|
13
|
+
|
|
14
|
+
_invalid_function_chars_regex = re.compile(r"[^a-zA-Z0-9_]")
|
|
15
|
+
|
|
16
|
+
class WorkflowToolGenerator():
|
|
17
|
+
def generate(self, workflow_id: str, workflow: WorkflowSchema, runner: Callable[[Optional[str], Any], Awaitable[Any]]) -> Tuple[Callable[[Any], Awaitable[Any]], str]:
|
|
18
|
+
async def _run_workflow(input: Any, workflow_id=workflow_id, variables=workflow.output) -> Any:
|
|
19
|
+
return await self._build_output_value(await runner(workflow_id, input), variables)
|
|
20
|
+
|
|
21
|
+
async def _build_input_value(arguments, variables=workflow.input) -> Any:
|
|
22
|
+
return await self._build_input_value(arguments, variables)
|
|
23
|
+
|
|
24
|
+
safe_workflow_id = re.sub(_invalid_function_chars_regex, "_", workflow_id)
|
|
25
|
+
arguments = ",".join([ variable.name or "input" for variable in workflow.input ])
|
|
26
|
+
code = f"async def _run_workflow_{safe_workflow_id}({arguments}): return await _run_workflow(await _build_input_value([{arguments}]))"
|
|
27
|
+
context = { "_run_workflow": _run_workflow, "_build_input_value": _build_input_value }
|
|
28
|
+
exec(compile(code, f"<string>", "exec"), context)
|
|
29
|
+
|
|
30
|
+
return (context[f"_run_workflow_{safe_workflow_id}"], self._generate_description(workflow))
|
|
31
|
+
|
|
32
|
+
async def _build_input_value(self, arguments: List[Any], variables: List[WorkflowVariableConfig]) -> Any:
|
|
33
|
+
input: Dict[str, Any] = {}
|
|
34
|
+
|
|
35
|
+
for value, variable in zip(arguments, variables):
|
|
36
|
+
type, subtype, format = variable.type.value, variable.subtype, variable.format.value if variable.format else None
|
|
37
|
+
input[variable.name or "input"] = await self._convert_input_value(value, type, subtype, format, variable.default)
|
|
38
|
+
|
|
39
|
+
return input
|
|
40
|
+
|
|
41
|
+
async def _convert_input_value(self, value: Any, type: str, subtype: Optional[str], format: Optional[str], default: Optional[Any]) -> Any:
|
|
42
|
+
if type in [ "image", "audio", "video", "file" ]:
|
|
43
|
+
if format and format != "path":
|
|
44
|
+
pass
|
|
45
|
+
|
|
46
|
+
return value if value != "" else None
|
|
47
|
+
|
|
48
|
+
async def _build_output_value(self, state: TaskState, variables: List[WorkflowVariableConfig]) -> List[Union[TextContent, ImageContent, AudioContent]]:
|
|
49
|
+
output: List[Union[TextContent, ImageContent]] = []
|
|
50
|
+
|
|
51
|
+
if state.output:
|
|
52
|
+
for variable in variables:
|
|
53
|
+
type, subtype, format = variable.type.value, variable.subtype, variable.format.value if variable.format else None
|
|
54
|
+
output.append(await self._convert_output_value(state.output, type, subtype, format))
|
|
55
|
+
|
|
56
|
+
return output
|
|
57
|
+
|
|
58
|
+
async def _convert_output_value(self, value: Any, type: str, subtype: Optional[str], format: Optional[str]) -> Union[TextContent, ImageContent, AudioContent]:
|
|
59
|
+
if type in [ "image", "audio", "video", "file" ]:
|
|
60
|
+
if format and format != "path":
|
|
61
|
+
pass
|
|
62
|
+
|
|
63
|
+
if isinstance(value, (dict, list)):
|
|
64
|
+
return TextContent(type="text", text=json.dumps(value))
|
|
65
|
+
|
|
66
|
+
return TextContent(type="text", text=str(value))
|
|
67
|
+
|
|
68
|
+
def _generate_description(self, workflow: WorkflowSchema) -> str:
|
|
69
|
+
lines = []
|
|
70
|
+
|
|
71
|
+
lines.append(workflow.description or workflow.title or "")
|
|
72
|
+
lines.append("")
|
|
73
|
+
lines.append("Args:")
|
|
74
|
+
|
|
75
|
+
for variable in workflow.input:
|
|
76
|
+
name, type = variable.name or "input", self._get_docstring_type(variable)
|
|
77
|
+
description = variable.get_annotation_value("description") or ""
|
|
78
|
+
lines.append(f" {name} ({type}): {description}")
|
|
79
|
+
|
|
80
|
+
lines.append("Returns:")
|
|
81
|
+
|
|
82
|
+
if len(workflow.output) == 1 and not workflow.output[0].name:
|
|
83
|
+
variable = workflow.output[0]
|
|
84
|
+
name, type = variable.name or "", self._get_docstring_type(variable)
|
|
85
|
+
lines.append(f" {type}: {description}")
|
|
86
|
+
else:
|
|
87
|
+
lines.append(f" dict: results containing:")
|
|
88
|
+
for variable in workflow.output:
|
|
89
|
+
name, type = variable.name or "", self._get_docstring_type(variable)
|
|
90
|
+
description = variable.get_annotation_value("description") or ""
|
|
91
|
+
lines.append(f" - {name} ({type}): {description}")
|
|
92
|
+
|
|
93
|
+
return "\n".join(lines)
|
|
94
|
+
|
|
95
|
+
def _get_docstring_type(self, variable: WorkflowVariableConfig) -> str:
|
|
96
|
+
type, subtype, format = variable.type.value, variable.subtype, variable.format.value if variable.format else None
|
|
97
|
+
|
|
98
|
+
if type == "object[]":
|
|
99
|
+
return "list[dict]"
|
|
100
|
+
|
|
101
|
+
if type == "number":
|
|
102
|
+
return "float"
|
|
103
|
+
|
|
104
|
+
if type == "integer":
|
|
105
|
+
return "int"
|
|
106
|
+
|
|
107
|
+
return "str"
|
|
108
|
+
|
|
109
|
+
class McpServerController(ControllerEngine):
|
|
110
|
+
def __init__(
|
|
111
|
+
self,
|
|
112
|
+
config: McpServerControllerConfig,
|
|
113
|
+
components: Dict[str, ComponentConfig],
|
|
114
|
+
listeners: List[ListenerConfig],
|
|
115
|
+
gateways: List[GatewayConfig],
|
|
116
|
+
workflows: Dict[str, WorkflowConfig],
|
|
117
|
+
env: Dict[str, str],
|
|
118
|
+
daemon: bool
|
|
119
|
+
):
|
|
120
|
+
super().__init__(config, components, listeners, gateways, workflows, env, daemon)
|
|
121
|
+
|
|
122
|
+
self.server: Optional[uvicorn.Server] = None
|
|
123
|
+
self.app: FastMCP = FastMCP(self.config.name, settings={
|
|
124
|
+
"streamable_http_path": self.config.base_path
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
self._configure_tools()
|
|
128
|
+
|
|
129
|
+
def _configure_tools(self) -> None:
|
|
130
|
+
schema = create_workflow_schema(self.workflows, self.components)
|
|
131
|
+
|
|
132
|
+
for workflow_id, workflow in schema.items():
|
|
133
|
+
fn, description = WorkflowToolGenerator().generate(workflow_id, workflow, self._run_workflow_as_tool)
|
|
134
|
+
self.app.add_tool(
|
|
135
|
+
fn=fn,
|
|
136
|
+
name=workflow.name or workflow_id,
|
|
137
|
+
title=workflow.title,
|
|
138
|
+
description=description,
|
|
139
|
+
annotations=None
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
async def _run_workflow_as_tool(self, workflow_id: Optional[str], input: Any) -> TaskState:
|
|
143
|
+
return await self.run_workflow(workflow_id, input, wait_for_completion=True)
|
|
144
|
+
|
|
145
|
+
async def _serve(self) -> None:
|
|
146
|
+
self.server = uvicorn.Server(uvicorn.Config(
|
|
147
|
+
self.app.streamable_http_app(),
|
|
148
|
+
host=self.config.host,
|
|
149
|
+
port=self.config.port,
|
|
150
|
+
log_level="info"
|
|
151
|
+
))
|
|
152
|
+
await self.server.serve()
|
|
153
|
+
|
|
154
|
+
async def _shutdown(self) -> None:
|
|
155
|
+
if self.server:
|
|
156
|
+
self.server.should_exit = True
|
|
157
|
+
|
|
158
|
+
ControllerEngineMap[ControllerType.MCP_SERVER] = McpServerController
|
|
@@ -62,18 +62,15 @@ class GradioWebUIBuilder:
|
|
|
62
62
|
|
|
63
63
|
def _build_input_component(self, variable: WorkflowVariableConfig) -> gr.Component:
|
|
64
64
|
label = (variable.name or "") + (" *" if variable.required else "") + (f" (default: {variable.default})" if variable.default else "")
|
|
65
|
-
info = variable.description or ""
|
|
65
|
+
info = variable.get_annotation_value("description") or ""
|
|
66
66
|
default = variable.default
|
|
67
67
|
|
|
68
68
|
if variable.type == "string" or variable.format in [ "base64", "url" ]:
|
|
69
69
|
return gr.Textbox(label=label, value="", info=info)
|
|
70
70
|
|
|
71
|
-
if variable.type
|
|
72
|
-
return gr.Number(label=label, value="",
|
|
73
|
-
|
|
74
|
-
if variable.type == "integer":
|
|
75
|
-
return gr.Number(label=label, value="", precision=0, info=info)
|
|
76
|
-
|
|
71
|
+
if variable.type in [ "integer", "number" ]:
|
|
72
|
+
return gr.Number(label=label, value="", info=info)
|
|
73
|
+
|
|
77
74
|
if variable.type == "boolean":
|
|
78
75
|
return gr.Checkbox(label=label, value=default or False, info=info)
|
|
79
76
|
|
|
@@ -96,14 +93,14 @@ class GradioWebUIBuilder:
|
|
|
96
93
|
|
|
97
94
|
async def _build_input_value(self, arguments: List[Any], variables: List[WorkflowVariableConfig]) -> Any:
|
|
98
95
|
if len(variables) == 1 and not variables[0].name:
|
|
99
|
-
|
|
96
|
+
value, variable = arguments[0], variables[0]
|
|
97
|
+
type, subtype, format = variable.type.value, variable.subtype, variable.format.value if variable.format else None
|
|
98
|
+
return await self._convert_input_value(value, type, subtype, format, variable.internal)
|
|
100
99
|
|
|
101
100
|
input: Dict[str, Any] = {}
|
|
102
|
-
|
|
103
101
|
for value, variable in zip(arguments, variables):
|
|
104
102
|
type, subtype, format = variable.type.value, variable.subtype, variable.format.value if variable.format else None
|
|
105
103
|
input[variable.name] = await self._convert_input_value(value, type, subtype, format, variable.internal)
|
|
106
|
-
|
|
107
104
|
return input
|
|
108
105
|
|
|
109
106
|
async def _convert_input_value(self, value: Any, type: str, subtype: Optional[str], format: Optional[str], internal: bool) -> Any:
|
|
@@ -125,13 +122,16 @@ class GradioWebUIBuilder:
|
|
|
125
122
|
return groups
|
|
126
123
|
|
|
127
124
|
label = variable.name or ""
|
|
128
|
-
info = variable.description or ""
|
|
125
|
+
info = variable.get_annotation_value("description") or ""
|
|
129
126
|
|
|
130
127
|
if variable.type in [ "string", "base64" ]:
|
|
131
128
|
return gr.Textbox(label=label, interactive=False, show_copy_button=True, info=info)
|
|
132
129
|
|
|
133
130
|
if variable.type == "markdown":
|
|
134
131
|
return gr.Markdown(label=label)
|
|
132
|
+
|
|
133
|
+
if variable.type in [ "json", "object[]" ]:
|
|
134
|
+
return gr.JSON(label=label)
|
|
135
135
|
|
|
136
136
|
if variable.type == "image":
|
|
137
137
|
return gr.Image(label=label, interactive=False)
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
from typing import Type, Union, Literal, Optional, Dict, List, Tuple, Set, Annotated, Callable, Iterator, Any
|
|
2
|
+
from mindor.dsl.schema.gateway import SshTunnelGatewayConfig
|
|
3
|
+
from .base import GatewayEngine, GatewayType, GatewayEngineMap
|
|
4
|
+
from pyngrok import ngrok
|
|
5
|
+
|
|
6
|
+
class SshTunnelGateway(GatewayEngine):
|
|
7
|
+
def __init__(self, id: str, config: SshTunnelGatewayConfig, env: Dict[str, str], daemon: bool):
|
|
8
|
+
super().__init__(id, config, env, daemon)
|
|
9
|
+
|
|
10
|
+
self.tunnel: Optional[Any] = None
|
|
11
|
+
self.public_url: Optional[str] = None
|
|
12
|
+
|
|
13
|
+
def get_context(self) -> Dict[str, Any]:
|
|
14
|
+
return {
|
|
15
|
+
"public_url": self.public_url,
|
|
16
|
+
"port": self.config.port
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
async def _serve(self) -> None:
|
|
20
|
+
pass
|
|
21
|
+
|
|
22
|
+
async def _shutdown(self) -> None:
|
|
23
|
+
pass
|
|
24
|
+
|
|
25
|
+
GatewayEngineMap[GatewayType.SSH_TUNNEL] = SshTunnelGateway
|
|
@@ -11,12 +11,13 @@ class TemplateRenderer:
|
|
|
11
11
|
self.source_resolver: Callable[[str], Awaitable[Any]] = source_resolver
|
|
12
12
|
self.patterns: Dict[str, re.Pattern] = {
|
|
13
13
|
"variable": re.compile(
|
|
14
|
-
r"""\$\{
|
|
15
|
-
([a-zA-Z_][^.\s]*)
|
|
16
|
-
(?:\.([^\s
|
|
17
|
-
(?:\s*as\s*([^\s
|
|
18
|
-
(?:\s*\|\s*([
|
|
19
|
-
|
|
14
|
+
r"""\$\{ # ${
|
|
15
|
+
(?:\s*([a-zA-Z_][^.\s]*)) # key: input, env, etc.
|
|
16
|
+
(?:\.([^\s|}]+))? # path: key, key.path[0], etc.
|
|
17
|
+
(?:\s*as\s*([^\s/;}]+)(?:/([^\s;}]+))?(?:;([^\s}]+))?)? # type/subtype;format
|
|
18
|
+
(?:\s*\|\s*((?:\$\{[^}]+\}|\\[$@{}]|(?!\s*(?:@\(|\$\{)).)+))? # default value after `|`
|
|
19
|
+
(?:\s*(@\(\s*[\w]+\s+(?:\\[$@{}]|(?!\s*\$\{).)+\)))? # annotations
|
|
20
|
+
\s*\}""", # }
|
|
20
21
|
re.VERBOSE,
|
|
21
22
|
),
|
|
22
23
|
"keypath": re.compile(r"[-_\w]+|\[\d+\]"),
|
|
@@ -39,18 +40,19 @@ class TemplateRenderer:
|
|
|
39
40
|
|
|
40
41
|
async def _render_text(self, text: str, ignore_files: bool) -> Any:
|
|
41
42
|
while True:
|
|
42
|
-
|
|
43
|
+
m = self.patterns["variable"].search(text)
|
|
43
44
|
|
|
44
|
-
if not
|
|
45
|
+
if not m:
|
|
45
46
|
break
|
|
46
47
|
|
|
47
|
-
key, path, type, subtype, format, default, variable = (*
|
|
48
|
+
key, path, type, subtype, format, default, variable = (*m.group(1, 2, 3, 4, 5, 6), m.group(0))
|
|
48
49
|
try:
|
|
49
50
|
value = self._resolve_by_path(await self.source_resolver(key), path)
|
|
50
51
|
except Exception:
|
|
51
52
|
value = None
|
|
52
53
|
|
|
53
|
-
value
|
|
54
|
+
if value is None and default is not None:
|
|
55
|
+
value = await self._render_text(default, ignore_files)
|
|
54
56
|
|
|
55
57
|
if type and value is not None:
|
|
56
58
|
value = await self._convert_value_to_type(value, type, subtype, format, ignore_files)
|
|
@@ -89,13 +91,24 @@ class TemplateRenderer:
|
|
|
89
91
|
|
|
90
92
|
if type == "integer":
|
|
91
93
|
return int(value)
|
|
92
|
-
|
|
93
|
-
if type == "json":
|
|
94
|
-
return json.loads(value)
|
|
95
94
|
|
|
96
95
|
if type == "boolean":
|
|
97
96
|
return str(value).lower() in [ "true", "1" ]
|
|
97
|
+
|
|
98
|
+
if type == "json":
|
|
99
|
+
if isinstance(value, str):
|
|
100
|
+
return json.loads(value)
|
|
101
|
+
return value
|
|
98
102
|
|
|
103
|
+
if type == "object[]":
|
|
104
|
+
if isinstance(value, list):
|
|
105
|
+
objects = [ v for v in value if isinstance(v, dict) ]
|
|
106
|
+
if subtype:
|
|
107
|
+
paths = [ (path, path.split(".")[-1]) for path in subtype.split(",") ]
|
|
108
|
+
return [ { key: self._resolve_by_path(obj, path) for path, key in paths } for obj in objects ]
|
|
109
|
+
return objects
|
|
110
|
+
return []
|
|
111
|
+
|
|
99
112
|
if type == "base64":
|
|
100
113
|
if isinstance(value, StreamResource):
|
|
101
114
|
return await encode_stream_to_base64(value)
|
|
@@ -1,27 +1,24 @@
|
|
|
1
1
|
from typing import Type, Union, Literal, Optional, Dict, List, Tuple, Set, Annotated, Any
|
|
2
|
+
from dataclasses import dataclass, asdict
|
|
2
3
|
from pydantic import BaseModel
|
|
3
4
|
from mindor.dsl.schema.workflow import WorkflowConfig, WorkflowVariableConfig, WorkflowVariableGroupConfig
|
|
4
5
|
from mindor.dsl.schema.component import ComponentConfig
|
|
5
6
|
import re, json
|
|
6
7
|
|
|
8
|
+
@dataclass
|
|
9
|
+
class WorkflowVariableAnnotation:
|
|
10
|
+
name: str
|
|
11
|
+
value: str
|
|
12
|
+
|
|
13
|
+
@dataclass
|
|
7
14
|
class WorkflowVariable:
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
def to_dict(self) -> Dict[str, Any]:
|
|
17
|
-
return {
|
|
18
|
-
"name": self.name,
|
|
19
|
-
"type": self.type,
|
|
20
|
-
"subtype": self.subtype,
|
|
21
|
-
"format": self.format,
|
|
22
|
-
"default": self.default,
|
|
23
|
-
"internal": self.internal
|
|
24
|
-
}
|
|
15
|
+
name: Optional[str]
|
|
16
|
+
type: str
|
|
17
|
+
subtype: Optional[str]
|
|
18
|
+
format: Optional[str]
|
|
19
|
+
default: Optional[Any]
|
|
20
|
+
annotations: Optional[List[WorkflowVariableAnnotation]]
|
|
21
|
+
internal: bool
|
|
25
22
|
|
|
26
23
|
def __eq__(self, other):
|
|
27
24
|
if not isinstance(other, WorkflowVariable):
|
|
@@ -32,40 +29,56 @@ class WorkflowVariable:
|
|
|
32
29
|
|
|
33
30
|
def __hash__(self):
|
|
34
31
|
return hash(self.name) if self.name is not None else id(self)
|
|
35
|
-
|
|
32
|
+
|
|
33
|
+
@dataclass
|
|
36
34
|
class WorkflowVariableGroup:
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
self.repeat_count: int = repeat_count
|
|
35
|
+
name: Optional[str]
|
|
36
|
+
variables: List[WorkflowVariable]
|
|
37
|
+
repeat_count: int
|
|
41
38
|
|
|
42
39
|
class WorkflowVariableResolver:
|
|
43
40
|
def __init__(self):
|
|
44
41
|
self.patterns: Dict[str, re.Pattern] = {
|
|
45
42
|
"variable": re.compile(
|
|
46
|
-
r"""\$\{
|
|
47
|
-
([a-zA-Z_][^.\s]*)
|
|
48
|
-
(?:\.([^\s
|
|
49
|
-
(?:\s*as\s*([^\s
|
|
50
|
-
(?:\s*\|\s*([
|
|
51
|
-
|
|
43
|
+
r"""\$\{ # ${
|
|
44
|
+
(?:\s*([a-zA-Z_][^.\s]*)) # key: input, env, etc.
|
|
45
|
+
(?:\.([^\s|}]+))? # path: key, key.path[0], etc.
|
|
46
|
+
(?:\s*as\s*([^\s/;}]+)(?:/([^\s;}]+))?(?:;([^\s}]+))?)? # type/subtype;format
|
|
47
|
+
(?:\s*\|\s*((?:\$\{[^}]+\}|\\[$@{}]|(?!\s*(?:@\(|\$\{)).)+))? # default value after `|`
|
|
48
|
+
(?:\s*(@\(\s*[\w]+\s+(?:\\[$@{}]|(?!\s*\$\{).)+\)))? # annotations
|
|
49
|
+
\s*\}""", # }
|
|
52
50
|
re.VERBOSE,
|
|
53
51
|
),
|
|
54
|
-
"
|
|
52
|
+
"annotation": {
|
|
53
|
+
"outer": re.compile(r"^@\(|\)$"),
|
|
54
|
+
"delimiter": re.compile(r"\)\s+@\("),
|
|
55
|
+
"inner": re.compile(r"([\w]+)\s+(.+)"),
|
|
56
|
+
}
|
|
55
57
|
}
|
|
56
58
|
|
|
57
59
|
def _enumerate_input_variables(self, value: Any, wanted_key: str, internal: bool = False) -> List[WorkflowVariable]:
|
|
58
60
|
if isinstance(value, str):
|
|
59
61
|
variables: List[WorkflowVariable] = []
|
|
60
62
|
|
|
61
|
-
for
|
|
62
|
-
key, path, type, subtype, format, default =
|
|
63
|
+
for m in self.patterns["variable"].finditer(value):
|
|
64
|
+
key, path, type, subtype, format, default, annotations = m.group(1, 2, 3, 4, 5, 6, 7)
|
|
63
65
|
|
|
64
66
|
if type and default:
|
|
65
|
-
default = self.
|
|
67
|
+
default = self._parse_value_as_type(default, type)
|
|
68
|
+
|
|
69
|
+
if annotations:
|
|
70
|
+
annotations = self._parse_annotations(annotations)
|
|
66
71
|
|
|
67
72
|
if key == wanted_key:
|
|
68
|
-
variables.append(WorkflowVariable(
|
|
73
|
+
variables.append(WorkflowVariable(
|
|
74
|
+
name=path,
|
|
75
|
+
type=type or "string",
|
|
76
|
+
subtype=subtype,
|
|
77
|
+
format=format,
|
|
78
|
+
default=default,
|
|
79
|
+
annotations=annotations,
|
|
80
|
+
internal=internal
|
|
81
|
+
))
|
|
69
82
|
|
|
70
83
|
return variables
|
|
71
84
|
|
|
@@ -84,13 +97,24 @@ class WorkflowVariableResolver:
|
|
|
84
97
|
variables: List[WorkflowVariable] = []
|
|
85
98
|
|
|
86
99
|
if isinstance(value, str):
|
|
87
|
-
for
|
|
88
|
-
key, path, type, subtype, format, default =
|
|
100
|
+
for m in self.patterns["variable"].finditer(value):
|
|
101
|
+
key, path, type, subtype, format, default, annotations = m.group(1, 2, 3, 4, 5, 6, 7)
|
|
89
102
|
|
|
90
103
|
if type and default:
|
|
91
|
-
default = self.
|
|
92
|
-
|
|
93
|
-
|
|
104
|
+
default = self._parse_value_as_type(default, type)
|
|
105
|
+
|
|
106
|
+
if annotations:
|
|
107
|
+
annotations = self._parse_annotations(annotations)
|
|
108
|
+
|
|
109
|
+
variables.append(WorkflowVariable(
|
|
110
|
+
name=name,
|
|
111
|
+
type=type or "string",
|
|
112
|
+
subtype=subtype,
|
|
113
|
+
format=format,
|
|
114
|
+
default=default,
|
|
115
|
+
annotations=annotations,
|
|
116
|
+
internal=internal
|
|
117
|
+
))
|
|
94
118
|
|
|
95
119
|
return variables
|
|
96
120
|
|
|
@@ -126,20 +150,20 @@ class WorkflowVariableResolver:
|
|
|
126
150
|
return configs
|
|
127
151
|
|
|
128
152
|
def _to_variable_config(self, variable: WorkflowVariable) -> WorkflowVariableConfig:
|
|
129
|
-
config_dict = variable
|
|
153
|
+
config_dict = asdict(variable)
|
|
130
154
|
|
|
131
155
|
if variable.type in [ "image", "audio", "video", "file", "select" ] and variable.subtype:
|
|
132
156
|
config_dict["options"] = variable.subtype.split(",")
|
|
133
157
|
|
|
134
158
|
return WorkflowVariableConfig(**config_dict)
|
|
135
159
|
|
|
136
|
-
def
|
|
137
|
-
if type == "number":
|
|
138
|
-
return float(value)
|
|
139
|
-
|
|
160
|
+
def _parse_value_as_type(self, value: Any, type: str) -> Any:
|
|
140
161
|
if type == "integer":
|
|
141
162
|
return int(value)
|
|
142
163
|
|
|
164
|
+
if type == "number":
|
|
165
|
+
return float(value)
|
|
166
|
+
|
|
143
167
|
if type == "boolean":
|
|
144
168
|
return str(value).lower() in [ "true", "1" ]
|
|
145
169
|
|
|
@@ -147,6 +171,21 @@ class WorkflowVariableResolver:
|
|
|
147
171
|
return json.loads(value)
|
|
148
172
|
|
|
149
173
|
return value
|
|
174
|
+
|
|
175
|
+
def _parse_annotations(self, value: str) -> List[WorkflowVariableAnnotation]:
|
|
176
|
+
parts: List[str] = re.split(self.patterns["annotation"]["delimiter"], re.sub(self.patterns["annotation"]["outer"], "", value))
|
|
177
|
+
annotations: List[WorkflowVariableAnnotation] = []
|
|
178
|
+
|
|
179
|
+
for part in parts:
|
|
180
|
+
m = re.match(self.patterns["annotation"]["inner"], part.strip())
|
|
181
|
+
|
|
182
|
+
if not m:
|
|
183
|
+
continue
|
|
184
|
+
|
|
185
|
+
name, value = m.group(1, 2)
|
|
186
|
+
annotations.append(WorkflowVariableAnnotation(name=name, value=value))
|
|
187
|
+
|
|
188
|
+
return annotations
|
|
150
189
|
|
|
151
190
|
class WorkflowInputVariableResolver(WorkflowVariableResolver):
|
|
152
191
|
def resolve(self, workflow: WorkflowConfig, components: Dict[str, ComponentConfig]) -> List[WorkflowVariableConfig]:
|
|
@@ -185,7 +224,7 @@ class WorkflowOutputVariableResolver(WorkflowVariableResolver):
|
|
|
185
224
|
repeat_count: int = job.repeat_count if isinstance(job.repeat_count, int) else 0
|
|
186
225
|
|
|
187
226
|
if repeat_count != 1:
|
|
188
|
-
variables.append(WorkflowVariableGroup(
|
|
227
|
+
variables.append(WorkflowVariableGroup(variables=(job_variables := []), repeat_count=repeat_count))
|
|
189
228
|
|
|
190
229
|
if not job.output or job.output == "${output}":
|
|
191
230
|
action_id = job.action or "__default__"
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
from typing import Type, Union, Literal, Optional, Dict, List, Tuple, Set, Annotated, Any
|
|
2
|
+
from pydantic import BaseModel, Field
|
|
3
|
+
from pydantic import model_validator
|
|
4
|
+
from .common import GatewayType, CommonGatewayConfig
|
|
5
|
+
|
|
6
|
+
class SshTunnelGatewayConfig(CommonGatewayConfig):
|
|
7
|
+
type: Literal[GatewayType.SSH_TUNNEL]
|
|
@@ -12,6 +12,7 @@ class WorkflowVariableType(str, Enum):
|
|
|
12
12
|
NUMBER = "number"
|
|
13
13
|
BOOLEAN = "boolean"
|
|
14
14
|
JSON = "json"
|
|
15
|
+
OBJECTS = "object[]"
|
|
15
16
|
# Encoded data
|
|
16
17
|
BASE64 = "base64"
|
|
17
18
|
MARKDOWN = "markdown"
|
|
@@ -29,6 +30,10 @@ class WorkflowVariableFormat(str, Enum):
|
|
|
29
30
|
PATH = "path"
|
|
30
31
|
STREAM = "stream"
|
|
31
32
|
|
|
33
|
+
class WorkflowVariableAnnotationConfig(BaseModel):
|
|
34
|
+
name: str = Field(..., description="The name of the annotation")
|
|
35
|
+
value: str = Field(..., description="Description of the annotation")
|
|
36
|
+
|
|
32
37
|
class WorkflowVariableConfig(BaseModel):
|
|
33
38
|
name: Optional[str] = Field(default=None, description="The name of the variable")
|
|
34
39
|
type: WorkflowVariableType = Field(..., description="Type of the variable")
|
|
@@ -37,9 +42,14 @@ class WorkflowVariableConfig(BaseModel):
|
|
|
37
42
|
options: Optional[List[str]] = Field(default=None, description="List of valid options for file or select type")
|
|
38
43
|
required: bool = Field(default=False, description="Whether this variable is required")
|
|
39
44
|
default: Optional[Any] = Field(default=None, description="Default value if not provided")
|
|
40
|
-
|
|
45
|
+
annotations: Optional[List[WorkflowVariableAnnotationConfig]] = Field(default_factory=list, description="Annotations of the variable")
|
|
41
46
|
internal: bool = Field(default=False, description="Whether this variable is for internal use")
|
|
42
47
|
|
|
48
|
+
def get_annotation_value(self, name: str) -> Optional[str]:
|
|
49
|
+
if self.annotations:
|
|
50
|
+
return next((annotation.value for annotation in self.annotations if annotation.name == name), None)
|
|
51
|
+
return None
|
|
52
|
+
|
|
43
53
|
class WorkflowVariableGroupConfig(BaseModel):
|
|
44
54
|
name: Optional[str] = Field(default=None, description="The name of the group of variables")
|
|
45
55
|
variables: List[WorkflowVariableConfig] = Field(default_factory=list, description="List of variables included in this group")
|
|
@@ -37,6 +37,7 @@ src/mindor/core/gateway/gateway.py
|
|
|
37
37
|
src/mindor/core/gateway/engine/__init__.py
|
|
38
38
|
src/mindor/core/gateway/engine/base.py
|
|
39
39
|
src/mindor/core/gateway/engine/http_tunnel.py
|
|
40
|
+
src/mindor/core/gateway/engine/ssh_tunnel.py
|
|
40
41
|
src/mindor/core/listener/__init__.py
|
|
41
42
|
src/mindor/core/listener/listener.py
|
|
42
43
|
src/mindor/core/listener/engine/__init__.py
|
|
@@ -52,6 +53,7 @@ src/mindor/core/utils/http_client.py
|
|
|
52
53
|
src/mindor/core/utils/http_request.py
|
|
53
54
|
src/mindor/core/utils/image.py
|
|
54
55
|
src/mindor/core/utils/mcp_client.py
|
|
56
|
+
src/mindor/core/utils/ssh_client.py
|
|
55
57
|
src/mindor/core/utils/streaming.py
|
|
56
58
|
src/mindor/core/utils/template.py
|
|
57
59
|
src/mindor/core/utils/time.py
|
|
@@ -98,6 +100,7 @@ src/mindor/dsl/schema/gateway/gateway.py
|
|
|
98
100
|
src/mindor/dsl/schema/gateway/impl/__init__.py
|
|
99
101
|
src/mindor/dsl/schema/gateway/impl/common.py
|
|
100
102
|
src/mindor/dsl/schema/gateway/impl/http_tunnel.py
|
|
103
|
+
src/mindor/dsl/schema/gateway/impl/ssh_tunnel.py
|
|
101
104
|
src/mindor/dsl/schema/gateway/impl/types.py
|
|
102
105
|
src/mindor/dsl/schema/listener/__init__.py
|
|
103
106
|
src/mindor/dsl/schema/listener/listener.py
|
|
@@ -1,93 +0,0 @@
|
|
|
1
|
-
from typing import Type, Union, Literal, Optional, Dict, List, Tuple, Set, Annotated, Callable, Awaitable, Any
|
|
2
|
-
from mindor.dsl.schema.controller import McpServerControllerConfig
|
|
3
|
-
from mindor.dsl.schema.component import ComponentConfig
|
|
4
|
-
from mindor.dsl.schema.listener import ListenerConfig
|
|
5
|
-
from mindor.dsl.schema.gateway import GatewayConfig
|
|
6
|
-
from mindor.dsl.schema.workflow import WorkflowConfig
|
|
7
|
-
from mindor.core.workflow.schema import WorkflowSchema, WorkflowVariableConfig, create_workflow_schema
|
|
8
|
-
from .base import ControllerEngine, ControllerType, ControllerEngineMap
|
|
9
|
-
from mcp.server.fastmcp.server import FastMCP
|
|
10
|
-
import uvicorn
|
|
11
|
-
|
|
12
|
-
class WorkflowToolGenerator():
|
|
13
|
-
def generate(self, workflow_id: str, workflow: WorkflowSchema, runner: Callable[[Optional[str], Any], Awaitable[Any]]) -> Tuple[Callable[[Any], Awaitable[Any]], str]:
|
|
14
|
-
async def _run_workflow(input: Any, workflow_id=workflow_id) -> Any:
|
|
15
|
-
return await runner(workflow_id, input)
|
|
16
|
-
|
|
17
|
-
async def _build_input_value(*arguments, variables=workflow.input) -> Any:
|
|
18
|
-
return await self._build_input_value(arguments, variables)
|
|
19
|
-
|
|
20
|
-
arguments = ",".join([ variable.name or "input" for variable in workflow.input ])
|
|
21
|
-
code = f"async def _run_workflow_{workflow_id}({arguments}): return await _run_workflow(await _build_input_value({arguments}))"
|
|
22
|
-
context = { "_run_workflow": _run_workflow, "_build_input_value": _build_input_value }
|
|
23
|
-
exec(compile(code, f"<string>", "exec"), context)
|
|
24
|
-
|
|
25
|
-
description = ""
|
|
26
|
-
|
|
27
|
-
return (context[f"_run_workflow_{workflow_id}"], description)
|
|
28
|
-
|
|
29
|
-
async def _build_input_value(self, arguments: List[Any], variables: List[WorkflowVariableConfig]) -> Any:
|
|
30
|
-
if len(variables) == 1 and not variables[0].name:
|
|
31
|
-
return arguments[0]
|
|
32
|
-
|
|
33
|
-
input: Dict[str, Any] = {}
|
|
34
|
-
|
|
35
|
-
for value, variable in zip(arguments, variables):
|
|
36
|
-
type, subtype, format = variable.type.value, variable.subtype, variable.format.value if variable.format else None
|
|
37
|
-
input[variable.name] = await self._convert_input_value(value, type, subtype, format, variable.internal)
|
|
38
|
-
|
|
39
|
-
return input
|
|
40
|
-
|
|
41
|
-
async def _convert_input_value(self, value: Any, type: str, subtype: Optional[str], format: Optional[str], internal: bool) -> Any:
|
|
42
|
-
if type in [ "image", "audio", "video", "file" ] and (not internal or not format):
|
|
43
|
-
pass
|
|
44
|
-
|
|
45
|
-
return value if value != "" else None
|
|
46
|
-
|
|
47
|
-
class McpServerController(ControllerEngine):
|
|
48
|
-
def __init__(
|
|
49
|
-
self,
|
|
50
|
-
config: McpServerControllerConfig,
|
|
51
|
-
components: Dict[str, ComponentConfig],
|
|
52
|
-
listeners: List[ListenerConfig],
|
|
53
|
-
gateways: List[GatewayConfig],
|
|
54
|
-
workflows: Dict[str, WorkflowConfig],
|
|
55
|
-
env: Dict[str, str],
|
|
56
|
-
daemon: bool
|
|
57
|
-
):
|
|
58
|
-
super().__init__(config, components, listeners, gateways, workflows, env, daemon)
|
|
59
|
-
|
|
60
|
-
self.server: Optional[uvicorn.Server] = None
|
|
61
|
-
self.app: FastMCP = FastMCP(self.config.name, settings={
|
|
62
|
-
"streamable_http_path": self.config.base_path
|
|
63
|
-
})
|
|
64
|
-
|
|
65
|
-
self._configure_tools()
|
|
66
|
-
|
|
67
|
-
def _configure_tools(self) -> None:
|
|
68
|
-
schema = create_workflow_schema(self.workflows, self.components)
|
|
69
|
-
|
|
70
|
-
for workflow_id, workflow in schema.items():
|
|
71
|
-
fn, description = WorkflowToolGenerator().generate(workflow_id, workflow, self._run_workflow)
|
|
72
|
-
self.app.add_tool(
|
|
73
|
-
fn=fn,
|
|
74
|
-
name=workflow.name or workflow_id,
|
|
75
|
-
annotations=None,
|
|
76
|
-
title=workflow.title,
|
|
77
|
-
description=description
|
|
78
|
-
)
|
|
79
|
-
|
|
80
|
-
async def _serve(self) -> None:
|
|
81
|
-
self.server = uvicorn.Server(uvicorn.Config(
|
|
82
|
-
self.app.streamable_http_app(),
|
|
83
|
-
host=self.config.host,
|
|
84
|
-
port=self.config.port,
|
|
85
|
-
log_level="info"
|
|
86
|
-
))
|
|
87
|
-
await self.server.serve()
|
|
88
|
-
|
|
89
|
-
async def _shutdown(self) -> None:
|
|
90
|
-
if self.server:
|
|
91
|
-
self.server.should_exit = True
|
|
92
|
-
|
|
93
|
-
ControllerEngineMap[ControllerType.MCP_SERVER] = McpServerController
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{model_compose-0.1.7 → model_compose-0.1.8}/src/mindor/core/controller/engine/http_server.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{model_compose-0.1.7 → model_compose-0.1.8}/src/mindor/core/controller/runner/http_client.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{model_compose-0.1.7 → model_compose-0.1.8}/src/mindor/core/listener/engine/http_callback.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{model_compose-0.1.7 → model_compose-0.1.8}/src/mindor/dsl/schema/action/impl/http_client.py
RENAMED
|
File without changes
|
{model_compose-0.1.7 → model_compose-0.1.8}/src/mindor/dsl/schema/action/impl/http_server.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{model_compose-0.1.7 → model_compose-0.1.8}/src/mindor/dsl/schema/component/impl/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{model_compose-0.1.7 → model_compose-0.1.8}/src/mindor/dsl/schema/component/impl/http_client.py
RENAMED
|
File without changes
|
{model_compose-0.1.7 → model_compose-0.1.8}/src/mindor/dsl/schema/component/impl/http_server.py
RENAMED
|
File without changes
|
{model_compose-0.1.7 → model_compose-0.1.8}/src/mindor/dsl/schema/component/impl/mcp_client.py
RENAMED
|
File without changes
|
{model_compose-0.1.7 → model_compose-0.1.8}/src/mindor/dsl/schema/component/impl/mcp_server.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{model_compose-0.1.7 → model_compose-0.1.8}/src/mindor/dsl/schema/controller/impl/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{model_compose-0.1.7 → model_compose-0.1.8}/src/mindor/dsl/schema/controller/impl/http_server.py
RENAMED
|
File without changes
|
{model_compose-0.1.7 → model_compose-0.1.8}/src/mindor/dsl/schema/controller/impl/mcp_server.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{model_compose-0.1.7 → model_compose-0.1.8}/src/mindor/dsl/schema/gateway/impl/http_tunnel.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{model_compose-0.1.7 → model_compose-0.1.8}/src/mindor/dsl/schema/listener/impl/http_callback.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|