Skip to content

Commit 1e83f43

Browse files
committed
fix: add name-based MCP and A2A lookup
1 parent 6de5bed commit 1e83f43

10 files changed

Lines changed: 431 additions & 32 deletions

File tree

packages/uipath-platform/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "uipath-platform"
3-
version = "0.2.13"
3+
version = "0.2.14"
44
description = "HTTP client library for programmatic access to UiPath Platform"
55
readme = { file = "README.md", content-type = "text/markdown" }
66
requires-python = ">=3.11"

packages/uipath-platform/src/uipath/platform/agenthub/_remote_a2a_service.py

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,15 @@
77

88
import warnings
99
from typing import Any, List
10+
from urllib.parse import quote
1011

1112
from ..common._base_service import BaseService
1213
from ..common._bindings import resource_override
1314
from ..common._config import UiPathApiConfig
1415
from ..common._execution_context import UiPathExecutionContext
1516
from ..common._folder_context import FolderContext, header_folder
1617
from ..common._models import Endpoint, RequestSpec
18+
from ..common._resource_identifier import resolve_retrieve_identifier
1719
from ..orchestrator import FolderService
1820
from .remote_a2a import RemoteA2aAgent
1921

@@ -150,20 +152,23 @@ async def main():
150152
data = response.json()
151153
return [RemoteA2aAgent.model_validate(agent) for agent in data.get("value", [])]
152154

