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

```
├── .github
│   └── workflows
│       ├── docker-publish.yml
│       └── npm-publish.yml
├── .gitignore
├── .husky
│   └── pre-commit
├── .npmignore
├── .prettierignore
├── .prettierrc
├── .vscode
│   └── mcp.json
├── about.jpg
├── common
│   ├── auth.ts
│   ├── error.ts
│   ├── types.ts
│   ├── util.ts
│   └── version.ts
├── Dockerfile
├── index.ts
├── LICENSE
├── operations
│   └── meeting.ts
├── package-lock.json
├── package.json
├── README.md
├── smithery.yaml
└── tsconfig.json
```

# Files

--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------

```
{}

```

--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------

```
.github
.vscode
```

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

```
node_modules
.DS_Store
dist
```

--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------

```
# Ignore artifacts:
build
coverage

```

--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------

```markdown
# Zoom MCP Server

[![NPM Version](https://img.shields.io/npm/v/@yitianyigexiangfa/zoom-mcp-server)](https://www.npmjs.com/package/@yitianyigexiangfa/zoom-mcp-server) ![MIT licensed](https://img.shields.io/npm/l/@yitianyigexiangfa/zoom-mcp-server) [![smithery badge](https://smithery.ai/badge/@JavaProgrammerLB/zoom-mcp-server)](https://smithery.ai/server/@JavaProgrammerLB/zoom-mcp-server) ![Zoom MCP Server](https://badge.mcpx.dev?type=server "MCP Server")

Now you can date a Zoom meeting with AI's help
![about.jpg](about.jpg)

[![MseeP.ai Security Assessment Badge](https://mseep.net/pr/javaprogrammerlb-zoom-mcp-server-badge.png)](https://mseep.ai/app/javaprogrammerlb-zoom-mcp-server)

## Usage

### 1. list meetings

- `list my meetings`
- `list my upcoming meetings`

### 2. create a meeting

- `Schedule a meeting at today 3 pm with a introduce mcp topic`

### 3. delete a meeting

- `delete the latest meeting`
- `delete the 86226580854 meeting`

### 4. get a meeting detail

- `Retrieve the latest meeting's details`
- `Retrieve 86226580854 meeting's details`

