Using Connectors in Chat Completions Cookbook

ConnectorsMCP

Use MCP connectors, built-in tools, and agents with the Chat Completions API (/v1/chat/completions) and Agent Completions API (/v1/agents/completions).

SDK support: The mistralai_private SDK supports connector-style tools in chat.complete(). Note that responses use messages (array) instead of message (object) when tools are invoked.


Table of Contents


Prerequisites

Install

# Python - using the private SDK
pip install mistralai-private
# or with uv
uv add mistralai-private
# TypeScript / Node.js
npm install @mistralai/mistralai

Required environment variables

MISTRAL_API_KEY=your-mistral-api-key

Get your API key from the Mistral AI dashboard.

What you need before starting

Most recipes assume:


Conversations vs Completions — When to Use Which

Mistral offers two APIs for interacting with models:

FeatureConversations APIChat Completions API
Endpoint/v1/conversations/v1/chat/completions
SDK supportclient.beta.conversationsclient.chat.complete()
Response formatoutputs[] with type: "message.output"choices[] with messages (array) when tools are used
Multi-turn stateStateless (send full history each call)Stateless (send full history each call)
Tool executionServer-side (automatic)Server-side with multi_completion format
Best forNew integrations, agent workflowsOpenAI-compatible apps, existing chat implementations

Use Chat Completions when:

  • You're migrating from OpenAI and want a familiar response format
  • Your existing code uses the choices[].message structure
  • You need compatibility with OpenAI-style SDKs

Use Conversations when:

  • You're building new integrations from scratch
  • You want the recommended, newest API surface
  • You're using the SDK's beta features

Reading Completion Responses

Chat completions with tools return responses in a multi_completion format where each choice contains a messages array instead of a single message. The helper below handles both formats.

Python (SDK):

def display_response(response) -> None:
    """Display text content from SDK chat completion response.

    When using connector tools, responses use `messages` array instead of `message`.
    """
    for choice in response.choices:
        # Handle multi_completion format (messages array) - used with connector tools
        if hasattr(choice, 'messages') and choice.messages:
            for message in choice.messages:
                content = message.content
                if content:
                    if isinstance(content, str):
                        print(content[:500] if len(content) > 500 else content)
                    elif isinstance(content, list):
                        for chunk in content:
                            if hasattr(chunk, 'type'):
                                if chunk.type == "text":
                                    print(getattr(chunk, 'text', ''))
                                elif chunk.type == "image_url":
                                    print(f"[Image: {getattr(chunk, 'image_url', '')[:80]}...]")
                tool_calls = getattr(message, 'tool_calls', None)
                if tool_calls:
                    print(f"Tool calls: {len(tool_calls)}")
                    for tc in tool_calls:
                        func = tc.function
                        print(f"  - {func.name}: {func.arguments}")
        # Handle standard completion format (single message)
        elif hasattr(choice, 'message') and choice.message:
            message = choice.message
            content = message.content
            if content:
                print(content[:500] if len(content) > 500 else content)
            tool_calls = getattr(message, 'tool_calls', None)
            if tool_calls:
                print(f"Tool calls: {len(tool_calls)}")
                for tc in tool_calls:
                    func = tc.function
                    print(f"  - {func.name}: {func.arguments}")

TypeScript:

function displayResponse(data: any): void {
  for (const choice of data.choices ?? []) {
    // Handle multi_completion format (messages array)
    const messages = choice.messages ?? [];
    if (messages.length > 0) {
      for (const message of messages) {
        const content = message.content;
        if (content) {
          if (typeof content === "string") {
            console.log(content.length > 500 ? content.slice(0, 500) : content);
          } else if (Array.isArray(content)) {
            for (const chunk of content) {
              if (chunk.type === "text") {
                console.log(chunk.text ?? "");
              } else if (chunk.type === "image_url") {
                console.log(`[Image: ${(chunk.image_url ?? "").slice(0, 80)}...]`);
              }
            }
          }
        }
        const toolCalls = message.tool_calls ?? [];
        if (toolCalls.length > 0) {
          console.log(`Tool calls: ${toolCalls.length}`);
          for (const tc of toolCalls) {
            const func = tc.function ?? {};
            console.log(`  - ${func.name}: ${func.arguments}`);
          }
        }
      }
    // Handle standard completion format (single message)
    } else {
      const message = choice.message ?? {};
      const content = message.content;
      if (content) {
        console.log(typeof content === "string" && content.length > 500 ? content.slice(0, 500) : content);
      }
      const toolCalls = message.tool_calls ?? [];
      if (toolCalls.length > 0) {
        console.log(`Tool calls: ${toolCalls.length}`);
        for (const tc of toolCalls) {
          const func = tc.function ?? {};
          console.log(`  - ${func.name}: ${func.arguments}`);
        }
      }
    }
  }
}

