SDK v1.4.0

Build

Connector SDK

A connector teaches Fibric to sense and act on one system. You declare it with three exports, defineConnector, tool, and auth, and the platform gives you the rest: capability binding so operators never depend on your brand, the governed executor for anything that changes the world, and a marketplace listing other tenants can install. First-class TypeScript and Python.

defineConnector()

Declares the connector: its name, the system it speaks to, its auth, and the tools it exposes.

returns ConnectorDef
tool()

Declares one capability the connector can perform, its input schema, and whether it changes the world.

returns ToolDef
auth.*

Declares how credentials are obtained, resolved from the vault at call time, never hard-coded.

returns AuthSpec

defineConnector #

Every connector is a default export built with defineConnector. The returned ConnectorDef is the whole contract: the platform reads it to register your capabilities, mount your auth in the vault, and publish the listing. Nothing about your connector is hidden in runtime behavior, the def is the source of truth.

ConnectorDef shape
interface ConnectorDef {
  name: string;            // stable slug, e.g. "acme-wms"
  summary: string;         // one line shown in the marketplace
  version?: string;        // semver; the CLI stamps it on publish
  auth: AuthSpec;          // how credentials are obtained
  tools: ToolDef[];        // the capabilities this connector exposes
  homepage?: string;       // vendor docs / status page
  scopes?: string[];       // OAuth scopes requested, if any
}
FieldRequiredNotes
nameyesLowercase slug, unique within the marketplace. Becomes the install id.
summaryyesOne sentence. This is the line a reader skims in the marketplace grid.
authyesAn AuthSpec from the auth helper. Resolved per connection at call time.
toolsyesAt least one ToolDef. Each binds to a capability operators can request.
versionnoSemver. Omit it and the CLI prompts on publish; pin it for reproducible builds.

tool #

A tool is one thing the connector can do. It binds to a capability, the verb an operator asks for, not your brand. Two connectors can both provide order.read; an operator that asks for order.read works against either, so swapping a vendor stays a configuration change. The most consequential field is sideEffecting: it decides whether a tool runs inline or routes through the governed executor.

ToolDef shape
interface ToolDef<I> {
  name: string;              // unique within the connector
  capability: string;        // the verb operators bind to, e.g. "order.hold"
  input: Schema<I>;          // zod (TS) / pydantic (Py); validated before run
  sideEffecting?: boolean;   // false (default) runs inline; true routes through the executor
  idempotent?: boolean;      // hint: is a retry of the same args safe to coalesce
  summary?: string;          // shown on the capability in the marketplace
  run(args: { input: I; ctx: ToolCtx }): Promise<unknown>;
}
FieldRequiredNotes
nameyesUnique within the connector. Identifies the tool in receipts and dry-runs.
capabilityyesThe capability this tool fulfills. Operators request the capability, never the tool.
inputyesA schema. Input is validated before run is ever called; bad input never reaches your code.
sideEffectingnoDefaults to false. Set true for anything that changes the outside world. See below.
idempotentnoTells the executor a retry with the same idempotency key is safe to coalesce.
runyesThe body. Gets validated input and a ctx with the resolved HTTP client and credentials.

sideEffecting routing #

This one flag is what makes Fibric governable. A read tool senses the world and can run the moment an operator asks. A side-effecting tool changes the world, so it is never called directly. It becomes a step in a proposed ExecutionPlan that the deterministic executor disposes of, under your trust policy, single-flight per entity, and idempotency keys.

sideEffecting: false

operator sense tool.run

Runs inline. No plan, no policy gate, because nothing in the outside world changes. Reads are how the operator perceives: open orders, a thermostat reading, an inbox.

sideEffecting: true

operator propose executor policy tool.run receipt

Becomes a plan step. The executor checks it against your policy, acquires single-flight, applies the idempotency key, runs the tool, and writes a receipt. Fail-closed: if policy is silent, the step does not run.

!
When in doubt, mark it side-effecting

If a tool writes, posts, refunds, holds, emails, opens a door, or moves anything in a system of record, it is side-effecting. The cost of marking a read as side-effecting is a little latency; the cost of marking a write as a read is an ungoverned action with no receipt. Choose the safe failure.

A full connector #

Here is a complete, publishable connector for a warehouse management system. It exposes one read capability (order.read) and one side-effecting capability (order.hold). An operator that wants to hold slipping orders binds to those capabilities, never to acme-wms itself.

TypeScript

connectors/acme-wms.ts
import { defineConnector, tool, auth } from "@fibric/sdk";
import { z } from "zod";

