Skip to main content
Glama
bbernstein
by bbernstein
graphql-client-simple.ts•42.8 kB
import fetch from 'cross-fetch'; import { Project, FixtureDefinition, FixtureInstance, Scene, CueList, Cue, FixtureUsage, SceneUsage, SceneComparison, SceneSummary, SceneFixtureSummary, SceneSortField } from '../types/lighting'; import { PaginatedResponse } from '../types/pagination'; import { normalizePaginationParams } from '../utils/pagination'; /** * Default time (in seconds) assumed for manual cue advance when followTime is not specified. * This is used when estimating total cue list duration. */ const DEFAULT_MANUAL_ADVANCE_TIME = 5; export class LacyLightsGraphQLClient { private endpoint: string; constructor(endpoint: string = 'http://localhost:4000/graphql') { this.endpoint = endpoint; } private async query(query: string, variables?: any): Promise<any> { const response = await fetch(this.endpoint, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ query, variables, }), }); const result = await response.json(); if (result.errors) { throw new Error(result.errors[0].message); } return result.data; } async getProjects(): Promise<Project[]> { const query = ` query GetProjects { projects { id name description createdAt updatedAt fixtures { id name description universe startChannel tags # Flattened fields definitionId manufacturer model type modeName channelCount channels { id offset name type minValue maxValue defaultValue } } scenes { id name description createdAt updatedAt } cueLists { id name description createdAt updatedAt } } } `; const data = await this.query(query); return data.projects; } async getProject(id: string): Promise<Project | null> { const query = ` query GetProject($id: ID!) { project(id: $id) { id name description createdAt updatedAt fixtures { id name description universe startChannel tags # Flattened fields definitionId manufacturer model type modeName channelCount channels { id offset name type minValue maxValue defaultValue } } scenes { id name description createdAt updatedAt fixtureValues { fixture { id name } channelValues } } cueLists { id name description createdAt updatedAt cues { id name cueNumber fadeInTime fadeOutTime followTime notes scene { id name } } } } } `; const data = await this.query(query, { id }); return data.project; } /** * Get a single project with metadata and counts only (no nested data). * More efficient than getProject when you only need summary information. * Part of MCP API Refactor - Task 2.2 */ async getProjectWithCounts(id: string): Promise<{ id: string; name: string; description?: string; createdAt: string; updatedAt: string; fixtureCount: number; sceneCount: number; cueListCount: number; } | null> { const query = ` query GetProjectWithCounts($id: ID!) { project(id: $id) { id name description createdAt updatedAt fixtureCount sceneCount cueListCount } } `; const data = await this.query(query, { id }); return data.project; } /** * Get all projects with metadata and counts. * Use includeDetails parameter to include counts in the response. * Part of MCP API Refactor - Task 2.2 */ async getProjectsWithCounts(): Promise<Array<{ id: string; name: string; description?: string; createdAt: string; updatedAt: string; fixtureCount: number; sceneCount: number; cueListCount: number; }>> { const query = ` query GetProjectsWithCounts { projects { id name description createdAt updatedAt fixtureCount sceneCount cueListCount } } `; const data = await this.query(query); return data.projects; } async getFixtureDefinitions(): Promise<FixtureDefinition[]> { const query = ` query GetFixtureDefinitions { fixtureDefinitions { id manufacturer model type isBuiltIn createdAt channels { id name type offset minValue maxValue defaultValue } modes { id name shortName channelCount } } } `; const data = await this.query(query); return data.fixtureDefinitions; } /** * Get paginated list of fixture instances with filtering * Part of MCP API Refactor - Task 2.3 */ async getFixtureInstances(args: { projectId: string; page?: number; perPage?: number; filter?: { type?: string; universe?: number; tags?: string[]; manufacturer?: string; model?: string; }; }): Promise<{ fixtures: FixtureInstance[]; pagination: { total: number; page: number; perPage: number; totalPages: number; hasMore: boolean; }; }> { const query = ` query GetFixtureInstances( $projectId: ID! $page: Int $perPage: Int $filter: FixtureFilterInput ) { fixtureInstances( projectId: $projectId page: $page perPage: $perPage filter: $filter ) { fixtures { id name description universe startChannel tags definitionId manufacturer model type modeName channelCount channels { id offset name type minValue maxValue defaultValue } } pagination { total page perPage totalPages hasMore } } } `; const data = await this.query(query, args); return data.fixtureInstances; } /** * Get a single fixture instance by ID * Part of MCP API Refactor - Task 2.3 */ async getFixtureInstance(id: string): Promise<FixtureInstance | null> { const query = ` query GetFixtureInstance($id: ID!) { fixtureInstance(id: $id) { id name description universe startChannel tags definitionId manufacturer model type modeName channelCount channels { id offset name type minValue maxValue defaultValue } } } `; const data = await this.query(query, { id }); return data.fixtureInstance; } async createScene(input: { name: string; description?: string; projectId: string; fixtureValues: Array<{ fixtureId: string; channelValues: number[]; }>; }): Promise<Scene> { const mutation = ` mutation CreateScene($input: CreateSceneInput!) { createScene(input: $input) { id name description createdAt updatedAt fixtureValues { fixture { id name } channelValues } } } `; const data = await this.query(mutation, { input }); return data.createScene; } async updateScene(id: string, input: { name?: string; description?: string; fixtureValues?: Array<{ fixtureId: string; channelValues: number[]; }>; }): Promise<Scene> { const mutation = ` mutation UpdateScene($id: ID!, $input: UpdateSceneInput!) { updateScene(id: $id, input: $input) { id name description updatedAt fixtureValues { fixture { id name } channelValues } } } `; const data = await this.query(mutation, { id, input }); return data.updateScene; } // šŸ›”ļø SAFE SCENE UPDATE METHODS async addFixturesToScene(sceneId: string, fixtureValues: Array<{ fixtureId: string; channelValues: number[]; sceneOrder?: number; }>, overwriteExisting: boolean = false): Promise<Scene> { const mutation = ` mutation AddFixturesToScene($sceneId: ID!, $fixtureValues: [FixtureValueInput!]!, $overwriteExisting: Boolean) { addFixturesToScene(sceneId: $sceneId, fixtureValues: $fixtureValues, overwriteExisting: $overwriteExisting) { id name description updatedAt fixtureValues { fixture { id name } channelValues sceneOrder } } } `; const data = await this.query(mutation, { sceneId, fixtureValues, overwriteExisting }); return data.addFixturesToScene; } async removeFixturesFromScene(sceneId: string, fixtureIds: string[]): Promise<Scene> { const mutation = ` mutation RemoveFixturesFromScene($sceneId: ID!, $fixtureIds: [ID!]!) { removeFixturesFromScene(sceneId: $sceneId, fixtureIds: $fixtureIds) { id name description updatedAt fixtureValues { fixture { id name } channelValues sceneOrder } } } `; const data = await this.query(mutation, { sceneId, fixtureIds }); return data.removeFixturesFromScene; } async updateScenePartial(sceneId: string, updates: { name?: string; description?: string; fixtureValues?: Array<{ fixtureId: string; channelValues: number[]; sceneOrder?: number; }>; mergeFixtures?: boolean; }): Promise<Scene> { const mutation = ` mutation UpdateScenePartial($sceneId: ID!, $name: String, $description: String, $fixtureValues: [FixtureValueInput!], $mergeFixtures: Boolean) { updateScenePartial(sceneId: $sceneId, name: $name, description: $description, fixtureValues: $fixtureValues, mergeFixtures: $mergeFixtures) { id name description updatedAt fixtureValues { fixture { id name } channelValues sceneOrder } } } `; const data = await this.query(mutation, { sceneId, name: updates.name, description: updates.description, fixtureValues: updates.fixtureValues, mergeFixtures: updates.mergeFixtures }); return data.updateScenePartial; } async getCueList(id: string): Promise<CueList | null> { const query = ` query GetCueList($id: ID!) { cueList(id: $id) { id name description createdAt updatedAt project { id } cues { id name cueNumber fadeInTime fadeOutTime followTime notes scene { id name } } } } `; const data = await this.query(query, { id }); return data.cueList; } /** * Get lightweight cue list summaries for a project * Part of Task 2.5 (Cue List Query Tools) - MCP API Refactor */ async getCueLists(projectId: string): Promise<any[]> { // For now, use the existing project query to get cue lists // This will be replaced with a dedicated cueLists query once backend pagination support is added const project = await this.getProject(projectId); if (!project || !project.cueLists) { return []; } // Return lightweight summaries return project.cueLists.map((cueList: any) => ({ id: cueList.id, name: cueList.name, description: cueList.description, cueCount: cueList.cues?.length || 0, totalDuration: this.estimateCueListDuration(cueList.cues || []), loop: cueList.loop || false, createdAt: cueList.createdAt, })); } /** * Get cue list with paginated cues * Part of Task 2.5 (Cue List Query Tools) - MCP API Refactor */ async getCueListWithPagination( cueListId: string, page: number = 1, perPage: number = 50, includeSceneDetails: boolean = false ): Promise<any> { // For now, use the existing getCueList and apply client-side pagination // This will be replaced with a backend paginated query once backend pagination support is added const cueList = await this.getCueList(cueListId); if (!cueList) { return null; } const cues = cueList.cues || []; const start = (page - 1) * perPage; const end = start + perPage; const paginatedCues = cues.slice(start, end); // Format cues with optional scene details const formattedCues = paginatedCues.map((cue: any) => { const baseCue = { id: cue.id, name: cue.name, cueNumber: cue.cueNumber, fadeInTime: cue.fadeInTime, fadeOutTime: cue.fadeOutTime, followTime: cue.followTime, notes: cue.notes, sceneId: cue.scene.id, sceneName: cue.scene.name, }; if (includeSceneDetails) { return { ...baseCue, scene: cue.scene, }; } return baseCue; }); const totalPages = Math.ceil(cues.length / perPage); return { id: cueList.id, name: cueList.name, description: cueList.description, loop: (cueList as any).loop || false, cues: formattedCues, pagination: { total: cues.length, page, perPage, totalPages, hasMore: page < totalPages, }, cueCount: cues.length, totalDuration: this.estimateCueListDuration(cues), createdAt: cueList.createdAt, updatedAt: cueList.updatedAt, }; } /** * Helper to estimate cue list duration */ private estimateCueListDuration(cues: any[]): number { let totalTime = 0; for (const cue of cues) { totalTime += cue.fadeInTime || 0; if (cue.followTime) { totalTime += cue.followTime; } else { totalTime += DEFAULT_MANUAL_ADVANCE_TIME; } } return totalTime; } async createCueList(input: { name: string; description?: string; projectId: string; }): Promise<CueList> { const mutation = ` mutation CreateCueList($input: CreateCueListInput!) { createCueList(input: $input) { id name description createdAt updatedAt cues { id name cueNumber fadeInTime fadeOutTime followTime notes } } } `; const data = await this.query(mutation, { input }); return data.createCueList; } async createCue(input: { name: string; cueNumber: number; cueListId: string; sceneId: string; fadeInTime: number; fadeOutTime: number; followTime?: number; notes?: string; }) { const mutation = ` mutation CreateCue($input: CreateCueInput!) { createCue(input: $input) { id name cueNumber fadeInTime fadeOutTime followTime notes scene { id name } } } `; const data = await this.query(mutation, { input }); return data.createCue; } async updateCueList(id: string, input: { name?: string; description?: string; loop?: boolean; }): Promise<CueList> { const mutation = ` mutation UpdateCueList($id: ID!, $input: CreateCueListInput!) { updateCueList(id: $id, input: $input) { id name description loop createdAt updatedAt cues { id name cueNumber fadeInTime fadeOutTime followTime notes scene { id name } } } } `; // Since the backend expects CreateCueListInput which requires projectId, // we need to get the current cue list first to maintain the projectId const cueListQuery = ` query GetCueList($id: ID!) { cueList(id: $id) { id name loop project { id } } } `; const cueListData = await this.query(cueListQuery, { id }); const projectId = cueListData.cueList.project.id; const updateInput = { name: input.name || cueListData.cueList.name, description: input.description, loop: input.loop !== undefined ? input.loop : cueListData.cueList.loop, projectId }; const data = await this.query(mutation, { id, input: updateInput }); return data.updateCueList; } async updateCue(id: string, input: { name?: string; cueNumber?: number; sceneId?: string; fadeInTime?: number; fadeOutTime?: number; followTime?: number | null; notes?: string; }): Promise<Cue> { const mutation = ` mutation UpdateCue($id: ID!, $input: CreateCueInput!) { updateCue(id: $id, input: $input) { id name cueNumber fadeInTime fadeOutTime followTime notes scene { id name } } } `; // Get current cue to maintain required fields const cueQuery = ` query GetCue($id: ID!) { cue(id: $id) { id name cueNumber cueList { id } scene { id } fadeInTime fadeOutTime followTime notes } } `; const cueData = await this.query(cueQuery, { id }); const currentCue = cueData.cue; const updateInput = { name: input.name ?? currentCue.name, cueNumber: input.cueNumber ?? currentCue.cueNumber, cueListId: currentCue.cueList.id, sceneId: input.sceneId ?? currentCue.scene.id, fadeInTime: input.fadeInTime ?? currentCue.fadeInTime, fadeOutTime: input.fadeOutTime ?? currentCue.fadeOutTime, followTime: input.followTime !== undefined ? input.followTime : currentCue.followTime, notes: input.notes !== undefined ? input.notes : currentCue.notes }; const data = await this.query(mutation, { id, input: updateInput }); return data.updateCue; } async bulkUpdateCues(input: { cueIds: string[]; fadeInTime?: number; fadeOutTime?: number; followTime?: number | null; easingType?: string; }): Promise<Cue[]> { const mutation = ` mutation BulkUpdateCues($input: BulkCueUpdateInput!) { bulkUpdateCues(input: $input) { id name cueNumber fadeInTime fadeOutTime followTime notes scene { id name } } } `; const data = await this.query(mutation, { input }); return data.bulkUpdateCues; } async deleteCue(id: string): Promise<boolean> { const mutation = ` mutation DeleteCue($id: ID!) { deleteCue(id: $id) } `; const data = await this.query(mutation, { id }); return data.deleteCue; } async deleteCueList(id: string): Promise<boolean> { const mutation = ` mutation DeleteCueList($id: ID!) { deleteCueList(id: $id) } `; const data = await this.query(mutation, { id }); return data.deleteCueList; } async createProject(input: { name: string; description?: string; }): Promise<Project> { const mutation = ` mutation CreateProject($input: CreateProjectInput!) { createProject(input: $input) { id name description createdAt updatedAt } } `; const data = await this.query(mutation, { input }); return data.createProject; } async deleteProject(id: string): Promise<boolean> { const mutation = ` mutation DeleteProject($id: ID!) { deleteProject(id: $id) } `; const data = await this.query(mutation, { id }); return data.deleteProject; } async createFixtureDefinition(input: { manufacturer: string; model: string; type: string; channels: Array<{ name: string; type: string; offset: number; minValue?: number; maxValue?: number; defaultValue?: number; }>; modes?: Array<{ name: string; channelCount: number; }>; }): Promise<FixtureDefinition> { const mutation = ` mutation CreateFixtureDefinition($input: CreateFixtureDefinitionInput!) { createFixtureDefinition(input: $input) { id manufacturer model type isBuiltIn channels { id name type offset minValue maxValue defaultValue } modes { id name shortName channelCount } } } `; const data = await this.query(mutation, { input }); return data.createFixtureDefinition; } async createFixtureInstance(input: { projectId: string; name: string; description?: string; definitionId: string; modeId?: string; universe: number; startChannel: number; tags: string[]; }): Promise<FixtureInstance> { const mutation = ` mutation CreateFixtureInstance($input: CreateFixtureInstanceInput!) { createFixtureInstance(input: $input) { id name description universe startChannel tags # Flattened fields definitionId manufacturer model type modeName channelCount } } `; const data = await this.query(mutation, { input }); return data.createFixtureInstance; } async updateFixtureInstance(id: string, input: { name?: string; description?: string; definitionId?: string; modeId?: string; universe?: number; startChannel?: number; tags?: string[]; }): Promise<FixtureInstance> { const mutation = ` mutation UpdateFixtureInstance($id: ID!, $input: UpdateFixtureInstanceInput!) { updateFixtureInstance(id: $id, input: $input) { id name description universe startChannel tags # Flattened fields definitionId manufacturer model type modeName channelCount } } `; const data = await this.query(mutation, { id, input }); return data.updateFixtureInstance; } async deleteFixtureInstance(id: string): Promise<boolean> { const mutation = ` mutation DeleteFixtureInstance($id: ID!) { deleteFixtureInstance(id: $id) } `; const data = await this.query(mutation, { id }); return data.deleteFixtureInstance; } async bulkUpdateFixtures(input: { fixtures: Array<{ fixtureId: string; name?: string; description?: string; universe?: number; startChannel?: number; tags?: string[]; layoutX?: number; layoutY?: number; layoutRotation?: number; }>; }): Promise<FixtureInstance[]> { const mutation = ` mutation BulkUpdateFixtures($input: BulkFixtureUpdateInput!) { bulkUpdateFixtures(input: $input) { id name description universe startChannel tags layoutX layoutY layoutRotation # Flattened fields definitionId manufacturer model type modeName channelCount } } `; const data = await this.query(mutation, { input }); return data.bulkUpdateFixtures; } async bulkCreateFixtures(input: { fixtures: Array<{ projectId: string; name: string; description?: string; definitionId: string; modeId?: string; universe: number; startChannel: number; tags?: string[]; }>; }): Promise<FixtureInstance[]> { const mutation = ` mutation BulkCreateFixtures($input: BulkFixtureCreateInput!) { bulkCreateFixtures(input: $input) { id name description universe startChannel tags # Flattened fields definitionId manufacturer model type modeName channelCount channels { id offset name type minValue maxValue defaultValue } } } `; const data = await this.query(mutation, { input }); return data.bulkCreateFixtures; } async setSceneLive(sceneId: string): Promise<boolean> { const mutation = ` mutation ActivateScene($sceneId: ID!) { setSceneLive(sceneId: $sceneId) } `; const data = await this.query(mutation, { sceneId }); return data.setSceneLive; } async fadeToBlack(fadeOutTime: number): Promise<boolean> { const mutation = ` mutation FadeToBlack($fadeOutTime: Float!) { fadeToBlack(fadeOutTime: $fadeOutTime) } `; const data = await this.query(mutation, { fadeOutTime }); return data.fadeToBlack; } async getScene(id: string): Promise<Scene | null> { const query = ` query GetScene($id: ID!) { scene(id: $id) { id name description createdAt updatedAt fixtureValues { fixture { id name } channelValues } } } `; const data = await this.query(query, { id }); return data.scene; } async getCurrentActiveScene(): Promise<Scene | null> { const query = ` query GetCurrentActiveScene { currentActiveScene { id name description createdAt updatedAt project { id name } fixtureValues { fixture { id name } channelValues } } } `; const data = await this.query(query); return data.currentActiveScene; } // MCP API Refactor - Task 2.4: Scene Query Tools /** * List scenes in a project with pagination and filtering (Task 2.4) * Returns lightweight scene summaries without fixture values */ async listScenes(params: { projectId: string; page?: number; perPage?: number; nameContains?: string; usesFixture?: string; sortBy?: SceneSortField; }): Promise<PaginatedResponse<SceneSummary>> { const { page: normalizedPage, perPage: normalizedPerPage } = normalizePaginationParams(params.page, params.perPage); const query = ` query ListScenes($projectId: ID!, $page: Int!, $perPage: Int!, $filter: SceneFilterInput, $sortBy: SceneSortField) { scenes(projectId: $projectId, page: $page, perPage: $perPage, filter: $filter, sortBy: $sortBy) { scenes { id name description fixtureCount createdAt updatedAt } pagination { total page perPage totalPages hasMore } } } `; const filter: any = {}; if (params.nameContains) filter.nameContains = params.nameContains; if (params.usesFixture) filter.usesFixture = params.usesFixture; const variables = { projectId: params.projectId, page: normalizedPage, perPage: normalizedPerPage, filter: Object.keys(filter).length > 0 ? filter : undefined, sortBy: params.sortBy || SceneSortField.CREATED_AT }; const data = await this.query(query, variables); return { items: data.scenes.scenes, pagination: data.scenes.pagination }; } /** * Get full scene details with optional fixture values (Task 2.4) * Set includeFixtureValues=false for faster queries when values not needed */ async getSceneWithOptions(id: string, includeFixtureValues: boolean = true): Promise<Scene | null> { const query = ` query GetSceneWithOptions($id: ID!, $includeFixtureValues: Boolean!) { scene(id: $id, includeFixtureValues: $includeFixtureValues) { id name description createdAt updatedAt fixtureValues @include(if: $includeFixtureValues) { fixture { id name } channelValues sceneOrder } } } `; const data = await this.query(query, { id, includeFixtureValues }); return data.scene; } /** * Get just the fixtures used in a scene without their values (Task 2.4) * Fastest way to understand scene composition */ async getSceneFixtures(sceneId: string): Promise<SceneFixtureSummary[]> { const query = ` query GetSceneFixtures($sceneId: ID!) { sceneFixtures(sceneId: $sceneId) { fixtureId fixtureName fixtureType } } `; const data = await this.query(query, { sceneId }); return data.sceneFixtures; } async getCue(id: string): Promise<Cue | null> { const query = ` query GetCue($id: ID!) { cue(id: $id) { id name cueNumber fadeInTime fadeOutTime followTime notes cueList { id name } scene { id name description } } } `; const data = await this.query(query, { id }); return data.cue; } async playCue(cueId: string, fadeInTime?: number): Promise<boolean> { const mutation = ` mutation PlayCue($cueId: ID!, $fadeInTime: Float) { playCue(cueId: $cueId, fadeInTime: $fadeInTime) } `; const data = await this.query(mutation, { cueId, fadeInTime }); return data.playCue; } async getCueListPlaybackStatus(cueListId: string): Promise<any> { const query = ` query GetCueListPlaybackStatus($cueListId: ID!) { cueListPlaybackStatus(cueListId: $cueListId) { cueListId currentCueIndex isPlaying currentCue { id name cueNumber fadeInTime fadeOutTime followTime } fadeProgress lastUpdated } } `; const data = await this.query(query, { cueListId }); return data.cueListPlaybackStatus; } async startCueList(cueListId: string, startFromCue?: number): Promise<boolean> { const mutation = ` mutation StartCueList($cueListId: ID!, $startFromCue: Int) { startCueList(cueListId: $cueListId, startFromCue: $startFromCue) } `; const data = await this.query(mutation, { cueListId, startFromCue }); return data.startCueList; } async nextCue(cueListId: string, fadeInTime?: number): Promise<boolean> { const mutation = ` mutation NextCue($cueListId: ID!, $fadeInTime: Float) { nextCue(cueListId: $cueListId, fadeInTime: $fadeInTime) } `; const data = await this.query(mutation, { cueListId, fadeInTime }); return data.nextCue; } async previousCue(cueListId: string, fadeInTime?: number): Promise<boolean> { const mutation = ` mutation PreviousCue($cueListId: ID!, $fadeInTime: Float) { previousCue(cueListId: $cueListId, fadeInTime: $fadeInTime) } `; const data = await this.query(mutation, { cueListId, fadeInTime }); return data.previousCue; } async goToCue(cueListId: string, cueIndex: number, fadeInTime?: number): Promise<boolean> { const mutation = ` mutation GoToCue($cueListId: ID!, $cueIndex: Int!, $fadeInTime: Float) { goToCue(cueListId: $cueListId, cueIndex: $cueIndex, fadeInTime: $fadeInTime) } `; const data = await this.query(mutation, { cueListId, cueIndex, fadeInTime }); return data.goToCue; } async stopCueList(cueListId: string): Promise<boolean> { const mutation = ` mutation StopCueList($cueListId: ID!) { stopCueList(cueListId: $cueListId) } `; const data = await this.query(mutation, { cueListId }); return data.stopCueList; } // importProjectFromQLC method removed - import functionality moved to web UI due to file size constraints // async importProjectFromQLC(xmlContent: string, originalFileName: string): Promise<any> { /*const mutation = ` mutation ImportProjectFromQLC($xmlContent: String!, $originalFileName: String!) { importProjectFromQLC(xmlContent: $xmlContent, originalFileName: $originalFileName) { project { id name description createdAt updatedAt fixtures { id name manufacturer model universe startChannel channelCount } scenes { id name description fixtureValues { fixture { id name } channelValues } } cueLists { id name description cues { id name cueNumber fadeInTime fadeOutTime scene { id name } } } } originalFileName fixtureCount sceneCount cueListCount warnings } } `; const data = await this.query(mutation, { xmlContent, originalFileName }); return data.importProjectFromQLC; }*/ /** * Get fixture usage information - shows which scenes and cues use this fixture * Part of MCP API Refactor - Task 2.7 */ async getFixtureUsage(fixtureId: string): Promise<FixtureUsage> { const query = ` query GetFixtureUsage($fixtureId: ID!) { fixtureUsage(fixtureId: $fixtureId) { fixtureId fixtureName scenes { id name description createdAt updatedAt fixtureCount } cues { cueId cueNumber cueName cueListId cueListName } } } `; const data = await this.query(query, { fixtureId }); return data.fixtureUsage; } /** * Get scene usage information - shows which cues use this scene * Part of MCP API Refactor - Task 2.7 */ async getSceneUsage(sceneId: string): Promise<SceneUsage> { const query = ` query GetSceneUsage($sceneId: ID!) { sceneUsage(sceneId: $sceneId) { sceneId sceneName cues { cueId cueNumber cueName cueListId cueListName } } } `; const data = await this.query(query, { sceneId }); return data.sceneUsage; } /** * Compare two scenes to identify differences * Part of MCP API Refactor - Task 2.7 */ async compareScenes(sceneId1: string, sceneId2: string): Promise<SceneComparison> { const query = ` query CompareScenes($sceneId1: ID!, $sceneId2: ID!) { compareScenes(sceneId1: $sceneId1, sceneId2: $sceneId2) { scene1 { id name description createdAt updatedAt fixtureCount } scene2 { id name description createdAt updatedAt fixtureCount } differences { fixtureId fixtureName differenceType scene1Values scene2Values } identicalFixtureCount differentFixtureCount } } `; const data = await this.query(query, { sceneId1, sceneId2 }); return data.compareScenes; } // Search methods async searchFixtures( projectId: string, query: string, filter?: { type?: string; universe?: number; tags?: string[]; manufacturer?: string; model?: string; }, page?: number, perPage?: number ): Promise<{ fixtures: FixtureInstance[]; pagination: { total: number; page: number; perPage: number; totalPages: number; hasMore: boolean; }; }> { const gqlQuery = ` query SearchFixtures( $projectId: ID! $query: String! $filter: FixtureFilterInput $page: Int $perPage: Int ) { searchFixtures( projectId: $projectId query: $query filter: $filter page: $page perPage: $perPage ) { fixtures { id name description manufacturer model type modeName channelCount universe startChannel tags channels { id offset name type minValue maxValue defaultValue } } pagination { total page perPage totalPages hasMore } } } `; const data = await this.query(gqlQuery, { projectId, query, filter, page, perPage, }); return data.searchFixtures; } async searchScenes( projectId: string, query: string, filter?: { nameContains?: string; usesFixture?: string; }, page?: number, perPage?: number ): Promise<{ scenes: Array<{ id: string; name: string; description?: string; fixtureCount: number; createdAt: string; updatedAt: string; }>; pagination: { total: number; page: number; perPage: number; totalPages: number; hasMore: boolean; }; }> { const gqlQuery = ` query SearchScenes( $projectId: ID! $query: String! $filter: SceneFilterInput $page: Int $perPage: Int ) { searchScenes( projectId: $projectId query: $query filter: $filter page: $page perPage: $perPage ) { scenes { id name description fixtureCount createdAt updatedAt } pagination { total page perPage totalPages hasMore } } } `; const data = await this.query(gqlQuery, { projectId, query, filter, page, perPage, }); return data.searchScenes; } async searchCues( cueListId: string, query: string, page?: number, perPage?: number ): Promise<{ cues: Cue[]; pagination: { total: number; page: number; perPage: number; totalPages: number; hasMore: boolean; }; }> { const gqlQuery = ` query SearchCues( $cueListId: ID! $query: String! $page: Int $perPage: Int ) { searchCues( cueListId: $cueListId query: $query page: $page perPage: $perPage ) { cues { id name cueNumber fadeInTime fadeOutTime followTime notes scene { id name } cueList { id name } } pagination { total page perPage totalPages hasMore } } } `; const data = await this.query(gqlQuery, { cueListId, query, page, perPage, }); return data.searchCues; } }

Implementation Reference

Latest Blog Posts

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/bbernstein/lacylights-mcp'

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