# Full Stack Chat App (/cookbooks/vercel-chat)

In this example, you will learn how to build a chatbot that:

* Lets users connect their various apps to the chatbot using the Devcaster SDK.
* Uses the Vercel provider in the Devcaster SDK to handle and execute tool calls from the LLM.

This page gives a high-level overview of the Devcaster SDK and how it is used in the
GitHub repository: [devcasterhq/chat](https://github.com/devcasterhq/chat). You can
find the demo live [here](https://chat.devcaster.dev/).

Prerequisites [#prerequisites]

Ensure you've followed the README.md in the [devcasterhq/chat](https://github.com/devcasterhq/chat)
repository to set up the project locally.

Creating auth configs [#creating-auth-configs]

For all the apps you want to connect to the chatbot, you need to create their
respective auth configs. Learn how to create auth configs [here](/docs/tools-direct/authenticating-tools#creating-an-auth-config).
Once done, your auth configs will be available in the [Devcaster dashboard](https://platform.devcaster.dev/integrations).

<img src="/assets/images/examples/auth-configs.png" alt="Auth configs" />

Save auth config IDs to environment variables [#save-auth-config-ids-to-environment-variables]

For this project, the auth config IDs should be saved to the environment variables
with the `NEXT_PUBLIC_` prefix.

```bash .env.local
NEXT_PUBLIC_GMAIL_AUTH_CONFIG_ID=ac_1234567890
NEXT_PUBLIC_GITHUB_AUTH_CONFIG_ID=ac_1234567890
```
Create a Devcaster client instance [#create-a-devcaster-client-instance]

We create a Devcaster client instance for server-side operations like API routes,
server components, etc.

```typescript lib/service/devcaster.ts
import { Devcaster } from '@devcaster/core';
import { VercelProvider } from '@devcaster/vercel';

const devcaster = new Devcaster({
  apiKey: process.env.DEVCASTER_API_KEY,
  provider: new VercelProvider(),
});

export default devcaster;
```
Creating an API for fetching toolkits [#creating-an-api-for-fetching-toolkits]

The Devcaster SDK is meant to be used only in server-side code. For client-side
functionality, we create API endpoints in the `/app/api/` directory. In order to
list the toolkits and their connection status, we create a Next.js API route to
fetch the toolkits using Devcaster SDK.

<img src="/assets/images/examples/toolkits.png" alt="Toolkits list with connection status" />

1. Listing connected accounts [#1-listing-connected-accounts]

First, we fetch all connected accounts for a user and create a mapping of toolkit
slugs to their connection IDs:

```typescript app/api/devcaster/toolkits.ts
// @noErrors
export async function GET() {
  // ... auth logic ...

  // List connected accounts to get connection IDs for each toolkit
  const connectedAccounts = await devcaster.connectedAccounts.list({
    userIds: [session.user.id],
  });

  const connectedToolkitMap = new Map();
  connectedAccounts.items.forEach(account => {
    connectedToolkitMap.set(account.toolkit.slug.toUpperCase(), account.id);
  });

  // ... continue with toolkit fetching ...

```
2. Fetching toolkit data and building response [#2-fetching-toolkit-data-and-building-response]

Next, we fetch toolkit information for each supported toolkit and combine it with the connection status:

```typescript app/api/devcaster/toolkits.ts
// @noErrors
export async function GET() {
  // ... auth logic ...
  // ... connected accounts mapping ...

  const SUPPORTED_TOOLKITS = ['GMAIL', 'GOOGLECALENDAR', 'GITHUB', 'NOTION'];

  // Fetch toolkit data from slugs
  const toolkitPromises = SUPPORTED_TOOLKITS.map(async slug => {
    const toolkit = await devcaster.toolkits.get(slug);
    const connectionId = connectedToolkitMap.get(slug.toUpperCase());

    return {
      name: toolkit.name,
      slug: toolkit.slug,
      description: toolkit.meta?.description,
      logo: toolkit.meta?.logo,
      categories: toolkit.meta?.categories,
      isConnected: !!connectionId,
      connectionId: connectionId || undefined,
    };
  });

  const toolkits = await Promise.all(toolkitPromises);
  return NextResponse.json({ toolkits });

```
Managing connections [#managing-connections]

Users need to connect and disconnect their accounts from the chatbot to enable tool usage. When users click "Connect" on a toolkit, we initiate an OAuth flow, and when they click "Disconnect", we remove their connection.

1. Initiating a connection [#1-initiating-a-connection]

When a user wants to connect their account, we create a connection request that redirects them to the OAuth provider:

<img src="/assets/images/examples/create-connection.png" alt="Create connection dialog" />

```typescript app/api/connections/initiate/route.ts
// @noErrors
export async function POST(request: Request) {
  // ... auth and validation ...

  const { authConfigId } = requestBody;

  // Initiate connection with Devcaster
  const connectionRequest = await devcaster.connectedAccounts.initiate(
    session.user.id,
    authConfigId
  );

  return NextResponse.json({
    redirectUrl: connectionRequest.redirectUrl,
    connectionId: connectionRequest.id,
  });

```
2. Checking connection status [#2-checking-connection-status]

After initiating a connection, we need to wait for the OAuth flow to complete. We check the connection status to know when it's ready to use:

<img src="/assets/images/examples/connection-status.png" alt="Connection status indicator" />

```typescript app/api/connections/status/route.ts
// @noErrors
export async function GET(request: Request) {
  // ... auth and validation ...

  const connectionId = searchParams.get('connectionId');

  // Wait for connection to complete
  const connection = await devcaster.connectedAccounts.waitForConnection(connectionId);

  return NextResponse.json({
    id: connection.id,
    status: connection.status,
    authConfig: connection.authConfig,
    data: connection.data,
  });

```
3. Deleting a connection [#3-deleting-a-connection]

When a user wants to disconnect their account, we remove the connection using the connection ID:

```typescript app/api/connections/delete/route.ts
// @noErrors
export async function DELETE(request: Request) {
  // ... auth and validation ...

  const connectionId = searchParams.get('connectionId');

  // Delete the connection
  await devcaster.connectedAccounts.delete(connectionId);

  return NextResponse.json({
    success: true,
    message: 'Connection deleted successfully',
  });

```
Working with tools [#working-with-tools]

Once users have connected their accounts, we need to track which toolkits are enabled and fetch the corresponding tools for the LLM.

1. Tracking enabled toolkits [#1-tracking-enabled-toolkits]

We keep track of which toolkits the user has enabled in the chat interface:

```typescript components/chat.tsx
// @noErrors
const { ... } = useChat({
    // ... other config ...
    experimental_prepareRequestBody: (body) => {
      // Get current toolbar state
      const currentToolbarState = toolbarStateRef.current;
      const enabledToolkits = Array.from(
        currentToolbarState.enabledToolkitsWithStatus.entries(),
      ).map(([slug, isConnected]) => ({ slug, isConnected }));

      return {
        // ... other fields ...
        enabledToolkits,
      };
    },
    // ... other handlers ...
  });
```
2. Fetching tools for enabled toolkits [#2-fetching-tools-for-enabled-toolkits]

We fetch Devcaster tools based on the enabled toolkit slugs:

```typescript lib/ai/tools/devcaster.ts
// @noErrors
export async function getDevcasterTools(userId: string, toolkitSlugs: string[]) {
  // ... validation ...

  const tools = await devcaster.tools.get(userId, {
    toolkits: toolkitSlugs,
  });
  return tools || {};

```
```typescript app/api/chat.ts
// @noErrors
export async function POST(request: Request) {
  // ... auth and parsing ...

  const toolkitSlugs = enabledToolkits?.map(t => t.slug) || [];

  const devcasterTools = await getDevcasterTools(session.user.id, toolkitSlugs);

  const result = streamText({
    // ... model config ...
    tools: {
      ...devcasterTools,
    },
  });

```
Bonus: Creating custom component to show tool calls [#bonus-creating-custom-component-to-show-tool-calls]

By default, tool calls appear as raw JSON in the chat interface. To create a better user experience, we can build custom components that display tool calls with proper formatting and loading states.

<img src="/assets/images/examples/custom-tool-component.png" alt="Custom tool call component" />

You can find the `ToolCall` component at `components/tool-call.tsx`. Here's how to integrate it into your message rendering:

```typescript components/messages.tsx
// @noErrors
if (type === 'tool-invocation') {
  const { toolInvocation } = part;
  const { toolName, toolCallId, state, args, result } = toolInvocation;

  if (state === 'call') {
    return (
      
    );

  if (state === 'result') {
    return (
      
    );

```

---

📚 **More documentation:** [View all docs](https://docs.devcaster.dev/llms.txt) | [Cookbooks](https://docs.devcaster.dev/llms.mdx/cookbooks) | [API Reference](https://docs.devcaster.dev/llms.mdx/reference)

---

# Devcaster SDK — Instructions for AI Code Generators

**Purpose:** Enforce only the **current** and **correct** patterns for integrating [Devcaster](https://devcaster.dev/) tools into AI agents.
**Scope:** All AI-generated code using Devcaster must follow these guardrails.

---

## 1. Correct Devcaster Integration

Devcaster supports two integration modes: **Native Tools** (with a provider package) and **MCP** (no provider package needed).

### Native Tools

```python
# ✅ CORRECT — Python (defaults to OpenAI)
from devcaster import Devcaster

devcaster = Devcaster()
session = devcaster.create(user_id="user_123")
tools = session.tools()
# Pass tools to your agent/LLM framework
```

```typescript
// ✅ CORRECT — TypeScript (defaults to OpenAI)
import { Devcaster } from "@devcaster/core";

const devcaster = new Devcaster();
const session = await devcaster.create("user_123");
const tools = await session.tools();
// Pass tools to your agent/LLM framework
```

For other providers, pass the provider explicitly. Provider packages follow the naming convention: `devcaster_<provider>` for Python, `@devcaster/<provider>` for TypeScript.

### MCP

Use `session.mcp.url` and `session.mcp.headers` with any MCP-compatible client (Claude Desktop, Cursor, OpenAI Agents, etc.). No provider package needed.

---

## 2. CRITICAL INSTRUCTIONS FOR AI MODELS

### 2.1 — ALWAYS DO THE FOLLOWING

1. **Use `devcaster.create(user_id)`** to create a session — this is the entry point for all Devcaster integrations.
2. **Use `session.tools()`** for native tool integration or **`session.mcp.url`** for MCP integration.
3. **Import** from the correct provider package (`devcaster_<provider>` for Python, `@devcaster/<provider>` for TypeScript).
4. **Pass `user_id` to `devcaster.create()`**, not to individual tool calls.

### 2.2 — NEVER DO THE FOLLOWING

1. **Do not** instruct users to manually create auth configs or connected accounts before running.
2. **Do not** import from `devcaster_openai` / `@devcaster/openai` when using OpenAI Agents SDK — use `devcaster_openai_agents` / `@devcaster/openai-agents` instead.

### 2.3 — DISCOURAGED (do not use unless user explicitly asks)

Do **not** generate `devcaster.tools.get()`, `devcaster.tools.execute()`, or `devcaster.provider.handle_tool_calls()` unless the user explicitly requests direct tool execution. These are a supported but **not recommended** low-level interface.

