Scrydon
Integrations

SDK Reference

Complete API reference for the @scrydon/sdk-authoring/integrations SDK

Installation

bun add -d @scrydon/sdk-authoring/integrations zod
npm install --save-dev @scrydon/sdk-authoring/integrations zod

Import Paths

The SDK uses explicit subpath exports — there is no root barrel import.

// Definition helpers (all define* functions)
import {
  defineVendor, defineProduct, defineTool, defineBlock, defineTriggerBlock,
  defineCapabilityLLM, defineCapabilitySTT, defineCapabilityTTS,
  defineCapabilityEmbedding, defineCapabilityVideo, defineCapabilityOCR,
  defineCapabilityWebhook, defineCapabilityDiscovery,
} from "@scrydon/sdk-authoring/integrations/define";

// Runtime types (used in execute() signatures)
import type {
  PureContext, PureContextAuth, ToolResponse, ExecutorResult,
  RealtimeSession, RealtimeMessage,
} from "@scrydon/sdk-authoring/integrations/context";

// Credential narrowing helpers (throw CredentialKindError on kind mismatch)
import {
  requireOAuthToken, requireOAuth,
  requireApiKey, requireBasicAuth, requireBotToken,
} from "@scrydon/sdk-authoring/integrations/context";

// Manifest types (for advanced use cases)
import type { ManifestSchema } from "@scrydon/sdk-authoring/integrations/manifest";

defineVendor(config)

Top-level container for an integration. The entry point's default export must be a defineVendor() result.

interface VendorInput {
  /** Unique vendor ID — lowercase alphanumeric + hyphens, starts with letter */
  id: string;
  /** Display name */
  name: string;
  /** Semantic version (e.g. "1.0.0") */
  version: string;
  /** Short description */
  description?: string;
  /** Hex brand color (e.g. "#FF6B35") */
  color?: string;
  /** SVG icon as a string */
  icon: string;
  /** Vendor website URL */
  website?: string;
  /** Documentation URL */
  docsUrl?: string;
  /** Categories (e.g. ["tools"], ["llm"], ["database"]) */
  categories?: string[];
  /** Whether this vendor is enabled by default for new orgs */
  defaultEnabled?: boolean;
  /** Whether this vendor supports system-mode (client_credentials) */
  supportsSystemMode?: boolean;
  /** Whether this vendor requires external network connectivity */
  connectivity?: "cloud" | "local" | "hybrid";
  /** Authentication configuration */
  auth: AuthConfig;
  /** Admin-configurable fields (e.g. base URL for self-hosted services) */
  configFields?: ConfigFieldInput[];
  /** Vendor-level secrets (resolved per-invocation via Dapr Secret Store) */
  secrets?: SecretInput[];
  /** One or more products */
  products: ProductDefinition[];
  /** Optional protocol definitions */
  protocols?: ProtocolDefinition[];
  /** LLM provider configuration (for AI model vendors) */
  llm?: VendorLLMDef;
}

Authentication Config

The auth field defines which credential types your vendor supports:

auth: {
  credentials: {
    none: { type: "none" },
  },
  default: "none",
}
auth: {
  credentials: {
    apiKey: {
      type: "apiKey",
      label: "API Key",
      description: "Your service API key",
      headerName: "Authorization",   // HTTP header name
      headerPrefix: "Bearer",        // Prefix before the key value
    },
  },
  default: "apiKey",
}
auth: {
  credentials: {
    oauth: {
      type: "oauth",
      label: "Connect Account",
      authorizationUrl: "https://example.com/oauth/authorize",
      tokenUrl: "https://example.com/oauth/token",
      scopes: ["read", "write"],
      pkce: true,
      useBasicAuth: false,
      supportsRefreshTokenRotation: true,
    },
  },
  default: "oauth",
}
auth: {
  credentials: {
    botToken: {
      type: "botToken",
      label: "Bot Token",
      description: "Your bot authentication token",
      headerName: "Authorization",
      headerPrefix: "Bot",
    },
  },
  default: "botToken",
}

