Integrations architecture
Project Brain connects to external systems through a provider registry. Jira, Confluence, and GitHub Issues all use the same dispatch model, so most app routes and UI pages do not need provider-specific code.
Core idea
- Providers are registered by id, such as
jira,confluence, orgithub_issues. - Connect, callback, preview, save, queue, worker sync, scope pages, and provider grids all dispatch through the registry.
- Server-side registry and provider wiring live in
@projectbrain/integrations. - Provider SDK clients and auth live in their own packages —
@projectbrain/atlassian(OAuth + HTTP client shared by Jira and Confluence) and@projectbrain/github(the GitHub App: manifest registration, config, installation tokens) — which the server providers wrap. - Browser-safe scope form code lives in
@projectbrain/integrations/ui.
This split keeps database clients, provider SDKs, and HTTP clients out of the browser bundle.
Package layout
packages/
adapters-jira/
adapters-confluence/
adapters-github-issues/
atlassian/ # shared Atlassian OAuth + HTTP client + config
src/
oauth.ts
atlassian-client.ts
resources.ts
config.ts
github/ # GitHub App: manifest, config, installations, JWT signing
src/
manifest.ts
config.ts
installations.ts
app-jwt.ts
integrations/
src/
types.ts
registry.ts
tokens.ts
sync/orphans.ts
oauth/atlassian.ts
providers/
jira.ts
confluence.ts
github-issues.ts
index.ts # module-load registration (registerProvider calls)
ui/
types.ts
jira.ts
confluence.ts
github-issues.ts
index.ts
apps/
web/components/scope-form.tsx
web/app/(app)/projects/[slug]/integrations/
web/app/api/integrations/
worker/src/workers/sync.ts
Provider contract
Each provider has two halves joined by the same id.
Server provider
Runs in Next.js API routes and the worker.
interface IntegrationProvider<TScope, TSample> {
id: string;
name: string;
blurb: string;
icon: string;
docs?: ProviderDocs;
isConfigured(): Promise<boolean>;
connect: ConnectFlow;
getAccessToken(integration): Promise<string>;
scope: {
schema: ZodType<TScope>;
empty: TScope;
};
preview(args): Promise<PreviewResult<TSample>>;
sync: {
kind;
pathPrefix;
pathRegex;
run;
};
}
UI provider
Runs in the browser scope form.
interface ScopeUiConfig<TScope> {
fields: ScopeFieldConfig[];
queryLabel: string;
itemNoun: string;
formToScope(form): TScope;
scopeToForm(scope): Record<string, string>;
summarizeSample(item): SampleItemSummary;
}
Connect flows
ConnectFlow is a discriminated union.
| Kind | Use case | Example |
|---|---|---|
oauth-redirect | Provider consent flow with callback and token storage | Atlassian |
project-resource | Adopt an existing project resource without OAuth token storage | GitHub App installation |
The connect route switches on kind. The OAuth callback route only handles oauth-redirect.
GitHub registration uses a separate manifest flow (/api/admin/integrations/github/manifest → GitHub manifest endpoint → /api/github/manifest/callback) that creates the App on GitHub and persists the resulting App id, slug, client id/secret, webhook secret, and PEM to platform_integrations (encrypted). This sits in front of the project-resource connect, which then attaches an installation id to the same row. See packages/github/src/manifest.ts and packages/github/src/config.ts.
Add a provider
-
Create an adapter package under
packages/adapters-X/. -
Add scope schema, client, fetch, preview, iteration, and transform code.
-
Add an OAuth family under
packages/integrations/src/oauth/if the provider uses OAuth. -
Add the server provider in
packages/integrations/src/providers/X.ts. -
Register the server provider in
packages/integrations/src/providers/index.ts. -
Add browser-safe UI config in
packages/integrations/src/ui/X.ts. -
Register the UI config in
packages/integrations/src/ui/index.ts. -
Extend
integration_typeinpackages/db/src/schema/integrations.ts. -
Run:
pnpm --filter @projectbrain/db generate -
Update casts that mention existing provider ids:
rg "as 'jira' \\| 'confluence'" -
Add an icon key in
apps/web/components/icons.tsxif needed.
After registration, the provider grid, dynamic scope page, and worker sync dispatcher pick it up automatically.
Important design choices
Server and client registries are separate
The server registry imports database and provider code. The UI registry imports only browser-safe field definitions and pure mapping functions.
Client components import:
@projectbrain/integrations/ui
They should not import:
@projectbrain/integrations
Functions do not cross the RSC boundary
React Server Components can only pass serializable props. The server page passes providerId, and the client scope form calls getProviderUi(providerId).
Hooks stay inside the inner form
The outer scope form handles missing provider UI and returns an error state if needed. The inner form receives a valid ui object and runs hooks unconditionally.
Token lifecycle belongs to the provider
OAuth providers call the shared getOAuthAccessToken(integration, family) helper. GitHub Apps mint installation tokens on demand through Octokit instead of using refresh tokens — the App's own credentials (id, client id/secret, webhook secret, PEM) live encrypted on platform_integrations, loaded via loadGitHubAppConfig() with a short-lived in-memory cache.
Orphan detection is shared
detectAndStageDeletions is shared in:
packages/integrations/src/sync/orphans.ts
Each provider declares sync.pathPrefix and sync.pathRegex. The helper compares seen external ids against existing files and stages deletions.
Current limitations
| Limitation | Impact |
|---|---|
integration_type is a Postgres enum | Adding a provider requires a Drizzle migration. |
| Some provider id casts are manual | Update casts when adding providers. |
| Multi-workspace selection auto-picks the first workspace | Users with multiple Atlassian sites or GitHub installations cannot choose yet. |
| Scope form layout is generic | Complex provider UIs may need a future custom field escape hatch. |
Preview sample items are typed as unknown in the registry | Each UI config casts locally in summarizeSample. |
| Single GitHub installation per deployment | Switching org replaces the existing installation; you can't sync from multiple GitHub orgs concurrently. |
Connecting an IDE via MCP
The sections above cover how Project Brain ingests external systems. The reverse direction — exposing a project's knowledge base to an IDE — is handled by an MCP server, configured in-app.
A project admin opens a project, clicks the gear menu in the project shell, and chooses Connect to IDE (MCP) (the menu is admin-only). The modal walks through three steps:
- Install the
mcp-project-brainbinary with the one-liner for your OS (PowerShell on Windows,curl … | bashon macOS/Linux). - Set a GitHub token — a fine-grained PAT scoped to the project's knowledge-base repo with Contents: Read and write, exported as
GITHUB_PERSONAL_ACCESS_TOKEN. - Add the MCP server to NeusisCode — copy the generated config, which runs
mcp-project-brain stdio --kb-repo=<owner/repo>over stdio. The repo is pre-filled from the project's connected GitHub repo.
This flow targets NeusisCode. For the binary, install scripts, and full reference, see docs.neusis.ai/docs/project-brain-mcp.
Reference files
| Concern | File |
|---|---|
| Provider types | packages/integrations/src/types.ts |
| Server registry | packages/integrations/src/registry.ts |
| UI registry | packages/integrations/src/ui/index.ts |
| Token lifecycle | packages/integrations/src/tokens.ts |
| Orphan helper | packages/integrations/src/sync/orphans.ts |
| Scope form | apps/web/components/scope-form.tsx |
| Provider grid | apps/web/app/(app)/projects/[slug]/integrations/page.tsx |
| Sources dashboard | apps/web/app/(app)/projects/[slug]/sources/page.tsx |
| Dynamic scope page | apps/web/app/(app)/projects/[slug]/integrations/[provider]/page.tsx |
| Source detail page | apps/web/app/(app)/projects/[slug]/sources/[provider]/page.tsx |
| Connect route | apps/web/app/api/projects/[slug]/integrations/[product]/connect/route.ts |
| OAuth callback | apps/web/app/api/integrations/oauth/callback/route.ts |
| Worker sync | apps/worker/src/workers/sync.ts |