npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@optimizely-opal/opal-tools-sdk

v0.1.32-dev

Published

SDK for creating Opal-compatible tools services

Readme

Opal Tools SDK for TypeScript

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
  • Express integration
  • Island components for interactive UI responses

Installation

npm install @optimizely-opal/opal-tools-sdk

Usage

import {
  ToolsService,
  tool,
  IslandResponse,
  IslandConfig,
} from "@optimizely-opal/opal-tools-sdk";
import express from "express";

const app = express();
const toolsService = new ToolsService(app);

interface WeatherParameters {
  location: string;
  units: string;
}

class WeatherTool {
  @tool({
    name: "get_weather",
    description: "Gets current weather for a location",
  })
  async getWeather(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 @requiresAuth decorator

import { ToolsService, tool } from "@optimizely-opal/opal-tools-sdk";
import { requiresAuth } from "@optimizely-opal/opal-tools-sdk/auth";
import express from "express";

const app = express();
const toolsService = new ToolsService(app);

interface CalendarParameters {
  date: string;
  timezone?: string;
}

class CalendarTool {
  // Single authentication requirement
  @requiresAuth({ provider: "google", scopeBundle: "calendar", required: true })
  @tool({
    name: "get_calendar_events",
    description: "Gets calendar events for a date",
  })
  async getCalendarEvents(parameters: CalendarParameters, authData?: any) {
    // The authData parameter contains authentication information
    const token = authData?.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)
  @requiresAuth({ provider: "google", scopeBundle: "calendar", required: true })
  @requiresAuth({
    provider: "microsoft",
    scopeBundle: "outlook",
    required: true,
  })
  @tool({
    name: "get_calendar_availability",
    description: "Check calendar availability",
  })
  async getCalendarAvailability(
    parameters: CalendarParameters,
    authData?: any,
  ) {
    const provider = authData?.provider || "";
    const token = authData?.credentials?.access_token || "";

    if (provider === "google") {
      // Use Google Calendar API
    } else if (provider === "microsoft") {
      // Use Microsoft Outlook API
    }

    return { available: true, provider_used: provider };
  }
}

2. Specifying auth requirements in the @tool decorator

interface EmailParameters {
  limit?: number;
  folder?: string;
}

class EmailTool {
  @tool({
    name: "get_email",
    description: "Gets emails from the user's inbox",
    authRequirements: {
      provider: "google",
      scopeBundle: "gmail",
      required: true,
    },
  })
  async getEmail(parameters: EmailParameters, authData?: any) {
    // Implementation...
    return { emails: ["Email 1", "Email 2"] };
  }
}

Authentication

The SDK provides authentication support for tools that require user credentials:

import {
  ToolsService,
  tool,
  requiresAuth,
  AuthData,
} from "@optimizely-opal/opal-tools-sdk";
import express from "express";

interface CalendarParameters {
  date: string;
  timezone?: string;
}

const app = express();
const toolsService = new ToolsService(app);

class CalendarTool {
  @requiresAuth({ provider: "google", scopeBundle: "calendar", required: true })
  @tool({
    name: "get_calendar_events",
    description: "Gets calendar events for a date",
  })
  async getCalendarEvents(parameters: CalendarParameters, authData?: AuthData) {
    // The authData parameter contains authentication information
    if (authData) {
      const token = authData.credentials.access_token;
      // Use the token to make authenticated requests
    }

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

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 registerInteraction vs @tool / registerTool:

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

Declaring an interaction

import express from "express";
import { z } from "zod/v4";
import {
  ToolsService,
  registerInteraction,
  type InteractionContext,
} from "@optimizely-opal/opal-tools-sdk";

const app = express();
new ToolsService(app);

registerInteraction(
  "submit_task_form",
  {
    description: "Handle task form submission",
    inputSchema: {
      title: z.string().describe("Task title"),
      priority: z.string().default("medium").describe("Task priority"),
      assignee: z.string().optional().describe("Assignee email"),
    },
  },
  async (parameters, context: InteractionContext) => {
    // parameters is typed as { title: string; priority: string; assignee?: string }
    // context.auth_data carries credentials when auth is configured
    return {
      task_id: "task-123",
      message: `Task '${parameters.title}' created`,
    };
  },
);

The Zod schema is converted to the same Parameter[] representation registerTool uses, and the handler's parameters argument is statically typed from it.

Handler signature & InteractionContext

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

type InteractionContext = {
  auth_data?: AuthData;
};

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

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": {...}}
}

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 throws an error.

Island Components

The SDK includes Island components for creating interactive UI responses:

import {
  ToolsService,
  tool,
  IslandResponse,
  IslandConfig,
} from "@optimizely-opal/opal-tools-sdk";
import express from "express";

interface WeatherParameters {
  location: string;
  units?: string;
}

const app = express();
const toolsService = new ToolsService(app);

class WeatherTool {
  @tool({
    name: "get_weather",
    description: "Gets current weather for a location",
  })
  async getWeather(parameters: WeatherParameters) {
    // Get weather data (implementation details omitted)
    const weatherData = { temperature: 22, condition: "sunny", humidity: 65 };

    // Create an interactive island for weather settings
    const island = new IslandConfig(
      [
        new IslandConfig.Field(
          "location",
          "Location",
          "string",
          parameters.location,
        ),
        new IslandConfig.Field(
          "units",
          "Temperature Units",
          "string",
          parameters.units || "metric",
          false,
          ["metric", "imperial", "kelvin"],
        ),
        new IslandConfig.Field(
          "current_temp",
          "Current Temperature",
          "string",
          `${weatherData.temperature}°${parameters.units === "metric" ? "C" : "F"}`,
        ),
      ],
      [
        new IslandConfig.Action(
          "refresh_weather",
          "Refresh Weather",
          "button",
          "/tools/get_weather",
          "update",
        ),
      ],
      "button", // Optional island type
      "plus", // Optional island icon
    );

    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: Array of IslandConfig.Field objects
  • actions: Array of IslandConfig.Action objects
  • type: Optional island type for custom rendering (optional)
  • icon: Optional icon identifier for the island (optional)

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.

Registering a Resource with registerResource

Use registerResource to define an MCP resource:

import { registerResource, UI } from "@optimizely-opal/opal-tools-sdk";

const getCreateForm = registerResource(
  "create-form",
  {
    uri: "ui://my-app/create-form",
    description: "Form for creating new items",
    title: "Create Form",
  },
  async () => {
    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:

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

Linking a Tool to a UI Resource

Use the uiResource option on registerTool to associate a tool with a Proteus UI resource:

import { registerTool } from "@optimizely-opal/opal-tools-sdk";

registerTool(
  "create_item",
  {
    description: "Create a new item",
    uiResource: "ui://my-app/create-form",
  },
  async (parameters: CreateItemParams) => {
    return { id: "item-123", name: parameters.name, status: "created" };
  },
);

Or with the @tool decorator on a class method:

class ItemTool {
  @tool({
    name: "create_item",
    description: "Create a new item",
    uiResource: "ui://my-app/create-form",
  })
  async createItem(parameters: CreateItemParams) {
    return { id: "item-123", name: parameters.name, status: "created" };
  }
}

Building UI with UI.Document

Import the UI namespace from the SDK. It provides factory functions for all Proteus components:

import { UI } from "@optimizely-opal/opal-tools-sdk";

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:

const getResults = registerResource(
  "results",
  {
    uri: "ui://my-app/results",
  },
  async () => {
    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 comprehensive TypeScript type definitions:

Authentication Types

  • AuthData: Interface containing provider and credentials information
  • Credentials: Interface with access_token, org_sso_id, customer_id, instance_id, and product_sku
  • Environment: Interface specifying execution mode ('headless' or 'interactive')

Parameter Types

  • ParameterType: Enum for supported parameter types
  • Parameter: Class for tool parameter definitions
  • Function: Class for complete tool function definitions

These types provide full IDE support and compile-time type checking for better development experience.

Documentation

See full documentation for more examples and configuration options.