Skip to main content
Glama

Liveblocks

Official
by liveblocks
server.ts18.3 kB
import { z } from "zod"; import { callLiveblocksApi } from "./utils.js"; import { Liveblocks } from "@liveblocks/node"; import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { CommentBody, DefaultAccesses, GroupsAccesses, UsersAccesses, } from "./zod.js"; export const server = new McpServer({ name: "liveblocks-mcp-server", version: "1.0.0", }); // === Setup ======================================================== let client: Liveblocks; function getLiveblocks() { if (!client) { client = new Liveblocks({ secret: process.env.LIVEBLOCKS_SECRET_KEY as string, }); } return client; } // === Rooms ======================================================== server.tool( "get-rooms", `Get recent Liveblocks rooms`, { limit: z.number().lte(100), userId: z.string().optional(), groupIds: z.array(z.string()).optional(), startingAfter: z.string().optional(), query: z .object({ roomId: z .object({ startsWith: z.string(), }) .optional(), metadata: z.record(z.string(), z.string()).optional(), }) .optional(), }, async ({ limit, userId, groupIds, startingAfter, query }, extra) => { return await callLiveblocksApi( getLiveblocks().getRooms( { limit, userId, groupIds, startingAfter, query }, { signal: extra.signal } ) ); } ); server.tool( "create-room", "Create a Liveblocks room", { roomId: z.string(), defaultAccesses: DefaultAccesses, groupsAccesses: GroupsAccesses.optional(), usersAccesses: UsersAccesses.optional(), metadata: z.record(z.string(), z.string()).optional(), }, async ( { roomId, defaultAccesses, groupsAccesses, usersAccesses, metadata }, extra ) => { return await callLiveblocksApi( getLiveblocks().createRoom( roomId, { defaultAccesses: defaultAccesses as any, groupsAccesses: groupsAccesses as any, usersAccesses: usersAccesses as any, metadata, }, { signal: extra.signal } ) ); } ); server.tool( "get-room", "Get a Liveblocks room", { roomId: z.string(), }, async ({ roomId }, extra) => { return await callLiveblocksApi( getLiveblocks().getRoom(roomId, { signal: extra.signal }) ); } ); server.tool( "update-room", "Update a Liveblocks room", { roomId: z.string(), defaultAccesses: DefaultAccesses, groupsAccesses: GroupsAccesses.optional(), usersAccesses: UsersAccesses.optional(), metadata: z.record(z.string(), z.union([z.string(), z.null()])).optional(), }, async ( { roomId, defaultAccesses, groupsAccesses, usersAccesses, metadata }, extra ) => { return await callLiveblocksApi( getLiveblocks().updateRoom( roomId, { defaultAccesses: defaultAccesses as any, groupsAccesses: groupsAccesses as any, usersAccesses: usersAccesses as any, metadata, }, { signal: extra.signal } ) ); } ); server.tool( "delete-room", "Delete a Liveblocks room", { roomId: z.string(), }, async ({ roomId }, extra) => { return await callLiveblocksApi( getLiveblocks().deleteRoom(roomId, { signal: extra.signal }) ); } ); server.tool( "update-room-id", "Update a Liveblocks room's ID", { roomId: z.string(), newRoomId: z.string(), }, async ({ roomId, newRoomId }, extra) => { return await callLiveblocksApi( getLiveblocks().updateRoomId( { currentRoomId: roomId, newRoomId }, { signal: extra.signal } ) ); } ); server.tool( "get-active-users", "Get a Liveblocks room's active users", { roomId: z.string(), }, async ({ roomId }, extra) => { return await callLiveblocksApi( getLiveblocks().getActiveUsers(roomId, { signal: extra.signal }) ); } ); server.tool( "broadcast-event", "Broadcast an event to a Liveblocks room", { roomId: z.string(), event: z.record(z.string(), z.any()), }, async ({ roomId, event }, extra) => { return await callLiveblocksApi( getLiveblocks().broadcastEvent(roomId, event, { signal: extra.signal }) ); } ); // === Storage ====================================================== server.tool( "get-storage-document", "Get a Liveblocks storage document", { roomId: z.string(), }, async ({ roomId }, extra) => { return await callLiveblocksApi( getLiveblocks().getStorageDocument(roomId, "json", { signal: extra.signal, }) ); } ); // === Yjs ========================================================== server.tool( "get-yjs-document", "Get a Liveblocks Yjs text document", { roomId: z.string(), options: z .object({ format: z.boolean().optional(), key: z.string().optional(), type: z.string().optional(), }) .optional(), }, async ({ roomId, options }, extra) => { return await callLiveblocksApi( getLiveblocks().getYjsDocument(roomId, options, { signal: extra.signal }) ); } ); // === Comments ===================================================== server.tool( "get-threads", `Get recent Liveblocks threads`, { roomId: z.string(), query: z .object({ resolved: z.boolean().optional(), metadata: z .record( z.string(), z.union([ z.string(), z.object({ startsWith: z.string(), }), ]) ) .optional(), }) .optional(), }, async ({ roomId, query }, extra) => { return await callLiveblocksApi( getLiveblocks().getThreads({ roomId, query }, { signal: extra.signal }) ); } ); server.tool( "create-thread", `Create a Liveblocks thread. Always ask for a userId.`, { roomId: z.string(), data: z.object({ comment: z.object({ body: CommentBody, userId: z.string(), createdAt: z.date().optional(), }), metadata: z .record(z.string(), z.union([z.string(), z.boolean(), z.number()])) .optional(), }), }, async ({ roomId, data }, extra) => { return await callLiveblocksApi( getLiveblocks().createThread({ roomId, data }, { signal: extra.signal }) ); } ); server.tool( "get-thread", "Get a Liveblocks thread", { roomId: z.string(), threadId: z.string(), }, async ({ roomId, threadId }, extra) => { return await callLiveblocksApi( getLiveblocks().getThread({ roomId, threadId }, { signal: extra.signal }) ); } ); server.tool( "get-thread-participants", "Get a Liveblocks thread's participants", { roomId: z.string(), threadId: z.string(), }, async ({ roomId, threadId }, extra) => { return await callLiveblocksApi( getLiveblocks().getThreadParticipants( { roomId, threadId }, { signal: extra.signal } ) ); } ); server.tool( "edit-thread-metadata", `Edit a Liveblocks thread's metadata. \`null\` can be used to remove a key.`, { roomId: z.string(), threadId: z.string(), data: z.object({ metadata: z.record( z.string(), z.union([z.string(), z.boolean(), z.number(), z.null()]) ), userId: z.string(), updatedAt: z.date().optional(), }), }, async ({ roomId, threadId, data }, extra) => { return await callLiveblocksApi( getLiveblocks().editThreadMetadata( { roomId, threadId, data }, { signal: extra.signal } ) ); } ); server.tool( "mark-thread-as-resolved", "Mark a Liveblocks thread as resolved", { roomId: z.string(), threadId: z.string(), data: z.object({ userId: z.string(), }), }, async ({ roomId, threadId, data }, extra) => { return await callLiveblocksApi( getLiveblocks().markThreadAsResolved( { roomId, threadId, data }, { signal: extra.signal } ) ); } ); server.tool( "mark-thread-as-unresolved", "Mark a Liveblocks thread as unresolved", { roomId: z.string(), threadId: z.string(), data: z.object({ userId: z.string(), }), }, async ({ roomId, threadId, data }, extra) => { return await callLiveblocksApi( getLiveblocks().markThreadAsUnresolved( { roomId, threadId, data }, { signal: extra.signal } ) ); } ); server.tool( "delete-thread", "Delete a Liveblocks thread", { roomId: z.string(), threadId: z.string(), }, async ({ roomId, threadId }, extra) => { return await callLiveblocksApi( getLiveblocks().deleteThread( { roomId, threadId }, { signal: extra.signal } ) ); } ); server.tool( "subscribe-to-thread", "Subscribe to a Liveblocks thread", { roomId: z.string(), threadId: z.string(), data: z.object({ userId: z.string(), }), }, async ({ roomId, threadId, data }, extra) => { return await callLiveblocksApi( getLiveblocks().subscribeToThread( { roomId, threadId, data }, { signal: extra.signal } ) ); } ); server.tool( "unsubscribe-from-thread", "Unsubscribe from a Liveblocks thread", { roomId: z.string(), threadId: z.string(), data: z.object({ userId: z.string(), }), }, async ({ roomId, threadId, data }, extra) => { return await callLiveblocksApi( getLiveblocks().unsubscribeFromThread( { roomId, threadId, data }, { signal: extra.signal } ) ); } ); server.tool( "get-thread-subscriptions", "Get a Liveblocks thread's subscriptions", { roomId: z.string(), threadId: z.string(), }, async ({ roomId, threadId }, extra) => { return await callLiveblocksApi( getLiveblocks().getThreadSubscriptions( { roomId, threadId }, { signal: extra.signal } ) ); } ); server.tool( "create-comment", `Create a Liveblocks comment. Always ask for a userId.`, { roomId: z.string(), threadId: z.string(), data: z.object({ body: CommentBody, userId: z.string(), createdAt: z.date().optional(), }), }, async ({ roomId, threadId, data }, extra) => { return await callLiveblocksApi( getLiveblocks().createComment( { roomId, threadId, data }, { signal: extra.signal } ) ); } ); server.tool( "get-comment", `Get a Liveblocks comment`, { roomId: z.string(), threadId: z.string(), commentId: z.string(), }, async ({ roomId, threadId, commentId }, extra) => { return await callLiveblocksApi( getLiveblocks().getComment( { roomId, threadId, commentId }, { signal: extra.signal } ) ); } ); server.tool( "edit-comment", `Edit a Liveblocks comment`, { roomId: z.string(), threadId: z.string(), commentId: z.string(), data: z.object({ body: CommentBody, userId: z.string(), editedAt: z.date().optional(), }), }, async ({ roomId, threadId, commentId, data }, extra) => { return await callLiveblocksApi( getLiveblocks().editComment( { roomId, threadId, commentId, data }, { signal: extra.signal } ) ); } ); server.tool( "delete-comment", `Delete a Liveblocks comment`, { roomId: z.string(), threadId: z.string(), commentId: z.string(), }, async ({ roomId, threadId, commentId }, extra) => { return await callLiveblocksApi( getLiveblocks().deleteComment( { roomId, threadId, commentId }, { signal: extra.signal } ) ); } ); server.tool( "add-comment-reaction", `Add a reaction to a Liveblocks comment`, { roomId: z.string(), threadId: z.string(), commentId: z.string(), data: z.object({ emoji: z.string(), userId: z.string(), createdAt: z.date().optional(), }), }, async ({ roomId, threadId, commentId, data }, extra) => { return await callLiveblocksApi( getLiveblocks().addCommentReaction( { roomId, threadId, commentId, data }, { signal: extra.signal } ) ); } ); server.tool( "remove-comment-reaction", `Remove a reaction from a Liveblocks comment`, { roomId: z.string(), threadId: z.string(), commentId: z.string(), data: z.object({ emoji: z.string(), userId: z.string(), removedAt: z.date().optional(), }), }, async ({ roomId, threadId, commentId, data }, extra) => { return await callLiveblocksApi( getLiveblocks().removeCommentReaction( { roomId, threadId, commentId, data }, { signal: extra.signal } ) ); } ); server.tool( "get-room-subscription-settings", `Get a Liveblocks room's subscription settings`, { roomId: z.string(), userId: z.string(), }, async ({ roomId, userId }, extra) => { return await callLiveblocksApi( getLiveblocks().getRoomSubscriptionSettings( { roomId, userId }, { signal: extra.signal } ) ); } ); server.tool( "update-room-subscription-settings", `Update a Liveblocks room's subscription settings`, { roomId: z.string(), userId: z.string(), data: z.object({ threads: z .union([ z.literal("all"), z.literal("replies_and_mentions"), z.literal("none"), ]) .optional(), textMentions: z.union([z.literal("mine"), z.literal("none")]).optional(), }), }, async ({ roomId, userId, data }, extra) => { return await callLiveblocksApi( getLiveblocks().updateRoomSubscriptionSettings( { roomId, userId, data }, { signal: extra.signal } ) ); } ); server.tool( "delete-room-subscription-settings", `Delete a Liveblocks room's subscription settings`, { roomId: z.string(), userId: z.string(), }, async ({ roomId, userId }, extra) => { return await callLiveblocksApi( getLiveblocks().deleteRoomSubscriptionSettings( { roomId, userId }, { signal: extra.signal } ) ); } ); server.tool( "get-user-room-subscription-settings", `Get a user's room subscription settings`, { userId: z.string(), }, async ({ userId }, extra) => { return await callLiveblocksApi( getLiveblocks().getUserRoomSubscriptionSettings( { userId }, { signal: extra.signal } ) ); } ); // === Notifications ================================================ server.tool( "get-inbox-notifications", `Get recent Liveblocks inbox notifications`, { userId: z.string(), query: z .object({ unread: z.boolean(), }) .optional(), startingAfter: z.string().optional(), limit: z.number().optional(), }, async ({ userId, query, startingAfter, limit }, extra) => { return await callLiveblocksApi( getLiveblocks().getInboxNotifications( { userId, query, startingAfter, limit }, { signal: extra.signal } ) ); } ); server.tool( "get-inbox-notification", "Get a Liveblocks inbox notification", { userId: z.string(), inboxNotificationId: z.string(), }, async ({ userId, inboxNotificationId }, extra) => { return await callLiveblocksApi( getLiveblocks().getInboxNotification( { userId, inboxNotificationId }, { signal: extra.signal } ) ); } ); server.tool( "trigger-inbox-notification", "Create a custom Liveblocks inbox notification", { userId: z.string(), kind: z.string().regex(/^\$/, { message: "String must start with '$'", }), subjectId: z.string(), activityData: z .record(z.string(), z.union([z.string(), z.boolean(), z.number()])) .describe("Custom data related to the notification"), roomId: z .string() .optional() .describe("Don't add this unless specifically asked"), }, async ({ userId, kind, subjectId, activityData, roomId }, extra) => { return await callLiveblocksApi( getLiveblocks().triggerInboxNotification( { userId, kind: kind as `$${string}`, subjectId, activityData, roomId }, { signal: extra.signal } ) ); } ); server.tool( "delete-inbox-notification", "Delete a Liveblocks inbox notification", { userId: z.string(), inboxNotificationId: z.string(), }, async ({ userId, inboxNotificationId }, extra) => { return await callLiveblocksApi( getLiveblocks().deleteInboxNotification( { userId, inboxNotificationId }, { signal: extra.signal } ) ); } ); server.tool( "delete-all-inbox-notifications", "Delete all Liveblocks inbox notifications", { userId: z.string(), }, async ({ userId }, extra) => { return await callLiveblocksApi( getLiveblocks().deleteAllInboxNotifications( { userId }, { signal: extra.signal } ) ); } ); server.tool( "get-notification-settings", "Get a Liveblocks notification settings", { userId: z.string(), }, async ({ userId }, extra) => { return await callLiveblocksApi( getLiveblocks().getNotificationSettings( { userId }, { signal: extra.signal } ) ); } ); server.tool( "update-notification-settings", "Update Liveblocks notification settings", { userId: z.string(), data: z.record( z.string(), z.record( z.union([ z.literal("thread"), z.literal("textMention"), z.string().regex(/^\$/, { message: "String must start with '$'", }), ]), z.boolean() ) ), }, async ({ userId }, extra) => { return await callLiveblocksApi( getLiveblocks().getNotificationSettings( { userId }, { signal: extra.signal } ) ); } ); server.tool( "delete-notification-settings", "Delete Liveblocks notification settings", { userId: z.string(), }, async ({ userId }, extra) => { return await callLiveblocksApi( getLiveblocks().deleteNotificationSettings( { userId }, { signal: extra.signal } ) ); } );

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/liveblocks/liveblocks-mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server