#
tokens: 9781/50000 12/12 files
lines: on (toggle) GitHub
raw markdown copy reset
# 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:
--------------------------------------------------------------------------------

```
1 | TLDV_API_KEY=dummy
```

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

```
1 | node_modules
2 | dist
3 | DS_Store
4 | .env
5 | 
```

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

```yaml
 1 | stages:
 2 |   - build
 3 |   - release
 4 |   - deploy
 5 | 
 6 | variables:
 7 |   DOCKER_REGISTRY: $CI_REGISTRY
 8 |   DOCKER_IMAGE: $CI_REGISTRY_IMAGE
 9 | 
10 | release-version:
11 |   stage: release
12 |   image: node:22
13 |   script:
14 |     - git config --global user.email "${GIT_CI_EMAIL}"
15 |     - git config --global user.name "${GIT_CI_USER}"
16 |     - npm i standard-version --location=global
17 |     - npm run release -- --no-verify
18 |     - git remote set-url --push origin https://$GIT_CI_USER:[email protected]/$CI_PROJECT_PATH.git
19 |     - git push --follow-tags origin HEAD:$CI_COMMIT_REF_NAME
20 |   rules:
21 |     - if: $CI_COMMIT_BRANCH == "main" && $CI_COMMIT_TITLE !~ /^chore\(release\).*\ \[release\]$/
22 | 
23 | build:
24 |   stage: build
25 |   image: node:22
26 |   script:
27 |     - npm ci
28 |     - npm run build
29 |   artifacts:
30 |     paths:
31 |       - dist/
32 | 
33 | publish-npm:
34 |   stage: deploy
35 |   image: node:22
36 |   script:
37 |     - npm config set @tldx:registry=https://gitlab.com/api/v4/packages/npm/
38 |     - npm config set -- '//gitlab.com/api/v4/packages/npm/:_authToken'="${CI_JOB_TOKEN}"
39 |     - npm config set -- '//gitlab.com/api/v4/projects/${CI_PROJECT_ID}/packages/npm/:_authToken'="${CI_JOB_TOKEN}"
40 |     - npm ci
41 |     - npm run build
42 |     - npm publish
43 |     - echo "-- npm publish completed successfully"
44 |   rules:
45 |     - if: $CI_COMMIT_TAG && $CI_COMMIT_TAG =~ /^v?[0-9]+\.[0-9]+\.[0-9]+$/
46 | 
47 | publish-docker:
48 |   stage: deploy
49 |   image: docker:latest
50 |   services:
51 |     - docker:dind
52 |   before_script:
53 |     - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
54 |   script:
55 |     - docker build -t $DOCKER_IMAGE:$CI_COMMIT_TAG .
56 |     - docker tag $DOCKER_IMAGE:$CI_COMMIT_TAG $DOCKER_IMAGE:latest
57 |     - docker push $DOCKER_IMAGE:$CI_COMMIT_TAG
58 |     - docker push $DOCKER_IMAGE:latest
59 |     - echo "-- docker publish completed successfully"
60 |   rules:
61 |     - if: $CI_COMMIT_TAG && $CI_COMMIT_TAG =~ /^v?[0-9]+\.[0-9]+\.[0-9]+$/
62 | 
63 | 
```

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

