#
tokens: 7077/50000 12/12 files
lines: off (toggle) GitHub
raw markdown copy
# Directory Structure

```
├── .env.example
├── .gitignore
├── .gitlab-ci.yml
├── Dockerfile
├── package-lock.json
├── package.json
├── README.MD
├── scripts
│   └── build.js
├── src
│   ├── api
│   │   ├── schemas.ts
│   │   ├── tldv-api.ts
│   │   └── types.ts
│   └── index.ts
└── tsconfig.json
```

# Files

--------------------------------------------------------------------------------
/.env.example:
--------------------------------------------------------------------------------

```
TLDV_API_KEY=dummy
```

--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------

```
node_modules
dist
DS_Store
.env

```

--------------------------------------------------------------------------------
/.gitlab-ci.yml:
--------------------------------------------------------------------------------

```yaml
stages:
  - build
  - release
  - deploy

variables:
  DOCKER_REGISTRY: $CI_REGISTRY
  DOCKER_IMAGE: $CI_REGISTRY_IMAGE

release-version:
  stage: release
  image: node:22
  script:
    - git config --global user.email "${GIT_CI_EMAIL}"
    - git config --global user.name "${GIT_CI_USER}"
    - npm i standard-version --location=global
    - npm run release -- --no-verify
    - git remote set-url --push origin https://$GIT_CI_USER:[email protected]/$CI_PROJECT_PATH.git
    - git push --follow-tags origin HEAD:$CI_COMMIT_REF_NAME
  rules:
    - if: $CI_COMMIT_BRANCH == "main" && $CI_COMMIT_TITLE !~ /^chore\(release\).*\ \[release\]$/

build:
  stage: build
  image: node:22
  script:
    - npm ci
    - npm run build
  artifacts:
    paths:
      - dist/

publish-npm:
  stage: deploy
  image: node:22
  script:
    - npm config set @tldx:registry=https://gitlab.com/api/v4/packages/npm/
    - npm config set -- '//gitlab.com/api/v4/packages/npm/:_authToken'="${CI_JOB_TOKEN}"
    - npm config set -- '//gitlab.com/api/v4/projects/${CI_PROJECT_ID}/packages/npm/:_authToken'="${CI_JOB_TOKEN}"
    - npm ci
    - npm run build
    - npm publish
    - echo "-- npm publish completed successfully"
  rules:
    - if: $CI_COMMIT_TAG && $CI_COMMIT_TAG =~ /^v?[0-9]+\.[0-9]+\.[0-9]+$/

publish-docker:
  stage: deploy
  image: docker:latest
  services:
    - docker:dind
  before_script:
    - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
  script:
    - docker build -t $DOCKER_IMAGE:$CI_COMMIT_TAG .
    - docker tag $DOCKER_IMAGE:$CI_COMMIT_TAG $DOCKER_IMAGE:latest
    - docker push $DOCKER_IMAGE:$CI_COMMIT_TAG
    - docker push $DOCKER_IMAGE:latest
    - echo "-- docker publish completed successfully"
  rules:
    - if: $CI_COMMIT_TAG && $CI_COMMIT_TAG =~ /^v?[0-9]+\.[0-9]+\.[0-9]+$/


```

--------------------------------------------------------------------------------
/README.MD:
--------------------------------------------------------------------------------

