Skip to main content
Pre-release

This release is a pre-release and may not be stable for production use.

Opal Tools SDK for Python

This SDK simplifies the creation of tools services compatible with the Opal Tools Management Service.

Features

  • Easy definition of tool functions with decorators
  • Automatic generation of discovery endpoints
  • Parameter validation and type checking
  • Authentication helpers
  • FastAPI integration
  • Island components for interactive UI responses

Installation

pip install optimizely-opal.opal-tools-sdk

Note: While the package is installed as optimizely-opal.opal-tools-sdk, you'll still import it in your code as opal_tools_sdk:

# Import using the package name
from opal_tools_sdk import ToolsService, tool, IslandResponse, IslandConfig

Usage

from opal_tools_sdk import ToolsService, tool
from pydantic import BaseModel
from fastapi import FastAPI

app = FastAPI()
tools_service = ToolsService(app)

class WeatherParameters(BaseModel):
    location: str
    units: str = "metric"

@tool("get_weather", "Gets current weather for a location")
async def get_weather(parameters: WeatherParameters):
    # Implementation...
    return {"temperature": 22, "condition": "sunny"}

# Discovery endpoint is automatically created at /discovery

Authentication

The SDK provides two ways to require authentication for your tools:

1. Using the @requires_auth decorator

from opal_tools_sdk import ToolsService, tool, AuthData
from opal_tools_sdk.auth import requires_auth
from pydantic import BaseModel
from fastapi import FastAPI
from typing import Optional

app = FastAPI()
tools_service = ToolsService(app)

class CalendarParameters(BaseModel):
    date: str
    timezone: str = "UTC"

# Single authentication requirement
@requires_auth(provider="google", scope_bundle="calendar", required=True)
@tool("get_calendar_events", "Gets calendar events for a date")
async def get_calendar_events(parameters: CalendarParameters, auth_data: Optional[AuthData] = None):
    # The auth_data parameter contains authentication information
    if auth_data:
        token = auth_data["credentials"]["access_token"]

    # Use the token to make authenticated requests
    # ...

    return {"events": ["Meeting at 10:00", "Lunch at 12:00"]}

# Multiple authentication requirements (tool can work with either provider)
@requires_auth(provider="google", scope_bundle="calendar", required=True)
@requires_auth(provider="microsoft", scope_bundle="outlook", required=True)
@tool("get_calendar_availability", "Check calendar availability")
async def get_calendar_availability(parameters: CalendarParameters, auth_data: Optional[AuthData] = None):
    provider = ""
    token = ""
    
    if auth_data:
        provider = auth_data["provider"]
        token = auth_data["credentials"]["access_token"]

        if provider == "google":
            # Use Google Calendar API
            pass
        elif provider == "microsoft":
            # Use Microsoft Outlook API
            pass

    return {"available": True, "provider_used": provider}

2. Specifying auth requirements in the @tool decorator

@tool(
    "get_email",
    "Gets emails from the user's inbox",
    auth_requirements=[
        {"provider": "google", "scope_bundle": "gmail", "required": True}
    ]
)
async def get_email(parameters: EmailParameters, auth_data: Optional[AuthData] = None):
    # Implementation...
    return {"emails": ["Email 1", "Email 2"]}

Interactions

Interactions are app-only handlers — actions callable from the UI but hidden from the LLM. Use them when a tool surface needs to expose a button, form submit, or other follow-up action that the model should not be able to call. They follow the MCP "app-only tools" pattern (visibility: ["app"]) and are not listed in the /discovery endpoint.

When to use @interaction vs @tool:

  • Use @tool when the LLM should be able to call the function on its own.
  • Use @interaction when only the UI (action cards, islands) should call it — the model should never see it.

Declaring an interaction

from typing import Optional
from opal_tools_sdk import ToolsService, interaction, InteractionContext
from pydantic import BaseModel, Field
from fastapi import FastAPI

app = FastAPI()
tools_service = ToolsService(app)