All recipes below reference this helper. Copy it into your project, or inline the logic.


Recipes


1. Hello World — Basic Chat Completion

Goal: Send your first chat completion request — no tools, no connectors.

When to use:

  • Verifying your SDK setup works end-to-end
  • Getting familiar with the response structure before adding connectors

Python:

import asyncio
from mistralai_private import MistralPrivate

API_KEY = "your-api-key"


async def main() -> None:
    client = MistralPrivate(api_key=API_KEY)

    response = await client.chat.complete_async(
        model="mistral-small-latest",
        messages=[
            {"role": "user", "content": "What is the capital of France?"}
        ],
    )

    # Standard completion uses choice.message
    print(response.choices[0].message.content)


asyncio.run(main())

TypeScript:

import { Mistral } from "@mistralai/mistralai";

const client = new Mistral({ apiKey: "your-api-key" });

async function main(): Promise<void> {
  const response = await client.chat.complete({
    model: "mistral-small-latest",
    messages: [
      { role: "user", content: "What is the capital of France?" },
    ],
  });

  console.log(response.choices[0].message.content);
}

main();

curl:

curl -X POST "https://api.mistral.ai/v1/chat/completions" \
  -H "Authorization: Bearer ${MISTRAL_API_KEY}" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "mistral-small-latest",
    "messages": [{"role": "user", "content": "What is the capital of France?"}]
  }'

Example of output:

The capital of France is Paris.

How it works:

  • /v1/chat/completions is the standard OpenAI-compatible chat endpoint
  • Response contains choices[] with each choice having a message object
  • No tools or connectors are required for a basic completion

Common errors & fixes:

ErrorCauseFix
401 UnauthorizedBad API keyCheck MISTRAL_API_KEY
422 Unprocessable EntityInvalid model nameUse a valid model like mistral-small-latest

2. Completion with Image Generation

Goal: Use the built-in image_generation tool in a chat completion.

When to use:

  • Generating images based on user prompts
  • Quick integration without custom connectors

Python:

import asyncio
from mistralai_private import MistralPrivate

API_KEY = "your-api-key"


async def main() -> None:
    client = MistralPrivate(api_key=API_KEY)

    response = await client.chat.complete_async(
        model="mistral-small-latest",
        messages=[
            {
                "role": "user",
                "content": "Generate an image of a sunset over the ocean.",
            }
        ],
        tools=[
            {"type": "image_generation"},
        ],
    )

    # With tools, use choice.messages (array) instead of choice.message
    display_response(response)


asyncio.run(main())

TypeScript:

import { Mistral } from "@mistralai/mistralai";

const client = new Mistral({ apiKey: "your-api-key" });

async function main(): Promise<void> {
  const response = await client.chat.complete({
    model: "mistral-small-latest",
    messages: [
      {
        role: "user",
        content: "Generate an image of a sunset over the ocean.",
      },
    ],
    tools: [
      { type: "image_generation" },
    ],
  });

  displayResponse(response);
}

main();

curl:

curl -X POST "https://api.mistral.ai/v1/chat/completions" \
  -H "Authorization: Bearer ${MISTRAL_API_KEY}" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "mistral-small-latest",
    "messages": [{"role": "user", "content": "Generate an image of a sunset over the ocean."}],
    "tools": [{"type": "image_generation"}]
  }'

Example of output:

[Image: https://files.mistral.ai/generated/abc123...]
Here's a beautiful sunset over the ocean as requested.

How it works:

  • image_generation is a built-in tool type — no connector creation required
  • The model decides to invoke the tool based on the user's request
  • Generated images are returned as URLs in the response content
  • When tools are invoked, responses use choice.messages (array) instead of choice.message

Common errors & fixes:

ErrorCauseFix
422 Unprocessable EntityInvalid tool typeEnsure the type is exactly "image_generation"

3. Completion with a Custom Connector

Goal: Use a custom MCP connector in a chat completion so the model can call external tools.

When to use:

  • You've registered a connector (e.g., DeepWiki) and want the model to use its tools
  • Connecting domain-specific capabilities to the model in a chat completion context

Prereqs:

Python:

import asyncio
from mistralai_private import MistralPrivate

API_KEY = "your-api-key"


async def main() -> None:
    client = MistralPrivate(api_key=API_KEY)

    response = await client.chat.complete_async(
        model="mistral-small-latest",
        messages=[
            {
                "role": "user",
                "content": "Using deepwiki, tell me about the structure of the sqlite/sqlite repository.",
            }
        ],
        tools=[
            {
                "type": "connector",
                "connector_id": "my_deepwiki",  # name or UUID
            },
        ],
    )

    # With connector tools, use choice.messages (array)
    display_response(response)


asyncio.run(main())

TypeScript:

import { Mistral } from "@mistralai/mistralai";

const client = new Mistral({ apiKey: "your-api-key" });

async function main(): Promise<void> {
  const response = await client.chat.complete({
    model: "mistral-small-latest",
    messages: [
      {
        role: "user",
        content:
          "Using deepwiki, tell me about the structure of the sqlite/sqlite repository.",
      },
    ],
    tools: [
      {
        type: "connector",
        connector_id: "my_deepwiki", // name or UUID
      },
    ],
  });

  displayResponse(response);
}

main();

curl:

curl -X POST "https://api.mistral.ai/v1/chat/completions" \
  -H "Authorization: Bearer ${MISTRAL_API_KEY}" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "mistral-small-latest",
    "messages": [{"role": "user", "content": "Using deepwiki, tell me about the structure of the sqlite/sqlite repository."}],
    "tools": [{"type": "connector", "connector_id": "my_deepwiki"}]
  }'

Example of output:

The sqlite/sqlite repository is organized into several key directories:
- src/ — core SQLite source code
- ext/ — extensions
- test/ — test suite
...

How it works:

  • The connector_id field accepts the connector's name or UUID
  • The model discovers the tools exposed by the MCP server and decides which to call
  • Tool calls and results are handled server-side — you only see the final response
  • Responses use choice.messages (array) when tools are invoked

Common errors & fixes:

ErrorCauseFix
404 Not FoundConnector name/ID doesn't existVerify with connector list/get API
422 Unprocessable EntityConnector is inactive or MCP server unreachableCheck the MCP server URL

4. Combining Multiple Tools

Goal: Give the model access to built-in tools and custom connectors simultaneously in a chat completion.

When to use:

  • You want the model to choose the best tool for the job from several options
  • Building a multi-capability assistant

Prereqs:

  • An existing connector

Python:

import asyncio
from mistralai_private import MistralPrivate

API_KEY = "your-api-key"


async def main() -> None:
    client = MistralPrivate(api_key=API_KEY)

    response = await client.chat.complete_async(
        model="mistral-small-latest",
        messages=[
            {
                "role": "user",
                "content": "What tools do you have access to? List them briefly.",
            }
        ],
        tools=[
            {"type": "image_generation"},
            {
                "type": "connector",
                "connector_id": "my_deepwiki",
            },
        ],
    )

    display_response(response)


asyncio.run(main())

TypeScript:

import { Mistral } from "@mistralai/mistralai";

const client = new Mistral({ apiKey: "your-api-key" });

async function main(): Promise<void> {
  const response = await client.chat.complete({
    model: "mistral-small-latest",
    messages: [
      {
        role: "user",
        content: "What tools do you have access to? List them briefly.",
      },
    ],
    tools: [
      { type: "image_generation" },
      {
        type: "connector",
        connector_id: "my_deepwiki",
      },
    ],
  });

  displayResponse(response);
}

main();

curl:

