How to integrate Close MCP with Vercel AI SDK v6

Connect Vercel AI SDK v6 to Close MCP. Create a new sales lead for acme corp, log an outbound call with follow-up notes, and more using natural language, with authentication handled for you.

Close logoClose
Api Key

Close is a CRM platform built for sales teams, combining calling, email automation, and predictive dialers. It streamlines sales workflows and boosts productivity with all-in-one communication tools.

70 Tools45 Triggers

Introduction

This guide walks you through connecting Close to Vercel AI SDK v6 using the Composio tool router. By the end, you'll have a working Close agent that can create leads and tasks, log calls and emails, send SMS messages, track opportunities, and review activities through natural language commands.

This guide will help you understand how to give your Vercel AI SDK agent real control over a Close account through Composio's Close MCP server.

Before we dive in, let's take a quick look at the key ideas and tools involved.

Also integrate Close with

TL;DR

Here's what you'll learn:
  • How to set up and configure a Vercel AI SDK agent with Close integration
  • Using Composio's Tool Router to dynamically load and access Close tools
  • Creating an MCP client connection using HTTP transport
  • Building an interactive CLI chat interface with conversation history management
  • Handling tool calls and results within the Vercel AI SDK framework

What is Vercel AI SDK?

The Vercel AI SDK is a TypeScript library for building AI-powered applications. It provides tools for creating agents that can use external services and maintain conversation state.

Key features include:

  • streamText: Core function for streaming responses with real-time tool support
  • MCP Client: Built-in support for Model Context Protocol via @ai-sdk/mcp
  • Step Counting: Control multi-step tool execution with stopWhen: stepCountIs()
  • OpenAI Provider: Native integration with OpenAI models

What is the Close MCP server, and what's possible with it?

The Close MCP server is an implementation of the Model Context Protocol that connects your AI agent and assistants like Claude, Cursor, etc directly to your Close account. It provides structured and secure access to your sales pipeline, so your agent can perform actions like creating leads, logging calls, sending SMS, managing tasks, and retrieving notes on your behalf.

  • Lead creation and management: Instantly create new leads with company details and contact information, keeping your CRM fresh and organized without manual entry.
  • Automated call logging: Have your agent log both inbound and outbound calls to specific leads, capturing call notes, durations, and recordings for seamless tracking.
  • SMS communication: Direct your agent to send or log SMS messages with leads, enabling timely follow-ups and improved client engagement.
  • Task management: Let your agent create tasks, set due dates, and track completion, making sure nothing slips through the cracks in your sales workflow.
  • Note retrieval and analysis: Fetch notes by lead or date, so your agent can summarize past interactions, surface important insights, or prepare for upcoming calls.

What is the Composio tool router, and how does it fit here?

What is Composio SDK?

Composio's Composio SDK helps agents find the right tools for a task at runtime. You can plug in multiple toolkits (like Gmail, HubSpot, and GitHub), and the agent will identify the relevant app and action to complete multi-step workflows. This can reduce token usage and improve the reliability of tool calls. Read more here: Getting started with Composio SDK

The tool router generates a secure MCP URL that your agents can access to perform actions.

How the Composio SDK works

The Composio SDK follows a three-phase workflow:

  1. Discovery: Searches for tools matching your task and returns relevant toolkits with their details.
  2. Authentication: Checks for active connections. If missing, creates an auth config and returns a connection URL via Auth Link.
  3. Execution: Executes the action using the authenticated connection.

Step-by-step Guide

Step by step09 STEPS
1

Prerequisites

Before you begin, make sure you have:
  • Node.js and npm installed
  • A Composio account with API key
  • An OpenAI API key
2

Getting API Keys for OpenAI and Composio

OpenAI API Key
  • Go to the OpenAI dashboard and create an API key. You'll need credits to use the models, or you can connect to another model provider.
  • Keep the API key safe.
Composio API Key
  • Log in to the Composio dashboard.
  • Navigate to your API settings and generate a new API key.
  • Store this key securely as you'll need it for authentication.
3

Install required dependencies

bash
npm install @ai-sdk/openai @ai-sdk/mcp @composio/core ai dotenv

First, install the necessary packages for your project.

What you're installing:

  • @ai-sdk/openai: Vercel AI SDK's OpenAI provider
  • @ai-sdk/mcp: MCP client for Vercel AI SDK
  • @composio/core: Composio SDK for tool integration
  • ai: Core Vercel AI SDK
  • dotenv: Environment variable management
4

Set up environment variables

bash
OPENAI_API_KEY=your_openai_api_key_here
COMPOSIO_API_KEY=your_composio_api_key_here
COMPOSIO_USER_ID=your_user_id_here

Create a .env file in your project root.