## Usage with VS Code
 [![Install with NPX in VS Code](https://img.shields.io/badge/VS_Code-NPM-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://insiders.vscode.dev/redirect/mcp/install?name=zoom-mcp-server&inputs=%5B%7B%22type%22%3A%22promptString%22%2C%22id%22%3A%22ZOOM_ACCOUNT_ID%22%7D%2C%20%7B%22type%22%3A%22promptString%22%2C%22id%22%3A%22ZOOM_CLIENT_ID%22%7D%2C%20%7B%22type%22%3A%22promptString%22%2C%22id%22%3A%22ZOOM_CLIENT_SECRET%22%7D%5D&config=%7B%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22%40yitianyigexiangfa%2Fzoom-mcp-server%40latest%22%5D%2C%22env%22%3A%7B%22ZOOM_ACCOUNT_ID%22%3A%22%24%7Binput%3AZOOM_ACCOUNT_ID%7D%22%2C%20%22ZOOM_CLIENT_ID%22%3A%22%24%7Binput%3AZOOM_CLIENT_ID%7D%22%2C%20%22ZOOM_CLIENT_SECRET%22%3A%22%24%7Binput%3AZOOM_CLIENT_SECRET%7D%22%7D%7D)

## 2 Steps to play with zoom-mcp-server

- Get Zoom Client ID, Zoom Client Secret and Account ID
- Config MCP server

### 1. Get Zoom Client ID, Zoom Client Secret and Account ID

1. vist [Zoom Marketplace](https://marketplace.zoom.us/)
1. Build App and choose **Server to Server OAuth App**
1. Add Scope > Meeting > Select All Meeting Permissions
1. Active your app
   then you can get **Account ID**, **Client ID**, **Client Secret** in App Credentials page

### 2. Config MCP Server

```json
{
  "mcpServers": {
    "zoom-mcp-server": {
      "command": "npx",
      "args": ["-y", "@yitianyigexiangfa/zoom-mcp-server@latest"],
      "env": {
        "ZOOM_ACCOUNT_ID": "${ZOOM_ACCOUNT_ID}",
        "ZOOM_CLIENT_ID": "${ZOOM_CLIENT_ID}",
        "ZOOM_CLIENT_SECRET": "${ZOOM_CLIENT_SECRET}"
      }
    }
  }
}
```

```

--------------------------------------------------------------------------------
/common/version.ts:
--------------------------------------------------------------------------------

```typescript
export const VERSION = "0.7.4";

```

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

```json
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "Node16",
    "moduleResolution": "Node16",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "resolveJsonModule": true,
    "outDir": "./dist",
    "rootDir": "."
  },
  "include": ["src/**/*", "./**/*.ts"],
  "exclude": ["node_modules"]
}

```

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

```dockerfile
# Generated by https://smithery.ai. See: https://smithery.ai/docs/config#dockerfile
FROM node:lts-alpine

ARG VERSION

WORKDIR /app

# Copy package files
COPY package*.json ./

# Install dependencies without running prepare scripts
RUN npm install --ignore-scripts

# Copy the rest of the application
COPY . .

# Build the project
RUN npm run build

# Expose port if needed (not strictly required for stdio-based MCP)

# Set entrypoint to run the server
ENTRYPOINT ["node", "dist/index.js"]

```

--------------------------------------------------------------------------------
/.vscode/mcp.json:
--------------------------------------------------------------------------------

```json
{
  "inputs": [
    {
      "type": "promptString",
      "id": "zoom_account_id",
      "description": "Zoom Account ID",
      "password": true
    },
    {
      "type": "promptString",
      "id": "zoom_client_id",
      "description": "Zoom Client ID",
      "password": true
    },
    {
      "type": "promptString",
      "id": "zoom_client_secret",
      "description": "Zoom Client Secret",
      "password": true
    }
  ],
  "servers": {
    "zoom-mcp-server": {
      "command": "docker",
      "args": [
        "run",
        "-i",
        "--rm",
        "-e",
        "ZOOM_ACCOUNT_ID",
        "-e",
        "ZOOM_CLIENT_ID",
        "-e",
        "ZOOM_CLIENT_SECRET",
        "ghcr.io/javaprogrammerlb/zoom-mcp-server"
      ],
      "env": {
        "ZOOM_ACCOUNT_ID": "${input:zoom_account_id}",
        "ZOOM_CLIENT_ID": "${input:zoom_client_id}",
        "ZOOM_CLIENT_SECRET": "${input:zoom_client_secret}"
      }
    }
  }
}

```

--------------------------------------------------------------------------------
/smithery.yaml:
--------------------------------------------------------------------------------

```yaml
# Smithery configuration file: https://smithery.ai/docs/config#smitheryyaml

startCommand:
  type: stdio
  configSchema:
    # JSON Schema defining the configuration options for the MCP.
    type: object
    required:
      - zoomAccountId
      - zoomClientId
      - zoomClientSecret
    properties:
      zoomAccountId:
        type: string
        description: Zoom account ID
      zoomClientId:
        type: string
        description: Zoom client ID
      zoomClientSecret:
        type: string
        description: Zoom client secret
    default: {}
  commandFunction:
    # A JS function that produces the CLI command based on the given config to start the MCP on stdio.
    |-
    (config) => ({
      command: 'node',
      args: ['dist/index.js'],
      env: {
        ZOOM_ACCOUNT_ID: config.zoomAccountId,
        ZOOM_CLIENT_ID: config.zoomClientId,
        ZOOM_CLIENT_SECRET: config.zoomClientSecret
      }
    })
  exampleConfig:
    zoomAccountId: example-account-id
    zoomClientId: example-client-id
    zoomClientSecret: example-client-secret

```

--------------------------------------------------------------------------------
/common/auth.ts:
--------------------------------------------------------------------------------

```typescript
import { createZoomError } from "./error.js";
import { TokenSchema } from "./types.js";
import { parseResponseBody, zoomRequest } from "./util.js";

export async function getAccessToken() {
  let accountId = process.env.ZOOM_ACCOUNT_ID
    ? process.env.ZOOM_ACCOUNT_ID
    : "";
  let clientId = process.env.ZOOM_CLIENT_ID ? process.env.ZOOM_CLIENT_ID : "";
  let clientSecret = process.env.ZOOM_CLIENT_SECRET
    ? process.env.ZOOM_CLIENT_SECRET
    : "";

  let authUrl = `https://zoom.us/oauth/token?grant_type=account_credentials&account_id=${accountId}`;

  const response = await fetch(authUrl, {
    method: "POST",
    headers: {
      Authorization: `Basic ${generateBasicAuth(clientId, clientSecret)}`,
    },
  });

  const responseBody = await parseResponseBody(response);
  if (!response.ok) {
    throw createZoomError(response.status, responseBody);
  }

  return TokenSchema.parse(responseBody);
}

function generateBasicAuth(username: string, password: string): string {
  const credentials = `${username}:${password}`;
  return Buffer.from(credentials).toString("base64");
}

```

--------------------------------------------------------------------------------
/.github/workflows/npm-publish.yml:
--------------------------------------------------------------------------------

```yaml
name: Npm Publish

on:
  release:
    types: [created]

jobs:
  build-and-publish:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
        with:
          ref: ${{ github.event.release.target_commitish }}

      - name: Set up Node.js
        uses: actions/setup-node@v3
        with:
          node-version: "22"
          registry-url: "https://registry.npmjs.org"

      - name: Install dependencies
        run: npm ci

      - name: Extract version from release
        id: extract_version
        run: echo "VERSION=${GITHUB_REF#refs/tags/v}" >> $GITHUB_ENV

      - name: Update version.ts
        run: |
          echo "export const VERSION = \"$VERSION\";" > common/version.ts

      - name: Update package.json version
        run: npm version $VERSION --no-git-tag-version

      - name: Build
        run: npm run build

      - name: Commit changes
        run: |
          git config --local user.email "[email protected]"
          git config --local user.name "GitHub Action"
          git add common/version.ts package.json
          git commit -m "chore: update version to $VERSION [skip ci]"
          git push

      - name: Publish to npm
        run: npm publish
        env:
          NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

```

--------------------------------------------------------------------------------
/common/util.ts:
--------------------------------------------------------------------------------

```typescript
import { getUserAgent } from "universal-user-agent";
import { VERSION } from "./version.js";
import { createZoomError } from "./error.js";
import { getAccessToken } from "./auth.js";

type RequestOptions = {
  method?: string;
  body?: unknown;
  headers?: Record<string, string>;
};

export async function parseResponseBody(response: Response): Promise<unknown> {
  const contentType = response.headers.get("content-type");
  if (contentType?.includes("application/json")) {
    return response.json();
  }
  return response.text();
}

const USER_AGENT = `zoom-mcp-server/v${VERSION} ${getUserAgent()}`;

export async function zoomRequest(
  url: string,
  options: RequestOptions = {},
): Promise<unknown> {
  const token = (await getAccessToken()).access_token;
  const headers: Record<string, string> = {
    "Content-Type": "application/json",
    "User-Agent": USER_AGENT,
    Authorization: `Bearer ${token}`,
    ...options.headers,
  };

  const response = await fetch(url, {
    method: options.method || "GET",
    headers,
    body: options.body ? JSON.stringify(options.body) : undefined,
  });

  const responseBody = await parseResponseBody(response);

  if (!response.ok) {
    throw createZoomError(response.status, responseBody);
  }

  return responseBody;
}

```

--------------------------------------------------------------------------------
/common/error.ts:
--------------------------------------------------------------------------------

```typescript
export class ZoomError extends Error {
  constructor(
    message: string,
    public readonly status: number,
    public readonly response: unknown,
  ) {
    super(message);
    this.name = "ZoomError";
  }
}

export class ZoomBadRequestError extends ZoomError {
  constructor(message = "Bad request") {
    super(message, 400, { message });
    this.name = "ZoomBadRequestError";
  }
}

export class ZoomAuthenticationError extends ZoomError {
  constructor(message = "Authentication failed") {
    super(message, 401, { message });
    this.name = "ZoomAuthenticationError";
  }
}

export class ZoomNotFoundError extends ZoomError {
  constructor(message = "Not Found") {
    super(message, 404, { message });
    this.name = "Not Found";
  }
}

export class ZoomTooManyRequests extends ZoomError {
  constructor(message = "Too Many Requests") {
    super(message, 429, { message });
    this.name = "Too Many Requests";
  }
}

export function createZoomError(status: number, response: any): ZoomError {
  switch (status) {
    case 400:
      return new ZoomBadRequestError(response?.message);
    case 401:
      return new ZoomAuthenticationError(response?.message);
    case 404:
      return new ZoomNotFoundError(response?.message);
    case 429:
      return new ZoomTooManyRequests(response?.message);
    default:
      return new ZoomError(
        response?.message || "Zoom API error",
        status,
        response,
      );
  }
}

```

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

```json
{
  "name": "@yitianyigexiangfa/zoom-mcp-server",
  "version": "0.7.4",
  "description": "Now you can date a ZOOM meeting with AI's help.",
  "scripts": {
    "build": "tsc && shx chmod +x dist/*.js",
    "prepare": "husky",
    "watch": "tsc --watch",
    "inspector": "npx @modelcontextprotocol/inspector -- node dist/index.js -e ZOOM_ACCOUNT_ID=$ZOOM_ACCOUNT_ID -e ZOOM_CLIENT_ID=$ZOOM_CLIENT_ID -e ZOOM_CLIENT_SECRET=$ZOOM_CLIENT_SECRET"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/JavaProgrammerLB/zoom-mcp-server.git"
  },
  "keywords": [
    "zoom",
    "mcp",
    "server"
  ],
  "author": "Bill Lau",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/JavaProgrammerLB/zoom-mcp-server/issues"
  },
  "homepage": "https://github.com/JavaProgrammerLB/zoom-mcp-server#readme",
  "type": "module",
  "bin": {
    "zoom-mcp-server": "dist/index.js"
  },
  "files": [
    "dist"
  ],
  "dependencies": {
    "@modelcontextprotocol/sdk": "1.9.0",
    "@types/node": "^22",
    "@types/node-fetch": "^2.6.12",
    "node-fetch": "^3.3.2",
    "universal-user-agent": "^7.0.2",
    "zod": "^3.22.4",
    "zod-to-json-schema": "^3.23.5"
  },
  "devDependencies": {
    "husky": "^9.1.7",
    "lint-staged": "^15.5.0",
    "prettier": "3.5.3",
    "shx": "^0.3.4",
    "typescript": "^5.6.2"
  },
  "lint-staged": {
    "**/*": "prettier --write --ignore-unknown"
  },
  "publishConfig": {
    "access": "public"
  }
}