```markdown
  1 | # Official MCP Server for tl;dv API
  2 | 
  3 | 🚀 **The First and Only MCP Server for Google Meet, Zoom, and Microsoft Teams Integration**
  4 | 
  5 | 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.
  6 | 
  7 | ## Features
  8 | 
  9 | *   **List Meetings:** Retrieve meetings based on filters (query, date range, participation status, type) across all supported platforms.
 10 | *   **Get Meeting Metadata:** Fetch detailed information for a specific meeting by ID, regardless of the platform it was hosted on.
 11 | *   **Get Transcript:** Obtain the transcript for any meeting ID, with consistent formatting across all platforms.
 12 | *   **Get Highlights:** Retrieve AI-generated highlights for meetings from any supported platform.
 13 | *   **Import Meeting (Coming Soon):** Functionality to import meetings via URL from any supported platform.
 14 | 
 15 | ## Prerequisites
 16 | 
 17 | *   **tl;dv Account:** A Business or Enterprise tl;dv account is required.
 18 | *   **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).
 19 | *   **Node.js & npm (for Node installation):** If installing via Node.js, ensure Node.js and npm are installed.
 20 | *   **Docker (for Docker installation):** If installing via Docker, ensure Docker is installed and running.
 21 | 
 22 | ## Installation and Configuration
 23 | 
 24 | 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.
 25 | 
 26 | ### Using Docker
 27 | 
 28 | Go in the repo. 
 29 | 
 30 | 1.  **Build the Docker image:**
 31 |     ```bash
 32 |     docker build -t tldv-mcp-server .
 33 |     ```
 34 | 
 35 | 2.  **Configure your MCP Client:**
 36 |     Update your MCP client's configuration file (e.g., `claude_desktop_config.json`). The exact location and format may vary depending on the client.
 37 | 
 38 |     ```json
 39 |     {
 40 |         "mcpServers": {
 41 |           "tldv": {
 42 |             "command": "docker",
 43 |             "args": [
 44 |                 "run",
 45 | 
 46 |                 "--rm",        
 47 |                 "--init",      
 48 |                 "-e",          
 49 |                 "TLDV_API_KEY=<your-tldv-api-key>",
 50 |                 "tldv-mcp-server"
 51 |             ],
 52 |           }
 53 |         }
 54 |       }
 55 |     ```
 56 |     Replace `<your-tldv-api-key>` with your actual tl;dv API key.
 57 | 
 58 | ### Using Node.js
 59 | 
 60 | 1.  **Install dependencies:**
 61 |     ```bash
 62 |     npm install
 63 |     ```
 64 | 
 65 | 2.  **Build the server:**
 66 |     ```bash
 67 |     npm run build
 68 |     ```
 69 |     This command creates a `dist` folder containing the compiled server code (`index.js`).
 70 | 
 71 | 3.  **Configure your MCP Client:**
 72 |     Update your MCP client's configuration file.
 73 | 
 74 |     ```json
 75 |     {
 76 |       "mcpServers": {
 77 |         "tldv": {
 78 |           "command": "node",
 79 |           "args": ["/absolute/path/to/tldv-mcp-server/dist/index.js"],
 80 |           "env": {
 81 |             "TLDV_API_KEY": "your_tldv_api_key"
 82 |           }
 83 |         }
 84 |       }
 85 |     }
 86 |     ```
 87 |     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.
 88 | 
 89 | *Refer to your specific MCP client's documentation for detailed setup instructions (e.g., [Claude Tools](https://modelcontextprotocol.io/quickstart/user)).*
 90 | 
 91 | *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. 
 92 | 
 93 | ## Development
 94 | 
 95 | 1.  **Install dependencies:**
 96 |     ```bash
 97 |     npm install
 98 |     ```
 99 | 
100 | 2.  **Set up Environment Variables:**
101 |     Copy the example environment file:
102 |     ```bash
103 |     cp .env.example .env
104 |     ```
105 |     Edit the `.env` file and add your `TLDV_API_KEY`. Other variables can be configured as needed.
106 | 
107 | 
108 | 3.  **Run in development mode:**
109 |     This command starts the server with auto-reloading on file changes:
110 |     ```bash
111 |     npm run watch
112 |     ```
113 | 
114 | 4.  **Update client for local development:**
115 |     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.
116 | 
117 | 5. **Reload your MCP Client**
118 |     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. 
119 | 
120 | ## Debugging
121 | 
122 | *   **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:*`).
123 | *   **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`).
124 | *   **MCP Client Logs:** Check the logs provided by your MCP client, which might show the requests sent and responses received from this server.
125 | 
126 | ## Learn More
127 | 
128 | *   [tl;dv Developer API Documentation](https://doc.tldv.io/)
129 | *   [Model Context Protocol (MCP) Specification](https://modelcontextprotocol.io/introduction)
```

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

```json
 1 | {
 2 |   "compilerOptions": {
 3 |     "baseUrl": "./",
 4 |     "sourceMap": false,
 5 |     "inlineSources": false,
 6 |     "removeComments": false,
 7 |     "target": "ES2022",
 8 |     "module": "NodeNext",
 9 |     "declaration": true,
10 |     "outDir": "./dist",
11 |     "emitDecoratorMetadata": true,
12 |     "experimentalDecorators": true,
13 |     "esModuleInterop": true,
14 |     "resolveJsonModule": true,
15 |     "paths": {
16 |       "~/*": ["src/*"]
17 |     }
18 |   }
19 | }
20 | 
```

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

