# 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
[](https://www.npmjs.com/package/@yitianyigexiangfa/zoom-mcp-server)  [](https://smithery.ai/server/@JavaProgrammerLB/zoom-mcp-server) 
Now you can date a Zoom meeting with AI's help

[](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
[](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(),
});
```