Skip to content

Commit b4ff199

Browse files
committed
fix: add name-based MCP and A2A lookup
1 parent 49b881d commit b4ff199

10 files changed

Lines changed: 430 additions & 34 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 & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,10 @@
77

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

1112
from ..common._base_service import BaseService
12-
from ..common._bindings import resource_override
13+
from ..common._bindings import resolve_retrieve_identifier, resource_override
1314
from ..common._config import UiPathApiConfig
1415
from ..common._execution_context import UiPathExecutionContext
1516
from ..common._folder_context import FolderContext, header_folder
@@ -150,20 +151,23 @@ async def main():
150151
data = response.json()
151152
return [RemoteA2aAgent.model_validate(agent) for agent in data.get("value", [])]
152153

154+
@resource_override(resource_type="remoteA2aAgent", resource_identifier="name")
153155
@resource_override(resource_type="remoteA2aAgent", resource_identifier="slug")
154156
def retrieve(
155157
self,
156-
slug: str,
158+
slug: str | None = None,
157159
*,
160+
name: str | None = None,
158161
folder_path: str | None = None,
159162
) -> RemoteA2aAgent:
160-
"""Retrieve a specific Remote A2A agent by slug.
163+
"""Retrieve a Remote A2A agent by its display name or legacy slug.
161164
162165
.. warning::
163166
This method is experimental and subject to change.
164167
165168
Args:
166-
slug: The unique slug identifier for the agent.
169+
slug: The legacy slug identifier of the agent.
170+
name: The display name of the agent.
167171
folder_path: The folder path where the agent is located.
168172
169173
Returns:
@@ -183,7 +187,8 @@ def retrieve(
183187
"remote_a2a.retrieve is experimental and subject to change.",
184188
stacklevel=2,
185189
)
186-
spec = self._retrieve_spec(slug=slug, folder_path=folder_path)
190+
identifier = resolve_retrieve_identifier(name=name, slug=slug)
191+
spec = self._retrieve_spec(name=identifier, folder_path=folder_path)
187192
response = self.request(
188193
spec.method,
189194
url=spec.endpoint,
@@ -192,20 +197,23 @@ def retrieve(
192197
)
193198
return RemoteA2aAgent.model_validate(response.json())
194199

200+
@resource_override(resource_type="remoteA2aAgent", resource_identifier="name")
195201
@resource_override(resource_type="remoteA2aAgent", resource_identifier="slug")
196202
async def retrieve_async(
197203
self,
198-
slug: str,
204+
slug: str | None = None,
199205
*,
206+
name: str | None = None,
200207
folder_path: str | None = None,
201208
) -> RemoteA2aAgent:
202-
"""Asynchronously retrieve a specific Remote A2A agent by slug.
209+
"""Asynchronously retrieve a Remote A2A agent by display name or legacy slug.
203210
204211
.. warning::
205212
This method is experimental and subject to change.
206213
207214
Args:
208-
slug: The unique slug identifier for the agent.
215+
slug: The legacy slug identifier of the agent.
216+
name: The display name of the agent.
209217
folder_path: The folder path where the agent is located.
210218
211219
Returns:
@@ -229,7 +237,8 @@ async def main():
229237
"remote_a2a.retrieve_async is experimental and subject to change.",
230238
stacklevel=2,
231239
)
232-
spec = self._retrieve_spec(slug=slug, folder_path=folder_path)
240+
identifier = resolve_retrieve_identifier(name=name, slug=slug)
241+
spec = self._retrieve_spec(name=identifier, folder_path=folder_path)
233242
response = await self.request_async(
234243
spec.method,
235244
url=spec.endpoint,
@@ -279,14 +288,16 @@ def _list_spec(
279288

280289
def _retrieve_spec(
281290
self,
282-
slug: str,
291+
name: str,
283292
*,
284293
folder_path: str | None,
285294
) -> RequestSpec:
286295
folder_key = self._resolve_folder_key(folder_path)
287296
return RequestSpec(
288297
method="GET",
289-
endpoint=Endpoint(f"/agenthub_/api/remote-a2a-agents/{slug}"),
298+
endpoint=Endpoint(
299+
f"/agenthub_/api/remote-a2a-agents/{quote(name, safe='')}"
300+
),
290301
headers={
291302
**header_folder(folder_key, None),
292303
},

packages/uipath-platform/src/uipath/platform/common/_bindings.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -322,3 +322,14 @@ def wrapper(*args, **kwargs):
322322
return wrapper
323323

324324
return decorator
325+
326+
327+
def resolve_retrieve_identifier(name: str | None, slug: str | None) -> str:
328+
"""Resolve a retrieve identifier, preferring the display name over the legacy slug."""
329+
if name is not None and slug is not None:
330+
raise ValueError("Specify either 'name' or 'slug', not both.")
331+
if name is not None:
332+
return name
333+
if slug is not None:
334+
return slug
335+
raise TypeError("Either 'name' or 'slug' must be provided.")

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

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

34
from uipath.core.tracing import traced
45

56
from ..common._base_service import BaseService
6-
from ..common._bindings import resource_override
7+
from ..common._bindings import resolve_retrieve_identifier, resource_override
78
from ..common._config import UiPathApiConfig
89
from ..common._execution_context import UiPathExecutionContext
910
from ..common._folder_context import FolderContext, header_folder
@@ -66,7 +67,7 @@ def list(
6667

6768
return [McpServer.model_validate(server) for server in response.json()]
6869

69-
@traced(name="mcp_list", run_type="uipath")
70+
@traced(name="mcp_list_async", run_type="uipath")
7071
async def list_async(
7172
self,
7273
*,
@@ -109,18 +110,21 @@ async def main():
109110

110111
return [McpServer.model_validate(server) for server in response.json()]
111112

113+
@resource_override(resource_type="mcpServer", resource_identifier="name")
112114
@resource_override(resource_type="mcpServer", resource_identifier="slug")
113115
@traced(name="mcp_retrieve", run_type="uipath")
114116
def retrieve(
115117
self,
116-
slug: str,
118+
slug: str | None = None,
117119
*,
120+
name: str | None = None,
118121
folder_path: str | None = None,
119122
) -> McpServer:
120-
"""Retrieve a specific MCP server by its slug.
123+
"""Retrieve a specific MCP server by its display name or legacy slug.
121124
122125
Args:
123-
slug (str): The unique slug identifier for the server.
126+
slug (Optional[str]): The legacy slug identifier of the server.
127+
name (Optional[str]): The display name of the server.
124128
folder_path (Optional[str]): The path of the folder where the server is located.
125129
126130
Returns:
@@ -132,12 +136,13 @@ def retrieve(
132136
133137
client = UiPath()
134138
135-
server = client.mcp.retrieve(slug="my-server-slug", folder_path="MyFolder")
139+
server = client.mcp.retrieve(name="My Server", folder_path="MyFolder")
136140
print(f"Server: {server.name}, URL: {server.mcp_url}")
137141
```
138142
"""
143+
identifier = resolve_retrieve_identifier(name=name, slug=slug)
139144
spec = self._retrieve_spec(
140-
slug=slug,
145+
name=identifier,
141146
folder_path=folder_path,
142147
)
143148

@@ -150,18 +155,21 @@ def retrieve(
150155

151156
return McpServer.model_validate(response.json())
152157

158+
@resource_override(resource_type="mcpServer", resource_identifier="name")
153159
@resource_override(resource_type="mcpServer", resource_identifier="slug")
154-
@traced(name="mcp_retrieve", run_type="uipath")
160+
@traced(name="mcp_retrieve_async", run_type="uipath")
155161
async def retrieve_async(
156162
self,
157-
slug: str,
163+
slug: str | None = None,
158164
*,
165+
name: str | None = None,
159166
folder_path: str | None = None,
160167
) -> McpServer:
161-
"""Asynchronously retrieve a specific MCP server by its slug.
168+
"""Asynchronously retrieve an MCP server by its display name or legacy slug.
162169
163170
Args:
164-
slug (str): The unique slug identifier for the server.
171+
slug (Optional[str]): The legacy slug identifier of the server.
172+
name (Optional[str]): The display name of the server.
165173
folder_path (Optional[str]): The path of the folder where the server is located.
166174
167175
Returns:
@@ -176,14 +184,15 @@ async def retrieve_async(
176184
sdk = UiPath()
177185
178186
async def main():
179-
server = await sdk.mcp.retrieve_async(slug="my-server-slug", folder_path="MyFolder")
187+
server = await sdk.mcp.retrieve_async(name="My Server", folder_path="MyFolder")
180188
print(f"Server: {server.name}, URL: {server.mcp_url}")
181189
182190
asyncio.run(main())
183191
```
184192
"""
193+
identifier = resolve_retrieve_identifier(name=name, slug=slug)
185194
spec = self._retrieve_spec(
186-
slug=slug,
195+
name=identifier,
187196
folder_path=folder_path,
188197
)
189198

@@ -223,14 +232,14 @@ def _list_spec(
223232

224233
def _retrieve_spec(
225234
self,
226-
slug: str,
235+
name: str,
227236
*,
228237
folder_path: str | None,
229238
) -> RequestSpec:
230239
folder_key = self._resolve_folder_key(folder_path)
231240
return RequestSpec(
232241
method="GET",
233-
endpoint=Endpoint(f"/agenthub_/api/servers/{slug}"),
242+
endpoint=Endpoint(f"/agenthub_/api/servers/{quote(name, safe='')}"),
234243
headers={
235244
**header_folder(folder_key, None),
236245
},

0 commit comments

Comments
 (0)