```javascript
 1 | #!/usr/bin/env node
 2 | 
 3 | const { build } = require('esbuild');
 4 | 
 5 | async function bundle() {
 6 |   try {
 7 |     await build({
 8 |       entryPoints: ['src/index.ts'],
 9 |       bundle: true,
10 |       platform: 'node',
11 |       target: 'node22',
12 |       outfile: 'dist/index.js',
13 |       sourcemap: true,
14 |       minify: true,
15 |       format: 'cjs',
16 |       banner: {
17 |         js: '#!/usr/bin/env node',
18 |       },
19 |       // No external packages, include everything in the bundle
20 |       external: [],
21 |     });
22 |     console.log('Bundle complete! Output: dist/index.js');
23 |   } catch (error) {
24 |     console.error('Bundle failed:', error);
25 |     process.exit(1);
26 |   }
27 | }
28 | 
29 | bundle(); 
```

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

```json
 1 | {
 2 |     "name": "tldv-mcp",
 3 |     "version": "1.0.0",
 4 |     "description": "TLDR API MCP Server",
 5 |     "main": "dist/index.js",
 6 |     "types": "dist/index.d.ts",
 7 |     "scripts": {
 8 |         "start": "node dist/index.js",
 9 |         "dev": "ts-node-dev --respawn --transpile-only src/index.ts",
10 |         "build": "rm -rf dist && npm run bundle && chmod +x dist/*.js",
11 |         "bundle": "node scripts/build.js",
12 |         "prepare": "npm run build",
13 |         "watch": "tsc --watch",
14 |         "release": "standard-version -m \"chore(release): {{currentTag}} [release]\""
15 |     },
16 |     "dependencies": {
17 |         "@modelcontextprotocol/sdk": "^1.8.0",
18 |         "axios": "^1.8.4",
19 |         "dotenv": "^16.3.1",
20 |         "fastify": "^4.26.1",
21 |         "zod": "^3.22.4"
22 |     },
23 |     "devDependencies": {
24 |         "@types/node": "^20.11.19",
25 |         "esbuild": "^0.25.2",
26 |         "esbuild-node-externals": "^1.18.0",
27 |         "ts-node-dev": "^2.0.0",
28 |         "typescript": "^5.3.3"
29 |     },
30 |     "volta": {
31 |         "node": "22.11.0"
32 |     }
33 | }
34 | 
```

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

```dockerfile
 1 | # Stage 1: Build the application
 2 | FROM node:22-slim AS builder
 3 | 
 4 | WORKDIR /app
 5 | 
 6 | # Copy package files and install dependencies
 7 | COPY package.json /app
 8 | COPY package-lock.json /app
 9 | COPY tsconfig.json /app
10 | 
11 | COPY src /app/src
12 | # Use npm ci for cleaner installs, ensure package-lock.json exists
13 | RUN npm install
14 | 
15 | # Build the TypeScript code
16 | RUN npm run build
17 | 
18 | # Prune development dependencies
19 | RUN npm prune --production
20 | 
21 | # Stage 2: Create the final production image
22 | FROM node:22-slim
23 | 
24 | WORKDIR /app
25 | 
26 | # Create a non-root user
27 | RUN addgroup --system appgroup && adduser --system --ingroup appgroup appuser
28 | 
29 | # Copy built code and production dependencies from the builder stage
30 | COPY --from=builder /app/dist ./dist
31 | COPY --from=builder /app/node_modules ./node_modules
32 | COPY package.json ./
33 | 
34 | # Set ownership to the non-root user
35 | RUN chown -R appuser:appgroup /app
36 | 
37 | # Switch to the non-root user
38 | USER appuser
39 | 
40 | # Command to run the server
41 | # The API key will be passed via environment variable at runtime
42 | CMD ["node", "dist/index.js"]
```

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