What's needed:

  • OPENAI_API_KEY: Your OpenAI API key for GPT model access
  • COMPOSIO_API_KEY: Your Composio API key for tool access
  • COMPOSIO_USER_ID: A unique identifier for the user session
5

Import required modules and validate environment

typescript
import "dotenv/config";
import { openai } from "@ai-sdk/openai";
import { Composio } from "@composio/core";
import * as readline from "readline";
import { streamText, type ModelMessage, stepCountIs } from "ai";
import { createMCPClient } from "@ai-sdk/mcp";

const composioAPIKey = process.env.COMPOSIO_API_KEY;
const composioUserID = process.env.COMPOSIO_USER_ID;

if (!process.env.OPENAI_API_KEY) throw new Error("OPENAI_API_KEY is not set");
if (!composioAPIKey) throw new Error("COMPOSIO_API_KEY is not set");
if (!composioUserID) throw new Error("COMPOSIO_USER_ID is not set");

const composio = new Composio({
  apiKey: composioAPIKey,
});
What's happening:
  • We're importing all necessary libraries including Vercel AI SDK's OpenAI provider and Composio
  • The dotenv/config import automatically loads environment variables
  • The MCP client import enables connection to Composio's tool server
6

Create Tool Router session and initialize MCP client

typescript
async function main() {
  // Create a tool router session for the user
  const session = await composio.create(composioUserID!, {
    toolkits: ["close"],
  });

  const mcpUrl = session.mcp.url;
What's happening:
  • We're creating a Tool Router session that gives your agent access to Close tools
  • The create method takes the user ID and specifies which toolkits should be available
  • The returned mcp object contains the URL and authentication headers needed to connect to the MCP server
  • This session provides access to all Close-related tools through the MCP protocol
7

Connect to MCP server and retrieve tools

typescript
const mcpClient = await createMCPClient({
  transport: {
    type: "http",
    url: mcpUrl,
    headers: session.mcp.headers, // Authentication headers for the Composio MCP server
  },
});

const tools = await mcpClient.tools();
What's happening:
  • We're creating an MCP client that connects to our Composio Tool Router session via HTTP
  • The mcp.url provides the endpoint, and mcp.headers contains authentication credentials
  • The type: "http" is important - Composio requires HTTP transport
  • tools() retrieves all available Close tools that the agent can use
8

Initialize conversation and CLI interface

typescript
let messages: ModelMessage[] = [];

console.log("Chat started! Type 'exit' or 'quit' to end the conversation.\n");
console.log(
  "Ask any questions related to close, like summarize my last 5 emails, send an email, etc... :)))\n",
);

const rl = readline.createInterface({
  input: process.stdin,
  output: process.stdout,
  prompt: "> ",
});

rl.prompt();
What's happening:
  • We initialize an empty messages array to maintain conversation history
  • A readline interface is created to accept user input from the command line
  • Instructions are displayed to guide the user on how to interact with the agent
9

Handle user input and stream responses with real-time tool feedback

typescript
rl.on("line", async (userInput: string) => {
  const trimmedInput = userInput.trim();

  if (["exit", "quit", "bye"].includes(trimmedInput.toLowerCase())) {
    console.log("\nGoodbye!");
    rl.close();
    process.exit(0);
  }

  if (!trimmedInput) {
    rl.prompt();
    return;
  }

  messages.push({ role: "user", content: trimmedInput });
  console.log("\nAgent is thinking...\n");

  try {
    const stream = streamText({
      model: openai("gpt-5"),
      messages,
      tools,
      toolChoice: "auto",
      stopWhen: stepCountIs(10),
      onStepFinish: (step) => {
        for (const toolCall of step.toolCalls) {
          console.log(`[Using tool: ${toolCall.toolName}]`);
          }
          if (step.toolCalls.length > 0) {
            console.log(""); // Add space after tool calls
          }
        },
      });

      for await (const chunk of stream.textStream) {
        process.stdout.write(chunk);
      }

      console.log("\n\n---\n");

      // Get final result for message history
      const response = await stream.response;
      if (response?.messages?.length) {
        messages.push(...response.messages);
      }
    } catch (error) {
      console.error("\nAn error occurred while talking to the agent:");
      console.error(error);
      console.log(
        "\nYou can try again or restart the app if it keeps happening.\n",
      );
    } finally {
      rl.prompt();
    }
  });

  rl.on("close", async () => {
    await mcpClient.close();
    console.log("\n👋 Session ended.");
    process.exit(0);
  });
}

main().catch((err) => {
  console.error("Fatal error:", err);
  process.exit(1);
});
What's happening:
  • We use streamText instead of generateText to stream responses in real-time
  • toolChoice: "auto" allows the model to decide when to use Close tools
  • stopWhen: stepCountIs(10) allows up to 10 steps for complex multi-tool operations
  • onStepFinish callback displays which tools are being used in real-time
  • We iterate through the text stream to create a typewriter effect as the agent responds
  • The complete response is added to conversation history to maintain context
  • Errors are caught and displayed with helpful retry suggestions

