Skip to content

Commit 2ae41d6

Browse files
committed
chore: update uipath sdk to 2.8
1 parent 93a4c2b commit 2ae41d6

File tree

14 files changed

+686
-1462
lines changed

14 files changed

+686
-1462
lines changed

pyproject.toml

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
[project]
22
name = "uipath-mcp"
3-
version = "0.0.108"
3+
version = "0.1.0"
44
description = "UiPath MCP SDK"
55
readme = { file = "README.md", content-type = "text/markdown" }
66
requires-python = ">=3.11"
77
dependencies = [
8-
"mcp==1.24.0",
8+
"mcp==1.26.0",
99
"pysignalr==1.3.0",
10-
"uipath>=2.1.108, <2.2.0",
10+
"uipath>=2.8.23, <2.9.0",
11+
"uipath-runtime>=0.8.0, <0.9.0",
1112
]
1213
classifiers = [
1314
"Development Status :: 3 - Alpha",
@@ -25,6 +26,9 @@ maintainers = [
2526
[project.entry-points."uipath.middlewares"]
2627
register = "uipath_mcp.middlewares:register_middleware"
2728

29+
[project.entry-points."uipath.runtime.factories"]
30+
mcp = "uipath_mcp._cli._runtime:register_runtime_factory"
31+
2832
[project.urls]
2933
Homepage = "https://uipath.com"
3034
Repository = "https://github.com/UiPath/uipath-mcp-python"
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
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+
__all__ = [
27+
"register_runtime_factory",
28+
"UiPathMcpRuntimeFactory",
29+
"UiPathMcpRuntime",
30+
]

src/uipath_mcp/_cli/_runtime/_context.py

Lines changed: 0 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,4 @@
11
from enum import Enum
2-
from typing import Optional
3-
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
432

443

454
class UiPathServerType(Enum):

src/uipath_mcp/_cli/_runtime/_exception.py

Lines changed: 2 additions & 2 deletions
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,
@@ -27,5 +27,5 @@ def __init__(
2727
status: Optional[int] = None,
2828
):
2929
super().__init__(
30-
code.value, title, detail, category, status, prefix="LlamaIndex"
30+
code.value, title, detail, category, status, prefix="UiPathMCP"
3131
)
Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
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+
UiPathRuntimeFactorySettings,
12+
UiPathRuntimeProtocol,
13+
)
14+
from uipath.runtime.errors import UiPathErrorCategory
15+
from uipath.runtime.storage import UiPathRuntimeStorageProtocol
16+
17+
from uipath_mcp._cli._runtime._exception import McpErrorCode, UiPathMcpRuntimeError
18+
from uipath_mcp._cli._runtime._runtime import UiPathMcpRuntime
19+
from uipath_mcp._cli._utils._config import McpConfig
20+
21+
logger = logging.getLogger(__name__)
22+
23+
24+
class UiPathMcpRuntimeFactory:
25+
"""Factory for creating MCP runtimes from mcp.json configuration."""
26+
27+
def __init__(
28+
self,
29+
context: UiPathRuntimeContext,
30+
):
31+
"""Initialize the factory.
32+
33+
Args:
34+
context: UiPathRuntimeContext to use for runtime creation.
35+
"""
36+
self.context = context
37+
self._mcp_config: McpConfig | None = None
38+
39+
def _load_mcp_config(self) -> McpConfig:
40+
"""Load mcp.json configuration."""
41+
if self._mcp_config is None:
42+
self._mcp_config = McpConfig()
43+
return self._mcp_config
44+
45+
def discover_entrypoints(self) -> list[str]:
46+
"""Discover all MCP server entrypoints.
47+
48+
Returns:
49+
List of server names that can be used as entrypoints.
50+
"""
51+
mcp_config = self._load_mcp_config()
52+
if not mcp_config.exists:
53+
return []
54+
return mcp_config.get_server_names()
55+
56+
def _mcp_slug(self, entrypoint: str) -> str:
57+
"""Loads the mcp slug from the uipath.config if available, otherwise it will use the entrypoint."""
58+
59+
logger.info(f"Loading slug from config {self.context.config_path}")
60+
if os.path.exists(self.context.config_path):
61+
config: dict[str, Any] = {}
62+
with open(self.context.config_path, "r") as f:
63+
config = json.load(f)
64+
65+
server_slug = (
66+
config.get("runtime", {})
67+
.get("fpsContext", {})
68+
.get("mcpServer.slug", None)
69+
)
70+
if server_slug is not None:
71+
logger.info(f"Server slug in config '{server_slug}'.")
72+
return server_slug
73+
74+
logger.info(
75+
f"No server slug found in config, using entrypoint '{entrypoint}' as slug."
76+
)
77+
return entrypoint
78+
79+
async def new_runtime(
80+
self, entrypoint: str, runtime_id: str, **kwargs: Any
81+
) -> UiPathRuntimeProtocol:
82+
"""Create a new MCP runtime instance.
83+
84+
Args:
85+
entrypoint: Server name from mcp.json.
86+
runtime_id: Unique identifier for the runtime instance.
87+
88+
Returns:
89+
Configured UiPathMcpRuntime instance.
90+
91+
Raises:
92+
UiPathMcpRuntimeError: If configuration is invalid or server not found.
93+
"""
94+
mcp_config = self._load_mcp_config()
95+
96+
if not mcp_config.exists:
97+
raise UiPathMcpRuntimeError(
98+
McpErrorCode.CONFIGURATION_ERROR,
99+
"Invalid configuration",
100+
"mcp.json not found",
101+
UiPathErrorCategory.DEPLOYMENT,
102+
)
103+
104+
server = mcp_config.get_server(entrypoint)
105+
logger.info("Creating MCP runtime for entrypoint '%s'", entrypoint)
106+
logger.info(server)
107+
if not server:
108+
available = ", ".join(mcp_config.get_server_names())
109+
raise UiPathMcpRuntimeError(
110+
McpErrorCode.SERVER_NOT_FOUND,
111+
"MCP server not found",
112+
f"Server '{entrypoint}' not found. Available: {available}",
113+
UiPathErrorCategory.DEPLOYMENT,
114+
)
115+
116+
# Validate runtime_id is a valid UUID, generate new one if not
117+
try:
118+
uuid.UUID(runtime_id)
119+
except ValueError:
120+
new_id = str(uuid.uuid4())
121+
logger.warning(
122+
"Invalid runtime_id '%s' is not a valid UUID; generated '%s'",
123+
runtime_id,
124+
new_id,
125+
)
126+
runtime_id = new_id
127+
128+
return UiPathMcpRuntime(
129+
server=server,
130+
runtime_id=runtime_id,
131+
entrypoint=entrypoint,
132+
folder_key=self.context.folder_key,
133+
server_id=self.context.mcp_server_id,
134+
server_slug=self._mcp_slug(entrypoint),
135+
)
136+
137+
async def get_storage(self) -> UiPathRuntimeStorageProtocol | None:
138+
"""Get factory storage.
139+
140+
MCP servers are long-running processes and don't need
141+
cross-invocation state persistence.
142+
"""
143+
return None
144+
145+
async def get_settings(self) -> UiPathRuntimeFactorySettings | None:
146+
"""Get factory settings."""
147+
return None
148+
149+
async def dispose(self) -> None:
150+
"""Cleanup factory resources."""
151+
self._mcp_config = None

0 commit comments

Comments
 (0)