```typescript
  1 | export interface TldvConfig {
  2 |   apiKey: string;
  3 |   baseUrl?: string;
  4 | }
  5 | 
  6 | export interface TldvResponse<T> {
  7 |   data: T;
  8 |   error?: string;
  9 | }
 10 | 
 11 | export interface User {
 12 |   name: string;
 13 |   email: string;
 14 | }
 15 | 
 16 | export interface Template {
 17 |   id: string;
 18 |   label: string;
 19 | }
 20 | 
 21 | export interface Meeting {
 22 |   id: string;
 23 |   name: string;
 24 |   happenedAt: string;
 25 |   url: string;
 26 |   organizer: User;
 27 |   invitees: User[];
 28 |   template: Template;
 29 | }
 30 | 
 31 | export interface Sentence {
 32 |   speaker: string;
 33 |   text: string;
 34 |   startTime: number;
 35 |   endTime: number;
 36 | }
 37 | 
 38 | export interface HighlightTopic {
 39 |   title: string;
 40 |   summary: string;
 41 | }
 42 | 
 43 | export interface Highlight {
 44 |   text: string;
 45 |   startTime: number;
 46 |   source: 'manual' | 'auto';
 47 |   topic: HighlightTopic;
 48 | }
 49 | 
 50 | export interface GetTranscriptResponse {
 51 |   id: string;
 52 |   meetingId: string;
 53 |   data: Sentence[];
 54 | }
 55 | 
 56 | export interface GetHighlightsResponse {
 57 |   meetingId: string;
 58 |   data: Highlight[];
 59 | }
 60 | 
 61 | export interface ImportMeetingParams {
 62 |   name: string;
 63 |   url: string;
 64 |   happenedAt?: string;
 65 |   dryRun?: boolean;
 66 | }
 67 | 
 68 | export interface ImportMeetingResponse {
 69 |   success: boolean;
 70 |   jobId: string;
 71 |   message: string;
 72 | }
 73 | 
 74 | export interface GetMeetingsParams {
 75 |   query?: string;
 76 |   page?: number;
 77 |   limit?: number;
 78 |   from?: string;
 79 |   to?: string;
 80 |   onlyParticipated?: boolean;
 81 |   meetingType?: 'internal' | 'external';
 82 | }
 83 | 
 84 | export interface GetMeetingsResponse {
 85 |   page: number;
 86 |   pages: number;
 87 |   total: number;
 88 |   pageSize: number;
 89 |   results: Meeting[];
 90 | }
 91 | 
 92 | export interface HealthResponse {
 93 |   status: string;
 94 | }
 95 | 
 96 | export interface ValidationError {
 97 |   property: string;
 98 |   constraints: Record<string, string>;
 99 | }
100 | 
101 | export interface ValidationErrorResponse {
102 |   message: string;
103 |   error: {
104 |     message: string;
105 |     errors: ValidationError[];
106 |   }[];
107 | }
108 | 
109 | export interface BasicErrorResponse {
110 |   name: string;
111 |   message: string;
112 | } 
```

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

```typescript
  1 | import { GetMeetingsParamsSchema } from "./api/schemas";
  2 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
  3 | import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
  4 | import { TldvApi } from "./api/tldv-api";
  5 | import dotenv from "dotenv";
  6 | import z from "zod";
  7 | 
  8 | dotenv.config();
  9 | 
 10 | async function main() {
 11 |   console.info({ name: "Initializing TLDV API..." });
 12 |   const tldvApi = new TldvApi({
 13 |     apiKey: process.env.TLDV_API_KEY,
 14 |   });
 15 |   
 16 |   console.info({ name: "Starting MCP server..." });
 17 |   
 18 |   const tools = {
 19 |     "get-meeting-metadata": {
 20 |       name: "get-meeting-metadata",
 21 |       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.",
 22 |       inputSchema: z.object({ id: z.string() }),
 23 |     },
 24 |     "get-transcript": {
 25 |       name: "get-transcript",
 26 |       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",
 27 |       inputSchema: z.object({ meetingId: z.string() }),
 28 |     },
 29 |     "list-meetings": {
 30 |       name: "list-meetings",
 31 |       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.",
 32 |       inputSchema: GetMeetingsParamsSchema,
 33 |     },
 34 |     "get-highlights": {
 35 |       name: "get-highlights",
 36 |       description: "Allows you to get highlights from a meeting by providing a meeting ID.",
 37 |       inputSchema: z.object({ meetingId: z.string() }),
 38 |     },
 39 |   };
 40 |   
 41 |   const server = new McpServer({
 42 |     name: "tldv-server",
 43 |     version: "1.0.0",
 44 |   }, {
 45 |     capabilities: {
 46 |       logging: {
 47 |         level: "debug",
 48 |       },
 49 |       prompts: {},
 50 |       tools: tools,
 51 |       resources: {},
 52 |     },
 53 |     instructions: "You are a helpful assistant that can help with TLDV API requests.",
 54 |   });
 55 | 
 56 |   //Register tool handlers
 57 |   server.tool(
 58 |     tools["get-meeting-metadata"].name,
 59 |     tools["get-meeting-metadata"].description,
 60 |     tools["get-meeting-metadata"].inputSchema.shape,
 61 |     async ({ id }) => {
 62 |       const meeting = await tldvApi.getMeeting(id);
 63 |       return {
 64 |         content: [{ type: "text", text: JSON.stringify(meeting) }]
 65 |       };
 66 |     }
 67 |   );
 68 | 
 69 |   server.tool(
 70 |     tools["get-transcript"].name,
 71 |     tools["get-transcript"].description,
 72 |     tools["get-transcript"].inputSchema.shape,
 73 |     async ({ meetingId }) => {
 74 |       const transcript = await tldvApi.getTranscript(meetingId);
 75 |       return {
 76 |         content: [{ type: "text", text: JSON.stringify(transcript) }]
 77 |       };
 78 |     }
 79 |   );
 80 | 
 81 |   server.tool(
 82 |     tools["list-meetings"].name,
 83 |     tools["list-meetings"].description,
 84 |     tools["list-meetings"].inputSchema.shape,
 85 |     async (input) => {
 86 |       const meetings = await tldvApi.getMeetings(input);
 87 |       return {
 88 |         content: [{ type: "text", text: JSON.stringify(meetings) }]
 89 |       };
 90 |     }
 91 |   );
 92 | 
 93 |   server.tool(
 94 |     tools["get-highlights"].name,
 95 |     tools["get-highlights"].description,
 96 |     tools["get-highlights"].inputSchema.shape,
 97 |     async ({ meetingId }) => {
 98 |       const highlights = await tldvApi.getHighlights(meetingId);
 99 |       return {
100 |         content: [{ type: "text", text: JSON.stringify(highlights) }]
101 |       };
102 |     }
103 |   );
104 | 
105 |   console.info({ message: "Initializing StdioServerTransport..." });
106 |   const transport = new StdioServerTransport();
107 | 
108 |   console.info({ message: "Connecting to MCP server..." });
109 |   await server.connect(transport);
110 | }
111 | 
112 | main().catch((error) => {
113 |   console.error({ message: "Fatal error in main():", error });
114 | //   process.exit(1);
115 | });
116 | 
```

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