Complete Code

Here's the complete code to get you started with Close and Vercel AI SDK:

typescript
import "dotenv/config";
import { openai } from "@ai-sdk/openai";
import { Composio } from "@composio/core";
import * as readline from "readline";
import { streamText, type ModelMessage, stepCountIs } from "ai";
import { createMCPClient } from "@ai-sdk/mcp";

const composioAPIKey = process.env.COMPOSIO_API_KEY;
const composioUserID = process.env.COMPOSIO_USER_ID;

if (!process.env.OPENAI_API_KEY) throw new Error("OPENAI_API_KEY is not set");
if (!composioAPIKey) throw new Error("COMPOSIO_API_KEY is not set");
if (!composioUserID) throw new Error("COMPOSIO_USER_ID is not set");

const composio = new Composio({
  apiKey: composioAPIKey,
});

async function main() {
  // Create a tool router session for the user
  const session = await composio.create(composioUserID!, {
    toolkits: ["close"],
  });

  const mcpUrl = session.mcp.url;

  const mcpClient = await createMCPClient({
    transport: {
      type: "http",
      url: mcpUrl,
      headers: session.mcp.headers, // Authentication headers for the Composio MCP server
    },
  });

  const tools = await mcpClient.tools();

  let messages: ModelMessage[] = [];

  console.log("Chat started! Type 'exit' or 'quit' to end the conversation.\n");
  console.log(
    "Ask any questions related to close, like summarize my last 5 emails, send an email, etc... :)))\n",
  );

  const rl = readline.createInterface({
    input: process.stdin,
    output: process.stdout,
    prompt: "> ",
  });

  rl.prompt();

  rl.on("line", async (userInput: string) => {
    const trimmedInput = userInput.trim();

    if (["exit", "quit", "bye"].includes(trimmedInput.toLowerCase())) {
      console.log("\nGoodbye!");
      rl.close();
      process.exit(0);
    }

    if (!trimmedInput) {
      rl.prompt();
      return;
    }

    messages.push({ role: "user", content: trimmedInput });
    console.log("\nAgent is thinking...\n");

    try {
      const stream = streamText({
        model: openai("gpt-5"),
        messages,
        tools,
        toolChoice: "auto",
        stopWhen: stepCountIs(10),
        onStepFinish: (step) => {
          for (const toolCall of step.toolCalls) {
            console.log(`[Using tool: ${toolCall.toolName}]`);
          }
          if (step.toolCalls.length > 0) {
            console.log(""); // Add space after tool calls
          }
        },
      });

      for await (const chunk of stream.textStream) {
        process.stdout.write(chunk);
      }

      console.log("\n\n---\n");

      // Get final result for message history
      const response = await stream.response;
      if (response?.messages?.length) {
        messages.push(...response.messages);
      }
    } catch (error) {
      console.error("\nAn error occurred while talking to the agent:");
      console.error(error);
      console.log(
        "\nYou can try again or restart the app if it keeps happening.\n",
      );
    } finally {
      rl.prompt();
    }
  });

  rl.on("close", async () => {
    await mcpClient.close();
    console.log("\n👋 Session ended.");
    process.exit(0);
  });
}

main().catch((err) => {
  console.error("Fatal error:", err);
  process.exit(1);
});

Conclusion

You've successfully built a Close agent using the Vercel AI SDK with streaming capabilities! This implementation provides a powerful foundation for building AI applications with natural language interfaces and real-time feedback.

Key features of this implementation:

  • Real-time streaming responses for a better user experience with typewriter effect
  • Live tool execution feedback showing which tools are being used as the agent works
  • Dynamic tool loading through Composio's Tool Router with secure authentication
  • Multi-step tool execution with configurable step limits (up to 10 steps)
  • Comprehensive error handling for robust agent execution
  • Conversation history maintenance for context-aware responses

You can extend this further by adding custom error handling, implementing specific business logic, or integrating additional Composio toolkits to create multi-app workflows.
TOOLS & TRIGGERS

Supported Tools and Triggers

Every Close action and event your agent gets out of the box.

Create Call

Creates a new call record in Close.

Create Contact

Create a contact on an existing Close lead, or create a new lead named after the contact when lead_id is omitted.

Create Custom Activity

Create a draft or published custom activity instance on a Close lead.

Create Custom Field

Create a custom-field definition for Close leads or opportunities.

Create Email

Create or log a Close email activity.

Create Lead

Creates a new lead in Close CRM.

Create Note

Create a plaintext or rich-text note activity on a Close lead.

Create Opportunity

