Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.syncline.run/llms.txt

Use this file to discover all available pages before exploring further.

Overview

Use the Node.js MCP SDK to build custom AI agents that can schedule meetings, find availability, and manage calendars through the Syncline MCP server.

Prerequisites

1

Install Syncline MCP Server

npm install -g @kekwanulabs/syncline-mcp-server
2

Install MCP SDK

npm install @modelcontextprotocol/sdk
3

Get Platform API Key

Quick Start

Create a simple agent that finds meeting availability:
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";

async function main() {
  // Configure transport to Syncline MCP server
  const transport = new StdioClientTransport({
    command: "npx",
    args: ["-y", "@kekwanulabs/syncline-mcp-server"],
    env: {
      ...process.env,
      SYNCLINE_API_KEY: "sk_live_your_api_key_here"
    }
  });

  // Create client
  const client = new Client({
    name: "my-scheduling-agent",
    version: "1.0.0"
  }, {
    capabilities: {}
  });

  // Connect to server
  await client.connect(transport);

  // List available tools
  const { tools } = await client.listTools();
  console.log("Available tools:", tools.map(t => t.name));

  // Find mutual availability
  const result = await client.callTool({
    name: "find_mutual_availability",
    arguments: {
      attendees: ["alice@example.com", "bob@example.com"],
      duration_minutes: 30
    }
  });

  console.log("Available time slots:");
  console.log(result.content[0].text);

  // Close connection
  await client.close();
}

main().catch(console.error);
Run the script:
node agent.js
Output:
Available tools: [ 'find_mutual_availability', 'schedule_meeting', 'check_availability', 'update_preferences' ]
Available time slots:
{
  "slots": [
    {
      "start_time": "2025-01-22T14:00:00-08:00",
      "end_time": "2025-01-22T14:30:00-08:00",
      "score": 0.95,
      "quality": "excellent"
    },
    ...
  ]
}

Building a Meeting Scheduler Agent

Create an AI agent that can schedule meetings via natural language:
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";

class MeetingSchedulerAgent {
  constructor(apiKey) {
    this.apiKey = apiKey;
    this.client = null;
  }

  async connect() {
    const transport = new StdioClientTransport({
      command: "npx",
      args: ["-y", "@kekwanulabs/syncline-mcp-server"],
      env: {
        ...process.env,
        SYNCLINE_API_KEY: this.apiKey
      }
    });

    this.client = new Client({
      name: "meeting-scheduler",
      version: "1.0.0"
    }, {
      capabilities: {}
    });

    await this.client.connect(transport);
  }

  async disconnect() {
    if (this.client) {
      await this.client.close();
    }
  }

  async findAvailability(attendees, duration = 30) {
    const result = await this.client.callTool({
      name: "find_mutual_availability",
      arguments: {
        attendees,
        duration_minutes: duration
      }
    });

    return JSON.parse(result.content[0].text);
  }

  async scheduleMeeting(attendees, startTime, title, duration = 30) {
    const result = await this.client.callTool({
      name: "schedule_meeting",
      arguments: {
        attendees,
        start_time: startTime,
        title,
        duration_minutes: duration
      }
    });

    return JSON.parse(result.content[0].text);
  }

  async checkCalendar(email, daysAhead = 7) {
    const startDate = new Date();
    const endDate = new Date();
    endDate.setDate(endDate.getDate() + daysAhead);

    const result = await this.client.callTool({
      name: "check_availability",
      arguments: {
        email,
        start_date: startDate.toISOString(),
        end_date: endDate.toISOString()
      }
    });

    return JSON.parse(result.content[0].text);
  }

  async updatePreferences(email, preferences) {
    const result = await this.client.callTool({
      name: "update_preferences",
      arguments: {
        email,
        preferences
      }
    });

    return JSON.parse(result.content[0].text);
  }
}

