Skip to main content

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.

Overview

This recipe shows how to wire Civic MCP tools to the OpenAI Node SDK using manual function calling.
Want a simpler approach? The OpenAI Agents SDK recipe uses hostedMcpTool() — no manual tool looping required. Use this page if you need full control over the tool loop.

Prerequisites

Installation

npm install openai @modelcontextprotocol/sdk

Authentication

Generate a Civic Token

  1. Log in to app.civic.com
  2. Click your account name in the bottom left
  3. Go to Install → MCP URL
  4. Click Generate Token and copy it immediately — it won’t be shown again
Never commit your token to source control. Store it in environment variables or a secrets manager. Tokens expire after 30 days.

Set Environment Variables

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:
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:
headers = {"Authorization": f"Bearer {os.environ['CIVIC_TOKEN']}"}
headers: { Authorization: `Bearer ${process.env.CIVIC_TOKEN}` }

Full credentials guide

Token generation, URL parameters, OAuth vs token comparison
# .env
OPENAI_API_KEY=your_openai_api_key
CIVIC_TOKEN=your-civic-token-here
CIVIC_URL=https://app.civic.com/hub/mcp

Create an MCP Client

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

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.
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

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

OpenAI Agents SDK

Simpler approach — hostedMcpTool() handles the loop for you

Get Help

Developer Slack