Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions templates/chat-sdk/CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Arcade Chat Bot — Development Notes

This is a multi-platform chat bot built with Chat SDK + AI SDK + Arcade MCP.

## Architecture

- **Chat SDK** (`chat` package) handles platform adapters and event routing
- **AI SDK** (`ai` package) handles LLM calls with tool use
- **Arcade MCP** provides tools (GitHub, Gmail, Calendar, etc.) via MCP Gateway
- **Next.js** serves the webhook endpoint and OAuth callback

## Key Files

- `src/bot.ts` — Chat instance, event handlers (`onNewMention`, `onSubscribedMessage`)
- `src/agent.ts` — Model selection and system prompt loading
- `src/arcade.ts` — MCP client with OAuth for Arcade Gateway
- `app/api/chat-webhook/route.ts` — Webhook endpoint (just re-exports `bot.handler`)
- `system-prompt.md` — Bot personality and instructions

## Adding Features

- To add a new event handler, use `bot.onReaction()`, `bot.onAction()`, etc. in `src/bot.ts`
- To add a new platform, install its adapter and add to the `adapters` object in `src/bot.ts`
- To change tools, update your Arcade MCP Gateway at https://app.arcade.dev/mcp-gateways
187 changes: 187 additions & 0 deletions templates/chat-sdk/README.md.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
# {{projectName}}