async function main() {
  const agent = new MeetingSchedulerAgent("sk_live_your_api_key_here");

  try {
    await agent.connect();

    // Find when Alice and Bob are both free
    console.log("Finding availability...");
    const availability = await agent.findAvailability(
      ["alice@example.com", "bob@example.com"],
      30
    );

    // Get the best time slot
    const bestSlot = availability.slots[0];
    console.log(`\nBest time slot: ${bestSlot.start_time}`);
    console.log(`Quality score: ${bestSlot.score}`);

    // Schedule the meeting
    console.log("\nScheduling meeting...");
    const meeting = await agent.scheduleMeeting(
      ["alice@example.com", "bob@example.com"],
      bestSlot.start_time,
      "Product Demo",
      30
    );

    console.log("\n✓ Meeting scheduled!");
    console.log(`  Meeting ID: ${meeting.meeting_id}`);
    console.log(`  Google Meet: ${meeting.google_meet_link}`);
    console.log(`  Calendar: ${meeting.calendar_event_link}`);

  } finally {
    await agent.disconnect();
  }
}

main().catch(console.error);

Advanced Examples

Conversational Scheduling Bot

Build a bot that schedules meetings through natural language:
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
import readline from "readline";

class SchedulingBot {
  constructor(apiKey) {
    this.apiKey = apiKey;
    this.client = null;
  }

  async connect() {
    const transport = new StdioClientTransport({
      command: "npx",
      args: ["-y", "@kekwanulabs/syncline-mcp-server"],
      env: {
        ...process.env,
        SYNCLINE_API_KEY: this.apiKey
      }
    });

    this.client = new Client({
      name: "scheduling-bot",
      version: "1.0.0"
    }, {
      capabilities: {}
    });

    await this.client.connect(transport);
  }

  async disconnect() {
    if (this.client) {
      await this.client.close();
    }
  }

  parseIntent(message) {
    const lowerMessage = message.toLowerCase();

    // Extract emails
    const emailRegex = /[\w\.-]+@[\w\.-]+\.\w+/g;
    const emails = message.match(emailRegex) || [];

    // Detect intent
    if (lowerMessage.includes("schedule") || lowerMessage.includes("book")) {
      return { intent: "schedule", attendees: emails };
    } else if (lowerMessage.includes("available") || lowerMessage.includes("free")) {
      return { intent: "find_availability", attendees: emails };
    } else if (lowerMessage.includes("check") && lowerMessage.includes("calendar")) {
      return { intent: "check_calendar", email: emails[0] };
    } else {
      return { intent: "unknown" };
    }
  }

  async handleMessage(message) {
    const intentData = this.parseIntent(message);

    try {
      if (intentData.intent === "find_availability") {
        const result = await this.client.callTool({
          name: "find_mutual_availability",
          arguments: {
            attendees: intentData.attendees,
            duration_minutes: 30
          }
        });

        return this.formatAvailabilityResponse(JSON.parse(result.content[0].text));

      } else if (intentData.intent === "schedule") {
        // First find availability
        const availResult = await this.client.callTool({
          name: "find_mutual_availability",
          arguments: {
            attendees: intentData.attendees,
            duration_minutes: 30
          }
        });

        const availability = JSON.parse(availResult.content[0].text);
        const bestSlot = availability.slots[0];

        // Schedule the meeting
        const meetingResult = await this.client.callTool({
          name: "schedule_meeting",
          arguments: {
            attendees: intentData.attendees,
            start_time: bestSlot.start_time,
            title: "Meeting",
            duration_minutes: 30
          }
        });

        return this.formatMeetingResponse(JSON.parse(meetingResult.content[0].text));

      } else if (intentData.intent === "check_calendar") {
        const startDate = new Date();
        const endDate = new Date();
        endDate.setDate(endDate.getDate() + 7);

        const result = await this.client.callTool({
          name: "check_availability",
          arguments: {
            email: intentData.email,
            start_date: startDate.toISOString(),
            end_date: endDate.toISOString()
          }
        });

        return this.formatCalendarResponse(JSON.parse(result.content[0].text));

      } else {
        return "I can help you schedule meetings, find availability, or check calendars. Try asking: 'When are alice@example.com and bob@example.com free?'";
      }
    } catch (error) {
      return `Error: ${error.message}`;
    }
  }

