optimizely-opal.opal-tools-sdk 0.1.11.dev0__tar.gz → 0.1.14.dev0__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 (22) hide show
  1. {optimizely_opal_opal_tools_sdk-0.1.11.dev0 → optimizely_opal_opal_tools_sdk-0.1.14.dev0}/PKG-INFO +5 -1
  2. {optimizely_opal_opal_tools_sdk-0.1.11.dev0 → optimizely_opal_opal_tools_sdk-0.1.14.dev0}/opal_tools_sdk/__init__.py +6 -2
  3. {optimizely_opal_opal_tools_sdk-0.1.11.dev0 → optimizely_opal_opal_tools_sdk-0.1.14.dev0}/opal_tools_sdk/decorators.py +92 -2
  4. {optimizely_opal_opal_tools_sdk-0.1.11.dev0 → optimizely_opal_opal_tools_sdk-0.1.14.dev0}/opal_tools_sdk/models.py +4 -0
  5. optimizely_opal_opal_tools_sdk-0.1.14.dev0/opal_tools_sdk/proteus.py +2415 -0
  6. {optimizely_opal_opal_tools_sdk-0.1.11.dev0 → optimizely_opal_opal_tools_sdk-0.1.14.dev0}/opal_tools_sdk/service.py +89 -4
  7. optimizely_opal_opal_tools_sdk-0.1.14.dev0/opal_tools_sdk/ui.py +3 -0
  8. {optimizely_opal_opal_tools_sdk-0.1.11.dev0 → optimizely_opal_opal_tools_sdk-0.1.14.dev0}/optimizely_opal.opal_tools_sdk.egg-info/PKG-INFO +5 -1
  9. {optimizely_opal_opal_tools_sdk-0.1.11.dev0 → optimizely_opal_opal_tools_sdk-0.1.14.dev0}/optimizely_opal.opal_tools_sdk.egg-info/SOURCES.txt +5 -1
  10. optimizely_opal_opal_tools_sdk-0.1.14.dev0/optimizely_opal.opal_tools_sdk.egg-info/requires.txt +8 -0
  11. {optimizely_opal_opal_tools_sdk-0.1.11.dev0 → optimizely_opal_opal_tools_sdk-0.1.14.dev0}/pyproject.toml +10 -9
  12. {optimizely_opal_opal_tools_sdk-0.1.11.dev0 → optimizely_opal_opal_tools_sdk-0.1.14.dev0}/setup.py +1 -1
  13. optimizely_opal_opal_tools_sdk-0.1.14.dev0/tests/test_integration.py +352 -0
  14. optimizely_opal_opal_tools_sdk-0.1.14.dev0/tests/test_proteus.py +106 -0
  15. optimizely_opal_opal_tools_sdk-0.1.11.dev0/optimizely_opal.opal_tools_sdk.egg-info/requires.txt +0 -3
  16. {optimizely_opal_opal_tools_sdk-0.1.11.dev0 → optimizely_opal_opal_tools_sdk-0.1.14.dev0}/README.md +0 -0
  17. {optimizely_opal_opal_tools_sdk-0.1.11.dev0 → optimizely_opal_opal_tools_sdk-0.1.14.dev0}/opal_tools_sdk/_registry.py +0 -0
  18. {optimizely_opal_opal_tools_sdk-0.1.11.dev0 → optimizely_opal_opal_tools_sdk-0.1.14.dev0}/opal_tools_sdk/auth.py +0 -0
  19. {optimizely_opal_opal_tools_sdk-0.1.11.dev0 → optimizely_opal_opal_tools_sdk-0.1.14.dev0}/opal_tools_sdk/logging.py +0 -0
  20. {optimizely_opal_opal_tools_sdk-0.1.11.dev0 → optimizely_opal_opal_tools_sdk-0.1.14.dev0}/optimizely_opal.opal_tools_sdk.egg-info/dependency_links.txt +0 -0
  21. {optimizely_opal_opal_tools_sdk-0.1.11.dev0 → optimizely_opal_opal_tools_sdk-0.1.14.dev0}/optimizely_opal.opal_tools_sdk.egg-info/top_level.txt +0 -0
  22. {optimizely_opal_opal_tools_sdk-0.1.11.dev0 → optimizely_opal_opal_tools_sdk-0.1.14.dev0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: optimizely-opal.opal-tools-sdk
3
- Version: 0.1.11.dev0
3
+ Version: 0.1.14.dev0
4
4
  Summary: SDK for creating Opal-compatible tools services
5
5
  Home-page: https://github.com/optimizely/opal-tools-sdk
6
6
  Author: Optimizely
@@ -18,6 +18,10 @@ Description-Content-Type: text/markdown
18
18
  Requires-Dist: fastapi>=0.100.0
19
19
  Requires-Dist: pydantic>=2.0.0
20
20
  Requires-Dist: httpx>=0.24.1
21
+ Provides-Extra: dev
22
+ Requires-Dist: datamodel-code-generator>=0.25.0; extra == "dev"
23
+ Requires-Dist: pytest>=7.0.0; extra == "dev"
24
+ Requires-Dist: pytest-asyncio>=0.21.0; extra == "dev"
21
25
  Dynamic: author
22
26
  Dynamic: home-page
23
27
  Dynamic: requires-python
@@ -1,13 +1,15 @@
1
1
  from .service import ToolsService
2
- from .decorators import tool
2
+ from .decorators import tool, resource
3
3
  from .auth import requires_auth
4
4
  from .logging import register_logger_factory
5
5
  from .models import AuthData, AuthRequirement, Credentials, Environment, IslandConfig, IslandResponse
6
+ from .proteus import UI
6
7
 
7
- __version__ = "0.1.0"
8
+ __version__ = "0.1.14-dev"
8
9
  __all__ = [
9
10
  "ToolsService",
10
11
  "tool",
12
+ "resource",
11
13
  "requires_auth",
12
14
  "register_logger_factory",
13
15
  # Models
@@ -17,4 +19,6 @@ __all__ = [
17
19
  "Environment",
18
20
  "IslandConfig",
19
21
  "IslandResponse",
22
+ # UI
23
+ "UI",
20
24
  ]
@@ -10,7 +10,12 @@ from .logging import get_logger
10
10
 
11
11
  logger = get_logger(__name__)
12
12
 
13
- def tool(name: str, description: str, auth_requirements: Optional[List[Dict[str, Any]]] = None):
13
+ def tool(
14
+ name: str,
15
+ description: str,
16
+ auth_requirements: Optional[List[Dict[str, Any]]] = None,
17
+ ui_resource: Optional[str] = None,
18
+ ):
14
19
  """Decorator to register a function as an Opal tool.
15
20
 
16
21
  Args:
@@ -19,6 +24,8 @@ def tool(name: str, description: str, auth_requirements: Optional[List[Dict[str,
19
24
  auth_requirements: Authentication requirements (optional)
20
25
  Format: [{"provider": "oauth_provider", "scope_bundle": "permissions_scope", "required": True}, ...]
21
26
  Example: [{"provider": "google", "scope_bundle": "calendar", "required": True}]
27
+ ui_resource: URI of associated UI resource for dynamic rendering (optional)
28
+ Example: "ui://my-app/create-form"
22
29
 
23
30
  Returns:
24
31
  Decorator function
@@ -27,6 +34,9 @@ def tool(name: str, description: str, auth_requirements: Optional[List[Dict[str,
27
34
  If your tool requires authentication, define your handler function with two parameters:
28
35
  async def my_tool(parameters: ParametersModel, auth_data: Optional[Dict] = None):
29
36
  ...
37
+
38
+ If ui_resource is specified, the frontend can fetch the resource to render a dynamic UI
39
+ for this tool without hardcoded integrations.
30
40
  """
31
41
  def decorator(func: Callable):
32
42
  # Get the ToolsService instance from the global registry
@@ -152,7 +162,87 @@ def tool(name: str, description: str, auth_requirements: Optional[List[Dict[str,
152
162
  handler=func,
153
163
  parameters=parameters,
154
164
  endpoint=endpoint,
155
- auth_requirements=auth_req_list
165
+ auth_requirements=auth_req_list,
166
+ ui_resource=ui_resource
167
+ )
168
+
169
+ return func
170
+
171
+ return decorator
172
+
173
+
174
+ def resource(
175
+ uri: str,
176
+ name: str,
177
+ description: Optional[str] = None,
178
+ mime_type: Optional[str] = None,
179
+ title: Optional[str] = None,
180
+ ):
181
+ """Decorator to register a function as an MCP resource.
182
+
183
+ Args:
184
+ uri: The unique URI for this resource (e.g., "ui://my-app/create-form")
185
+ name: Name of the resource
186
+ description: Description of the resource (optional)
187
+ mime_type: MIME type of the resource content (optional, e.g., "application/vnd.opal.proteus+json")
188
+ title: Human-readable title for the resource (optional)
189
+
190
+ Returns:
191
+ Decorator function
192
+
193
+ Note:
194
+ The handler function should be async and return either a str or a UI.Document.
195
+
196
+ When returning a UI.Document, the SDK will automatically:
197
+ - Serialize it to JSON based on the document's Pydantic model data
198
+ - Set the MIME type to "application/vnd.opal.proteus+json" (if not specified)
199
+
200
+ Examples:
201
+ # Example 1: Returning a string (manual serialization)
202
+ @resource(
203
+ uri="ui://my-app/create-form",
204
+ name="create-form",
205
+ description="Form for creating new items",
206
+ mime_type="application/vnd.opal.proteus+json"
207
+ )
208
+ async def get_create_form() -> str:
209
+ proteus_spec = {"type": "form", "fields": [...]}
210
+ return json.dumps(proteus_spec)
211
+
212
+ # Example 2: Returning a UI.Document (automatic serialization)
213
+ from opal_tools_sdk.ui import UI
214
+
215
+ @resource(
216
+ uri="ui://my-app/create-form",
217
+ name="create-form",
218
+ description="Form for creating new items"
219
+ )
220
+ async def get_create_form() -> UI.Document:
221
+ return UI.Document(
222
+ body=[
223
+ UI.Heading(children="Create Item"),
224
+ UI.Field(label="Name", children=UI.Input(name="item_name"))
225
+ ],
226
+ actions=[UI.Action(children="Save", appearance="primary")]
227
+ )
228
+ """
229
+ def decorator(func: Callable):
230
+ # Get the ToolsService instance from the global registry
231
+ from . import _registry
232
+
233
+ logger.info(f"Registering resource {name} with URI {uri}")
234
+
235
+ if not _registry.services:
236
+ logger.warning("No services registered in registry! Make sure to create ToolsService before decorating functions.")
237
+
238
+ for service in _registry.services:
239
+ service.register_resource(
240
+ uri=uri,
241
+ name=name,
242
+ description=description,
243
+ mime_type=mime_type,
244
+ title=title,
245
+ handler=func,
156
246
  )
157
247
 
158
248
  return func
@@ -83,6 +83,7 @@ class Function:
83
83
  endpoint: str
84
84
  auth_requirements: Optional[List[AuthRequirement]] = None
85
85
  http_method: str = "POST"
86
+ ui_resource: Optional[str] = None # UI resource URI for dynamic UI rendering
86
87
 
87
88
  def to_dict(self) -> Dict[str, Any]:
88
89
  """Convert to dictionary for the discovery endpoint."""
@@ -97,6 +98,9 @@ class Function:
97
98
  if self.auth_requirements:
98
99
  result["auth_requirements"] = [auth.to_dict() for auth in self.auth_requirements]
99
100
 
101
+ if self.ui_resource:
102
+ result["ui_resource"] = self.ui_resource
103
+
100
104
  return result
101
105
 
102
106