155+
@resource_override(resource_type="remoteA2aAgent", resource_identifier="name")
153156
@resource_override(resource_type="remoteA2aAgent", resource_identifier="slug")
154157
def retrieve(
155158
self,
156-
slug: str,
159+
slug: str | None = None,
157160
*,
161+
name: str | None = None,
158162
folder_path: str | None = None,
159163
) -> RemoteA2aAgent:
160-
"""Retrieve a specific Remote A2A agent by slug.
164+
"""Retrieve a Remote A2A agent by its display name or legacy slug.
161165
162166
.. warning::
163167
This method is experimental and subject to change.
164168
165169
Args:
166-
slug: The unique slug identifier for the agent.
170+
slug: The legacy slug identifier of the agent.
171+
name: The display name of the agent.
167172
folder_path: The folder path where the agent is located.
168173
169174
Returns:
@@ -183,7 +188,8 @@ def retrieve(
183188
"remote_a2a.retrieve is experimental and subject to change.",
184189
stacklevel=2,
185190
)
186-
spec = self._retrieve_spec(slug=slug, folder_path=folder_path)
191+
identifier = resolve_retrieve_identifier(name=name, slug=slug)
192+
spec = self._retrieve_spec(name=identifier, folder_path=folder_path)
187193
response = self.request(
188194
spec.method,
189195
url=spec.endpoint,
@@ -192,20 +198,23 @@ def retrieve(
192198
)
193199
return RemoteA2aAgent.model_validate(response.json())
194200

201+
@resource_override(resource_type="remoteA2aAgent", resource_identifier="name")
195202
@resource_override(resource_type="remoteA2aAgent", resource_identifier="slug")
196203
async def retrieve_async(
197204
self,
198-
slug: str,
205+
slug: str | None = None,
199206
*,
207+
name: str | None = None,
200208
folder_path: str | None = None,
201209
) -> RemoteA2aAgent:
202-
"""Asynchronously retrieve a specific Remote A2A agent by slug.
210+
"""Asynchronously retrieve a Remote A2A agent by display name or legacy slug.
203211
204212
.. warning::
205213
This method is experimental and subject to change.
206214
207215
Args:
208-
slug: The unique slug identifier for the agent.
216+
slug: The legacy slug identifier of the agent.
217+
name: The display name of the agent.
209218
folder_path: The folder path where the agent is located.
210219
211220
Returns:
@@ -229,7 +238,8 @@ async def main():
229238
"remote_a2a.retrieve_async is experimental and subject to change.",
230239
stacklevel=2,
231240
)
232-
spec = self._retrieve_spec(slug=slug, folder_path=folder_path)
241+
identifier = resolve_retrieve_identifier(name=name, slug=slug)
242+
spec = self._retrieve_spec(name=identifier, folder_path=folder_path)
233243
response = await self.request_async(
234244
spec.method,
235245
url=spec.endpoint,
@@ -279,14 +289,16 @@ def _list_spec(
279289

280290
def _retrieve_spec(
281291
self,
282-
slug: str,
292+
name: str,
283293
*,
284294
folder_path: str | None,
285295
) -> RequestSpec:
286296
folder_key = self._resolve_folder_key(folder_path)
287297
return RequestSpec(
288298
method="GET",
289-
endpoint=Endpoint(f"/agenthub_/api/remote-a2a-agents/{slug}"),
299+
endpoint=Endpoint(
300+
f"/agenthub_/api/remote-a2a-agents/{quote(name, safe='')}"
301+
),
290302
headers={
291303
**header_folder(folder_key, None),
292304
},
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
"""Shared retrieve-identifier resolution for name/slug resources (MCP servers and Remote A2A agents)."""
2+
3+
4+
def resolve_retrieve_identifier(name: str | None, slug: str | None) -> str:
5+
"""Resolve a retrieve identifier, preferring the display name over the legacy slug."""
6+
if name is not None and slug is not None:
7+
raise ValueError("Specify either 'name' or 'slug', not both.")
8+
if name is not None:
9+
return name
10+
if slug is not None:
11+
return slug
12+
raise TypeError("Either 'name' or 'slug' must be provided.")

packages/uipath-platform/src/uipath/platform/orchestrator/_mcp_service.py

Lines changed: 24 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from typing import List
2+
from urllib.parse import quote
23

34
from uipath.core.tracing import traced
45

@@ -8,6 +9,7 @@
89
from ..common._execution_context import UiPathExecutionContext
910
from ..common._folder_context import FolderContext, header_folder
1011
from ..common._models import Endpoint, RequestSpec
12+
from ..common._resource_identifier import resolve_retrieve_identifier
1113
from ._folder_service import FolderService
1214
from .mcp import McpServer
1315

@@ -66,7 +68,7 @@ def list(
6668

6769
return [McpServer.model_validate(server) for server in response.json()]
6870

69-
@traced(name="mcp_list", run_type="uipath")
71+
@traced(name="mcp_list_async", run_type="uipath")
7072
async def list_async(
7173
self,
7274
*,
@@ -109,18 +111,21 @@ async def main():
109111

110112
return [McpServer.model_validate(server) for server in response.json()]
111113

114+
@resource_override(resource_type="mcpServer", resource_identifier="name")
112115
@resource_override(resource_type="mcpServer", resource_identifier="slug")
113116
@traced(name="mcp_retrieve", run_type="uipath")
114117
def retrieve(
115118
self,
116-
slug: str,
119+
slug: str | None = None,
117120
*,
121+
name: str | None = None,
118122
folder_path: str | None = None,
119123
) -> McpServer:
120-
"""Retrieve a specific MCP server by its slug.
124+
"""Retrieve a specific MCP server by its display name or legacy slug.
121125
122126
Args:
123-
slug (str): The unique slug identifier for the server.
127+
slug (Optional[str]): The legacy slug identifier of the server.
128+
name (Optional[str]): The display name of the server.
124129
folder_path (Optional[str]): The path of the folder where the server is located.
125130
126131
Returns:
@@ -132,12 +137,13 @@ def retrieve(
132137
133138
client = UiPath()
134139
135-
server = client.mcp.retrieve(slug="my-server-slug", folder_path="MyFolder")
140+
server = client.mcp.retrieve(name="My Server", folder_path="MyFolder")
136141
print(f"Server: {server.name}, URL: {server.mcp_url}")
137142
```
138143
"""
144+
identifier = resolve_retrieve_identifier(name=name, slug=slug)
139145
spec = self._retrieve_spec(
140-
slug=slug,
146+
name=identifier,
141147
folder_path=folder_path,
142148
)
143149

@@ -150,18 +156,21 @@ def retrieve(
150156

151157
return McpServer.model_validate(response.json())
152158

159+
@resource_override(resource_type="mcpServer", resource_identifier="name")
153160
@resource_override(resource_type="mcpServer", resource_identifier="slug")
154-
@traced(name="mcp_retrieve", run_type="uipath")
161+
@traced(name="mcp_retrieve_async", run_type="uipath")
155162
async def retrieve_async(
156163
self,
157-
slug: str,
164+
slug: str | None = None,
158165
*,
166+
name: str | None = None,
159167
folder_path: str | None = None,
160168
) -> McpServer:
161-
"""Asynchronously retrieve a specific MCP server by its slug.
169+
"""Asynchronously retrieve an MCP server by its display name or legacy slug.
162170
163171
Args:
164-
slug (str): The unique slug identifier for the server.
172+
slug (Optional[str]): The legacy slug identifier of the server.
173+
name (Optional[str]): The display name of the server.
165174
folder_path (Optional[str]): The path of the folder where the server is located.
166175
167176
Returns:
@@ -176,14 +185,15 @@ async def retrieve_async(
176185
sdk = UiPath()
177186
178187
async def main():
179-
server = await sdk.mcp.retrieve_async(slug="my-server-slug", folder_path="MyFolder")
188+
server = await sdk.mcp.retrieve_async(name="My Server", folder_path="MyFolder")
180189
print(f"Server: {server.name}, URL: {server.mcp_url}")
181190
182191
asyncio.run(main())
183192
```
184193
"""
194+
identifier = resolve_retrieve_identifier(name=name, slug=slug)
185195
spec = self._retrieve_spec(
186-
slug=slug,
196+
name=identifier,
187197
folder_path=folder_path,
188198
)
189199

@@ -223,14 +233,14 @@ def _list_spec(
223233

224234
def _retrieve_spec(
225235
self,
226-
slug: str,
236+
name: str,
227237
*,
228238
folder_path: str | None,
229239
) -> RequestSpec:
230240
folder_key = self._resolve_folder_key(folder_path)
231241
return RequestSpec(
232242
method="GET",
233-
endpoint=Endpoint(f"/agenthub_/api/servers/{slug}"),
243+
endpoint=Endpoint(f"/agenthub_/api/servers/{quote(name, safe='')}"),
234244
headers={
235245
**header_folder(folder_key, None),
236246
},

0 commit comments

Comments
 (0)