mirror of
https://github.com/czlonkowski/n8n-mcp.git
synced 2026-03-25 19:53:08 +00:00
fix: Add defensive response validation for n8n API list operations (issue #349)
Addresses "Cannot read properties of undefined (reading 'map')" error
by adding validation and fallback handling for n8n API responses.
Changes:
- Add response structure validation in listWorkflows, listExecutions,
listCredentials, and listTags methods
- Handle edge case where API returns array directly instead of
{data: [], nextCursor} wrapper object
- Provide clear error messages when response format is unexpected
- Add logging when using fallback format handling
This fix ensures compatibility with different n8n API versions and
prevents runtime errors when the response structure varies from expected.
Fixes #349
Conceived by Romuald Członkowski - www.aiadvisors.pl/en
This commit is contained in:
@@ -173,7 +173,28 @@ export class N8nApiClient {
|
|||||||
async listWorkflows(params: WorkflowListParams = {}): Promise<WorkflowListResponse> {
|
async listWorkflows(params: WorkflowListParams = {}): Promise<WorkflowListResponse> {
|
||||||
try {
|
try {
|
||||||
const response = await this.client.get('/workflows', { params });
|
const response = await this.client.get('/workflows', { params });
|
||||||
return response.data;
|
const responseData = response.data;
|
||||||
|
|
||||||
|
// Validate response structure
|
||||||
|
if (!responseData || typeof responseData !== 'object') {
|
||||||
|
throw new Error('Invalid response from n8n API: response is not an object');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle case where response.data is an array (older n8n versions or different API format)
|
||||||
|
if (Array.isArray(responseData)) {
|
||||||
|
logger.warn('n8n API returned array directly instead of {data, nextCursor} object. Wrapping in expected format.');
|
||||||
|
return {
|
||||||
|
data: responseData,
|
||||||
|
nextCursor: null
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate expected format {data: [], nextCursor?: string}
|
||||||
|
if (!Array.isArray(responseData.data)) {
|
||||||
|
throw new Error(`Invalid response from n8n API: expected {data: [], nextCursor?: string}, got: ${JSON.stringify(Object.keys(responseData))}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return responseData;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw handleN8nApiError(error);
|
throw handleN8nApiError(error);
|
||||||
}
|
}
|
||||||
@@ -194,7 +215,28 @@ export class N8nApiClient {
|
|||||||
async listExecutions(params: ExecutionListParams = {}): Promise<ExecutionListResponse> {
|
async listExecutions(params: ExecutionListParams = {}): Promise<ExecutionListResponse> {
|
||||||
try {
|
try {
|
||||||
const response = await this.client.get('/executions', { params });
|
const response = await this.client.get('/executions', { params });
|
||||||
return response.data;
|
const responseData = response.data;
|
||||||
|
|
||||||
|
// Validate response structure
|
||||||
|
if (!responseData || typeof responseData !== 'object') {
|
||||||
|
throw new Error('Invalid response from n8n API: response is not an object');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle case where response.data is an array (older n8n versions or different API format)
|
||||||
|
if (Array.isArray(responseData)) {
|
||||||
|
logger.warn('n8n API returned array directly instead of {data, nextCursor} object for executions. Wrapping in expected format.');
|
||||||
|
return {
|
||||||
|
data: responseData,
|
||||||
|
nextCursor: null
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate expected format {data: [], nextCursor?: string}
|
||||||
|
if (!Array.isArray(responseData.data)) {
|
||||||
|
throw new Error(`Invalid response from n8n API for executions: expected {data: [], nextCursor?: string}, got: ${JSON.stringify(Object.keys(responseData))}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return responseData;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw handleN8nApiError(error);
|
throw handleN8nApiError(error);
|
||||||
}
|
}
|
||||||
@@ -264,7 +306,28 @@ export class N8nApiClient {
|
|||||||
async listCredentials(params: CredentialListParams = {}): Promise<CredentialListResponse> {
|
async listCredentials(params: CredentialListParams = {}): Promise<CredentialListResponse> {
|
||||||
try {
|
try {
|
||||||
const response = await this.client.get('/credentials', { params });
|
const response = await this.client.get('/credentials', { params });
|
||||||
return response.data;
|
const responseData = response.data;
|
||||||
|
|
||||||
|
// Validate response structure
|
||||||
|
if (!responseData || typeof responseData !== 'object') {
|
||||||
|
throw new Error('Invalid response from n8n API: response is not an object');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle case where response.data is an array (older n8n versions or different API format)
|
||||||
|
if (Array.isArray(responseData)) {
|
||||||
|
logger.warn('n8n API returned array directly instead of {data, nextCursor} object for credentials. Wrapping in expected format.');
|
||||||
|
return {
|
||||||
|
data: responseData,
|
||||||
|
nextCursor: null
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate expected format {data: [], nextCursor?: string}
|
||||||
|
if (!Array.isArray(responseData.data)) {
|
||||||
|
throw new Error(`Invalid response from n8n API for credentials: expected {data: [], nextCursor?: string}, got: ${JSON.stringify(Object.keys(responseData))}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return responseData;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw handleN8nApiError(error);
|
throw handleN8nApiError(error);
|
||||||
}
|
}
|
||||||
@@ -309,7 +372,28 @@ export class N8nApiClient {
|
|||||||
async listTags(params: TagListParams = {}): Promise<TagListResponse> {
|
async listTags(params: TagListParams = {}): Promise<TagListResponse> {
|
||||||
try {
|
try {
|
||||||
const response = await this.client.get('/tags', { params });
|
const response = await this.client.get('/tags', { params });
|
||||||
return response.data;
|
const responseData = response.data;
|
||||||
|
|
||||||
|
// Validate response structure
|
||||||
|
if (!responseData || typeof responseData !== 'object') {
|
||||||
|
throw new Error('Invalid response from n8n API: response is not an object');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle case where response.data is an array (older n8n versions or different API format)
|
||||||
|
if (Array.isArray(responseData)) {
|
||||||
|
logger.warn('n8n API returned array directly instead of {data, nextCursor} object for tags. Wrapping in expected format.');
|
||||||
|
return {
|
||||||
|
data: responseData,
|
||||||
|
nextCursor: null
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate expected format {data: [], nextCursor?: string}
|
||||||
|
if (!Array.isArray(responseData.data)) {
|
||||||
|
throw new Error(`Invalid response from n8n API for tags: expected {data: [], nextCursor?: string}, got: ${JSON.stringify(Object.keys(responseData))}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return responseData;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw handleN8nApiError(error);
|
throw handleN8nApiError(error);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user