model-compose 0.1.6__tar.gz → 0.1.7__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.
Files changed (123) hide show
  1. {model_compose-0.1.6/src/model_compose.egg-info → model_compose-0.1.7}/PKG-INFO +2 -1
  2. {model_compose-0.1.6 → model_compose-0.1.7}/pyproject.toml +2 -1
  3. {model_compose-0.1.6 → model_compose-0.1.7}/src/mindor/core/component/engine/__init__.py +3 -0
  4. model_compose-0.1.7/src/mindor/core/component/engine/mcp_client.py +19 -0
  5. model_compose-0.1.7/src/mindor/core/component/engine/mcp_server.py +19 -0
  6. model_compose-0.1.7/src/mindor/core/component/engine/shell.py +69 -0
  7. model_compose-0.1.7/src/mindor/core/controller/engine/mcp_server.py +93 -0
  8. {model_compose-0.1.6 → model_compose-0.1.7}/src/mindor/core/controller/runner/mcp_client.py +3 -0
  9. {model_compose-0.1.6 → model_compose-0.1.7}/src/mindor/core/controller/webui/gradio.py +10 -8
  10. {model_compose-0.1.6 → model_compose-0.1.7}/src/mindor/core/controller/webui/webui.py +4 -19
  11. {model_compose-0.1.6 → model_compose-0.1.7}/src/mindor/core/utils/http_client.py +1 -1
  12. model_compose-0.1.7/src/mindor/core/utils/mcp_client.py +4 -0
  13. {model_compose-0.1.6 → model_compose-0.1.7}/src/mindor/core/utils/streaming.py +1 -1
  14. {model_compose-0.1.6/src/mindor/core/controller/webui → model_compose-0.1.7/src/mindor/core/workflow}/schema.py +22 -1
  15. {model_compose-0.1.6 → model_compose-0.1.7}/src/mindor/dsl/schema/action/impl/__init__.py +1 -0
  16. model_compose-0.1.7/src/mindor/dsl/schema/action/impl/shell.py +10 -0
  17. {model_compose-0.1.6 → model_compose-0.1.7}/src/mindor/dsl/schema/component/component.py +2 -1
  18. {model_compose-0.1.6 → model_compose-0.1.7}/src/mindor/dsl/schema/component/impl/__init__.py +1 -0
  19. {model_compose-0.1.6 → model_compose-0.1.7}/src/mindor/dsl/schema/component/impl/http_server.py +1 -1
  20. model_compose-0.1.7/src/mindor/dsl/schema/component/impl/shell.py +19 -0
  21. {model_compose-0.1.6 → model_compose-0.1.7}/src/mindor/dsl/schema/component/impl/types.py +1 -0
  22. {model_compose-0.1.6 → model_compose-0.1.7}/src/mindor/dsl/schema/controller/impl/common.py +1 -0
  23. {model_compose-0.1.6 → model_compose-0.1.7}/src/mindor/dsl/schema/controller/impl/mcp_server.py +2 -0
  24. {model_compose-0.1.6 → model_compose-0.1.7}/src/mindor/dsl/schema/workflow.py +27 -26
  25. {model_compose-0.1.6 → model_compose-0.1.7/src/model_compose.egg-info}/PKG-INFO +2 -1
  26. {model_compose-0.1.6 → model_compose-0.1.7}/src/model_compose.egg-info/SOURCES.txt +7 -1
  27. {model_compose-0.1.6 → model_compose-0.1.7}/src/model_compose.egg-info/requires.txt +1 -0
  28. model_compose-0.1.6/src/mindor/core/controller/engine/mcp_server.py +0 -28
  29. {model_compose-0.1.6 → model_compose-0.1.7}/LICENSE +0 -0
  30. {model_compose-0.1.6 → model_compose-0.1.7}/README.md +0 -0
  31. {model_compose-0.1.6 → model_compose-0.1.7}/setup.cfg +0 -0
  32. {model_compose-0.1.6 → model_compose-0.1.7}/src/mindor/__init__.py +0 -0
  33. {model_compose-0.1.6 → model_compose-0.1.7}/src/mindor/cli/__init__.py +0 -0
  34. {model_compose-0.1.6 → model_compose-0.1.7}/src/mindor/cli/compose.py +0 -0
  35. {model_compose-0.1.6 → model_compose-0.1.7}/src/mindor/core/__init__.py +0 -0
  36. {model_compose-0.1.6 → model_compose-0.1.7}/src/mindor/core/component/__init__.py +0 -0
  37. {model_compose-0.1.6 → model_compose-0.1.7}/src/mindor/core/component/component.py +0 -0
  38. {model_compose-0.1.6 → model_compose-0.1.7}/src/mindor/core/component/engine/base.py +0 -0
  39. {model_compose-0.1.6 → model_compose-0.1.7}/src/mindor/core/component/engine/context.py +0 -0
  40. {model_compose-0.1.6 → model_compose-0.1.7}/src/mindor/core/component/engine/http_client.py +0 -0
  41. {model_compose-0.1.6 → model_compose-0.1.7}/src/mindor/core/component/engine/http_server.py +0 -0
  42. {model_compose-0.1.6 → model_compose-0.1.7}/src/mindor/core/compose/__init__.py +0 -0
  43. {model_compose-0.1.6 → model_compose-0.1.7}/src/mindor/core/compose/compose.py +0 -0
  44. {model_compose-0.1.6 → model_compose-0.1.7}/src/mindor/core/compose/manager.py +0 -0
  45. {model_compose-0.1.6 → model_compose-0.1.7}/src/mindor/core/controller/__init__.py +0 -0
  46. {model_compose-0.1.6 → model_compose-0.1.7}/src/mindor/core/controller/controller.py +0 -0
  47. {model_compose-0.1.6 → model_compose-0.1.7}/src/mindor/core/controller/engine/__init__.py +0 -0
  48. {model_compose-0.1.6 → model_compose-0.1.7}/src/mindor/core/controller/engine/base.py +0 -0
  49. {model_compose-0.1.6 → model_compose-0.1.7}/src/mindor/core/controller/engine/http_server.py +0 -0
  50. {model_compose-0.1.6 → model_compose-0.1.7}/src/mindor/core/controller/runner/__init__.py +0 -0
  51. {model_compose-0.1.6 → model_compose-0.1.7}/src/mindor/core/controller/runner/client.py +0 -0
  52. {model_compose-0.1.6 → model_compose-0.1.7}/src/mindor/core/controller/runner/http_client.py +0 -0
  53. {model_compose-0.1.6 → model_compose-0.1.7}/src/mindor/core/controller/runner/runner.py +0 -0
  54. {model_compose-0.1.6 → model_compose-0.1.7}/src/mindor/core/controller/webui/__init__.py +0 -0
  55. {model_compose-0.1.6 → model_compose-0.1.7}/src/mindor/core/gateway/__init__.py +0 -0
  56. {model_compose-0.1.6 → model_compose-0.1.7}/src/mindor/core/gateway/engine/__init__.py +0 -0
  57. {model_compose-0.1.6 → model_compose-0.1.7}/src/mindor/core/gateway/engine/base.py +0 -0
  58. {model_compose-0.1.6 → model_compose-0.1.7}/src/mindor/core/gateway/engine/http_tunnel.py +0 -0
  59. {model_compose-0.1.6 → model_compose-0.1.7}/src/mindor/core/gateway/gateway.py +0 -0
  60. {model_compose-0.1.6 → model_compose-0.1.7}/src/mindor/core/listener/__init__.py +0 -0
  61. {model_compose-0.1.6 → model_compose-0.1.7}/src/mindor/core/listener/engine/__init__.py +0 -0
  62. {model_compose-0.1.6 → model_compose-0.1.7}/src/mindor/core/listener/engine/base.py +0 -0
  63. {model_compose-0.1.6 → model_compose-0.1.7}/src/mindor/core/listener/engine/http_callback.py +0 -0
  64. {model_compose-0.1.6 → model_compose-0.1.7}/src/mindor/core/listener/listener.py +0 -0
  65. {model_compose-0.1.6 → model_compose-0.1.7}/src/mindor/core/runtime/__init__.py +0 -0
  66. {model_compose-0.1.6 → model_compose-0.1.7}/src/mindor/core/runtime/env.py +0 -0
  67. {model_compose-0.1.6 → model_compose-0.1.7}/src/mindor/core/services/__init__.py +0 -0
  68. {model_compose-0.1.6 → model_compose-0.1.7}/src/mindor/core/services/async_service.py +0 -0
  69. {model_compose-0.1.6 → model_compose-0.1.7}/src/mindor/core/utils/__init__.py +0 -0
  70. {model_compose-0.1.6 → model_compose-0.1.7}/src/mindor/core/utils/expiring.py +0 -0
  71. {model_compose-0.1.6 → model_compose-0.1.7}/src/mindor/core/utils/http_request.py +0 -0
  72. {model_compose-0.1.6 → model_compose-0.1.7}/src/mindor/core/utils/image.py +0 -0
  73. {model_compose-0.1.6 → model_compose-0.1.7}/src/mindor/core/utils/template.py +0 -0
  74. {model_compose-0.1.6 → model_compose-0.1.7}/src/mindor/core/utils/time.py +0 -0
  75. {model_compose-0.1.6 → model_compose-0.1.7}/src/mindor/core/utils/workqueue.py +0 -0
  76. {model_compose-0.1.6 → model_compose-0.1.7}/src/mindor/core/workflow/__init__.py +0 -0
  77. {model_compose-0.1.6 → model_compose-0.1.7}/src/mindor/core/workflow/context.py +0 -0
  78. {model_compose-0.1.6 → model_compose-0.1.7}/src/mindor/core/workflow/job.py +0 -0
  79. {model_compose-0.1.6 → model_compose-0.1.7}/src/mindor/core/workflow/workflow.py +0 -0
  80. {model_compose-0.1.6 → model_compose-0.1.7}/src/mindor/dsl/__init__.py +0 -0
  81. {model_compose-0.1.6 → model_compose-0.1.7}/src/mindor/dsl/loader.py +0 -0
  82. {model_compose-0.1.6 → model_compose-0.1.7}/src/mindor/dsl/schema/__init__.py +0 -0
  83. {model_compose-0.1.6 → model_compose-0.1.7}/src/mindor/dsl/schema/action/__init__.py +0 -0
  84. {model_compose-0.1.6 → model_compose-0.1.7}/src/mindor/dsl/schema/action/action.py +0 -0
  85. {model_compose-0.1.6 → model_compose-0.1.7}/src/mindor/dsl/schema/action/impl/common.py +0 -0
  86. {model_compose-0.1.6 → model_compose-0.1.7}/src/mindor/dsl/schema/action/impl/http_client.py +0 -0
  87. {model_compose-0.1.6 → model_compose-0.1.7}/src/mindor/dsl/schema/action/impl/http_server.py +0 -0
  88. {model_compose-0.1.6 → model_compose-0.1.7}/src/mindor/dsl/schema/action/impl/mcp_client.py +0 -0
  89. {model_compose-0.1.6 → model_compose-0.1.7}/src/mindor/dsl/schema/action/impl/mcp_server.py +0 -0
  90. {model_compose-0.1.6 → model_compose-0.1.7}/src/mindor/dsl/schema/component/__init__.py +0 -0
  91. {model_compose-0.1.6 → model_compose-0.1.7}/src/mindor/dsl/schema/component/impl/common.py +0 -0
  92. {model_compose-0.1.6 → model_compose-0.1.7}/src/mindor/dsl/schema/component/impl/http_client.py +0 -0
  93. {model_compose-0.1.6 → model_compose-0.1.7}/src/mindor/dsl/schema/component/impl/mcp_client.py +0 -0
  94. {model_compose-0.1.6 → model_compose-0.1.7}/src/mindor/dsl/schema/component/impl/mcp_server.py +0 -0
  95. {model_compose-0.1.6 → model_compose-0.1.7}/src/mindor/dsl/schema/compose.py +0 -0
  96. {model_compose-0.1.6 → model_compose-0.1.7}/src/mindor/dsl/schema/controller/__init__.py +0 -0
  97. {model_compose-0.1.6 → model_compose-0.1.7}/src/mindor/dsl/schema/controller/controller.py +0 -0
  98. {model_compose-0.1.6 → model_compose-0.1.7}/src/mindor/dsl/schema/controller/impl/__init__.py +0 -0
  99. {model_compose-0.1.6 → model_compose-0.1.7}/src/mindor/dsl/schema/controller/impl/http_server.py +0 -0
  100. {model_compose-0.1.6 → model_compose-0.1.7}/src/mindor/dsl/schema/controller/impl/types.py +0 -0
  101. {model_compose-0.1.6 → model_compose-0.1.7}/src/mindor/dsl/schema/controller/impl/webui.py +0 -0
  102. {model_compose-0.1.6 → model_compose-0.1.7}/src/mindor/dsl/schema/gateway/__init__.py +0 -0
  103. {model_compose-0.1.6 → model_compose-0.1.7}/src/mindor/dsl/schema/gateway/gateway.py +0 -0
  104. {model_compose-0.1.6 → model_compose-0.1.7}/src/mindor/dsl/schema/gateway/impl/__init__.py +0 -0
  105. {model_compose-0.1.6 → model_compose-0.1.7}/src/mindor/dsl/schema/gateway/impl/common.py +0 -0
  106. {model_compose-0.1.6 → model_compose-0.1.7}/src/mindor/dsl/schema/gateway/impl/http_tunnel.py +0 -0
  107. {model_compose-0.1.6 → model_compose-0.1.7}/src/mindor/dsl/schema/gateway/impl/types.py +0 -0
  108. {model_compose-0.1.6 → model_compose-0.1.7}/src/mindor/dsl/schema/listener/__init__.py +0 -0
  109. {model_compose-0.1.6 → model_compose-0.1.7}/src/mindor/dsl/schema/listener/impl/__init__.py +0 -0
  110. {model_compose-0.1.6 → model_compose-0.1.7}/src/mindor/dsl/schema/listener/impl/common.py +0 -0
  111. {model_compose-0.1.6 → model_compose-0.1.7}/src/mindor/dsl/schema/listener/impl/http_callback.py +0 -0
  112. {model_compose-0.1.6 → model_compose-0.1.7}/src/mindor/dsl/schema/listener/impl/types.py +0 -0
  113. {model_compose-0.1.6 → model_compose-0.1.7}/src/mindor/dsl/schema/listener/listener.py +0 -0
  114. {model_compose-0.1.6 → model_compose-0.1.7}/src/mindor/dsl/schema/runtime/__init__.py +0 -0
  115. {model_compose-0.1.6 → model_compose-0.1.7}/src/mindor/dsl/schema/runtime/impl/__init__.py +0 -0
  116. {model_compose-0.1.6 → model_compose-0.1.7}/src/mindor/dsl/schema/runtime/impl/common.py +0 -0
  117. {model_compose-0.1.6 → model_compose-0.1.7}/src/mindor/dsl/schema/runtime/impl/docker.py +0 -0
  118. {model_compose-0.1.6 → model_compose-0.1.7}/src/mindor/dsl/schema/runtime/impl/types.py +0 -0
  119. {model_compose-0.1.6 → model_compose-0.1.7}/src/mindor/dsl/schema/runtime/runtime.py +0 -0
  120. {model_compose-0.1.6 → model_compose-0.1.7}/src/model_compose.egg-info/dependency_links.txt +0 -0
  121. {model_compose-0.1.6 → model_compose-0.1.7}/src/model_compose.egg-info/entry_points.txt +0 -0
  122. {model_compose-0.1.6 → model_compose-0.1.7}/src/model_compose.egg-info/top_level.txt +0 -0
  123. {model_compose-0.1.6 → model_compose-0.1.7}/tests/test_cli.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: model-compose
