How to integrate Cal MCP with Pydantic AI

Framework Integration Gradient
Cal Logo
Pydantic AI Logo
divider

Introduction

This guide walks you through connecting Cal to Pydantic AI using the Composio tool router. By the end, you'll have a working Cal agent that can check if my google calendar is synced, cancel a meeting using its unique id, see if my calendar is free tomorrow afternoon, confirm a team booking by its uid through natural language commands.

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

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

TL;DR

Here's what you'll learn:
  • How to set up your Composio API key and User ID
  • How to create a Composio Tool Router session for Cal
  • How to attach an MCP Server to a Pydantic AI agent
  • How to stream responses and maintain chat history
  • How to build a simple REPL-style chat interface to test your Cal workflows

What is Pydantic AI?

Pydantic AI is a Python framework for building AI agents with strong typing and validation. It leverages Pydantic's data validation capabilities to create robust, type-safe AI applications.

Key features include:

  • Type Safety: Built on Pydantic for automatic data validation
  • MCP Support: Native support for Model Context Protocol servers
  • Streaming: Built-in support for streaming responses
  • Async First: Designed for async/await patterns

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

The Cal MCP server is an implementation of the Model Context Protocol that connects your AI agent and assistants like Claude, Cursor, etc directly to your Cal account. It provides structured and secure access to your scheduling and calendar management tools, so your agent can perform actions like confirming bookings, checking calendar availability, managing team members, and handling integrations on your behalf.

  • Instant meeting confirmation and cancellation: Ask your agent to confirm or cancel any meeting booking using a unique identifier, streamlining the back-and-forth of scheduling.
  • Real-time calendar availability checks: Let your agent fetch free/busy slots from your connected calendars to suggest optimal meeting times without revealing event details.
  • Team and organization management: Effortlessly add new members to teams or update organization attribute options, making group scheduling and administration smooth.
  • Integration status monitoring: Have your agent verify the synchronization status of connected calendars like Google Calendar, check Stripe payment integration, or review webhook subscriptions for reliability.
  • Calendar feed verification: Use your agent to validate and check accessibility of ICS calendar feeds, ensuring external calendars are synced and up-to-date.

Supported Tools & Triggers

