Jira Insights MCP
by aaronsb
Verified
# Handler File Improvements
This document outlines the specific changes needed for each handler file to implement the error handling and other enhancements.
## 1. Schema Handlers (`src/handlers/schema-handlers.ts`)
### Add Error Handler Import
```typescript
import { handleError } from '../utils/error-handler.js';
```
### Update Error Handling in Each Operation
Replace the current catch block with the enhanced error handling:
```typescript
try {
// Existing operation code
} catch (error) {
console.error('Error in schema handler:', error);
if (error instanceof McpError) {
throw error;
}
// Use the new error handler with context
return handleError(error, operation, {
schemaId,
name: args.name,
description: args.description,
startAt,
maxResults,
expand: args.expand
});
}
```
## 2. Object Type Handlers (`src/handlers/object-type-handlers.ts`)
### Add Imports
```typescript
import { handleError } from '../utils/error-handler.js';
import { getObjectTypeAttributes } from '../utils/attribute-utils.js';
```
### Update Error Handling in Each Operation
Replace the current catch block with the enhanced error handling:
```typescript
try {
// Existing operation code
} catch (error) {
console.error('Error in object type handler:', error);
if (error instanceof McpError) {
throw error;
}
// Use the new error handler with context
return handleError(error, operation, {
objectTypeId,
schemaId,
name: args.name,
description: args.description,
icon: args.icon,
startAt,
maxResults,
expand: args.expand
});
}
```
### Add Attribute Handling for Get Operation
Enhance the 'get' operation to include attributes:
```typescript
case 'get': {
if (!objectTypeId) {
throw new McpError(ErrorCode.InvalidParams, 'Object Type ID is required for get operation');
}
const objectType = await assetsApi.objectTypeFind({ id: objectTypeId });
// Check if attributes should be included
if (args.expand && args.expand.includes('attributes')) {
try {
// Get attributes using the new utility
const attributesResult = await getObjectTypeAttributes(jiraClient, objectTypeId);
// Add attributes to the response
return {
content: [
{
type: 'text',
text: JSON.stringify({
...objectType,
attributes: attributesResult.attributes,
_attributesCount: attributesResult.count,
_attributesCached: attributesResult._cached,
_attributesCachedAt: attributesResult._cachedAt
}, null, 2),
},
],
};
} catch (attrError) {
console.error(`Error fetching attributes for object type ${objectTypeId}:`, attrError);
// Return the object type without attributes
return {
content: [
{
type: 'text',
text: JSON.stringify({
...objectType,
_attributesError: 'Failed to fetch attributes',
_attributesErrorMessage: (attrError as Error).message
}, null, 2),
},
],
};
}
}
// Return the object type without attributes
return {
content: [
{
type: 'text',
text: JSON.stringify(objectType, null, 2),
},
],
};
}
```
## 3. Object Handlers (`src/handlers/object-handlers.ts`)
### Add Imports
```typescript
import { handleError } from '../utils/error-handler.js';
import { validateAqlQuery, formatAqlForRequest } from '../utils/aql-utils.js';
import { formatAttributes } from '../utils/attribute-utils.js';
```
### Update Error Handling in Each Operation
Replace the current catch block with the enhanced error handling:
```typescript
try {
// Existing operation code
} catch (error) {
console.error('Error in object handler:', error);
if (error instanceof McpError) {
throw error;
}
// Use the new error handler with context
return handleError(error, operation, {
objectId,
objectTypeId,
name: args.name,
attributes: args.attributes,
startAt,
maxResults,
aql: args.aql,
expand: args.expand
});
}
```
### Enhance the Query Operation with Validation
```typescript
case 'query': {
if (!args.aql) {
throw new McpError(ErrorCode.InvalidParams, 'AQL query is required for query operation');
}
// Validate the AQL query
const validation = validateAqlQuery(args.aql);
// If the query is invalid, return validation errors
if (!validation.isValid) {
return {
content: [
{
type: 'text',
text: JSON.stringify({
error: 'Invalid AQL query',
validation,
operation,
}, null, 2),
},
],
isError: true,
};
}
// Format the AQL query for the API
const formattedAql = formatAqlForRequest(args.aql);
// Execute the query with the formatted AQL
const queryResults = await assetsApi.objectsByAql({
requestBody: {
aql: formattedAql
},
startAt,
maxResults,
includeAttributes: true
});
return {
content: [
{
type: 'text',
text: JSON.stringify({
...queryResults,
_originalAql: args.aql,
_formattedAql: formattedAql
}, null, 2),
},
],
};
}
```
### Update Create and Update Operations to Use formatAttributes
```typescript
// In create operation
const newObject = await assetsApi.objectCreate({
objectIn: {
name: args.name,
objectTypeId,
attributes: args.attributes ? formatAttributes(args.attributes) : [],
},
});
// In update operation
const updatedObject = await assetsApi.objectUpdate({
id: objectId,
objectIn: {
name: args.name || existingObject.name,
objectTypeId: existingObject.objectTypeId,
attributes: args.attributes ? formatAttributes(args.attributes) : undefined,
},
});
```
## 4. Resource Handlers (`src/handlers/resource-handlers.ts`)
### Add AQL Documentation Resource
Enhance the AQL syntax resource to include more detailed examples and error handling guidance:
```typescript
// AQL syntax resource
if (uri === 'jira-insights://aql-syntax') {
return {
contents: [
{
uri,
mimeType: 'application/json',
text: JSON.stringify(
{
title: 'Assets Query Language (AQL) Syntax Guide',
description: 'AQL is a powerful query language used in Jira Insights to search, filter, and retrieve objects.',
basicSyntax: {
pattern: '<attribute> <operator> <value/function>',
example: 'Owner = "Ted Anderson"',
description: 'Returns all objects where the Owner is Ted Anderson'
},
guidelines: [
'AQL is not case-sensitive',
'Values with spaces must be enclosed in quotes: "Ted Anderson"',
'Escape quotes with backslashes: 15\\" Screen',
'Attribute names must exist in your schema'
],
// ... existing content ...
// Add new section for common errors and solutions
commonErrors: [
{
error: 'Validation error',
cause: 'Missing quotes around values with spaces',
example: 'Name = John Doe',
solution: 'Add quotes: Name = "John Doe"'
},
{
error: 'Validation error',
cause: 'Using lowercase logical operators',
example: 'objectType = "Server" and Status = "Active"',
solution: 'Use uppercase: objectType = "Server" AND Status = "Active"'
},
{
error: 'Object type not found',
cause: 'Referencing a non-existent object type',
example: 'objectType = "NonExistentType"',
solution: 'Check available object types in your schema'
},
{
error: 'Attribute not found',
cause: 'Referencing a non-existent attribute',
example: 'NonExistentAttribute = "Value"',
solution: 'Check available attributes for the object type'
}
],
// Add new section for query building tips
queryBuildingTips: [
'Start with simple queries and add complexity gradually',
'Test each condition separately before combining them',
'Use objectType = "X" as the first condition to narrow down results',
'When using referenced objects, ensure the reference chain exists',
'For complex queries, break them down into smaller parts'
]
},
null,
2
),
},
],
};
}
```
### Add New Resource for AQL Examples by Schema
```typescript
// AQL examples by schema resource
const aqlExamplesBySchemaMatch = uri.match(/^jira-insights:\/\/schemas\/([^/]+)\/aql-examples$/);
if (aqlExamplesBySchemaMatch) {
const schemaId = decodeURIComponent(aqlExamplesBySchemaMatch[1]);
try {
// Wait for schema cache to be initialized
await schemaCacheManager.waitForInitialization();
const schema = schemaCacheManager.getSchema(schemaId);
if (!schema) {
return {
contents: [
{
uri,
mimeType: 'application/json',
text: JSON.stringify(
{
error: `Schema not found: ${schemaId}`,
},
null,
2
),
},
],
};
}
// Get object types for this schema
const objectTypes = schema.objectTypes || [];
// Generate examples based on object types
const examples = [];
// Add basic examples for each object type
for (const objectType of objectTypes) {
examples.push({
description: `Find all ${objectType.name} objects`,
aql: `objectType = "${objectType.name}"`,
complexity: 'basic'
});
}
// Add some more complex examples if there are object types
if (objectTypes.length > 0) {
examples.push({
description: 'Find objects with a specific status',
aql: `objectType = "${objectTypes[0].name}" AND Status = "Active"`,
complexity: 'intermediate'
});
examples.push({
description: 'Find objects with a specific attribute containing text',
aql: `objectType = "${objectTypes[0].name}" AND Name like "Test"`,
complexity: 'intermediate'
});
examples.push({
description: 'Find objects with multiple conditions',
aql: `objectType = "${objectTypes[0].name}" AND Status = "Active" AND Created > now(-30d)`,
complexity: 'advanced'
});
}
return {
contents: [
{
uri,
mimeType: 'application/json',
text: JSON.stringify(
{
schemaId,
schemaName: schema.name,
examples,
timestamp: new Date().toISOString(),
},
null,
2
),
},
],
};
} catch (error) {
console.error(`Error generating AQL examples for schema ${schemaId}:`, error);
return {
contents: [
{
uri,
mimeType: 'application/json',
text: JSON.stringify(
{
error: `Failed to generate AQL examples for schema ${schemaId}`,
message: (error as Error).message,
},
null,
2
),
},
],
};
}
}
```
### Add New Resource for Object Type Templates
```typescript
// Object type template resource
const objectTypeTemplateMatch = uri.match(/^jira-insights:\/\/object-types\/([^/]+)\/template$/);
if (objectTypeTemplateMatch) {
const objectTypeId = decodeURIComponent(objectTypeTemplateMatch[1]);
try {
const assetsApi = await jiraClient.getAssetsApi();
const objectType = await assetsApi.objectTypeFind({ id: objectTypeId }) as {
id: string;
name: string;
description: string;
objectSchemaId: string;
};
// Get attributes for this object type
const attributesList = await assetsApi.objectTypeFindAllAttributes({
id: objectTypeId,
onlyValueEditable: false,
orderByName: false,
query: '""',
includeValuesExist: false,
excludeParentAttributes: false,
includeChildren: false,
orderByRequired: false
});
const attributes = attributesList.values || [];
// Generate template object
const template: Record<string, any> = {
name: `[${objectType.name} Name]`,
};
// Generate template values for each attribute
attributes.forEach((attr: any) => {
let placeholder;
switch(attr.type) {
case 'TEXT':
placeholder = `[${attr.name} text]`;
break;
case 'INTEGER':
placeholder = 0;
break;
case 'FLOAT':
placeholder = 0.0;
break;
case 'DATE':
placeholder = new Date().toISOString().split('T')[0];
break;
case 'DATETIME':
placeholder = new Date().toISOString();
break;
case 'BOOLEAN':
placeholder = false;
break;
case 'REFERENCE':
placeholder = {
objectTypeId: attr.referenceObjectTypeId,
objectMappingIQL: `[Referenced ${attr.name} AQL query]`
};
break;
default:
placeholder = `[${attr.name}]`;
}
template[attr.name] = placeholder;
});
return {
contents: [
{
uri,
mimeType: 'application/json',
text: JSON.stringify(
{
objectTypeId,
objectTypeName: objectType.name,
schemaId: objectType.objectSchemaId,
template,
_attributesCount: attributes.length,
_generatedAt: new Date().toISOString(),
usage: {
description: 'This template provides a starting point for creating objects of this type.',
notes: [
'Replace placeholder values with actual data',
'Required attributes must be provided',
'For reference attributes, provide a valid AQL query or object ID'
]
}
},
null,
2
),
},
],
};
} catch (error) {
console.error(`Error generating template for object type ${objectTypeId}:`, error);
return {
contents: [
{
uri,
mimeType: 'application/json',
text: JSON.stringify(
{
error: `Failed to generate template for object type ${objectTypeId}`,
message: (error as Error).message,
},
null,
2
),
},
],
};
}
}
```
## 5. Update Resource Templates List
Update the `listResourceTemplates` function to include the new resource templates:
```typescript
const listResourceTemplates = async () => {
return {
resourceTemplates: [
{
uriTemplate: 'jira-insights://schemas/{schemaId}/overview',
name: 'Schema Overview',
mimeType: 'application/json',
description: 'Overview of a specific schema including metadata and statistics',
},
{
uriTemplate: 'jira-insights://object-types/{objectTypeId}/overview',
name: 'Object Type Overview',
mimeType: 'application/json',
description: 'Overview of a specific object type including attributes and statistics',
},
// Add new resource templates
{
uriTemplate: 'jira-insights://schemas/{schemaId}/aql-examples',
name: 'Schema AQL Examples',
mimeType: 'application/json',
description: 'Example AQL queries for a specific schema',
},
{
uriTemplate: 'jira-insights://object-types/{objectTypeId}/template',
name: 'Object Type Template',
mimeType: 'application/json',
description: 'Template for creating objects of a specific type',
},
],
};
};