```markdown
# Official MCP Server for tl;dv API

🚀 **The First and Only MCP Server for Google Meet, Zoom, and Microsoft Teams Integration**

This project provides a Model Context Protocol (MCP) server enabling seamless interaction with the [tl;dv](https://tldv.io/) API. As the pioneering MCP solution for video conferencing platforms, it unlocks the power of tl;dv's meeting intelligence across Google Meet, Zoom, and Microsoft Teams through a standardized interface. This integration allows AI models and MCP clients to access, analyze, and derive insights from your meetings across all major platforms in one unified way.

## Features

*   **List Meetings:** Retrieve meetings based on filters (query, date range, participation status, type) across all supported platforms.
*   **Get Meeting Metadata:** Fetch detailed information for a specific meeting by ID, regardless of the platform it was hosted on.
*   **Get Transcript:** Obtain the transcript for any meeting ID, with consistent formatting across all platforms.
*   **Get Highlights:** Retrieve AI-generated highlights for meetings from any supported platform.
*   **Import Meeting (Coming Soon):** Functionality to import meetings via URL from any supported platform.

## Prerequisites

*   **tl;dv Account:** A Business or Enterprise tl;dv account is required.
*   **tl;dv API Key:** You need an API key, which can be requested from your tl;dv settings: [https://tldv.io/app/settings/personal-settings/api-keys](https://tldv.io/app/settings/personal-settings/api-keys).
*   **Node.js & npm (for Node installation):** If installing via Node.js, ensure Node.js and npm are installed.
*   **Docker (for Docker installation):** If installing via Docker, ensure Docker is installed and running.

## Installation and Configuration

You can run this MCP server using either Docker or Node.js. Configure your MCP client (e.g., Claude Desktop, Cursor) to connect to the server.

### Using Docker

Go in the repo. 

1.  **Build the Docker image:**
    ```bash
    docker build -t tldv-mcp-server .
    ```

2.  **Configure your MCP Client:**
    Update your MCP client's configuration file (e.g., `claude_desktop_config.json`). The exact location and format may vary depending on the client.

    ```json
    {
        "mcpServers": {
          "tldv": {
            "command": "docker",
            "args": [
                "run",

                "--rm",        
                "--init",      
                "-e",          
                "TLDV_API_KEY=<your-tldv-api-key>",
                "tldv-mcp-server"
            ],
          }
        }
      }
    ```
    Replace `<your-tldv-api-key>` with your actual tl;dv API key.

### Using Node.js

1.  **Install dependencies:**
    ```bash
    npm install
    ```

2.  **Build the server:**
    ```bash
    npm run build
    ```
    This command creates a `dist` folder containing the compiled server code (`index.js`).

3.  **Configure your MCP Client:**
    Update your MCP client's configuration file.

    ```json
    {
      "mcpServers": {
        "tldv": {
          "command": "node",
          "args": ["/absolute/path/to/tldv-mcp-server/dist/index.js"],
          "env": {
            "TLDV_API_KEY": "your_tldv_api_key"
          }
        }
      }
    }
    ```
    Replace `/absolute/path/to/tldv-mcp-server/dist/index.js` with the correct absolute path to the built server file and `your_tldv_api_key` with your tl;dv API key.

*Refer to your specific MCP client's documentation for detailed setup instructions (e.g., [Claude Tools](https://modelcontextprotocol.io/quickstart/user)).*

*Disclaimer* Once you are updating this config file, you will need to kill your MCP client and restart it for the changes to be effective. 

## Development

1.  **Install dependencies:**
    ```bash
    npm install
    ```

2.  **Set up Environment Variables:**
    Copy the example environment file:
    ```bash
    cp .env.example .env
    ```
    Edit the `.env` file and add your `TLDV_API_KEY`. Other variables can be configured as needed.


3.  **Run in development mode:**
    This command starts the server with auto-reloading on file changes:
    ```bash
    npm run watch
    ```

4.  **Update client for local development:**
    Configure your MCP client to use the local development server path (typically `/path/to/your/project/dist/index.js`). Ensure the `TLDV_API_KEY` is accessible, either through the client's `env` configuration or loaded via the `.env` file by the server process.

5. **Reload your MCP Client**
    Since you are running the watch command, it will recompiled a new version. Reloading your Client (e.g Claud Desktop App), your changes will be effective. 

## Debugging

*   **Console Logs:** Check the console output when running `npm run dev` for detailed logs. The server uses the `debug` library; you can control log levels via environment variables (e.g., `DEBUG=tldv-mcp:*`).
*   **Node.js Debugger:** Utilize standard Node.js debugging tools (e.g., Chrome DevTools Inspector, VS Code debugger) by launching the server process with the appropriate flags (e.g., `node --inspect dist/index.js`).
*   **MCP Client Logs:** Check the logs provided by your MCP client, which might show the requests sent and responses received from this server.

## Learn More

*   [tl;dv Developer API Documentation](https://doc.tldv.io/)
*   [Model Context Protocol (MCP) Specification](https://modelcontextprotocol.io/introduction)
```

