From bc511b00e7de7a60a710cf1608158bc4ae4f5971 Mon Sep 17 00:00:00 2001 From: hesto2 Date: Thu, 31 Jul 2025 15:35:55 -0600 Subject: [PATCH 1/2] feat: add entity discovery tools and bump version to 0.0.12 - Add list-entity-types tool for discovering available entity classes and types - Add list-entity-properties tool for exploring entity properties - Update J1QL service with new GraphQL queries for entity metadata - Enhanced execute-j1ql-query documentation with discovery patterns - Version bump to 0.0.12 --- package.json | 2 +- src/__tests__/dashboard-url.test.ts | 26 ++-- src/client/graphql/queries.ts | 25 ++++ src/client/jupiterone-client.ts | 8 + src/client/services/j1ql-service.ts | 54 +++++-- src/descriptions/execute-j1ql-query.md | 39 ++--- src/descriptions/list-entity-properties.md | 69 +++++++++ src/descriptions/list-entity-types.md | 53 +++++++ src/generated/description-map.ts | 161 ++++++++++++++++++--- src/server/mcp-server.ts | 101 ++++++++++--- 10 files changed, 460 insertions(+), 78 deletions(-) create mode 100644 src/descriptions/list-entity-properties.md create mode 100644 src/descriptions/list-entity-types.md diff --git a/package.json b/package.json index 81eda66..a363f6f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@jupiterone/jupiterone-mcp", - "version": "0.0.11", + "version": "0.0.12", "description": "Model Context Protocol server for JupiterOne account rules and rule details", "main": "dist/index.js", "bin": { diff --git a/src/__tests__/dashboard-url.test.ts b/src/__tests__/dashboard-url.test.ts index 148ebe8..82bcf9d 100644 --- a/src/__tests__/dashboard-url.test.ts +++ b/src/__tests__/dashboard-url.test.ts @@ -12,26 +12,32 @@ describe('Dashboard URL Construction', () => { it('should construct URL with provided subdomain', () => { const dashboardId = '6101fdba-e7bb-4f64-a9fb-62ba5f0641a2'; const subdomain = 'ripple-4e'; - + const url = dashboardService.constructDashboardUrl(dashboardId, subdomain); - - expect(url).toBe('https://ripple-4e.apps.us.jupiterone.io/insights/dashboards/6101fdba-e7bb-4f64-a9fb-62ba5f0641a2'); + + expect(url).toBe( + 'https://ripple-4e.apps.us.jupiterone.io/insights/dashboards/6101fdba-e7bb-4f64-a9fb-62ba5f0641a2' + ); }); it('should default to j1 subdomain when not provided', () => { const dashboardId = '6101fdba-e7bb-4f64-a9fb-62ba5f0641a2'; - + const url = dashboardService.constructDashboardUrl(dashboardId); - - expect(url).toBe('https://j1.apps.us.jupiterone.io/insights/dashboards/6101fdba-e7bb-4f64-a9fb-62ba5f0641a2'); + + expect(url).toBe( + 'https://j1.apps.us.jupiterone.io/insights/dashboards/6101fdba-e7bb-4f64-a9fb-62ba5f0641a2' + ); }); it('should handle undefined subdomain', () => { const dashboardId = '6101fdba-e7bb-4f64-a9fb-62ba5f0641a2'; - + const url = dashboardService.constructDashboardUrl(dashboardId, undefined); - - expect(url).toBe('https://j1.apps.us.jupiterone.io/insights/dashboards/6101fdba-e7bb-4f64-a9fb-62ba5f0641a2'); + + expect(url).toBe( + 'https://j1.apps.us.jupiterone.io/insights/dashboards/6101fdba-e7bb-4f64-a9fb-62ba5f0641a2' + ); }); }); -}); \ No newline at end of file +}); diff --git a/src/client/graphql/queries.ts b/src/client/graphql/queries.ts index 5fd9c5f..bc9376c 100644 --- a/src/client/graphql/queries.ts +++ b/src/client/graphql/queries.ts @@ -653,4 +653,29 @@ export const QUERY_V2 = ` correlationId } } +`; + +// Get entity counts by class and type +export const GET_ENTITY_COUNTS = ` + query GetEntityCounts { + getEntityCounts { + classes + types + } + } +`; + +// Get properties for a specific entity type +export const QUERY_PROPERTIES = ` + query QueryProperties($entity: String) { + queryProperties(entity: $entity) { + id + accountId + entity + name + valueType + count + __typename + } + } `; \ No newline at end of file diff --git a/src/client/jupiterone-client.ts b/src/client/jupiterone-client.ts index d3171de..f45914c 100644 --- a/src/client/jupiterone-client.ts +++ b/src/client/jupiterone-client.ts @@ -197,4 +197,12 @@ export class JupiterOneClient { async executeJ1qlQuery(...args: Parameters) { return this.j1qlService.executeJ1qlQuery(...args); } + + async listEntityTypes() { + return this.j1qlService.listEntityTypes(); + } + + async listEntityProperties(entityType: string) { + return this.j1qlService.listEntityProperties(entityType); + } } diff --git a/src/client/services/j1ql-service.ts b/src/client/services/j1ql-service.ts index a59b3c9..7bc7db2 100644 --- a/src/client/services/j1ql-service.ts +++ b/src/client/services/j1ql-service.ts @@ -1,7 +1,7 @@ import { GraphQLClient } from 'graphql-request'; import { CreateJ1qlFromNaturalLanguageResponse } from '../../types/jupiterone.js'; import { CREATE_J1QL_FROM_NATURAL_LANGUAGE } from '../graphql/mutations.js'; -import { QUERY_V2 } from '../graphql/queries.js'; +import { QUERY_V2, GET_ENTITY_COUNTS, QUERY_PROPERTIES } from '../graphql/queries.js'; import { getEnv } from '../../utils/getEnv.js'; export class J1qlService { @@ -62,21 +62,21 @@ export class J1qlService { returnComputedProperties: flags?.computedProperties, } ); - + // Handle deferred response if (response.queryV2.type === 'deferred' && response.queryV2.url) { // Poll the URL until results are ready const maxAttempts = 60; // 60 attempts = 1 minute max wait const pollInterval = 1000; // 1 second between polls - + for (let attempt = 0; attempt < maxAttempts; attempt++) { const fetchResponse = await fetch(response.queryV2.url); if (!fetchResponse.ok) { throw new Error(`Failed to fetch query results: ${fetchResponse.statusText}`); } - + const result = await fetchResponse.json(); - + // Check if the query is complete if (result.status === 'COMPLETE' || result.status === 'FAILED') { if (result.status === 'FAILED') { @@ -84,20 +84,56 @@ export class J1qlService { } return result; } - + // If still in progress, wait before next poll if (result.status === 'IN_PROGRESS') { await new Promise(resolve => setTimeout(resolve, pollInterval)); continue; } - + // If we get an unexpected status, return the result as-is return result; } - + throw new Error('Query timed out after 60 seconds'); } - + return response.queryV2; } + + /** + * List all entity types in the account + */ + async listEntityTypes(): Promise { + const response = await this.client.request<{ + getEntityCounts: { + classes: Record; + types: Record; + }; + }>(GET_ENTITY_COUNTS); + + return { + classes: response.getEntityCounts.classes, + types: response.getEntityCounts.types, + }; + } + + /** + * List properties for a specific entity type + */ + async listEntityProperties(entityType: string): Promise { + const response = await this.client.request<{ + queryProperties: Array<{ + id: string; + accountId: string; + entity: string; + name: string; + valueType: string; + count: number; + __typename: string; + }>; + }>(QUERY_PROPERTIES, { entity: entityType }); + + return response.queryProperties; + } } \ No newline at end of file diff --git a/src/descriptions/execute-j1ql-query.md b/src/descriptions/execute-j1ql-query.md index d350437..1c56c58 100644 --- a/src/descriptions/execute-j1ql-query.md +++ b/src/descriptions/execute-j1ql-query.md @@ -2,6 +2,21 @@ **Purpose**: Executes JupiterOne Query Language (J1QL) queries against your JupiterOne data and returns the results. This tool is used to directly run J1QL queries that have been created, either manually or through the natural language converter. +## Recommended Query Development Workflow + +**CRITICAL**: Follow this workflow for best results when writing J1QL queries: + +1. **Use `list-entity-types`** - Discover what entity classes and types are available in the account +2. **Evaluate relevant entity types** - Based on the user's request, identify which entity types you need to query +3. **Use `list-entity-properties`** - For each relevant entity type, list its available properties +4. **Run exploratory queries** - Execute simple queries with the entity types and properties to see sample values: + ``` + FIND AS e RETURN e.* LIMIT 5 + ``` +5. **Construct final query** - Build your complete query using the discovered types, properties, and values + +This systematic approach ensures your queries will return meaningful results and use the correct property names and filters. + This tool should be used when: - You need to validate the data of a query - You need to get results from a previously generated query @@ -319,6 +334,11 @@ LIMIT 10 #### Discovery Queries - ALWAYS START HERE +**Recommended**: Use the dedicated tools for faster discovery: +- **`list-entity-types`**: Get all entity classes and types with counts +- **`list-entity-properties`**: Get all properties for a specific entity type + +Manual discovery queries if needed: 1. **Find all entity classes**: `FIND * AS e RETURN e._class, COUNT(e)` 2. **Explore entity properties**: `FIND EntityClass AS e RETURN e.* LIMIT 10` 3. **Discover relationships**: `FIND Entity1 THAT RELATES TO AS rel Entity2 RETURN rel._class` @@ -376,21 +396,4 @@ Before running any J1QL query, verify: - LIMIT to prevent timeouts - Proper capitalization for classes -**Remember**: The execute-j1ql-query tool now provides enhanced error messages with specific suggestions. Always test queries here first! - -#### 📌 IMPORTANT: Query Results URL - -When this tool returns query results, it includes a `url` field that provides a direct link to view the results in the JupiterOne UI. **Always share this URL with users when presenting query results** - it allows them to: -- View the data in an interactive table format -- Export results to CSV or other formats -- Save the query for future use -- Share results with team members -- Further refine the query in the JupiterOne UI - -Example response: -```json -{ - "data": [...query results...], - "url": "https://your-account.apps.us.jupiterone.io/home/results?search=..." -} -``` \ No newline at end of file +**Remember**: The execute-j1ql-query tool now provides enhanced error messages with specific suggestions. Always test queries here first! \ No newline at end of file diff --git a/src/descriptions/list-entity-properties.md b/src/descriptions/list-entity-properties.md new file mode 100644 index 0000000..2b67f7c --- /dev/null +++ b/src/descriptions/list-entity-properties.md @@ -0,0 +1,69 @@ +# JupiterOne Entity Properties Discoverer + +**Purpose**: Lists all available properties for a specific entity type in your JupiterOne account. This tool is essential for understanding what data fields are available to query for a given entity type. + +This tool should be used AFTER running `list-entity-types` to discover available entity types, and BEFORE writing J1QL queries that need to access specific properties. + +## Features +- Returns all properties available for a specific entity type +- Shows property names, value types, and usage counts +- Helps you understand the data model for specific entities +- Essential for writing accurate J1QL queries with property filters + +## Parameters +- `entityType`: The entity type to get properties for (e.g., `aws_instance`, `okta_user`, `github_user`) + +## Example Usage + +### Get properties for AWS instances: +``` +entityType: "aws_instance" +``` + +### Get properties for Okta users: +``` +entityType: "okta_user" +``` + +## Response Format +Returns an array of property objects, each containing: +- `id`: Unique identifier for the property +- `accountId`: The account ID +- `entity`: The entity type this property belongs to +- `name`: The property name (use this in J1QL queries) +- `valueType`: The data type of the property (e.g., "string", "number", "boolean") +- `count`: Number of entities that have this property + +Example response: +```json +[ + { + "id": "123456", + "accountId": "j1dev", + "entity": "okta_user", + "name": "email", + "valueType": "string", + "count": 150 + }, + { + "id": "123457", + "accountId": "j1dev", + "entity": "okta_user", + "name": "active", + "valueType": "boolean", + "count": 150 + } +] +``` + +## Use Cases +- **Query Preparation**: Discover available properties before writing J1QL queries +- **Data Exploration**: Understand what data is available for specific entity types +- **Filter Development**: Find properties to use in WHERE clauses and WITH filters +- **Schema Understanding**: Learn about the data model of integrated services + +## Workflow Integration +This tool is typically used as part of a three-step process: +1. Run `list-entity-types` to discover available entity types +2. Run `list-entity-properties` for relevant entity types +3. Write J1QL queries using the discovered types and properties \ No newline at end of file diff --git a/src/descriptions/list-entity-types.md b/src/descriptions/list-entity-types.md new file mode 100644 index 0000000..9e23d2a --- /dev/null +++ b/src/descriptions/list-entity-types.md @@ -0,0 +1,53 @@ +# JupiterOne Entity Types Discoverer + +**Purpose**: Discovers all entity classes and types in your JupiterOne account. This tool helps you find relevant entity types when building queries or exploring your data model. + +**CRITICAL**: This tool should ALWAYS be run FIRST before writing any J1QL queries. Without knowing what entity classes and types exist in your JupiterOne account, you cannot write valid queries that will return results and are more likely to hallucinate. This is the essential first step for any query-writing task. + +This tool provides a comprehensive count of all entity classes and types in your account using an efficient GraphQL query. + +## Features +- Returns all entity classes and types in your account +- Shows counts for each class and type +- Provides results as key-value pairs for easy lookup +- Fast and efficient - no pagination required + +## Example Usage + +``` +list-entity-types +``` + +## Response Format +Returns an object with two properties: +- `classes`: Object mapping class names to their counts (e.g., `{"User": 150, "Device": 200}`) +- `types`: Object mapping type names to their counts (e.g., `{"aws_instance": 50, "github_user": 25}`) + +Example response: +```json +{ + "classes": { + "User": 150, + "Device": 200, + "Finding": 1234, + "DataStore": 45 + }, + "types": { + "aws_instance": 50, + "aws_s3_bucket": 30, + "github_user": 25, + "crowdstrike_device": 100 + } +} +``` + +## Use Cases +- **REQUIRED FIRST STEP**: Run this before writing any J1QL queries to know what entities exist +- Discovering available entity types before writing J1QL queries +- Finding integration-specific entities (e.g., all Snyk entities) +- Understanding your data model and available entity types +- Exploring what data is available from specific integrations + +## Why This Is Essential + +When writing J1QL queries, you need to specify entity classes (like `User`, `Device`, `Finding`) or entity types (like `aws_instance`, `github_user`). Without running this tool first, you're essentially guessing what entities exist in your account. This tool provides the complete list of valid entity classes and types that you can query, ensuring your J1QL queries will actually find and return data. \ No newline at end of file diff --git a/src/generated/description-map.ts b/src/generated/description-map.ts index 346ef9a..ec959b8 100644 --- a/src/generated/description-map.ts +++ b/src/generated/description-map.ts @@ -783,6 +783,21 @@ Evaluate a specific rule: **Purpose**: Executes JupiterOne Query Language (J1QL) queries against your JupiterOne data and returns the results. This tool is used to directly run J1QL queries that have been created, either manually or through the natural language converter. +## Recommended Query Development Workflow + +**CRITICAL**: Follow this workflow for best results when writing J1QL queries: + +1. **Use \`list-entity-types\`** - Discover what entity classes and types are available in the account +2. **Evaluate relevant entity types** - Based on the user's request, identify which entity types you need to query +3. **Use \`list-entity-properties\`** - For each relevant entity type, list its available properties +4. **Run exploratory queries** - Execute simple queries with the entity types and properties to see sample values: + \`\`\` + FIND AS e RETURN e.* LIMIT 5 + \`\`\` +5. **Construct final query** - Build your complete query using the discovered types, properties, and values + +This systematic approach ensures your queries will return meaningful results and use the correct property names and filters. + This tool should be used when: - You need to validate the data of a query - You need to get results from a previously generated query @@ -1100,6 +1115,11 @@ LIMIT 10 #### Discovery Queries - ALWAYS START HERE +**Recommended**: Use the dedicated tools for faster discovery: +- **\`list-entity-types\`**: Get all entity classes and types with counts +- **\`list-entity-properties\`**: Get all properties for a specific entity type + +Manual discovery queries if needed: 1. **Find all entity classes**: \`FIND * AS e RETURN e._class, COUNT(e)\` 2. **Explore entity properties**: \`FIND EntityClass AS e RETURN e.* LIMIT 10\` 3. **Discover relationships**: \`FIND Entity1 THAT RELATES TO AS rel Entity2 RETURN rel._class\` @@ -1157,24 +1177,7 @@ Before running any J1QL query, verify: - LIMIT to prevent timeouts - Proper capitalization for classes -**Remember**: The execute-j1ql-query tool now provides enhanced error messages with specific suggestions. Always test queries here first! - -#### 📌 IMPORTANT: Query Results URL - -When this tool returns query results, it includes a \`url\` field that provides a direct link to view the results in the JupiterOne UI. **Always share this URL with users when presenting query results** - it allows them to: -- View the data in an interactive table format -- Export results to CSV or other formats -- Save the query for future use -- Share results with team members -- Further refine the query in the JupiterOne UI - -Example response: -\`\`\`json -{ - "data": [...query results...], - "url": "https://your-account.apps.us.jupiterone.io/home/results?search=..." -} -\`\`\``, +**Remember**: The execute-j1ql-query tool now provides enhanced error messages with specific suggestions. Always test queries here first!`, "get-dashboard-details.md": `# Get Dashboard Details Tool Get detailed information about a specific JupiterOne dashboard including its widgets, layout, and configuration. @@ -1395,6 +1398,128 @@ Request the first 5 active alerts: "limit": 5 } \`\`\``, + "list-entity-properties.md": `# JupiterOne Entity Properties Discoverer + +**Purpose**: Lists all available properties for a specific entity type in your JupiterOne account. This tool is essential for understanding what data fields are available to query for a given entity type. + +This tool should be used AFTER running \`list-entity-types\` to discover available entity types, and BEFORE writing J1QL queries that need to access specific properties. + +## Features +- Returns all properties available for a specific entity type +- Shows property names, value types, and usage counts +- Helps you understand the data model for specific entities +- Essential for writing accurate J1QL queries with property filters + +## Parameters +- \`entityType\`: The entity type to get properties for (e.g., \`aws_instance\`, \`okta_user\`, \`github_user\`) + +## Example Usage + +### Get properties for AWS instances: +\`\`\` +entityType: "aws_instance" +\`\`\` + +### Get properties for Okta users: +\`\`\` +entityType: "okta_user" +\`\`\` + +## Response Format +Returns an array of property objects, each containing: +- \`id\`: Unique identifier for the property +- \`accountId\`: The account ID +- \`entity\`: The entity type this property belongs to +- \`name\`: The property name (use this in J1QL queries) +- \`valueType\`: The data type of the property (e.g., "string", "number", "boolean") +- \`count\`: Number of entities that have this property + +Example response: +\`\`\`json +[ + { + "id": "123456", + "accountId": "j1dev", + "entity": "okta_user", + "name": "email", + "valueType": "string", + "count": 150 + }, + { + "id": "123457", + "accountId": "j1dev", + "entity": "okta_user", + "name": "active", + "valueType": "boolean", + "count": 150 + } +] +\`\`\` + +## Use Cases +- **Query Preparation**: Discover available properties before writing J1QL queries +- **Data Exploration**: Understand what data is available for specific entity types +- **Filter Development**: Find properties to use in WHERE clauses and WITH filters +- **Schema Understanding**: Learn about the data model of integrated services + +## Workflow Integration +This tool is typically used as part of a three-step process: +1. Run \`list-entity-types\` to discover available entity types +2. Run \`list-entity-properties\` for relevant entity types +3. Write J1QL queries using the discovered types and properties`, + "list-entity-types.md": `# JupiterOne Entity Types Discoverer + +**Purpose**: Discovers all entity classes and types in your JupiterOne account. This tool helps you find relevant entity types when building queries or exploring your data model. + +**CRITICAL**: This tool should ALWAYS be run FIRST before writing any J1QL queries. Without knowing what entity classes and types exist in your JupiterOne account, you cannot write valid queries that will return results and are more likely to hallucinate. This is the essential first step for any query-writing task. + +This tool provides a comprehensive count of all entity classes and types in your account using an efficient GraphQL query. + +## Features +- Returns all entity classes and types in your account +- Shows counts for each class and type +- Provides results as key-value pairs for easy lookup +- Fast and efficient - no pagination required + +## Example Usage + +\`\`\` +list-entity-types +\`\`\` + +## Response Format +Returns an object with two properties: +- \`classes\`: Object mapping class names to their counts (e.g., \`{"User": 150, "Device": 200}\`) +- \`types\`: Object mapping type names to their counts (e.g., \`{"aws_instance": 50, "github_user": 25}\`) + +Example response: +\`\`\`json +{ + "classes": { + "User": 150, + "Device": 200, + "Finding": 1234, + "DataStore": 45 + }, + "types": { + "aws_instance": 50, + "aws_s3_bucket": 30, + "github_user": 25, + "crowdstrike_device": 100 + } +} +\`\`\` + +## Use Cases +- **REQUIRED FIRST STEP**: Run this before writing any J1QL queries to know what entities exist +- Discovering available entity types before writing J1QL queries +- Finding integration-specific entities (e.g., all Snyk entities) +- Understanding your data model and available entity types +- Exploring what data is available from specific integrations + +## Why This Is Essential + +When writing J1QL queries, you need to specify entity classes (like \`User\`, \`Device\`, \`Finding\`) or entity types (like \`aws_instance\`, \`github_user\`). Without running this tool first, you're essentially guessing what entities exist in your account. This tool provides the complete list of valid entity classes and types that you can query, ensuring your J1QL queries will actually find and return data.`, "list-rule-evaluations.md": `# List Rule Evaluations Tool List the evaluation history for a specific rule. This tool shows when a rule was evaluated and the results of each evaluation. diff --git a/src/server/mcp-server.ts b/src/server/mcp-server.ts index 81a1341..ce53958 100644 --- a/src/server/mcp-server.ts +++ b/src/server/mcp-server.ts @@ -248,7 +248,7 @@ export class JupiterOneMcpServer { try { const isConnected = await client.testConnection(); const accountInfo = isConnected ? await client.getAccountInfo() : null; - + // Get package version const packageJson = require('../../package.json'); const version = packageJson.version; @@ -465,7 +465,7 @@ export class JupiterOneMcpServer { handler: async ({ name, type }, client) => { try { const result = await client.createDashboard({ name, type }); - + // Get account info to construct the URL const accountInfo = await client.getAccountInfo(); const dashboardUrl = client.dashboardService.constructDashboardUrl( @@ -1037,13 +1037,10 @@ export class JupiterOneMcpServer { }; const result = await client.createInlineQuestionRuleInstance(instance); - + // Get account info to construct the URL const accountInfo = await client.getAccountInfo(); - const ruleUrl = client.ruleService.constructRuleUrl( - result.id, - accountInfo.subdomain - ); + const ruleUrl = client.ruleService.constructRuleUrl(result.id, accountInfo.subdomain); return { content: [ @@ -1271,13 +1268,10 @@ export class JupiterOneMcpServer { }; const result = await client.updateInlineQuestionRuleInstance(instance); - + // Get account info to construct the URL const accountInfo = await client.getAccountInfo(); - const ruleUrl = client.ruleService.constructRuleUrl( - result.id, - accountInfo.subdomain - ); + const ruleUrl = client.ruleService.constructRuleUrl(result.id, accountInfo.subdomain); return { content: [ @@ -1726,22 +1720,26 @@ export class JupiterOneMcpServer { } const widget = await client.createDashboardWidget(dashboardId, widgetInput); - + // Get account info to construct the dashboard URL const accountInfo = await client.getAccountInfo(); const dashboardUrl = client.dashboardService.constructDashboardUrl( dashboardId, accountInfo.subdomain ); - + return { content: [ { type: 'text' as const, - text: JSON.stringify({ - ...widget, - dashboardUrl - }, null, 2), + text: JSON.stringify( + { + ...widget, + dashboardUrl, + }, + null, + 2 + ), }, ], }; @@ -1909,10 +1907,7 @@ export class JupiterOneMcpServer { // Get account info to construct the URL const accountInfo = await client.getAccountInfo(); - const queryUrl = client.j1qlService.constructQueryUrl( - query, - accountInfo.subdomain - ); + const queryUrl = client.j1qlService.constructQueryUrl(query, accountInfo.subdomain); // Add URL to the result const enhancedResult = { @@ -1933,6 +1928,68 @@ export class JupiterOneMcpServer { } }, }); + + // Tool: List entity types + this.registerTool({ + name: 'list-entity-types', + description: loadDescription('list-entity-types.md'), + schema: {}, + handler: async (_, client) => { + try { + const results = await client.listEntityTypes(); + return { + content: [ + { + type: 'text' as const, + text: JSON.stringify(results, null, 2), + }, + ], + }; + } catch (error) { + return { + content: [ + { + type: 'text' as const, + text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`, + }, + ], + }; + } + }, + }); + + // Tool: List entity properties + this.registerTool({ + name: 'list-entity-properties', + description: loadDescription('list-entity-properties.md'), + schema: { + entityType: z + .string() + .describe('The entity type to get properties for (e.g., "aws_instance", "okta_user")'), + }, + handler: async ({ entityType }, client) => { + try { + const results = await client.listEntityProperties(entityType); + return { + content: [ + { + type: 'text' as const, + text: JSON.stringify(results, null, 2), + }, + ], + }; + } catch (error) { + return { + content: [ + { + type: 'text' as const, + text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`, + }, + ], + }; + } + }, + }); } // Helper methods for validation From f662aece383fe7b6921071fe6f2ce8a57e396cc9 Mon Sep 17 00:00:00 2001 From: hesto2 Date: Thu, 31 Jul 2025 15:43:11 -0600 Subject: [PATCH 2/2] re add documentation --- src/descriptions/execute-j1ql-query.md | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/src/descriptions/execute-j1ql-query.md b/src/descriptions/execute-j1ql-query.md index 1c56c58..9cd9803 100644 --- a/src/descriptions/execute-j1ql-query.md +++ b/src/descriptions/execute-j1ql-query.md @@ -58,8 +58,8 @@ Unified entities typically also have additional enrichment making them valuable ``` FIND UnifiedIdentity AS identity - THAT IS << User - THAT RELATES TO AS rel (Device|Host) + THAT IS << User + THAT RELATES TO AS rel (Device|Host) THAT IS >> UnifiedDevice AS device RETURN identity.displayName, rel._class, device.displayName ``` @@ -360,7 +360,7 @@ Before running any J1QL query, verify: #### Most Common Errors (Quick Reference) 1. **Missing quotes**: `name = john` → `name = 'john'` -2. **Wrong quotes**: `name = "john"` → `name = 'john'` +2. **Wrong quotes**: `name = "john"` → `name = 'john'` 3. **Alias placement**: `AS u WITH active = true` → `WITH active = true AS u` 4. **WHERE needs alias**: `WHERE active = true` → `AS u WHERE u.active = true` 5. **Undefined alias**: `FIND User RETURN u.name` → `FIND User AS u RETURN u.name` @@ -396,4 +396,20 @@ Before running any J1QL query, verify: - LIMIT to prevent timeouts - Proper capitalization for classes -**Remember**: The execute-j1ql-query tool now provides enhanced error messages with specific suggestions. Always test queries here first! \ No newline at end of file +**Remember**: The execute-j1ql-query tool now provides enhanced error messages with specific suggestions. Always test queries here first! + +#### 📌 IMPORTANT: Query Results URL + +When this tool returns query results, it includes a `url` field that provides a direct link to view the results in the JupiterOne UI. **Always share this URL with users when presenting query results** - it allows them to: +- View the data in an interactive table format +- Export results to CSV or other formats +- Save the query for future use +- Share results with team members +- Further refine the query in the JupiterOne UI + +Example response: +```json +{ + "data": [...query results...], + "url": "https://your-account.apps.us.jupiterone.io/home/results?search=..." +} \ No newline at end of file