UNPKG

@jupiterone/jupiterone-mcp

Version:

Model Context Protocol server for JupiterOne account rules and rule details

361 lines 18.4 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.dashboardTools = void 0; const zod_1 = require("zod"); const load_description_js_1 = require("../../utils/load-description.js"); const dashboard_layout_js_1 = require("./dashboard-layout.js"); const errors_js_1 = require("./errors.js"); const result_js_1 = require("./result.js"); const types_js_1 = require("./types.js"); // Poll budget for the widget pre-create query validation gate (attempts × 1s). Small on purpose: // executing the widget's J1QL to completion could take up to a minute, so time-box it and proceed. const WIDGET_VALIDATION_POLL_ATTEMPTS = 5; // The dashboard grid layout uses five responsive breakpoints (xs/sm/md/lg/xl); the API requires // all five as non-null arrays. `moved`/`static` are grid-internal flags an agent laying out a // dashboard almost never sets, so they default to false when omitted. const layoutItemSchema = zod_1.z.object({ w: zod_1.z.number(), h: zod_1.z.number(), x: zod_1.z.number(), y: zod_1.z.number(), i: zod_1.z.string(), moved: zod_1.z.boolean().default(false), static: zod_1.z.boolean().default(false), }); // Auto-layout alternative to hand-packed `layouts`: an ordered list of widget IDs (optionally with // a size). The handler computes all five breakpoints, so agents never build the grid by hand. const autoLayoutItemSchema = zod_1.z.union([ zod_1.z.string(), zod_1.z.object({ id: zod_1.z.string(), size: zod_1.z.enum(['small', 'medium', 'large', 'full']).optional(), }), ]); const pickLayoutItem = (item) => ({ i: item.i, x: item.x, y: item.y, w: item.w, h: item.h, static: item.static, moved: item.moved, }); // Input schema for create-dashboard-widget. Every property is typed (the connector directory // rejects untyped parameters), while both object levels are `.passthrough()` so chart-specific // fields not modeled here are never false-rejected. const widgetInputSchema = zod_1.z .object({ title: zod_1.z.string().describe('Widget title'), description: zod_1.z.string().optional().describe('Widget description'), type: zod_1.z .enum(['area', 'bar', 'graph', 'line', 'matrix', 'number', 'pie', 'table', 'status', 'markdown']) .describe("Chart type, e.g. 'pie', 'bar', 'table', 'number', 'markdown'"), noResultMessage: zod_1.z.string().optional().describe('Message shown when a query returns no results'), config: zod_1.z .object({ queries: zod_1.z .array(zod_1.z.object({ query: zod_1.z.string().describe('J1QL query string'), name: zod_1.z.string().describe('Name identifier for the query'), })) .describe('Queries powering the widget (use an empty array for markdown widgets)'), settings: zod_1.z.record(zod_1.z.any()).optional().describe('Chart-type-specific display settings'), }) .passthrough() .describe('Widget configuration: queries plus chart-specific settings'), }) .passthrough(); // Output schemas document the key fields and expose the typed object; nested shapes are left open // (z.any()) so real data never false-rejects. The SDK validates without stripping the sent payload. const getDashboardsOutput = { total: zod_1.z.number(), dashboards: zod_1.z.array(zod_1.z.record(zod_1.z.any())) }; const createDashboardOutput = { id: zod_1.z.string().optional(), name: zod_1.z.string().optional(), type: zod_1.z.string().optional(), url: zod_1.z.string().optional(), }; const dashboardDetailsOutput = { id: zod_1.z.string().optional(), name: zod_1.z.string().optional(), parameters: zod_1.z.array(zod_1.z.any()).optional(), widgets: zod_1.z.array(zod_1.z.any()).optional(), layouts: zod_1.z.record(zod_1.z.any()).optional(), }; const createWidgetOutput = { id: zod_1.z.string().optional(), title: zod_1.z.string().optional(), type: zod_1.z.string().optional(), dashboardUrl: zod_1.z.string().optional(), }; const updateDashboardOutput = { patchDashboard: zod_1.z.any().optional() }; const deleteWidgetOutput = { success: zod_1.z.boolean().nullish(), widgetId: zod_1.z.string().optional(), dashboardUrl: zod_1.z.string().optional(), }; const updateWidgetOutput = { resultCode: zod_1.z.string().nullish(), widgetId: zod_1.z.string().optional(), dashboardUrl: zod_1.z.string().optional(), }; const deleteDashboardOutput = { success: zod_1.z.boolean().nullish(), dashboardId: zod_1.z.string().optional() }; exports.dashboardTools = [ (0, types_js_1.defineTool)({ name: 'get-dashboards', title: 'Get Dashboards', description: (0, load_description_js_1.loadDescription)('get-dashboards.md'), inputSchema: {}, outputSchema: getDashboardsOutput, annotations: types_js_1.READ_ONLY, errorContext: 'Error getting dashboards', handler: async (_params, client) => { const dashboards = await client.getDashboards(); return (0, result_js_1.structuredResult)({ total: dashboards.length, dashboards: dashboards.map((dashboard) => ({ id: dashboard.id, name: dashboard.name, category: dashboard.category, supportedUseCase: dashboard.supportedUseCase, isJ1ManagedBoard: dashboard.isJ1ManagedBoard, resourceGroupId: dashboard.resourceGroupId, starred: dashboard.starred, lastUpdated: dashboard._timeUpdated, createdAt: dashboard._createdAt, prerequisites: dashboard.prerequisites ? { prerequisitesMet: dashboard.prerequisites.prerequisitesMet, preRequisitesGroupsFulfilled: dashboard.prerequisites.preRequisitesGroupsFulfilled, preRequisitesGroupsRequired: dashboard.prerequisites.preRequisitesGroupsRequired, } : null, })), }); }, }), (0, types_js_1.defineTool)({ name: 'create-dashboard', title: 'Create Dashboard', description: (0, load_description_js_1.loadDescription)('create-dashboard.md'), inputSchema: { name: zod_1.z.string(), type: zod_1.z.enum(['User', 'Account']), }, outputSchema: createDashboardOutput, annotations: types_js_1.CREATE, errorContext: 'Error creating dashboard', handler: async ({ name, type }, client) => { const result = await client.createDashboard({ name, type }); const accountInfo = await client.getAccountInfo(); const dashboardUrl = client.dashboardService.constructDashboardUrl(result.id, accountInfo.subdomain); return (0, result_js_1.structuredResult)({ id: result.id, name, type, url: dashboardUrl }); }, }), (0, types_js_1.defineTool)({ name: 'get-dashboard-details', title: 'Get Dashboard Details', description: (0, load_description_js_1.loadDescription)('get-dashboard-details.md'), inputSchema: { dashboardId: zod_1.z.string(), }, outputSchema: dashboardDetailsOutput, annotations: types_js_1.READ_ONLY, errorContext: 'Error getting dashboard details', handler: async ({ dashboardId }, client) => { const dashboard = await client.getDashboard(dashboardId); return (0, result_js_1.structuredResult)({ id: dashboard.id, name: dashboard.name, category: dashboard.category, supportedUseCase: dashboard.supportedUseCase, isJ1ManagedBoard: dashboard.isJ1ManagedBoard, published: dashboard.published, publishedToUserIds: dashboard.publishedToUserIds, publishedToGroupIds: dashboard.publishedToGroupIds, groupIds: dashboard.groupIds, userIds: dashboard.userIds, scopeFilters: dashboard.scopeFilters, resourceGroupId: dashboard.resourceGroupId, starred: dashboard.starred, lastUpdated: dashboard._timeUpdated, createdAt: dashboard._createdAt, prerequisites: dashboard.prerequisites ? { prerequisitesMet: dashboard.prerequisites.prerequisitesMet, preRequisitesGroupsFulfilled: dashboard.prerequisites.preRequisitesGroupsFulfilled, preRequisitesGroupsRequired: dashboard.prerequisites.preRequisitesGroupsRequired, } : null, parameters: (dashboard.parameters ?? []).map((param) => ({ id: param.id, label: param.label, name: param.name, type: param.type, valueType: param.valueType, default: param.default, options: param.options, requireValue: param.requireValue, disableCustomInput: param.disableCustomInput, })), widgets: (dashboard.widgets ?? []).map((widget) => ({ id: widget.id, title: widget.title, description: widget.description, type: widget.type, questionId: widget.questionId, noResultMessage: widget.noResultMessage, includeDeleted: widget.includeDeleted, config: { queries: (widget.config?.queries ?? []).map((query) => ({ id: query.id, name: query.name, query: query.query, })), settings: widget.config?.settings, postQueryFilters: widget.config?.postQueryFilters, disableQueryPolicyFilters: widget.config?.disableQueryPolicyFilters, }, })), layouts: { xs: (dashboard.layouts?.xs ?? []).map(pickLayoutItem), sm: (dashboard.layouts?.sm ?? []).map(pickLayoutItem), md: (dashboard.layouts?.md ?? []).map(pickLayoutItem), lg: (dashboard.layouts?.lg ?? []).map(pickLayoutItem), xl: (dashboard.layouts?.xl ?? []).map(pickLayoutItem), }, }); }, }), (0, types_js_1.defineTool)({ name: 'create-dashboard-widget', title: 'Create Dashboard Widget', description: (0, load_description_js_1.loadDescription)('create-dashboard-widget.md'), inputSchema: { dashboardId: zod_1.z.string().describe('ID of the dashboard to add the widget to'), input: widgetInputSchema.describe('Widget configuration (CreateInsightsWidgetInput)'), }, outputSchema: createWidgetOutput, annotations: types_js_1.CREATE, errorContext: 'Error creating dashboard widget', handler: async ({ dashboardId, input }, client, validator) => { // Pre-create validation is time-boxed: executing the widget's J1QL to completion can take up // to a minute, so a poll timeout is treated as advisory and creation proceeds. Only definitive // syntax/parse errors block. See J1QLValidator.validateQuery. const validationResults = await (0, errors_js_1.validateQueries)(input.config?.queries, validator, { maxPollAttempts: WIDGET_VALIDATION_POLL_ATTEMPTS, }); if (validationResults.length > 0) { return (0, errors_js_1.createValidationErrorResponse)(validationResults); } const widget = await client.createDashboardWidget(dashboardId, input); const accountInfo = await client.getAccountInfo(); const dashboardUrl = client.dashboardService.constructDashboardUrl(dashboardId, accountInfo.subdomain); return (0, result_js_1.structuredResult)({ ...widget, dashboardUrl }); }, }), (0, types_js_1.defineTool)({ name: 'update-dashboard', title: 'Update Dashboard', description: (0, load_description_js_1.loadDescription)('update-dashboard.md'), inputSchema: { dashboardId: zod_1.z.string().describe('ID of the dashboard to update'), autoLayout: zod_1.z .array(autoLayoutItemSchema) .optional() .describe('RECOMMENDED for initial layout: an ordered list of widget IDs — either a bare ID or ' + '{ id, size } where size is small|medium|large|full (default medium). The server computes ' + 'x/y/w/h for all five breakpoints (left-to-right, top-to-bottom). Mutually exclusive with ' + '`layouts`; supply exactly one.'), layouts: zod_1.z .object({ xs: zod_1.z.array(layoutItemSchema).optional(), sm: zod_1.z.array(layoutItemSchema).optional(), md: zod_1.z.array(layoutItemSchema).optional(), lg: zod_1.z.array(layoutItemSchema).optional(), xl: zod_1.z.array(layoutItemSchema).optional(), }) .optional() .describe('Manual layout for fine control. Use `autoLayout` instead for initial placement. ' + 'Mutually exclusive with `autoLayout`; supply exactly one.'), }, outputSchema: updateDashboardOutput, annotations: types_js_1.MUTATE, errorContext: 'Error updating dashboard', handler: async ({ dashboardId, layouts, autoLayout }, client) => { if (layouts && autoLayout) { return (0, errors_js_1.toErrorResponse)(new Error('Provide either `layouts` or `autoLayout`, not both.'), 'Error updating dashboard'); } if (!layouts && !autoLayout) { return (0, errors_js_1.toErrorResponse)(new Error('Provide a layout: either `autoLayout` (recommended) or `layouts`.'), 'Error updating dashboard'); } // The API requires all five breakpoints as non-null arrays; a partial `layouts` payload (the // common agent mistake behind the high update-dashboard error rate) 500s. autoLayout always // computes the full set; manual layouts default any missing breakpoint to []. const normalizedLayouts = autoLayout ? (0, dashboard_layout_js_1.computeAutoLayout)(autoLayout) : { xs: layouts?.xs ?? [], sm: layouts?.sm ?? [], md: layouts?.md ?? [], lg: layouts?.lg ?? [], xl: layouts?.xl ?? [], }; const updated = await client.updateDashboard({ dashboardId, layouts: normalizedLayouts }); return (0, result_js_1.structuredResult)(updated); }, }), (0, types_js_1.defineTool)({ name: 'update-dashboard-widget', title: 'Update Dashboard Widget', description: (0, load_description_js_1.loadDescription)('update-dashboard-widget.md'), inputSchema: { dashboardId: zod_1.z.string().describe('ID of the dashboard the widget belongs to'), widgetId: zod_1.z.string().describe('ID of the widget to update'), input: widgetInputSchema.describe('The full replacement widget definition (same shape as create-dashboard-widget). This ' + 'replaces the widget, so include all fields, not just the ones you are changing.'), }, outputSchema: updateWidgetOutput, annotations: types_js_1.MUTATE, errorContext: 'Error updating dashboard widget', handler: async ({ dashboardId, widgetId, input }, client) => { const result = await client.updateDashboardWidget(dashboardId, { id: widgetId, ...input }); const accountInfo = await client.getAccountInfo(); const dashboardUrl = client.dashboardService.constructDashboardUrl(dashboardId, accountInfo.subdomain); return (0, result_js_1.structuredResult)({ resultCode: result.resultCode, widgetId, dashboardUrl }); }, }), (0, types_js_1.defineTool)({ name: 'delete-dashboard-widget', title: 'Delete Dashboard Widget', description: (0, load_description_js_1.loadDescription)('delete-dashboard-widget.md'), inputSchema: { dashboardId: zod_1.z.string().describe('ID of the dashboard the widget belongs to'), widgetId: zod_1.z.string().describe('ID of the widget to delete'), }, outputSchema: deleteWidgetOutput, annotations: types_js_1.MUTATE, errorContext: 'Error deleting dashboard widget', handler: async ({ dashboardId, widgetId }, client) => { const result = await client.deleteDashboardWidget(dashboardId, widgetId); const accountInfo = await client.getAccountInfo(); const dashboardUrl = client.dashboardService.constructDashboardUrl(dashboardId, accountInfo.subdomain); return (0, result_js_1.structuredResult)({ success: result.success, widgetId, dashboardUrl }); }, }), (0, types_js_1.defineTool)({ name: 'delete-dashboard', title: 'Delete Dashboard', description: (0, load_description_js_1.loadDescription)('delete-dashboard.md'), inputSchema: { dashboardId: zod_1.z.string().describe('ID of the dashboard to delete'), }, outputSchema: deleteDashboardOutput, annotations: types_js_1.MUTATE, errorContext: 'Error deleting dashboard', handler: async ({ dashboardId }, client) => { const result = await client.deleteDashboard(dashboardId); return (0, result_js_1.structuredResult)({ success: result.success, dashboardId }); }, }), ]; //# sourceMappingURL=dashboards.js.map