diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml index 0a4275f..1bc1529 100644 --- a/.github/workflows/docker-publish.yml +++ b/.github/workflows/docker-publish.yml @@ -1,4 +1,4 @@ -name: Docker +name: Docker Publish # This workflow uses actions that are not certified by GitHub. # They are provided by a third-party and are governed by @@ -7,11 +7,8 @@ name: Docker on: push: - branches: ["master"] # Publish semver tags as releases. - tags: ["v*.*"] - pull_request: - branches: ["master"] + tags: ["v*.*.*"] env: # Use docker.io for Docker Hub if empty diff --git a/.github/workflows/npm-publish.yml b/.github/workflows/npm-publish.yml new file mode 100644 index 0000000..8e9e929 --- /dev/null +++ b/.github/workflows/npm-publish.yml @@ -0,0 +1,49 @@ +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 "action@github.com" + 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 }} diff --git a/README.md b/README.md index 6d37613..0cf5046 100644 --- a/README.md +++ b/README.md @@ -1,22 +1,40 @@ # 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) -[![smithery badge](https://smithery.ai/badge/@JavaProgrammerLB/zoom-mcp-server)](https://smithery.ai/server/@JavaProgrammerLB/zoom-mcp-server) +## Usage -## 3 Steps to play with zoom-mcp-server +### 1. list meetings -- Download this repository -- Get Zoom Client ID, Zoom Client Secret and Account ID -- Config MCP server +- `list my meetings` +- `list my upcoming meetings` -### 1. Download this repository +### 2. create a meeting -``` -git clone https://github.com/JavaProgrammerLB/zoom-mcp-server -``` +- `Schedule a meeting at today 3 pm with a introduce mcp topic` + +### 3. delete a meeting + +- `delete the latest meeting` +- `delete the 86226580854 meeting` -### 2. Get Zoom Client ID, Zoom Client Secret and Account ID +### 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** @@ -24,16 +42,14 @@ git clone https://github.com/JavaProgrammerLB/zoom-mcp-server 1. Active your app then you can get **Account ID**, **Client ID**, **Client Secret** in App Credentials page -### 3. Config MCP Server +### 2. Config MCP Server -``` +```json { "mcpServers": { "zoom-mcp-server": { "command": "npx", - "args": [ - "/PATH/TO/zoom-mcp-server" - ], + "args": ["-y", "@yitianyigexiangfa/zoom-mcp-server@latest"], "env": { "ZOOM_ACCOUNT_ID": "${ZOOM_ACCOUNT_ID}", "ZOOM_CLIENT_ID": "${ZOOM_CLIENT_ID}", diff --git a/common/types.ts b/common/types.ts index fa47fc8..93c26fa 100644 --- a/common/types.ts +++ b/common/types.ts @@ -109,3 +109,172 @@ export const ZoomListMeetingsSchema = z.object({ }), ), }); + +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(), +}); diff --git a/common/version.ts b/common/version.ts index 2451669..a972ad6 100644 --- a/common/version.ts +++ b/common/version.ts @@ -1 +1 @@ -export const VERSION = "v0.6"; +export const VERSION = "0.7.2"; diff --git a/index.ts b/index.ts index a82b858..819cdcc 100644 --- a/index.ts +++ b/index.ts @@ -14,6 +14,8 @@ import { CreateMeetingOptionsSchema, deleteMeeting, DeleteMeetingOptionsSchema, + getAMeetingDetails, + GetMeetingOptionsSchema, ListMeetingOptionsSchema, listMeetings, } from "./operations/meeting.js"; @@ -37,6 +39,7 @@ 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 () => { @@ -54,6 +57,10 @@ server.setRequestHandler(ListPromptsRequestSchema, async () => { 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", + }, ], }; }); @@ -97,6 +104,18 @@ server.setRequestHandler(GetPromptRequestSchema, async (request) => { }, ], }; + } 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}`); }); @@ -115,10 +134,15 @@ server.setRequestHandler(ListToolsRequestSchema, async () => { inputSchema: zodToJsonSchema(ListMeetingOptionsSchema), }, { - name: "delete_meeting", + 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), + }, ], }; }); @@ -145,13 +169,21 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => { }; } - case "delete_meeting": { + 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) { diff --git a/operations/meeting.ts b/operations/meeting.ts index bc4267b..0ebda2c 100644 --- a/operations/meeting.ts +++ b/operations/meeting.ts @@ -1,23 +1,29 @@ import { z } from "zod"; import { zoomRequest } from "../common/util.js"; -import { ZoomListMeetingsSchema, ZoomMeetingSchema } from "../common/types.js"; +import { + ZoomListMeetingsSchema, + ZoomMeetingDetailSchema, + ZoomMeetingSchema, +} from "../common/types.js"; export const CreateMeetingOptionsSchema = z.object({ agenda: z .string() .max(2000) - .describe("The meeting's agenda") + .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.", + `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"), + .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."), }); @@ -35,9 +41,14 @@ 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; export type ListMeetingOptions = z.infer; export type DeleteMeetingOptions = z.infer; +export type GetMeetingOptions = z.infer; export async function createMeeting(options: CreateMeetingOptions) { const response = await zoomRequest( @@ -76,3 +87,13 @@ export async function deleteMeeting(options: DeleteMeetingOptions) { ); 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); +} diff --git a/package-lock.json b/package-lock.json index be2579e..1190ccf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { - "name": "zoom-mcp-server", - "version": "1.0.0", + "name": "@yitianyigexiangfa/zoom-mcp-server", + "version": "0.6.3", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "zoom-mcp-server", - "version": "1.0.0", + "name": "@yitianyigexiangfa/zoom-mcp-server", + "version": "0.6.3", "license": "MIT", "dependencies": { "@modelcontextprotocol/sdk": "1.9.0", diff --git a/package.json b/package.json index 4e68eb4..ade6c13 100644 --- a/package.json +++ b/package.json @@ -1,12 +1,12 @@ { - "name": "zoom-mcp-server", - "version": "v0.6", + "name": "@yitianyigexiangfa/zoom-mcp-server", + "version": "0.7.2", "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 dist/index.js" + "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", @@ -48,5 +48,8 @@ }, "lint-staged": { "**/*": "prettier --write --ignore-unknown" + }, + "publishConfig": { + "access": "public" } }