--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------

```json
{
  "compilerOptions": {
    "baseUrl": "./",
    "sourceMap": false,
    "inlineSources": false,
    "removeComments": false,
    "target": "ES2022",
    "module": "NodeNext",
    "declaration": true,
    "outDir": "./dist",
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "esModuleInterop": true,
    "resolveJsonModule": true,
    "paths": {
      "~/*": ["src/*"]
    }
  }
}

```

--------------------------------------------------------------------------------
/scripts/build.js:
--------------------------------------------------------------------------------

```javascript
#!/usr/bin/env node

const { build } = require('esbuild');

async function bundle() {
  try {
    await build({
      entryPoints: ['src/index.ts'],
      bundle: true,
      platform: 'node',
      target: 'node22',
      outfile: 'dist/index.js',
      sourcemap: true,
      minify: true,
      format: 'cjs',
      banner: {
        js: '#!/usr/bin/env node',
      },
      // No external packages, include everything in the bundle
      external: [],
    });
    console.log('Bundle complete! Output: dist/index.js');
  } catch (error) {
    console.error('Bundle failed:', error);
    process.exit(1);
  }
}

bundle(); 
```

--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------

```json
{
    "name": "tldv-mcp",
    "version": "1.0.0",
    "description": "TLDR API MCP Server",
    "main": "dist/index.js",
    "types": "dist/index.d.ts",
    "scripts": {
        "start": "node dist/index.js",
        "dev": "ts-node-dev --respawn --transpile-only src/index.ts",
        "build": "rm -rf dist && npm run bundle && chmod +x dist/*.js",
        "bundle": "node scripts/build.js",
        "prepare": "npm run build",
        "watch": "tsc --watch",
        "release": "standard-version -m \"chore(release): {{currentTag}} [release]\""
    },
    "dependencies": {
        "@modelcontextprotocol/sdk": "^1.8.0",
        "axios": "^1.8.4",
        "dotenv": "^16.3.1",
        "fastify": "^4.26.1",
        "zod": "^3.22.4"
    },
    "devDependencies": {
        "@types/node": "^20.11.19",
        "esbuild": "^0.25.2",
        "esbuild-node-externals": "^1.18.0",
        "ts-node-dev": "^2.0.0",
        "typescript": "^5.3.3"
    },
    "volta": {
        "node": "22.11.0"
    }
}

```

--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------

```dockerfile
# Stage 1: Build the application
FROM node:22-slim AS builder

WORKDIR /app

# Copy package files and install dependencies
COPY package.json /app
COPY package-lock.json /app
COPY tsconfig.json /app

COPY src /app/src
# Use npm ci for cleaner installs, ensure package-lock.json exists
RUN npm install

# Build the TypeScript code
RUN npm run build

# Prune development dependencies
RUN npm prune --production

# Stage 2: Create the final production image
FROM node:22-slim

WORKDIR /app

# Create a non-root user
RUN addgroup --system appgroup && adduser --system --ingroup appgroup appuser

# Copy built code and production dependencies from the builder stage
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY package.json ./

# Set ownership to the non-root user
RUN chown -R appuser:appgroup /app

# Switch to the non-root user
USER appuser

# Command to run the server
# The API key will be passed via environment variable at runtime
CMD ["node", "dist/index.js"]
```

--------------------------------------------------------------------------------
/src/api/types.ts:
--------------------------------------------------------------------------------

