How to integrate Dynamics365 MCP with Vercel AI SDK v6

Connect Vercel AI SDK v6 to Dynamics365 MCP. Create a new sales lead for acme corp, list all open invoices for this quarter, and more using natural language, with authentication handled for you.

Dynamics365 logoDynamics365
Oauth2

Dynamics 365 is Microsoft's platform combining CRM, ERP, and productivity apps. It streamlines sales, marketing, service, and operations in one place.

119 Tools

Introduction

This guide walks you through connecting Dynamics365 to Vercel AI SDK v6 using the Composio tool router. By the end, you'll have a working Dynamics365 agent that can manage accounts and contacts, capture leads, track opportunities, create quotes and invoices, and resolve support cases through natural language commands.

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

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

For exact tool slugs and parameter tables, see the tool catalog in the Composio docs.

Also integrate Dynamics365 with

TL;DR

Here's what you'll learn:
  • How to set up and configure a Vercel AI SDK agent with Dynamics365 integration
  • Using Composio's Tool Router to dynamically load and access Dynamics365 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 Dynamics365 MCP server, and what's possible with it?

The Dynamics 365 MCP server is an implementation of the Model Context Protocol that connects your AI agent and assistants like Claude, Cursor, etc directly to your Dynamics 365 account. It provides structured and secure access to your CRM records, so your agent can create accounts and contacts, manage leads and opportunities, handle customer cases, and process invoices and sales orders on your behalf.

  • Account and contact creation: Have your agent create new customer accounts and contact records in Dynamics 365.
  • Lead and opportunity management: Let the agent create leads and opportunities, retrieve lead details, list all leads, and update records as deals progress.
  • Customer case handling: Direct your agent to open new customer cases and update existing cases as issues move toward resolution.
  • Invoice management: Instruct your agent to create invoices, retrieve individual invoices or a full invoice list, and update invoice details.
  • Sales order processing: Have your agent create sales orders and update them when order information changes.

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: ["dynamics365"],
  });

  const mcpUrl = session.mcp.url;
What's happening:
  • We're creating a Tool Router session that gives your agent access to Dynamics365 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 Dynamics365-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 Dynamics365 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 dynamics365, 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 Dynamics365 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 Dynamics365 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: ["dynamics365"],
  });

  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 dynamics365, 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 Dynamics365 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

Supported Tools

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

Add Contact to Account

Associate an existing contact with an existing account in Dynamics 365.

Assign User Security Role

Assign a security role to a system user in the connected Dataverse environment.

Associate Contact Account

Sets an existing contact's parent account, replacing any current parent customer.

Clear Account Description

Clear an existing account's description to null without deleting the account.

Create Annotation

Create a text or attachment note, optionally linked to a supported record.

Create appointment

Create a Dataverse appointment from writable logical column values and return the created row.

Create Email Activity

Create and store a draft email activity without sending it.

Create invoice line item

Create a Dataverse invoice line item from writable logical column values and return the created row.

Create opportunity line item

Create a Dataverse opportunity line item from writable logical column values and return the created row.

Create Phone Call

Record a CRM phone-call activity; this does not place a telephone call.

Create price list

Create a Dataverse price list from writable logical column values and return the created row.

Create Product

Create a Dataverse catalog product and return its selected representation.

Create Product Price List Item

Add an existing product and unit to an existing Dynamics price list.

Create quote

Create a Dataverse quote from writable logical column values and return the created row.

Create quote line item

Create a Dataverse quote line item from writable logical column values and return the created row.

Create sales order line item

Create a Dataverse sales order line item from writable logical column values and return the created row.

Create Social Activity

Create a Dataverse social activity and return its stored representation.

Create system user

Create a Dataverse system user from writable logical column values and return the created row.

Create Task

Create a Dataverse task activity and return its selected representation.

Create transaction currency

Create a Dataverse transaction currency from writable logical column values and return the created row.

Delete account

Delete a Dataverse account by GUID.

Delete appointment

Delete a Dataverse appointment by GUID.

Delete case

Delete a Dataverse case by GUID.

