@jupiterone/jupiterone-mcp 0.0.2 → 0.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,35 @@
1
+ # Get Integration Instances Tool
2
+
3
+ Get all integration instances in your JupiterOne account. This tool returns a list of configured integration instances, including their configuration, status, and recent job information. Integration instances are the actual configured connections to external services like AWS accounts, GitHub repositories, etc. Unless you have an integration definition id, you typically will not want to query this yet. To get an integration definition id, use the `get-integration-definitions` tool. If you need an integration instance id for another task (such as creating a rule action), ask the user which of the possible integrations they want you to use.
4
+
5
+ ## Parameters
6
+ - `definitionId` (optional): Filter instances by a specific integration definition ID. Use this to get only instances of a particular integration type.
7
+ - `limit` (optional): Maximum number of instances to return (between 1 and 1000).
8
+
9
+ ## Example Usage
10
+ Get all integration instances:
11
+ ```json
12
+ {}
13
+ ```
14
+
15
+ Get the first 10 integration instances:
16
+ ```json
17
+ {
18
+ "limit": 10
19
+ }
20
+ ```
21
+
22
+ Get all instances of a specific integration type:
23
+ ```json
24
+ {
25
+ "definitionId": "integration-definition-id-here"
26
+ }
27
+ ```
28
+
29
+ Get the first 5 instances of a specific integration type:
30
+ ```json
31
+ {
32
+ "definitionId": "integration-definition-id-here",
33
+ "limit": 5
34
+ }
35
+ ```
@@ -1,14 +1,53 @@
1
1
  # List Rules Tool
2
2
 
3
- List all rules in your JupiterOne account. This tool returns a list of rule instances, including their IDs, names, descriptions, versions, polling intervals, and other metadata. You can optionally specify a limit to restrict the number of rules returned. This does not get alerts, but rather the configuration behind what may generate an alert, or other workflow action.
3
+ List rules in your JupiterOne account using cursor pagination. This tool returns a page of rule instances, including their IDs, names, descriptions, versions, polling intervals, and other metadata. Use the cursor parameter to navigate through pages of results. This does not get alerts, but rather the configuration behind what may generate an alert, or other workflow action.
4
4
 
5
5
  ## Parameters
6
- - `limit` (optional): Maximum number of rules to return (between 1 and 1000).
6
+ - `limit` (optional): Maximum number of rules to return per page (between 1 and 1000). Defaults to 100 if not specified.
7
+ - `cursor` (optional): Pagination cursor to get the next page of results. Use the `endCursor` from a previous response's `pageInfo`. Omit this parameter to get the first page.
7
8
 
8
9
  ## Example Usage