```typescript
export interface TldvConfig {
  apiKey: string;
  baseUrl?: string;
}

export interface TldvResponse<T> {
  data: T;
  error?: string;
}

export interface User {
  name: string;
  email: string;
}

export interface Template {
  id: string;
  label: string;
}

export interface Meeting {
  id: string;
  name: string;
  happenedAt: string;
  url: string;
  organizer: User;
  invitees: User[];
  template: Template;
}

export interface Sentence {
  speaker: string;
  text: string;
  startTime: number;
  endTime: number;
}

export interface HighlightTopic {
  title: string;
  summary: string;
}

export interface Highlight {
  text: string;
  startTime: number;
  source: 'manual' | 'auto';
  topic: HighlightTopic;
}

export interface GetTranscriptResponse {
  id: string;
  meetingId: string;
  data: Sentence[];
}

export interface GetHighlightsResponse {
  meetingId: string;
  data: Highlight[];
}

export interface ImportMeetingParams {
  name: string;
  url: string;
  happenedAt?: string;
  dryRun?: boolean;
}

export interface ImportMeetingResponse {
  success: boolean;
  jobId: string;
  message: string;
}

export interface GetMeetingsParams {
  query?: string;
  page?: number;
  limit?: number;
  from?: string;
  to?: string;
  onlyParticipated?: boolean;
  meetingType?: 'internal' | 'external';
}

export interface GetMeetingsResponse {
  page: number;
  pages: number;
  total: number;
  pageSize: number;
  results: Meeting[];
}

export interface HealthResponse {
  status: string;
}

export interface ValidationError {
  property: string;
  constraints: Record<string, string>;
}

export interface ValidationErrorResponse {
  message: string;
  error: {
    message: string;
    errors: ValidationError[];
  }[];
}

export interface BasicErrorResponse {
  name: string;
  message: string;
} 
```

--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------

```typescript
import { GetMeetingsParamsSchema } from "./api/schemas";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { TldvApi } from "./api/tldv-api";
import dotenv from "dotenv";
import z from "zod";

dotenv.config();

async function main() {
  console.info({ name: "Initializing TLDV API..." });
  const tldvApi = new TldvApi({
    apiKey: process.env.TLDV_API_KEY,
  });
  
  console.info({ name: "Starting MCP server..." });
  
  const tools = {
    "get-meeting-metadata": {
      name: "get-meeting-metadata",
      description: "Get a meeting by its ID. The meeting ID is a unique identifier for a meeting. It will return the meeting metadata, including the name, the date, the organizer, participants and more.",
      inputSchema: z.object({ id: z.string() }),
    },
    "get-transcript": {
      name: "get-transcript",
      description: "Get transcript by meeting ID. The transcript is a list of messages exchanged between the participants in the meeting. It's time-stamped and contains the speaker and the message",
      inputSchema: z.object({ meetingId: z.string() }),
    },
    "list-meetings": {
      name: "list-meetings",
      description: "List all meetings based on the filters provided. You can filter by date, status, and more. Those meetings are the sames you have access to in the TLDV app.",
      inputSchema: GetMeetingsParamsSchema,
    },
    "get-highlights": {
      name: "get-highlights",
      description: "Allows you to get highlights from a meeting by providing a meeting ID.",
      inputSchema: z.object({ meetingId: z.string() }),
    },
  };
  
  const server = new McpServer({
    name: "tldv-server",
    version: "1.0.0",
  }, {
    capabilities: {
      logging: {
        level: "debug",
      },
      prompts: {},
      tools: tools,
      resources: {},
    },
    instructions: "You are a helpful assistant that can help with TLDV API requests.",
  });

  //Register tool handlers
  server.tool(
    tools["get-meeting-metadata"].name,
    tools["get-meeting-metadata"].description,
    tools["get-meeting-metadata"].inputSchema.shape,
    async ({ id }) => {
      const meeting = await tldvApi.getMeeting(id);
      return {
        content: [{ type: "text", text: JSON.stringify(meeting) }]
      };
    }
  );

  server.tool(
    tools["get-transcript"].name,
    tools["get-transcript"].description,
    tools["get-transcript"].inputSchema.shape,
    async ({ meetingId }) => {
      const transcript = await tldvApi.getTranscript(meetingId);
      return {
        content: [{ type: "text", text: JSON.stringify(transcript) }]
      };
    }
  );

  server.tool(
    tools["list-meetings"].name,
    tools["list-meetings"].description,
    tools["list-meetings"].inputSchema.shape,
    async (input) => {
      const meetings = await tldvApi.getMeetings(input);
      return {
        content: [{ type: "text", text: JSON.stringify(meetings) }]
      };
    }
  );

  server.tool(
    tools["get-highlights"].name,
    tools["get-highlights"].description,
    tools["get-highlights"].inputSchema.shape,
    async ({ meetingId }) => {
      const highlights = await tldvApi.getHighlights(meetingId);
      return {
        content: [{ type: "text", text: JSON.stringify(highlights) }]
      };
    }
  );

  console.info({ message: "Initializing StdioServerTransport..." });
  const transport = new StdioServerTransport();

  console.info({ message: "Connecting to MCP server..." });
  await server.connect(transport);
}

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

```