Delete contact

Delete a Dataverse contact by GUID.

Delete email activity

Delete a Dataverse email activity by GUID.

Delete invoice

Delete a Dataverse invoice by GUID.

Delete invoice line item

Delete a Dataverse invoice line item by GUID.

Delete lead

Delete a Dataverse lead by GUID.

Delete note

Delete a Dataverse note by GUID.

Delete opportunity

Delete a Dataverse opportunity by GUID.

Delete opportunity line item

Delete a Dataverse opportunity line item by GUID.

Delete price list

Delete a Dataverse price list by GUID.

Delete product

Delete a Dataverse product by GUID.

Delete quote

Delete a Dataverse quote by GUID.

Delete quote line item

Delete a Dataverse quote line item by GUID.

Delete sales order

Delete a Dataverse sales order by GUID.

Delete sales order line item

Delete a Dataverse sales order line item by GUID.

Delete system user

Request deletion of a Dataverse system user by GUID.

Delete task

Delete a Dataverse task by GUID.

Delete transaction currency

Delete a Dataverse transaction currency by GUID.

Disassociate Contact Account

Remove a contact's parent account relationship without deleting either record.

Create Account

Creates a new account entity record in Dynamics CRM using the Web API.

Create Case

Creates a new case (incident) in Dynamics CRM using the Web API.

Create Contact

Creates a new contact entity record in Dynamics CRM using the Web API.

Create Invoice

Creates a new invoice entity record in Dynamics CRM using the Web API.

Create Lead

Creates a new lead entity record in Dynamics CRM using the Web API.

Create Opportunity

Creates a new opportunity entity record in Dynamics CRM using the Web API.

Create Sales Order

Creates a new sales order entity record in Dynamics CRM using the Web API.

Dynamicscrm get a invoice

Retrieve a Dynamics CRM invoice by ID, returning selected invoice details such as its number, dates, billing and shipping addresses, amounts and status.

Dynamicscrm get a lead

Dynamicscrm get a lead

Dynamicscrm get all leads

Retrieve one page of Dynamics CRM leads with optional OData query options.

Update Case

Updates a case (incident) in Dynamics CRM using the Web API.

Update Invoice

Update a Dynamics CRM invoice by ID using the Web API.

Update Lead

Updates an existing Dynamics CRM lead using the Web API.

Update Opportunity

Updates an existing Dynamics CRM opportunity using the Web API.

Update Sales Order

Updates an existing Dynamics CRM sales order using the Web API.

Execute Batch

Execute a Dataverse multipart batch, optionally containing atomic change sets.

Get account

Retrieve a Dataverse account by its GUID, optionally selecting columns and expanding relationships.

Get all invoices action

Retrieve one page of Dynamics 365 invoices with optional OData query options.

Get appointment

Retrieve a Dataverse appointment by its GUID, optionally selecting columns and expanding relationships.

Get Attribute Metadata

Retrieve one Dataverse attribute definition by its metadata IDs.

Get case

Retrieve a Dataverse case by its GUID, optionally selecting columns and expanding relationships.

Get contact

Retrieve a Dataverse contact by its GUID, optionally selecting columns and expanding relationships.

Get email activity

Retrieve a Dataverse email activity by its GUID, optionally selecting columns and expanding relationships.

Get invoice line item

Retrieve a Dataverse invoice line item by its GUID, optionally selecting columns and expanding relationships.

Get note

Retrieve a Dataverse note by its GUID, optionally selecting columns and expanding relationships.

Get opportunity

Retrieve a Dataverse opportunity by its GUID, optionally selecting columns and expanding relationships.

Get opportunity line item

Retrieve a Dataverse opportunity line item by its GUID, optionally selecting columns and expanding relationships.

Get price list

Retrieve a Dataverse price list by its GUID, optionally selecting columns and expanding relationships.

Get product

Retrieve a Dataverse product by its GUID, optionally selecting columns and expanding relationships.

Get quote

Retrieve a Dataverse quote by its GUID, optionally selecting columns and expanding relationships.

