Skip to main content
Glama
aegntic

Obsidian Elite RAG MCP Server

n8n-api-client.ts11.2 kB
import axios, { AxiosInstance, AxiosRequestConfig, InternalAxiosRequestConfig } from 'axios'; import { logger } from '../utils/logger'; import { Workflow, WorkflowListParams, WorkflowListResponse, Execution, ExecutionListParams, ExecutionListResponse, Credential, CredentialListParams, CredentialListResponse, Tag, TagListParams, TagListResponse, HealthCheckResponse, Variable, WebhookRequest, WorkflowExport, WorkflowImport, SourceControlStatus, SourceControlPullResult, SourceControlPushResult, } from '../types/n8n-api'; import { handleN8nApiError, logN8nError } from '../utils/n8n-errors'; import { cleanWorkflowForCreate, cleanWorkflowForUpdate } from './n8n-validation'; export interface N8nApiClientConfig { baseUrl: string; apiKey: string; timeout?: number; maxRetries?: number; } export class N8nApiClient { private client: AxiosInstance; private maxRetries: number; constructor(config: N8nApiClientConfig) { const { baseUrl, apiKey, timeout = 30000, maxRetries = 3 } = config; this.maxRetries = maxRetries; // Ensure baseUrl ends with /api/v1 const apiUrl = baseUrl.endsWith('/api/v1') ? baseUrl : `${baseUrl.replace(/\/$/, '')}/api/v1`; this.client = axios.create({ baseURL: apiUrl, timeout, headers: { 'X-N8N-API-KEY': apiKey, 'Content-Type': 'application/json', }, }); // Request interceptor for logging this.client.interceptors.request.use( (config: InternalAxiosRequestConfig) => { logger.debug(`n8n API Request: ${config.method?.toUpperCase()} ${config.url}`, { params: config.params, data: config.data, }); return config; }, (error: unknown) => { logger.error('n8n API Request Error:', error); return Promise.reject(error); } ); // Response interceptor for logging this.client.interceptors.response.use( (response: any) => { logger.debug(`n8n API Response: ${response.status} ${response.config.url}`); return response; }, (error: unknown) => { const n8nError = handleN8nApiError(error); logN8nError(n8nError, 'n8n API Response'); return Promise.reject(n8nError); } ); } // Health check to verify API connectivity async healthCheck(): Promise<HealthCheckResponse> { try { // First try the health endpoint const response = await this.client.get('/health'); return response.data; } catch (error) { // If health endpoint doesn't exist, try listing workflows with limit 1 // This is a fallback for older n8n versions try { await this.client.get('/workflows', { params: { limit: 1 } }); return { status: 'ok', features: {} // We can't determine features without proper health endpoint }; } catch (fallbackError) { throw handleN8nApiError(fallbackError); } } } // Workflow Management async createWorkflow(workflow: Partial<Workflow>): Promise<Workflow> { try { const cleanedWorkflow = cleanWorkflowForCreate(workflow); const response = await this.client.post('/workflows', cleanedWorkflow); return response.data; } catch (error) { throw handleN8nApiError(error); } } async getWorkflow(id: string): Promise<Workflow> { try { const response = await this.client.get(`/workflows/${id}`); return response.data; } catch (error) { throw handleN8nApiError(error); } } async updateWorkflow(id: string, workflow: Partial<Workflow>): Promise<Workflow> { try { // First, try PUT method (newer n8n versions) const cleanedWorkflow = cleanWorkflowForUpdate(workflow as Workflow); try { const response = await this.client.put(`/workflows/${id}`, cleanedWorkflow); return response.data; } catch (putError: any) { // If PUT fails with 405 (Method Not Allowed), try PATCH if (putError.response?.status === 405) { logger.debug('PUT method not supported, falling back to PATCH'); const response = await this.client.patch(`/workflows/${id}`, cleanedWorkflow); return response.data; } throw putError; } } catch (error) { throw handleN8nApiError(error); } } async deleteWorkflow(id: string): Promise<void> { try { await this.client.delete(`/workflows/${id}`); } catch (error) { throw handleN8nApiError(error); } } async listWorkflows(params: WorkflowListParams = {}): Promise<WorkflowListResponse> { try { const response = await this.client.get('/workflows', { params }); return response.data; } catch (error) { throw handleN8nApiError(error); } } // Execution Management async getExecution(id: string, includeData = false): Promise<Execution> { try { const response = await this.client.get(`/executions/${id}`, { params: { includeData }, }); return response.data; } catch (error) { throw handleN8nApiError(error); } } async listExecutions(params: ExecutionListParams = {}): Promise<ExecutionListResponse> { try { const response = await this.client.get('/executions', { params }); return response.data; } catch (error) { throw handleN8nApiError(error); } } async deleteExecution(id: string): Promise<void> { try { await this.client.delete(`/executions/${id}`); } catch (error) { throw handleN8nApiError(error); } } // Webhook Execution async triggerWebhook(request: WebhookRequest): Promise<any> { try { const { webhookUrl, httpMethod, data, headers, waitForResponse = true } = request; // Extract path from webhook URL const url = new URL(webhookUrl); const webhookPath = url.pathname; // Make request directly to webhook endpoint const config: AxiosRequestConfig = { method: httpMethod, url: webhookPath, headers: { ...headers, // Don't override API key header for webhook endpoints 'X-N8N-API-KEY': undefined, }, data: httpMethod !== 'GET' ? data : undefined, params: httpMethod === 'GET' ? data : undefined, // Webhooks might take longer timeout: waitForResponse ? 120000 : 30000, }; // Create a new axios instance for webhook requests to avoid API interceptors const webhookClient = axios.create({ baseURL: new URL('/', webhookUrl).toString(), validateStatus: (status) => status < 500, // Don't throw on 4xx }); const response = await webhookClient.request(config); return { status: response.status, statusText: response.statusText, data: response.data, headers: response.headers, }; } catch (error) { throw handleN8nApiError(error); } } // Credential Management async listCredentials(params: CredentialListParams = {}): Promise<CredentialListResponse> { try { const response = await this.client.get('/credentials', { params }); return response.data; } catch (error) { throw handleN8nApiError(error); } } async getCredential(id: string): Promise<Credential> { try { const response = await this.client.get(`/credentials/${id}`); return response.data; } catch (error) { throw handleN8nApiError(error); } } async createCredential(credential: Partial<Credential>): Promise<Credential> { try { const response = await this.client.post('/credentials', credential); return response.data; } catch (error) { throw handleN8nApiError(error); } } async updateCredential(id: string, credential: Partial<Credential>): Promise<Credential> { try { const response = await this.client.patch(`/credentials/${id}`, credential); return response.data; } catch (error) { throw handleN8nApiError(error); } } async deleteCredential(id: string): Promise<void> { try { await this.client.delete(`/credentials/${id}`); } catch (error) { throw handleN8nApiError(error); } } // Tag Management async listTags(params: TagListParams = {}): Promise<TagListResponse> { try { const response = await this.client.get('/tags', { params }); return response.data; } catch (error) { throw handleN8nApiError(error); } } async createTag(tag: Partial<Tag>): Promise<Tag> { try { const response = await this.client.post('/tags', tag); return response.data; } catch (error) { throw handleN8nApiError(error); } } async updateTag(id: string, tag: Partial<Tag>): Promise<Tag> { try { const response = await this.client.patch(`/tags/${id}`, tag); return response.data; } catch (error) { throw handleN8nApiError(error); } } async deleteTag(id: string): Promise<void> { try { await this.client.delete(`/tags/${id}`); } catch (error) { throw handleN8nApiError(error); } } // Source Control Management (Enterprise feature) async getSourceControlStatus(): Promise<SourceControlStatus> { try { const response = await this.client.get('/source-control/status'); return response.data; } catch (error) { throw handleN8nApiError(error); } } async pullSourceControl(force = false): Promise<SourceControlPullResult> { try { const response = await this.client.post('/source-control/pull', { force }); return response.data; } catch (error) { throw handleN8nApiError(error); } } async pushSourceControl( message: string, fileNames?: string[] ): Promise<SourceControlPushResult> { try { const response = await this.client.post('/source-control/push', { message, fileNames, }); return response.data; } catch (error) { throw handleN8nApiError(error); } } // Variable Management (via Source Control API) async getVariables(): Promise<Variable[]> { try { const response = await this.client.get('/variables'); return response.data.data || []; } catch (error) { // Variables might not be available in all n8n versions logger.warn('Variables API not available, returning empty array'); return []; } } async createVariable(variable: Partial<Variable>): Promise<Variable> { try { const response = await this.client.post('/variables', variable); return response.data; } catch (error) { throw handleN8nApiError(error); } } async updateVariable(id: string, variable: Partial<Variable>): Promise<Variable> { try { const response = await this.client.patch(`/variables/${id}`, variable); return response.data; } catch (error) { throw handleN8nApiError(error); } } async deleteVariable(id: string): Promise<void> { try { await this.client.delete(`/variables/${id}`); } catch (error) { throw handleN8nApiError(error); } } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/aegntic/aegntic-MCP'

If you have feedback or need assistance with the MCP directory API, please join our Discord server