  formatAvailabilityResponse(availability) {
    let response = "Here are the available time slots:\n\n";
    availability.slots.slice(0, 5).forEach((slot, i) => {
      response += `${i + 1}. ${slot.start_time} (quality: ${slot.quality})\n`;
    });
    return response;
  }

  formatMeetingResponse(meeting) {
    return `✓ Meeting scheduled!\nGoogle Meet: ${meeting.google_meet_link}\nCalendar: ${meeting.calendar_event_link}`;
  }

  formatCalendarResponse(calendar) {
    const freeSlots = calendar.free_slots?.length || 0;
    const busySlots = calendar.busy_slots?.length || 0;
    return `Calendar checked. Free slots: ${freeSlots}, Busy slots: ${busySlots}`;
  }
}

async function chatLoop() {
  const bot = new SchedulingBot("sk_live_your_api_key_here");
  await bot.connect();

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

  console.log("Scheduling Bot Ready! Ask me to schedule meetings or find availability.");
  console.log("Type 'quit' to exit.\n");

  const askQuestion = () => {
    rl.question("You: ", async (input) => {
      if (input.toLowerCase() === "quit" || input.toLowerCase() === "exit") {
        console.log("Goodbye!");
        await bot.disconnect();
        rl.close();
        return;
      }

      const response = await bot.handleMessage(input);
      console.log(`Bot: ${response}\n`);

      askQuestion();
    });
  };

  askQuestion();
}

chatLoop().catch(console.error);
Example conversation:
Scheduling Bot Ready! Ask me to schedule meetings or find availability.
Type 'quit' to exit.

You: When are alice@example.com and bob@example.com free?
Bot: Here are the available time slots:

1. 2025-01-22T14:00:00-08:00 (quality: excellent)
2. 2025-01-22T15:00:00-08:00 (quality: good)
3. 2025-01-23T10:00:00-08:00 (quality: good)
4. 2025-01-23T14:00:00-08:00 (quality: excellent)
5. 2025-01-24T09:00:00-08:00 (quality: fair)

You: Schedule a meeting with alice@example.com and bob@example.com
Bot: ✓ Meeting scheduled!
Google Meet: https://meet.google.com/abc-defg-hij
Calendar: https://calendar.google.com/calendar/event?eid=...

Error Handling

Handle errors gracefully:
async function safeScheduleMeeting(agent, attendees, startTime, title) {
  try {
    const meeting = await agent.scheduleMeeting(attendees, startTime, title);
    return { success: true, meeting };

  } catch (error) {
    console.error(`Error scheduling meeting: ${error.message}`);
    return { success: false, error: error.message };
  }
}

// Usage
const agent = new MeetingSchedulerAgent("sk_live_your_key");
await agent.connect();

const result = await safeScheduleMeeting(
  agent,
  ["alice@example.com", "bob@example.com"],
  "2025-01-22T14:00:00-08:00",
  "Product Demo"
);

if (result.success) {
  console.log(`✓ Meeting scheduled: ${result.meeting.google_meet_link}`);
} else {
  console.log(`✗ Failed to schedule: ${result.error}`);
}

await agent.disconnect();

Best Practices

Use Environment Variables

Store API keys securely:
import dotenv from "dotenv";
dotenv.config();

const apiKey = process.env.SYNCLINE_API_KEY;
if (!apiKey) {
  throw new Error("SYNCLINE_API_KEY environment variable not set");
}

const agent = new MeetingSchedulerAgent(apiKey);

Implement Retry Logic

Handle transient failures:
async function scheduleWithRetry(agent, attendees, startTime, title, maxRetries = 3) {
  let lastError;

  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      return await agent.scheduleMeeting(attendees, startTime, title);
    } catch (error) {
      lastError = error;
      console.log(`Attempt ${attempt} failed: ${error.message}`);

      if (attempt < maxRetries) {
        // Exponential backoff
        const delay = Math.pow(2, attempt) * 1000;
        await new Promise(resolve => setTimeout(resolve, delay));
      }
    }
  }

  throw new Error(`Failed after ${maxRetries} attempts: ${lastError.message}`);
}