You can define multiple credential types and let the user choose:

auth: {
  credentials: {
    oauth: { type: "oauth", /* ... */ },
    apiKey: { type: "apiKey", /* ... */ },
  },
  default: "oauth", // Which one to show first
}

Config Fields

For vendors that need admin-configured settings (e.g. a self-hosted base URL):

configFields: [
  {
    key: "baseUrl",
    label: "Base URL",
    placeholder: "https://your-instance.example.com",
    description: "The base URL of your self-hosted instance",
    required: true,
    type: "url",
  },
],

Secrets

For vendor-level secrets that are stored in the Dapr Secret Store:

secrets: [
  {
    key: "WEBHOOK_SIGNING_SECRET",
    label: "Webhook Signing Secret",
    description: "Used to verify incoming webhook payloads",
    required: true,
  },
],

Secrets are available at runtime via ctx.secrets["WEBHOOK_SIGNING_SECRET"].


defineProduct(config)

Groups tools and a block under a product that can be enabled/disabled per organization.

interface ProductInput {
  /** Unique product ID within the vendor */
  id: string;
  /** Display name */
  name: string;
  /** Short description */
  description?: string;
  /** SVG icon as string (falls back to vendor icon if omitted) */
  icon?: string;
  /** Which credential from auth.credentials this product uses */
  credentialRef?: string;
  /** OAuth scopes required by this product */
  credentialScopes?: string[];
  /** Runtime capabilities */
  capabilities: {
    tools?: ToolDefinition[];
    triggers?: TriggerDefinition[];
    runtimes?: {
      llm?: LLMCapability;
      stt?: STTCapability;
      tts?: TTSCapability;
      embedding?: EmbeddingCapability;
      video?: VideoCapability;
      ocr?: OCRCapability;
    };
    webhooks?: WebhookCapability;
    discovery?: DiscoveryCapability;
  };
  /** The block definition for the workflow editor */
  block: BlockDefinition;
  /** NPM package dependencies (for risk assessment at upload time) */
  dependencies?: Array<{
    type: "npm";
    package: string;
    version: string;
    reason?: string;
  }>;

The dependencies array is for human-readable context — each entry includes a reason explaining why the dependency is needed. The CLI automatically generates a machine-readable SBOM (meta/sbom.cdx.json) that captures the complete set of bundled packages with versions and licenses. Both are complementary: declared dependencies explain intent, the SBOM provides the auditable inventory.

  /** System-mode (client_credentials) configuration */
  systemMode?: {
    supported: boolean;
    permissions?: string[];
    resources?: Array<{
      resourceType: string;
      displayName: string;
      description?: string;
      required: boolean;
      allowMultiple?: boolean;
    }>;
  };
}

OAuth credentials can declare manifest-safe extension points for provider-specific setup flows such as Microsoft admin consent. Keep this logic owned by the integration: the manifest should describe the provider URL shape, the required organization config fields, and the callback query parameters.

For most providers, implement system consent with a declarative redirect template. This is the supported path when the platform only needs to build a URL from runtime values:

auth: {
  credentials: {
    oauth: {
      type: "oauth",
      label: "Microsoft Account",
      authorizationUrl: "https://login.microsoftonline.com/{tenantId}/oauth2/v2.0/authorize",
      tokenUrl: "https://login.microsoftonline.com/{tenantId}/oauth2/v2.0/token",
      requiredConfig: ["tenantId"],
      supportsClientCredentials: true,
      clientCredentialScopes: ["https://graph.microsoft.com/.default"],
      extensions: {
        systemConsent: {
          kind: "redirect-template",
          urlTemplate: "https://login.microsoftonline.com/{tenantId}/adminconsent",
          params: {
            client_id: "{clientId}",
            redirect_uri: "{redirectUri}",
            scope: "{scope}",
            state: "{state}",
          },
          requiredConfig: ["tenantId"],
          callback: {
            successParam: "admin_consent",
            tenantParam: "tenant",
          },
        },
      },
    },
  },
}