A multi-platform chat bot powered by [Arcade](https://arcade.dev) and [Chat SDK](https://chat-sdk.dev).

Deploy a single bot to Slack, Discord, Microsoft Teams, Google Chat, GitHub, and Linear — with AI-powered responses and Arcade tool integration.

## Prerequisites

### Create a Slack App

1. Go to [api.slack.com/apps](https://api.slack.com/apps) → **Create New App** → **From scratch**
2. Under **OAuth & Permissions**, add these Bot Token Scopes:
- `app_mentions:read`
- `chat:write`
- `reactions:read`
3. Install the app to your workspace and copy the **Bot User OAuth Token** (`xoxb-...`)
4. Under **Basic Information**, copy the **Signing Secret**

### Configure Arcade Gateway

1. Create a gateway at [app.arcade.dev/mcp-gateways](https://app.arcade.dev/mcp-gateways)
2. Add toolkits like Slack, Google Calendar, Linear, GitHub, Gmail
3. Copy the gateway URL

### Set up environment variables

```bash
cp .env.example .env
```

Fill in:

| Variable | Description |
| --- | --- |
| `ARCADE_GATEWAY_URL` | Your MCP Gateway URL |
| `OPENAI_API_KEY` or `ANTHROPIC_API_KEY` | LLM provider key (set one) |
| `SLACK_BOT_TOKEN` | Bot User OAuth Token (`xoxb-...`) |
| `SLACK_SIGNING_SECRET` | Signing Secret from Basic Information |

---

## Option A: Run Locally with ngrok

Best for development and testing.

### 1. Install and start ngrok

```bash
# Install ngrok (https://ngrok.com)
brew install ngrok # macOS
# or download from https://ngrok.com/download

# Start a tunnel to your dev server
ngrok http 3000
```

Copy the `https://...ngrok-free.app` URL that ngrok gives you.

### 2. Configure Slack Event Subscriptions

1. Go to your Slack app → **Event Subscriptions** → Enable Events
2. Set the Request URL to: `https://<your-ngrok-url>/api/chat-webhook`
3. Subscribe to bot events: `app_mention`
4. Save changes

### 3. Start the bot

```bash
npm run dev
```

### 4. Test it

Invite the bot to a Slack channel and @mention it. The first time, it will post an Arcade authorization link — click it, then @mention again.

> **Note:** ngrok URLs change every time you restart. Update the Slack Event Subscription URL each time.

---

## Option B: Deploy to Vercel

Best for production. Requires Redis for persistent storage (Vercel's filesystem is read-only).

### 1. Push to GitHub and import to Vercel

1. Push your project to a GitHub repo
2. Import it at [vercel.com/new](https://vercel.com/new)

### 2. Add Redis via Vercel Storage

1. In your Vercel project dashboard, go to **Storage**
2. Click **Create Database** → **Upstash Redis**
3. Follow the prompts — Vercel will automatically add the `REDIS_URL` environment variable to your project

### 3. Add environment variables

In your Vercel project, go to **Settings** → **Environment Variables** and add:

| Variable | Value |
| --- | --- |
| `ARCADE_GATEWAY_URL` | Your MCP Gateway URL |
| `OPENAI_API_KEY` or `ANTHROPIC_API_KEY` | LLM provider key |
| `SLACK_BOT_TOKEN` | Bot User OAuth Token |
| `SLACK_SIGNING_SECRET` | Signing Secret |
| `APP_URL` | Your Vercel deployment URL (e.g. `https://my-bot.vercel.app`) |

> **Note:** `REDIS_URL` is already set from the previous step.

### 4. Disable Deployment Protection

Vercel's Deployment Protection blocks external webhooks by default.

1. Go to your project → **Settings** → **Deployment Protection**
2. Set **Vercel Authentication** to **Off**

### 5. Configure Slack Event Subscriptions

1. Go to your Slack app → **Event Subscriptions** → Enable Events
2. Set the Request URL to: `https://<your-vercel-url>/api/chat-webhook`
3. Subscribe to bot events: `app_mention`
4. Save changes

### 6. Test it

Invite the bot to a Slack channel and @mention it. The first time, it will post an Arcade authorization link — click it, then @mention again.

---

## How It Works

When someone @mentions the bot in Slack:

1. The bot receives the event via the `/api/chat-webhook` endpoint
2. It subscribes to the thread for follow-up messages
3. It calls an LLM (Claude or GPT) with your system prompt and Arcade tools
4. The AI response streams back to the Slack thread

## Project Structure

```
├── app/
│ ├── api/
│ │ ├── chat-webhook/route.ts # Chat SDK webhook handler
│ │ └── auth/arcade/callback/ # Arcade OAuth callback
│ ├── layout.tsx
│ └── page.tsx # Status page
├── src/
│ ├── bot.ts # Chat instance + event handlers
│ ├── agent.ts # Model selection + system prompt
│ └── arcade.ts # Arcade MCP client + OAuth
├── system-prompt.md # Bot personality (edit this!)
├── .env.example
└── package.json
```

## Customization

### Change the bot's personality

Edit `system-prompt.md` to change how the bot responds.

### Change the LLM

The model is auto-selected based on your API key. Set `ANTHROPIC_API_KEY` for Claude or `OPENAI_API_KEY` for GPT. If both are set, Claude takes priority. Edit `src/agent.ts` to change the specific model.

### Add more platforms

Edit `src/bot.ts` to add adapters. Example for Discord:

```bash
npm install @chat-adapter/discord
```

```typescript
import { createDiscordAdapter } from "@chat-adapter/discord";

// In the adapters object:
adapters.discord = createDiscordAdapter({
publicKey: process.env.DISCORD_PUBLIC_KEY!,
});
```

See [chat-sdk.dev/docs](https://chat-sdk.dev/docs) for all available adapters.

### Add or change Arcade tools

Visit [app.arcade.dev/mcp-gateways](https://app.arcade.dev/mcp-gateways) to add toolkits to your gateway. The bot will automatically pick up any tools available through the gateway.
23 changes: 23 additions & 0 deletions templates/chat-sdk/_env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# --- Arcade ---
# Create a gateway at https://app.arcade.dev/mcp-gateways
# Add tools like Slack, GitHub, Google Calendar, Linear, Gmail
ARCADE_GATEWAY_URL=

# --- LLM Provider (set one) ---
# ANTHROPIC_API_KEY=
# OPENAI_API_KEY=

# --- Slack ---
# Create a Slack app at https://api.slack.com/apps
# Required scopes: app_mentions:read, chat:write, reactions:read
# Enable Event Subscriptions and point to your webhook URL
SLACK_BOT_TOKEN=
SLACK_SIGNING_SECRET=

# --- App URL (required for deployed environments) ---
# Set to your deployment URL so OAuth callbacks work correctly
# APP_URL=https://your-app.vercel.app

# --- Redis (required for Vercel/serverless, optional for local dev) ---
# Stores OAuth tokens and chat state across serverless invocations
# REDIS_URL=redis://localhost:6379
37 changes: 37 additions & 0 deletions templates/chat-sdk/_gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# dependencies
/node_modules
/.pnp
.pnp.*
.yarn/*
!.yarn/patches
!.yarn/plugins
!.yarn/releases
!.yarn/versions

# next.js
/.next/
/out/

# production
/build

# env files
.env
.env*.local

# arcade auth
.arcade-auth/

# misc
.DS_Store
*.pem

# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*

# typescript
*.tsbuildinfo
next-env.d.ts
36 changes: 36 additions & 0 deletions templates/chat-sdk/app/api/auth/arcade/callback/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { NextResponse } from "next/server";
import { auth, oauthProvider } from "@/src/arcade";

export async function GET(req: Request) {
const url = new URL(req.url);
const authorizationCode = url.searchParams.get("code");

if (!authorizationCode) {
return NextResponse.json(
{ error: "Missing authorization code" },
{ status: 400 }
);
}

try {
const result = await auth(oauthProvider, {
serverUrl: process.env.ARCADE_GATEWAY_URL || "https://mcp.arcade.dev/sse",
authorizationCode,
});

if (result !== "AUTHORIZED") {
return NextResponse.json(
{ error: "Authorization incomplete", result },
{ status: 400 }
);
}

return NextResponse.json({ success: true, message: "Arcade connected!" });
} catch (error) {
console.error("Arcade OAuth callback error:", error);
return NextResponse.json(
{ error: "Authorization failed" },
{ status: 500 }
);
}
}
10 changes: 10 additions & 0 deletions templates/chat-sdk/app/api/chat-webhook/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { after } from "next/server";
import { bot } from "@/src/bot";

// Chat SDK webhook handler — all platform events are routed through here.
// Point your Slack Event Subscription URL to: https://<your-domain>/api/chat-webhook
export async function POST(request: Request) {
return bot.webhooks.slack(request, {
waitUntil: (p) => after(() => p),
});
}
18 changes: 18 additions & 0 deletions templates/chat-sdk/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import type { Metadata } from "next";

export const metadata: Metadata = {
title: "Arcade Chat Bot",
description: "Multi-platform chat bot powered by Arcade",
};

export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en">
<body>{children}</body>
</html>
);
}
22 changes: 22 additions & 0 deletions templates/chat-sdk/app/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
export default function Home() {
return (
<main style={{ maxWidth: 600, margin: "80px auto", fontFamily: "system-ui" }}>
<h1>Arcade Chat Bot</h1>
<p>
This bot is running and listening for events from your chat platform.
</p>
<h2>Setup</h2>
<ol>
<li>
Point your Slack Event Subscription URL to:{" "}
<code>{process.env.APP_URL || "http://localhost:3000"}/api/chat-webhook</code>
</li>
<li>
@mention the bot in any Slack channel to start a conversation.
</li>
</ol>
<h2>Status</h2>
<p>Webhook endpoint: <code>/api/chat-webhook</code> — Active</p>
</main>
);
}
16 changes: 16 additions & 0 deletions templates/chat-sdk/eslint.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { dirname } from "path";
import { fileURLToPath } from "url";
import { FlatCompat } from "@eslint/eslintrc";

const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);

const compat = new FlatCompat({
baseDirectory: __dirname,
});

const eslintConfig = [
...compat.extends("next/core-web-vitals", "next/typescript"),
];

export default eslintConfig;
7 changes: 7 additions & 0 deletions templates/chat-sdk/next.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import type { NextConfig } from "next";

const nextConfig: NextConfig = {
serverExternalPackages: ["@modelcontextprotocol/sdk"],
};

export default nextConfig;
Loading