Create a Close sales opportunity on an existing lead, or create an Untitled lead for it when lead_id is omitted.

Create Sequence

Create a Close sales sequence with ordered call or email steps.

Create SMS Message

Create an SMS activity on a Close lead to log received, sent, or draft messages, or to send or schedule a message.

Create Task

Create a Close task associated with a lead, using task text and an actionable date, with optional completion status and assignment.

Delete Activity Custom Field

Permanently delete one Close activity custom-field definition by ID.

Delete Call

This tool allows you to delete a specific call activity in Close.

Delete Custom Activity

Permanently delete one Close custom activity instance by ID.

Delete Email

This tool allows you to permanently delete a specific email activity in Close.

Delete Lead

Permanently delete one Close lead by ID.

Delete Lead Custom Field

Permanently delete a Close lead custom-field definition and its values.

Delete Note

Permanently delete one Close note activity by ID.

Delete Opportunity

Permanently delete one Close opportunity by ID.

Delete Opportunity Status

Permanently delete an unused Close opportunity status; this cannot be undone.

Delete SMS

Permanently delete one Close SMS activity by ID.

Download File

Download bytes from a provider-issued Close Files API URL.

Get Activity Custom Field

Retrieve one Close activity custom-field definition by ID.

Get Call

Retrieve one Close call activity by ID with optional field projection.

Get Contact

Retrieve one Close contact by ID, optionally limiting returned fields.

Get Created Activity

Retrieve one Close lead-creation activity with optional field projection.

Get Email

Retrieve one Close email activity by ID with optional field projection.

Get Event

Retrieve one Close event-log record by event ID.

Get Lead

Retrieve one Close lead by ID with optional field projection.

Get Lead Custom Field

Retrieve one Close lead custom-field definition by ID.

Get Notes

This tool retrieves a list of note activities from Close.

Get Note by ID

Retrieve one Close note activity by ID with optional field projection.

Get Opportunity

Retrieve one Close opportunity by ID with optional field projection.

Get Sequence

Retrieve one Close sequence by ID with its workflow configuration.

Get Shared Custom Field

Retrieve one Close shared custom-field definition by ID.

Get SMS

Retrieve one Close SMS activity by ID with optional field projection.

Get Task

Retrieve one Close task by ID with optional field projection.

Get User

Retrieve one Close organization user by ID with optional field projection.

List Activities

Lists one page of Close email or call activities using documented filters.

List Contacts

List Close contacts with lead filtering, projection, and offset pagination.

List Custom Activities

List Close custom activity instances with filters and offset pagination.

List Custom Activity Types

This tool lists the custom activity types defined in a Close organization, returning each type's id and name.

List Custom Objects

List custom object instances attached to a Close lead, optionally by type.

List Custom Object Types

This tool lists the custom object types defined in a Close organization, returning each type's id and name.

List Events

List Close change events with filters, projection, and cursor pagination.

List Lead Custom Fields

List Close lead custom-field definitions with projection and pagination.

List Leads

List or text-search Close leads with field projection and offset pagination.

List Lead Statuses

List the configured lead statuses for the connected organization.

List Opportunities

List a page of Close opportunities with offset pagination.

List Opportunity Custom Fields

List Close opportunity custom-field definitions with projection and pagination.

List Opportunity Statuses

List the configured opportunity statuses for the connected organization.

List Outcomes

List Close call and meeting outcomes with projection and offset pagination.

List Pipelines

List pipelines and their ordered opportunity statuses.

List Sequences

List Close sales sequences with offset pagination.

List Sequence Subscriptions

List Close sequence subscriptions.

List Tasks

List Close tasks using task, assignee, lead, date, and pagination filters.

List Unified Activities

List one page of Close activities across all types and matching filters.

List Users

List users in the connected Close organization with offset pagination.

List Webhook Subscriptions

List webhook subscriptions configured for the Close organization.

Search Data

Search Close objects using the structured Advanced Filtering query language.

Update Activity Custom Field

Update mutable settings of one Close activity custom-field definition.

Update Contact

Update standard and custom fields on one existing Close contact.

Update Custom Activity

Update standard and custom fields on one Close custom activity instance.

Update Email

Update an existing Close email activity.

Update Lead Custom Field

Update a Close lead custom-field definition.

Update Note

Update the mutable content and metadata of one Close note activity.

Update Opportunity

Update standard and custom fields on one Close opportunity.

Update Sequence

Update a Close sequence name or replace its entire ordered step list.

Update Task

Update assignment, actionable date, or completion state on a Close task; lead tasks can also update text.

Who Am I

Return the identity (email, name) of the connected Close account.

FAQ

Frequently asked questions

Start with Close.It takes 30 seconds.

Managed auth, hosted MCP servers, and every Close tool your agent needs.Free to start.

Start building