The platform substitutes these placeholders:

PlaceholderValue
{clientId}OAuth client ID resolved for the organization
{redirectUri}Platform admin-consent callback URL
{scope}Space-joined clientCredentialScopes
{state}CSRF state generated by the platform
Any requiredConfig key, such as {tenantId}Organization provider config value

If a provider cannot be represented as a URL template, declare a handler reference in the manifest and implement the referenced method in the vendor runtime. The method should be pure: accept resolved runtime values, validate any provider-specific config it requires, and return the redirect URL instead of hardcoding provider logic in the platform.

extensions: {
  systemConsent: {
    kind: "handler",
    handlerRef: "microsoft.systemConsent.buildUrl",
    callback: {
      handlerRef: "microsoft.systemConsent.callback",
    },
  },
}
type SystemConsentBuildUrlInput = {
  clientId: string;
  redirectUri: string;
  state?: string;
  scopes: string[];
  providerConfig: Record<string, string | undefined>;
};

export async function buildUrl(input: SystemConsentBuildUrlInput): Promise<string> {
  const tenantId = input.providerConfig.tenantId;
  if (!tenantId) {
    throw new Error("Microsoft admin consent requires tenantId");
  }

  const url = new URL(`https://login.microsoftonline.com/${tenantId}/adminconsent`);
  url.searchParams.set("client_id", input.clientId);
  url.searchParams.set("redirect_uri", input.redirectUri);
  url.searchParams.set("scope", input.scopes.join(" "));

  if (input.state) {
    url.searchParams.set("state", input.state);
  }

  return url.toString();
}

type SystemConsentCallbackInput = {
  query: Record<string, string | undefined>;
  providerConfig: Record<string, string | undefined>;
};

export async function callback(input: SystemConsentCallbackInput) {
  return {
    approved: input.query.admin_consent === "True",
    tenantId: input.query.tenant,
  };
}

Handler references are manifest metadata. Uploaded bundles should use kind: "redirect-template" today unless the host application has explicitly wired runtime execution for the referenced handler.

Multi-Product Vendors

Vendors like Google or Microsoft have multiple products. Each product gets its own block in the workflow editor and can be enabled independently:

export default defineVendor({
  id: "acme",
  // ...
  products: [
    defineProduct({
      id: "acme-crm",
      name: "Acme CRM",
      block: crmBlock,
      capabilities: { tools: [listContacts, createContact] },
    }),
    defineProduct({
      id: "acme-email",
      name: "Acme Email",
      block: emailBlock,
      capabilities: { tools: [sendEmail, listInbox] },
    }),
  ],
});

defineTool(config)

Defines the runtime logic for a tool. This is where your actual API calls, data transformations, or computations happen.

interface ToolInput<TInput, TOutput> {
  /** Unique tool ID — convention: {vendor}:{product}:{action} */
  id: string;
  /** Display name */
  name: string;
  /** Semantic version */
  version: string;
  /** Description shown to users and LLM agents */
  description?: string;
  /** Zod schema for input validation */
  input: z.ZodType<TInput>;
  /** Zod schema for output validation */
  output: z.ZodType<TOutput>;
  /** Parameter metadata — controls visibility in the UI and to LLMs */
  params?: Record<string, ParamDef>;
  /** The function that runs when this tool is invoked */
  execute: (input: TInput, ctx: PureContext) => Promise<ToolResponse<TOutput>>;
}

interface ParamDef {
  type: string;
  required?: boolean;
  description?: string;
  default?: unknown;
  /** Controls where this parameter appears */
  visibility?: "user-only" | "user-or-llm" | "hidden";
}

Parameter Visibility

