> ## Documentation Index
> Fetch the complete documentation index at: https://docs.civic.com/llms.txt
> Use this file to discover all available pages before exploring further.

# OpenAI SDK

> Use OpenAI with Civic tools via manual function calling

## Overview

This recipe shows how to wire Civic MCP tools to the OpenAI Node SDK using manual function calling.

<Note>
  **Want a simpler approach?** The [OpenAI Agents SDK recipe](/civic/recipes/openai-agents) uses `hostedMcpTool()` — no manual tool looping required. Use this page if you need full control over the tool loop.
</Note>

## Prerequisites

* Civic account at [app.civic.com](https://app.civic.com) with at least one MCP server connected
* OpenAI API key from [platform.openai.com](https://platform.openai.com)

## Installation

```bash theme={null}
npm install openai @modelcontextprotocol/sdk
```

## Authentication

<Tabs>
  <Tab title="Backend / Script (Civic Token)">
    ### Generate a Civic Token

    1. Log in to [app.civic.com](https://app.civic.com)
    2. Click your account name in the bottom left
    3. Go to **[Install → MCP URL](https://app.civic.com/web/install/mcp-url)**
    4. Click **Generate Token** and copy it immediately — it won't be shown again

    <Warning>
      Never commit your token to source control. Store it in environment variables or a secrets manager. Tokens expire after 30 days.
    </Warning>

    ### Set Environment Variables

    ```bash theme={null}
    CIVIC_TOKEN=your-civic-token-here
    CIVIC_URL=https://app.civic.com/hub/mcp
    ```

    For production agents, lock to a specific toolkit by appending a `profile` parameter:

    ```bash theme={null}
    CIVIC_URL=https://app.civic.com/hub/mcp?profile=your-toolkit-alias
    ```

    ### Use the Token

    Pass the token as a Bearer token in the `Authorization` header:

    ```python theme={null}
    headers = {"Authorization": f"Bearer {os.environ['CIVIC_TOKEN']}"}
    ```

    ```typescript theme={null}
    headers: { Authorization: `Bearer ${process.env.CIVIC_TOKEN}` }
    ```

    <Card title="Full credentials guide" icon="key" href="/civic/quickstart/credentials">
      Token generation, URL parameters, OAuth vs token comparison
    </Card>

    ```bash theme={null}
    # .env
    OPENAI_API_KEY=your_openai_api_key
    CIVIC_TOKEN=your-civic-token-here
    CIVIC_URL=https://app.civic.com/hub/mcp
    ```
  </Tab>

  <Tab title="Next.js App (Civic Auth)">
    For web apps where each user has their own Civic session.

    Install the additional auth package:

    ```bash theme={null}
    npm install @civic/auth
    ```

    **Why Civic Auth?** Civic needs to identify which user is accessing tools and authorize their permissions. Civic Auth provides the secure access token. (Support for additional identity providers coming soon.)

    <Tabs>
      <Tab title="1. next.config.ts">
        ```ts theme={null}
        import { createCivicAuthPlugin } from "@civic/auth/nextjs"
        import type { NextConfig } from "next";

        const nextConfig: NextConfig = {};
        const withCivicAuth = createCivicAuthPlugin({ clientId: "YOUR_CLIENT_ID" });
        export default withCivicAuth(nextConfig)
        ```
      </Tab>

      <Tab title="2. API Route">
        **File:** `src/app/api/auth/[...civicauth]/route.ts`

        ```ts theme={null}
        import { handler } from "@civic/auth/nextjs"
        export const GET = handler()
        export const POST = handler()
        ```
      </Tab>

      <Tab title="3. Middleware">
        **File:** `src/middleware.ts`

        ```ts theme={null}
        import { authMiddleware } from "@civic/auth/nextjs/middleware"
        export default authMiddleware();
        export const config = { matcher: ['/((?!_next|favicon.ico|.*\\.png).*)',] };
        ```
      </Tab>

      <Tab title="4. Get Token">
        ```ts theme={null}
        import { getTokens } from "@civic/auth/nextjs";
        const { accessToken } = await getTokens();
        // Use in headers:
        headers: { Authorization: `Bearer ${accessToken}` }
        ```
      </Tab>
    </Tabs>

    <CardGroup cols={2}>
      <Card title="Full Integration Guide" icon="book" href="/integration/nextjs">
        Complete Next.js setup with frontend components, configuration options, and deployment details
      </Card>

      <Card title="AI Prompt for Next.js" icon="robot" href="/ai-prompts/nextjs">
        Use Claude, ChatGPT, or other AI assistants to automatically set up Civic Auth
      </Card>
    </CardGroup>

    <Note>
      Get your Client ID at [auth.civic.com](https://auth.civic.com)
    </Note>

    ```bash theme={null}
    # .env.local
    OPENAI_API_KEY=your_openai_api_key
    CIVIC_AUTH_CLIENT_ID=your_client_id  # from auth.civic.com
    ```
  </Tab>
</Tabs>

## Create an MCP Client

```ts theme={null}
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
import { Client } from '@modelcontextprotocol/sdk/client/index.js';

async function createMCP(token: string) {
  const transport = new StreamableHTTPClientTransport(
    new URL(process.env.CIVIC_URL!),
    {
      requestInit: {
        headers: {
          Authorization: `Bearer ${token}`,
          'Content-Type': 'application/json',
        },
      },
    }
  );
  const client = new Client(
    { name: 'my-app', version: '1.0.0' },
    { capabilities: {} }
  );
  await client.connect(transport);
  return client;
}
```

## Call with Tool Functions

<Note>
  This example handles a single round of tool calling. Real agent loops need to iterate until the model stops requesting tools — see the multi-turn loop below.
</Note>

```ts theme={null}
import OpenAI from 'openai';

export async function chatWithTools(messages: any[], civicToken: string) {
  const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY! });
  const mcp = await createMCP(civicToken);
  const { tools } = await mcp.listTools();

  const toolDefs = tools.map((t) => ({
    type: 'function' as const,
    function: {
      name: t.name,
      description: t.description,
      parameters: t.inputSchema,
    },
  }));

  // Multi-turn tool loop — runs until the model stops requesting tools
  let response = await openai.chat.completions.create({
    model: 'gpt-4o-mini',
    messages,
    tools: toolDefs,
    tool_choice: 'auto',
  });

  while (response.choices[0]?.finish_reason === 'tool_calls') {
    const toolCalls = response.choices[0].message.tool_calls ?? [];
    const toolResults = await Promise.all(
      toolCalls.map(async (call) => {
        const args = JSON.parse(call.function.arguments || '{}');
        const result = await mcp.callTool({ name: call.function.name, arguments: args });
        return {
          role: 'tool' as const,
          tool_call_id: call.id,
          content: JSON.stringify(result.content),
        };
      })
    );

    messages = [
      ...messages,
      response.choices[0].message,
      ...toolResults,
    ];

    response = await openai.chat.completions.create({
      model: 'gpt-4o-mini',
      messages,
      tools: toolDefs,
    });
  }

  await mcp.close();
  return response;
}
```

## Usage

```ts theme={null}
// Backend / Script
const result = await chatWithTools(
  [{ role: 'user', content: 'List my GitHub repositories' }],
  process.env.CIVIC_TOKEN!
);
console.log(result.choices[0].message.content);
```

<CardGroup cols={2}>
  <Card title="OpenAI Agents SDK" icon="robot" href="/civic/recipes/openai-agents">
    Simpler approach — hostedMcpTool() handles the loop for you
  </Card>

  <Card title="Get Help" icon="slack" href="https://join.slack.com/t/civic-developers/shared_invite/zt-37tv9fyo7-aDT43mUjOFQwdQFmfZLTRw">
    Developer Slack
  </Card>
</CardGroup>