```

--------------------------------------------------------------------------------
/operations/meeting.ts:
--------------------------------------------------------------------------------

```typescript
import { z } from "zod";
import { zoomRequest } from "../common/util.js";
import {
  ZoomListMeetingsSchema,
  ZoomMeetingDetailSchema,
  ZoomMeetingSchema,
} from "../common/types.js";

export const CreateMeetingOptionsSchema = z.object({
  agenda: z
    .string()
    .max(2000)
    .describe("The meeting's agenda.")
    .default("New Meeting's agenda"),
  start_time: z
    .string()
    .optional()
    .describe(
      `The meeting's start time. This supports local time and GMT formats.To set a meeting's start time in GMT, use the yyyy-MM-ddTHH:mm:ssZ date-time format. For example, 2020-03-31T12:02:00Z. To set a meeting's start time using a specific timezone, use the yyyy-MM-ddTHH:mm:ss date-time format and specify the timezone ID in the timezone field. If you do not specify a timezone, the timezone value defaults to your Zoom account's timezone. You can also use UTC for the timezone value. Note: If no start_time is set for a scheduled meeting, the start_time is set at the current time and the meeting type changes to an instant meeting, which expires after 30 days. current time is ${new Date().toISOString()}.`,
    ),
  timezone: z
    .string()
    .optional()
    .describe(
      `Timezone for the meeting's start time. The Current timezone is ${Intl.DateTimeFormat().resolvedOptions().timeZone}.`,
    ),
  topic: z.string().max(200).optional().describe("The meeting's topic."),
});