VisibilityWho can provide the valueUse case
user-or-llmUser sets it in the UI, or an AI agent generates itMost parameters
user-onlyOnly the user can configure it (hidden from LLM)Connection settings, credentials
hiddenNot shown in UI or to LLM — set programmaticallyInternal IDs, computed values

Execute Function

The execute function receives validated input and a PureContext:

import { requireOAuthToken } from "@scrydon/sdk-authoring/integrations/context";

async execute(input, ctx) {
  // Use the logger (visible in workflow execution logs)
  ctx.logger.info(`Processing request for org ${ctx.execution.orgId}`);

  // Narrow on ctx.auth.kind before reading credential fields.
  // This example assumes the vendor declares an OAuth credential —
  // requireOAuthToken throws a CredentialKindError if the kind doesn't match.
  const accessToken = requireOAuthToken(ctx.auth);

  // Make authenticated API calls
  const response = await fetch("https://api.example.com/data", {
    headers: {
      Authorization: `Bearer ${accessToken}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify(input),
  });

  if (!response.ok) {
    return {
      success: false,
      output: { error: `API returned ${response.status}` },
      error: `HTTP ${response.status}: ${response.statusText}`,
    };
  }

  const data = await response.json();
  return {
    success: true,
    output: data,
    metadata: { requestId: response.headers.get("x-request-id") },
  };
},

PureContext

The PureContext interface is the only interface between the platform and bundle code. It is injected into every execute() call at runtime:

/**
 * Discriminated union of all credential shapes the platform can inject.
 * Always narrow on `kind` before reading credential fields.
 * Import narrowing helpers from @scrydon/sdk-authoring/integrations/context:
 *   requireOAuthToken(auth) → string (accessToken, throws on mismatch)
 *   requireOAuth(auth)      → { accessToken, refreshToken?, tokenType?, expiresAt? }
 *   requireApiKey(auth)     → string (apiKey)
 *   requireBasicAuth(auth)  → { username, password }
 *   requireBotToken(auth)   → string (botToken)
 * Each helper throws a CredentialKindError with an actionable message on mismatch.
 */
type PureContextAuth =
  | { kind: "oauth"; accessToken: string; refreshToken?: string; tokenType?: string; expiresAt?: number }
  | { kind: "apiKey"; apiKey: string }
  | { kind: "basicAuth"; username: string; password: string }
  | { kind: "botToken"; botToken: string }
  | { kind: "none" };

interface PureContext {
  /** Credential resolved by the platform before invocation */
  auth: PureContextAuth;
  /** Execution metadata for logging and correlation */
  execution: {
    executionId: string;      // Unique execution run ID
    workflowId?: string;      // Workflow ID (if running in a workflow)
    nodeId?: string;          // Block node ID in the workflow
    orgId: string;            // Organization ID
    workspaceId?: string;     // Workspace ID
    userId?: string;          // User who triggered the execution
  };
  /** Structured logger — console calls are intercepted in the sandbox */
  logger: {
    info(message: string, ...args: unknown[]): void;
    warn(message: string, ...args: unknown[]): void;
    error(message: string, ...args: unknown[]): void;
    debug(message: string, ...args: unknown[]): void;
  };
  /** Vendor secrets configured by org admin (resolved per-invocation via Dapr Secret Store) */
  secrets: Record<string, string>;
  /** Present when executing via an Integration Profile — contains profile-specific overrides */
  profileConfig?: Record<string, unknown>;
}

PureContext is imported from @scrydon/sdk-authoring/integrations/context. The platform constructs it before each tool invocation — your code never needs to create one manually. All fields except auth and execution may be empty objects if not applicable. auth is a kind-discriminated union (PureContextAuth); always narrow on auth.kind or use a require* helper before accessing credential-specific fields. See Authentication Modes for the full per-mode shape reference.

ToolResponse

Every execute() must return a ToolResponse:

interface ToolResponse<T = unknown> {
  /** Whether the tool execution succeeded */
  success: boolean;
  /** The typed output data */
  output: T;
  /** Error message (shown to user if success is false) */
  error?: string;
  /** Optional metadata (logged but not passed to downstream blocks) */
  metadata?: Record<string, unknown>;
}

defineBlock(config)

Defines the workflow editor UI — how the block appears, what form fields it has, and how data flows through it.

interface BlockInput {
  /** Unique block type — lowercase with underscores (e.g. "acme_crm") */
  type: string;
  /** Display name in the block palette */
  name: string;
  /** Short description */
  description?: string;
  /** Block category — use "integration" for bundle blocks */
  category: string;
  /** Background color in hex */
  bgColor?: string;
  /** Auth mode: "none", "apiKey", "oauth", "botToken" */
  authMode?: string;
  /** Form fields displayed in the block panel */
  subBlocks?: SubBlockInput[];
  /** Which tools this block can invoke */
  tools?: {
    access: string[];
    config?: {
      /** Dynamic tool selection based on user input */
      tool: (params: Record<string, any>) => string;
      /** Parameter transformation before passing to tool */
      params?: (params: Record<string, any>) => any;
    };
  };
  /** Input connections from upstream blocks */
  inputs?: Record<string, { type: string; description?: string }>;
  /** Output connections to downstream blocks */
  outputs?: Record<string, { type: string; description?: string }>;
  /** Trigger configuration (for trigger blocks) */
  triggers?: { enabled: boolean; available: string[] };
}

SubBlock Types

SubBlocks are the form fields that appear when a user clicks on a block:

TypeDescriptionKey Props
short-inputSingle-line text inputplaceholder, password, readOnly, showCopyButton
long-inputMulti-line textareaplaceholder
codeCode editor with syntax highlightingplaceholder, mode
dropdownSelect menuoptions: [{ label, id }]
comboboxSearchable dropdownoptions, fetchOptions
sliderRange slidermin, max, step
switchToggle switchdefaultValue
oauth-inputOAuth credential selectorserviceId, requiredScopes

SubBlock Definition

interface SubBlockInput {
  /** Unique field ID within the block */
  id: string;
  /** Field label */
  title?: string;
  /** SubBlock type */
  type: string;
  /** Layout: "full" (default) or "half" */
  layout?: "full" | "half";
  /** Whether this field is required */
  required?: boolean;
  /** Placeholder text */
  placeholder?: string;
  /** Mask input as password dots */
  password?: boolean;
  /** Default value */
  defaultValue?: unknown;
  /** Options for dropdown/combobox */
  options?: Array<{ label: string; id: string }>;
  /** Help text shown below the field */
  description?: string;
  /** Conditional visibility based on another field's value */
  condition?: { field: string; value: string };
  /** Whether the field is read-only */
  readOnly?: boolean;
  /** Show a copy-to-clipboard button */
  showCopyButton?: boolean;
  /** Hide this field from the UI */
  hidden?: boolean;
}

Dynamic Tool Selection

When a block has multiple tools, use tools.config.tool to select which one runs:

defineBlock({
  // ...
  subBlocks: [
    {
      id: "operation",
      title: "Operation",
      type: "dropdown",
      options: [
        { label: "Create", id: "create" },
        { label: "Read", id: "read" },
        { label: "Update", id: "update" },
        { label: "Delete", id: "delete" },
      ],
    },
    // ... other fields
  ],
  tools: {
    access: ["acme_create", "acme_read", "acme_update", "acme_delete"],
    config: {
      tool: (params) => `acme_${params.operation}`,
    },
  },
});

Conditional Visibility

Show or hide fields based on other field values:

subBlocks: [
  {
    id: "authType",
    title: "Auth Type",
    type: "dropdown",
    options: [
      { label: "API Key", id: "apiKey" },
      { label: "Username/Password", id: "basic" },
    ],
  },
  {
    id: "apiKey",
    title: "API Key",
    type: "short-input",
    password: true,
    condition: { field: "authType", value: "apiKey" },
  },
  {
    id: "username",
    title: "Username",
    type: "short-input",
    condition: { field: "authType", value: "basic" },
  },
  {
    id: "password",
    title: "Password",
    type: "short-input",
    password: true,
    condition: { field: "authType", value: "basic" },
  },
],

defineTriggerBlock(config)

Defines a trigger block — a first-class workflow member that renders in the "Triggers" sidebar section and carries runtime metadata describing how it fires (webhook, polling, or both). The category is auto-set to "trigger".

interface TriggerBlockInput {
  /** Unique block type */
  type: string;
  /** Display name */
  name: string;
  /** Description */
  description?: string;
  /** Background color in hex */
  bgColor?: string;
  /** Auth mode: "none", "apiKey", "oauth", "botToken" */
  authMode?: string;
  /** Form fields for trigger configuration */
  subBlocks?: SubBlockInput[];
  /** Which tools this trigger block can invoke */
  tools?: {
    access: string[];
    config?: {
      tool?: (params: Record<string, any>) => string;
      params?: (params: Record<string, any>) => any;
    };
  };
  /** Input connections */
  inputs?: Record<string, { type: string; description?: string }>;
  /** Output schema — what data the trigger emits */
  outputs?: Record<string, { type: string; description?: string }>;
  /** Provider identifier */
  provider?: string;
  /** Semantic version */
  version?: string;
  /** Runtime configuration — how events reach the workflow engine */
  runtime?: {
    webhook?: {
      method?: "POST" | "GET" | "PUT" | "DELETE";
      headers?: Record<string, string>;
    };
    polling?: PollingProvider;
  };
}

Secrets Management

Vendors can declare secrets that are stored encrypted at rest and resolved per-invocation. Secrets are scoped to the organization and vendor, so each org has its own isolated set of credentials.

Declaring Secrets

Define secret requirements in defineVendor() using the secrets array:

export default defineVendor({
  id: "acme",
  name: "Acme",
  version: "1.0.0",
  icon: acmeIcon,
  auth: { credentials: { apiKey: { type: "apiKey", label: "API Key" } }, default: "apiKey" },
  secrets: [
    {
      key: "WEBHOOK_SIGNING_SECRET",
      label: "Webhook Signing Secret",
      description: "Used to verify incoming webhook payloads",
      required: true,
    },
    {
      key: "ENCRYPTION_KEY",
      label: "Encryption Key",
      description: "AES key for encrypting sensitive payloads",
      required: false,
    },
  ],
  products: [/* ... */],
});

Each SecretInput has the following shape:

interface SecretInput {
  /** Unique key within the vendor — must match ^[a-zA-Z0-9_-]+$ */
  key: string;
  /** Human-readable label shown in the admin UI */
  label: string;
  /** Help text describing what this secret is used for */
  description?: string;
  /** Whether the integration requires this secret to function */
  required: boolean;
}

Secret keys must contain only alphanumeric characters, hyphens, and underscores (^[a-zA-Z0-9_-]+$). Keys that do not match this pattern are rejected at save time.

Secret Key Format

Secrets are stored using a structured key that scopes them to the organization and vendor:

integration:{orgId}:{vendorId}:{key}

For example, a secret with key WEBHOOK_SIGNING_SECRET for vendor acme in organization org_abc123 is stored as:

integration:org_abc123:acme:WEBHOOK_SIGNING_SECRET

This ensures complete isolation between organizations and between vendors within the same organization.

Encryption at Rest

All secret values are encrypted with AES-256-GCM before being persisted. The encryption key strategy is configured per organisation — LOCAL, BYOK, or HYOK. Secret values are never stored in plaintext. See Security → Secrets management for the full model.

Accessing Secrets at Runtime

When a tool executes, the platform resolves all declared secrets for the vendor and injects them into ctx.secrets as a flat Record<string, string>:

import { defineTool } from "@scrydon/sdk-authoring/integrations/define";
import { z } from "zod";

export const verifyWebhook = defineTool({
  id: "acme:webhooks:verify",
  name: "Verify Webhook",
  version: "1.0.0",
  input: z.object({ payload: z.string(), signature: z.string() }),
  output: z.object({ valid: z.boolean() }),
  async execute(input, ctx) {
    const signingSecret = ctx.secrets["WEBHOOK_SIGNING_SECRET"];
    if (!signingSecret) {
      return { success: false, output: { valid: false }, error: "Webhook signing secret not configured" };
    }

    const valid = verifyHmac(input.payload, input.signature, signingSecret);
    return { success: true, output: { valid } };
  },
});

The ctx.secrets object only contains secrets that have been set by the org admin. If a secret has not been configured, it will be absent from the record. Always check for the presence of required secrets before using them.

Admin UI

Organization administrators configure secrets through the integration settings UI under Settings > Integrations > [Vendor] > Secrets. The UI displays the declared secret fields from the vendor's secrets array.

Secret values are write-only -- after a value is saved, it is never displayed again. The UI only shows which keys have been configured (with a masked indicator) and allows admins to update or delete them.

API Endpoints

The platform exposes three internal admin endpoints for secret management:

EndpointPurpose
/integrations/admin/bundle-secret-setCreate or update a secret value
/integrations/admin/bundle-secret-deleteDelete a secret
/integrations/admin/bundle-secret-listList configured secret keys (values are never returned)

These endpoints are internal (service-to-service) and require the caller to be an authorized internal service. They are not directly accessible from client applications.


Build CLI

# Build a bundle
sdk-authoring integrations build [--entry src/index.ts] [--outDir dist]

# Test a bundle
sdk-authoring integrations test [--level static|sandbox|live] [--tool <id>]

Build Output

my-vendor-1.0.0.bundle.tar.gz
├── manifest.json           # Auto-generated metadata (JSON Schemas, UI defs)
├── dist/
│   └── index.js            # Compiled, minified ESM (all deps inlined via esbuild)
├── meta/
│   ├── sbom.cdx.json       # CycloneDX 1.6 SBOM (auto-generated)
│   └── metafile.json       # esbuild dependency graph
└── assets/                 # Optional icons
    └── icon.svg

The build CLI inlines all npm dependencies into the single dist/index.js file and auto-generates a CycloneDX 1.6 SBOM at meta/sbom.cdx.json. There is no runtime npm install — the bundle is fully self-contained and deterministic.

Vendor ID Rules

  • Lowercase alphanumeric characters and hyphens only
  • Must start with a letter
  • Pattern: /^[a-z][a-z0-9-]*$/
  • Examples: acme, my-vendor, hello-world

Tool ID Convention

Tool IDs use the fully qualified format {vendorId}:{productId}:{action} at runtime:

  • hello-world:hello-world:say-hello
  • google:sheets:read
  • slack:messaging:send-message

Shorthand: In authoring code you can use just the action part (e.g. "say-hello", "read"). The SDK auto-qualifies short IDs by prepending {vendorId}:{productId}: during defineVendor(). All first-party integrations use the short form. If you use the full form, the vendor/product prefix must match — the build CLI validates this.


Capability Helpers

Capability helpers wrap runtime configurations with a type tag so the manifest extractor can identify them. Each helper returns its config with a _capability tag used internally during build.

HelperTagPurpose
defineCapabilityLLM(config)llmFull LLM provider (chat, completion, streaming)
defineCapabilitySTT(config)sttSpeech-to-text (batch and realtime)
defineCapabilityTTS(config)ttsText-to-speech (batch and realtime)
defineCapabilityEmbedding(config)embeddingVector embedding generation
defineCapabilityVideo(config)videoVideo generation
defineCapabilityOCR(config)ocrDocument parsing / optical character recognition
defineCapabilityWebhook(config)webhookVendor-specific webhook handling
defineCapabilityDiscovery(config)discoveryResource browsing, selectors, access scopes

All helpers are imported from @scrydon/sdk-authoring/integrations/define:

import {
  defineCapabilityLLM,
  defineCapabilitySTT,
  defineCapabilityTTS,
  defineCapabilityEmbedding,
  defineCapabilityVideo,
  defineCapabilityOCR,
  defineCapabilityWebhook,
  defineCapabilityDiscovery,
} from "@scrydon/sdk-authoring/integrations/define";

Capability configs are placed inside defineProduct({ capabilities: { runtimes: { ... } } }). The manifest extractor converts runtime functions to boolean flags and extracts model metadata (dimensions, benchmarks, pricing) into the manifest's capabilityMeta field. See Capabilities for full usage examples of each helper.


Manifest Schema

The build CLI auto-generates manifest.json from your defineVendor() call. The manifest is a JSON-serializable snapshot of your vendor's metadata, stripped of runtime functions. It follows manifestVersion: 1.

Top-Level Structure

{
  "$schema": "...",              // Optional JSON Schema URL
  "manifestVersion": 1,         // Always 1
  "version": "1.0.0",           // Vendor version (from defineVendor)
  "vendor": { ... },            // Vendor metadata
  "auth": { ... },              // Authentication config
  "products": [ ... ],          // Product definitions
  "protocols": [ ... ],         // Protocol definitions (optional)
  "llm": { ... }                // LLM provider config (optional)
}

Vendor Section

Contains display metadata. Runtime code is excluded.

FieldTypeRequiredDescription
idstringYesVendor ID (/^[a-z][a-z0-9-]*$/)
namestringYesDisplay name
descriptionstringNoShort description
colorstringNoHex brand color
iconstringYesSVG icon as string
websitestringNoVendor website URL
docsUrlstringNoDocumentation URL
categoriesstring[]NoCategory tags
defaultEnabledbooleanNoAuto-enable for new orgs (default: false)
supportsSystemModebooleanNoSupports client_credentials (default: false)
connectivity"cloud" | "local" | "hybrid"NoNetwork requirements
configFieldsConfigField[]NoAdmin-configurable fields
secretsSecret[]NoVendor-level secrets

Product Section

Each product in the products array contains:

FieldTypeRequiredDescription
idstringYesProduct ID
namestringYesDisplay name
capabilities.toolsTool[]NoTool definitions with JSON Schema input/output
capabilities.triggersTrigger[]NoTrigger definitions
capabilities.runtimesobjectNoBoolean flags for llm, stt, tts, embedding, video, ocr
capabilities.capabilityMetaobjectNoExtracted model metadata (embedding dimensions, STT/TTS models, benchmarks)
capabilities.webhooksbooleanNoWhether the product handles webhooks
capabilities.discoverybooleanNoWhether the product supports resource discovery
blockBlockNoWorkflow editor block definition
dependenciesDependency[]NoDeclared NPM dependencies with reasons
systemModeobjectNoSystem-mode (client_credentials) configuration

How Runtime Code Becomes Manifest Data

The build CLI transforms runtime definitions into manifest-safe JSON:

Source (defineProduct)Manifest Output
runtimes.llm (with executeRequest function)runtimes.llm: true
runtimes.embedding (with embed function, models with dimension)runtimes.embedding: true + capabilityMeta.embeddingModels
runtimes.stt (with transcribe function, models)runtimes.stt: true + capabilityMeta.sttModels
runtimes.tts (with synthesize function, models)runtimes.tts: true + capabilityMeta.ttsModels
Tool input/output (Zod schemas)JSON Schema objects
Tool execute functionOmitted (not serializable)
Block tools.config.tool functionOmitted (serialized separately)

The manifest is what the platform reads at boot time to populate the integration catalog. It contains no executable code -- only metadata, JSON Schemas, and UI definitions. Actual tool execution happens lazily when a tool is first invoked, loading the bundle's dist/index.js inside a sandboxed Worker Thread.

On this page

On this page