@jupiterone/jupiterone-mcp
Version:
Model Context Protocol server for JupiterOne account rules and rule details
361 lines • 18.4 kB
JavaScript
"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