Tools
Add member to teamAdds a new member to a specified team within an organization by creating a team membership.
Add organization attribute optionAdds a new option to an organization's attribute, requiring a display `value` and a `slug` unique for that attribute.
Delete oauth client webhookPermanently deletes a specific webhook subscription of an oauth client in the cal application, stopping its notifications.
Cancel booking via uidCancels an existing and active cal.
Check calendar availabilityRetrieves free/busy availability for a specified calendar to aid scheduling without revealing event details; requires an existing, accessible calendar, noting that data granularity can vary.
Check gcal synchronization statusCall this read-only action to verify the connection and synchronization status of a user's google calendar integration with cal.
Check ics feed calendar endpointChecks an ics feed url (expected as a query parameter) to verify its validity, accessibility, and icalendar data integrity.
Check Stripe statusVerifies if stripe is correctly connected to the cal scheduling system and functional for processing payments, reporting only on the integration's status.
Check team stripe integration statusRetrieves the stripe integration status and related information for a team, primarily to verify account connection, subscription details, or payment setup; this is a read-only operation that does not modify stripe settings.
Confirm booking by uidConfirms an existing booking by `bookinguid` if the booking exists and is in a state allowing confirmation (e.
Connect to calendarInitiates or checks the external connection status for a specified calendar, possibly returning a redirect url for user authorization to complete integration, without altering calendar data.
Create membership for organizationCreates a new membership or updates an existing one for a user within an organization; the user specified by userid must already exist in the system.
Create oauth client userCreates a new managed user for an oauth client, setting profile and scheduling preferences; if `timezone` is not provided, a default schedule (mon-fri, 9am-5pm) is not created, requiring manual setup via `/schedules` endpoint before bookings are possible.
Create OAuth client webhook configurationCreates a webhook for an existing oauth client to send real-time cal.
Create organization attributesCreates a new custom attribute for an existing organization, used to enhance data collection for event bookings or user profiles.
Create organization webhook by org IDCreates a webhook for a specified, existing organization, sending notifications for selected trigger events to a designated, publicly accessible subscriber url that accepts post requests.
Create or update team profileCreates a new team profile, or updates one if a 'slug' matches, customizing branding, scheduling, privacy, and operational details.
Create phone call eventSchedules a phone call event in cal.
Create phone call for event typeCreates a phone call template associated with an existing event type and team, for defining reusable call configurations like numbers and scripts; note this does not initiate an actual call.
Create team event typeCreates a new event type for a specified team in cal.
Create team event types with custom optionsCreates a highly customizable cal.
Create team in organizationCreates a new team with customizable attributes within an existing and accessible cal.
Create team membership with roleAdds a user to a team with a specified role, acceptance status, and impersonation settings; ensure `teamid` and `userid` refer to existing, valid entities.
Create user availability scheduleCreates a cal.
Create user schedule in organizationCreates a new schedule defining a user's availability with weekly slots and date-specific overrides in an organization; setting 'isdefault' to true may replace an existing default schedule for the user.
Create webhook for event typeCreates a webhook for an existing `eventtypeid` in cal.
Decline booking with reasonDeclines a pending booking using its bookinguid, optionally with a reason; this action is irreversible and applies only to bookings awaiting confirmation.
Delete conference app connectionDisconnects the specified conferencing application (e.
Delete event type by idPermanently deletes an existing event type by its id, which invalidates its scheduling links; the operation is irreversible, and while existing bookings are unaffected, no new bookings can be made for this event type.
Delete event type in teamPermanently removes an event type's configuration from a team's scheduling options (e.
Delete membership in teamUse to permanently remove a user's membership from a specific team within an organization, which revokes their team-associated access but does not remove them from the organization.
Delete oauth client userIrreversibly revokes a specific user's association with a given oauth client, without deleting the user's cal account.
Delete organization attributePermanently deletes an existing attribute (specified by `attributeid`) from an existing organization (specified by `orgid`); this action is irreversible and may affect features dependent on the attribute.
Delete organization attribute optionPermanently deletes a specified option from an organization's attribute, typically to remove an unnecessary configuration choice.
Delete organization membershipIrreversibly deletes a user's membership from an organization, removing all associated access and permissions; the response confirms deletion without returning details of the deleted membership.
Delete org webhookPermanently deletes an organization's webhook, which stops all its notifications; this action is irreversible and the webhook cannot be recovered.
Delete schedule by idPermanently deletes a specific schedule using its unique identifier, which must correspond to an existing schedule.
Delete selected calendarsRemoves a specified, currently selected calendar from the user's active list within the application, without deleting it from the external provider.
Delete selected slotDeletes a previously selected time slot from the cal schedule using its `uid`; the slot must exist and this action is irreversible.
Delete team by idPermanently and irreversibly deletes an existing team and all its associated data from the cal system, using the team's unique `teamid`.
Delete team from organizationPermanently and irreversibly deletes a specific team (and all its associated data, members, and linked projects/events) from an organization, when the team is no longer needed or during organizational restructuring.
Delete team memberships by idIrreversibly removes a user's team membership in the cal application, revoking access to that specific team; the user's overall cal account remains active.
Delete user attribute optionRemoves a custom attribute option (e.
Delete user from organizationPermanently removes a user from a specific organization (user's system-wide account is unaffected), revoking their access rights therein; this action is irreversible via api and expects the user to be a current member.
Delete user schedulePermanently deletes a specific user's schedule, provided the organization, user, and schedule (identified by `orgid`, `userid`, and `scheduleid`) exist.
Delete webhook by idPermanently deletes an existing webhook by its `webhookid`, stopping future notifications; this action is irreversible.
Delete webhook for event typePermanently deletes a specific webhook for an event type, halting its real-time notifications; this operation is irreversible and leaves the event type and other webhooks untouched.
Delete webhooks for event typeCall this to irreversibly delete all webhooks for a specific `eventtypeid` if the event type exists; details of deleted webhooks are not returned.
Disconnect calendar using credential idDisconnects a calendar integration by its provider name and credential id, irreversibly revoking cal's access; external calendar data remains unaffected.
Fetch all bookingsFetches a list of bookings, optionally filtered by status, attendee, date range, or by event/team ids (which must belong to/include the authenticated user respectively), with support for pagination and sorting.
Fetch event type detailsFetches all configuration settings and characteristics for a single event type (identified by orgid, teamid, and eventtypeid), which must exist and be accessible; this read-only action cannot list, create, or modify event types.
Fetch organization attribute by idRetrieves a specific attribute of an organization, useful for fetching a single data point instead of the entire organization record.
Fetch provider access tokenFetches an oauth access token for the specified `clientid` to authenticate api calls; this action only retrieves the token, not managing scheduling or calendar events.
Fetch schedule by idFetches comprehensive details for a specific, existing schedule using its `scheduleid`.
Fetch user schedule by org idFetches a specific user's schedule, potentially including events and availability, using `orgid`, `userid`, and `scheduleid`.
Fetch webhook by event type idRetrieves details for a single, specific webhook using its `webhookid` and associated `eventtypeid`.
Force refresh user oauth clientForces an immediate refresh of oauth tokens for a specified user and client, bypassing the normal expiration cycle, typically when existing tokens are suspected to be invalid or fresh credentials are required; ensure the user exists, the client is registered, and prior authorization is valid.
Get all timezonesRetrieves all supported time zone identifiers (e.
Get available slots infoRetrieves available time slots for scheduling by considering existing bookings and availability, based on criteria like a specified time range and event type.
Get conference OAuth authorization urlGenerates an oauth 2.
Get default schedule detailsRetrieves the cal system's global default schedule configuration, not custom or user-specific ones.
Get event type by team idRetrieves a specific event type by its id, requiring that the event type is associated with the given team id.
Get google calendar oauth authentication urlGenerates the initial google calendar oauth 2.
Get oauth clients userRetrieves users associated with a specific oauth client (identified by a valid `clientid`) for auditing access or managing permissions.
Get organization IDRetrieves all organization ids associated with the currently authenticated user.
Get organization schedulesRetrieves a list of schedules (e.
Get organization teams event typesRetrieves event types, including names, durations, and custom settings for team scheduling, for all teams within an existing organization specified by `orgid`.
Get organization user schedulesFetches all schedule information for a specific user within an organization; handle potential pagination for extensive schedules.
Get schedule for user in teamFetches all scheduled events or appointments for a specified user within their team and organization.
Get stripe connect infoRetrieves stripe connect account details (id, charges/payouts status, verification, settings) for the user's linked cal.
Get team details by organization ID and team IDRetrieves comprehensive details (e.
Get team information by team IDFetches comprehensive details for an existing team using its unique id; returned fields may vary by team configuration and user permissions.
Get teams listRetrieves all teams the user belongs to, including their names and members.
Get webhook by idRetrieves details for an existing and accessible webhook by its id; this is a read-only operation.
Handle conferencing oauth callback for appProcesses an oauth 2.
List event typesRetrieves cal event types, filterable by `username` (required if `eventslug` is provided), multiple `usernames`, or organization details (`orgslug` or `orgid`).
List organization membershipsRetrieves all memberships for a given organization, including user details, roles, status, and membership dates.
List team event types by org and team idRetrieves all event types for a specific team within an organization, optionally filtering by a specific event slug.
Mark booking absent for UIDMarks the host and/or specified attendees as absent for an existing booking, typically used after a scheduled event to record no-shows.
Modify organization membership by idUpdates an organization membership's status (accepted), role, or impersonation settings, identified by `orgid` and `membershipid` in the path; requires at least one of these fields in the request to apply changes.
Modify org attribute by idPartially updates an organization attribute using `orgid` and `attributeid`, allowing modification of its name, slug, type, or enabled status; changing the 'type' may affect existing data.
Patch organization attribute optionPartially updates a specific option for an organization's attribute, modifying its 'value' and/or 'slug'; at least one of 'value' or 'slug' must be provided.
Patch organization user detailsPartially updates details for a user that exists within the specified organization.
Patch team details by IDUpdates specified details for an existing team identified by `teamid`; unspecified fields remain unchanged.
Patch webhook event typeUpdates configuration (e.
Post calendar credentialsUse to submit/update authentication credentials (passed in the request body) for an existing calendar, enabling cal to connect with external calendar services for synchronization.
Connect conferencing appConnects or reconnects cal.
Post a new booking requestCreates a new booking for an active event type, scheduling it for a specified start time (preferably in the future) with primary attendee details and optional customizations.
Assign or create attribute option for userAssigns an existing attribute option (using `attributeoptionid`) or creates a new one (using `value`) for a user, linking it to a specified `attributeid` which must already exist within the organization.
Add selected calendarLinks a new external calendar or updates an existing link to one, enabling synchronization with the cal application by specifying the `integration` provider, the calendar's `externalid`, and the `credentialid`.
Post user to organizationAdds a new user to an existing organization (identified by `orgid` in path), requiring user's `email` and allowing extensive optional profile customization.
Post webhook eventCreates a new cal.
Reassign booking to another userReassigns an existing, active booking to a specified, authorized user; does not notify participants of this change.
Reassign booking with uidReassigns the specified booking to a new team member, who is determined by the system rather than being specified in the request.
Refresh oauth token for client idRefreshes an oauth access token for a specified `clientid` and managed user using their `refreshtoken`, enabling continued api access when the current token is near or past expiry.
Reschedule booking by uidReschedules an existing booking (identified by `bookinguid`) to a new time, provided the new slot's availability is confirmed beforehand; all other rescheduling parameters (e.
Reserve slot for eventTemporarily reserves an available time slot for an existing and bookable event type, useful for high-demand slots to prevent double-bookings while the user completes the booking.
Retrieve attribute options for orgRetrieves all available options for a specific attribute within a designated organization, requiring valid and associated `orgid` and `attributeid`.
Retrieve booking details by uidFetches comprehensive details for an existing booking, identified by its `bookinguid`.
Retrieve calendar busy timesTo find busy calendar slots for scheduling/conflict detection, call this with a valid `credentialid`, an `externalid` accessible by it, and a recognized iana `loggedinuserstz`; returns only busy intervals, not event details or free slots.
Retrieve calendar listRetrieves a list of all calendar summaries (no event details) associated with the authenticated user's account.
Retrieve current team for organizationRetrieves details of the team(s) for the currently authenticated user within the specified organization `orgid`.
Retrieve default conferencing settingsRetrieves an account's or organization's read-only default conferencing settings in cal.
Retrieve event type by idRetrieves comprehensive details for a specific, existing cal.
Retrieve membership from organizationRetrieves detailed information about a specific membership within a particular organization.
Retrieve my informationRetrieves the authenticated user's core profile information (e.
Retrieve OAuth client user by IDRetrieves detailed information for an existing user specifically associated with an existing oauth client.
Retrieve oauth client webhook by idRetrieves a specific webhook using its `webhookid` and the `clientid` of its associated oauth client.
Retrieve organization attributesRetrieves detailed attributes (e.
Retrieve organization attributes optionsFetches all available attribute options for a specific user within a given organization, provided the organization and user exist and the user is part of the organization.
Retrieve organization webhook by idRetrieves detailed information, including configuration and status, for a specific webhook by its id (`webhookid`) within a given organization (`orgid`).
Retrieve organization webhooks by org IDRetrieves a list of webhooks for an organization specified by `orgid`, supporting pagination; this is a read-only operation.
Retrieve provider detailsRetrieves detailed information for an existing provider in the cal scheduling system using their unique client id.
Retrieve schedules listRetrieves a comprehensive list of all schedules associated with the authenticated user's cal.
Retrieve team details in organizationRetrieves a paginated list of teams and their details for a specific organization id; individual team member details or schedules are not included.
Retrieve team event typesRetrieves event types for a team within the cal scheduling system; this action does not provide details on scheduled instances or member availability.
Retrieve team membership by idRetrieves detailed information, including role and status, for a specific team membership using its id, for a given organization and team.
Retrieve team membership detailsRetrieves detailed attributes for a specific team membership by its id and the team id, such as member information, role, and status; does not list all team members.
Retrieve team membershipsRetrieves a list of memberships, detailing member roles and statuses, for an existing team specified by `teamid`, with support for pagination.
Retrieve team memberships for organizationRetrieves all user memberships, including their roles and states, for a specific team within an organization.
Retrieve users in organizationRetrieves users associated with a specific organization id, excluding individual scheduling or calendar data; the `orgid` must be a valid identifier for an existing organization.
Retrieve v2 conferencing infoRetrieves an authenticated cal user's or organization's video conferencing configurations, capabilities, and installed apps, useful for understanding options before scheduling or verifying setups; provider availability may vary by subscription or settings.
Retrieve webhook details for oauth clientRetrieves a list of webhooks for a specific oauth client, supporting pagination.
Retrieve webhooks for event typeRetrieves a paginated list of webhooks (including urls, subscribed events, and status) for a specified, existing event type id, useful for auditing configurations or troubleshooting.
Retrieve webhooks listRetrieves a paginated list of webhooks from the user's cal scheduling system account, which are used for real-time notifications on events like new bookings, cancellations, or updates.
Save calendar entrySaves or updates a calendar's settings using a get request, typically for data already on the server or simple updates via query parameters.
Save calendar ics feedsImports and saves one or more publicly accessible external icalendar (ics) feed urls into the cal.
Save OAuth credentials via GCal APICompletes the google calendar oauth 2.
Save stripe detailsCompletes the stripe oauth flow by saving stripe details; call this when a user is redirected back from stripe with an authorization `code` and `state`.
Set default conferencing appSets the specified, valid, and configured conferencing application as the default for new meetings for the authenticated user.
Update destination calendar integrationUpdates the destination calendar for syncing events, using `integration` and `externalid` (typically from `/calendars` endpoint).
Update OAuth client user settingsUpdates specified profile and scheduling preference fields for a user associated with an oauth client; `defaultscheduleid`, if provided, must be an existing, valid schedule for the user.
Update oauth client webhookUpdates specified properties of an existing webhook for an oauth client; omitted fields remain unchanged.
Update schedule by IDUpdates an existing schedule by its id, allowing partial modification of properties; providing `availability` or `overrides` replaces them entirely.
Update team information by idUpdates an existing team's information by its id within a specified organization; the `slug`, if provided, must be unique within the organization.
Update team membership by idUpdates specified properties (e.
Update team membership propertiesUpdates attributes like acceptance status, role, or impersonation settings for an existing team membership; only provided fields are changed, and this action cannot create or delete memberships.
Update user profile detailsUpdates the profile information and preferences for the authenticated user, affecting only the fields provided in the request.
Update user schedule in organizationModifies an existing schedule for a specified user within an organization by updating only the provided fields; the organization, user, and schedule must already exist.
Update webhook by idUpdates an existing cal.
Update webhook for organizationUse this action to modify settings of an already created webhook for an organization, such as its target url, active status, or the events that trigger it.

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

What is Tool Router?

Composio's Tool Router 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 Tool Router

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

How the Tool Router works

The Tool Router 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

Prerequisites

Before starting, make sure you have:
  • Python 3.9 or higher
  • A Composio account with an active API key
  • Basic familiarity with Python and async programming

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.

Install dependencies

bash
pip install composio pydantic-ai python-dotenv

Install the required libraries.

What's happening:

  • composio connects your agent to external SaaS tools like Cal
  • pydantic-ai lets you create structured AI agents with tool support
  • python-dotenv loads your environment variables securely from a .env file

Set up environment variables

bash
COMPOSIO_API_KEY=your_composio_api_key_here
USER_ID=your_user_id_here
OPENAI_API_KEY=your_openai_api_key

Create a .env file in your project root.

What's happening:

  • COMPOSIO_API_KEY authenticates your agent to Composio's API
  • USER_ID associates your session with your account for secure tool access
  • OPENAI_API_KEY to access OpenAI LLMs

Import dependencies

python
import asyncio
import os
from dotenv import load_dotenv
from composio import Composio
from pydantic_ai import Agent
from pydantic_ai.mcp import MCPServerStreamableHTTP

load_dotenv()
What's happening:
  • We load environment variables and import required modules
  • Composio manages connections to Cal
  • MCPServerStreamableHTTP connects to the Cal MCP server endpoint
  • Agent from Pydantic AI lets you define and run the AI assistant

Create a Tool Router Session

python
async def main():
    api_key = os.getenv("COMPOSIO_API_KEY")
    user_id = os.getenv("USER_ID")
    if not api_key or not user_id:
        raise RuntimeError("Set COMPOSIO_API_KEY and USER_ID in your environment")

    # Create a Composio Tool Router session for Cal
    composio = Composio(api_key=api_key)
    session = composio.create(
        user_id=user_id,
        toolkits=["cal"],
    )
    url = session.mcp.url
    if not url:
        raise ValueError("Composio session did not return an MCP URL")
What's happening:
  • We're creating a Tool Router session that gives your agent access to Cal tools
  • The create method takes the user ID and specifies which toolkits should be available
  • The returned session.mcp.url is the MCP server URL that your agent will use

Initialize the Pydantic AI Agent

python
# Attach the MCP server to a Pydantic AI Agent
cal_mcp = MCPServerStreamableHTTP(url, headers={"x-api-key": COMPOSIO_API_KEY})
agent = Agent(
    "openai:gpt-5",
    toolsets=[cal_mcp],
    instructions=(
        "You are a Cal assistant. Use Cal tools to help users "
        "with their requests. Ask clarifying questions when needed."
    ),
)
What's happening:
  • The MCP client connects to the Cal endpoint
  • The agent uses GPT-5 to interpret user commands and perform Cal operations
  • The instructions field defines the agent's role and behavior

Build the chat interface

python
# Simple REPL with message history
history = []
print("Chat started! Type 'exit' or 'quit' to end.\n")
print("Try asking the agent to help you with Cal.\n")

while True:
    user_input = input("You: ").strip()
    if user_input.lower() in {"exit", "quit", "bye"}:
        print("\nGoodbye!")
        break
    if not user_input:
        continue

    print("\nAgent is thinking...\n", flush=True)

    async with agent.run_stream(user_input, message_history=history) as stream_result:
        collected_text = ""
        async for chunk in stream_result.stream_output():
            text_piece = None
            if isinstance(chunk, str):
                text_piece = chunk
            elif hasattr(chunk, "delta") and isinstance(chunk.delta, str):
                text_piece = chunk.delta
            elif hasattr(chunk, "text"):
                text_piece = chunk.text
            if text_piece:
                collected_text += text_piece
        result = stream_result

    print(f"Agent: {collected_text}\n")
    history = result.all_messages()
What's happening:
  • The agent reads input from the terminal and streams its response
  • Cal API calls happen automatically under the hood
  • The model keeps conversation history to maintain context across turns

Run the application

python
if __name__ == "__main__":
    asyncio.run(main())
What's happening:
  • The asyncio loop launches the agent and keeps it running until you exit

Complete Code

Here's the complete code to get you started with Cal and Pydantic AI:

import asyncio
import os
from dotenv import load_dotenv
from composio import Composio
from pydantic_ai import Agent
from pydantic_ai.mcp import MCPServerStreamableHTTP

load_dotenv()

async def main():
    api_key = os.getenv("COMPOSIO_API_KEY")
    user_id = os.getenv("USER_ID")
    if not api_key or not user_id:
        raise RuntimeError("Set COMPOSIO_API_KEY and USER_ID in your environment")

    # Create a Composio Tool Router session for Cal
    composio = Composio(api_key=api_key)
    session = composio.create(
        user_id=user_id,
        toolkits=["cal"],
    )
    url = session.mcp.url
    if not url:
        raise ValueError("Composio session did not return an MCP URL")

    # Attach the MCP server to a Pydantic AI Agent
    cal_mcp = MCPServerStreamableHTTP(url, headers={"x-api-key": COMPOSIO_API_KEY})
    agent = Agent(
        "openai:gpt-5",
        toolsets=[cal_mcp],
        instructions=(
            "You are a Cal assistant. Use Cal tools to help users "
            "with their requests. Ask clarifying questions when needed."
        ),
    )

    # Simple REPL with message history
    history = []
    print("Chat started! Type 'exit' or 'quit' to end.\n")
    print("Try asking the agent to help you with Cal.\n")

    while True:
        user_input = input("You: ").strip()
        if user_input.lower() in {"exit", "quit", "bye"}:
            print("\nGoodbye!")
            break
        if not user_input:
            continue

        print("\nAgent is thinking...\n", flush=True)

        async with agent.run_stream(user_input, message_history=history) as stream_result:
            collected_text = ""
            async for chunk in stream_result.stream_output():
                text_piece = None
                if isinstance(chunk, str):
                    text_piece = chunk
                elif hasattr(chunk, "delta") and isinstance(chunk.delta, str):
                    text_piece = chunk.delta
                elif hasattr(chunk, "text"):
                    text_piece = chunk.text
                if text_piece:
                    collected_text += text_piece
            result = stream_result

        print(f"Agent: {collected_text}\n")
        history = result.all_messages()

if __name__ == "__main__":
    asyncio.run(main())

Conclusion

You've built a Pydantic AI agent that can interact with Cal through Composio's Tool Router. With this setup, your agent can perform real Cal actions through natural language. You can extend this further by:
  • Adding other toolkits like Gmail, HubSpot, or Salesforce
  • Building a web-based chat interface around this agent
  • Using multiple MCP endpoints to enable cross-app workflows (for example, Gmail + Cal for workflow automation)
This architecture makes your AI agent "agent-native", able to securely use APIs in a unified, composable way without custom integrations.

How to build Cal MCP Agent with another framework

FAQ

What are the differences in Tool Router MCP and Cal MCP?

With a standalone Cal MCP server, the agents and LLMs can only access a fixed set of Cal tools tied to that server. However, with the Composio Tool Router, agents can dynamically load tools from Cal and many other apps based on the task at hand, all through a single MCP endpoint.

Can I use Tool Router MCP with Pydantic AI?

Yes, you can. Pydantic AI fully supports MCP integration. You get structured tool calling, message history handling, and model orchestration while Tool Router takes care of discovering and serving the right Cal tools.

Can I manage the permissions and scopes for Cal while using Tool Router?

Yes, absolutely. You can configure which Cal scopes and actions are allowed when connecting your account to Composio. You can also bring your own OAuth credentials or API configuration so you keep full control over what the agent can do.

How safe is my data with Composio Tool Router?

All sensitive data such as tokens, keys, and configuration is fully encrypted at rest and in transit. Composio is SOC 2 Type 2 compliant and follows strict security practices so your Cal data and credentials are handled as safely as possible.

Used by agents from

Context
Letta
glean
HubSpot
Agent.ai
Altera
DataStax
Entelligence
Rolai
Context
Letta
glean
HubSpot
Agent.ai
Altera
DataStax
Entelligence
Rolai
Context
Letta
glean
HubSpot
Agent.ai
Altera
DataStax
Entelligence
Rolai

Never worry about agent reliability

We handle tool reliability, observability, and security so you never have to second-guess an agent action.