export const ListMeetingOptionsSchema = z.object({
  type: z
    .string()
    .optional()
    .describe(
      "The type of meeting. Choose from upcoming, scheduled or previous_meetings. upcoming - All upcoming meetings; scheduled - All valid previous (unexpired) meetings and upcoming scheduled meetings; previous_meetings - All the previous meetings;",
    )
    .default("upcoming"),
});

export const DeleteMeetingOptionsSchema = z.object({
  id: z.number().describe("The ID of the meeting to delete."),
});

export const GetMeetingOptionsSchema = z.object({
  id: z.number().describe("The ID of the meeting."),
});

export type CreateMeetingOptions = z.infer<typeof CreateMeetingOptionsSchema>;
export type ListMeetingOptions = z.infer<typeof ListMeetingOptionsSchema>;
export type DeleteMeetingOptions = z.infer<typeof DeleteMeetingOptionsSchema>;
export type GetMeetingOptions = z.infer<typeof GetMeetingOptionsSchema>;

export async function createMeeting(options: CreateMeetingOptions) {
  const response = await zoomRequest(
    `https://api.zoom.us/v2/users/me/meetings`,
    {
      method: "POST",
      body: options,
    },
  );
  return ZoomMeetingSchema.parse(response);
}

export async function listMeetings(options: ListMeetingOptions) {
  let url = "https://api.zoom.us/v2/users/me/meetings";
  const params = new URLSearchParams();
  Object.entries(options).forEach(([key, value]) => {
    if (value !== undefined && value !== null) {
      params.append(key, value.toString());
    }
  });
  if (Array.from(params).length > 0) {
    url += `?${params.toString()}`;
  }
  const response = await zoomRequest(url, {
    method: "GET",
  });
  return ZoomListMeetingsSchema.parse(response);
}