// Usage
try {
  const meeting = await scheduleWithRetry(
    agent,
    ["alice@example.com", "bob@example.com"],
    "2025-01-22T14:00:00-08:00",
    "Product Demo"
  );
  console.log(`✓ Scheduled: ${meeting.meeting_id}`);
} catch (error) {
  console.error(`✗ Failed: ${error.message}`);
}

Validate Input

Validate user input before calling tools:
function validateEmail(email) {
  const regex = /^[\w\.-]+@[\w\.-]+\.\w+$/;
  return regex.test(email);
}

function validateAttendees(emails) {
  const validated = emails.filter(email => {
    if (!validateEmail(email)) {
      console.warn(`Invalid email: ${email}`);
      return false;
    }
    return true;
  });

  if (validated.length === 0) {
    throw new Error("No valid email addresses provided");
  }

  return validated;
}

// Usage
const userEmails = ["alice@example.com", "not-an-email", "bob@example.com"];
const validEmails = validateAttendees(userEmails);
// Returns: ["alice@example.com", "bob@example.com"]

Graceful Shutdown

Ensure proper cleanup:
class MeetingSchedulerAgent {
  // ... existing code ...

  async scheduleWithCleanup(attendees, startTime, title) {
    try {
      await this.connect();
      const meeting = await this.scheduleMeeting(attendees, startTime, title);
      return meeting;
    } finally {
      await this.disconnect();
    }
  }
}

// Usage with proper error handling
async function main() {
  const agent = new MeetingSchedulerAgent(process.env.SYNCLINE_API_KEY);

  try {
    const meeting = await agent.scheduleWithCleanup(
      ["alice@example.com", "bob@example.com"],
      "2025-01-22T14:00:00-08:00",
      "Product Demo"
    );

    console.log(`✓ Meeting scheduled: ${meeting.meeting_id}`);

  } catch (error) {
    console.error(`✗ Error: ${error.message}`);
    process.exit(1);
  }
}

main();

Testing

Write tests for your agent:
import { jest } from "@jest/globals";

describe("MeetingSchedulerAgent", () => {
  let agent;
  let mockClient;

  beforeEach(() => {
    // Mock the MCP client
    mockClient = {
      connect: jest.fn(),
      close: jest.fn(),
      callTool: jest.fn()
    };

    agent = new MeetingSchedulerAgent("test_key");
    agent.client = mockClient;
  });

  test("findAvailability returns time slots", async () => {
    // Mock response
    mockClient.callTool.mockResolvedValue({
      content: [{
        text: JSON.stringify({
          slots: [
            { start_time: "2025-01-22T14:00:00Z", score: 0.95 }
          ]
        })
      }]
    });

    // Test
    const result = await agent.findAvailability(
      ["alice@example.com", "bob@example.com"],
      30
    );

    // Assert
    expect(result.slots).toHaveLength(1);
    expect(result.slots[0].score).toBe(0.95);
  });

  test("scheduleMeeting creates meeting", async () => {
    // Mock response
    mockClient.callTool.mockResolvedValue({
      content: [{
        text: JSON.stringify({
          meeting_id: "mtg_123",
          google_meet_link: "https://meet.google.com/abc-defg-hij"
        })
      }]
    });

    // Test
    const meeting = await agent.scheduleMeeting(
      ["alice@example.com", "bob@example.com"],
      "2025-01-22T14:00:00Z",
      "Product Demo",
      30
    );

    // Assert
    expect(meeting.meeting_id).toBe("mtg_123");
    expect(meeting.google_meet_link).toContain("meet.google.com");
  });
});
Run tests:
npm test

Python Client

Build agents with Python

Protocol Spec

MCP protocol details

API Reference

REST API documentation