3
- Version: 0.1.6
3
+ Version: 0.1.7
4
4
  Summary: model-compose: Declarative AI Model and Workflow Orchestrator (part of the Mindor project)
5
5
  Author-email: Hanyeol Cho <hanyeol.cho@gmail.com>
6
6
  License-Expression: MIT
@@ -15,6 +15,7 @@ Requires-Dist: aiohttp
15
15
  Requires-Dist: requests
16
16
  Requires-Dist: fastapi
17
17
  Requires-Dist: uvicorn
18
+ Requires-Dist: mcp
18
19
  Requires-Dist: pyngrok
19
20
  Requires-Dist: ulid
20
21
  Requires-Dist: gradio
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "model-compose"
3
- version = "0.1.6"
3
+ version = "0.1.7"
4
4
  description = "model-compose: Declarative AI Model and Workflow Orchestrator (part of the Mindor project)"
5
5
  authors = [
6
6
  { name = "Hanyeol Cho", email = "hanyeol.cho@gmail.com" }
@@ -16,6 +16,7 @@ dependencies = [
16
16
  "requests",
17
17
  "fastapi",
18
18
  "uvicorn",
19
+ "mcp",
19
20
  "pyngrok",
20
21
  "ulid",
21
22
  "gradio",
@@ -1,3 +1,6 @@
1
1
  from .base import *
2
2
  from .http_server import *
3
3
  from .http_client import *
4
+ from .mcp_server import *
5
+ from .mcp_client import *
6
+ from .shell import *
@@ -0,0 +1,19 @@
1
+ from typing import Type, Union, Literal, Optional, Dict, List, Tuple, Set, Annotated, Any
2
+ from mindor.dsl.schema.component import McpClientComponentConfig
3
+ from .base import ComponentEngine, ComponentType, ComponentEngineMap, ActionConfig
4
+ from .context import ComponentContext
5
+
6
+ class McpClientComponent(ComponentEngine):
7
+ def __init__(self, id: str, config: McpClientComponentConfig, env: Dict[str, str], daemon: bool):
8
+ super().__init__(id, config, env, daemon)
9
+
10
+ async def _serve(self) -> None:
11
+ pass
12
+
13
+ async def _shutdown(self) -> None:
14
+ pass
15
+
16
+ async def _run(self, action: ActionConfig, context: ComponentContext) -> Any:
17
+ return {}
18
+
19
+ ComponentEngineMap[ComponentType.MCP_CLIENT] = McpClientComponent
@@ -0,0 +1,19 @@
1
+ from typing import Type, Union, Literal, Optional, Dict, List, Tuple, Set, Annotated, Any
2
+ from mindor.dsl.schema.component import McpServerComponentConfig
3
+ from .base import ComponentEngine, ComponentType, ComponentEngineMap, ActionConfig
4
+ from .context import ComponentContext
5
+
6
+ class McpServerComponent(ComponentEngine):
7
+ def __init__(self, id: str, config: McpServerComponentConfig, env: Dict[str, str], daemon: bool):
8
+ super().__init__(id, config, env, daemon)
9
+
10
+ async def _serve(self) -> None:
11
+ pass
12
+
13
+ async def _shutdown(self) -> None:
14
+ pass
15
+
16
+ async def _run(self, action: ActionConfig, context: ComponentContext) -> Any:
17
+ return {}
18
+
19
+ ComponentEngineMap[ComponentType.MCP_SERVER] = McpServerComponent
@@ -0,0 +1,69 @@
1
+ from typing import Type, Union, Literal, Optional, Dict, List, Tuple, Set, Annotated, Any
2
+ from mindor.dsl.schema.component import ShellComponentConfig
3
+ from mindor.dsl.schema.action import ActionConfig, ShellActionConfig
4
+ from .base import ComponentEngine, ComponentType, ComponentEngineMap, ActionConfig
5
+ from .context import ComponentContext
6
+ import asyncio, os
7
+
8
+ class ShellAction:
9
+ def __init__(self, base_dir: Optional[str], env: Optional[Dict[str, str]], config: ShellActionConfig):
10
+ self.base_dir: Optional[str] = base_dir
11
+ self.env: Optional[Dict[str, str]] = env
12
+ self.config: ShellActionConfig = config
13
+
14
+ async def run(self, context: ComponentContext) -> Any:
15
+ working_dir = self._resolve_working_directory()
16
+ env = { **(self.env or {}), **(self.config.env or {}) }
17
+
18
+ result = await self._run_command(self.config.command, working_dir, env, self.config.timeout)
19
+ context.register_source("result", result)
20
+
21
+ return (await context.render_template(self.config.output, ignore_files=True)) if self.config.output else result
22
+
23
+ async def _run_command(self, command: List[str], working_dir: str, env: Dict[str, str], timeout: Optional[float]) -> Dict[str, Any]:
24
+ process = await asyncio.create_subprocess_exec(
25
+ *command,
26
+ cwd=working_dir,
27
+ env={**os.environ, **env},
28
+ stdout=asyncio.subprocess.PIPE,
29
+ stderr=asyncio.subprocess.PIPE
30
+ )
31
+
32
+ try:
33
+ stdout, stderr = await asyncio.wait_for(process.communicate(), timeout=timeout)
34
+ except asyncio.TimeoutError:
35
+ process.kill()
36
+ await process.wait()
37
+ raise RuntimeError(f"Command timed out: {' '.join(command)}")
38
+
39
+ return {
40
+ "exit_code": process.returncode,
41
+ "stdout": stdout.decode().strip(),
42
+ "stderr": stderr.decode().strip()
43
+ }
44
+
45
+ def _resolve_working_directory(self) -> str:
46
+ working_dir = self.config.working_dir
47
+ if working_dir:
48
+ if self.base_dir:
49
+ working_dir = os.path.abspath(os.path.join(self.base_dir, working_dir))
50
+ else:
51
+ working_dir = os.path.abspath(working_dir)
52
+ else:
53
+ working_dir = self.base_dir or os.getcwd()
54
+ return working_dir
55
+
56
+ class ShellComponent(ComponentEngine):
57
+ def __init__(self, id: str, config: ShellComponentConfig, env: Dict[str, str], daemon: bool):
58
+ super().__init__(id, config, env, daemon)
59
+
60
+ async def _serve(self) -> None:
61
+ pass
62
+
63
+ async def _shutdown(self) -> None:
64
+ pass
65
+
66
+ async def _run(self, action: ActionConfig, context: ComponentContext) -> Any:
67
+ return await ShellAction(self.config.base_dir, self.config.env, action).run(context)
68
+
69
+ ComponentEngineMap[ComponentType.SHELL] = ShellComponent
@@ -0,0 +1,93 @@
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
@@ -1,10 +1,13 @@
1
1
  from typing import Type, Union, Literal, Optional, Dict, List, Tuple, Set, Annotated, Any
2
2
  from mindor.dsl.schema.controller import ControllerConfig
3
+ from mindor.core.utils.mcp_client import McpClient
3
4
  from .runner import ControllerClient
4
5
 
5
6
  class McpControllerClient(ControllerClient):
6
7
  def __init__(self, config: ControllerConfig):
7
8
  super().__init__(config)
8
9
 
10
+ self.client: McpClient = McpClient()
11
+
9
12
  async def run_workflow(self, workflow_id: Optional[str], input: Any) -> Any:
10
13
  pass
@@ -1,14 +1,13 @@
1
1
  from typing import Type, Union, Literal, Optional, Dict, List, Tuple, Set, Annotated, Callable, Awaitable, Any
2
2
  from mindor.dsl.schema.workflow import WorkflowVariableConfig, WorkflowVariableGroupConfig
3
+ from mindor.core.workflow.schema import WorkflowSchema
3
4
  from mindor.core.utils.streaming import StreamResource, Base64StreamResource
4
5
  from mindor.core.utils.streaming import save_stream_to_temporary_file
5
6
  from mindor.core.utils.http_request import create_upload_file
6
7
  from mindor.core.utils.http_client import HttpClient
7
8
  from mindor.core.utils.image import load_image_from_stream
8
- from .schema import WorkflowSchema
9
- from starlette.datastructures import UploadFile
10
9
  import gradio as gr
11
- import json, os
10
+ import json
12
11
 
13
12
  class ComponentGroup:
14
13
  def __init__(self, group: gr.Component, components: List[gr.Component]):
@@ -48,9 +47,9 @@ class GradioWebUIBuilder:
48
47
  output_components = gr.Textbox(label="", lines=8, interactive=False, show_copy_button=True)
49
48
 
50
49
  async def _run_workflow(*args):
51
- input = await self._build_input_values(args, workflow.input)
50
+ input = await self._build_input_value(args, workflow.input)
52
51
  output = await runner(input)
53
- output = await self._flatten_output_values(output, workflow.output)
52
+ output = await self._flatten_output_value(output, workflow.output)
54
53
  return output[0] if len(output) == 1 else output
55
54
 
56
55
  run_button.click(
@@ -95,7 +94,7 @@ class GradioWebUIBuilder:
95
94
 
96
95
  return gr.Textbox(label=label, value=default, info=f"Unsupported type: {variable.type}")
97
96
 
98
- async def _build_input_values(self, arguments: List[Any], variables: List[WorkflowVariableConfig]) -> Any:
97
+ async def _build_input_value(self, arguments: List[Any], variables: List[WorkflowVariableConfig]) -> Any:
99
98
  if len(variables) == 1 and not variables[0].name:
100
99
  return arguments[0]
101
100
 
@@ -131,6 +130,9 @@ class GradioWebUIBuilder:
131
130
  if variable.type in [ "string", "base64" ]:
132
131
  return gr.Textbox(label=label, interactive=False, show_copy_button=True, info=info)
133
132
 
133
+ if variable.type == "markdown":
134
+ return gr.Markdown(label=label)
135
+
134
136
  if variable.type == "image":
135
137
  return gr.Image(label=label, interactive=False)
136
138
 
@@ -152,13 +154,13 @@ class GradioWebUIBuilder:
152
154
  flattened.append(item)
153
155
  return flattened
154
156
 
155
- async def _flatten_output_values(self, output: Any, variables: List[Union[WorkflowVariableConfig, WorkflowVariableGroupConfig]]) -> Any:
157
+ async def _flatten_output_value(self, output: Any, variables: List[Union[WorkflowVariableConfig, WorkflowVariableGroupConfig]]) -> Any:
156
158
  flattened = []
157
159
  for variable in variables:
158
160
  if isinstance(variable, WorkflowVariableGroupConfig):
159
161
  group = output[variable.name] if variable.name else output
160
162
  for value in group:
161
- flattened.extend(await self._flatten_output_values(value, variable.variables))
163
+ flattened.extend(await self._flatten_output_value(value, variable.variables))
162
164
  else:
163
165
  value = output[variable.name] if variable.name else output
164
166
  type, subtype, format = variable.type.value, variable.subtype, variable.format.value if variable.format else None
@@ -3,14 +3,13 @@ from mindor.dsl.schema.controller import ControllerWebUIConfig, ControllerConfig
3
3
  from mindor.dsl.schema.component import ComponentConfig
4
4
  from mindor.dsl.schema.workflow import WorkflowConfig
5
5
  from mindor.core.controller.runner import ControllerRunner
6
+ from mindor.core.workflow.schema import create_workflow_schema
6
7
  from mindor.core.services import AsyncService
7
- from .schema import WorkflowSchema, WorkflowInputVariableResolver, WorkflowOutputVariableResolver
8
8
  from .gradio import GradioWebUIBuilder
9
9
 
10
- from fastapi import FastAPI, APIRouter, Body, HTTPException
11
- import asyncio, uvicorn
10
+ from fastapi import FastAPI
12
11
  from gradio import mount_gradio_app
13
- import gradio
12
+ import uvicorn, gradio
14
13
 
15
14
  class ControllerWebUI(AsyncService):
16
15
  def __init__(
@@ -38,7 +37,7 @@ class ControllerWebUI(AsyncService):
38
37
  def _configure_driver(self) -> None:
39
38
  if self.config.driver == "gradio":
40
39
  blocks: gradio.Blocks = GradioWebUIBuilder().build(
41
- schema=self._resolve_workflow_schema(),
40
+ schema=create_workflow_schema(self.workflows, self.components),
42
41
  runner=self._run_workflow
43
42
  )
44
43
  self.app = mount_gradio_app(self.app, blocks, path="")
@@ -59,17 +58,3 @@ class ControllerWebUI(AsyncService):
59
58
 
60
59
  async def _run_workflow(self, workflow_id: Optional[str], input: Any) -> Any:
61
60
  return await self.runner.run_workflow(workflow_id, input)
62
-
63
- def _resolve_workflow_schema(self) -> Dict[str, WorkflowSchema]:
64
- schema: Dict[str, WorkflowSchema] = {}
65
-
66
- for workflow_id, workflow in self.workflows.items():
67
- schema[workflow_id] = WorkflowSchema(
68
- name=workflow.name,
69
- title=workflow.title,
70
- description=workflow.description,
71
- input=WorkflowInputVariableResolver().resolve(workflow, self.components),
72
- output=WorkflowOutputVariableResolver().resolve(workflow, self.components)
73
- )
74
-
75
- return schema
@@ -1,4 +1,4 @@
1
- from typing import Type, Union, Literal, Optional, Dict, List, Tuple, Set, Annotated, AsyncIterator, Any
1
+ from typing import Optional, Dict, Tuple, AsyncIterator, Any
2
2
  from .http_request import build_request_body, parse_options_header
3
3
  from .streaming import StreamResource
4
4
  from requests.structures import CaseInsensitiveDict
@@ -0,0 +1,4 @@
1
+ from typing import Optional, Dict, Tuple, AsyncIterator, Any
2
+
3
+ class McpClient:
4
+ pass
@@ -2,7 +2,7 @@ from typing import Optional, AsyncIterator
2
2
  from abc import ABC, abstractmethod
3
3
  from tempfile import NamedTemporaryFile
4
4
  from aiofiles.threadpool.binary import AsyncBufferedReader
5
- import aiofiles, io, base64
5
+ import io, base64
6
6
 
7
7
  class StreamResource(ABC):
8
8
  def __init__(self, content_type: Optional[str], filename: Optional[str]):
@@ -208,9 +208,30 @@ class WorkflowOutputVariableResolver(WorkflowVariableResolver):
208
208
  return all(job_id not in job.depends_on for other_id, job in workflow.jobs.items() if other_id != job_id)
209
209
 
210
210
  class WorkflowSchema:
211
- def __init__(self, name: str, title: str, description: Optional[str], input: List[WorkflowVariableConfig], output: List[Union[WorkflowVariableConfig, WorkflowVariableGroupConfig]]):
211
+ def __init__(
212
+ self,
213
+ name: Optional[str],
214
+ title: Optional[str],
215
+ description: Optional[str],
216
+ input: List[WorkflowVariableConfig],
217
+ output: List[Union[WorkflowVariableConfig, WorkflowVariableGroupConfig]]
218
+ ):
212
219
  self.name: str = name
213
220
  self.title: str = title
214
221
  self.description: Optional[str] = description
215
222
  self.input: List[WorkflowVariableConfig] = input
216
223
  self.output: List[Union[WorkflowVariableConfig, WorkflowVariableGroupConfig]] = output
224
+
225
+ def create_workflow_schema(workflows: Dict[str, WorkflowConfig], components: Dict[str, ComponentConfig]) -> Dict[str, WorkflowSchema]:
226
+ schema: Dict[str, WorkflowSchema] = {}
227
+
228
+ for workflow_id, workflow in workflows.items():
229
+ schema[workflow_id] = WorkflowSchema(
230
+ name=workflow.name,
231
+ title=workflow.title,
232
+ description=workflow.description,
233
+ input=WorkflowInputVariableResolver().resolve(workflow, components),
234
+ output=WorkflowOutputVariableResolver().resolve(workflow, components)
235
+ )
236
+
237
+ return schema
@@ -3,3 +3,4 @@ from .http_server import *
3
3
  from .http_client import *
4
4
  from .mcp_server import *
5
5
  from .mcp_client import *
6
+ from .shell import *
@@ -0,0 +1,10 @@
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 CommonActionConfig
5
+
6
+ class ShellActionConfig(CommonActionConfig):
7
+ command: List[str] = Field(..., description="The shell command to execute, as a list of arguments.")
8
+ working_dir: Optional[str] = Field(default=None, description="Working directory for the command.")
9
+ env: Optional[Dict[str, str]] = Field(default_factory=dict, description="Environment variables to set when executing the command.")
10
+ timeout: Optional[float] = Field(default=None, description="Maximum time allowed for the command to run, in seconds.")
@@ -7,7 +7,8 @@ ComponentConfig = Annotated[
7
7
  HttpServerComponentConfig,
8
8
  HttpClientComponentConfig,
9
9
  McpServerComponentConfig,
10
- McpClientComponentConfig
10
+ McpClientComponentConfig,
11
+ ShellComponentConfig
11
12
  ],
12
13
  Field(discriminator="type")
13
14
  ]
@@ -3,3 +3,4 @@ from .http_server import *
3
3
  from .http_client import *
4
4
  from .mcp_server import *
5
5
  from .mcp_client import *
6
+ from .shell import *
@@ -7,5 +7,5 @@ from .common import ComponentType, CommonComponentConfig
7
7
  class HttpServerComponentConfig(CommonComponentConfig):
8
8
  type: Literal[ComponentType.HTTP_SERVER]
9
9
  port: Optional[int] = Field(None, ge=1, le=65535)
10
- base_path: Optional[str]
10
+ base_path: Optional[str] = None
11
11
  actions: Optional[Dict[str, HttpServerActionConfig]] = Field(default_factory=dict)
@@ -0,0 +1,19 @@
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 mindor.dsl.schema.action import ShellActionConfig
5
+ from .common import ComponentType, CommonComponentConfig
6
+
7
+ class ShellComponentConfig(CommonComponentConfig):
8
+ type: Literal[ComponentType.SHELL]
9
+ base_dir: Optional[str] = Field(default=None, description="Base working directory for all actions in this component.")
10
+ env: Optional[Dict[str, str]] = Field(default_factory=dict, description="Environment variables to set for all actions in this component.")
11
+ actions: Optional[Dict[str, ShellActionConfig]] = Field(default_factory=dict, description="Shell actions mapped by an identifier.")
12
+
13
+ @model_validator(mode="before")
14
+ def inflate_single_action(cls, values: Dict[str, Any]):
15
+ if "actions" not in values:
16
+ action_keys = set(ShellActionConfig.model_fields.keys())
17
+ if any(k in values for k in action_keys):
18
+ values["actions"] = { "__default__": { k: values.pop(k) for k in action_keys if k in values } }
19
+ return values
@@ -5,3 +5,4 @@ class ComponentType(str, Enum):
5
5
  HTTP_CLIENT = "http-client"
6
6
  MCP_SERVER = "mcp-server"
7
7
  MCP_CLIENT = "mcp-client"
8
+ SHELL = "shell"
@@ -4,6 +4,7 @@ from .types import ControllerType
4
4
  from .webui import ControllerWebUIConfig
5
5
 
6
6
  class CommonControllerConfig(BaseModel):
7
+ name: Optional[str] = None
7
8
  type: ControllerType
8
9
  runtime: Literal[ "docker", "native" ] = "native"
9
10
  max_concurrent_count: int = 1
@@ -5,4 +5,6 @@ from .common import ControllerType, CommonControllerConfig
5
5
 
6
6
  class McpServerControllerConfig(CommonControllerConfig):
7
7
  type: Literal[ControllerType.MCP_SERVER]
8
+ host: Optional[str] = "0.0.0.0"
8
9
  port: Optional[int] = 8080
10
+ base_path: Optional[str] = None
@@ -4,37 +4,24 @@ from pydantic import BaseModel, Field
4
4
  from pydantic import model_validator, field_validator
5
5
  from .component import ComponentConfig
6
6
 
7
- class JobConfig(BaseModel):
8
- component: Optional[Union[str, ComponentConfig]] = Field(default="__default__", description="The component to execute. Can be a string identifier or a ComponentConfig object.")
9
- action: Optional[str] = Field(default="__default__", description="The action to invoke within the component. Defaults to '__default__'.")
10
- input: Optional[Any] = Field(default=None, description="The input data passed to the component. Can be of any type.")
11
- output: Optional[Any] = Field(default=None, description="The expected output data from the component. Can be of any type.")
12
- repeat_count: Optional[Union[int, str]] = Field(default=1, description="Number of times to repeat the component execution. Must be at least 1.")
13
- depends_on: Optional[List[str]] = Field(default_factory=list, description="List of job names that this job depends on. Ensures execution order.")
14
-
15
- @field_validator("repeat_count")
16
- def validate_repeat_count(cls, value):
17
- if isinstance(value, int) and value < 1:
18
- raise ValueError("'repeat_count' must be at least 1")
19
- return value
20
-
21
7
  class WorkflowVariableType(str, Enum):
22
8
  # Primitive data types
23
- STRING = "string"
24
- TEXT = "text"
25
- INTEGER = "integer"
26
- NUMBER = "number"
27
- BOOLEAN = "boolean"
28
- JSON = "json"
9
+ STRING = "string"
10
+ TEXT = "text"
11
+ INTEGER = "integer"
12
+ NUMBER = "number"
13
+ BOOLEAN = "boolean"
14
+ JSON = "json"
29
15
  # Encoded data
30
- BASE64 = "base64"
16
+ BASE64 = "base64"
17
+ MARKDOWN = "markdown"
31
18
  # Media and files
32
- IMAGE = "image"
33
- AUDIO = "audio"
34
- VIDEO = "video"
35
- FILE = "file"
19
+ IMAGE = "image"
20
+ AUDIO = "audio"
21
+ VIDEO = "video"
22
+ FILE = "file"
36
23
  # UI-related types
37
- SELECT = "select"
24
+ SELECT = "select"
38
25
 
39
26
  class WorkflowVariableFormat(str, Enum):
40
27
  BASE64 = "base64"
@@ -58,6 +45,20 @@ class WorkflowVariableGroupConfig(BaseModel):
58
45
  variables: List[WorkflowVariableConfig] = Field(default_factory=list, description="List of variables included in this group")
59
46
  repeat_count: int = Field(default=1, description="The number of times this group of variables should be repeated")
60
47
 
48
+ class JobConfig(BaseModel):
49
+ component: Optional[Union[str, ComponentConfig]] = Field(default="__default__", description="The component to execute. Can be a string identifier or a ComponentConfig object.")
50
+ action: Optional[str] = Field(default="__default__", description="The action to invoke within the component. Defaults to '__default__'.")
51
+ input: Optional[Any] = Field(default=None, description="The input data passed to the component. Can be of any type.")
52
+ output: Optional[Any] = Field(default=None, description="The expected output data from the component. Can be of any type.")
53
+ repeat_count: Optional[Union[int, str]] = Field(default=1, description="Number of times to repeat the component execution. Must be at least 1.")
54
+ depends_on: Optional[List[str]] = Field(default_factory=list, description="List of job names that this job depends on. Ensures execution order.")
55
+
56
+ @field_validator("repeat_count")
57
+ def validate_repeat_count(cls, value):
58
+ if isinstance(value, int) and value < 1:
59
+ raise ValueError("'repeat_count' must be at least 1")
60
+ return value
61
+
61
62
  class WorkflowConfig(BaseModel):
62
63
  name: Optional[str] = None
63
64
  title: Optional[str] = None
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: model-compose
3
- Version: 0.1.6
3
+ Version: 0.1.7
4
4
  Summary: model-compose: Declarative AI Model and Workflow Orchestrator (part of the Mindor project)
5
5
  Author-email: Hanyeol Cho <hanyeol.cho@gmail.com>
6
6
  License-Expression: MIT
@@ -15,6 +15,7 @@ Requires-Dist: aiohttp
15
15
  Requires-Dist: requests
16
16
  Requires-Dist: fastapi
17
17
  Requires-Dist: uvicorn
18
+ Requires-Dist: mcp
18
19
  Requires-Dist: pyngrok
19
20
  Requires-Dist: ulid
20
21
  Requires-Dist: gradio
@@ -12,6 +12,9 @@ src/mindor/core/component/engine/base.py
12
12
  src/mindor/core/component/engine/context.py
13
13
  src/mindor/core/component/engine/http_client.py
14
14
  src/mindor/core/component/engine/http_server.py
15
+ src/mindor/core/component/engine/mcp_client.py
16
+ src/mindor/core/component/engine/mcp_server.py
17
+ src/mindor/core/component/engine/shell.py
15
18
  src/mindor/core/compose/__init__.py
16
19
  src/mindor/core/compose/compose.py
17
20
  src/mindor/core/compose/manager.py
@@ -28,7 +31,6 @@ src/mindor/core/controller/runner/mcp_client.py
28
31
  src/mindor/core/controller/runner/runner.py
29
32
  src/mindor/core/controller/webui/__init__.py
30
33
  src/mindor/core/controller/webui/gradio.py
31
- src/mindor/core/controller/webui/schema.py
32
34
  src/mindor/core/controller/webui/webui.py
33
35
  src/mindor/core/gateway/__init__.py
34
36
  src/mindor/core/gateway/gateway.py
@@ -49,6 +51,7 @@ src/mindor/core/utils/expiring.py
49
51
  src/mindor/core/utils/http_client.py
50
52
  src/mindor/core/utils/http_request.py
51
53
  src/mindor/core/utils/image.py
54
+ src/mindor/core/utils/mcp_client.py
52
55
  src/mindor/core/utils/streaming.py
53
56
  src/mindor/core/utils/template.py
54
57
  src/mindor/core/utils/time.py
@@ -56,6 +59,7 @@ src/mindor/core/utils/workqueue.py
56
59
  src/mindor/core/workflow/__init__.py
57
60
  src/mindor/core/workflow/context.py
58
61
  src/mindor/core/workflow/job.py
62
+ src/mindor/core/workflow/schema.py
59
63
  src/mindor/core/workflow/workflow.py
60
64
  src/mindor/dsl/__init__.py
61
65
  src/mindor/dsl/loader.py
@@ -70,6 +74,7 @@ src/mindor/dsl/schema/action/impl/http_client.py
70
74
  src/mindor/dsl/schema/action/impl/http_server.py
71
75
  src/mindor/dsl/schema/action/impl/mcp_client.py
72
76
  src/mindor/dsl/schema/action/impl/mcp_server.py
77
+ src/mindor/dsl/schema/action/impl/shell.py
73
78
  src/mindor/dsl/schema/component/__init__.py
74
79
  src/mindor/dsl/schema/component/component.py
75
80
  src/mindor/dsl/schema/component/impl/__init__.py
@@ -78,6 +83,7 @@ src/mindor/dsl/schema/component/impl/http_client.py
78
83
  src/mindor/dsl/schema/component/impl/http_server.py
79
84
  src/mindor/dsl/schema/component/impl/mcp_client.py
80
85
  src/mindor/dsl/schema/component/impl/mcp_server.py
86
+ src/mindor/dsl/schema/component/impl/shell.py
81
87
  src/mindor/dsl/schema/component/impl/types.py
82
88
  src/mindor/dsl/schema/controller/__init__.py
83
89
  src/mindor/dsl/schema/controller/controller.py
@@ -6,6 +6,7 @@ aiohttp
6
6
  requests
7
7
  fastapi
8
8
  uvicorn
9
+ mcp
9
10
  pyngrok
10
11
  ulid
11
12
  gradio
@@ -1,28 +0,0 @@
1
- from typing import Type, Union, Literal, Optional, Dict, List, Tuple, Set, Annotated, 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 .base import ControllerEngine, ControllerType, ControllerEngineMap
8
-
9
- class McpServerController(ControllerEngine):
10
- def __init__(
11
- self,
12
- config: McpServerControllerConfig,
13
- components: Dict[str, ComponentConfig],
14
- listeners: List[ListenerConfig],
15
- gateways: List[GatewayConfig],
16
- workflows: Dict[str, WorkflowConfig],
17
- env: Dict[str, str],
18
- daemon: bool
19
- ):
20
- super().__init__(config, components, listeners, gateways, workflows, env, daemon)
21
-
22
- async def _serve(self) -> None:
23
- pass
24
-
25
- async def _shutdown(self) -> None:
26
- pass
27
-
28
- ControllerEngineMap[ControllerType.MCP_SERVER] = McpServerController
File without changes
File without changes
File without changes