export async function deleteMeeting(options: DeleteMeetingOptions) {
  const response = await zoomRequest(
    `https://api.zoom.us/v2/meetings/${options.id}`,
    {
      method: "DELETE",
    },
  );
  return response;
}

export async function getAMeetingDetails(options: GetMeetingOptions) {
  const response = await zoomRequest(
    `https://api.zoom.us/v2/meetings/${options.id}`,
    {
      method: "GET",
    },
  );
  return ZoomMeetingDetailSchema.parse(response);
}

```

--------------------------------------------------------------------------------
/.github/workflows/docker-publish.yml:
--------------------------------------------------------------------------------

```yaml
name: Docker Publish

# This workflow uses actions that are not certified by GitHub.
# They are provided by a third-party and are governed by
# separate terms of service, privacy policy, and support
# documentation.

on:
  push:
    # Publish semver tags as releases.
    tags: ["v*.*.*"]

env:
  # Use docker.io for Docker Hub if empty
  REGISTRY: ghcr.io
  # github.repository as <account>/<repo>
  IMAGE_NAME: ${{ github.repository }}

jobs:
  build:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write
      # This is used to complete the identity challenge
      # with sigstore/fulcio when running outside of PRs.
      id-token: write

    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      # Install the cosign tool except on PR
      # https://github.com/sigstore/cosign-installer
      - name: Install cosign
        if: github.event_name != 'pull_request'
        uses: sigstore/cosign-installer@59acb6260d9c0ba8f4a2f9d9b48431a222b68e20 #v3.5.0
        with:
          cosign-release: "v2.2.4"

      # Set up BuildKit Docker container builder to be able to build
      # multi-platform images and export cache
      # https://github.com/docker/setup-buildx-action
      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@f95db51fddba0c2d1ec667646a06c2ce06100226 # v3.0.0

      # Login against a Docker registry except on PR
      # https://github.com/docker/login-action
      - name: Log into registry ${{ env.REGISTRY }}
        if: github.event_name != 'pull_request'
        uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d # v3.0.0
        with:
          registry: ${{ env.REGISTRY }}
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      # Extract metadata (tags, labels) for Docker
      # https://github.com/docker/metadata-action
      - name: Extract Docker metadata
        id: meta
        uses: docker/metadata-action@96383f45573cb7f253c731d3b3ab81c87ef81934 # v5.0.0
        with:
          images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}

      # Build and push Docker image with Buildx (don't push on PR)
      # https://github.com/docker/build-push-action
      - name: Build and push Docker image
        id: build-and-push
        uses: docker/build-push-action@0565240e2d4ab88bba5387d719585280857ece09 # v5.0.0
        with:
          context: .
          push: ${{ github.event_name != 'pull_request' }}
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}
          cache-from: type=gha
          cache-to: type=gha,mode=max
          platforms: linux/amd64,linux/arm64
          build-args: |
            VERSION=${{ github.ref_name }}

      # Sign the resulting Docker image digest except on PRs.
      # This will only write to the public Rekor transparency log when the Docker
      # repository is public to avoid leaking data.  If you would like to publish
      # transparency data even for private images, pass --force to cosign below.
      # https://github.com/sigstore/cosign
      - name: Sign the published Docker image
        if: ${{ github.event_name != 'pull_request' }}
        env:
          # https://docs.github.com/en/actions/security-guides/security-hardening-for-github-actions#using-an-intermediate-environment-variable
          TAGS: ${{ steps.meta.outputs.tags }}
          DIGEST: ${{ steps.build-and-push.outputs.digest }}
        # This step uses the identity token to provision an ephemeral certificate
        # against the sigstore community Fulcio instance.
        run: echo "${TAGS}" | xargs -I {} cosign sign --yes {}@${DIGEST}