--------------------------------------------------------------------------------
/src/api/schemas.ts:
--------------------------------------------------------------------------------

```typescript
import { z } from 'zod';

/**
 * Configuration for the TLDR API client
 */
export const TldvConfigSchema = z.object({
  apiKey: z.string().min(1, 'API key is required')
});

export type TldvConfig = z.infer<typeof TldvConfigSchema>;

/**
 * Generic response wrapper for all API calls
 */
export const TldvResponseSchema = <T extends z.ZodType>(dataSchema: T) => dataSchema.nullable()

export type TldvResponse<T> = z.infer<ReturnType<typeof TldvResponseSchema<z.ZodType<T>>>>;

/**
 * User information schema
 */
export const UserSchema = z.object({
  name: z.string(),
  email: z.string().email(),
});

export type User = z.infer<typeof UserSchema>;

/**
 * Template information schema
 */
export const TemplateSchema = z.object({
  id: z.string(),
  label: z.string(),
});

export type Template = z.infer<typeof TemplateSchema>;

/**
 * Meeting information schema
 */
export const MeetingSchema = z.object({
  id: z.string(),
  name: z.string(),
  happenedAt: z.string().datetime(),
  url: z.string().url(),
  organizer: UserSchema,
  invitees: z.array(UserSchema),
  template: TemplateSchema,
});

export type Meeting = z.infer<typeof MeetingSchema>;

/**
 * Sentence information schema for transcripts
 */
export const SentenceSchema = z.object({
  speaker: z.string(),
  text: z.string(),
  startTime: z.number().int().nonnegative(),
  endTime: z.number().int().nonnegative(),
});

export type Sentence = z.infer<typeof SentenceSchema>;

/**
 * Highlight topic information schema
 */
export const HighlightTopicSchema = z.object({
  title: z.string(),
  summary: z.string(),
});

export type HighlightTopic = z.infer<typeof HighlightTopicSchema>;

/**
 * Highlight information schema
 */
export const HighlightSchema = z.object({
  text: z.string(),
  startTime: z.number().int().nonnegative(),
  source: z.enum(['manual', 'auto']),
  topic: HighlightTopicSchema,
});

export type Highlight = z.infer<typeof HighlightSchema>;

/**
 * Transcript response schema
 */
export const GetTranscriptResponseSchema = z.object({
  id: z.string(),
  meetingId: z.string(),
  data: z.array(SentenceSchema),
});

export type GetTranscriptResponse = z.infer<typeof GetTranscriptResponseSchema>;

/**
 * Highlights response schema
 */
export const GetHighlightsResponseSchema = z.object({
  meetingId: z.string(),
  data: z.array(HighlightSchema),
});

export type GetHighlightsResponse = z.infer<typeof GetHighlightsResponseSchema>;

/**
 * Import meeting response schema
 */
export const ImportMeetingResponseSchema = z.object({
  success: z.boolean(),
  jobId: z.string(),
  message: z.string(),
});

export type ImportMeetingResponse = z.infer<typeof ImportMeetingResponseSchema>;

/**
 * Get meetings parameters schema
 */
export const GetMeetingsParamsSchema = z.object({
  query: z.string().optional(), // search query
  page: z.number().int().positive().optional(), // page number      
  limit: z.number().int().positive().optional().default(50), // number of results per page
  from: z.string().datetime().optional(), // start date
  to: z.string().datetime().optional(), // end date
  onlyParticipated: z.boolean().optional(), // only return meetings where the user participated

  // meeting type. internal is default. 
  // This is used to filter meetings by type. Type is determined by comparing the organizer's email with the invitees' emails. 
  // If the organizer's domain is different from at least one of the invitees' domains, the meeting is external.
  // Otherwise, the meeting is internal.  
  meetingType: z.enum(['internal', 'external']).optional(), 
});

export type GetMeetingsParams = z.infer<typeof GetMeetingsParamsSchema>;

/**
 * Get meetings response schema
 */
export const GetMeetingsResponseSchema = z.object({
  page: z.number().int().nonnegative(), // current page number
  pages: z.number().int().positive(), // total number of pages  
  total: z.number().int().nonnegative(), // total number of results
  pageSize: z.number().int().positive(), // number of results per page
  results: z.array(MeetingSchema), // array of meetings
});

export type GetMeetingsResponse = z.infer<typeof GetMeetingsResponseSchema>;

/**
 * Health check response schema
 */
export const HealthResponseSchema = z.object({
  status: z.string(),
});

export type HealthResponse = z.infer<typeof HealthResponseSchema>;

/**
 * Validation error schema
 */
export const ValidationErrorSchema = z.object({
  property: z.string(),
  constraints: z.record(z.string()),
});

export type ValidationError = z.infer<typeof ValidationErrorSchema>;

/**
 * Validation error response schema
 */
export const ValidationErrorResponseSchema = z.object({
  message: z.string(),
  error: z.array(
    z.object({
      message: z.string(),
      errors: z.array(ValidationErrorSchema),
    })
  ),
});

export type ValidationErrorResponse = z.infer<typeof ValidationErrorResponseSchema>;

/**
 * Basic error response schema
 */
export const BasicErrorResponseSchema = z.object({
  name: z.string(),
  message: z.string(),
});

export type BasicErrorResponse = z.infer<typeof BasicErrorResponseSchema>; 
```