export default defineConnector({
  name: "acme-wms",
  summary: "Acme warehouse management system",
  version: "1.2.0",

  // credentials are resolved from the vault per connection, never hard-coded
  auth: auth.apiKey({ header: "X-Acme-Key", secret: "ACME_API_KEY" }),

  tools: [
    // ---- READ: senses the world, runs inline, no plan ----
    tool({
      name: "list_open_orders",
      capability: "order.read",
      summary: "List open orders, newest first",
      input: z.object({ since: z.string().datetime() }),
      async run({ input, ctx }) {
        const { data } = await ctx.http.get("/orders", {
          params: { status: "open", since: input.since },
        });
        return data.orders;
      },
    }),

    // ---- WRITE: side-effecting, routes through the governed executor ----
    tool({
      name: "hold_order",
      capability: "order.hold",
      summary: "Place a hold on one order so it cannot ship",
      sideEffecting: true,
      idempotent: true,
      input: z.object({
        orderId: z.string(),
        reason: z.string().min(8),
      }),
      async run({ input, ctx }) {
        const { data } = await ctx.http.post(
          `/orders/${input.orderId}/hold`,
          { reason: input.reason },
        );
        return { held: true, orderId: input.orderId, at: data.heldAt };
      },
    }),
  ],
});

Python

connectors/acme_wms.py
from fibric import define_connector, auth
from pydantic import BaseModel, Field


class ListOpenOrders(BaseModel):
    since: str


class HoldOrder(BaseModel):
    order_id: str
    reason: str = Field(min_length=8)


connector = define_connector(
    name="acme-wms",
    summary="Acme warehouse management system",
    version="1.2.0",
    # credentials resolved from the vault per connection, never hard-coded
    auth=auth.api_key(header="X-Acme-Key", secret="ACME_API_KEY"),
)


# ---- READ: senses the world, runs inline, no plan ----
@connector.tool(capability="order.read", summary="List open orders, newest first")
def list_open_orders(input: ListOpenOrders, ctx) -> list[dict]:
    resp = ctx.http.get("/orders", params={"status": "open", "since": input.since})
    return resp.json()["orders"]


# ---- WRITE: side-effecting, routes through the governed executor ----
@connector.tool(
    capability="order.hold",
    summary="Place a hold on one order so it cannot ship",
    side_effecting=True,
    idempotent=True,
)
def hold_order(input: HoldOrder, ctx) -> dict:
    resp = ctx.http.post(
        f"/orders/{input.order_id}/hold",
        json={"reason": input.reason},
    )
    return {"held": True, "order_id": input.order_id, "at": resp.json()["heldAt"]}
i
The two languages are the same contract

TypeScript uses zod, Python uses pydantic, but the ConnectorDef and ToolDef they produce are identical on the wire. Pick the language your team already runs; an operator cannot tell which one built the connector it is calling.

auth #

The auth helper declares how a connection gets its credentials. You never see a secret value in your code, you name the slot, and the platform resolves it from the per-tenant vault at call time and injects it into ctx.http. The same connector code serves every tenant that installs it; their credentials never cross the wall.

TypeScript
// 1. API key in a header
auth.apiKey({ header: "X-Acme-Key", secret: "ACME_API_KEY" })

// 2. Bearer token
auth.bearer({ secret: "ACME_TOKEN" })

// 3. Basic auth
auth.basic({ user: "ACME_USER", pass: "ACME_PASS" })

// 4. OAuth 2.0 (the platform runs the dance and refreshes for you)
auth.oauth2({
  authorizeUrl: "https://acme.example/oauth/authorize",
  tokenUrl: "https://acme.example/oauth/token",
  scopes: ["orders.read", "orders.write"],
})
HelperUse when
auth.apiKeyThe system takes a static key in a named header.
auth.bearerA single bearer token in Authorization.
auth.basicHTTP basic, username and password.
auth.oauth2OAuth 2.0. The platform stores, refreshes, and injects the token; you never touch it.
auth.noneA public system with no credentials, rare, but supported for open data feeds.

Publish to the marketplace #

A connector becomes installable the moment you publish it. The CLI validates it against the marketplace contract, stamps a semver, and lists it so any tenant can install it and create their own connection. Operators in that tenant then bind to its capabilities. The platform stays free; connectors are the add-ons.

bash
# 1. validate against the marketplace contract
fibric connector validate ./connectors/acme-wms

# 2. dry-run a tool against a real connection, nothing is published
fibric connector test acme-wms list_open_orders --input '{"since":"2026-06-01T00:00:00Z"}'

# 3. publish a versioned, immutable build
fibric connector publish ./connectors/acme-wms --version 1.2.0

# 4. confirm it is live in the marketplace
fibric connector show acme-wms

Validation enforces the contract every listed connector honors:

+
Versions are immutable

Once 1.2.0 is published it can never change, so a tenant pinned to it can trust it will behave the same tomorrow. Ship a fix as 1.2.1; tenants upgrade on their own schedule. Deprecate an old line with fibric connector deprecate acme-wms@1.1.x.

For the operator side of this loop, how a proposed ExecutionPlan is disposed of and turned into receipts, see Operators and Governance & trust. To drive publishing and connections from scripts, see the CLI reference.