9
- Request the first 10 rules:
10
+ Get the first page of rules (default page size):
11
+ ```json
12
+ {}
13
+ ```
14
+
15
+ Get the first page with specific limit:
16
+ ```json
17
+ {
18
+ "limit": 50
19
+ }
20
+ ```
21
+
22
+ Get the next page using a cursor:
10
23
  ```json
11
24
  {
12
- "limit": 10
25
+ "limit": 50,
26
+ "cursor": "cursor_value_from_previous_response"
13
27
  }
14
- ```
28
+ ```
29
+
30
+ ## Response Format
31
+ All responses include pagination information:
32
+ ```json
33
+ {
34
+ "returned": 50,
35
+ "rules": [...],
36
+ "pageInfo": {
37
+ "hasNextPage": true,
38
+ "endCursor": "cursor_for_next_page"
39
+ }
40
+ }
41
+ ```
42
+
43
+ - `returned`: Number of rules in this page
44
+ - `rules`: Array of rule objects
45
+ - `pageInfo.hasNextPage`: Whether there are more pages available
46
+ - `pageInfo.endCursor`: Cursor to use for the next page (if `hasNextPage` is true)
47
+
48
+ ## Pagination Pattern
49
+ To get all rules across multiple pages:
50
+ 1. Call with no cursor to get the first page
51
+ 2. Check if `pageInfo.hasNextPage` is true
52
+ 3. If true, call again with `cursor` set to `pageInfo.endCursor`
53
+ 4. Repeat until `hasNextPage` is false
@@ -0,0 +1,363 @@
1
+ # JupiterOne Rule Update Tool - Complete Guide
2
+
3
+ **Purpose**: Updates existing inline question-based alert rules in JupiterOne. This tool modifies the configuration of an existing rule while preserving its identity and version history.
4
+
5
+ **Important**: Before updating a rule, use the `get-rule-details` tool to retrieve the current configuration. This ensures you have all required fields and can see what needs to be changed.
6
+
7
+ ## Key Requirements for Updates
8
+
9
+ ### 1. Required Fields for Updates
10
+ When updating a rule, you must provide **ALL** fields, not just the ones you want to change. The update operation replaces the entire rule configuration, so missing fields will result in errors.
11
+
12
+ **Critical Required Fields**:
13
+ - `id`: The existing rule ID (from `get-rule-details`)
14
+ - `version`: The current version number (from `get-rule-details`)
15
+ - `specVersion`: Usually 1
16
+ - `ignorePreviousResults`: Must be included
17
+ - `templates`: Must be included (use `{}` if empty)
18
+ - `tags`: Must be included but should always be empty `[]` (deprecated)
19
+ - `labels`: Use this for actual tagging functionality
20
+ - `resourceGroupId`: Must be included (can be null)
21
+ - `remediationSteps`: Must be included (can be null)
22
+
23
+ ### 2. Condition Format (Critical)
24
+ The `condition` parameter must use JupiterOne's specific array format:
25
+ - **Structure**: `["LOGICAL_OPERATOR", [left_value, operator, right_value]]`
26
+ - **Example**: `["AND", ["queries.queryName.total", ">", 0]]`
27
+ - **Supported operators**: `>`, `<`, `>=`, `<=`, `=`, `!=`
28
+ - **Logical operators**: `"AND"`, `"OR"`
29
+
30
+ ### 3. Operations Structure
31
+ The `when` clause should only contain:
32
+ - `type`: Always `"FILTER"`
33
+ - `condition`: The array format described above
34
+ - **Do NOT include**: `version`, `specVersion` (these belong at the rule level, not in the when clause)
35
+
36
+ ### 4. Query Naming Convention
37
+ - Query names in the `queries` array must match the references in conditions
38
+ - Example: If query name is `"users"`, reference it as `"queries.users.total"`
39
+ - **IMPORTANT**: Use `"query0"` as the standard query name for compatibility with existing patterns
40
+
41
+ ### 5. Version Management
42
+ - The `version` field will be automatically incremented by JupiterOne
43
+ - You must provide the current version number in your update request
44
+ - Get the current version using `get-rule-details` before updating
45
+
46
+ ### 6. Tags vs Labels (Important)
47
+ - **DEPRECATED**: The `tags` array field is deprecated and should always be set to an empty array `[]`
48
+ - **USE INSTEAD**: For tagging functionality, use the `labels` field with key-value pairs
49
+ - **Format**: `labels: [{"labelName": "key", "labelValue": "value"}]`
50
+ - **When users ask for tagging**: Always use the `labels` field to meet their needs
51
+ - **Note**: The `tags` field is still required in the schema for compatibility but should remain empty
52
+
53
+ ## Update Workflow
54
+
55
+ ### Step 1: Get Current Rule Configuration
56
+ ```
57
+ Use get-rule-details with the rule ID to get the current configuration
58
+ ```
59
+
60
+ ### Step 2: Modify Required Fields
61
+ Update only the fields you need to change while preserving all other required fields.
62
+
63
+ ### Step 3: Submit Update
64
+ Use this tool with the complete configuration including your changes.
65
+
66
+ ## Required Schema Fields for Updates
67
+
68
+ ### Complete Required Parameters for update-inline-question-rule
69
+ **CRITICAL**: All of these fields must be included for successful rule updates:
70
+
71
+ ```json
72
+ {
73
+ "id": "existing-rule-id",
74
+ "name": "Updated Rule Name",
75
+ "description": "Updated rule description",
76
+ "notifyOnFailure": true,
77
+ "triggerActionsOnNewEntitiesOnly": true,
78
+ "ignorePreviousResults": false,
79
+ "pollingInterval": "ONE_DAY",
80
+ "specVersion": 1,
81
+ "version": 2,
82
+ "templates": {},
83
+ "outputs": ["alertLevel"],
84
+ "tags": [],
85
+ "labels": [
86
+ {"labelName": "environment", "labelValue": "production"},
87
+ {"labelName": "team", "labelValue": "security"}
88
+ ],
89
+ "resourceGroupId": null,
90
+ "remediationSteps": null,
91
+ "question": {
92
+ "queries": [
93
+ {
94
+ "query": "FIND Entity...",
95
+ "name": "query0",
96
+ "version": "v1",
97
+ "includeDeleted": false
98
+ }
99
+ ]
100
+ },
101
+ "operations": [
102
+ {
103
+ "when": {
104
+ "type": "FILTER",
105
+ "condition": ["AND", ["queries.query0.total", ">", 0]]
106
+ },
107
+ "actions": [...]
108
+ }
109
+ ]
110
+ }
111
+ ```
112
+
113
+ **Key Update Requirements**:
114
+ - `id`: Must match the existing rule ID
115
+ - `version`: Must be the current version number from the existing rule
116
+ - `ignorePreviousResults`: Must be included (typically `false`)
117
+ - `templates`: Must be included (use `{}` if empty)
118
+ - `tags`: Must be included but should always be empty `[]` (deprecated field)
119
+ - `labels`: Use this for actual tagging functionality with key-value pairs
120
+ - `resourceGroupId`: Must be included (can be null)
121
+ - `remediationSteps`: Must be included (can be null)
122
+ - Query `name`: Use `"query0"` for primary query
123
+ - Query `version`: Include `"v1"` for compatibility
124
+ - Query `includeDeleted`: Must be explicitly set to `false`
125
+
126
+ ## Available Action Types
127
+
128
+ ### 1. SET_PROPERTY
129
+ Sets a property value on the alert (commonly used for alert severity levels).
130
+
131
+ **Configuration**:
132
+ ```json
133
+ {
134
+ "type": "SET_PROPERTY",
135
+ "targetProperty": "alertLevel",
136
+ "targetValue": "CRITICAL"
137
+ }
138
+ ```
139
+
140
+ **Common Values for alertLevel**: `"LOW"`, `"MEDIUM"`, `"HIGH"`, `"CRITICAL"`, `"INFO"`
141
+
142
+ ### 2. CREATE_ALERT
143
+ Creates a basic alert in JupiterOne.
144
+
145
+ **Configuration**:
146
+ ```json
147
+ {
148
+ "type": "CREATE_ALERT"
149
+ }
150
+ ```
151
+
152
+ **Note**: This is the most basic action and should almost always be included.
153
+
154
+ ### 3. SEND_EMAIL
155
+ Sends email notifications to specified recipients.
156
+
157
+ **Configuration**:
158
+ ```json
159
+ {
160
+ "type": "SEND_EMAIL",
161
+ "recipients": ["user1@company.com", "user2@company.com"],
162
+ "body": "Affected Items: <br><br>* {{queries.query0.data|mapProperty('displayName')|join('<br>* ')}}"
163
+ }
164
+ ```
165
+
166
+ ### 4. TAG_ENTITIES
167
+ Adds or removes tags from entities that triggered the rule.
168
+
169
+ **Configuration**:
170
+ ```json
171
+ {
172
+ "type": "TAG_ENTITIES",
173
+ "entities": "{{queries.query0.data}}",
174
+ "tags": [
175
+ {"name": "existing tag to remove", "value": null},
176
+ {"name": "new tag", "value": "tag value"}
177
+ ]
178
+ }
179
+ ```
180
+
181
+ ### 5. SEND_SLACK_MESSAGE
182
+ Sends messages to Slack channels (requires Slack integration).
183
+
184
+ **Configuration**:
185
+ ```json
186
+ {
187
+ "integrationInstanceId": "d97d9127-c532-410a-bf0a-9ea93f66c3d2",
188
+ "type": "SEND_SLACK_MESSAGE",
189
+ "channels": ["#security-alerts", "#general"],
190
+ "body": "*Affected Items:* \n\n- {{queries.query0.data|mapProperty('displayName')|join('\n- ')}}"
191
+ }
192
+ ```
193
+
194
+ ### 6. SEND_TO_S3
195
+ Sends alert data to an S3 bucket (requires AWS S3 integration).
196
+
197
+ **Configuration**:
198
+ ```json
199
+ {
200
+ "integrationInstanceId": "f89568b4-2a1b-4bd8-8abd-aee21270df75",
201
+ "type": "SEND_TO_S3",
202
+ "bucket": "security-alerts-bucket",
203
+ "region": "us-east-1",
204
+ "data": {
205
+ "description": "{{alertWebLink}}\n\n**Affected Items:**\n\n* {{queries.query0.data|mapProperty('displayName')|join('\n* ')}}"
206
+ }
207
+ }
208
+ ```
209
+
210
+ ### 7. CREATE_JIRA_TICKET
211
+ Creates a Jira ticket for the alert (requires Jira integration).
212
+
213
+ **Configuration**:
214
+ ```json
215
+ {
216
+ "integrationInstanceId": "53a99eaa-18a5-45ef-b748-2de39d642a91",
217
+ "type": "CREATE_JIRA_TICKET",
218
+ "entityClass": "Finding",
219
+ "summary": "Security Alert: Critical Unencrypted Data Found",
220
+ "issueType": "Bug",
221
+ "project": "SEC",
222
+ "updateContentOnChanges": false,
223
+ "additionalFields": {
224
+ "description": {
225
+ "type": "doc",
226
+ "version": 1,
227
+ "content": [
228
+ {
229
+ "type": "paragraph",
230
+ "content": [
231
+ {
232
+ "type": "text",
233
+ "text": "{{alertWebLink}}\n\n**Affected Items:**\n\n* {{queries.query0.data|mapProperty('displayName')|join('\n* ')}}"
234
+ }
235
+ ]
236
+ }
237
+ ]
238
+ }
239
+ }
240
+ }
241
+ ```
242
+
243
+ ## Template Variables and Formatting
244
+
245
+ ### Available Variables
246
+ - `{{alertWebLink}}` - Direct link to the alert in JupiterOne
247
+ - `{{queries.queryName.data}}` - Array of entities from the specified query
248
+ - `{{queries.queryName.total}}` - Count of entities from the query
249
+
250
+ ### Data Formatting
251
+ - `|mapProperty('fieldName')` - Extract specific field from each entity
252
+ - `|join('separator')` - Join array elements with specified separator
253
+ - Example: `{{queries.users.data|mapProperty('displayName')|join(', ')}}` - Creates comma-separated list of user names
254
+
255
+ ## Integration Dependencies
256
+
257
+ For actions requiring integrations, you may need to:
258
+ 1. Query available integration instances using `get-integration-instances`
259
+ 2. Ask the user which integration to use
260
+ 3. Use the integration's `id` as the `integrationInstanceId`
261
+
262
+ **Actions requiring integrations**:
263
+ - `SEND_SLACK_MESSAGE` (Slack integration)
264
+ - `SEND_TO_S3` (AWS S3 integration)
265
+ - `CREATE_JIRA_TICKET` (Jira integration)
266
+
267
+ ## Working Example Update
268
+
269
+ ### Complete Working Rule Update Structure
270
+ ```json
271
+ {
272
+ "id": "12345678-1234-1234-1234-123456789abc",
273
+ "name": "Updated Rule Name",
274
+ "description": "Updated rule description",
275
+ "notifyOnFailure": true,
276
+ "triggerActionsOnNewEntitiesOnly": true,
277
+ "ignorePreviousResults": false,
278
+ "pollingInterval": "ONE_DAY",
279
+ "specVersion": 1,
280
+ "version": 3,
281
+ "templates": {},
282
+ "outputs": ["alertLevel"],
283
+ "tags": [],
284
+ "labels": [
285
+ {"labelName": "severity", "labelValue": "high"},
286
+ {"labelName": "category", "labelValue": "security"}
287
+ ],
288
+ "resourceGroupId": null,
289
+ "remediationSteps": "1. Review the affected entities\n2. Apply security patches\n3. Update configurations",
290
+ "question": {
291
+ "queries": [
292
+ {
293
+ "query": "FIND Entity WITH condition",
294
+ "name": "query0",
295
+ "version": "v1",
296
+ "includeDeleted": false
297
+ }
298
+ ]
299
+ },
300
+ "operations": [
301
+ {
302
+ "when": {
303
+ "type": "FILTER",
304
+ "condition": ["AND", ["queries.query0.total", ">", 0]]
305
+ },
306
+ "actions": [
307
+ {
308
+ "type": "SET_PROPERTY",
309
+ "targetProperty": "alertLevel",
310
+ "targetValue": "CRITICAL"
311
+ },
312
+ {
313
+ "type": "CREATE_ALERT"
314
+ },
315
+ {
316
+ "type": "SEND_EMAIL",
317
+ "recipients": ["updated-user@company.com"],
318
+ "body": "Updated notification: {{alertWebLink}}"
319
+ }
320
+ ]
321
+ }
322
+ ]
323
+ }
324
+ ```
325
+
326
+ ## Common Update Scenarios
327
+
328
+ ### 1. Changing Notification Recipients
329
+ Update only the `recipients` array in the `SEND_EMAIL` action while preserving all other fields.
330
+
331
+ ### 2. Modifying Polling Interval
332
+ Update the `pollingInterval` field while keeping all other configuration the same.
333
+
334
+ ### 3. Adding New Actions
335
+ Add new actions to the `actions` array in the operations.
336
+
337
+ ### 4. Updating Query Logic
338
+ Modify the `query` string in the queries array or adjust the `condition` in operations.
339
+
340
+ ### 5. Changing Labels
341
+ Update the `labels` array to add, remove, or modify rule labels.
342
+
343
+ ## Debugging Tips
344
+ - Always start by getting the current rule configuration with `get-rule-details`
345
+ - Ensure the `version` number matches the current rule version
346
+ - Include ALL required fields, even if they're not changing
347
+ - If you get "Invalid conjunction operator" errors, check the condition array format
348
+ - If you get "additional properties" errors, remove extra fields from the `when` clause
349
+ - If you get missing property errors, ensure all required schema fields are included
350
+ - **Always include**: `id`, `version`, `ignorePreviousResults`, `templates`, `tags`, `labels`, `resourceGroupId`, `remediationSteps`
351
+ - Use `"query0"` as the standard query name for compatibility
352
+
353
+ ## Best Practices for Updates
354
+ - Always retrieve the current rule configuration first using `get-rule-details`
355
+ - Only modify the fields that actually need to change
356
+ - Preserve the existing `version` number (it will be auto-incremented)
357
+ - Use the `labels` field for rule organization and tagging (not the deprecated `tags` field)
358
+ - Test rule changes with simple modifications first
359
+ - Document changes in the `description` field if significant
360
+ - When users request tagging functionality, use the `labels` field with key-value pairs
361
+ - Always include `CREATE_ALERT` action as a baseline unless specifically removing it
362
+
363
+ This format ensures reliable rule updates and helps avoid common pitfalls encountered during rule modification.
@@ -2,8 +2,13 @@ import { JupiterOneConfig } from '../types/jupiterone.js';
2
2
  export declare class JupiterOneMcpServer {
3
3
  private server;
4
4
  private client;
5
+ private validator;
5
6
  constructor(config: JupiterOneConfig);
6
7
  private setupTools;
8
+ private validateQueries;
9
+ private validateWidgetQueries;
10
+ private createValidationErrorResponse;
11
+ private createQueryErrorResponse;
7
12
  start(): Promise<void>;
8
13
  stop(): Promise<void>;
9
14
  }
@@ -1 +1 @@
1
- {"version":3,"file":"mcp-server.d.ts","sourceRoot":"","sources":["../../src/server/mcp-server.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAW1D,qBAAa,mBAAmB;IAC9B,OAAO,CAAC,MAAM,CAAY;IAC1B,OAAO,CAAC,MAAM,CAAmB;gBAErB,MAAM,EAAE,gBAAgB;IAUpC,OAAO,CAAC,UAAU;IA2tDZ,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAKtB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;CAG5B"}
1
+ {"version":3,"file":"mcp-server.d.ts","sourceRoot":"","sources":["../../src/server/mcp-server.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAY1D,qBAAa,mBAAmB;IAC9B,OAAO,CAAC,MAAM,CAAY;IAC1B,OAAO,CAAC,MAAM,CAAmB;IACjC,OAAO,CAAC,SAAS,CAAgB;gBAErB,MAAM,EAAE,gBAAgB;IAWpC,OAAO,CAAC,UAAU;YA4uDJ,eAAe;YAqBf,qBAAqB;IAOnC,OAAO,CAAC,6BAA6B;IAkBrC,OAAO,CAAC,wBAAwB;IAc1B,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAKtB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;CAG5B"}
@@ -3,11 +3,14 @@ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
3
3
  import { z } from 'zod';
4
4
  import { JupiterOneClient } from '../client/jupiterone-client.js';
5
5
  import { loadDescription } from '../utils/load-description.js';
6
+ import { J1QLValidator } from '../utils/j1ql-validator.js';
6
7
  export class JupiterOneMcpServer {
7
8
  server;
8
9
  client;
10
+ validator;
9
11
  constructor(config) {
10
12
  this.client = new JupiterOneClient(config);
13
+ this.validator = new J1QLValidator(this.client.j1qlService);
11
14
  this.server = new McpServer({
12
15
  name: 'jupiterone-mcp',
13
16
  version: '1.0.0',
@@ -18,18 +21,18 @@ export class JupiterOneMcpServer {
18
21
  // Tool: List all rules
19
22
  this.server.tool('list-rules', loadDescription('list-rules.md'), {
20
23
  limit: z.number().min(1).max(1000).optional(),
21
- }, async ({ limit }) => {
24
+ cursor: z.string().optional(),
25
+ }, async ({ limit, cursor }) => {
22
26
  try {
23
- const instances = await this.client.getAllRuleInstances();
24
- const limitedInstances = limit ? instances.slice(0, limit) : instances;
27
+ const response = await this.client.listRuleInstances(limit, cursor);
28
+ const instances = response.questionInstances || [];
25
29
  return {
26
30
  content: [
27
31
  {
28
32
  type: 'text',
29
33
  text: JSON.stringify({
30
- total: instances.length,
31
- returned: limitedInstances.length,
32
- rules: limitedInstances.map((instance) => ({
34
+ returned: instances.length,
35
+ rules: instances.map((instance) => ({
33
36
  id: instance.id,
34
37
  name: instance.name,
35
38
  description: instance.description,
@@ -43,6 +46,7 @@ export class JupiterOneMcpServer {
43
46
  tags: instance.tags,
44
47
  outputs: instance.outputs,
45
48
  })),
49
+ pageInfo: response.pageInfo,
46
50
  }, null, 2),
47
51
  },
48
52
  ],
@@ -469,11 +473,12 @@ export class JupiterOneMcpServer {
469
473
  }
470
474
  });
471
475
  // Tool: Get integration definitions
472
- this.server.tool('get-integration-definitions', {
476
+ this.server.tool('get-integration-definitions', loadDescription('get-integration-definitions.md'), {
477
+ cursor: z.string().optional().describe('Optional cursor for pagination'),
473
478
  includeConfig: z.boolean().optional().describe('Whether to include configuration fields'),
474
- }, async ({ includeConfig }) => {
479
+ }, async ({ cursor, includeConfig }) => {
475
480
  try {
476
- const definitions = await this.client.getIntegrationDefinitions(undefined, includeConfig);
481
+ const definitions = await this.client.getIntegrationDefinitions(cursor, includeConfig);
477
482
  return {
478
483
  content: [
479
484
  {
@@ -524,7 +529,7 @@ export class JupiterOneMcpServer {
524
529
  }
525
530
  });
526
531
  // Tool: Get integration instances
527
- this.server.tool('get-integration-instances', {
532
+ this.server.tool('get-integration-instances', loadDescription('get-integration-instances.md'), {
528
533
  definitionId: z
529
534
  .string()
530
535
  .optional()
@@ -762,6 +767,11 @@ export class JupiterOneMcpServer {
762
767
  .describe('Operations to perform when conditions are met'),
763
768
  }, async ({ name, description, notifyOnFailure, triggerActionsOnNewEntitiesOnly, ignorePreviousResults, pollingInterval, outputs, specVersion, tags, templates, queries, operations, }) => {
764
769
  try {
770
+ // Validate all queries before creating the rule
771
+ const validationResults = await this.validateQueries(queries);
772
+ if (validationResults.length > 0) {
773
+ return this.createValidationErrorResponse(validationResults);
774
+ }
765
775
  const instance = {
766
776
  name,
767
777
  description,
@@ -820,7 +830,7 @@ export class JupiterOneMcpServer {
820
830
  }
821
831
  });
822
832
  // Tool: Update inline question rule instance
823
- this.server.tool('update-inline-question-rule', {
833
+ this.server.tool('update-inline-question-rule', loadDescription('update-inline-question-rule.md'), {
824
834
  id: z.string().describe('ID of the rule to update'),
825
835
  name: z.string().describe('Name of the rule'),
826
836
  description: z.string().describe('Description of the rule'),
@@ -933,6 +943,11 @@ export class JupiterOneMcpServer {
933
943
  .describe('Operations that define when and what actions to take'),
934
944
  }, async ({ id, name, description, notifyOnFailure, triggerActionsOnNewEntitiesOnly, ignorePreviousResults, pollingInterval, outputs, specVersion, version, tags, templates, labels, resourceGroupId, remediationSteps, question, operations, }) => {
935
945
  try {
946
+ // Validate all queries before updating the rule
947
+ const validationResults = await this.validateQueries(question?.queries);
948
+ if (validationResults.length > 0) {
949
+ return this.createValidationErrorResponse(validationResults);
950
+ }
936
951
  const instance = {
937
952
  id,
938
953
  name,
@@ -1316,6 +1331,11 @@ export class JupiterOneMcpServer {
1316
1331
  throw new Error('Input must be a valid object or JSON string');
1317
1332
  }
1318
1333
  }
1334
+ // Validate queries before creating widget
1335
+ const validationResults = await this.validateWidgetQueries(widgetInput.config?.queries);
1336
+ if (validationResults.length > 0) {
1337
+ return this.createValidationErrorResponse(validationResults);
1338
+ }
1319
1339
  const widget = await this.client.createDashboardWidget(dashboardId, widgetInput);
1320
1340
  return {
1321
1341
  content: [
@@ -1473,18 +1493,58 @@ export class JupiterOneMcpServer {
1473
1493
  };
1474
1494
  }
1475
1495
  catch (error) {
1476
- return {
1477
- content: [
1478
- {
1479
- type: 'text',
1480
- text: `Error executing J1QL query: ${error instanceof Error ? error.message : 'Unknown error'}`,
1481
- },
1482
- ],
1483
- isError: true,
1484
- };
1496
+ return this.createQueryErrorResponse(error, query);
1485
1497
  }
1486
1498
  });
1487
1499
  }
1500
+ // Helper methods for validation
1501
+ async validateQueries(queries) {
1502
+ if (!queries || !Array.isArray(queries))
1503
+ return [];
1504
+ const validationResults = [];
1505
+ for (const queryObj of queries) {
1506
+ if (queryObj.query) {
1507
+ const validation = await this.validator.validateQuery(queryObj.query);
1508
+ if (!validation.isValid) {
1509
+ validationResults.push({
1510
+ queryName: queryObj.name || 'Unnamed query',
1511
+ error: validation.error || 'Query validation failed',
1512
+ suggestion: validation.suggestion || 'Please check the query syntax and try again',
1513
+ });
1514
+ }
1515
+ }
1516
+ }
1517
+ return validationResults;
1518
+ }
1519
+ async validateWidgetQueries(queries) {
1520
+ // Use the same validation logic as rules - actually execute the queries
1521
+ return this.validateQueries(queries);
1522
+ }
1523
+ createValidationErrorResponse(validationResults) {
1524
+ return {
1525
+ content: [
1526
+ {
1527
+ type: 'text',
1528
+ text: `Query validation failed. Please fix the following issues:\n\n${validationResults
1529
+ .map((r) => `Query: ${r.queryName}\nError: ${r.error}\nSuggestion: ${r.suggestion}`)
1530
+ .join('\n\n')}\n\nUse the execute-j1ql-query tool to test and refine your queries first.`,
1531
+ },
1532
+ ],
1533
+ isError: true,
1534
+ };
1535
+ }
1536
+ createQueryErrorResponse(error, query) {
1537
+ const errorResult = this.validator.handleQueryError(error, query);
1538
+ return {
1539
+ content: [
1540
+ {
1541
+ type: 'text',
1542
+ text: `Error executing J1QL query:\n\n${errorResult.error}\n\nSuggestion: ${errorResult.suggestion}\n\nDebugging tips:\n1. Modify existing queries that are already working as expected.\n2. Use discovery queries to understand available data\n3. Verify entity classes exist (use proper capitalization)\n4. Check property names match exactly\n5. Use single quotes for strings, not double quotes\n6. Place aliases after WITH statements\n7. Add LIMIT clause to prevent timeouts`,
1543
+ },
1544
+ ],
1545
+ isError: true,
1546
+ };
1547
+ }
1488
1548
  async start() {
1489
1549
  const transport = new StdioServerTransport();
1490
1550
  await this.server.connect(transport);