Skip to main content
Glama
bbernstein
by bbernstein
project-tools.ts10.5 kB
import { z } from 'zod'; import { LacyLightsGraphQLClient } from '../services/graphql-client-simple'; // Project type not currently used in this file const ListProjectsSchema = z.object({ includeDetails: z.boolean().default(false).describe('Include fixture and scene counts') }); const CreateProjectSchema = z.object({ name: z.string().describe('Project name'), description: z.string().optional().describe('Project description') }); const GetProjectSchema = z.object({ projectId: z.string().describe('Project ID to get') }); const GetProjectDetailsSchema = z.object({ projectId: z.string().describe('Project ID to get details for') }); const DeleteProjectSchema = z.object({ projectId: z.string().describe('Project ID to delete'), confirmDelete: z.boolean().default(false).describe('Confirm deletion of project and all its data') }); // Bulk operation schemas const BulkCreateProjectsSchema = z.object({ projects: z.array(z.object({ name: z.string().describe('Project name'), description: z.string().optional().describe('Project description') })).describe('Array of projects to create') }); const BulkDeleteProjectsSchema = z.object({ projectIds: z.array(z.string()).describe('Array of project IDs to delete'), confirmDelete: z.boolean().describe('Confirm deletion of projects and all their data') }); // Removed ImportProjectFromQLCSchema - import functionality moved to web UI due to file size constraints export class ProjectTools { constructor(private graphqlClient: LacyLightsGraphQLClient) {} async listProjects(args: z.infer<typeof ListProjectsSchema>) { const { includeDetails } = ListProjectsSchema.parse(args); try { if (includeDetails) { // Use efficient count query const projects = await this.graphqlClient.getProjectsWithCounts(); return { projects: projects.map(project => ({ id: project.id, name: project.name, description: project.description, createdAt: project.createdAt, updatedAt: project.updatedAt, fixtureCount: project.fixtureCount, sceneCount: project.sceneCount, cueListCount: project.cueListCount })), totalProjects: projects.length }; } // Lightweight query without counts const projects = await this.graphqlClient.getProjects(); return { projects: projects.map(project => ({ id: project.id, name: project.name, description: project.description })), totalProjects: projects.length }; } catch (error) { throw new Error(`Failed to list projects: ${error}`); } } async getProject(args: z.infer<typeof GetProjectSchema>) { const { projectId } = GetProjectSchema.parse(args); try { const project = await this.graphqlClient.getProjectWithCounts(projectId); if (!project) { throw new Error(`Project with ID ${projectId} not found`); } return { project: { id: project.id, name: project.name, description: project.description, createdAt: project.createdAt, updatedAt: project.updatedAt, fixtureCount: project.fixtureCount, sceneCount: project.sceneCount, cueListCount: project.cueListCount } }; } catch (error) { throw new Error(`Failed to get project: ${error}`); } } async createProject(args: z.infer<typeof CreateProjectSchema>) { const { name, description} = CreateProjectSchema.parse(args); try { const project = await this.graphqlClient.createProject({ name, description }); return { project: { id: project.id, name: project.name, description: project.description, createdAt: project.createdAt }, message: `Successfully created project "${name}"` }; } catch (error) { throw new Error(`Failed to create project: ${error}`); } } async getProjectDetails(args: z.infer<typeof GetProjectDetailsSchema>) { const { projectId } = GetProjectDetailsSchema.parse(args); try { const project = await this.graphqlClient.getProject(projectId); if (!project) { throw new Error(`Project with ID ${projectId} not found`); } // Organize fixtures by universe const fixturesByUniverse = project.fixtures.reduce((acc, fixture) => { if (!acc[fixture.universe]) { acc[fixture.universe] = []; } acc[fixture.universe].push(fixture); return acc; }, {} as Record<number, typeof project.fixtures>); // Sort fixtures within each universe by start channel Object.values(fixturesByUniverse).forEach(fixtures => { fixtures.sort((a, b) => a.startChannel - b.startChannel); }); return { project: { id: project.id, name: project.name, description: project.description, createdAt: project.createdAt, updatedAt: project.updatedAt }, fixtures: { total: project.fixtures.length, byUniverse: Object.entries(fixturesByUniverse).map(([universe, fixtures]) => ({ universe: parseInt(universe), fixtureCount: fixtures.length, channelRanges: this.calculateChannelRanges(fixtures), fixtures: fixtures.map(f => ({ id: f.id, name: f.name, type: f.type, manufacturer: f.manufacturer, model: f.model, mode: f.modeName, channels: `${f.startChannel}-${f.startChannel + f.channelCount - 1}`, tags: f.tags })) })) }, scenes: { total: project.scenes.length, list: project.scenes.map(s => ({ id: s.id, name: s.name, description: s.description, fixtureCount: s.fixtureValues?.length || 0 })) }, cueLists: { total: project.cueLists.length, list: project.cueLists.map(cl => ({ id: cl.id, name: cl.name, description: cl.description, cueCount: cl.cues?.length || 0 })) } }; } catch (error) { throw new Error(`Failed to get project details: ${error}`); } } async deleteProject(args: z.infer<typeof DeleteProjectSchema>) { const { projectId, confirmDelete } = DeleteProjectSchema.parse(args); if (!confirmDelete) { throw new Error('Deletion not confirmed. Set confirmDelete to true to proceed.'); } try { const success = await this.graphqlClient.deleteProject(projectId); return { success, message: success ? `Project ${projectId} deleted successfully` : 'Failed to delete project' }; } catch (error) { throw new Error(`Failed to delete project: ${error}`); } } // Bulk Operations /** * Create multiple projects in a single operation */ async bulkCreateProjects(args: z.infer<typeof BulkCreateProjectsSchema>) { const { projects } = BulkCreateProjectsSchema.parse(args); try { if (projects.length === 0) { throw new Error('No projects provided for bulk creation'); } // Use the GraphQL client's bulk create method const createdProjects = await this.graphqlClient.bulkCreateProjects({ projects, }); return { success: true, createdProjects: createdProjects.map(project => ({ projectId: project.id, name: project.name, description: project.description, createdAt: project.createdAt, })), summary: { totalCreated: createdProjects.length, }, message: `Successfully created ${createdProjects.length} projects`, }; } catch (error) { throw new Error(`Failed to bulk create projects: ${error}`); } } /** * Delete multiple projects in a single operation */ async bulkDeleteProjects(args: z.infer<typeof BulkDeleteProjectsSchema>) { const { projectIds, confirmDelete } = BulkDeleteProjectsSchema.parse(args); try { if (!confirmDelete) { throw new Error('confirmDelete must be true to delete projects'); } if (projectIds.length === 0) { throw new Error('No project IDs provided for bulk deletion'); } // Use the GraphQL client's bulk delete method const result = await this.graphqlClient.bulkDeleteProjects(projectIds); // Note: 'success' is true if at least one deletion succeeded, even if some deletions failed. // Partial successes are possible; see 'deletedCount' and 'failedIds' for details. return { success: result.successCount > 0, deletedCount: result.successCount, failedIds: result.failedIds, summary: { totalRequested: projectIds.length, successCount: result.successCount, failureCount: result.failedIds.length, }, message: result.failedIds.length === 0 ? `Successfully deleted ${result.successCount} projects` : `Deleted ${result.successCount} projects, ${result.failedIds.length} failed`, }; } catch (error) { throw new Error(`Failed to bulk delete projects: ${error}`); } } private calculateChannelRanges(fixtures: any[]): string { if (fixtures.length === 0) return 'None'; const ranges: Array<{ start: number; end: number }> = []; fixtures.forEach(fixture => { const start = fixture.startChannel; const channelCount = fixture.channelCount; const end = start + channelCount - 1; // Check if this range can be merged with the last one if (ranges.length > 0) { const lastRange = ranges[ranges.length - 1]; if (start <= lastRange.end + 1) { // Extend the last range lastRange.end = Math.max(lastRange.end, end); return; } } // Add new range ranges.push({ start, end }); }); return ranges.map(r => r.start === r.end ? `${r.start}` : `${r.start}-${r.end}`).join(', '); } // importProjectFromQLC method removed - import functionality moved to web UI due to file size constraints // Users should use the LacyLights web interface to import QLC+ files }

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