curl -X POST "https://api.mistral.ai/v1/chat/completions" \
  -H "Authorization: Bearer ${MISTRAL_API_KEY}" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "mistral-small-latest",
    "messages": [{"role": "user", "content": "What tools do you have access to? List them briefly."}],
    "tools": [
      {"type": "image_generation"},
      {"type": "connector", "connector_id": "my_deepwiki"}
    ]
  }'

Example of output:

I have access to the following tools:
1. Image Generation — create images from text descriptions
2. read_wiki_structure — explore repository wiki structure
3. read_wiki_contents — read specific wiki pages
4. ask_question — ask questions about a repository

How it works:

  • The tools array accepts any mix of built-in tools (image_generation) and custom connectors
  • Each connector exposes its own set of MCP tools; the model sees all of them
  • The model decides which tool(s) to invoke based on the user's question

Common errors & fixes:

ErrorCauseFix
422 Unprocessable EntityDuplicate connector IDs in the tools arrayEach connector should appear only once

5. Creating an Agent with Connectors

Goal: Create a persistent agent pre-configured with connectors and custom instructions for use with the Agent Completions API.

When to use:

  • You want a reusable agent that always has access to specific tools
  • Building a product feature where users interact with a specialized assistant
  • Simplifying API calls by pre-configuring model, instructions, and tools

Prereqs:

  • An existing connector

Python:

import asyncio
from mistralai_private import MistralPrivate

API_KEY = "your-api-key"


async def main() -> None:
    client = MistralPrivate(api_key=API_KEY)
    agent_id: str | None = None

    try:
        # Create the agent
        agent = await client.beta.agents.create_async(
            name="deepwiki_completion_agent",
            description="Agent with DeepWiki access for code repository exploration",
            model="mistral-small-latest",
            instructions="You are a helpful assistant that can explore code repositories using DeepWiki. Be concise.",
            tools=[
                {
                    "type": "connector",
                    "connector_id": "my_deepwiki",
                },
            ],
        )
        agent_id = str(agent.id)
        print(f"Created agent: {agent.name} ({agent_id})")

    finally:
        # Clean up
        if agent_id:
            await client.beta.agents.delete_async(agent_id=agent_id)
            print(f"Deleted agent: {agent_id}")


asyncio.run(main())

TypeScript:

import { Mistral } from "@mistralai/mistralai";

const client = new Mistral({ apiKey: "your-api-key" });

async function main(): Promise<void> {
  let agentId: string | undefined;

  try {
    // Create the agent
    const agent = await client.beta.agents.create({
      name: "deepwiki_completion_agent",
      description: "Agent with DeepWiki access for code repository exploration",
      model: "mistral-small-latest",
      instructions:
        "You are a helpful assistant that can explore code repositories using DeepWiki. Be concise.",
      tools: [
        {
          type: "connector",
          connectorId: "my_deepwiki",
        },
      ],
    });
    agentId = agent.id;
    console.log(`Created agent: ${agent.name} (${agentId})`);
  } finally {
    // Clean up
    if (agentId) {
      await client.beta.agents.delete({ agentId });
      console.log(`Deleted agent: ${agentId}`);
    }
  }
}

main();

curl:

# Create agent
curl -X POST "https://api.mistral.ai/v1/agents" \
  -H "Authorization: Bearer ${MISTRAL_API_KEY}" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "deepwiki_completion_agent",
    "description": "Agent with DeepWiki access",
    "model": "mistral-small-latest",
    "instructions": "You are a helpful assistant that can explore code repositories using DeepWiki. Be concise.",
    "tools": [{"type": "connector", "connector_id": "my_deepwiki"}]
  }'

# Delete agent when done (use the agent ID from the response above)
curl -X DELETE "https://api.mistral.ai/v1/agents/<agent-id>" \
  -H "Authorization: Bearer ${MISTRAL_API_KEY}"

Example of output:

Created agent: deepwiki_completion_agent (b2c3d4e5-6789-01ab-cdef-234567890abc)
Deleted agent: b2c3d4e5-6789-01ab-cdef-234567890abc

How it works:

  • Agents are persistent configurations: model + instructions + tools
  • Once created, use the agent's ID with /v1/agents/completions to chat
  • The agent's tools array uses the same format as the tools parameter in completions
  • Delete agents when they are no longer needed

Common errors & fixes:

ErrorCauseFix
404 Not FoundThe connector referenced in the agent's tools doesn't existCreate the connector first
409 ConflictAn agent with this name already existsChoose a different name or delete the existing agent

6. Agent Completions

Goal: Chat with a pre-configured agent using the Agent Completions API.

When to use:

  • You have an existing agent with tools configured
  • You want to avoid passing model, instructions, and tools on every request
  • Building conversational flows with a specialized assistant

Prereqs:

Python:

import asyncio
from mistralai_private import MistralPrivate

API_KEY = "your-api-key"


async def main() -> None:
    client = MistralPrivate(api_key=API_KEY)
    agent_id = "your-agent-id"  # From agent creation

    response = await client.agents.complete_async(
        agent_id=agent_id,
        messages=[
            {
                "role": "user",
                "content": "What is the main purpose of the sqlite repository?",
            }
        ],
    )

    display_response(response)


asyncio.run(main())

TypeScript:

import { Mistral } from "@mistralai/mistralai";

const client = new Mistral({ apiKey: "your-api-key" });

async function main(): Promise<void> {
  const agentId = "your-agent-id"; // From agent creation

  const response = await client.agents.complete({
    agentId,
    messages: [
      {
        role: "user",
        content: "What is the main purpose of the sqlite repository?",
      },
    ],
  });

  displayResponse(response);
}

main();

curl:

curl -X POST "https://api.mistral.ai/v1/agents/completions" \
  -H "Authorization: Bearer ${MISTRAL_API_KEY}" \
  -H "Content-Type: application/json" \
  -d '{
    "agent_id": "<agent-id>",
    "messages": [{"role": "user", "content": "What is the main purpose of the sqlite repository?"}]
  }'

Example of output:

SQLite is a self-contained, serverless, zero-configuration SQL database engine. It is the most widely deployed database in the world, embedded in countless applications including web browsers, mobile phones, and operating systems.

How it works:

  • /v1/agents/completions uses the agent's pre-configured model, instructions, and tools
  • You only need to provide agent_id and messages — no need to repeat configuration
  • The response format is the same as /v1/chat/completions
  • For multi-turn conversations, include the full message history in the messages array

Common errors & fixes:

ErrorCauseFix
404 Not FoundInvalid agent IDThe agent may have been deleted
422 Unprocessable EntityMissing agent_id or invalid message formatEnsure agent_id is provided

7. OAuth-Authenticated Connectors (Gmail)

Goal: Use a connector that requires OAuth2 authentication in a chat completion.

When to use:

  • Integrating with services that require user-level OAuth tokens (Gmail, Google Drive, Slack, etc.)
  • Building features where the model accesses user-specific data

Prereqs:

  • A valid OAuth2 access token for the target service

Python:

import asyncio
from mistralai_private import MistralPrivate

API_KEY = "your-api-key"


async def main() -> None:
    client = MistralPrivate(api_key=API_KEY)
    google_oauth_token = "your-google-oauth-token"

    response = await client.chat.complete_async(
        model="mistral-small-latest",
        messages=[
            {
                "role": "user",
                "content": "What's the latest email I received?",
            }
        ],
        tools=[
            {
                "type": "connector",
                "connector_id": "gmail",
                "authorization": {
                    "type": "oauth2-token",
                    "value": google_oauth_token,
                },
            },
        ],
    )

    display_response(response)


asyncio.run(main())

TypeScript:

import { Mistral } from "@mistralai/mistralai";

const client = new Mistral({ apiKey: "your-api-key" });

async function main(): Promise<void> {
  const googleOauthToken = "your-google-oauth-token";

  const response = await client.chat.complete({
    model: "mistral-small-latest",
    messages: [
      {
        role: "user",
        content: "What's the latest email I received?",
      },
    ],
    tools: [
      {
        type: "connector",
        connector_id: "gmail",
        authorization: {
          type: "oauth2-token",
          value: googleOauthToken,
        },
      },
    ],
  });

  displayResponse(response);
}

main();

curl:

curl -X POST "https://api.mistral.ai/v1/chat/completions" \
  -H "Authorization: Bearer ${MISTRAL_API_KEY}" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "mistral-small-latest",
    "messages": [{"role": "user", "content": "What is the latest email I received?"}],
    "tools": [{
      "type": "connector",
      "connector_id": "gmail",
      "authorization": {
        "type": "oauth2-token",
        "value": "<your-google-oauth-token>"
      }
    }]
  }'

Example of output:

Your latest email is from John Doe with the subject "Q1 Report Review" received at 2:30 PM today...

How it works:

  • The authorization field is passed per-tool, not globally — different connectors can use different tokens
  • type: "oauth2-token" tells the backend to forward the token to the MCP server
  • The token is not stored by Mistral — it is used for the duration of the request only
  • Built-in connectors like gmail are pre-registered; you don't need to create them

Common errors & fixes:

ErrorCauseFix
401 UnauthorizedOAuth token is expiredRefresh the token and retry
403 ForbiddenToken doesn't have required scopesRequest the correct scopes (e.g., gmail.readonly)

8. Full Example — Create, Complete, and Clean Up

Goal: End-to-end workflow: create a connector, use it in chat completions and with an agent, then clean up.

When to use:

  • Integration tests
  • Ephemeral connectors for one-off tasks
  • Template for production workflows

Python:

import asyncio
from mistralai_private import MistralPrivate

API_KEY = "your-api-key"
CONNECTOR_HEADERS = {"X-Private-Access": "your-private-access-key"}


async def main() -> None:
    client = MistralPrivate(api_key=API_KEY)
    connector_id: str | None = None
    agent_id: str | None = None

    try:
        # 1. Create a connector
        connector = await client.beta.connectors.create_async(
            name="completions_deepwiki",
            description="DeepWiki connector for completion testing",
            server="https://mcp.deepwiki.com/mcp",
            visibility="private",
            http_headers=CONNECTOR_HEADERS,
        )
        connector_id = str(connector.id)
        print(f"Created connector: {connector.name} ({connector_id})")

        # 2. Use it in a chat completion
        response = await client.chat.complete_async(
            model="mistral-small-latest",
            messages=[
                {
                    "role": "user",
                    "content": "Using deepwiki, summarize the sqlite/sqlite repo in one sentence.",
                }
            ],
            tools=[
                {"type": "connector", "connector_id": "completions_deepwiki"},
            ],
        )
        print("\nChat completion response:")
        display_response(response)

        # 3. Create an agent with the connector
        agent = await client.beta.agents.create_async(
            name="completions_test_agent",
            description="Test agent for completion cookbook",
            model="mistral-small-latest",
            instructions="You are a helpful assistant. Be concise.",
            tools=[
                {"type": "connector", "connector_id": connector_id},
            ],
        )
        agent_id = str(agent.id)
        print(f"\nCreated agent: {agent.name} ({agent_id})")

        # 4. Use agent completions
        response = await client.agents.complete_async(
            agent_id=agent_id,
            messages=[
                {
                    "role": "user",
                    "content": "What programming language is SQLite written in?",
                }
            ],
        )
        print("\nAgent completion response:")
        display_response(response)

        print("\n" + "=" * 60)
        print("  SUCCESS")
        print("=" * 60)

    finally:
        # Clean up
        print("\nCleaning up...")
        if agent_id:
            try:
                await client.beta.agents.delete_async(agent_id=agent_id)
                print(f"Deleted agent: {agent_id}")
            except Exception:
                pass

        if connector_id:
            try:
                await client.beta.connectors.delete_async(
                    connector_id=connector_id,
                    http_headers=CONNECTOR_HEADERS,
                )
                print(f"Deleted connector: {connector_id}")
            except Exception:
                pass


asyncio.run(main())

TypeScript:

import { Mistral } from "@mistralai/mistralai";

const client = new Mistral({ apiKey: "your-api-key" });

