Skip to main content
Use direct execution when your app already knows the operation — scheduled jobs, workflow steps, or UI buttons like “Send calendar invite”. No MCP client required.

API vs DB paths

Corsair plugins expose two families of operations:
Path patternWhat it doesWhen to use
<plugin>.api.*Live call to the third-party API (GitHub, Slack, …)Writes, one-off reads, refreshing the synced cache
<plugin>.db.*Read/search Corsair’s synced database for that instanceUI feeds, lists, search, detail pages — avoids rate limits
Default for read-heavy UI: tenant.run("github.db.issues.search", …) (or the relevant entity). Corsair populates the DB when *.api.* endpoints run (e.g. during an explicit Resync). Design the UI to read from .db on every render and call .api only when the user asks to refresh or when performing a write.
// Feed: read from cache
const { data: issueRows } = await t.run("github.db.issues.search", {
  data: { url: { contains: "corsairdev/corsair" } },
  limit: 100,
  offset: 0,
});

// Resync button: pull from GitHub into the cache
await t.run("github.api.issues.list", {
  owner: "corsairdev",
  repo: "corsair",
  state: "open",
  perPage: 100,
});

// Post comment: write via API
await t.run("github.api.issues.createComment", {
  owner: "corsairdev",
  repo: "corsair",
  issueNumber: 42,
  body: "On it!",
});
Paths and schemas: integrations catalog — e.g. /md/integrations/github for all GitHub paths, /md/integrations/github.api.issues.list for that endpoint’s schema. Then call tenant.run(path, input).

Run an operation

import { createClient } from "@corsair-dev/app";

const corsair = createClient({ apiKey: process.env.CORSAIR_DEV_KEY! });
const inst = corsair.instance(process.env.CORSAIR_INSTANCE_ID!);

// List tenants your backend can act on behalf of
const { tenants } = await inst.tenants.list();

// Run an operation for a specific tenant
const t = inst.tenant("alice");
await t.run("slack.api.messages.post", {
  channel: "#general",
  text: "Hello from chat",
});
Paths use the catalog’s dot notation: plugin.section.operation (e.g. github.api.repositories.star, github.db.pullRequests.search).

Connect the user

After provisioning, mint a connect link proactively and send it to the user:
const { url } = await t.connectLink.create();
// Send url to the user so they can connect all installed plugins
See Tenants and auth for options (plugins, ttlMs).

Handle missing auth

run() returns a result object instead of throwing when the tenant still needs to connect a plugin:
const result = await t.run<{ messages: unknown[] }>("gmail.api.messages.list");

if (!result.success) {
  // Reactive fallback — same connect page as connectLink.create()
  redirect(result.signInLink);
  return;
}

console.log(result.data);
Prefer connectLink.create() when you know the user still needs to connect accounts. Use signInLink when you discover missing auth at call time.

Permissions

Direct execution respects the same instance policies as MCP. Denied operations return CorsairApiError with status 403.
await inst.plugins.permissions.setOverride(
  "github",
  "api.repositories.delete",
  "deny",
);

MCP vs direct

Use caseUse
Agent picks tools at runtimeCoding agents or Agent SDKs — MCP meta tools only; no operation hints in the system prompt
UI feeds / lists / searchtenant.run("<plugin>.db.*")
User-triggered refresh into cachetenant.run("<plugin>.api.*") list/read endpoints
Backend knows the exact writetenant.run("<plugin>.api.*")