Skip to main content
Glama

Carbon Voice

by PhononX
server.ts27.4 kB
import { z } from 'zod'; import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import { setCarbonVoiceAuthHeader } from './auth'; import { SERVICE_NAME, SERVICE_VERSION } from './constants'; import { getCarbonVoiceAPI } from './cv-api'; import { getCarbonVoiceSimplifiedAPI } from './generated'; import { addLinkAttachmentsToMessageBody, addLinkAttachmentsToMessageParams, addMessageToFolderOrWorkspaceBody, aIPromptControllerGetPromptsQueryParams, aIResponseControllerCreateResponseBody, aIResponseControllerGetAllResponsesQueryParams, createConversationMessageBody, createConversationMessageParams, createFolderBody, createShareLinkAIResponseBody, createVoiceMemoMessageBody, deleteFolderParams, getAllRootFoldersQueryParams, getConversationByIdParams, getFolderByIdParams, getFolderByIdQueryParams, getFolderMessagesParams, getMessageByIdParams, getMessageByIdQueryParams, getTenRecentMessagesResponseQueryParams, getUserByIdParams, listMessagesQueryParams, moveFolderBody, moveFolderParams, searchUserQueryParams, searchUsersBody, sendDirectMessageBody, updateFolderNameBody, updateFolderNameParams, } from './generated/carbon-voice-api/CarbonVoiceSimplifiedAPI.zod'; import { AddMessageToFolderPayload, AIPromptControllerGetPromptsParams, AIResponseControllerGetAllResponsesParams, CreateAIResponse, CreateFolderPayload, CreateShareLinkAIResponse, CreateVoicememoMessage, GetAllRootFoldersParams, GetTenRecentMessagesResponseParams, ListMessagesParams, SearchUserParams, SearchUsersBody, SendDirectMessage, } from './generated/models'; import { AddLinkAttachmentsToMessageInput, CreateConversationMessageInput, GetByIdParams, GetFolderInput, GetMessageInput, McpToolResponse, MoveFolderInput, UpdateFolderNameInput, } from './interfaces'; import { SummarizeConversationParams } from './interfaces/conversation.interface'; import { summarizeConversationParams } from './schemas'; import { formatToMCPToolResponse, logger } from './utils'; // Create server instance const server = new McpServer({ name: SERVICE_NAME, version: SERVICE_VERSION, capabilities: { resources: {}, tools: {}, }, }); const simplifiedApi = getCarbonVoiceSimplifiedAPI(); const cvApi = getCarbonVoiceAPI(); /********************** * Tools *********************/ // Messages server.registerTool( 'list_messages', { description: 'List Messages. By default returns latest 20 messages. The maximum allowed range between dates is 183 days (6 months). ' + 'All presigned URLs returned by this tool are ready to use. ' + 'Do not parse, modify, or re-encode them—always present or use the URLs exactly as received.' + 'If you want to get messages from a specific date range, you can use the "start_date" and "end_date" parameters. ' + 'If you want to get messages from a specific date, you can use the "date" parameter. ' + 'If you want to get messages from a specific user, you can use the "user_ids" parameter. ' + 'If you want to get messages from a specific conversation, you can use the "conversation_id" parameter. ' + 'If you want to get messages from a specific folder, you can use the "folder_id" parameter. ' + 'If you want to get messages from a specific workspace, you can use the "workspace_id" parameter. ' + 'If you want to get messages for a particular language, you can use the "language" parameter. ', inputSchema: listMessagesQueryParams.shape, annotations: { readOnlyHint: true, destructiveHint: false, }, }, async ( params: ListMessagesParams, { authInfo }, ): Promise<McpToolResponse> => { try { // Fallback to regular API return formatToMCPToolResponse( await simplifiedApi.listMessages( params, setCarbonVoiceAuthHeader(authInfo?.token), ), ); } catch (error) { logger.error('Error listing messages:', { params, error }); return formatToMCPToolResponse(error); } }, ); server.registerTool( 'get_message', { description: 'Get a message by its ID.', inputSchema: getMessageByIdParams.merge(getMessageByIdQueryParams).shape, annotations: { readOnlyHint: true, destructiveHint: false, }, }, async (args: GetMessageInput, { authInfo }): Promise<McpToolResponse> => { try { const { id, ...queryParams } = args; return formatToMCPToolResponse( await simplifiedApi.getMessageById( id, queryParams, setCarbonVoiceAuthHeader(authInfo?.token), ), ); } catch (error) { logger.error('Error getting message by id:', { args, error }); return formatToMCPToolResponse(error); } }, ); server.registerTool( 'get_recent_messages', { description: 'Get most recent messages, including their associated Conversation, Creator, and Labels information. ' + 'Returns a maximum of 10 messages.', inputSchema: getTenRecentMessagesResponseQueryParams.shape, annotations: { readOnlyHint: true, destructiveHint: false, }, }, async ( args: GetTenRecentMessagesResponseParams, { authInfo }, ): Promise<McpToolResponse> => { try { return formatToMCPToolResponse( await simplifiedApi.getTenRecentMessagesResponse( args, setCarbonVoiceAuthHeader(authInfo?.token), ), ); } catch (error) { logger.error('Error getting recent messages:', { args, error }); return formatToMCPToolResponse(error); } }, ); server.registerTool( 'create_conversation_message', { description: 'Sends a message to an existing conversation or any type with a conversation_id. ' + 'To reply as a thread, included a message_id for "parent_id". You must provide a transcript or attachment.', inputSchema: createConversationMessageParams.merge( createConversationMessageBody, ).shape, annotations: { readOnlyHint: false, destructiveHint: false, }, }, async ( args: CreateConversationMessageInput, { authInfo }, ): Promise<McpToolResponse> => { try { return formatToMCPToolResponse( await simplifiedApi.createConversationMessage( args.id, args, setCarbonVoiceAuthHeader(authInfo?.token), ), ); } catch (error) { logger.error('Error creating conversation message:', { args, error }); return formatToMCPToolResponse(error); } }, ); server.registerTool( 'create_direct_message', { description: 'Send a Direct Message (DM) to a User or a Group of Users. ' + 'In order to create a Direct Message, you must provide transcript or link attachments.', inputSchema: sendDirectMessageBody.shape, annotations: { readOnlyHint: false, destructiveHint: false, }, }, async (args: SendDirectMessage, { authInfo }): Promise<McpToolResponse> => { try { return formatToMCPToolResponse( await simplifiedApi.sendDirectMessage( args, setCarbonVoiceAuthHeader(authInfo?.token), ), ); } catch (error) { logger.error('Error creating direct message:', { args, error }); return formatToMCPToolResponse(error); } }, ); server.registerTool( 'create_voicememo_message', { description: 'Create a VoiceMemo Message. In order to create a VoiceMemo Message, you must provide a transcript or link attachments.', inputSchema: createVoiceMemoMessageBody.shape, annotations: { readOnlyHint: false, destructiveHint: false, }, }, async ( args: CreateVoicememoMessage, { authInfo }, ): Promise<McpToolResponse> => { try { return formatToMCPToolResponse( await simplifiedApi.createVoiceMemoMessage( args, setCarbonVoiceAuthHeader(authInfo?.token), ), ); } catch (error) { logger.error('Error creating voicememo message:', { args, error }); return formatToMCPToolResponse(error); } }, ); server.registerTool( 'add_attachments_to_message', { description: 'Add attachments to a message. In order to add attachments to a message, you must provide a message id and the attachments.', inputSchema: addLinkAttachmentsToMessageParams.merge( addLinkAttachmentsToMessageBody, ).shape, annotations: { readOnlyHint: false, destructiveHint: false, }, }, async ( args: AddLinkAttachmentsToMessageInput, { authInfo }, ): Promise<McpToolResponse> => { try { return formatToMCPToolResponse( await simplifiedApi.addLinkAttachmentsToMessage( args.id, { links: args.links, }, setCarbonVoiceAuthHeader(authInfo?.token), ), ); } catch (error) { logger.error('Error adding attachments to message:', { args, error }); return formatToMCPToolResponse(error); } }, ); // Users server.registerTool( 'get_user', { description: 'Get a User by their ID.', inputSchema: getUserByIdParams.shape, annotations: { readOnlyHint: true, destructiveHint: false, }, }, async (args: GetByIdParams, { authInfo }): Promise<McpToolResponse> => { try { return formatToMCPToolResponse( await simplifiedApi.getUserById( args.id, setCarbonVoiceAuthHeader(authInfo?.token), ), ); } catch (error) { logger.error('Error getting user by id:', { args, error }); return formatToMCPToolResponse(error); } }, ); server.registerTool( 'search_user', { description: 'Search for a User by their phone number, email address, id or name. ' + '(In order to search for a User, you must provide a phone number, email address, id or name.)' + 'When searching by name, only users that are part of your contacts will be returned', inputSchema: searchUserQueryParams.shape, annotations: { readOnlyHint: true, destructiveHint: false, }, }, async (args: SearchUserParams, { authInfo }): Promise<McpToolResponse> => { try { return formatToMCPToolResponse( await simplifiedApi.searchUser( args, setCarbonVoiceAuthHeader(authInfo?.token), ), ); } catch (error) { logger.error('Error searching for user:', { args, error }); return formatToMCPToolResponse(error); } }, ); server.registerTool( 'search_users', { description: 'Search multiple Users by their phone numbers, email addresses, ids or names. ' + '(In order to search Users, you must provide phone numbers, email addresses, ids or names.)' + 'When searching by name, only users that are part of your contacts will be returned', inputSchema: searchUsersBody.shape, annotations: { readOnlyHint: true, destructiveHint: false, }, }, async (args: SearchUsersBody, { authInfo }): Promise<McpToolResponse> => { try { return formatToMCPToolResponse( await simplifiedApi.searchUsers( args, setCarbonVoiceAuthHeader(authInfo?.token), ), ); } catch (error) { logger.error('Error searching users:', { args, error }); return formatToMCPToolResponse(error); } }, ); server.registerTool( 'get_current_user', { description: 'Get the current user information. ', inputSchema: z.object({}).shape, // Needed in order to have access to authInfo annotations: { readOnlyHint: true, destructiveHint: false, }, }, async (params: unknown, { authInfo }): Promise<McpToolResponse> => { try { return formatToMCPToolResponse( await cvApi.getWhoAmI(setCarbonVoiceAuthHeader(authInfo?.token)), ); } catch (error) { logger.error('Error searching users:', { params, error }); return formatToMCPToolResponse(error); } }, ); // Conversations server.registerTool( 'list_conversations', { description: 'List all conversations. ' + 'Returns a simplified view of user conversations that have had messages sent or received within the last 6 months.', inputSchema: z.object({}).shape, annotations: { readOnlyHint: true, destructiveHint: false, }, }, async (args: unknown, { authInfo }): Promise<McpToolResponse> => { try { return formatToMCPToolResponse( await simplifiedApi.getAllConversations( setCarbonVoiceAuthHeader(authInfo?.token), ), ); } catch (error) { logger.error('Error listing conversations:', { error }); return formatToMCPToolResponse(error); } }, ); server.registerTool( 'get_conversation', { description: 'Get a conversation by its ID.', inputSchema: getConversationByIdParams.shape, annotations: { readOnlyHint: true, destructiveHint: false, }, }, async (args: GetByIdParams, { authInfo }): Promise<McpToolResponse> => { try { return formatToMCPToolResponse( await simplifiedApi.getConversationById( args.id, setCarbonVoiceAuthHeader(authInfo?.token), ), ); } catch (error) { logger.error('Error getting conversation by id:', { error }); return formatToMCPToolResponse(error); } }, ); server.registerTool( 'get_conversation_users', { description: 'Get users in a conversation.', inputSchema: getConversationByIdParams.shape, annotations: { readOnlyHint: true, destructiveHint: false, }, }, async (args: GetByIdParams, { authInfo }): Promise<McpToolResponse> => { try { return formatToMCPToolResponse( await simplifiedApi.getConversationUsers( args.id, setCarbonVoiceAuthHeader(authInfo?.token), ), ); } catch (error) { logger.error('Error getting conversation users:', { error }); return formatToMCPToolResponse(error); } }, ); server.registerTool( 'summarize_conversation', { description: 'Summarize a conversation.', inputSchema: summarizeConversationParams.shape, annotations: { readOnlyHint: false, destructiveHint: false, }, }, async ( args: SummarizeConversationParams, { authInfo }, ): Promise<McpToolResponse> => { try { let message_ids: string[] = args.message_ids || []; // If no message ids are provided, get couple of messages from the conversation if (!args.message_ids) { const messages = await simplifiedApi.listMessages( args, setCarbonVoiceAuthHeader(authInfo?.token), ); message_ids = messages.results?.map((message) => message.id) || []; } const aiResponse = await simplifiedApi.aIResponseControllerCreateResponse( { prompt_id: args.prompt_id, message_ids: message_ids, channel_id: args.conversation_id, language: args.language, }, setCarbonVoiceAuthHeader(authInfo?.token), ); return formatToMCPToolResponse(aiResponse); } catch (error) { logger.error('Error summarizing conversation:', { error }); return formatToMCPToolResponse(error); } }, ); // TODO: First we need to implement List messages filtered by unread messages // server.registerTool( // 'catch_up_conversation', // { // description: 'Catch up a conversation.', // inputSchema: catchUpConversationParams.shape, // }, // async (args: CatchUpConversationParams): Promise<McpToolResponse> => { // try { // let message_ids: string[] = args.message_ids || []; // // If no message ids are provided, get couple of messages from the conversation // if (!args.message_ids?.length) { // const messages = await api.listMessages(args); // message_ids = messages.results?.map((message) => message.id) || []; // } // const aiResponse = await api.aIResponseControllerCreateResponse({ // prompt_id: args.prompt_id, // message_ids: message_ids, // channel_id: args.conversation_id, // language: args.language, // }); // return formatToMCPToolResponse(aiResponse); // } catch (error) { // logger.error('Error catching up conversation:', { error }); // return formatToMCPToolResponse(error); // } // }, // ); // Folders // server.registerTool( // 'get_workspace_folders_and_message_counts', // { // description: // 'Returns, for each workspace, the total number of folders and messages, as well as a breakdown of folders, ' + // 'messages, and messages not in any folder.(Required to inform message type:voicememo,prerecorded)', // inputSchema: getCountsGroupedByWorkspaceQueryParams.shape, // }, // async (args: GetCountsGroupedByWorkspaceParams): Promise<McpToolResponse> => { // try { // return formatToMCPToolResponse( // await api.getCountsGroupedByWorkspace(args), // ); // } catch (error) { // logger.error('Error listing workspace folders:', { error }); // return formatToMCPToolResponse(error); // } // }, // ); server.registerTool( 'get_root_folders', { description: 'Lists all root folders for a given workspace, including their names, IDs, and basic structure, ' + 'but does not provide aggregate counts.(Required to inform message type:voicememo,prerecorded)', inputSchema: getAllRootFoldersQueryParams.shape, annotations: { readOnlyHint: true, destructiveHint: false, }, }, async ( args: GetAllRootFoldersParams, { authInfo }, ): Promise<McpToolResponse> => { try { return formatToMCPToolResponse( await simplifiedApi.getAllRootFolders( args, setCarbonVoiceAuthHeader(authInfo?.token), ), ); } catch (error) { logger.error('Error listing root folders:', { error }); return formatToMCPToolResponse(error); } }, ); server.registerTool( 'create_folder', { description: 'Create a new folder.', inputSchema: createFolderBody.shape, annotations: { readOnlyHint: false, destructiveHint: false, }, }, async (args: CreateFolderPayload, { authInfo }): Promise<McpToolResponse> => { try { return formatToMCPToolResponse( await simplifiedApi.createFolder( args, setCarbonVoiceAuthHeader(authInfo?.token), ), ); } catch (error) { logger.error('Error creating folder:', { error }); return formatToMCPToolResponse(error); } }, ); server.registerTool( 'get_folder', { description: 'Get a folder by its ID.', inputSchema: getFolderByIdParams.merge(getFolderByIdQueryParams).shape, annotations: { readOnlyHint: true, destructiveHint: false, }, }, async (args: GetFolderInput, { authInfo }): Promise<McpToolResponse> => { try { return formatToMCPToolResponse( await simplifiedApi.getFolderById( args.id, args, setCarbonVoiceAuthHeader(authInfo?.token), ), ); } catch (error) { logger.error('Error getting folder by id:', { error }); return formatToMCPToolResponse(error); } }, ); server.registerTool( 'get_folder_with_messages', { description: 'Get a folder including its messages by its ID. (Only messages at folder level are returned.)', inputSchema: getFolderMessagesParams.shape, annotations: { readOnlyHint: true, destructiveHint: false, }, }, async (args: GetByIdParams, { authInfo }): Promise<McpToolResponse> => { try { return formatToMCPToolResponse( await simplifiedApi.getFolderMessages( args.id, setCarbonVoiceAuthHeader(authInfo?.token), ), ); } catch (error) { logger.error('Error getting folder with messages:', { error }); return formatToMCPToolResponse(error); } }, ); server.registerTool( 'update_folder_name', { description: 'Update a folder name by its ID.', inputSchema: updateFolderNameParams.merge(updateFolderNameBody).shape, annotations: { readOnlyHint: false, destructiveHint: false, }, }, async ( args: UpdateFolderNameInput, { authInfo }, ): Promise<McpToolResponse> => { try { return formatToMCPToolResponse( await simplifiedApi.updateFolderName( args.id, args, setCarbonVoiceAuthHeader(authInfo?.token), ), ); } catch (error) { logger.error('Error updating folder name:', { error }); return formatToMCPToolResponse(error); } }, ); server.registerTool( 'delete_folder', { description: 'Delete a folder by its ID. Deleting a folder will also delete nested folders and all the messages in referenced folders. ' + '(This is a destructive action and cannot be undone, so please be careful.)', inputSchema: deleteFolderParams.shape, annotations: { readOnlyHint: false, destructiveHint: true, }, }, async (args: GetByIdParams, { authInfo }): Promise<McpToolResponse> => { try { return formatToMCPToolResponse( await simplifiedApi.deleteFolder( args.id, setCarbonVoiceAuthHeader(authInfo?.token), ), ); } catch (error) { logger.error('Error deleting folder:', { error }); return formatToMCPToolResponse(error); } }, ); server.registerTool( 'move_folder', { description: 'Move a folder by its ID. Move a Folder into another Folder or into a Workspace.', inputSchema: moveFolderParams.merge(moveFolderBody).shape, annotations: { readOnlyHint: false, destructiveHint: false, }, }, async (args: MoveFolderInput, { authInfo }): Promise<McpToolResponse> => { try { return formatToMCPToolResponse( await simplifiedApi.moveFolder( args.id, args, setCarbonVoiceAuthHeader(authInfo?.token), ), ); } catch (error) { logger.error('Error moving folder:', { error }); return formatToMCPToolResponse(error); } }, ); server.registerTool( 'move_message_to_folder', { description: 'Move a message to a folder by its ID. Move a Message into another Folder or into a Workspace. ' + 'Only allowed to move messages of type: voicememo,prerecorded.', inputSchema: addMessageToFolderOrWorkspaceBody.shape, annotations: { readOnlyHint: false, destructiveHint: false, }, }, async ( args: AddMessageToFolderPayload, { authInfo }, ): Promise<McpToolResponse> => { try { return formatToMCPToolResponse( await simplifiedApi.addMessageToFolderOrWorkspace( args, setCarbonVoiceAuthHeader(authInfo?.token), ), ); } catch (error) { logger.error('Error moving message to folder:', { error }); return formatToMCPToolResponse(error); } }, ); // Workspace server.registerTool( 'get_workspaces_basic_info', { description: 'Get basic information about a workspace.', inputSchema: z.object({}).shape, annotations: { readOnlyHint: true, destructiveHint: false, }, }, async (params: unknown, { authInfo }): Promise<McpToolResponse> => { try { return formatToMCPToolResponse( await simplifiedApi.getAllWorkspacesWithBasicInfo( setCarbonVoiceAuthHeader(authInfo?.token), ), ); } catch (error) { logger.error('Error getting workspaces basic info:', { error }); return formatToMCPToolResponse(error); } }, ); // AI Magic server.registerTool( 'list_ai_actions', { description: 'List AI Actions (Prompts). Optionally, you can filter by owner type and workspace id. ' + 'Filtering by owner type, Possible values: "user", "workspace", "system". ' + 'Do not use unless the user explicitly requests it.', inputSchema: aIPromptControllerGetPromptsQueryParams.shape, annotations: { readOnlyHint: true, destructiveHint: false, }, }, async ( args: AIPromptControllerGetPromptsParams, { authInfo }, ): Promise<McpToolResponse> => { try { return formatToMCPToolResponse( await simplifiedApi.aIPromptControllerGetPrompts( args, setCarbonVoiceAuthHeader(authInfo?.token), ), ); } catch (error) { logger.error('Error listing ai actions:', { error }); return formatToMCPToolResponse(error); } }, ); server.registerTool( 'run_ai_action', { description: 'Run an AI Action (Prompt) for a message. You can run an AI Action for a message by its ID or a list of message IDs.', inputSchema: aIResponseControllerCreateResponseBody.shape, annotations: { readOnlyHint: false, destructiveHint: false, }, }, async (args: CreateAIResponse, { authInfo }): Promise<McpToolResponse> => { try { return formatToMCPToolResponse( await simplifiedApi.aIResponseControllerCreateResponse( args, setCarbonVoiceAuthHeader(authInfo?.token), ), ); } catch (error) { logger.error('Error running ai action:', { error }); return formatToMCPToolResponse(error); } }, ); server.registerTool( 'run_ai_action_for_shared_link', { description: 'Run an AI Action (Prompt) for a shared link. You can run an AI Action for a shared link by its ID or a list of shared link IDs. ' + 'You can also provide the language of the response.', inputSchema: createShareLinkAIResponseBody.shape, annotations: { readOnlyHint: false, destructiveHint: false, }, }, async ( args: CreateShareLinkAIResponse, { authInfo }, ): Promise<McpToolResponse> => { try { return formatToMCPToolResponse( await simplifiedApi.createShareLinkAIResponse( args, setCarbonVoiceAuthHeader(authInfo?.token), ), ); } catch (error) { logger.error('Error running ai action for shared link:', { error }); return formatToMCPToolResponse(error); } }, ); server.registerTool( 'get_ai_action_responses', { description: 'Retrieve previously generated AI Action (Prompt) responses by filtering for a specific prompt, message, or conversation ID. ' + 'Combine filters to narrow results and view all AI-generated responses related to a particular prompt, message, or conversation.', inputSchema: aIResponseControllerGetAllResponsesQueryParams.shape, annotations: { readOnlyHint: true, destructiveHint: false, }, }, async ( args: AIResponseControllerGetAllResponsesParams, { authInfo }, ): Promise<McpToolResponse> => { try { return formatToMCPToolResponse( await simplifiedApi.aIResponseControllerGetAllResponses( args, setCarbonVoiceAuthHeader(authInfo?.token), ), ); } catch (error) { logger.error('Error getting ai action responses:', { error }); return formatToMCPToolResponse(error); } }, ); export default server;

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/PhononX/cv-mcp-server'

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