Connect SDK

Connect a browser application

Declare a workout contract, authorize one collection, complete the browser callback, and make a revision-safe update.
Local and managed Connect are separate environments.

This guide's localhost example uses the repository's local Connect stack. The managed service rejects HTTP application manifests, including localhost. To use https://connect.mdbase.dev, deploy the application at HTTPS URLs and declare those exact URLs in its manifest.

Before you begin

This guide expects:

  • the repository's local Connect stack for the runnable localhost example;
  • a browser application running at http://localhost:5173;
  • Node 24 LTS and the repository's pinned pnpm version;
  • a Connect SDK source build or beta artifact.

In the Connect checkout, copy .env.example to .env on first use, then start the local control plane and an isolated desktop profile:

Local Connect environment
# Run in the mdbase-connect checkout.
pnpm dev:environment:up

# Run in another terminal.
pnpm dev:desktop:fresh

Enter http://127.0.0.1:8787 in the desktop pairing screen, approve the computer in the local portal, and add a collection. This profile is separate from the normal desktop profile: accounts, grants, and collections registered with the managed service do not appear automatically.

The example manifest carries a complete transactional contract/type pack. Connect can install it during approval when the chosen collection does not yet provide workout.record 1.0.0.

1. Add a bundled application manifest

Save this file at public/.well-known/mdbase-app.json. These loopback URLs are accepted by the local Connect stack. Pass --allow-local to the validator when checking this development manifest.

public/.well-known/mdbase-app.json
{
  "manifest_version": 1,
  "id": "dev.example.workouts",
  "name": "Example Workouts",
  "homepage": "http://localhost:5173",
  "redirect_uris": [
    "http://localhost:5173/auth/mdbase/callback"
  ],
  "requirements": {
    "contracts": [
      { "id": "workout.record", "version": "1.0.0" }
    ]
  },
  "provisions": {
    "type_packs": [
      {
        "provides": [
          { "id": "workout.record", "version": "1.0.0" }
        ],
        "manifest": {
          "kind": "mdbase.type-pack",
          "id": "example.workouts",
          "version": "1.0.0",
          "resources": [
            {
              "kind": "contract",
              "source": "contracts/workout.record.md",
              "target": "_contracts/workout.record.md",
              "digest": "sha256:e0c2f77dd7158ca7e6b52b13da8c306f0a7e1b97582a1024f00ec0cf7fe5f259"
            },
            {
              "kind": "type",
              "source": "types/workout.md",
              "target": "_types/workout.md",
              "digest": "sha256:ff35309cd4598182d2021f698ee85cad4d320d965cd5f7c1bd608ee2eb16505b"
            }
          ]
        },
        "resources": [
          {
            "source": "contracts/workout.record.md",
            "document": "---\nkind: mdbase.contract\ncontract_type: record\nid: workout.record\nversion: 1.0.0\nrecord_schema:\n  dialect: json-schema-2020-12\n  value:\n    $schema: https://json-schema.org/draft/2020-12/schema\n    type: object\n    additionalProperties: false\n    properties:\n      title: { type: string }\n      completed: { type: boolean, default: false }\n    required: [title]\n---\n"
          },
          {
            "source": "types/workout.md",
            "document": "---\nkind: mdbase.type\nname: workout\nversion: 1\nschema:\n  dialect: json-schema-2020-12\n  value:\n    type: object\n    additionalProperties: true\n    properties:\n      title: { type: string }\n      completed: { type: boolean }\n    required: [title]\nimplements:\n  - contract: workout.record\n    version: 1.0.0\n    fields:\n      title: title\n      completed: completed\n---\n"
          }
        ]
      }
    ]
  }
}

The contract requirement limits compatible collections. The type pack makes a collection safely provisionable during approval and does not give the application general type-management access.

2. Add the SDK and validator

After the public npm release, installation uses:

Public release command
pnpm add @mdbase-dev/connect
pnpm add -D @mdbase-dev/connect-dev

Until the packages are published to npm, create immutable consumer archives from a clean Connect checkout. The command prints the exact artifact filenames and source revision. Add the printed protocol and client archives to the application with pnpm add <archive>.

Open-beta source checkout
# Run in the mdbase-connect checkout.
mkdir -p ../workouts-app/vendor
pnpm package:consumer -- \
  --destination ../workouts-app/vendor \
  --packages connect,protocol

The validator is currently run from the Connect workspace or supplied as a beta artifact. --allow-local affects static validation only; it is not an option on the desktop application and does not change the managed service's HTTPS policy:

Validate a local manifest
mdbase-connect-dev validate-manifest \
  public/.well-known/mdbase-app.json \
  --allow-local

3. Create one stable Connect client

src/connect.ts
import {
  MdbaseBrowserLocation,
  MdbaseConnect,
  type JsonObject
} from "@mdbase-dev/connect";

export interface Workout extends JsonObject {
  title: string;
  completed?: boolean;
}

const loopback = new Set([
  "localhost",
  "127.0.0.1",
  "::1"
]).has(location.hostname);

export const mdbase = new MdbaseConnect<Workout>({
  serverUrl: loopback
    ? "http://127.0.0.1:8787"
    : "https://connect.mdbase.dev",
  manifest: new URL(
    "/.well-known/mdbase-app.json",
    location.origin
  ).href,
  redirectUri: new URL(
    "/auth/mdbase/callback",
    location.origin
  ).href
});

export const collectionLocation =
  new MdbaseBrowserLocation(mdbase);

The manager owns saved authorizations. The location helper owns the bookmarkable collection ID, browser history changes, and authorization return cleanup.

4. Start authorization from a user action

src/connect-button.ts
import { collectionLocation, mdbase } from "./connect";

export async function connectCollection() {
  await mdbase.authorize({
    operations: ["describe", "read", "query", "create", "update"],
    collectionId:
      collectionLocation.selectedCollectionId() ?? undefined,
    returnTo: collectionLocation.authorizationReturnTo()
  });
  // authorize() navigates to Connect and does not return.
}

Connect presents ready and provisionable collections, then asks the user to approve the listed operations. Collection records remain private until approval succeeds.

5. Complete the exact callback route

src/mdbase-callback.ts
import { collectionLocation } from "./connect";

export async function completeMdbaseCallback() {
  const connection =
    await collectionLocation.completeAuthorization();

  console.log(
    "Connected collection:",
    connection.collectionId
  );

  return connection;
}

Call this once at the manifest's callback path. PKCE is mandatory. A successful callback stores the grant-bound authorization and returns a collection-bound connection.

src/main.ts
import {
  completeMdbaseCallback
} from "./mdbase-callback";
import { connectCollection } from "./connect-button";

document
  .querySelector("[data-connect-mdbase]")
  ?.addEventListener("click", () => connectCollection());

if (location.pathname === "/auth/mdbase/callback") {
  await completeMdbaseCallback();
}

6. Query and update with a revision

src/workouts.ts
import { unwrapOperation } from "@mdbase-dev/connect";
import { collectionLocation } from "./connect";

const collection = collectionLocation.activeConnection();
if (!collection) throw new Error("Choose a collection.");

const page = unwrapOperation(await collection.query({
  contract: {
    id: "workout.record",
    version: "1.0.0"
  },
  limit: 50
}));

const first = page.results
  .filter((row) => row.completed !== true)
  .sort((left, right) =>
    left.title.localeCompare(right.title)
  )[0];
if (first) {
  unwrapOperation(await collection.update({
    path: first.path,
    patch: { completed: true },
    contract: {
      id: "workout.record",
      version: "1.0.0"
    },
    if_revision: first.revision
  }));
}

Query results carry an opaque revision. Passing it as if_revision prevents one application from silently overwriting a newer change.

Contract-scoped queries expose normalized mapped fields only. They union every approved implementation and deliberately leave filtering and sorting those normalized values to the application.

Move to the managed service

The production service accepts public beta accounts. Before using it, deploy the application at an HTTPS origin, replace every localhost homepage, icon, and redirect URL in the manifest with the exact deployed URLs, and validate without --allow-local. The non-loopback branch in the client example then uses https://connect.mdbase.dev.

A localhost application cannot connect to the managed service. Use the full local environment above for local protocol testing, or use the in-memory sandbox for feature tests that do not need authorization, routing, or a real connector.

What success looks like

  1. The Connect approval screen names Example Workouts.
  2. The collection chooser marks a matching collection as ready or provisionable.
  3. The approval screen lists the five requested operations.
  4. The browser returns to /auth/mdbase/callback.
  5. The console prints the stable ID of the approved collection.
  6. The first incomplete workout receives completed: true.
Keep the collection ID in application URLs.

MdbaseBrowserLocation writes ?collection=<id> and follows browser back and forward navigation. Collection IDs are opaque locators and can appear in browser history and logs. They are not credentials.