async function main(): Promise<void> {
  let connectorId: string | undefined;
  let agentId: string | undefined;

  try {
    // 1. Create a connector
    const connector = await client.beta.connectors.create({
      name: "completions_deepwiki",
      description: "DeepWiki connector for completion testing",
      server: "https://mcp.deepwiki.com/mcp",
      visibility: "private",
    });
    connectorId = connector.id;
    console.log(`Created connector: ${connector.name} (${connectorId})`);

    // 2. Use it in a chat completion
    let response = await client.chat.complete({
      model: "mistral-small-latest",
      messages: [
        {
          role: "user",
          content: "Using deepwiki, summarize the sqlite/sqlite repo in one sentence.",
        },
      ],
      tools: [
        { type: "connector", connector_id: "completions_deepwiki" },
      ],
    });
    console.log("\nChat completion response:");
    displayResponse(response);

    // 3. Create an agent with the connector
    const agent = await client.beta.agents.create({
      name: "completions_test_agent",
      description: "Test agent for completion cookbook",
      model: "mistral-small-latest",
      instructions: "You are a helpful assistant. Be concise.",
      tools: [
        { type: "connector", connectorId },
      ],
    });
    agentId = agent.id;
    console.log(`\nCreated agent: ${agent.name} (${agentId})`);

    // 4. Use agent completions
    response = await client.agents.complete({
      agentId,
      messages: [
        {
          role: "user",
          content: "What programming language is SQLite written in?",
        },
      ],
    });
    console.log("\nAgent completion response:");
    displayResponse(response);

    console.log("\n" + "=".repeat(60));
    console.log("  SUCCESS");
    console.log("=".repeat(60));
  } finally {
    // Clean up
    console.log("\nCleaning up...");
    if (agentId) {
      try {
        await client.beta.agents.delete({ agentId });
        console.log(`Deleted agent: ${agentId}`);
      } catch {
        // ignore
      }
    }
    if (connectorId) {
      try {
        await client.beta.connectors.delete({ connectorId });
        console.log(`Deleted connector: ${connectorId}`);
      } catch {
        // ignore
      }
    }
  }
}

main();

Output:

Created connector: completions_deepwiki (c3d4e5f6-...)

Chat completion response:
SQLite is a self-contained, serverless SQL database engine used worldwide.

Created agent: completions_test_agent (d4e5f6a7-...)

Agent completion response:
SQLite is primarily written in C.

============================================================
  SUCCESS
============================================================

Cleaning up...
Deleted agent: d4e5f6a7-...
Deleted connector: c3d4e5f6-...

How it works:

  • The try/finally pattern ensures resources are always cleaned up, even if requests fail
  • This recipe demonstrates the full workflow: connector creation → chat completion → agent creation → agent completion
  • Both /v1/chat/completions and /v1/agents/completions support the same tool formats

Python / TypeScript Naming Conventions

ConceptPython SDKTypeScript SDK
Connector IDconnector_idconnectorId
Agent IDagent_idagentId
Tool typetypetype
OAuth authorizationauthorization.type, authorization.valueauthorization.type, authorization.value

Troubleshooting

"Connector not found" error

  • Verify the connector exists by listing connectors or getting by name/ID
  • Check that the connector name/ID is spelled correctly
  • Ensure the connector was created in the same workspace

"Tool calls not appearing in response"

  • The model decides whether to call tools based on the user's message
  • Try being more explicit in your prompt (e.g., "Use deepwiki to...")
  • Ensure the tool type is valid (connector, image_generation, etc.)

"Timeout errors"

  • MCP servers may take time to respond — increase the timeout
  • Check if the MCP server URL is reachable
  • Try with a simpler query first

"OAuth token expired"

  • OAuth tokens have limited lifetimes
  • Implement token refresh logic in your application
  • Check the token's expiration before making requests

"AttributeError: 'Unset' object has no attribute 'content'"

  • When using connector tools, responses use choice.messages (array) instead of choice.message
  • Use the display_response helper function or check for both formats
  • Access content via response.choices[0].messages[0].content

Error Codes Reference

HTTP StatusErrorCommon Causes
400Bad RequestInvalid JSON, missing required fields
401UnauthorizedInvalid or missing API key, expired OAuth token
403ForbiddenInsufficient permissions, invalid OAuth scopes
404Not FoundConnector/agent doesn't exist, wrong ID
409ConflictResource with same name already exists
422Unprocessable EntityInvalid model name, invalid tool format, unreachable MCP server
429Too Many RequestsRate limit exceeded
500Internal Server ErrorServer-side issue, retry with backoff
502Bad GatewayMCP server unreachable or returned an error
504Gateway TimeoutMCP server took too long to respond