Skip to content

Commit e7df223

Browse files
committed
Update uipath 2.2
1 parent d6aafff commit e7df223

File tree

14 files changed

+649
-1461
lines changed

14 files changed

+649
-1461
lines changed

.vscode/settings.json

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
{
2+
"window.title": "${rootName}${separator}${activeEditorMedium}",
3+
"files.exclude": {
4+
"**/*.pyc": true,
5+
"**/__pycache__": true,
6+
".pytest_cache": true,
7+
".mypy_cache": true,
8+
".ruff_cache": true,
9+
".venv": true
10+
},
11+
"search.exclude": {
12+
"**/__pycache__": true,
13+
"**/*.pyc": true,
14+
".venv": true,
15+
".pytest_cache": true,
16+
".mypy_cache": true,
17+
".ruff_cache": true
18+
},
19+
// Formatting
20+
"editor.formatOnSave": true,
21+
"[python]": {
22+
"editor.defaultFormatter": "charliermarsh.ruff",
23+
"editor.codeActionsOnSave": {
24+
"source.organizeImports": "explicit"
25+
}
26+
},
27+
"workbench.colorCustomizations": {
28+
"titleBar.activeBackground": "#0099cc",
29+
"titleBar.inactiveBackground": "#0099cc"
30+
},
31+
"python.testing.pytestArgs": [
32+
"tests"
33+
],
34+
"python.testing.unittestEnabled": false,
35+
"python.testing.pytestEnabled": true
36+
}