```typescript
  1 | import { z } from 'zod';
  2 | 
  3 | /**
  4 |  * Configuration for the TLDR API client
  5 |  */
  6 | export const TldvConfigSchema = z.object({
  7 |   apiKey: z.string().min(1, 'API key is required')
  8 | });
  9 | 
 10 | export type TldvConfig = z.infer<typeof TldvConfigSchema>;
 11 | 
 12 | /**
 13 |  * Generic response wrapper for all API calls
 14 |  */
 15 | export const TldvResponseSchema = <T extends z.ZodType>(dataSchema: T) => dataSchema.nullable()
 16 | 
 17 | export type TldvResponse<T> = z.infer<ReturnType<typeof TldvResponseSchema<z.ZodType<T>>>>;
 18 | 
 19 | /**
 20 |  * User information schema
 21 |  */
 22 | export const UserSchema = z.object({
 23 |   name: z.string(),
 24 |   email: z.string().email(),
 25 | });
 26 | 
 27 | export type User = z.infer<typeof UserSchema>;
 28 | 
 29 | /**
 30 |  * Template information schema
 31 |  */
 32 | export const TemplateSchema = z.object({
 33 |   id: z.string(),
 34 |   label: z.string(),
 35 | });
 36 | 
 37 | export type Template = z.infer<typeof TemplateSchema>;
 38 | 
 39 | /**
 40 |  * Meeting information schema
 41 |  */
 42 | export const MeetingSchema = z.object({
 43 |   id: z.string(),
 44 |   name: z.string(),
 45 |   happenedAt: z.string().datetime(),
 46 |   url: z.string().url(),
 47 |   organizer: UserSchema,
 48 |   invitees: z.array(UserSchema),
 49 |   template: TemplateSchema,
 50 | });
 51 | 
 52 | export type Meeting = z.infer<typeof MeetingSchema>;
 53 | 
 54 | /**
 55 |  * Sentence information schema for transcripts
 56 |  */
 57 | export const SentenceSchema = z.object({
 58 |   speaker: z.string(),
 59 |   text: z.string(),
 60 |   startTime: z.number().int().nonnegative(),
 61 |   endTime: z.number().int().nonnegative(),
 62 | });
 63 | 
 64 | export type Sentence = z.infer<typeof SentenceSchema>;
 65 | 
 66 | /**
 67 |  * Highlight topic information schema
 68 |  */
 69 | export const HighlightTopicSchema = z.object({
 70 |   title: z.string(),
 71 |   summary: z.string(),
 72 | });
 73 | 
 74 | export type HighlightTopic = z.infer<typeof HighlightTopicSchema>;
 75 | 
 76 | /**
 77 |  * Highlight information schema
 78 |  */
 79 | export const HighlightSchema = z.object({
 80 |   text: z.string(),
 81 |   startTime: z.number().int().nonnegative(),
 82 |   source: z.enum(['manual', 'auto']),
 83 |   topic: HighlightTopicSchema,
 84 | });
 85 | 
 86 | export type Highlight = z.infer<typeof HighlightSchema>;
 87 | 
 88 | /**
 89 |  * Transcript response schema
 90 |  */
 91 | export const GetTranscriptResponseSchema = z.object({
 92 |   id: z.string(),
 93 |   meetingId: z.string(),
 94 |   data: z.array(SentenceSchema),
 95 | });
 96 | 
 97 | export type GetTranscriptResponse = z.infer<typeof GetTranscriptResponseSchema>;
 98 | 
 99 | /**
100 |  * Highlights response schema
101 |  */
102 | export const GetHighlightsResponseSchema = z.object({
103 |   meetingId: z.string(),
104 |   data: z.array(HighlightSchema),
105 | });
106 | 
107 | export type GetHighlightsResponse = z.infer<typeof GetHighlightsResponseSchema>;
108 | 
109 | /**
110 |  * Import meeting response schema
111 |  */
112 | export const ImportMeetingResponseSchema = z.object({
113 |   success: z.boolean(),
114 |   jobId: z.string(),
115 |   message: z.string(),
116 | });
117 | 
118 | export type ImportMeetingResponse = z.infer<typeof ImportMeetingResponseSchema>;
119 | 
120 | /**
121 |  * Get meetings parameters schema
122 |  */
123 | export const GetMeetingsParamsSchema = z.object({
124 |   query: z.string().optional(), // search query
125 |   page: z.number().int().positive().optional(), // page number      
126 |   limit: z.number().int().positive().optional().default(50), // number of results per page
127 |   from: z.string().datetime().optional(), // start date
128 |   to: z.string().datetime().optional(), // end date
129 |   onlyParticipated: z.boolean().optional(), // only return meetings where the user participated
130 | 
131 |   // meeting type. internal is default. 
132 |   // This is used to filter meetings by type. Type is determined by comparing the organizer's email with the invitees' emails. 
133 |   // If the organizer's domain is different from at least one of the invitees' domains, the meeting is external.
134 |   // Otherwise, the meeting is internal.  
135 |   meetingType: z.enum(['internal', 'external']).optional(), 
136 | });
137 | 
138 | export type GetMeetingsParams = z.infer<typeof GetMeetingsParamsSchema>;
139 | 
140 | /**
141 |  * Get meetings response schema
142 |  */
143 | export const GetMeetingsResponseSchema = z.object({
144 |   page: z.number().int().nonnegative(), // current page number
145 |   pages: z.number().int().positive(), // total number of pages  
146 |   total: z.number().int().nonnegative(), // total number of results
147 |   pageSize: z.number().int().positive(), // number of results per page
148 |   results: z.array(MeetingSchema), // array of meetings
149 | });
150 | 
151 | export type GetMeetingsResponse = z.infer<typeof GetMeetingsResponseSchema>;
152 | 
153 | /**
154 |  * Health check response schema
155 |  */
156 | export const HealthResponseSchema = z.object({
157 |   status: z.string(),
158 | });
159 | 
160 | export type HealthResponse = z.infer<typeof HealthResponseSchema>;
161 | 
162 | /**
163 |  * Validation error schema
164 |  */
165 | export const ValidationErrorSchema = z.object({
166 |   property: z.string(),
167 |   constraints: z.record(z.string()),
168 | });
169 | 
170 | export type ValidationError = z.infer<typeof ValidationErrorSchema>;
171 | 
172 | /**
173 |  * Validation error response schema
174 |  */
175 | export const ValidationErrorResponseSchema = z.object({
176 |   message: z.string(),
177 |   error: z.array(
178 |     z.object({
179 |       message: z.string(),
180 |       errors: z.array(ValidationErrorSchema),
181 |     })
182 |   ),
183 | });
184 | 
185 | export type ValidationErrorResponse = z.infer<typeof ValidationErrorResponseSchema>;
186 | 
187 | /**
188 |  * Basic error response schema
189 |  */
190 | export const BasicErrorResponseSchema = z.object({
191 |   name: z.string(),
192 |   message: z.string(),
193 | });
194 | 
195 | export type BasicErrorResponse = z.infer<typeof BasicErrorResponseSchema>; 
```

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