Get quote line item

Retrieve a Dataverse quote line item by its GUID, optionally selecting columns and expanding relationships.

Get sales order

Retrieve a Dataverse sales order by its GUID, optionally selecting columns and expanding relationships.

Get sales order line item

Retrieve a Dataverse sales order line item by its GUID, optionally selecting columns and expanding relationships.

Get system user

Retrieve a Dataverse system user by its GUID, optionally selecting columns and expanding relationships.

Get task

Retrieve a Dataverse task by its GUID, optionally selecting columns and expanding relationships.

Get transaction currency

Retrieve a Dataverse transaction currency by its GUID, optionally selecting columns and expanding relationships.

List appointments

List one page of Dataverse appointments.

List Entity Definitions

Discover Dataverse table definitions and return caller-selected metadata.

List entity sets

List available Dataverse entity sets and their resource URLs, not table rows.

List invoice line items

List one page of Dataverse invoice line items.

List notes

List one page of Dataverse notes.

List opportunity line items

List one page of Dataverse opportunity line items.

List price lists

List one page of Dataverse price lists.

List products

List one page of Dataverse products.

List quote line items

List one page of Dataverse quote line items.

List quotes

List one page of Dataverse quotes.

List sales orders

List one page of Dataverse sales orders.

List system users

List one page of Dataverse system users.

List transaction currencies

List one page of Dataverse transaction currencies.

Query accounts

List or filter Dynamics accounts with projection, ordering, and bounded results.

Query campaigns

List, filter, expand, or aggregate Dynamics campaigns.

Query cases

Return one bounded page of cases; pass next_link back to fetch the next page.

Query contacts

List or filter Dynamics contacts with selected fields and ordering.

Query email activities

Return one bounded page of email activities; pass next_link back to fetch the next page.

Query Entity Data

Query contacts, incidents, phone calls, activity pointers, opportunities, or sales order details and return one page with its next link.

Query opportunities

Return one bounded page of opportunities; pass next_link back to fetch the next page.

Query phone calls

Return one bounded page of phone-call activities; pass next_link back to fetch the next page.

Query social activities

Return one bounded page of social activities; pass next_link back to fetch the next page.

Query tasks

Return one bounded page of tasks; pass next_link back to fetch the next page.

Remove Contact from Account

Remove an existing contact from an account without deleting either record.

Resolve Case

Resolve an active case by creating its incident-resolution activity.

Update account

Update only the supplied writable columns of an existing Dataverse account and return the updated row.

Update Account Name

Updates only the name of an existing Dynamics 365 account.

Update appointment

Update only the supplied writable columns of an existing Dataverse appointment and return the updated row.

Update contact

Update only the supplied writable columns of an existing Dataverse contact and return the updated row.

Update email activity

Update only the supplied writable columns of an existing Dataverse email activity and return the updated row.

Update invoice line item

Update only the supplied writable columns of an existing Dataverse invoice line item and return the updated row.

Update note

Update only the supplied writable columns of an existing Dataverse note and return the updated row.

Update opportunity line item

Update only the supplied writable columns of an existing Dataverse opportunity line item and return the updated row.

Update price list

Update only the supplied writable columns of an existing Dataverse price list and return the updated row.

Update product

Update only the supplied writable columns of an existing Dataverse product and return the updated row.

Update quote

Update only the supplied writable columns of an existing Dataverse quote and return the updated row.

Update quote line item

Update only the supplied writable columns of an existing Dataverse quote line item and return the updated row.

Update sales order line item

Update only the supplied writable columns of an existing Dataverse sales order line item and return the updated row.

Update system user

Update only the supplied writable columns of an existing Dataverse system user and return the updated row.

Update task

Update only the supplied writable columns of an existing Dataverse task and return the updated row.

Update transaction currency

Update only the supplied writable columns of an existing Dataverse transaction currency and return the updated row.

Get Caller Identity

Return the connected Dataverse caller's identity IDs.

FAQ

Frequently asked questions

Start with Dynamics365.It takes 30 seconds.

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

Start building