--------------------------------------------------------------------------------
/src/api/tldv-api.ts:
--------------------------------------------------------------------------------

```typescript
import {
  GetHighlightsResponse,
  GetMeetingsParams,
  GetMeetingsParamsSchema,
  GetMeetingsResponse,
  GetTranscriptResponse,
  HealthResponse,
  Meeting,
  TldvConfig,
  TldvConfigSchema,
} from './schemas';

import { TldvResponse } from './types';
import axios from 'axios';

const BASE_URL = 'https://pasta.tldv.io/v1alpha1';

const MAX_RETRIES = 3;
const RETRY_DELAY = 1_000; 
const MAX_RETRY_DELAY = 2_000;

/**
 * TLDV API Client
 * 
 * This class provides a type-safe interface to interact with the TLDV API.
 * It handles authentication, request formatting, and response validation.
 * 
 * @example
 * ```typescript
 * const api = new TldvApi({
 *   apiKey: 'your-api-key'
 * });
 * 
 * ```typescript
 * const meeting = await tldvApi.getMeeting(id);
 * ```
 */
export class TldvApi {
  private apiKey: string;
  private baseUrl: string;
  private headers: any;

  /**
   * Creates a new instance of the TLDV API client
   * @param config - Configuration object containing API key and optional base URL
   * @throws {Error} If the configuration is invalid
   */
  constructor(config: TldvConfig) {
    const validatedConfig = TldvConfigSchema.parse(config);
    this.apiKey = validatedConfig.apiKey;
    this.baseUrl = BASE_URL;
    this.headers = {
      'x-api-key': this.apiKey,
      'Content-Type': 'application/json',
    };
  }

  /**
   * Makes a request to the TLDV API
   * @param endpoint - The API endpoint to call
   * @param options - Request options including method, body, etc.
   * @returns A promise that resolves to the validated API response
   */
  private async request<T>(
    endpoint: string,
    options: RequestInit = {},
    retryCount = 0,
    maxRetries = MAX_RETRIES
  ): Promise<TldvResponse<T>> {
    try {
      const response = await axios(`${this.baseUrl}${endpoint}`, {
        ...options,
        headers: {
          ...this.headers,
        },
      });

      if (response.status > 200) {
        throw new Error(response.data.message || 'API request failed');
      }

      // Ensure the response is properly formatted
      const responseData = response.data;
      
      // If the response is already in the expected format, return it
      if (responseData && typeof responseData === 'object' && 'data' in responseData) {
        return responseData as TldvResponse<T>;
      }
      
      // Otherwise, wrap it in the expected format
      return {
        data: responseData as T,
        error: undefined,
      };
    } catch (error) {
      // Determine if we should retry based on the error
      const shouldRetry = 
        retryCount < maxRetries && 
        (
          // Network errors
          (error instanceof Error && error.message.includes('Network Error')) ||
          // 5xx server errors
          (axios.isAxiosError(error) && error.response && error.response.status >= 500) ||
          // Rate limiting
          (axios.isAxiosError(error) && error.response && error.response.status === 429)
        );
      
      if (shouldRetry) {
        // Calculate exponential backoff delay: 2^retryCount * 1000ms (1s, 2s, 4s, etc.)
        const delay = Math.min(RETRY_DELAY * 2 ** retryCount, MAX_RETRY_DELAY);
        
        // Wait before retrying
        await new Promise(resolve => setTimeout(resolve, delay));
        
        // Retry the request
        return this.request<T>(endpoint, options, retryCount + 1, maxRetries);
      }
      
      return {
        data: null as T,
        error: error instanceof Error ? error.message : 'Unknown error occurred',
      };
    }
  }