```typescript
  1 | import {
  2 |   GetHighlightsResponse,
  3 |   GetMeetingsParams,
  4 |   GetMeetingsParamsSchema,
  5 |   GetMeetingsResponse,
  6 |   GetTranscriptResponse,
  7 |   HealthResponse,
  8 |   Meeting,
  9 |   TldvConfig,
 10 |   TldvConfigSchema,
 11 | } from './schemas';
 12 | 
 13 | import { TldvResponse } from './types';
 14 | import axios from 'axios';
 15 | 
 16 | const BASE_URL = 'https://pasta.tldv.io/v1alpha1';
 17 | 
 18 | const MAX_RETRIES = 3;
 19 | const RETRY_DELAY = 1_000; 
 20 | const MAX_RETRY_DELAY = 2_000;
 21 | 
 22 | /**
 23 |  * TLDV API Client
 24 |  * 
 25 |  * This class provides a type-safe interface to interact with the TLDV API.
 26 |  * It handles authentication, request formatting, and response validation.
 27 |  * 
 28 |  * @example
 29 |  * ```typescript
 30 |  * const api = new TldvApi({
 31 |  *   apiKey: 'your-api-key'
 32 |  * });
 33 |  * 
 34 |  * ```typescript
 35 |  * const meeting = await tldvApi.getMeeting(id);
 36 |  * ```
 37 |  */
 38 | export class TldvApi {
 39 |   private apiKey: string;
 40 |   private baseUrl: string;
 41 |   private headers: any;
 42 | 
 43 |   /**
 44 |    * Creates a new instance of the TLDV API client
 45 |    * @param config - Configuration object containing API key and optional base URL
 46 |    * @throws {Error} If the configuration is invalid
 47 |    */
 48 |   constructor(config: TldvConfig) {
 49 |     const validatedConfig = TldvConfigSchema.parse(config);
 50 |     this.apiKey = validatedConfig.apiKey;
 51 |     this.baseUrl = BASE_URL;
 52 |     this.headers = {
 53 |       'x-api-key': this.apiKey,
 54 |       'Content-Type': 'application/json',
 55 |     };
 56 |   }
 57 | 
 58 |   /**
 59 |    * Makes a request to the TLDV API
 60 |    * @param endpoint - The API endpoint to call
 61 |    * @param options - Request options including method, body, etc.
 62 |    * @returns A promise that resolves to the validated API response
 63 |    */
 64 |   private async request<T>(
 65 |     endpoint: string,
 66 |     options: RequestInit = {},
 67 |     retryCount = 0,
 68 |     maxRetries = MAX_RETRIES
 69 |   ): Promise<TldvResponse<T>> {
 70 |     try {
 71 |       const response = await axios(`${this.baseUrl}${endpoint}`, {
 72 |         ...options,
 73 |         headers: {
 74 |           ...this.headers,
 75 |         },
 76 |       });
 77 | 
 78 |       if (response.status > 200) {
 79 |         throw new Error(response.data.message || 'API request failed');
 80 |       }
 81 | 
 82 |       // Ensure the response is properly formatted
 83 |       const responseData = response.data;
 84 |       
 85 |       // If the response is already in the expected format, return it
 86 |       if (responseData && typeof responseData === 'object' && 'data' in responseData) {
 87 |         return responseData as TldvResponse<T>;
 88 |       }
 89 |       
 90 |       // Otherwise, wrap it in the expected format
 91 |       return {
 92 |         data: responseData as T,
 93 |         error: undefined,
 94 |       };
 95 |     } catch (error) {
 96 |       // Determine if we should retry based on the error
 97 |       const shouldRetry = 
 98 |         retryCount < maxRetries && 
 99 |         (
100 |           // Network errors
101 |           (error instanceof Error && error.message.includes('Network Error')) ||
102 |           // 5xx server errors
103 |           (axios.isAxiosError(error) && error.response && error.response.status >= 500) ||
104 |           // Rate limiting
105 |           (axios.isAxiosError(error) && error.response && error.response.status === 429)
106 |         );
107 |       
108 |       if (shouldRetry) {
109 |         // Calculate exponential backoff delay: 2^retryCount * 1000ms (1s, 2s, 4s, etc.)
110 |         const delay = Math.min(RETRY_DELAY * 2 ** retryCount, MAX_RETRY_DELAY);
111 |         
112 |         // Wait before retrying
113 |         await new Promise(resolve => setTimeout(resolve, delay));
114 |         
115 |         // Retry the request
116 |         return this.request<T>(endpoint, options, retryCount + 1, maxRetries);
117 |       }
118 |       
119 |       return {
120 |         data: null as T,
121 |         error: error instanceof Error ? error.message : 'Unknown error occurred',
122 |       };
123 |     }
124 |   }
125 | 
126 | 
127 |   /**
128 |    * Retrieves a meeting by its ID
129 |    * 
130 |    * @param meetingId - The unique identifier of the meeting
131 |    * @returns A promise that resolves to the meeting details
132 |    * 
133 |    * @example
134 |    * ```typescript
135 |    * const meeting = await api.getMeeting('meeting-123');
136 |    * ```
137 |    */
138 |   async getMeeting(meetingId: string): Promise<TldvResponse<Meeting>> {
139 |     return this.request<Meeting>(`/meetings/${meetingId}`);
140 |   }
141 | 
142 |   /**
143 |    * Retrieves a list of meetings with optional filtering
144 |    * 
145 |    * @param params - Optional parameters for filtering and pagination
146 |    * @returns A promise that resolves to the paginated list of meetings
147 |    * 
148 |    * @example
149 |    * ```typescript
150 |    * const meetings = await api.getMeetings({
151 |    *   query: 'team sync',
152 |    *   page: 1,
153 |    *   limit: 10,
154 |    *   from: '2024-01-01T00:00:00Z',
155 |    *   to: '2024-12-31T23:59:59Z',
156 |    *   onlyParticipated: true,
157 |    *   meetingType: 'internal'
158 |    * });
159 |    * ```
160 |    */
161 |   async getMeetings(params: GetMeetingsParams = {}): Promise<TldvResponse<GetMeetingsResponse>> {
162 |     const validatedParams = GetMeetingsParamsSchema.parse(params);
163 |     const queryParams = new URLSearchParams();
164 |     
165 |     if (validatedParams.query) queryParams.append('query', validatedParams.query);
166 |     if (validatedParams.page) queryParams.append('page', validatedParams.page.toString());
167 |     if (validatedParams.limit) queryParams.append('limit', validatedParams.limit.toString());
168 |     if (validatedParams.from) queryParams.append('from', validatedParams.from);
169 |     if (validatedParams.to) queryParams.append('to', validatedParams.to);
170 |     if (validatedParams.onlyParticipated !== undefined) queryParams.append('onlyParticipated', validatedParams.onlyParticipated.toString());
171 |     if (validatedParams.meetingType) queryParams.append('meetingType', validatedParams.meetingType);
172 | 
173 |     return this.request<GetMeetingsResponse>(`/meetings?${queryParams}`);
174 |   }
175 | 
176 |   /**
177 |    * Retrieves the transcript for a specific meeting
178 |    * 
179 |    * @param meetingId - The unique identifier of the meeting
180 |    * @returns A promise that resolves to the meeting transcript
181 |    * 
182 |    * @example
183 |    * ```typescript
184 |    * const transcript = await api.getTranscript('meeting-123');
185 |    * ```
186 |    */
187 |   async getTranscript(meetingId: string): Promise<TldvResponse<GetTranscriptResponse>> {
188 |     return this.request<GetTranscriptResponse>(`/meetings/${meetingId}/transcript`);
189 |   }
190 | 
191 |   /**
192 |    * Retrieves the highlights for a specific meeting
193 |    * 
194 |    * @param meetingId - The unique identifier of the meeting
195 |    * @returns A promise that resolves to the meeting highlights
196 |    * 
197 |    * @example
198 |    * ```typescript
199 |    * const highlights = await api.getHighlights('meeting-123');
200 |    * ```
201 |    */
202 |   async getHighlights(meetingId: string): Promise<TldvResponse<GetHighlightsResponse>> {
203 |     return this.request<GetHighlightsResponse>(`/meetings/${meetingId}/highlights`);
204 |   }
205 | 
206 |   /**
207 |    * Checks the health status of the API
208 |    * 
209 |    * @returns A promise that resolves to the health status
210 |    * 
211 |    * @example
212 |    * ```typescript
213 |    * const health = await api.healthCheck();
214 |    * ```
215 |    */
216 |   async healthCheck(): Promise<TldvResponse<HealthResponse>> {
217 |     return this.request<HealthResponse>('/health');
218 |   }
219 | } 
```