```

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

```typescript
#!/usr/bin/env node
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { VERSION } from "./common/version.js";
import {
  CallToolRequestSchema,
  GetPromptRequestSchema,
  ListPromptsRequestSchema,
  ListToolsRequestSchema,
} from "@modelcontextprotocol/sdk/types.js";
import { zodToJsonSchema } from "zod-to-json-schema";
import {
  createMeeting,
  CreateMeetingOptionsSchema,
  deleteMeeting,
  DeleteMeetingOptionsSchema,
  getAMeetingDetails,
  GetMeetingOptionsSchema,
  ListMeetingOptionsSchema,
  listMeetings,
} from "./operations/meeting.js";
import { z } from "zod";

const server = new Server(
  {
    name: "zoom-mcp-server",
    version: VERSION,
  },
  {
    capabilities: {
      tools: {},
      prompts: {},
      logging: {},
    },
  },
);

enum PromptName {
  LIST_MEETINGS = "list_meetings",
  CREATE_A_MEETING = "create_meeting",
  DELETE_A_MEETING = "delete_a_meeting",
  GET_A_MEETING_DETAILS = "get_a_meeting_details",
}

server.setRequestHandler(ListPromptsRequestSchema, async () => {
  return {
    prompts: [
      {
        name: PromptName.LIST_MEETINGS,
        description: "A prompt to list meetings",
      },
      {
        name: PromptName.CREATE_A_MEETING,
        description: "A prompt to create a meeting",
      },
      {
        name: PromptName.DELETE_A_MEETING,
        description: "A prompt to delete a meeting",
      },
      {
        name: PromptName.GET_A_MEETING_DETAILS,
        description: "A prompt to get a meeting's details",
      },
    ],
  };
});

server.setRequestHandler(GetPromptRequestSchema, async (request) => {
  const { name, arguments: args } = request.params;

  if (name === PromptName.LIST_MEETINGS) {
    return {
      messages: [
        {
          role: "user",
          content: {
            type: "text",
            text: "List my zoom meetings",
          },
        },
      ],
    };
  } else if (name === PromptName.CREATE_A_MEETING) {
    return {
      messages: [
        {
          role: "user",
          content: {
            type: "text",
            text: "Create a zoom meeting",
          },
        },
      ],
    };
  } else if (name === PromptName.DELETE_A_MEETING) {
    return {
      messages: [
        {
          role: "user",
          content: {
            type: "text",
            text: "Delete a zoom meeting",
          },
        },
      ],
    };
  } else if (name === PromptName.GET_A_MEETING_DETAILS) {
    return {
      messages: [
        {
          role: "user",
          content: {
            type: "text",
            text: "Get a zoom meeting's details",
          },
        },
      ],
    };
  }
  throw new Error(`Unknown prompt: ${name}`);
});

server.setRequestHandler(ListToolsRequestSchema, async () => {
  return {
    tools: [
      {
        name: "create_meeting",
        description: "Create a meeting",
        inputSchema: zodToJsonSchema(CreateMeetingOptionsSchema),
      },
      {
        name: "list_meetings",
        description: "List scheduled meetings",
        inputSchema: zodToJsonSchema(ListMeetingOptionsSchema),
      },
      {
        name: "delete_a_meeting",
        description: "Delete a meeting with a given ID",
        inputSchema: zodToJsonSchema(DeleteMeetingOptionsSchema),
      },
      {
        name: "get_a_meeting_details",
        description: "Retrieve the meeting's details with a given ID",
        inputSchema: zodToJsonSchema(GetMeetingOptionsSchema),
      },
    ],
  };
});

server.setRequestHandler(CallToolRequestSchema, async (request) => {
  try {
    if (!request.params.arguments) {
      throw new Error("No arguments provided");
    }
    switch (request.params.name) {
      case "create_meeting": {
        const args = CreateMeetingOptionsSchema.parse(request.params.arguments);
        const result = await createMeeting(args);
        return {
          content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
        };
      }

      case "list_meetings": {
        const args = ListMeetingOptionsSchema.parse(request.params.arguments);
        const result = await listMeetings(args);
        return {
          content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
        };
      }

      case "delete_a_meeting": {
        const args = DeleteMeetingOptionsSchema.parse(request.params.arguments);
        const result = await deleteMeeting(args);
        return {
          content: [{ type: "text", text: result }],
        };
      }

      case "get_a_meeting_details": {
        const args = GetMeetingOptionsSchema.parse(request.params.arguments);
        const result = await getAMeetingDetails(args);
        return {
          content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
        };
      }
    }
  } catch (error) {
    if (error instanceof z.ZodError) {
      throw new Error(`Invalid input: ${JSON.stringify(error.errors)}`);
    }
    throw error;
  }

  return {};
});

