UNPKG

powerplatform-mcp

Version:

PowerPlatform Model Context Protocol server

112 lines (111 loc) 6.13 kB
import { outputResult } from '../output.js'; function parseJsonArg(raw, argName) { try { const parsed = JSON.parse(raw); if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) { throw new Error(`${argName} must be a JSON object`); } return parsed; } catch (err) { throw new Error(`Invalid JSON for ${argName}: ${err.message}`); } } export function registerRecordCommands(program, registry) { program .command('record <entityNamePlural> <recordId>') .description('Get a specific record by entity (plural) and ID') .action(async (entityNamePlural, recordId, _opts, command) => { const ctx = registry.getContext(command.optsWithGlobals().env); const service = ctx.getRecordService(); const record = await service.getRecord(entityNamePlural, recordId); const keys = Object.keys(record).filter((k) => !k.startsWith('@') && !k.startsWith('_')); const preview = keys.slice(0, 5).map((k) => `${k}: ${record[k]}`).join(', '); outputResult({ fileName: `${entityNamePlural}-record-${recordId}`, data: record, summary: [ `Record from '${entityNamePlural}' (${recordId}):`, ` Fields: ${keys.length}`, ` Preview: ${preview}${keys.length > 5 ? ', ...' : ''}`, ].join('\n'), }, ctx.environmentName); }); program .command('query-records <entityNamePlural> <filter>') .description('Query records using an OData filter expression') .option('--max <number>', 'Maximum records', '50') .action(async (entityNamePlural, filter, opts, command) => { const ctx = registry.getContext(command.optsWithGlobals().env); const service = ctx.getRecordService(); const result = await service.queryRecords(entityNamePlural, filter, parseInt(opts.max, 10)); const records = result.value || []; // Show field names from first record let fieldPreview = ''; if (records.length > 0) { const keys = Object.keys(records[0]).filter((k) => !k.startsWith('@') && !k.startsWith('_')); fieldPreview = `Fields: ${keys.slice(0, 8).join(', ')}${keys.length > 8 ? ', ...' : ''}`; } const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19); outputResult({ fileName: `${entityNamePlural}-query-${timestamp}`, data: result, summary: [ `Query '${entityNamePlural}' where ${filter}:`, ` Records returned: ${records.length}`, fieldPreview ? ` ${fieldPreview}` : '', ].filter(Boolean).join('\n'), }, ctx.environmentName); }); program .command('create-record <entityNamePlural> <jsonBody>') .description('Create a record. jsonBody is a JSON object; use @odata.bind for lookups.') .action(async (entityNamePlural, jsonBody, _opts, command) => { const ctx = registry.getContext(command.optsWithGlobals().env); const data = parseJsonArg(jsonBody, 'jsonBody'); const result = await ctx.getRecordService().createRecord(entityNamePlural, data); const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19); outputResult({ fileName: `${entityNamePlural}-create-${timestamp}`, data: result, summary: [ `Created record in '${entityNamePlural}':`, ` id: ${result.entityId}`, ].join('\n'), }, ctx.environmentName); }); program .command('update-record <entityNamePlural> <recordId> <jsonBody>') .description('Update a record via PATCH. jsonBody is a partial JSON object.') .action(async (entityNamePlural, recordId, jsonBody, _opts, command) => { const ctx = registry.getContext(command.optsWithGlobals().env); const data = parseJsonArg(jsonBody, 'jsonBody'); await ctx.getRecordService().updateRecord(entityNamePlural, recordId, data); console.log(`Updated '${entityNamePlural}' (${recordId}): ${Object.keys(data).length} field(s)`); }); program .command('delete-record <entityNamePlural> <recordId>') .description('Delete a record.') .action(async (entityNamePlural, recordId, _opts, command) => { const ctx = registry.getContext(command.optsWithGlobals().env); await ctx.getRecordService().deleteRecord(entityNamePlural, recordId); console.log(`Deleted '${entityNamePlural}' (${recordId})`); }); program .command('associate-records <entityNamePlural> <recordId> <navigationProperty> <relatedEntityNamePlural> <relatedRecordId>') .description('Associate two records across a navigation property (N:N or 1:N).') .action(async (entityNamePlural, recordId, navigationProperty, relatedEntityNamePlural, relatedRecordId, _opts, command) => { const ctx = registry.getContext(command.optsWithGlobals().env); await ctx.getRecordService().associateRecords(entityNamePlural, recordId, navigationProperty, relatedEntityNamePlural, relatedRecordId); console.log(`Associated '${entityNamePlural}'(${recordId}).${navigationProperty} -> '${relatedEntityNamePlural}'(${relatedRecordId})`); }); program .command('disassociate-records <entityNamePlural> <recordId> <navigationProperty> [relatedRecordId]') .description('Disassociate records. Omit relatedRecordId for single-valued navigation properties.') .action(async (entityNamePlural, recordId, navigationProperty, relatedRecordId, _opts, command) => { const ctx = registry.getContext(command.optsWithGlobals().env); await ctx.getRecordService().disassociateRecords(entityNamePlural, recordId, navigationProperty, relatedRecordId); const target = relatedRecordId ? ` -> ${relatedRecordId}` : ''; console.log(`Disassociated '${entityNamePlural}'(${recordId}).${navigationProperty}${target}`); }); }