  /**
   * Retrieves a meeting by its ID
   * 
   * @param meetingId - The unique identifier of the meeting
   * @returns A promise that resolves to the meeting details
   * 
   * @example
   * ```typescript
   * const meeting = await api.getMeeting('meeting-123');
   * ```
   */
  async getMeeting(meetingId: string): Promise<TldvResponse<Meeting>> {
    return this.request<Meeting>(`/meetings/${meetingId}`);
  }

  /**
   * Retrieves a list of meetings with optional filtering
   * 
   * @param params - Optional parameters for filtering and pagination
   * @returns A promise that resolves to the paginated list of meetings
   * 
   * @example
   * ```typescript
   * const meetings = await api.getMeetings({
   *   query: 'team sync',
   *   page: 1,
   *   limit: 10,
   *   from: '2024-01-01T00:00:00Z',
   *   to: '2024-12-31T23:59:59Z',
   *   onlyParticipated: true,
   *   meetingType: 'internal'
   * });
   * ```
   */
  async getMeetings(params: GetMeetingsParams = {}): Promise<TldvResponse<GetMeetingsResponse>> {
    const validatedParams = GetMeetingsParamsSchema.parse(params);
    const queryParams = new URLSearchParams();
    
    if (validatedParams.query) queryParams.append('query', validatedParams.query);
    if (validatedParams.page) queryParams.append('page', validatedParams.page.toString());
    if (validatedParams.limit) queryParams.append('limit', validatedParams.limit.toString());
    if (validatedParams.from) queryParams.append('from', validatedParams.from);
    if (validatedParams.to) queryParams.append('to', validatedParams.to);
    if (validatedParams.onlyParticipated !== undefined) queryParams.append('onlyParticipated', validatedParams.onlyParticipated.toString());
    if (validatedParams.meetingType) queryParams.append('meetingType', validatedParams.meetingType);

    return this.request<GetMeetingsResponse>(`/meetings?${queryParams}`);
  }

  /**
   * Retrieves the transcript for a specific meeting
   * 
   * @param meetingId - The unique identifier of the meeting
   * @returns A promise that resolves to the meeting transcript
   * 
   * @example
   * ```typescript
   * const transcript = await api.getTranscript('meeting-123');
   * ```
   */
  async getTranscript(meetingId: string): Promise<TldvResponse<GetTranscriptResponse>> {
    return this.request<GetTranscriptResponse>(`/meetings/${meetingId}/transcript`);
  }

  /**
   * Retrieves the highlights for a specific meeting
   * 
   * @param meetingId - The unique identifier of the meeting
   * @returns A promise that resolves to the meeting highlights
   * 
   * @example
   * ```typescript
   * const highlights = await api.getHighlights('meeting-123');
   * ```
   */
  async getHighlights(meetingId: string): Promise<TldvResponse<GetHighlightsResponse>> {
    return this.request<GetHighlightsResponse>(`/meetings/${meetingId}/highlights`);
  }

  /**
   * Checks the health status of the API
   * 
   * @returns A promise that resolves to the health status
   * 
   * @example
   * ```typescript
   * const health = await api.healthCheck();
   * ```
   */
  async healthCheck(): Promise<TldvResponse<HealthResponse>> {
    return this.request<HealthResponse>('/health');
  }
} 
```