Custom Integrations
The three shipped integrations (Linear, GitHub, Slack) are concrete implementations of a single interface: IntegrationProvider, defined in packages/integrations-core/src/provider.ts. Implement that interface and your integration plugs into the same install / webhook / MCP-tools / publication shape.
What an integration provides
Section titled “What an integration provides”A provider is a small module that answers four questions:
- How do users install it? OAuth flow or credential paste.
- How do incoming webhooks map to sessions? Webhook payload → session dispatch.
- What can the agent do? A list of MCP tool descriptors + their executors.
- How is publication scoped? Per-workspace, per-issue, per-user, etc.
// packages/integrations-core/src/provider.ts (sketch)export interface IntegrationProvider { id: string; displayName: string;
install: { type: 'oauth' | 'credentials'; // ... };
parseWebhook(req: Request): WebhookEvent | null;
dispatch(event: WebhookEvent, ctx: ProviderContext): Promise<SessionDispatch>;
tools: McpToolDescriptor[]; executeTool(name: string, input: unknown, ctx: PublicationContext): Promise<unknown>;
publication: { scopes: PublicationScope[]; };}Anatomy of an existing provider
Section titled “Anatomy of an existing provider”Look at apps/integrations/src/routes/linear/ for a complete reference:
apps/integrations/src/routes/linear/├── install.ts // OAuth start + callback├── webhook.ts // Verify signature, parse event, dispatch├── tools.ts // mcp_linear_* tool definitions + executors└── publication.ts // Scopes (comment.create, issue.update, etc.)Each is small: install/callback is ~50 lines; webhook handling is ~80; tools are one block per capability. The interesting design work is in what scopes you expose and how you map webhooks to dispatches, not in plumbing.
Build a new provider
Section titled “Build a new provider”-
Add a folder under
apps/integrations/src/routes/<your-integration>/. -
Implement
IntegrationProvider. Start withid,displayName, the OAuth flow, and one MCP tool — get a session dispatched end-to-end before adding more. -
Register it.
apps/integrations/src/index.tshas a registry; add your provider to the list. The gateway exposes/install/<id>and/webhook/<id>automatically. -
Add UI install flow (optional but recommended). The Console has an
Integrationspage that picks up registered providers from/v1/integrations. -
Test locally.
pnpm dev:integrationsruns the gateway against your local main worker. Use a tunneling tool (cloudflared tunnel) to receive webhooks from the third-party service.
Persistence
Section titled “Persistence”Use the abstract repos in packages/integrations-adapters-cf/. They give you typed CRUD on installations, oauth_state, publications, and webhook_events against the shared AUTH_DB D1 instance. Don’t roll your own — the adapters are the same ones Linear/GitHub/Slack use, and they handle multi-tenant scoping for you.
Webhook security
Section titled “Webhook security”The base provider verifies webhook signatures using MCP_SIGNING_KEY for outbound delivery and per-provider signing keys for inbound. Don’t bypass this. If the third-party service has its own signature scheme, validate it inside parseWebhook and return null on failure.
MCP tool design
Section titled “MCP tool design”Each tool is one capability — favor many small tools over few mega-tools. Names are namespaced as mcp_<provider>_<tool>. Input schemas are JSON Schema.
{ name: 'mcp_jira_update_issue_status', description: 'Move a Jira issue to a new status.', inputSchema: { type: 'object', properties: { issue_key: { type: 'string', description: 'e.g. PROJ-123' }, status: { type: 'string', enum: ['Open', 'In Progress', 'Done'] }, }, required: ['issue_key', 'status'], },}The executor receives a PublicationContext with the OAuth token (already validated against the publication’s scopes) and the originating session metadata.