UNPKG

@digitalsamba/embedded-api-mcp-server

Version:

Digital Samba Embedded API MCP Server - Model Context Protocol server for Digital Samba's Embedded API

423 lines 15.6 kB
/** * Digital Samba MCP Server - Live Session Control Tools * * This module implements tools for real-time control of live Digital Samba sessions. * It provides MCP tools for managing active sessions including transcription and * phone participant integration. * * Tools provided: * - start-transcription: Start transcription for a session * - stop-transcription: Stop transcription for a session * - phone-participants-joined: Register phone participants joining * - phone-participants-left: Register phone participants leaving * * Note: Recording controls (start/stop) are in the recording-management module * Note: Session termination (end-session) is in the session-management module * * @module tools/live-session-controls * @author Digital Samba Team * @version 1.0.0 */ import { ErrorCode, McpError } from "@modelcontextprotocol/sdk/types.js"; // Local modules import { getApiKeyFromRequest } from "../../auth.js"; import { DigitalSambaApiClient } from "../../digital-samba-api.js"; import logger from "../../logger.js"; /** * Register live session control tools with the MCP SDK * * This function returns an array of tool definitions that can be registered * with the MCP server. It follows the modular pattern where tools are defined * here and registered in the main index.ts file. * * @returns {ToolDefinition[]} Array of tool definitions */ export function registerLiveSessionTools() { return [ { name: "start-transcription", description: '[Live Session Controls] Start real-time transcription for an active room session. Use when users say: "start transcription", "enable transcription", "transcribe the meeting", "turn on transcription", "start live captions". Requires roomId with an active session. Transcripts can be exported later.', inputSchema: { type: "object", properties: { roomId: { type: "string", description: "The ID of the room to start transcription for", }, }, required: ["roomId"], }, }, { name: "stop-transcription", description: '[Live Session Controls] Stop ongoing transcription in a room. Use when users say: "stop transcription", "disable transcription", "turn off transcription", "stop live captions", "end transcription". Requires roomId. Only works if transcription is currently active.', inputSchema: { type: "object", properties: { roomId: { type: "string", description: "The ID of the room to stop transcription for", }, }, required: ["roomId"], }, }, { name: "phone-participants-joined", description: '[Live Session Controls] Register phone/dial-in participants joining a room. Use when users say: "add phone participant", "someone dialed in", "phone user joined", "register dial-in participant". Requires roomId and participant details including callId. Used for tracking phone-based attendees.', inputSchema: { type: "object", properties: { roomId: { type: "string", description: "The ID of the room", }, participants: { type: "array", description: "Array of phone participants joining", items: { type: "object", properties: { callId: { type: "string", description: "Unique identifier for the phone call", }, name: { type: "string", description: "Name of the participant", }, callerNumber: { type: "string", description: "Phone number of the participant", }, externalId: { type: "string", description: "External identifier for the participant", }, }, required: ["callId"], }, }, }, required: ["roomId", "participants"], }, }, { name: "phone-participants-left", description: '[Live Session Controls] Register phone/dial-in participants leaving a room. Use when users say: "phone participant left", "dial-in user disconnected", "remove phone participant", "phone user hung up". Requires roomId and callIds array. Updates participant tracking.', inputSchema: { type: "object", properties: { roomId: { type: "string", description: "The ID of the room", }, callIds: { type: "array", description: "Array of call IDs for participants leaving", items: { type: "string", }, }, }, required: ["roomId", "callIds"], }, }, ]; } /** * Execute a live session control tool * * This function handles the execution of live session control tools. * It's called by the main server when a tool is invoked. * * @param {string} toolName - Name of the tool to execute * @param {any} params - Tool parameters * @param {DigitalSambaApiClient} apiClient - API client instance * @returns {Promise<any>} Tool execution result */ export async function executeLiveSessionTool(toolName, params, _apiClient) { switch (toolName) { case "start-transcription": return handleStartTranscription(params, _apiClient); case "stop-transcription": return handleStopTranscription(params, _apiClient); case "phone-participants-joined": return handlePhoneParticipantsJoined(params, _apiClient); case "phone-participants-left": return handlePhoneParticipantsLeft(params, _apiClient); default: throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${toolName}`); } } /** * Handle start transcription tool */ async function handleStartTranscription(params, _apiClient) { const { roomId } = params; if (!roomId || roomId.trim() === "") { return { content: [ { type: "text", text: "Room ID is required to start transcription.", }, ], isError: true, }; } logger.info("Starting transcription", { roomId }); try { await _apiClient.startTranscription(roomId); logger.info("Transcription started successfully", { roomId }); return { content: [ { type: "text", text: `Successfully started transcription for room ${roomId}.`, }, ], }; } catch (error) { logger.error("Error starting transcription", { roomId, error: error instanceof Error ? error.message : String(error), }); const errorMessage = error instanceof Error ? error.message : String(error); let displayMessage = `Error starting transcription: ${errorMessage}`; if (errorMessage.includes("Room not found") || errorMessage.includes("404")) { displayMessage = `Room with ID ${roomId} not found`; } else if (errorMessage.includes("Already transcribing")) { displayMessage = `Transcription already in progress for room ${roomId}`; } return { content: [ { type: "text", text: displayMessage, }, ], isError: true, }; } } /** * Handle stop transcription tool */ async function handleStopTranscription(params, _apiClient) { const { roomId } = params; if (!roomId || roomId.trim() === "") { return { content: [ { type: "text", text: "Room ID is required to stop transcription.", }, ], isError: true, }; } logger.info("Stopping transcription", { roomId }); try { await _apiClient.stopTranscription(roomId); logger.info("Transcription stopped successfully", { roomId }); return { content: [ { type: "text", text: `Successfully stopped transcription for room ${roomId}.`, }, ], }; } catch (error) { logger.error("Error stopping transcription", { roomId, error: error instanceof Error ? error.message : String(error), }); return { content: [ { type: "text", text: `Error stopping transcription: ${error instanceof Error ? error.message : String(error)}`, }, ], isError: true, }; } } /** * Handle phone participants joined tool */ async function handlePhoneParticipantsJoined(params, _apiClient) { const { roomId, participants } = params; if (!roomId || roomId.trim() === "") { return { content: [ { type: "text", text: "Room ID is required for phone participants.", }, ], isError: true, }; } if (!participants || participants.length === 0) { return { content: [ { type: "text", text: "At least one participant is required.", }, ], isError: true, }; } logger.info("Registering phone participants joined", { roomId, participantCount: participants.length, }); try { // Convert camelCase to snake_case for API const apiParticipants = participants.map(p => ({ call_id: p.callId, name: p.name, caller_number: p.callerNumber, external_id: p.externalId })); await _apiClient.phoneParticipantsJoined(roomId, apiParticipants); const participantNames = participants .map((p) => p.name || p.callerNumber || p.callId) .join(", "); return { content: [ { type: "text", text: `Successfully registered ${participants.length} phone participant(s) joining room ${roomId}: ${participantNames}`, }, ], }; } catch (error) { logger.error("Error registering phone participants joined", { roomId, error: error instanceof Error ? error.message : String(error), }); const errorMessage = error instanceof Error ? error.message : String(error); let displayMessage = `Error registering phone participants: ${errorMessage}`; if (errorMessage.includes("Room not found") || errorMessage.includes("404")) { displayMessage = `Room with ID ${roomId} not found`; } return { content: [ { type: "text", text: displayMessage, }, ], isError: true, }; } } /** * Handle phone participants left tool */ async function handlePhoneParticipantsLeft(params, _apiClient) { const { roomId, callIds } = params; if (!roomId || roomId.trim() === "") { return { content: [ { type: "text", text: "Room ID is required for phone participants.", }, ], isError: true, }; } if (!callIds || callIds.length === 0) { return { content: [ { type: "text", text: "At least one call ID is required.", }, ], isError: true, }; } logger.info("Registering phone participants left", { roomId, callIdCount: callIds.length, }); try { await _apiClient.phoneParticipantsLeft(roomId, callIds); return { content: [ { type: "text", text: `Successfully registered ${callIds.length} phone participant(s) leaving room ${roomId}`, }, ], }; } catch (error) { logger.error("Error registering phone participants left", { roomId, error: error instanceof Error ? error.message : String(error), }); const errorMessage = error instanceof Error ? error.message : String(error); let displayMessage = `Error registering phone participants leaving: ${errorMessage}`; if (errorMessage.includes("Room not found") || errorMessage.includes("404")) { displayMessage = `Room with ID ${roomId} not found`; } return { content: [ { type: "text", text: displayMessage, }, ], isError: true, }; } } /** * Set up live session control tools for the MCP server (legacy function) * * This function is kept for backward compatibility but delegates to the * modular registration system. * * @deprecated Use registerLiveSessionTools() and executeLiveSessionTool() instead * @param {McpServer} server - The MCP server instance * @param {string} apiUrl - Base URL for the Digital Samba API */ export function setupLiveSessionTools(server, apiUrl) { logger.info("Setting up live session control tools"); const tools = registerLiveSessionTools(); tools.forEach((tool) => { server.tool(tool.name, tool.inputSchema, async (params, request) => { const apiKey = getApiKeyFromRequest(request); if (!apiKey) { return { content: [ { type: "text", text: "No API key found. Please include an Authorization header with a Bearer token.", }, ], isError: true, }; } const apiClient = new DigitalSambaApiClient(apiKey, apiUrl); return executeLiveSessionTool(tool.name, params, apiClient); }); }); logger.info("Live session control tools set up successfully"); } //# sourceMappingURL=index.js.map