pyproject.toml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ requires-python = ">=3.11"
77
dependencies = [
88
"mcp==1.11.0",
99
"pysignalr==1.3.0",
10-
"uipath>=2.1.108, <2.2.0",
10+
"uipath>=2.2.16, <2.3.0",
1111
]
1212
classifiers = [
1313
"Development Status :: 3 - Alpha",
@@ -25,6 +25,9 @@ maintainers = [
2525
[project.entry-points."uipath.middlewares"]
2626
register = "uipath_mcp.middlewares:register_middleware"
2727

28+
[project.entry-points."uipath.runtime.factories"]
29+
mcp = "uipath_mcp._cli._runtime:register_runtime_factory"
30+
2831
[project.urls]
2932
Homepage = "https://uipath.com"
3033
Repository = "https://github.com/UiPath/uipath-mcp-python"
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
"""UiPath MCP Runtime package."""
2+
3+
from uipath.runtime import (
4+
UiPathRuntimeContext,
5+
UiPathRuntimeFactoryProtocol,
6+
UiPathRuntimeFactoryRegistry,
7+
)
8+
9+
from uipath_mcp._cli._runtime._factory import UiPathMcpRuntimeFactory
10+
from uipath_mcp._cli._runtime._runtime import UiPathMcpRuntime
11+
12+
13+
def register_runtime_factory() -> None:
14+
"""Register the MCP factory. Called automatically via entry point."""
15+
16+
def create_factory(
17+
context: UiPathRuntimeContext | None = None,
18+
) -> UiPathRuntimeFactoryProtocol:
19+
return UiPathMcpRuntimeFactory(
20+
context=context if context else UiPathRuntimeContext(),
21+
)
22+
23+
UiPathRuntimeFactoryRegistry.register("mcp", create_factory, "mcp.json")
24+
25+
26+
register_runtime_factory()
27+
28+
__all__ = [
29+
"register_runtime_factory",
30+
"UiPathMcpRuntimeFactory",
31+
"UiPathMcpRuntime",
32+
]

src/uipath_mcp/_cli/_runtime/_context.py

Lines changed: 0 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,6 @@
11
from enum import Enum
22
from typing import Optional
33

4-
from uipath._cli._runtime._contracts import UiPathRuntimeContext
5-
6-
from .._utils._config import McpConfig
7-
8-
9-
class UiPathMcpRuntimeContext(UiPathRuntimeContext):
10-
"""Context information passed throughout the runtime execution."""
11-
12-
config: Optional[McpConfig] = None
13-
folder_key: Optional[str] = None
14-
server_id: Optional[str] = None
15-
server_slug: Optional[str] = None
16-
17-
@classmethod
18-
def from_config(
19-
cls, config_path: str | None = None, **kwargs: object
20-
) -> "UiPathMcpRuntimeContext":
21-
"""Load configuration from uipath.json file with MCP-specific handling."""
22-
# Use the parent's implementation
23-
instance = super().from_config(config_path, **kwargs)
24-
25-
# Convert to our type (since parent returns UiPathRuntimeContext)
26-
mcp_instance = cls(**instance.model_dump())
27-
28-
# Add AgentHub-specific configuration handling
29-
import json
30-
import os
31-
32-
path = config_path or "uipath.json"
33-
if os.path.exists(path):
34-
with open(path, "r") as f:
35-
config = json.load(f)
36-
37-
config_runtime = config.get("runtime", {})
38-
if "fpsContext" in config_runtime:
39-
fps_context = config_runtime["fpsContext"]
40-
mcp_instance.server_id = fps_context.get("Id")
41-
mcp_instance.server_slug = fps_context.get("Slug")
42-
return mcp_instance
43-
444

455
class UiPathServerType(Enum):
466
"""Defines the different types of UiPath servers used in the MCP ecosystem.

src/uipath_mcp/_cli/_runtime/_exception.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from enum import Enum
22
from typing import Optional, Union
33

4-
from uipath._cli._runtime._contracts import (
4+
from uipath.runtime.errors import (
55
UiPathBaseRuntimeError,
66
UiPathErrorCategory,
77
UiPathErrorCode,
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
"""Factory for creating MCP runtime instances."""
2+
3+
import json
4+
import logging
5+
import os
6+
import uuid
7+
from typing import Any
8+
9+
from uipath.runtime import (
10+
UiPathRuntimeContext,
11+
UiPathRuntimeProtocol,
12+
)
13+
from uipath.runtime.errors import UiPathErrorCategory
14+
15+
from uipath_mcp._cli._runtime._exception import McpErrorCode, UiPathMcpRuntimeError
16+
from uipath_mcp._cli._runtime._runtime import UiPathMcpRuntime
17+
from uipath_mcp._cli._utils._config import McpConfig
18+
19+
logger = logging.getLogger(__name__)
20+
21+
22+
class UiPathMcpRuntimeFactory:
23+
"""Factory for creating MCP runtimes from mcp.json configuration."""
24+
25+
def __init__(
26+
self,
27+
context: UiPathRuntimeContext,
28+
):
29+
"""Initialize the factory.
30+
31+
Args:
32+
context: UiPathRuntimeContext to use for runtime creation.
33+
"""
34+
self.context = context
35+
self._mcp_config: McpConfig | None = None
36+
self._server_id: str | None = None
37+
self._server_slug: str | None = None
38+
39+
# Load fps context from uipath.json if available
40+
self._load_fps_context()
41+
42+
def _load_fps_context(self) -> None:
43+
"""
44+
Load fps context from uipath.json for server registration.
45+
"""
46+
config_path = self.context.config_path or "uipath.json"
47+
if os.path.exists(config_path):
48+
try:
49+
with open(config_path, "r") as f:
50+
config: dict[str, Any] = json.load(f)
51+
52+
config_runtime = config.get("runtime", {})
53+
if "fpsContext" in config_runtime:
54+
fps_context = config_runtime["fpsContext"]
55+
self._server_id = fps_context.get("Id")
56+
self._server_slug = fps_context.get("Slug")
57+
except Exception as e:
58+
logger.warning(f"Failed to load fps context: {e}")
59+
60+
def _load_mcp_config(self) -> McpConfig:
61+
"""Load mcp.json configuration."""
62+
if self._mcp_config is None:
63+
self._mcp_config = McpConfig()
64+
return self._mcp_config
65+
66+
def discover_entrypoints(self) -> list[str]:
67+
"""Discover all MCP server entrypoints.
68+
69+
Returns:
70+
List of server names that can be used as entrypoints.
71+
"""
72+
mcp_config = self._load_mcp_config()
73+
if not mcp_config.exists:
74+
return []
75+
return mcp_config.get_server_names()
76+
77+
async def discover_runtimes(self) -> list[UiPathRuntimeProtocol]:
78+
"""Discover runtime instances for all entrypoints.
79+
This is not running as part of a job, but is intended for the dev machine.
80+
81+
Returns:
82+
List of UiPathMcpRuntime instances, one per entrypoint.
83+
"""
84+
entrypoints = self.discover_entrypoints()
85+
runtimes: list[UiPathRuntimeProtocol] = []
86+
87+
for entrypoint in entrypoints:
88+
runtime = await self.new_runtime(entrypoint, entrypoint)
89+
runtimes.append(runtime)
90+
91+
return runtimes
92+
93+
async def new_runtime(
94+
self, entrypoint: str, runtime_id: str
95+
) -> UiPathRuntimeProtocol:
96+
"""Create a new MCP runtime instance.
97+
98+
Args:
99+
entrypoint: Server name from mcp.json.
100+
runtime_id: Unique identifier for the runtime instance.
101+
102+
Returns:
103+
Configured UiPathMcpRuntime instance.
104+
105+
Raises:
106+
UiPathMcpRuntimeError: If configuration is invalid or server not found.
107+
"""
108+
mcp_config = self._load_mcp_config()
109+
110+
if not mcp_config.exists:
111+
raise UiPathMcpRuntimeError(
112+
McpErrorCode.CONFIGURATION_ERROR,
113+
"Invalid configuration",
114+
"mcp.json not found",
115+
UiPathErrorCategory.DEPLOYMENT,
116+
)
117+
118+
server = mcp_config.get_server(entrypoint)
119+
if not server:
120+
available = ", ".join(mcp_config.get_server_names())
121+
raise UiPathMcpRuntimeError(
122+
McpErrorCode.SERVER_NOT_FOUND,
123+
"MCP server not found",
124+
f"Server '{entrypoint}' not found. Available: {available}",
125+
UiPathErrorCategory.DEPLOYMENT,
126+
)
127+
128+
# Validate runtime_id is a valid UUID, generate new one if not
129+
try:
130+
uuid.UUID(runtime_id)
131+
except ValueError:
132+
runtime_id = str(uuid.uuid4())
133+
134+
return UiPathMcpRuntime(
135+
server=server,
136+
runtime_id=runtime_id,
137+
entrypoint=entrypoint,
138+
folder_key=self.context.folder_key,
139+
server_id=self._server_id,
140+
server_slug=self._server_slug,
141+
)
142+
143+
async def dispose(self) -> None:
144+
"""Cleanup factory resources."""
145+
self._mcp_config = None

0 commit comments

Comments
 (0)