class TaskFormInput(BaseModel):
    title: str = Field(description="Task title")
    priority: str = Field(default="medium", description="Task priority")
    assignee: Optional[str] = Field(default=None, description="Assignee email")


@interaction(
    name="submit_task_form",
    description="Handle task form submission",
)
async def handle_task_submission(parameters: TaskFormInput, context: InteractionContext):
    # parameters is validated against TaskFormInput
    # context.auth_data carries credentials when auth is configured
    return {"task_id": "task-123", "message": f"Task '{parameters.title}' created"}

The first parameter is introspected from the Pydantic model the same way @tool does it (typing the parameter as dict skips schema extraction). The second parameter is an InteractionContext.

Handler signature & InteractionContext

InteractionContext carries the auth data resolved by TMS from the parent tool's auth_requirements:

@dataclass
class InteractionContext:
    auth_data: AuthData | None = None
    auth_credentials: list[AuthCredentialEntry] | None = None

If the parent tool did not declare auth requirements (or no credentials are available), context.auth_data is None.

auth_data carries a single credential. A tool declaring several auth requirements reads context.auth_credentials, which holds one entry per declared requirement in declaration order. Each entry has a provider, a scope_bundle (None for a provider that resolves against no bundle) and a status: delivered entries carry credentials, missing entries carry a reason (provider_missing, bundle_token_unset, scope_bundle_missing or no_connected_account), and duplicate entries repeat an earlier delivered provider and scope bundle without credentials.

None and [] mean different things, everywhere. A body that never carried auth_credentials reads back as None — the caller is a server that predates the key — while a key present as [] is a list that genuinely holds nothing. The tool ToolExecutionContext, the interaction InteractionContext and the injected rollback keyword argument all preserve that distinction, so a handler that only wants to iterate should spell context.auth_credentials or [] itself.

Malformed entries never fail the request, and never shift the list. Handlers read auth_credentials positionally, so entry i always answers declared requirement i: every object entry is kept exactly as sent, including a status or reason this SDK version does not know, a missing field or a mistyped one. Only an entry that is not an object is dropped. An auth_credentials that is present but is not a list reaches the handler as []. A dropped entry, an unknown status or reason and a non-list value each log a warning naming the index and field, never a value. This holds on the tool, interaction and rollback endpoints alike.

Generated endpoint

The SDK exposes a single shared endpoint for all interactions:

POST /interactions/execute
Content-Type: application/json

{
  "name": "submit_task_form",
  "parameters": {"title": "Ship docs", "priority": "high"},
  "auth": {"provider": "...", "credentials": {...}},
  "auth_credentials": [
    {"provider": "...", "scope_bundle": {...}, "status": "delivered", "credentials": {...}},
    {"provider": "...", "scope_bundle": null, "status": "missing", "reason": "no_connected_account"}
  ]
}

The response is whatever the handler returns, serialized as JSON. Interactions are not listed in /discovery.

Name uniqueness

Interaction names must be unique across both tools and interactions in the same service, since both can be exported as MCP tools. Registering an interaction with a name that conflicts with an existing tool or interaction raises ValueError.

Island Components

The SDK includes Island components for creating interactive UI responses that allow users to input data and trigger actions.

Weather Tool with Interactive Island

from opal_tools_sdk import ToolsService, tool, IslandResponse, IslandConfig
from pydantic import BaseModel
from fastapi import FastAPI

app = FastAPI()
tools_service = ToolsService(app)

class WeatherParameters(BaseModel):
    location: str
    units: str = "metric"

@tool("get_weather", "Gets current weather for a location")
async def get_weather(parameters: WeatherParameters):
    # Get weather data (implementation details omitted)
    weather_data = {"temperature": 22, "condition": "sunny", "humidity": 65}
    
    # Create an interactive island for weather settings
    island = IslandConfig(
        fields=[
            IslandConfig.Field(
                name="location",
                label="Location",
                type="string",
                value=parameters.location
            ),
            IslandConfig.Field(
                name="units",
                label="Temperature Units",
                type="string",
                value=parameters.units,
                options=["metric", "imperial", "kelvin"]
            ),
            IslandConfig.Field(
                name="current_temp",
                label="Current Temperature",
                type="string",
                value=f"{weather_data['temperature']}°{'C' if parameters.units == 'metric' else 'F'}"
            )
        ],
        actions=[
            IslandConfig.Action(
                name="refresh_weather",
                label="Refresh Weather",
                type="button",
                endpoint="/tools/get_weather",
                operation="update"
            )
        ]
    )
    
    return IslandResponse.create([island])