async function runServer() {
  const transport = new StdioServerTransport();
  await server.connect(transport);
  server.sendLoggingMessage({
    level: "info",
    data: "Server started successfully",
  });
}

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

```

--------------------------------------------------------------------------------
/common/types.ts:
--------------------------------------------------------------------------------

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

export const TokenSchema = z.object({
  access_token: z.string(),
  token_type: z.string(),
  expires_in: z.number(),
  scope: z.string(),
  api_url: z.string(),
});

export const ZoomMeetingSettingsSchema = z.object({
  host_video: z.boolean().optional(),
  participant_video: z.boolean().optional(),
  cn_meeting: z.boolean().optional(),
  in_meeting: z.boolean().optional(),
  join_before_host: z.boolean().optional(),
  jbh_time: z.number().optional(),
  mute_upon_entry: z.boolean().optional(),
  watermark: z.boolean().optional(),
  use_pmi: z.boolean().optional(),
  approval_type: z.number().optional(),
  audio: z.string().optional(),
  auto_recording: z.string().optional(),
  enforce_login: z.boolean().optional(),
  enforce_login_domains: z.string().optional(),
  alternative_hosts: z.string().optional(),
  alternative_host_update_polls: z.boolean().optional(),
  close_registration: z.boolean().optional(),
  show_share_button: z.boolean().optional(),
  allow_multiple_devices: z.boolean().optional(),
  registrants_confirmation_email: z.boolean().optional(),
  waiting_room: z.boolean().optional(),
  request_permission_to_unmute_participants: z.boolean().optional(),
  registrants_email_notification: z.boolean().optional(),
  meeting_authentication: z.boolean().optional(),
  encryption_type: z.string().optional(),
  approved_or_denied_countries_or_regions: z.object({
    enable: z.boolean().optional(),
  }),
  breakout_room: z.object({
    enable: z.boolean().optional(),
  }),
  internal_meeting: z.boolean().optional(),
  continuous_meeting_chat: z.object({
    enable: z.boolean().optional(),
    auto_add_invited_external_users: z.boolean().optional(),
    auto_add_meeting_participants: z.boolean().optional(),
    channel_id: z.string().optional(),
  }),
  participant_focused_meeting: z.boolean().optional(),
  push_change_to_calendar: z.boolean().optional(),
  resources: z.array(z.unknown().optional()),
  allow_host_control_participant_mute_state: z.boolean().optional(),
  alternative_hosts_email_notification: z.boolean().optional(),
  show_join_info: z.boolean().optional(),
  device_testing: z.boolean().optional(),
  focus_mode: z.boolean().optional(),
  meeting_invitees: z.array(z.unknown().optional()),
  private_meeting: z.boolean().optional(),
  email_notification: z.boolean().optional(),
  host_save_video_order: z.boolean().optional(),
  sign_language_interpretation: z.object({
    enable: z.boolean().optional(),
  }),
  email_in_attendee_report: z.boolean().optional(),
});

export const ZoomMeetingSchema = z.object({
  uuid: z.string(),
  id: z.number(),
  host_id: z.string().optional(),
  host_email: z.string().optional(),
  topic: z.string().optional(),
  type: z.number().optional(),
  status: z.string().optional(),
  start_time: z.string().optional(),
  duration: z.number().optional(),
  timezone: z.string().optional(),
  agenda: z.string().optional(),
  created_at: z.string().optional(),
  start_url: z.string().optional(),
  join_url: z.string().optional(),
  password: z.string().optional(),
  h323_password: z.string().optional(),
  pstn_password: z.string().optional(),
  encrypted_password: z.string().optional(),
  settings: ZoomMeetingSettingsSchema,
  creation_source: z.string().optional(),
  pre_schedule: z.boolean().optional(),
});

export const ZoomListMeetingsSchema = z.object({
  page_size: z.number(),
  total_records: z.number(),
  next_page_token: z.string(),
  meetings: z.array(
    z.object({
      uuid: z.string(),
      id: z.number(),
      host_id: z.string().optional(),
      topic: z.string().optional(),
      type: z.number().optional(),
      start_time: z.string().optional(),
      duration: z.number().optional(),
      timezone: z.string().optional(),
      agenda: z.string().optional(),
      created_at: z.string().optional(),
      join_url: z.string().optional(),
    }),
  ),
});

export const ZoomMeetingDetailSchema = z.object({
  assistant_id: z.string().optional(),
  host_email: z.string().optional(),
  host_id: z.string().optional(),
  id: z.number(),
  uuid: z.string(),
  agenda: z.string().optional(),
  created_at: z.string().optional(),
  duration: z.number().optional(),
  encrypted_password: z.string().optional(),
  pstn_password: z.string().optional(),
  h323_password: z.string().optional(),
  join_url: z.string().optional(),
  chat_join_url: z.string().optional(),
  occurrences: z
    .array(
      z.object({
        duration: z.number().optional(),
        occurrence_id: z.string().optional(),
        start_time: z.string().optional(),
        status: z.string().optional(),
      }),
    )
    .optional(),
  password: z.string().optional(),
  pmi: z.string().optional(),
  pre_schedule: z.boolean().optional(),
  recurrence: z
    .object({
      end_date_time: z.string().optional(),
      end_times: z.number().optional(),
      monthly_day: z.number().optional(),
      monthly_week: z.number().optional(),
      monthly_week_day: z.number().optional(),
      repeat_interval: z.number().optional(),
      type: z.number().optional(),
      weekly_days: z.string().optional(),
    })
    .optional(),
  settings: ZoomMeetingSettingsSchema.extend({
    approved_or_denied_countries_or_regions: z
      .object({
        approved_list: z.array(z.string()).optional(),
        denied_list: z.array(z.string()).optional(),
        enable: z.boolean().optional(),
        method: z.string().optional(),
      })
      .optional(),
    authentication_exception: z
      .array(
        z.object({
          email: z.string().optional(),
          name: z.string().optional(),
          join_url: z.string().optional(),
        }),
      )
      .optional(),
    breakout_room: z
      .object({
        enable: z.boolean().optional(),
        rooms: z
          .array(
            z.object({
              name: z.string().optional(),
              participants: z.array(z.string()).optional(),
            }),
          )
          .optional(),
      })
      .optional(),
    global_dial_in_numbers: z
      .array(
        z.object({
          city: z.string().optional(),
          country: z.string().optional(),
          country_name: z.string().optional(),
          number: z.string().optional(),
          type: z.string().optional(),
        }),
      )
      .optional(),
    language_interpretation: z
      .object({
        enable: z.boolean().optional(),
        interpreters: z
          .array(
            z.object({
              email: z.string().optional(),
              languages: z.string().optional(),
              interpreter_languages: z.string().optional(),
            }),
          )
          .optional(),
      })
      .optional(),
    sign_language_interpretation: z
      .object({
        enable: z.boolean().optional(),
        interpreters: z
          .array(
            z.object({
              email: z.string().optional(),
              sign_language: z.string().optional(),
            }),
          )
          .optional(),
      })
      .optional(),
    meeting_invitees: z
      .array(
        z.object({
          email: z.string().optional(),
          internal_user: z.boolean().optional(),
        }),
      )
      .optional(),
    continuous_meeting_chat: z
      .object({
        enable: z.boolean().optional(),
        auto_add_invited_external_users: z.boolean().optional(),
        auto_add_meeting_participants: z.boolean().optional(),
        who_is_added: z.string().optional(),
        channel_id: z.string().optional(),
      })
      .optional(),
    resources: z
      .array(
        z.object({
          resource_type: z.string().optional(),
          resource_id: z.string().optional(),
          permission_level: z.string().optional(),
        }),
      )
      .optional(),
    question_and_answer: z
      .object({
        enable: z.boolean().optional(),
        allow_submit_questions: z.boolean().optional(),
        allow_anonymous_questions: z.boolean().optional(),
        question_visibility: z.string().optional(),
        attendees_can_comment: z.boolean().optional(),
        attendees_can_upvote: z.boolean().optional(),
      })
      .optional(),
    auto_start_meeting_summary: z.boolean().optional(),
    who_will_receive_summary: z.number().optional(),
    auto_start_ai_companion_questions: z.boolean().optional(),
    who_can_ask_questions: z.number().optional(),
    summary_template_id: z.string().optional(),
  }).optional(),
  start_time: z.string().optional(),
  start_url: z.string().optional(),
  status: z.string().optional(),
  timezone: z.string().optional(),
  topic: z.string().optional(),
  tracking_fields: z
    .array(
      z.object({
        field: z.string().optional(),
        value: z.string().optional(),
        visible: z.boolean().optional(),
      }),
    )
    .optional(),
  type: z.number().optional(),
  dynamic_host_key: z.string().optional(),
  creation_source: z.string().optional(),
});

```