Island Components

IslandConfig.Field

Fields represent data inputs in the UI:

  • name: Programmatic field identifier
  • label: Human-readable label
  • type: Field type ("string", "boolean", "json")
  • value: Current field value (optional)
  • hidden: Whether to hide from user (optional, default: False)
  • options: Available options for selection (optional)

IslandConfig.Action

Actions represent buttons or operations:

  • name: Programmatic action identifier
  • label: Human-readable button label
  • type: UI element type (typically "button")
  • endpoint: API endpoint to call
  • operation: Operation type (default: "create")

IslandConfig

Contains the complete island configuration:

  • fields: List of IslandConfig.Field objects
  • actions: List of IslandConfig.Action objects
  • type: Island type for UI rendering (optional, default: None)
  • icon: Icon to display in the island (optional, default: None)

IslandResponse

The response wrapper for islands:

  • Use IslandResponse.create([islands]) to create responses
  • Supports multiple islands per response

Resources & Proteus UI

The SDK supports defining MCP resources that serve dynamic UI specifications using the Proteus framework. This enables tools to render rich, interactive interfaces without hardcoded frontend integrations.

For the full Proteus component reference and visual designer, see the Proteus documentation.

Defining a Resource with @resource

Use the @resource decorator to register a function as an MCP resource:

from opal_tools_sdk import UI
from opal_tools_sdk.decorators import resource

@resource(
    uri="ui://my-app/create-form",
    name="create-form",
    description="Form for creating new items",
)
async def get_create_form():
    return UI.Document(
        title="Create Item",
        body=[
            UI.Heading(children="New Item"),
            UI.Field(
                label="Item Name",
                children=UI.Input(name="item_name", placeholder="Enter item name"),
            ),
            UI.Field(
                label="Description",
                children=UI.Textarea(name="description", placeholder="Enter description"),
            ),
        ],
        actions=[
            UI.Action(children="Save", appearance="primary"),
            UI.CancelAction(children="Cancel"),
        ],
    )

Parameters:

  • uri (required): Unique URI for the resource (e.g., "ui://my-app/create-form")
  • name (required): Name of the resource
  • description (optional): Description of the resource
  • mime_type (optional): MIME type of the content. Auto-set to "application/vnd.opal.proteus+json" when returning a UI.Document
  • title (optional): Human-readable title

The handler function can return either a str (manual JSON serialization) or a UI.Document (automatic serialization with MIME type set automatically).

Linking a Tool to a UI Resource

Use the ui_resource parameter on @tool to associate a tool with a Proteus UI resource. The frontend fetches and renders the resource when the tool is invoked:

from opal_tools_sdk import tool
from pydantic import BaseModel, Field

class CreateItemParams(BaseModel):
    item_name: str = Field(description="Name of the item")
    description: str = Field(description="Item description")

@tool(
    "create_item",
    "Create a new item",
    ui_resource="ui://my-app/create-form",
)
async def create_item(parameters: CreateItemParams):
    return {"id": "item-123", "name": parameters.item_name, "status": "created"}

Building UI with UI.Document

Import the UI namespace from opal_tools_sdk or opal_tools_sdk.ui. It provides type-safe builders for all Proteus components:

from opal_tools_sdk import UI

Available components:

Category Components
Layout UI.Document, UI.Group, UI.Card, UI.CardHeader, UI.CardLink, UI.Separator
Typography UI.Heading, UI.Text, UI.Link
Data Display UI.Avatar, UI.Badge, UI.DataTable, UI.Chart, UI.IconCalendar, UI.Image, UI.ImageCarousel, UI.Time
Form Controls UI.Field, UI.Input, UI.Textarea, UI.Select, UI.SelectTrigger, UI.SelectContent, UI.Switch, UI.Range, UI.Question
Actions UI.Action, UI.CancelAction
Dynamic UI.Value, UI.Map, UI.MapIndex, UI.Show, UI.Concat, UI.Zip

Data binding with UI.Value resolves paths from the tool response:

@resource(uri="ui://my-app/results", name="results")
async def get_results():
    return UI.Document(
        title=UI.Value(path="/title"),
        body=UI.Map(
            path="/items",
            children=UI.Text(children=UI.Value(path="name")),
        ),
    )

Conditional rendering with UI.Show:

UI.Show(
    when={"!!": UI.Value(path="/error")},
    children=UI.Text(children="An error occurred", color="fg.error"),
)

The MIME type constant is available as UI.MIME_TYPE ("application/vnd.opal.proteus+json").

Type Definitions

The SDK provides several TypedDict and dataclass definitions for better type safety:

Authentication Types

  • AuthData: TypedDict containing provider and credentials information
  • Credentials: TypedDict with access_token, token_type, org_sso_id, cfg_client_id, user_id, customer_id, instance_id, product_sku and expires_at — every key optional and every value possibly None; a newer server may add keys of any JSON type
  • AuthRequirement: Dataclass for specifying authentication requirements

Execution Environment

  • Environment: TypedDict specifying execution mode ("headless" or "interactive")
  • ToolExecutionContext: the execution context a @tool handler may declare — environment, async callback, caller identity (thread_id / workflow_execution_id / agent_execution_id), and the auth payload with its auth_credentials list. The SDK matches the parameter on its ToolExecutionContext annotation, so name it whatever reads best; a parameter named context is still recognised without an annotation.

Parameter Types

  • ParameterType: Enum for supported parameter types (string, integer, number, boolean, list, dictionary)
  • Parameter: Dataclass for tool parameter definitions
  • Function: Dataclass for complete tool function definitions

These types are automatically imported when you import from opal_tools_sdk and provide better IDE support and type checking.

Documentation

See full documentation for more examples and configuration options.

Metadata

Release files for optimizely-opal.opal-tools-sdk 0.2.2.dev0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for optimizely-opal.opal-tools-sdk 0.2.2.dev0
File Size Uploaded
optimizely_opal_opal_tools_sdk-0.2.2.dev0.tar.gz 90.4 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for optimizely-opal.opal-tools-sdk 0.2.2.dev0
File Interpreter ABI Platform
optimizely_opal_opal_tools_sdk-0.2.2.dev0-py3-none-any.whl Python 3 none any Details

Total release size: 146.0 kB

Release files / optimizely_opal_opal_tools_sdk-0.2.2.dev0.tar.gz

Download URL optimizely_opal_opal_tools_sdk-0.2.2.dev0.tar.gz
Size 90.4 kB
Tags Source
SHA-256 checksum
How to use checksums
ccdea4923ee5f2f19231be2c94413414a153a329b59d147098ebb28248e6947f
BLAKE2b-256 checksum
How to use checksums
741457dc882f42b14c1eaceff9b7689c61fdb55102a05adc268717537b678ab8
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.13.0 {"installer":{"name":"uv","version":"0.13.0","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / optimizely_opal_opal_tools_sdk-0.2.2.dev0-py3-none-any.whl

Download URL optimizely_opal_opal_tools_sdk-0.2.2.dev0-py3-none-any.whl
Size 55.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
4b6d9c54400bbf9d549cbed31cfa2bb4f0463e5c113b1e1c2da2f9ac37d525a6
BLAKE2b-256 checksum
How to use checksums
aad95d576025938c18c6a07bb925731dc3328fab86aa018733b90d47275f6471
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.13.0 {"installer":{"name":"uv","version":"0.13.0","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release history Release notifications | RSS feed

This release

0.2.2.dev0 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page